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}