Skip to main content

gpui_query/core/infinite_query/
resource.rs

1//! Struct definition, serde helpers, constants, and constructors for
2//! [`InfiniteQueryResource`].
3
4use std::collections::VecDeque;
5use std::sync::Arc;
6
7use serde::{Deserialize, Serialize};
8
9use crate::core::{
10    CachePolicy, QueryError, QueryKey, QuerySignal, QueryStatus, QueryTimestamp, RequestId,
11    RequestPolicy, RequestSequencer, RetryPolicy,
12};
13
14/// Default maximum number of pages to retain.
15const DEFAULT_MAX_PAGES: usize = 50;
16
17/// Direction mode for an infinite query.
18///
19/// Controls the default assumptions for `has_next_page` and
20/// `has_previous_page` on construction and after `reset()`.
21///
22/// - **ForwardOnly** (default): `has_next_page` starts `true`, `has_previous_page` starts `false`.
23///   This is the common case for feed-style pagination where you only fetch next pages.
24///   The `true` default for `has_next_page` assumes more pages exist until the fetcher says
25///   otherwise.
26///
27/// - **Bidirectional**: Both `has_next_page` and `has_previous_page` start `false`.
28///   The query will not attempt to fetch in either direction until the caller explicitly
29///   sets `has_next_page(true)` or `has_previous_page(true)`, or the fetcher returns
30///   `has_more = true` from a successful completion.
31#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
32pub enum FetchDirection {
33    /// Fetch next pages only. `has_next_page` defaults to `true`.
34    #[default]
35    ForwardOnly,
36    /// Fetch in both directions. Both flags default to `false`.
37    Bidirectional,
38}
39
40/// An infinite query resource that manages paginated data.
41///
42/// Inspired by TanStack Query's `useInfiniteQuery`. Each "page" is a `T` —
43/// typically a batch of items fetched from an API.
44///
45/// Pages are stored internally as `Arc<T>` so that [`last_page_arc`](Self::last_page_arc)
46/// and [`first_page_arc`](Self::first_page_arc) can hand the fetcher a cheap
47/// `Arc::clone` instead of cloning the full page data.
48#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(bound(serialize = "T: serde::Serialize, E: serde::Serialize"))]
50#[serde(bound(deserialize = "T: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned"))]
51pub struct InfiniteQueryResource<T, E = QueryError> {
52    pub(super) key: QueryKey,
53    #[serde(with = "vec_deque_serde")]
54    pub(super) pages: VecDeque<Arc<T>>,
55    pub(super) status: QueryStatus,
56    pub(super) error: Option<E>,
57    pub(super) active_request_id: Option<RequestId>,
58    pub(super) cache_policy: CachePolicy,
59    pub(super) request_policy: RequestPolicy,
60    pub(super) started_at: Option<QueryTimestamp>,
61    pub(super) last_updated_at: Option<QueryTimestamp>,
62    pub(super) cache_hits: u64,
63    pub(super) cancelled_count: u64,
64    pub(super) ignored_results: u64,
65    pub(super) retry_count: u32,
66    pub(super) has_next_page: bool,
67    pub(super) has_previous_page: bool,
68    /// Which direction (if any) is currently being fetched.
69    ///
70    /// Collapses the previous `is_fetching_next_page` / `is_fetching_previous_page`
71    /// pair into a single `Option<PageDirection>`, making the mutual-exclusion
72    /// invariant unbreakable at the type level (N18). The two boolean getters
73    /// are retained for backwards compatibility.
74    pub(super) fetching_direction: Option<super::lifecycle::PageDirection>,
75    pub(super) max_pages: Option<usize>,
76    pub(super) direction: FetchDirection,
77    pub(super) retry_policy: RetryPolicy,
78    /// Per-resource sequencer used by [`begin_fetch`](Self::begin_fetch_next)
79    /// when no external id is supplied, so transient callers without a
80    /// `QueryClient` still get monotonic, collision-free ids instead of every
81    /// call colliding at `RequestId(1,1)` (N3). `#[serde(skip)]` — runtime
82    /// state, not persisted.
83    #[serde(skip)]
84    pub(super) transient_sequencer: RequestSequencer,
85    #[serde(skip)]
86    pub(super) signal: Option<QuerySignal>,
87    #[cfg(feature = "client")]
88    #[serde(skip)]
89    pub(crate) current_task: crate::core::current_task::CurrentTask,
90}
91
92/// Serde helpers for `VecDeque<Arc<T>>` — serializes as a plain sequence and
93/// deserializes into `VecDeque<Arc<T>>`. This keeps the wire format identical
94/// to the old `Vec<T>` representation so existing cached data remains
95/// compatible (`Arc<T>` serializes transparently as `T`).
96pub(super) mod vec_deque_serde {
97    use std::collections::VecDeque;
98    use std::sync::Arc;
99
100    use serde::de::{Deserialize, DeserializeOwned};
101    use serde::ser::SerializeSeq;
102    use serde::{Deserializer, Serializer};
103
104    pub fn serialize<S, T>(deque: &VecDeque<Arc<T>>, serializer: S) -> Result<S::Ok, S::Error>
105    where
106        S: Serializer,
107        T: serde::Serialize,
108    {
109        let mut seq = serializer.serialize_seq(Some(deque.len()))?;
110        for item in deque {
111            // Serialize the inner `T` directly (`&**item`) rather than the
112            // `Arc<T>`. This avoids requiring `Arc<T>: Serialize` (which is only
113            // available with serde's `rc` feature / certain configs) and keeps
114            // the wire format identical to the old `Vec<T>` representation.
115            seq.serialize_element(&**item)?;
116        }
117        seq.end()
118    }
119
120    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<VecDeque<Arc<T>>, D::Error>
121    where
122        D: Deserializer<'de>,
123        T: DeserializeOwned,
124    {
125        let vec: Vec<T> = Vec::<T>::deserialize(deserializer)?;
126        Ok(vec.into_iter().map(Arc::new).collect())
127    }
128}
129
130impl<T, E> InfiniteQueryResource<T, E> {
131    /// Create a new infinite query resource.
132    ///
133    /// **v2**: `max_pages` defaults to `Some(50)` to prevent unbounded memory growth.
134    ///
135    /// **Audit 3**: Uses `FetchDirection::ForwardOnly` by default, meaning
136    /// `has_next_page` starts `true`. Use [`new_bidirectional`](Self::new_bidirectional)
137    /// for queries that paginate in both directions.
138    pub fn new(
139        key: impl Into<QueryKey>,
140        cache_policy: CachePolicy,
141        request_policy: RequestPolicy,
142    ) -> Self {
143        Self::with_direction(
144            key,
145            cache_policy,
146            request_policy,
147            FetchDirection::ForwardOnly,
148        )
149    }
150
151    /// Create a new infinite query resource configured for bidirectional paging.
152    ///
153    /// Both `has_next_page` and `has_previous_page` default to `false`. The
154    /// query will not attempt to fetch in either direction until the caller
155    /// explicitly enables it.
156    pub fn new_bidirectional(
157        key: impl Into<QueryKey>,
158        cache_policy: CachePolicy,
159        request_policy: RequestPolicy,
160    ) -> Self {
161        Self::with_direction(
162            key,
163            cache_policy,
164            request_policy,
165            FetchDirection::Bidirectional,
166        )
167    }
168
169    /// Create a new infinite query resource with an explicit [`FetchDirection`].
170    pub(crate) fn with_direction(
171        key: impl Into<QueryKey>,
172        cache_policy: CachePolicy,
173        request_policy: RequestPolicy,
174        direction: FetchDirection,
175    ) -> Self {
176        let (has_next, has_prev) = match direction {
177            FetchDirection::ForwardOnly => (true, false),
178            FetchDirection::Bidirectional => (false, false),
179        };
180        Self {
181            key: key.into(),
182            pages: VecDeque::new(),
183            status: QueryStatus::Idle,
184            error: None,
185            active_request_id: None,
186            cache_policy,
187            request_policy,
188            started_at: None,
189            last_updated_at: None,
190            cache_hits: 0,
191            cancelled_count: 0,
192            ignored_results: 0,
193            retry_count: 0,
194            has_next_page: has_next,
195            has_previous_page: has_prev,
196            fetching_direction: None,
197            max_pages: Some(DEFAULT_MAX_PAGES),
198            direction,
199            retry_policy: RetryPolicy::default(),
200            transient_sequencer: RequestSequencer::new(),
201            signal: None,
202            #[cfg(feature = "client")]
203            current_task: crate::core::current_task::CurrentTask::default(),
204        }
205    }
206}
207
208#[cfg(feature = "client")]
209impl<T, E> InfiniteQueryResource<T, E> {
210    /// Store a new background task, cancelling any previously stored task.
211    pub(crate) fn set_current_task(&mut self, task: gpui::Task<()>) {
212        self.current_task.set(task);
213    }
214}