leptos_fetch/
query_scope.rs

1use paste::paste;
2use std::{
3    any::TypeId,
4    fmt::{self, Debug, Formatter},
5    future::Future,
6    hash::{DefaultHasher, Hash, Hasher},
7    pin::Pin,
8    sync::Arc,
9};
10
11use crate::QueryOptions;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct ScopeCacheKey(u64);
15
16impl ScopeCacheKey {
17    pub fn new(fetcher_type_id: TypeId, options: &QueryOptions) -> Self {
18        let mut hasher = DefaultHasher::new();
19        fetcher_type_id.hash(&mut hasher);
20        options.hash(&mut hasher);
21        Self(hasher.finish())
22    }
23}
24
25impl Hash for ScopeCacheKey {
26    fn hash<H: Hasher>(&self, state: &mut H) {
27        self.0.hash(state);
28    }
29}
30
31#[cfg(any(
32    all(debug_assertions, feature = "devtools"),
33    feature = "devtools-always"
34))]
35#[track_caller]
36fn format_title(base: &str) -> Arc<String> {
37    let loc = std::panic::Location::caller();
38    let filepath = loc.file();
39    let file = format!(
40        "{}:{}:{}",
41        // Only want the final file, not the full path:
42        filepath
43            .split(std::path::MAIN_SEPARATOR_STR)
44            .last()
45            .unwrap_or(filepath),
46        loc.line(),
47        loc.column()
48    );
49    Arc::new(format!(
50        "{}: {}",
51        file,
52        base.trim_end_matches("::{{closure}}")
53    ))
54}
55
56/// A marker struct to allow query function with or without a key.
57///
58/// Ignore.
59pub struct QueryMarkerWithKey;
60
61/// A marker struct to allow query function with or without a key.
62///
63/// Ignore.
64pub struct QueryMarkerNoKey;
65
66macro_rules! define {
67    ([$($impl_fut_generics:tt)*], [$($impl_fn_generics:tt)*], $name:ident, $sname:literal, $sthread:literal) => {
68        /// A
69        #[doc = $sthread]
70        /// wrapper for a query function. This can be used to add specific [`QueryOptions`] to only apply to one query scope.
71        ///
72        /// These [`QueryOptions`] will be combined with the global [`QueryOptions`] set on the [`crate::QueryClient`], with the local options taking precedence.
73        ///
74        /// If you don't need to set specific options, you can use functions with the [`crate::QueryClient`] directly.
75        #[derive(Clone)]
76        pub struct $name<K, V> {
77            query: Arc<dyn Fn(K) -> Pin<Box<dyn Future<Output = V> $($impl_fut_generics)*>> $($impl_fn_generics)*>,
78            invalidation_hierarchy_fn: Option<Arc<dyn Fn(&K) -> Vec<String> + Send + Sync>>,
79            fetcher_type_id: TypeId,
80            cache_key: ScopeCacheKey,
81            options: QueryOptions,
82            #[cfg(any(
83                all(debug_assertions, feature = "devtools"),
84                feature = "devtools-always"
85            ))]
86            title: Arc<String>,
87        }
88
89        impl<K, V> Debug for $name<K, V> {
90            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
91                f.debug_struct(stringify!($name))
92                    .field("query", &"Arc<dyn Fn(K) -> Pin<Box<dyn Future<Output = V>>")
93                    .field("options", &self.options)
94                    .finish()
95            }
96        }
97
98        paste! {
99            impl<K, V> $name<K, V> {
100                /// Create a new
101                #[doc = $sname]
102                ///.
103                /// If the query fn does not have a key argument, `K=()`
104                ///
105                #[track_caller]
106                pub fn new<M>(
107                    query_scope: impl [<$name Trait>]<K, V, M> $($impl_fn_generics)* + 'static,
108                ) -> Self
109                where
110                    K: 'static $($impl_fn_generics)*,
111                    V: 'static $($impl_fn_generics)*,
112                {
113                    let options = query_scope.options().unwrap_or_default();
114                    let fetcher_type_id = query_scope.fetcher_type_id();
115                    Self {
116                        fetcher_type_id,
117                        cache_key: ScopeCacheKey::new(fetcher_type_id, &options),
118                        options,
119                        #[cfg(any(
120                            all(debug_assertions, feature = "devtools"),
121                            feature = "devtools-always"
122                        ))]
123                        title: query_scope.title(),
124                        invalidation_hierarchy_fn: None,
125                        query: Arc::new(move |key| Box::pin(query_scope.query(key))),
126                    }
127                }
128
129                /// Set specific [`QueryOptions`] to only apply to this query scope.
130                ///
131                /// These [`QueryOptions`] will be combined with the global [`QueryOptions`] set on the [`crate::QueryClient`], with the local options taking precedence.
132                pub fn with_options(mut self, options: QueryOptions) -> Self {
133                    self.options = options;
134                    self
135                }
136
137                /// Different query types are sometimes linked to the same source, e.g. you may want an invalidation of `list_blogposts()` to always automatically invalidate `get_blogpost(id)`.
138                ///
139                /// [`QueryScope::with_invalidation_link`](https://docs.rs/leptos-fetch/latest/leptos_fetch/struct.QueryScope.html#method.subscribe_is_fetching::with_invalidation_link) can be used to this effect, given a query key `&K`, you provide a `Vec<String>` that's used as a **hierarchy key (HK)** for that query. When a query is invalidated, any query's **HK** that's prefixed by this **HK** will also be invalidated automatically. E.g. A query with **HK** `["users"]` will also auto invalidate another query with `["users", "1"]`, but not the other way around. 2 queries with an identicial **HK** of `["users"]` will auto invalidate each other.
140                ///
141                /// ```rust
142                /// use std::time::Duration;
143                ///
144                /// use leptos_fetch::{QueryClient, QueryScope, QueryOptions};
145                /// use leptos::prelude::*;
146                ///
147                /// #[derive(Debug, Clone)]
148                /// struct User;
149                ///
150                /// fn list_users_query() -> QueryScope<(), Vec<User>> {
151                ///     QueryScope::new(async || vec![])
152                ///         .with_invalidation_link(
153                ///             |_key| ["users"]
154                ///         )
155                /// }
156                ///
157                /// fn get_user_query() -> QueryScope<i32, User> {
158                ///     QueryScope::new(async move |user_id: i32| User)
159                ///         .with_invalidation_link(
160                ///             |user_id| ["users".to_string(), user_id.to_string()]
161                ///         )
162                /// }
163                ///
164                /// let client = QueryClient::new();
165                ///
166                /// // This invalidates only user "2", because ["users", "2"] is not a prefix of ["users"],
167                /// // list_users_query is NOT invalidated.
168                /// client.invalidate_query(get_user_query(), &2);
169                ///
170                /// // This invalidates both queries, because ["users"] is a prefix of ["users", "$x"]
171                /// client.invalidate_query(list_users_query(), &());
172                /// ```
173                pub fn with_invalidation_link<S, I>(
174                    mut self,
175                    invalidation_hierarchy_fn: impl Fn(&K) -> I + Send + Sync + 'static
176                ) -> Self
177                where
178                    I: IntoIterator<Item = S> + 'static $($impl_fn_generics)*,
179                    S: Into<String> + 'static $($impl_fn_generics)*,
180                {
181                    self.invalidation_hierarchy_fn = Some(Arc::new(move |key| {
182                        invalidation_hierarchy_fn(key).into_iter().map(|s| s.into()).collect()
183                    }));
184                    self
185                }
186
187                #[cfg(any(feature = "devtools", feature = "devtools-always"))]
188                /// Set a custom query scope/type title that will show in devtools.
189                #[track_caller]
190                pub fn with_title(mut self, title: impl Into<String>) -> Self {
191                    #[cfg(any(
192                        all(debug_assertions, feature = "devtools"),
193                        feature = "devtools-always"
194                    ))]
195                    {
196                        self.title = format_title(&title.into());
197                    }
198                    self
199                }
200            }
201
202            pub trait [<$name Trait>] <K, V, M>
203            where
204                K: 'static,
205                V: 'static,
206             {
207                fn options(&self) -> Option<QueryOptions> {
208                    Default::default()
209                }
210
211                fn fetcher_type_id(&self) -> TypeId;
212
213                fn cache_key(&self) -> ScopeCacheKey;
214
215                fn query(&self, key: K) -> impl Future<Output = V> $($impl_fut_generics)* + 'static;
216
217                fn invalidation_prefix(&self, key: &K) -> Option<Vec<String>>;
218
219                #[cfg(any(
220                    all(debug_assertions, feature = "devtools"),
221                    feature = "devtools-always"
222                ))]
223                #[track_caller]
224                fn title(&self) -> Arc<String>;
225            }
226
227            impl<K, V, F, Fut> [<$name Trait>]<K, V, QueryMarkerWithKey> for F
228            where
229                K: 'static,
230                V: 'static,
231                F: Fn(K) -> Fut + 'static,
232                Fut: Future<Output = V> $($impl_fut_generics)* + 'static,
233             {
234
235                fn fetcher_type_id(&self) -> TypeId {
236                    TypeId::of::<Self>()
237                }
238
239                fn cache_key(&self) -> ScopeCacheKey {
240                    ScopeCacheKey::new(TypeId::of::<Self>(), &Default::default())
241                }
242
243                fn query(&self, key: K) -> impl Future<Output = V> $($impl_fut_generics)* + 'static {
244                    self(key)
245                }
246
247                fn invalidation_prefix(&self, _key: &K) -> Option<Vec<String>> {
248                    None
249                }
250
251                #[cfg(any(
252                    all(debug_assertions, feature = "devtools"),
253                    feature = "devtools-always"
254                ))]
255                #[track_caller]
256                fn title(&self) -> Arc<String> {
257                    format_title(std::any::type_name::<Self>())
258                }
259            }
260
261            impl<V, F, Fut> [<$name Trait>]<(), V, QueryMarkerNoKey> for F
262            where
263                V: 'static,
264                F: Fn() -> Fut + 'static,
265                Fut: Future<Output = V> $($impl_fut_generics)* + 'static,
266             {
267                fn fetcher_type_id(&self) -> TypeId {
268                    TypeId::of::<Self>()
269                }
270
271                fn cache_key(&self) -> ScopeCacheKey {
272                    ScopeCacheKey::new(TypeId::of::<Self>(), &Default::default())
273                }
274
275                fn query(&self, _key: ()) -> impl Future<Output = V> $($impl_fut_generics)* + 'static {
276                    self()
277                }
278
279                fn invalidation_prefix(&self, _key: &()) -> Option<Vec<String>> {
280                    None
281                }
282
283                #[cfg(any(
284                    all(debug_assertions, feature = "devtools"),
285                    feature = "devtools-always"
286                ))]
287                #[track_caller]
288                fn title(&self) -> Arc<String> {
289                    format_title(std::any::type_name::<Self>())
290                }
291            }
292
293            impl<K, V> [<$name Trait>]<K, V, QueryMarkerWithKey> for $name<K, V>
294            where
295                K: 'static,
296                V: 'static,
297            {
298                fn options(&self) -> Option<QueryOptions> {
299                    Some(self.options)
300                }
301
302                fn fetcher_type_id(&self) -> TypeId {
303                    self.fetcher_type_id
304                }
305
306                fn cache_key(&self) -> ScopeCacheKey {
307                    self.cache_key
308                }
309
310                fn query(&self, key: K) -> impl Future<Output = V> $($impl_fut_generics)* + 'static {
311                    (self.query)(key)
312                }
313
314                fn invalidation_prefix(&self, key: &K) -> Option<Vec<String>> {
315                    if let Some(invalidation_hierarchy_fn) = &self.invalidation_hierarchy_fn {
316                        Some(invalidation_hierarchy_fn(key))
317                    } else {
318                        None
319                    }
320                }
321
322                #[cfg(any(
323                    all(debug_assertions, feature = "devtools"),
324                    feature = "devtools-always"
325                ))]
326                #[track_caller]
327                fn title(&self) -> Arc<String> {
328                    self.title.clone()
329                }
330            }
331
332            impl<K, V> [<$name Trait>]<K, V, QueryMarkerWithKey> for &$name<K, V>
333            where
334                K: 'static,
335                V: 'static,
336            {
337                fn options(&self) -> Option<QueryOptions> {
338                    Some(self.options)
339                }
340
341                fn fetcher_type_id(&self) -> TypeId {
342                    self.fetcher_type_id
343                }
344
345                fn cache_key(&self) -> ScopeCacheKey {
346                    self.cache_key
347                }
348
349                fn query(&self, key: K) -> impl Future<Output = V> $($impl_fut_generics)* + 'static {
350                    (self.query)(key)
351                }
352
353                fn invalidation_prefix(&self, key: &K) -> Option<Vec<String>> {
354                    if let Some(invalidation_hierarchy_fn) = &self.invalidation_hierarchy_fn {
355                        Some(invalidation_hierarchy_fn(key))
356                    } else {
357                        None
358                    }
359                }
360
361                #[cfg(any(
362                    all(debug_assertions, feature = "devtools"),
363                    feature = "devtools-always"
364                ))]
365                #[track_caller]
366                fn title(&self) -> Arc<String> {
367                    self.title.clone()
368                }
369            }
370
371            impl<K, V, T, M> [<$name Trait>]<K, V, M> for Arc<T>
372            where
373                K: 'static,
374                V: 'static,
375                T: [<$name Trait>]<K, V, M>,
376            {
377                fn options(&self) -> Option<QueryOptions> {
378                    T::options(self)
379                }
380
381                fn fetcher_type_id(&self) -> TypeId {
382                    T::fetcher_type_id(self)
383                }
384
385                fn cache_key(&self) -> ScopeCacheKey {
386                    T::cache_key(self)
387                }
388
389                fn query(&self, key: K) -> impl Future<Output = V> $($impl_fut_generics)* + 'static {
390                    T::query(self, key)
391                }
392
393                fn invalidation_prefix(&self, key: &K) -> Option<Vec<String>> {
394                    T::invalidation_prefix(self, key)
395                }
396
397                #[cfg(any(
398                    all(debug_assertions, feature = "devtools"),
399                    feature = "devtools-always"
400                ))]
401                #[track_caller]
402                fn title(&self) -> Arc<String> {
403                    T::title(self)
404                }
405            }
406        }
407    };
408}
409
410impl<K, V> QueryScopeLocalTrait<K, V, QueryMarkerWithKey> for QueryScope<K, V>
411where
412    K: 'static,
413    V: 'static,
414{
415    fn options(&self) -> Option<QueryOptions> {
416        Some(self.options)
417    }
418
419    fn fetcher_type_id(&self) -> TypeId {
420        self.fetcher_type_id
421    }
422
423    fn cache_key(&self) -> ScopeCacheKey {
424        self.cache_key
425    }
426
427    fn query(&self, key: K) -> impl Future<Output = V> + 'static {
428        (self.query)(key)
429    }
430
431    fn invalidation_prefix(&self, key: &K) -> Option<Vec<String>> {
432        self.invalidation_hierarchy_fn
433            .as_ref()
434            .map(|invalidation_hierarchy_fn| invalidation_hierarchy_fn(key))
435    }
436
437    #[cfg(any(
438        all(debug_assertions, feature = "devtools"),
439        feature = "devtools-always"
440    ))]
441    fn title(&self) -> Arc<String> {
442        self.title.clone()
443    }
444}
445
446impl<K, V> QueryScopeLocalTrait<K, V, QueryMarkerWithKey> for &QueryScope<K, V>
447where
448    K: 'static,
449    V: 'static,
450{
451    fn options(&self) -> Option<QueryOptions> {
452        Some(self.options)
453    }
454
455    fn fetcher_type_id(&self) -> TypeId {
456        self.fetcher_type_id
457    }
458
459    fn cache_key(&self) -> ScopeCacheKey {
460        self.cache_key
461    }
462
463    fn query(&self, key: K) -> impl Future<Output = V> + 'static {
464        (self.query)(key)
465    }
466
467    fn invalidation_prefix(&self, key: &K) -> Option<Vec<String>> {
468        self.invalidation_hierarchy_fn
469            .as_ref()
470            .map(|invalidation_hierarchy_fn| invalidation_hierarchy_fn(key))
471    }
472
473    #[cfg(any(
474        all(debug_assertions, feature = "devtools"),
475        feature = "devtools-always"
476    ))]
477    fn title(&self) -> Arc<String> {
478        self.title.clone()
479    }
480}
481
482define! { [+ Send], [+ Send + Sync], QueryScope, "QueryScope", "threadsafe" }
483define! { [], [], QueryScopeLocal, "QueryScopeLocal", "non-threadsafe" }
484
485#[derive(Debug, Clone)]
486pub(crate) struct QueryScopeInfo {
487    pub options: Option<QueryOptions>,
488    pub cache_key: ScopeCacheKey,
489    #[cfg(any(
490        all(debug_assertions, feature = "devtools"),
491        feature = "devtools-always"
492    ))]
493    pub title: Arc<String>,
494}
495
496impl QueryScopeInfo {
497    #[track_caller]
498    pub fn new<K, V, M>(query_scope: &impl QueryScopeTrait<K, V, M>) -> Self
499    where
500        K: 'static,
501        V: 'static,
502    {
503        Self {
504            options: query_scope.options(),
505            cache_key: query_scope.cache_key(),
506            #[cfg(any(
507                all(debug_assertions, feature = "devtools"),
508                feature = "devtools-always"
509            ))]
510            title: query_scope.title(),
511        }
512    }
513
514    pub fn new_local<K, V, M>(query_scope: &impl QueryScopeLocalTrait<K, V, M>) -> Self
515    where
516        K: 'static,
517        V: 'static,
518    {
519        Self {
520            options: query_scope.options(),
521            cache_key: query_scope.cache_key(),
522            #[cfg(any(
523                all(debug_assertions, feature = "devtools"),
524                feature = "devtools-always"
525            ))]
526            title: query_scope.title(),
527        }
528    }
529}