leptos_query_rs/infinite/
mod.rs

1use crate::{
2    client::QueryClient,
3    types::QueryKey,
4    retry::RetryConfig,
5    QueryError,
6};
7use leptos::prelude::*;
8use leptos::task::spawn_local;
9use serde::{de::DeserializeOwned, Serialize, Deserialize};
10use std::{sync::Arc, future::Future};
11use crate::QueryObserverId;
12
13/// Configuration for infinite queries
14#[derive(Clone, Debug)]
15pub struct InfiniteQueryOptions {
16    /// Retry configuration for failed requests
17    pub retry: RetryConfig,
18    /// Whether to keep previous pages when fetching new ones
19    pub keep_previous_data: bool,
20    /// Maximum number of pages to keep in memory
21    pub max_pages: Option<usize>,
22    /// Whether to refetch when window regains focus
23    pub refetch_on_window_focus: bool,
24    /// Whether to refetch when reconnecting to the internet
25    pub refetch_on_reconnect: bool,
26}
27
28impl Default for InfiniteQueryOptions {
29    fn default() -> Self {
30        Self {
31            retry: RetryConfig::default(),
32            keep_previous_data: true,
33            max_pages: Some(10),
34            refetch_on_window_focus: true,
35            refetch_on_reconnect: true,
36        }
37    }
38}
39
40/// Builder for infinite query options
41#[derive(Clone)]
42pub struct InfiniteQueryOptionsBuilder {
43    options: InfiniteQueryOptions,
44}
45
46impl Default for InfiniteQueryOptionsBuilder {
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52impl InfiniteQueryOptionsBuilder {
53    pub fn new() -> Self {
54        Self {
55            options: InfiniteQueryOptions::default(),
56        }
57    }
58
59    pub fn retry(mut self, retry: RetryConfig) -> Self {
60        self.options.retry = retry;
61        self
62    }
63
64    pub fn keep_previous_data(mut self, keep: bool) -> Self {
65        self.options.keep_previous_data = keep;
66        self
67    }
68
69    pub fn max_pages(mut self, max: Option<usize>) -> Self {
70        self.options.max_pages = max;
71        self
72    }
73
74    pub fn refetch_on_window_focus(mut self, refetch: bool) -> Self {
75        self.options.refetch_on_window_focus = refetch;
76        self
77    }
78
79    pub fn refetch_on_reconnect(mut self, refetch: bool) -> Self {
80        self.options.refetch_on_reconnect = refetch;
81        self
82    }
83
84    pub fn build(self) -> InfiniteQueryOptions {
85        self.options
86    }
87}
88
89/// Page information for infinite queries
90#[derive(Clone, Debug, Serialize, Deserialize)]
91pub struct PageInfo {
92    /// Current page number
93    pub page: usize,
94    /// Items per page
95    pub per_page: usize,
96    /// Total number of items
97    pub total: usize,
98    /// Whether there are more pages
99    pub has_next: bool,
100    /// Whether there are previous pages
101    pub has_prev: bool,
102}
103
104/// A single page of data
105#[derive(Clone, Debug, Serialize, Deserialize)]
106pub struct Page<T> {
107    /// Page data
108    pub data: Vec<T>,
109    /// Page metadata
110    pub info: PageInfo,
111}
112
113/// Infinite query result with pagination support
114#[derive(Clone)]
115pub struct InfiniteQueryResult<T> {
116    /// All pages of data
117    pub pages: RwSignal<Vec<Page<T>>>,
118    /// Current page number
119    pub current_page: RwSignal<usize>,
120    /// Whether more data can be loaded
121    pub has_next: RwSignal<bool>,
122    /// Whether previous data exists
123    pub has_prev: RwSignal<bool>,
124    /// Loading state
125    pub is_loading: RwSignal<bool>,
126    /// Error state
127    pub error: RwSignal<Option<QueryError>>,
128    /// Whether data is stale
129    pub is_stale: RwSignal<bool>,
130    /// Whether currently fetching
131    pub is_fetching: RwSignal<bool>,
132    /// Query key
133    pub key: QueryKey,
134    /// Observer ID
135    pub observer_id: QueryObserverId,
136    /// Client reference
137    client: Arc<QueryClient>,
138}
139
140impl<T> InfiniteQueryResult<T>
141where
142    T: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
143{
144    /// Get the next page of data
145    pub async fn fetch_next_page(&self) -> Result<(), QueryError> {
146        let current_page = self.current_page.get();
147        let has_next = self.has_next.get();
148        
149        if !has_next {
150            return Ok(());
151        }
152
153        // Update loading state
154        self.is_loading.set(true);
155        
156        // Fetch next page
157        let next_page = current_page + 1;
158        let result = self
159            .client
160            .fetch_infinite_page::<T>(&self.key, next_page)
161            .await?;
162
163        // Update pages
164        let result_clone = result.clone();
165        self.pages.update(|pages| {
166            if let Some(max_pages) = self.client.get_infinite_options(&self.key).max_pages {
167                if pages.len() >= max_pages {
168                    pages.remove(0); // Remove oldest page
169                }
170            }
171            pages.push(result_clone);
172        });
173
174        // Update current page and has_next
175        self.current_page.set(next_page);
176        self.has_next.set(result.info.has_next);
177
178        self.is_loading.set(false);
179        Ok(())
180    }
181
182    /// Get the previous page of data
183    pub async fn fetch_previous_page(&self) -> Result<(), QueryError> {
184        let current_page = self.current_page.get();
185        let has_prev = self.has_prev.get();
186        
187        if !has_prev {
188            return Ok(());
189        }
190
191        // Update loading state
192        self.is_loading.set(true);
193        
194        // Fetch previous page
195        let prev_page = current_page.saturating_sub(1);
196        let result = self
197            .client
198            .fetch_infinite_page::<T>(&self.key, prev_page)
199            .await?;
200
201        // Update pages
202        let result_clone = result.clone();
203        self.pages.update(|pages| {
204            pages.insert(0, result_clone);
205            
206            if let Some(max_pages) = self.client.get_infinite_options(&self.key).max_pages {
207                if pages.len() > max_pages {
208                    pages.pop(); // Remove newest page
209                }
210            }
211        });
212
213        // Update current page and has_prev
214        self.current_page.set(prev_page);
215        self.has_prev.set(result.info.has_prev);
216
217        self.is_loading.set(false);
218        Ok(())
219    }
220
221    /// Refetch all pages
222    pub async fn refetch(&self) -> Result<(), QueryError> {
223        self.is_fetching.set(true);
224        
225        // Clear existing pages
226        self.pages.set(Vec::new());
227        self.current_page.set(0);
228        self.has_next.set(true);
229        self.has_prev.set(false);
230        
231        // Fetch first page
232        let result = self
233            .client
234            .fetch_infinite_page::<T>(&self.key, 0)
235            .await?;
236
237        // Update state
238        let result_clone = result.clone();
239        self.pages.set(vec![result_clone]);
240        self.has_next.set(result.info.has_next);
241        self.is_stale.set(false);
242        self.is_fetching.set(false);
243        
244        Ok(())
245    }
246
247    /// Invalidate and refetch
248    pub async fn invalidate(&self) -> Result<(), QueryError> {
249        // TODO: Implement invalidation when the method is available
250        self.refetch().await
251    }
252
253    /// Remove all pages from cache
254    pub async fn remove(&self) -> Result<(), QueryError> {
255        self.client.remove_query(&self.key);
256        self.pages.set(Vec::new());
257        self.current_page.set(0);
258        self.has_next.set(true);
259        self.has_prev.set(false);
260        Ok(())
261    }
262
263    /// Get all data from all pages as a flat vector
264    pub fn get_all_data(&self) -> Vec<T> {
265        self.pages
266            .get()
267            .iter()
268            .flat_map(|page| page.data.clone())
269            .collect()
270    }
271
272    /// Get data from a specific page
273    pub fn get_page_data(&self, page: usize) -> Option<Vec<T>> {
274        self.pages
275            .get()
276            .get(page)
277            .map(|page| page.data.clone())
278    }
279
280    /// Get the total number of items across all pages
281    pub fn get_total_count(&self) -> usize {
282        self.pages
283            .get()
284            .iter()
285            .map(|page| page.info.total)
286            .sum()
287    }
288}
289
290/// Hook for infinite queries with pagination
291pub fn use_infinite_query<T, K, F, Fut>(
292    key_fn: impl Fn() -> K + 'static,
293    query_fn: impl Fn(usize) -> F + Clone + Send + Sync + 'static,
294    _options: InfiniteQueryOptions,
295) -> InfiniteQueryResult<T>
296where
297    T: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
298    K: Into<QueryKey>,
299    F: Future<Output = Result<Page<T>, QueryError>> + Send + 'static,
300{
301    let client = use_context::<Arc<QueryClient>>()
302        .expect("use_infinite_query must be used within QueryClientProvider");
303
304    let key = key_fn().into();
305    let observer_id = client.register_infinite_observer(&key);
306
307    // Create signals for state management
308    let pages = RwSignal::new(Vec::new());
309    let current_page = RwSignal::new(0);
310    let has_next = RwSignal::new(true);
311    let has_prev = RwSignal::new(false);
312    let is_loading = RwSignal::new(false);
313    let error = RwSignal::new(None);
314    let is_stale = RwSignal::new(false);
315    let is_fetching = RwSignal::new(false);
316
317    // Initial fetch
318    spawn_local(async move {
319        is_loading.set(true);
320        
321        match query_fn(0).await {
322            Ok(page) => {
323                let page_clone = page.clone();
324                pages.set(vec![page_clone]);
325                has_next.set(page.info.has_next);
326                is_stale.set(false);
327            }
328            Err(e) => {
329                error.set(Some(e));
330            }
331        }
332        
333        is_loading.set(false);
334    });
335
336    InfiniteQueryResult {
337        pages,
338        current_page,
339        has_next,
340        has_prev,
341        is_loading,
342        error,
343        is_stale,
344        is_fetching,
345        key,
346        observer_id,
347        client,
348    }
349}
350
351/// Builder pattern for infinite query options
352impl InfiniteQueryOptions {
353    pub fn builder() -> InfiniteQueryOptionsBuilder {
354        InfiniteQueryOptionsBuilder::new()
355    }
356}
357
358#[cfg(test)]
359mod tests {
360    use super::*;
361
362
363    #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
364    struct TestItem {
365        id: usize,
366        name: String,
367    }
368
369    // Mock function removed to eliminate warnings
370
371    #[test]
372    fn test_infinite_query_options_builder() {
373        let options = InfiniteQueryOptions::builder()
374            .retry(RetryConfig::default())
375            .keep_previous_data(false)
376            .max_pages(Some(5))
377            .build();
378
379        assert_eq!(options.keep_previous_data, false);
380        assert_eq!(options.max_pages, Some(5));
381    }
382
383    #[test]
384    fn test_page_info() {
385        let info = PageInfo {
386            page: 1,
387            per_page: 10,
388            total: 100,
389            has_next: true,
390            has_prev: true,
391        };
392
393        assert_eq!(info.page, 1);
394        assert_eq!(info.per_page, 10);
395        assert_eq!(info.total, 100);
396        assert!(info.has_next);
397        assert!(info.has_prev);
398    }
399}