gpui_query/client/prepared_fetch.rs
1//! Prepared fetch type for imperative and prefetch query operations.
2//!
3//! [`PreparedFetch`] is returned by `QueryClient::prepare_fetch_query` and
4//! `QueryClient::prepare_prefetch_query`. It holds the entity, request ID,
5//! and cooperative cancellation signal needed to complete an async fetch.
6
7use gpui::{App, Entity};
8
9use crate::core::QueryResource;
10
11/// A prepared fetch returned by [`QueryClient::prepare_fetch_query`] or
12/// [`QueryClient::prepare_prefetch_query`].
13///
14/// Contains the entity, request ID, and cooperative cancellation signal
15/// needed to perform the async fetch and complete the resource.
16///
17/// The caller should:
18/// 1. Call their fetcher with `self.signal`
19/// 2. Use `complete_success` or `complete_failure` with the result
20///
21/// # Example
22///
23/// ```no_run
24/// use gpui_query::client::QueryClient;
25/// use gpui_query::core::QueryKey;
26/// # #[derive(Clone)]
27/// # struct Data;
28/// # #[derive(Clone, Debug)]
29/// # struct Error;
30/// # fn _doc(client: &mut QueryClient, cx: &mut gpui::App) {
31/// # let key = QueryKey::from("data");
32///
33/// let prepared = client.prepare_fetch_query::<Data, Error>(key, cx).unwrap();
34/// let signal = prepared.signal.clone();
35/// // Use cx.spawn() to run your async fetcher with the signal, then call
36/// // prepared.complete_success(data, cx) or prepared.complete_failure(e, cx).
37/// # }
38/// ```
39#[must_use = "the prepared fetch holds the request ID and cancellation signal; dropping it without calling complete_success/complete_failure abandons the in-flight request"]
40pub struct PreparedFetch<T, E> {
41 /// The query resource entity.
42 pub entity: Entity<QueryResource<T, E>>,
43 /// The request ID for the started request.
44 pub request_id: crate::core::RequestId,
45 /// The cooperative cancellation signal for the in-flight request.
46 pub signal: crate::core::QuerySignal,
47 /// **M3**: the wall-clock ms captured at prepare time. Reused by
48 /// `complete_success` / `complete_failure` so they don't re-syscall
49 /// `current_time_ms()` (the fetch's logical completion time is the prepare
50 /// time, matching the request's `started_at`).
51 pub(crate) now_ms: u64,
52}
53
54impl<T: Clone + Send + Sync + 'static, E: Clone + Send + Sync + 'static> PreparedFetch<T, E> {
55 /// Complete the fetch with success.
56 ///
57 /// Calls `complete_current_success` on the resource entity. If the request
58 /// ID is no longer active (replaced by a newer request), this is a no-op.
59 ///
60 /// **M3**: reuses the `now_ms` captured at prepare time instead of
61 /// re-syscalling `current_time_ms()`.
62 pub fn complete_success(self, data: T, cx: &mut App) {
63 self.entity.update(cx, |resource, cx| {
64 let accepted = resource.complete_current_success(self.request_id, data, self.now_ms);
65 // B2: precise dirty signal for the persistence layer. Imperative
66 // completions (`prepare_fetch_query`) mutate the cache just like the
67 // hook-layer completions, so they must wake `persist_with` too —
68 // without this a resolved imperative fetch is silently never saved.
69 // Gated on `accepted` to match the hook sites (which bump inside the
70 // `accept_current_request` guard), so a stale no-op completion does
71 // not spuriously schedule a save.
72 if accepted {
73 #[cfg(feature = "persist")]
74 cx.default_global::<crate::client::CacheMutation>();
75 }
76 });
77 }
78
79 /// Complete the fetch with failure.
80 ///
81 /// Calls `complete_current_failure` on the resource entity. If the request
82 /// ID is no longer active (replaced by a newer request), this is a no-op.
83 ///
84 /// **M3**: reuses the `now_ms` captured at prepare time instead of
85 /// re-syscalling `current_time_ms()`.
86 pub fn complete_failure(self, error: E, cx: &mut App) {
87 self.entity.update(cx, |resource, cx| {
88 let accepted = resource.complete_current_failure(self.request_id, error, self.now_ms);
89 // B2: see `complete_success` — bump only when the failure was
90 // actually accepted, so an imperative failure is visible to
91 // `persist_with` without spurious saves on stale completions.
92 if accepted {
93 #[cfg(feature = "persist")]
94 cx.default_global::<crate::client::CacheMutation>();
95 }
96 });
97 }
98}