Skip to main content

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}