Skip to main content

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, maybe_local::MaybeLocal};
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(all(debug_assertions, feature = "devtools"), feature = "devtools-always"))]
32#[track_caller]
33fn format_title(base: &str) -> Arc<String> {
34    let loc = std::panic::Location::caller();
35    let filepath = loc.file();
36    let file = format!(
37        "{}:{}:{}",
38        // Only want the final file, not the full path:
39        filepath.split(std::path::MAIN_SEPARATOR_STR).last().unwrap_or(filepath),
40        loc.line(),
41        loc.column()
42    );
43    Arc::new(format!("{}: {}", file, base.trim_end_matches("::{{closure}}")))
44}
45
46/// A marker struct to allow query function with or without a key.
47///
48/// Ignore.
49pub struct QueryMarkerWithKey;
50
51/// A marker struct to allow query function with or without a key.
52///
53/// Ignore.
54pub struct QueryMarkerNoKey;
55
56macro_rules! define {
57    ([$($impl_fut_generics:tt)*], [$($impl_fn_generics:tt)*], $name:ident, $sname:literal, $sthread:literal) => {
58        /// A
59        #[doc = $sthread]
60        /// wrapper for a query function. This can be used to add specific
61        /// [`QueryOptions`] to only apply to one query scope.
62        ///
63        /// These [`QueryOptions`] will be combined with the global [`QueryOptions`]
64        /// set on the [`crate::QueryClient`], with the local options taking precedence.
65        ///
66        /// If you don't need to set specific options, you can use functions with the [`crate::QueryClient`] directly.
67        pub struct $name<K, V> {
68            query: Arc<dyn Fn(K) -> Pin<Box<dyn Future<Output = V> $($impl_fut_generics)*>> $($impl_fn_generics)*>,
69            invalidation_hierarchy_fn: Option<Arc<dyn for<'a> Fn(&'a K) -> Vec<String> $($impl_fn_generics)*>>,
70            on_invalidation: Vec<Arc<dyn for<'a> Fn(&'a K) $($impl_fn_generics)*>>,
71            on_gc: Vec<Arc<dyn for<'a> Fn(&'a K) $($impl_fn_generics)*>>,
72            fetcher_type_id: TypeId,
73            pub(crate) cache_key: ScopeCacheKey,
74            options: QueryOptions,
75            #[cfg(any(
76                all(debug_assertions, feature = "devtools"),
77                feature = "devtools-always"
78            ))]
79            title: Arc<String>,
80        }
81
82        impl<K, V> Clone for $name<K, V> {
83            fn clone(&self) -> Self {
84                Self {
85                    query: self.query.clone(),
86                    invalidation_hierarchy_fn: self.invalidation_hierarchy_fn.clone(),
87                    on_invalidation: self.on_invalidation.clone(),
88                    on_gc: self.on_gc.clone(),
89                    fetcher_type_id: self.fetcher_type_id,
90                    cache_key: self.cache_key,
91                    options: self.options,
92                    #[cfg(any(
93                        all(debug_assertions, feature = "devtools"),
94                        feature = "devtools-always"
95                    ))]
96                    title: self.title.clone(),
97                }
98            }
99        }
100
101        impl<K, V> Debug for $name<K, V> {
102            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
103                f.debug_struct(stringify!($name))
104                    .field("query", &"Arc<dyn Fn(K) -> Pin<Box<dyn Future<Output = V>>")
105                    .field("options", &self.options)
106                    .finish()
107            }
108        }
109
110        paste! {
111            impl<K, V> $name<K, V> {
112                /// Create a new
113                #[doc = $sname]
114                ///.
115                /// If the query fn does not have a key argument, `K=()`
116                ///
117                #[track_caller]
118                pub fn new<M>(
119                    query_scope: impl [<$name Trait>]<K, V, M> $($impl_fn_generics)* + 'static,
120                ) -> Self
121                where
122                    K: 'static $($impl_fn_generics)*,
123                    V: 'static $($impl_fn_generics)*,
124                {
125                    let options = query_scope.options().unwrap_or_default();
126                    let fetcher_type_id = query_scope.fetcher_type_id();
127                    Self {
128                        fetcher_type_id,
129                        cache_key: ScopeCacheKey::new(fetcher_type_id, &options),
130                        options,
131                        #[cfg(any(
132                            all(debug_assertions, feature = "devtools"),
133                            feature = "devtools-always"
134                        ))]
135                        title: query_scope.title(),
136                        invalidation_hierarchy_fn: None,
137                        on_invalidation: vec![],
138                        on_gc: vec![],
139                        query: Arc::new(move |key| Box::pin(query_scope.query(key))),
140                    }
141                }
142
143                /// Set specific [`QueryOptions`] to only apply to this query scope.
144                ///
145                /// These [`QueryOptions`] will be combined with the global
146                /// [`QueryOptions`] set on the [`crate::QueryClient`], with the local
147                /// options taking precedence.
148                pub fn with_options(mut self, options: QueryOptions) -> Self {
149                    self.options = options;
150                    self
151                }
152
153                /// Different query types are sometimes linked to the same source,
154                /// e.g. you may want an invalidation of `list_blogposts()` to always
155                /// automatically invalidate `get_blogpost(id)`.
156                ///
157                /// [`QueryScope::with_invalidation_link`] can be used to this effect,
158                /// given a query key `&K`, you provide a `Vec<String>` that's used as
159                /// a **hierarchy key (HK)** for that query. When a query is invalidated,
160                /// any query's **HK** that's prefixed by this **HK** will also be
161                /// invalidated automatically. E.g. A query with **HK** `["users"]` will
162                /// also auto invalidate another query with `["users", "1"]`, but not the
163                /// other way around. 2 queries with an identicial **HK** of `["users"]`
164                /// will auto invalidate each other.
165                ///
166                /// [`QueryScope::with_invalidation_link`]: https://docs.rs/leptos-fetch/latest/leptos_fetch/struct.QueryScope.html#method.with_invalidation_link
167                ///
168                /// ```rust
169                /// use std::time::Duration;
170                ///
171                /// use leptos_fetch::{QueryClient, QueryScope, QueryOptions};
172                /// use leptos::prelude::*;
173                ///
174                /// #[derive(Debug, Clone)]
175                /// struct User;
176                ///
177                /// fn list_users_query() -> QueryScope<(), Vec<User>> {
178                ///     QueryScope::new(async || vec![])
179                ///         .with_invalidation_link(
180                ///             |_key| ["users"]
181                ///         )
182                /// }
183                ///
184                /// fn get_user_query() -> QueryScope<i32, User> {
185                ///     QueryScope::new(async move |user_id: i32| User)
186                ///         .with_invalidation_link(
187                ///             |user_id| ["users".to_string(), user_id.to_string()]
188                ///         )
189                /// }
190                ///
191                /// let client = QueryClient::new();
192                ///
193                /// // This invalidates only user "2", because ["users", "2"] is not a prefix of ["users"],
194                /// // list_users_query is NOT invalidated.
195                /// client.invalidate_query(get_user_query(), &2);
196                ///
197                /// // This invalidates both queries, because ["users"] is a prefix of ["users", "$x"]
198                /// client.invalidate_query(list_users_query(), &());
199                /// ```
200                pub fn with_invalidation_link<S, I>(
201                    mut self,
202                    invalidation_hierarchy_fn: impl Fn(&K) -> I + 'static $($impl_fn_generics)*,
203                ) -> Self
204                where
205                    I: IntoIterator<Item = S> + 'static $($impl_fn_generics)*,
206                    S: Into<String> + 'static $($impl_fn_generics)*,
207                {
208                    self.invalidation_hierarchy_fn = Some(Arc::new(move |key| {
209                        invalidation_hierarchy_fn(key).into_iter().map(|s| s.into()).collect()
210                    }));
211                    self
212                }
213
214                /// Run a callback when a query is invalidated.
215                /// Will only run once when a query is first invalidated,
216                /// and not on repeated invalidations before a refresh/reset.
217                pub fn on_invalidation(
218                    mut self,
219                    on_invalidation_cb: impl Fn(&K) + 'static $($impl_fn_generics)*,
220                ) -> Self {
221                    self.on_invalidation.push(Arc::new(on_invalidation_cb));
222                    self
223                }
224
225                /// Run a callback when a query is garbage collected.
226                pub fn on_gc(
227                    mut self,
228                    on_gc_cb: impl Fn(&K) + 'static $($impl_fn_generics)*,
229                ) -> Self {
230                    self.on_gc.push(Arc::new(on_gc_cb));
231                    self
232                }
233
234                #[cfg(any(feature = "devtools", feature = "devtools-always"))]
235                /// Set a custom query scope/type title that will show in devtools.
236                #[track_caller]
237                pub fn with_title(mut self, title: impl Into<String>) -> Self {
238                    #[cfg(any(
239                        all(debug_assertions, feature = "devtools"),
240                        feature = "devtools-always"
241                    ))]
242                    {
243                        self.title = format_title(&title.into());
244                    }
245                    self
246                }
247            }
248
249            pub trait [<$name Trait>] <K, V, M>
250            where
251                K: 'static,
252                V: 'static,
253             {
254                fn options(&self) -> Option<QueryOptions> {
255                    Default::default()
256                }
257
258                fn fetcher_type_id(&self) -> TypeId;
259
260                fn cache_key(&self) -> ScopeCacheKey;
261
262                fn query(&self, key: K) -> impl Future<Output = V> $($impl_fut_generics)* + 'static;
263
264                fn invalidation_prefix(&self, key: &K) -> Option<Vec<String>>;
265
266                fn on_invalidation(&self) -> Option<Arc<dyn Fn(&K) $($impl_fn_generics)*>>;
267
268                fn on_gc(&self) -> Option<Arc<dyn Fn(&K) $($impl_fn_generics)*>>;
269
270                #[cfg(any(
271                    all(debug_assertions, feature = "devtools"),
272                    feature = "devtools-always"
273                ))]
274                #[track_caller]
275                fn title(&self) -> Arc<String>;
276            }
277
278            impl<K, V, F, Fut> [<$name Trait>]<K, V, QueryMarkerWithKey> for F
279            where
280                K: 'static,
281                V: 'static,
282                F: Fn(K) -> Fut + 'static,
283                Fut: Future<Output = V> $($impl_fut_generics)* + 'static,
284             {
285
286                fn fetcher_type_id(&self) -> TypeId {
287                    TypeId::of::<Self>()
288                }
289
290                fn cache_key(&self) -> ScopeCacheKey {
291                    ScopeCacheKey::new(TypeId::of::<Self>(), &Default::default())
292                }
293
294                fn query(&self, key: K) -> impl Future<Output = V> $($impl_fut_generics)* + 'static {
295                    self(key)
296                }
297
298                fn invalidation_prefix(&self, _key: &K) -> Option<Vec<String>> {
299                    None
300                }
301
302                fn on_invalidation(&self) -> Option<Arc<dyn Fn(&K) $($impl_fn_generics)*>> {
303                    None
304                }
305
306                fn on_gc(&self) -> Option<Arc<dyn Fn(&K) $($impl_fn_generics)*>> {
307                    None
308                }
309
310                #[cfg(any(
311                    all(debug_assertions, feature = "devtools"),
312                    feature = "devtools-always"
313                ))]
314                #[track_caller]
315                fn title(&self) -> Arc<String> {
316                    format_title(std::any::type_name::<Self>())
317                }
318            }
319
320            impl<V, F, Fut> [<$name Trait>]<(), V, QueryMarkerNoKey> for F
321            where
322                V: 'static,
323                F: Fn() -> Fut + 'static,
324                Fut: Future<Output = V> $($impl_fut_generics)* + 'static,
325             {
326                fn fetcher_type_id(&self) -> TypeId {
327                    TypeId::of::<Self>()
328                }
329
330                fn cache_key(&self) -> ScopeCacheKey {
331                    ScopeCacheKey::new(TypeId::of::<Self>(), &Default::default())
332                }
333
334                fn query(&self, _key: ()) -> impl Future<Output = V> $($impl_fut_generics)* + 'static {
335                    self()
336                }
337
338                fn invalidation_prefix(&self, _key: &()) -> Option<Vec<String>> {
339                    None
340                }
341
342                fn on_invalidation(&self) -> Option<Arc<dyn Fn(&()) $($impl_fn_generics)*>> {
343                    None
344                }
345
346                fn on_gc(&self) -> Option<Arc<dyn Fn(&()) $($impl_fn_generics)*>> {
347                    None
348                }
349
350                #[cfg(any(
351                    all(debug_assertions, feature = "devtools"),
352                    feature = "devtools-always"
353                ))]
354                #[track_caller]
355                fn title(&self) -> Arc<String> {
356                    format_title(std::any::type_name::<Self>())
357                }
358            }
359
360            impl<K, V> [<$name Trait>]<K, V, QueryMarkerWithKey> for $name<K, V>
361            where
362                K: 'static,
363                V: 'static,
364            {
365                fn options(&self) -> Option<QueryOptions> {
366                    Some(self.options)
367                }
368
369                fn fetcher_type_id(&self) -> TypeId {
370                    self.fetcher_type_id
371                }
372
373                fn cache_key(&self) -> ScopeCacheKey {
374                    self.cache_key
375                }
376
377                fn query(&self, key: K) -> impl Future<Output = V> $($impl_fut_generics)* + 'static {
378                    (self.query)(key)
379                }
380
381                fn invalidation_prefix(&self, key: &K) -> Option<Vec<String>> {
382                    if let Some(invalidation_hierarchy_fn) = &self.invalidation_hierarchy_fn {
383                        Some(invalidation_hierarchy_fn(key))
384                    } else {
385                        None
386                    }
387                }
388
389                fn on_invalidation(&self) -> Option<Arc<dyn Fn(&K) $($impl_fn_generics)*>> {
390                    if self.on_invalidation.is_empty() {
391                        None
392                    } else {
393                        let callbacks = self.on_invalidation.clone();
394                        Some(Arc::new(move |key| {
395                            for cb in &callbacks {
396                                cb(key);
397                            }
398                        }))
399                    }
400                }
401
402                fn on_gc(&self) -> Option<Arc<dyn Fn(&K) $($impl_fn_generics)*>> {
403                    if self.on_gc.is_empty() {
404                        None
405                    } else {
406                        let callbacks = self.on_gc.clone();
407                        Some(Arc::new(move |key| {
408                            for cb in &callbacks {
409                                cb(key);
410                            }
411                        }))
412                    }
413                }
414
415                #[cfg(any(
416                    all(debug_assertions, feature = "devtools"),
417                    feature = "devtools-always"
418                ))]
419                #[track_caller]
420                fn title(&self) -> Arc<String> {
421                    self.title.clone()
422                }
423            }
424
425            impl<K, V> [<$name Trait>]<K, V, QueryMarkerWithKey> for &$name<K, V>
426            where
427                K: 'static,
428                V: 'static,
429            {
430                fn options(&self) -> Option<QueryOptions> {
431                    Some(self.options)
432                }
433
434                fn fetcher_type_id(&self) -> TypeId {
435                    self.fetcher_type_id
436                }
437
438                fn cache_key(&self) -> ScopeCacheKey {
439                    self.cache_key
440                }
441
442                fn query(&self, key: K) -> impl Future<Output = V> $($impl_fut_generics)* + 'static {
443                    (self.query)(key)
444                }
445
446                fn invalidation_prefix(&self, key: &K) -> Option<Vec<String>> {
447                    if let Some(invalidation_hierarchy_fn) = &self.invalidation_hierarchy_fn {
448                        Some(invalidation_hierarchy_fn(key))
449                    } else {
450                        None
451                    }
452                }
453
454                fn on_invalidation(&self) -> Option<Arc<dyn Fn(&K) $($impl_fn_generics)*>> {
455                    if self.on_invalidation.is_empty() {
456                        None
457                    } else {
458                        let callbacks = self.on_invalidation.clone();
459                        Some(Arc::new(move |key| {
460                            for cb in &callbacks {
461                                cb(key);
462                            }
463                        }))
464                    }
465                }
466
467                fn on_gc(&self) -> Option<Arc<dyn Fn(&K) $($impl_fn_generics)*>> {
468                    if self.on_gc.is_empty() {
469                        None
470                    } else {
471                        let callbacks = self.on_gc.clone();
472                        Some(Arc::new(move |key| {
473                            for cb in &callbacks {
474                                cb(key);
475                            }
476                        }))
477                    }
478                }
479
480                #[cfg(any(
481                    all(debug_assertions, feature = "devtools"),
482                    feature = "devtools-always"
483                ))]
484                #[track_caller]
485                fn title(&self) -> Arc<String> {
486                    self.title.clone()
487                }
488            }
489
490            impl<K, V, T, M> [<$name Trait>]<K, V, M> for Arc<T>
491            where
492                K: 'static,
493                V: 'static,
494                T: [<$name Trait>]<K, V, M>,
495            {
496                fn options(&self) -> Option<QueryOptions> {
497                    T::options(self)
498                }
499
500                fn fetcher_type_id(&self) -> TypeId {
501                    T::fetcher_type_id(self)
502                }
503
504                fn cache_key(&self) -> ScopeCacheKey {
505                    T::cache_key(self)
506                }
507
508                fn query(&self, key: K) -> impl Future<Output = V> $($impl_fut_generics)* + 'static {
509                    T::query(self, key)
510                }
511
512                fn invalidation_prefix(&self, key: &K) -> Option<Vec<String>> {
513                    T::invalidation_prefix(self, key)
514                }
515
516                fn on_invalidation(&self) -> Option<Arc<dyn Fn(&K) $($impl_fn_generics)*>> {
517                    T::on_invalidation(self)
518                }
519
520                fn on_gc(&self) -> Option<Arc<dyn Fn(&K) $($impl_fn_generics)*>> {
521                    T::on_gc(self)
522                }
523
524                #[cfg(any(
525                    all(debug_assertions, feature = "devtools"),
526                    feature = "devtools-always"
527                ))]
528                #[track_caller]
529                fn title(&self) -> Arc<String> {
530                    T::title(self)
531                }
532            }
533        }
534    };
535}
536
537impl<K, V> QueryScopeLocalTrait<K, V, QueryMarkerWithKey> for QueryScope<K, V>
538where
539    K: 'static,
540    V: 'static,
541{
542    fn options(&self) -> Option<QueryOptions> {
543        Some(self.options)
544    }
545
546    fn fetcher_type_id(&self) -> TypeId {
547        self.fetcher_type_id
548    }
549
550    fn cache_key(&self) -> ScopeCacheKey {
551        self.cache_key
552    }
553
554    fn query(&self, key: K) -> impl Future<Output = V> + 'static {
555        (self.query)(key)
556    }
557
558    fn invalidation_prefix(&self, key: &K) -> Option<Vec<String>> {
559        self.invalidation_hierarchy_fn
560            .as_ref()
561            .map(|invalidation_hierarchy_fn| invalidation_hierarchy_fn(key))
562    }
563
564    fn on_invalidation(&self) -> Option<Arc<dyn Fn(&K)>> {
565        if self.on_invalidation.is_empty() {
566            None
567        } else {
568            let callbacks = self.on_invalidation.clone();
569            Some(Arc::new(move |key| {
570                for cb in &callbacks {
571                    cb(key);
572                }
573            }))
574        }
575    }
576
577    fn on_gc(&self) -> Option<Arc<dyn Fn(&K)>> {
578        if self.on_gc.is_empty() {
579            None
580        } else {
581            let callbacks = self.on_gc.clone();
582            Some(Arc::new(move |key| {
583                for cb in &callbacks {
584                    cb(key);
585                }
586            }))
587        }
588    }
589
590    #[cfg(any(all(debug_assertions, feature = "devtools"), feature = "devtools-always"))]
591    fn title(&self) -> Arc<String> {
592        self.title.clone()
593    }
594}
595
596impl<K, V> QueryScopeLocalTrait<K, V, QueryMarkerWithKey> for &QueryScope<K, V>
597where
598    K: 'static,
599    V: 'static,
600{
601    fn options(&self) -> Option<QueryOptions> {
602        Some(self.options)
603    }
604
605    fn fetcher_type_id(&self) -> TypeId {
606        self.fetcher_type_id
607    }
608
609    fn cache_key(&self) -> ScopeCacheKey {
610        self.cache_key
611    }
612
613    fn query(&self, key: K) -> impl Future<Output = V> + 'static {
614        (self.query)(key)
615    }
616
617    fn invalidation_prefix(&self, key: &K) -> Option<Vec<String>> {
618        self.invalidation_hierarchy_fn
619            .as_ref()
620            .map(|invalidation_hierarchy_fn| invalidation_hierarchy_fn(key))
621    }
622
623    fn on_invalidation(&self) -> Option<Arc<dyn Fn(&K)>> {
624        if self.on_invalidation.is_empty() {
625            None
626        } else {
627            let callbacks = self.on_invalidation.clone();
628            Some(Arc::new(move |key| {
629                for cb in &callbacks {
630                    cb(key);
631                }
632            }))
633        }
634    }
635
636    fn on_gc(&self) -> Option<Arc<dyn Fn(&K)>> {
637        if self.on_gc.is_empty() {
638            None
639        } else {
640            let callbacks = self.on_gc.clone();
641            Some(Arc::new(move |key| {
642                for cb in &callbacks {
643                    cb(key);
644                }
645            }))
646        }
647    }
648
649    #[cfg(any(all(debug_assertions, feature = "devtools"), feature = "devtools-always"))]
650    fn title(&self) -> Arc<String> {
651        self.title.clone()
652    }
653}
654
655define! { [+ Send], [+ Send + Sync], QueryScope, "QueryScope", "threadsafe" }
656define! { [], [], QueryScopeLocal, "QueryScopeLocal", "non-threadsafe" }
657
658#[derive(Debug, Clone)]
659pub(crate) struct QueryScopeInfo {
660    pub options: Option<QueryOptions>,
661    pub cache_key: ScopeCacheKey,
662    #[cfg(any(all(debug_assertions, feature = "devtools"), feature = "devtools-always"))]
663    pub title: Arc<String>,
664}
665
666impl QueryScopeInfo {
667    #[track_caller]
668    pub fn new<K, V, M>(query_scope: &impl QueryScopeTrait<K, V, M>) -> Self
669    where
670        K: 'static,
671        V: 'static,
672    {
673        Self {
674            options: query_scope.options(),
675            cache_key: query_scope.cache_key(),
676            #[cfg(any(all(debug_assertions, feature = "devtools"), feature = "devtools-always"))]
677            title: query_scope.title(),
678        }
679    }
680
681    #[track_caller]
682    pub fn new_local<K, V, M>(query_scope: &impl QueryScopeLocalTrait<K, V, M>) -> Self
683    where
684        K: 'static,
685        V: 'static,
686    {
687        Self {
688            options: query_scope.options(),
689            cache_key: query_scope.cache_key(),
690            #[cfg(any(all(debug_assertions, feature = "devtools"), feature = "devtools-always"))]
691            title: query_scope.title(),
692        }
693    }
694}
695
696pub(crate) struct QueryScopeQueryInfo<K> {
697    pub on_invalidation: Option<MaybeLocal<Arc<dyn Fn(&K)>>>,
698    pub on_gc: Option<MaybeLocal<Arc<dyn Fn(&K)>>>,
699    pub invalidation_prefix: Option<Vec<String>>,
700    _key_marker: std::marker::PhantomData<K>,
701}
702
703impl<K> QueryScopeQueryInfo<K>
704where
705    K: 'static,
706{
707    #[track_caller]
708    pub fn new<V, M>(query_scope: &impl QueryScopeTrait<K, V, M>, key: &K) -> Self
709    where
710        K: 'static,
711        V: 'static,
712    {
713        Self {
714            on_invalidation: query_scope
715                .on_invalidation()
716                .map(MaybeLocal::new_arc_with_key_arg_special),
717            on_gc: query_scope.on_gc().map(MaybeLocal::new_arc_with_key_arg_special),
718            invalidation_prefix: query_scope.invalidation_prefix(key),
719            _key_marker: std::marker::PhantomData,
720        }
721    }
722
723    #[track_caller]
724    pub fn new_local<V, M>(query_scope: &impl QueryScopeLocalTrait<K, V, M>, key: &K) -> Self
725    where
726        V: 'static,
727    {
728        Self {
729            on_invalidation: query_scope.on_invalidation().map(MaybeLocal::new_local),
730            on_gc: query_scope.on_gc().map(MaybeLocal::new_local),
731            invalidation_prefix: query_scope.invalidation_prefix(key),
732            _key_marker: std::marker::PhantomData,
733        }
734    }
735}