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