leptos_query_rs/query/
mod.rs1use leptos::prelude::*;
6use leptos::task::spawn_local;
7use std::time::Duration;
8use std::future::Future;
9use serde::{Serialize, de::DeserializeOwned};
10
11use crate::client::QueryClient;
12use crate::retry::{QueryError, RetryConfig, execute_with_retry};
13use crate::types::{QueryStatus, QueryKey};
14
15#[derive(Clone)]
17pub struct QueryOptions {
18 pub enabled: bool,
20 pub stale_time: Duration,
22 pub cache_time: Duration,
24 pub refetch_interval: Option<Duration>,
26 pub retry: RetryConfig,
28}
29
30impl Default for QueryOptions {
31 fn default() -> Self {
32 Self {
33 enabled: true,
34 stale_time: Duration::from_secs(0),
35 cache_time: Duration::from_secs(5 * 60), refetch_interval: None,
37 retry: RetryConfig::default(),
38 }
39 }
40}
41
42impl QueryOptions {
43 pub fn with_stale_time(mut self, duration: Duration) -> Self {
45 self.stale_time = duration;
46 self
47 }
48
49 pub fn with_cache_time(mut self, duration: Duration) -> Self {
51 self.cache_time = duration;
52 self
53 }
54
55 pub fn with_refetch_interval(mut self, interval: Duration) -> Self {
57 self.refetch_interval = Some(interval);
58 self
59 }
60
61 pub fn with_retry(mut self, retry: RetryConfig) -> Self {
63 self.retry = retry;
64 self
65 }
66
67 pub fn disabled(mut self) -> Self {
69 self.enabled = false;
70 self
71 }
72}
73
74#[derive(Clone)]
76pub struct QueryResult<T: 'static + Send + Sync> {
77 pub data: Signal<Option<T>>,
79 pub error: Signal<Option<QueryError>>,
81 pub is_loading: Signal<bool>,
83 pub is_success: Signal<bool>,
85 pub is_error: Signal<bool>,
87 pub status: Signal<QueryStatus>,
89
90 pub refetch: Callback<()>,
93}
94
95pub fn use_query<T, F, Fut>(
97 key_fn: F,
98 query_fn: impl Fn() -> Fut + Clone + Send + Sync + 'static,
99 options: QueryOptions,
100) -> QueryResult<T>
101where
102 T: Clone + Send + Sync + Serialize + DeserializeOwned + 'static,
103 F: Fn() -> QueryKey + Clone + Send + Sync + 'static,
104 Fut: Future<Output = Result<T, QueryError>> + 'static,
105{
106 let (data, set_data) = signal(None::<T>);
108 let (error, set_error) = signal(None::<QueryError>);
109 let (is_loading, set_loading) = signal(true);
110 let (status, set_status) = signal(QueryStatus::Loading);
111
112 let client = use_context::<QueryClient>().expect("QueryClient not found in context");
114
115 let key = Memo::new(move |_| key_fn());
117
118 let initial_fetch = {
120 let client = client.clone();
121 let query_fn = query_fn.clone();
122 let options = options.clone();
123
124 move |force: bool| {
125 let client = client.clone();
126 let query_fn = query_fn.clone();
127 let options = options.clone();
128
129 spawn_local(async move {
130 let current_key = key.get();
131
132 if let Some(cache_entry) = client.get_cache_entry(¤t_key) {
134 if !force && !cache_entry.is_stale() {
135 if let Ok(cached_data) = cache_entry.get_data::<T>() {
137 set_data.set(Some(cached_data));
138 set_loading.set(false);
139 set_status.set(QueryStatus::Success);
140 return;
141 }
142 }
143 }
144
145 set_status.set(QueryStatus::Loading);
147
148 let result = execute_with_retry(
149 &query_fn,
150 &options.retry,
151 ).await;
152
153 match result {
154 Ok(result_data) => {
155 if let Ok(()) = client.set_query_data(¤t_key, result_data.clone()) {
157 set_data.set(Some(result_data));
158 set_error.set(None);
159 set_status.set(QueryStatus::Success);
160 }
161 }
162 Err(err) => {
163 set_error.set(Some(err.clone()));
164 set_status.set(QueryStatus::Error);
165 }
166 }
167
168 set_loading.set(false);
169 });
170 }
171 };
172
173 let refetch_fn = {
175 let client = client.clone();
176 let query_fn = query_fn.clone();
177 let options = options.clone();
178
179 move |force: bool| {
180 let client = client.clone();
181 let query_fn = query_fn.clone();
182 let options = options.clone();
183
184 spawn_local(async move {
185 let current_key = key.get();
186
187 if let Some(cache_entry) = client.get_cache_entry(¤t_key) {
189 if !force && !cache_entry.is_stale() {
190 if let Ok(cached_data) = cache_entry.get_data::<T>() {
192 set_data.set(Some(cached_data));
193 set_loading.set(false);
194 set_status.set(QueryStatus::Success);
195 return;
196 }
197 }
198 }
199
200 set_status.set(QueryStatus::Loading);
202
203 let result = execute_with_retry(
204 &query_fn,
205 &options.retry,
206 ).await;
207
208 match result {
209 Ok(result_data) => {
210 if let Ok(()) = client.set_query_data(¤t_key, result_data.clone()) {
212 set_data.set(Some(result_data));
213 set_error.set(None);
214 set_status.set(QueryStatus::Success);
215 }
216 }
217 Err(err) => {
218 set_error.set(Some(err.clone()));
219 set_status.set(QueryStatus::Error);
220 }
221 }
222
223 set_loading.set(false);
224 });
225 }
226 };
227
228 Effect::new(move |_| {
230 if options.enabled {
231 let current_key = key.get();
232
233 if let Some(cache_entry) = client.get_cache_entry(¤t_key) {
235 if !cache_entry.is_stale() {
236 if let Ok(cached_data) = cache_entry.get_data::<T>() {
238 set_data.set(Some(cached_data));
239 set_loading.set(false);
240 set_status.set(QueryStatus::Success);
241 }
242 } else {
243 initial_fetch(false);
245 }
246 } else {
247 initial_fetch(false);
249 }
250 }
251 });
252
253 let is_success = Memo::new(move |_| status.get() == QueryStatus::Success);
255 let is_error = Memo::new(move |_| status.get() == QueryStatus::Error);
256
257 QueryResult {
259 data: data.into(),
260 error: error.into(),
261 is_loading: is_loading.into(),
262 is_success: is_success.into(),
263 is_error: is_error.into(),
264 status: status.into(),
265 refetch: Callback::new(move |_| refetch_fn(true)),
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn test_query_options_builder() {
275 let options = QueryOptions::default()
276 .with_stale_time(Duration::from_secs(60))
277 .with_cache_time(Duration::from_secs(300))
278 .disabled();
279
280 assert_eq!(options.stale_time, Duration::from_secs(60));
281 assert_eq!(options.cache_time, Duration::from_secs(300));
282 assert!(!options.enabled);
283 }
284}