leptos_server/
once_resource.rs

1use crate::{
2    initial_value, FromEncodedStr, IntoEncodedString,
3    IS_SUPPRESSING_RESOURCE_LOAD,
4};
5#[cfg(feature = "rkyv")]
6use codee::binary::RkyvCodec;
7#[cfg(feature = "serde-wasm-bindgen")]
8use codee::string::JsonSerdeWasmCodec;
9#[cfg(feature = "miniserde")]
10use codee::string::MiniserdeCodec;
11#[cfg(feature = "serde-lite")]
12use codee::SerdeLite;
13use codee::{
14    string::{FromToStringCodec, JsonSerdeCodec},
15    Decoder, Encoder,
16};
17use core::{fmt::Debug, marker::PhantomData};
18use futures::Future;
19use or_poisoned::OrPoisoned;
20use reactive_graph::{
21    computed::{
22        suspense::SuspenseContext, AsyncDerivedReadyFuture, ScopedFuture,
23    },
24    diagnostics::{SpecialNonReactiveFuture, SpecialNonReactiveZone},
25    graph::{AnySource, ToAnySource},
26    owner::{use_context, ArenaItem, Owner},
27    prelude::*,
28    signal::{
29        guards::{Plain, ReadGuard},
30        ArcTrigger,
31    },
32    unwrap_signal,
33};
34use std::{
35    future::IntoFuture,
36    mem,
37    panic::Location,
38    pin::Pin,
39    sync::{
40        atomic::{AtomicBool, Ordering},
41        Arc, RwLock,
42    },
43    task::{Context, Poll, Waker},
44};
45
46/// A reference-counted resource that only loads once.
47///
48/// Resources allow asynchronously loading data and serializing it from the server to the client,
49/// so that it loads on the server, and is then deserialized on the client. This improves
50/// performance by beginning data loading on the server when the request is made, rather than
51/// beginning it on the client after WASM has been loaded.
52///
53/// You can access the value of the resource either synchronously using `.get()` or asynchronously
54/// using `.await`.
55#[derive(Debug)]
56pub struct ArcOnceResource<T, Ser = JsonSerdeCodec> {
57    trigger: ArcTrigger,
58    value: Arc<RwLock<Option<T>>>,
59    wakers: Arc<RwLock<Vec<Waker>>>,
60    suspenses: Arc<RwLock<Vec<SuspenseContext>>>,
61    loading: Arc<AtomicBool>,
62    ser: PhantomData<fn() -> Ser>,
63    #[cfg(any(debug_assertions, leptos_debuginfo))]
64    defined_at: &'static Location<'static>,
65}
66
67impl<T, Ser> Clone for ArcOnceResource<T, Ser> {
68    fn clone(&self) -> Self {
69        Self {
70            trigger: self.trigger.clone(),
71            value: self.value.clone(),
72            wakers: self.wakers.clone(),
73            suspenses: self.suspenses.clone(),
74            loading: self.loading.clone(),
75            ser: self.ser,
76            #[cfg(any(debug_assertions, leptos_debuginfo))]
77            defined_at: self.defined_at,
78        }
79    }
80}
81
82impl<T, Ser> ArcOnceResource<T, Ser>
83where
84    T: Send + Sync + 'static,
85    Ser: Encoder<T> + Decoder<T>,
86    <Ser as Encoder<T>>::Error: Debug,
87    <Ser as Decoder<T>>::Error: Debug,
88    <<Ser as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
89    <Ser as Encoder<T>>::Encoded: IntoEncodedString,
90    <Ser as Decoder<T>>::Encoded: FromEncodedStr,
91{
92    /// Creates a new resource with the encoding `Ser`. If `blocking` is `true`, this is a blocking
93    /// resource.
94    ///
95    /// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
96    /// This is useful if you need their data to set HTML document metadata or information that
97    /// needs to appear in HTTP headers.
98    #[track_caller]
99    pub fn new_with_options(
100        fut: impl Future<Output = T> + Send + 'static,
101        #[allow(unused)] // this is used with `feature = "ssr"`
102        blocking: bool,
103    ) -> Self {
104        let shared_context = Owner::current_shared_context();
105        let id = shared_context
106            .as_ref()
107            .map(|sc| sc.next_id())
108            .unwrap_or_default();
109
110        let initial = initial_value::<T, Ser>(&id, shared_context.as_ref());
111        let is_ready = initial.is_some();
112        let value = Arc::new(RwLock::new(initial));
113        let wakers = Arc::new(RwLock::new(Vec::<Waker>::new()));
114        let suspenses = Arc::new(RwLock::new(Vec::<SuspenseContext>::new()));
115        let loading = Arc::new(AtomicBool::new(!is_ready));
116        let trigger = ArcTrigger::new();
117
118        let fut = ScopedFuture::new(fut);
119
120        if !is_ready && !IS_SUPPRESSING_RESOURCE_LOAD.load(Ordering::Relaxed) {
121            let value = Arc::clone(&value);
122            let wakers = Arc::clone(&wakers);
123            let loading = Arc::clone(&loading);
124            let trigger = trigger.clone();
125            reactive_graph::spawn(async move {
126                let loaded = fut.await;
127                *value.write().or_poisoned() = Some(loaded);
128                loading.store(false, Ordering::Relaxed);
129                for waker in mem::take(&mut *wakers.write().or_poisoned()) {
130                    waker.wake();
131                }
132                trigger.notify();
133            });
134        }
135
136        let data = Self {
137            trigger,
138            value: value.clone(),
139            loading,
140            wakers,
141            suspenses,
142            ser: PhantomData,
143            #[cfg(any(debug_assertions, leptos_debuginfo))]
144            defined_at: Location::caller(),
145        };
146
147        #[cfg(feature = "ssr")]
148        if let Some(shared_context) = shared_context {
149            let value = Arc::clone(&value);
150            let ready_fut = data.ready();
151
152            if blocking {
153                shared_context.defer_stream(Box::pin(data.ready()));
154            }
155
156            if shared_context.get_is_hydrating() {
157                shared_context.write_async(
158                    id,
159                    Box::pin(async move {
160                        ready_fut.await;
161                        let value = value.read().or_poisoned();
162                        let value = value.as_ref().unwrap();
163                        Ser::encode(value).unwrap().into_encoded_string()
164                    }),
165                );
166            }
167        }
168
169        data
170    }
171
172    /// Synchronously, reactively reads the current value of the resource and applies the function
173    /// `f` to its value if it is `Some(_)`.
174    #[track_caller]
175    pub fn map<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U>
176    where
177        T: Send + Sync + 'static,
178    {
179        self.try_with(|n| n.as_ref().map(f))?
180    }
181}
182
183impl<T, E, Ser> ArcOnceResource<Result<T, E>, Ser>
184where
185    Ser: Encoder<Result<T, E>> + Decoder<Result<T, E>>,
186    <Ser as Encoder<Result<T, E>>>::Error: Debug,
187    <Ser as Decoder<Result<T, E>>>::Error: Debug,
188    <<Ser as Decoder<Result<T, E>>>::Encoded as FromEncodedStr>::DecodingError:
189        Debug,
190    <Ser as Encoder<Result<T, E>>>::Encoded: IntoEncodedString,
191    <Ser as Decoder<Result<T, E>>>::Encoded: FromEncodedStr,
192    T: Send + Sync + 'static,
193    E: Send + Sync + Clone + 'static,
194{
195    /// Applies the given function when a resource that returns `Result<T, E>`
196    /// has resolved and loaded an `Ok(_)`, rather than requiring nested `.map()`
197    /// calls over the `Option<Result<_, _>>` returned by the resource.
198    ///
199    /// This is useful when used with features like server functions, in conjunction
200    /// with `<ErrorBoundary/>` and `<Suspense/>`, when these other components are
201    /// left to handle the `None` and `Err(_)` states.
202    #[track_caller]
203    pub fn and_then<U>(&self, f: impl FnOnce(&T) -> U) -> Option<Result<U, E>> {
204        self.map(|data| data.as_ref().map(f).map_err(|e| e.clone()))
205    }
206}
207
208impl<T, Ser> ArcOnceResource<T, Ser> {
209    /// Returns a `Future` that is ready when this resource has next finished loading.
210    pub fn ready(&self) -> AsyncDerivedReadyFuture {
211        AsyncDerivedReadyFuture::new(
212            self.to_any_source(),
213            &self.loading,
214            &self.wakers,
215        )
216    }
217}
218
219impl<T, Ser> DefinedAt for ArcOnceResource<T, Ser> {
220    fn defined_at(&self) -> Option<&'static Location<'static>> {
221        #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
222        {
223            None
224        }
225        #[cfg(any(debug_assertions, leptos_debuginfo))]
226        {
227            Some(self.defined_at)
228        }
229    }
230}
231
232impl<T, Ser> IsDisposed for ArcOnceResource<T, Ser> {
233    #[inline(always)]
234    fn is_disposed(&self) -> bool {
235        false
236    }
237}
238
239impl<T, Ser> ToAnySource for ArcOnceResource<T, Ser> {
240    fn to_any_source(&self) -> AnySource {
241        self.trigger.to_any_source()
242    }
243}
244
245impl<T, Ser> Track for ArcOnceResource<T, Ser> {
246    fn track(&self) {
247        self.trigger.track();
248    }
249}
250
251impl<T, Ser> ReadUntracked for ArcOnceResource<T, Ser>
252where
253    T: 'static,
254{
255    type Value = ReadGuard<Option<T>, Plain<Option<T>>>;
256
257    fn try_read_untracked(&self) -> Option<Self::Value> {
258        if let Some(suspense_context) = use_context::<SuspenseContext>() {
259            if self.value.read().or_poisoned().is_none() {
260                let handle = suspense_context.task_id();
261                let ready = SpecialNonReactiveFuture::new(self.ready());
262                reactive_graph::spawn(async move {
263                    ready.await;
264                    drop(handle);
265                });
266                self.suspenses.write().or_poisoned().push(suspense_context);
267            }
268        }
269        Plain::try_new(Arc::clone(&self.value)).map(ReadGuard::new)
270    }
271}
272
273impl<T, Ser> IntoFuture for ArcOnceResource<T, Ser>
274where
275    T: Clone + 'static,
276{
277    type Output = T;
278    type IntoFuture = OnceResourceFuture<T>;
279
280    fn into_future(self) -> Self::IntoFuture {
281        OnceResourceFuture {
282            source: self.to_any_source(),
283            value: Arc::clone(&self.value),
284            loading: Arc::clone(&self.loading),
285            wakers: Arc::clone(&self.wakers),
286            suspenses: Arc::clone(&self.suspenses),
287        }
288    }
289}
290
291/// A [`Future`] that is ready when an
292/// [`ArcAsyncDerived`](reactive_graph::computed::ArcAsyncDerived) is finished loading or reloading,
293/// and contains its value. `.await`ing this clones the value `T`.
294pub struct OnceResourceFuture<T> {
295    source: AnySource,
296    value: Arc<RwLock<Option<T>>>,
297    loading: Arc<AtomicBool>,
298    wakers: Arc<RwLock<Vec<Waker>>>,
299    suspenses: Arc<RwLock<Vec<SuspenseContext>>>,
300}
301
302impl<T> Future for OnceResourceFuture<T>
303where
304    T: Clone + 'static,
305{
306    type Output = T;
307
308    #[track_caller]
309    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
310        #[cfg(any(debug_assertions, leptos_debuginfo))]
311        let _guard = SpecialNonReactiveZone::enter();
312        let waker = cx.waker();
313        self.source.track();
314
315        if let Some(suspense_context) = use_context::<SuspenseContext>() {
316            self.suspenses.write().or_poisoned().push(suspense_context);
317        }
318
319        if self.loading.load(Ordering::Relaxed) {
320            self.wakers.write().or_poisoned().push(waker.clone());
321            Poll::Pending
322        } else {
323            Poll::Ready(
324                self.value.read().or_poisoned().as_ref().unwrap().clone(),
325            )
326        }
327    }
328}
329
330impl<T> ArcOnceResource<T, JsonSerdeCodec>
331where
332    T: Send + Sync + 'static,
333    JsonSerdeCodec: Encoder<T> + Decoder<T>,
334    <JsonSerdeCodec as Encoder<T>>::Error: Debug,
335    <JsonSerdeCodec as Decoder<T>>::Error: Debug,
336    <<JsonSerdeCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
337        Debug,
338    <JsonSerdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
339    <JsonSerdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
340{
341    /// Creates a resource using [`JsonSerdeCodec`] for encoding/decoding the value.
342    #[track_caller]
343    pub fn new(fut: impl Future<Output = T> + Send + 'static) -> Self {
344        ArcOnceResource::new_with_options(fut, false)
345    }
346
347    /// Creates a blocking resource using [`JsonSerdeCodec`] for encoding/decoding the value.
348    ///
349    /// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
350    /// This is useful if you need their data to set HTML document metadata or information that
351    /// needs to appear in HTTP headers.
352    #[track_caller]
353    pub fn new_blocking(fut: impl Future<Output = T> + Send + 'static) -> Self {
354        ArcOnceResource::new_with_options(fut, true)
355    }
356}
357
358impl<T> ArcOnceResource<T, FromToStringCodec>
359where
360T: Send + Sync + 'static,
361    FromToStringCodec: Encoder<T> + Decoder<T>,
362    <FromToStringCodec as Encoder<T>>::Error: Debug, <FromToStringCodec as Decoder<T>>::Error: Debug,
363    <<FromToStringCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
364    <FromToStringCodec as Encoder<T>>::Encoded: IntoEncodedString,
365    <FromToStringCodec as Decoder<T>>::Encoded: FromEncodedStr,
366{
367    /// Creates a resource using [`FromToStringCodec`] for encoding/decoding the value.
368    pub fn new_str(
369        fut: impl Future<Output = T> + Send + 'static
370    ) -> Self
371    {
372        ArcOnceResource::new_with_options(fut, false)
373    }
374
375    /// Creates a blocking resource using [`FromToStringCodec`] for encoding/decoding the value.
376    ///
377    /// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
378    /// This is useful if you need their data to set HTML document metadata or information that
379    /// needs to appear in HTTP headers.
380    pub fn new_str_blocking(
381        fut: impl Future<Output = T> + Send + 'static
382    ) -> Self
383    {
384        ArcOnceResource::new_with_options(fut, true)
385    }
386}
387
388#[cfg(feature = "serde-wasm-bindgen")]
389impl<T> ArcOnceResource<T, JsonSerdeWasmCodec>
390where
391T: Send + Sync + 'static,
392    JsonSerdeWasmCodec: Encoder<T> + Decoder<T>,
393    <JsonSerdeWasmCodec as Encoder<T>>::Error: Debug, <JsonSerdeWasmCodec as Decoder<T>>::Error: Debug,
394    <<JsonSerdeWasmCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
395    <JsonSerdeWasmCodec as Encoder<T>>::Encoded: IntoEncodedString,
396    <JsonSerdeWasmCodec as Decoder<T>>::Encoded: FromEncodedStr,
397{
398    /// Creates a resource using [`JsonSerdeWasmCodec`] for encoding/decoding the value.
399    #[track_caller]
400    pub fn new_serde_wb(
401fut: impl Future<Output = T> + Send + 'static
402    ) -> Self
403    {
404        ArcOnceResource::new_with_options(fut, false)
405    }
406
407    /// Creates a blocking resource using [`JsonSerdeWasmCodec`] for encoding/decoding the value.
408    ///
409    /// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
410    /// This is useful if you need their data to set HTML document metadata or information that
411    /// needs to appear in HTTP headers.
412    #[track_caller]
413    pub fn new_serde_wb_blocking(
414fut: impl Future<Output = T> + Send + 'static
415    ) -> Self
416    {
417        ArcOnceResource::new_with_options(fut, true)
418    }
419}
420#[cfg(feature = "miniserde")]
421impl<T> ArcOnceResource<T, MiniserdeCodec>
422where
423    T: Send + Sync + 'static,
424    MiniserdeCodec: Encoder<T> + Decoder<T>,
425    <MiniserdeCodec as Encoder<T>>::Error: Debug,
426    <MiniserdeCodec as Decoder<T>>::Error: Debug,
427    <<MiniserdeCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
428        Debug,
429    <MiniserdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
430    <MiniserdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
431{
432    /// Creates a resource using [`MiniserdeCodec`] for encoding/decoding the value.
433    #[track_caller]
434    pub fn new_miniserde(
435        fut: impl Future<Output = T> + Send + 'static,
436    ) -> Self {
437        ArcOnceResource::new_with_options(fut, false)
438    }
439
440    /// Creates a blocking resource using [`MiniserdeCodec`] for encoding/decoding the value.
441    ///
442    /// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
443    /// This is useful if you need their data to set HTML document metadata or information that
444    /// needs to appear in HTTP headers.
445    #[track_caller]
446    pub fn new_miniserde_blocking(
447        fut: impl Future<Output = T> + Send + 'static,
448    ) -> Self {
449        ArcOnceResource::new_with_options(fut, true)
450    }
451}
452
453#[cfg(feature = "serde-lite")]
454impl<T> ArcOnceResource<T, SerdeLite<JsonSerdeCodec>>
455where
456T: Send + Sync + 'static,
457    SerdeLite<JsonSerdeCodec>: Encoder<T> + Decoder<T>,
458    <SerdeLite<JsonSerdeCodec> as Encoder<T>>::Error: Debug, <SerdeLite<JsonSerdeCodec> as Decoder<T>>::Error: Debug,
459    <<SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
460    <SerdeLite<JsonSerdeCodec> as Encoder<T>>::Encoded: IntoEncodedString,
461    <SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded: FromEncodedStr,
462{
463    /// Creates a resource using [`SerdeLite`] for encoding/decoding the value.
464    #[track_caller]
465    pub fn new_serde_lite(
466fut: impl Future<Output = T> + Send + 'static
467    ) -> Self
468    {
469        ArcOnceResource::new_with_options(fut, false)
470    }
471
472    /// Creates a blocking resource using [`SerdeLite`] for encoding/decoding the value.
473    ///
474    /// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
475    /// This is useful if you need their data to set HTML document metadata or information that
476    /// needs to appear in HTTP headers.
477    #[track_caller]
478    pub fn new_serde_lite_blocking(
479fut: impl Future<Output = T> + Send + 'static
480    ) -> Self
481    {
482        ArcOnceResource::new_with_options(fut, true)
483    }
484}
485
486#[cfg(feature = "rkyv")]
487impl<T> ArcOnceResource<T, RkyvCodec>
488where
489    T: Send + Sync + 'static,
490    RkyvCodec: Encoder<T> + Decoder<T>,
491    <RkyvCodec as Encoder<T>>::Error: Debug,
492    <RkyvCodec as Decoder<T>>::Error: Debug,
493    <<RkyvCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
494        Debug,
495    <RkyvCodec as Encoder<T>>::Encoded: IntoEncodedString,
496    <RkyvCodec as Decoder<T>>::Encoded: FromEncodedStr,
497{
498    /// Creates a resource using [`RkyvCodec`] for encoding/decoding the value.
499    #[track_caller]
500    pub fn new_rkyv(fut: impl Future<Output = T> + Send + 'static) -> Self {
501        ArcOnceResource::new_with_options(fut, false)
502    }
503
504    /// Creates a blocking resource using [`RkyvCodec`] for encoding/decoding the value.
505    ///
506    /// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
507    /// This is useful if you need their data to set HTML document metadata or information that
508    /// needs to appear in HTTP headers.
509    #[track_caller]
510    pub fn new_rkyv_blocking(
511        fut: impl Future<Output = T> + Send + 'static,
512    ) -> Self {
513        ArcOnceResource::new_with_options(fut, true)
514    }
515}
516
517/// A resource that only loads once.
518///
519/// Resources allow asynchronously loading data and serializing it from the server to the client,
520/// so that it loads on the server, and is then deserialized on the client. This improves
521/// performance by beginning data loading on the server when the request is made, rather than
522/// beginning it on the client after WASM has been loaded.
523///
524/// You can access the value of the resource either synchronously using `.get()` or asynchronously
525/// using `.await`.
526#[derive(Debug)]
527pub struct OnceResource<T, Ser = JsonSerdeCodec> {
528    inner: ArenaItem<ArcOnceResource<T, Ser>>,
529    #[cfg(any(debug_assertions, leptos_debuginfo))]
530    defined_at: &'static Location<'static>,
531}
532
533impl<T, Ser> Clone for OnceResource<T, Ser> {
534    fn clone(&self) -> Self {
535        *self
536    }
537}
538
539impl<T, Ser> Copy for OnceResource<T, Ser> {}
540
541impl<T, Ser> OnceResource<T, Ser>
542where
543    T: Send + Sync + 'static,
544    Ser: Encoder<T> + Decoder<T>,
545    <Ser as Encoder<T>>::Error: Debug,
546    <Ser as Decoder<T>>::Error: Debug,
547    <<Ser as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
548    <Ser as Encoder<T>>::Encoded: IntoEncodedString,
549    <Ser as Decoder<T>>::Encoded: FromEncodedStr,
550{
551    /// Creates a new resource with the encoding `Ser`. If `blocking` is `true`, this is a blocking
552    /// resource.
553    ///
554    /// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
555    /// This is useful if you need their data to set HTML document metadata or information that
556    /// needs to appear in HTTP headers.
557    #[track_caller]
558    pub fn new_with_options(
559        fut: impl Future<Output = T> + Send + 'static,
560        blocking: bool,
561    ) -> Self {
562        #[cfg(any(debug_assertions, leptos_debuginfo))]
563        let defined_at = Location::caller();
564        Self {
565            inner: ArenaItem::new(ArcOnceResource::new_with_options(
566                fut, blocking,
567            )),
568            #[cfg(any(debug_assertions, leptos_debuginfo))]
569            defined_at,
570        }
571    }
572
573    /// Synchronously, reactively reads the current value of the resource and applies the function
574    /// `f` to its value if it is `Some(_)`.
575    pub fn map<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U> {
576        self.try_with(|n| n.as_ref().map(|n| Some(f(n))))?.flatten()
577    }
578}
579
580impl<T, E, Ser> OnceResource<Result<T, E>, Ser>
581where
582    Ser: Encoder<Result<T, E>> + Decoder<Result<T, E>>,
583    <Ser as Encoder<Result<T, E>>>::Error: Debug,
584    <Ser as Decoder<Result<T, E>>>::Error: Debug,
585    <<Ser as Decoder<Result<T, E>>>::Encoded as FromEncodedStr>::DecodingError:
586        Debug,
587    <Ser as Encoder<Result<T, E>>>::Encoded: IntoEncodedString,
588    <Ser as Decoder<Result<T, E>>>::Encoded: FromEncodedStr,
589    T: Send + Sync + 'static,
590    E: Send + Sync + Clone + 'static,
591{
592    /// Applies the given function when a resource that returns `Result<T, E>`
593    /// has resolved and loaded an `Ok(_)`, rather than requiring nested `.map()`
594    /// calls over the `Option<Result<_, _>>` returned by the resource.
595    ///
596    /// This is useful when used with features like server functions, in conjunction
597    /// with `<ErrorBoundary/>` and `<Suspense/>`, when these other components are
598    /// left to handle the `None` and `Err(_)` states.
599    #[track_caller]
600    pub fn and_then<U>(&self, f: impl FnOnce(&T) -> U) -> Option<Result<U, E>> {
601        self.map(|data| data.as_ref().map(f).map_err(|e| e.clone()))
602    }
603}
604
605impl<T, Ser> OnceResource<T, Ser>
606where
607    T: Send + Sync + 'static,
608    Ser: 'static,
609{
610    /// Returns a `Future` that is ready when this resource has next finished loading.
611    pub fn ready(&self) -> AsyncDerivedReadyFuture {
612        self.inner
613            .try_with_value(|inner| inner.ready())
614            .unwrap_or_else(unwrap_signal!(self))
615    }
616}
617
618impl<T, Ser> DefinedAt for OnceResource<T, Ser> {
619    fn defined_at(&self) -> Option<&'static Location<'static>> {
620        #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
621        {
622            None
623        }
624        #[cfg(any(debug_assertions, leptos_debuginfo))]
625        {
626            Some(self.defined_at)
627        }
628    }
629}
630
631impl<T, Ser> IsDisposed for OnceResource<T, Ser> {
632    #[inline(always)]
633    fn is_disposed(&self) -> bool {
634        false
635    }
636}
637
638impl<T, Ser> ToAnySource for OnceResource<T, Ser>
639where
640    T: Send + Sync + 'static,
641    Ser: 'static,
642{
643    fn to_any_source(&self) -> AnySource {
644        self.inner
645            .try_with_value(|inner| inner.to_any_source())
646            .unwrap_or_else(unwrap_signal!(self))
647    }
648}
649
650impl<T, Ser> Track for OnceResource<T, Ser>
651where
652    T: Send + Sync + 'static,
653    Ser: 'static,
654{
655    fn track(&self) {
656        if let Some(inner) = self.inner.try_get_value() {
657            inner.track();
658        }
659    }
660}
661
662impl<T, Ser> ReadUntracked for OnceResource<T, Ser>
663where
664    T: Send + Sync + 'static,
665    Ser: 'static,
666{
667    type Value = ReadGuard<Option<T>, Plain<Option<T>>>;
668
669    fn try_read_untracked(&self) -> Option<Self::Value> {
670        self.inner
671            .try_with_value(|inner| inner.try_read_untracked())
672            .flatten()
673    }
674}
675
676impl<T, Ser> IntoFuture for OnceResource<T, Ser>
677where
678    T: Clone + Send + Sync + 'static,
679    Ser: 'static,
680{
681    type Output = T;
682    type IntoFuture = OnceResourceFuture<T>;
683
684    fn into_future(self) -> Self::IntoFuture {
685        self.inner
686            .try_get_value()
687            .unwrap_or_else(unwrap_signal!(self))
688            .into_future()
689    }
690}
691
692impl<T> OnceResource<T, JsonSerdeCodec>
693where
694    T: Send + Sync + 'static,
695    JsonSerdeCodec: Encoder<T> + Decoder<T>,
696    <JsonSerdeCodec as Encoder<T>>::Error: Debug,
697    <JsonSerdeCodec as Decoder<T>>::Error: Debug,
698    <<JsonSerdeCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
699        Debug,
700    <JsonSerdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
701    <JsonSerdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
702{
703    /// Creates a resource using [`JsonSerdeCodec`] for encoding/decoding the value.
704    #[track_caller]
705    pub fn new(fut: impl Future<Output = T> + Send + 'static) -> Self {
706        OnceResource::new_with_options(fut, false)
707    }
708
709    /// Creates a blocking resource using [`JsonSerdeCodec`] for encoding/decoding the value.
710    ///
711    /// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
712    /// This is useful if you need their data to set HTML document metadata or information that
713    /// needs to appear in HTTP headers.
714    #[track_caller]
715    pub fn new_blocking(fut: impl Future<Output = T> + Send + 'static) -> Self {
716        OnceResource::new_with_options(fut, true)
717    }
718}
719
720impl<T> OnceResource<T, FromToStringCodec>
721where
722T: Send + Sync + 'static,
723    FromToStringCodec: Encoder<T> + Decoder<T>,
724    <FromToStringCodec as Encoder<T>>::Error: Debug, <FromToStringCodec as Decoder<T>>::Error: Debug,
725    <<FromToStringCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
726    <FromToStringCodec as Encoder<T>>::Encoded: IntoEncodedString,
727    <FromToStringCodec as Decoder<T>>::Encoded: FromEncodedStr,
728{
729    /// Creates a resource using [`FromToStringCodec`] for encoding/decoding the value.
730    pub fn new_str(
731        fut: impl Future<Output = T> + Send + 'static
732    ) -> Self
733    {
734        OnceResource::new_with_options(fut, false)
735    }
736
737    /// Creates a blocking resource using [`FromToStringCodec`] for encoding/decoding the value.
738    ///
739    /// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
740    /// This is useful if you need their data to set HTML document metadata or information that
741    /// needs to appear in HTTP headers.
742    pub fn new_str_blocking(
743        fut: impl Future<Output = T> + Send + 'static
744    ) -> Self
745    {
746        OnceResource::new_with_options(fut, true)
747    }
748}
749
750#[cfg(feature = "serde-wasm-bindgen")]
751impl<T> OnceResource<T, JsonSerdeWasmCodec>
752where
753T: Send + Sync + 'static,
754    JsonSerdeWasmCodec: Encoder<T> + Decoder<T>,
755    <JsonSerdeWasmCodec as Encoder<T>>::Error: Debug, <JsonSerdeWasmCodec as Decoder<T>>::Error: Debug,
756    <<JsonSerdeWasmCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
757    <JsonSerdeWasmCodec as Encoder<T>>::Encoded: IntoEncodedString,
758    <JsonSerdeWasmCodec as Decoder<T>>::Encoded: FromEncodedStr,
759{
760    /// Creates a resource using [`JsonSerdeWasmCodec`] for encoding/decoding the value.
761    #[track_caller]
762    pub fn new_serde_wb(
763fut: impl Future<Output = T> + Send + 'static
764    ) -> Self
765    {
766        OnceResource::new_with_options(fut, false)
767    }
768
769    /// Creates a blocking resource using [`JsonSerdeWasmCodec`] for encoding/decoding the value.
770    ///
771    /// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
772    /// This is useful if you need their data to set HTML document metadata or information that
773    /// needs to appear in HTTP headers.
774    #[track_caller]
775    pub fn new_serde_wb_blocking(
776fut: impl Future<Output = T> + Send + 'static
777    ) -> Self
778    {
779        OnceResource::new_with_options(fut, true)
780    }
781}
782#[cfg(feature = "miniserde")]
783impl<T> OnceResource<T, MiniserdeCodec>
784where
785    T: Send + Sync + 'static,
786    MiniserdeCodec: Encoder<T> + Decoder<T>,
787    <MiniserdeCodec as Encoder<T>>::Error: Debug,
788    <MiniserdeCodec as Decoder<T>>::Error: Debug,
789    <<MiniserdeCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
790        Debug,
791    <MiniserdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
792    <MiniserdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
793{
794    /// Creates a resource using [`MiniserdeCodec`] for encoding/decoding the value.
795    #[track_caller]
796    pub fn new_miniserde(
797        fut: impl Future<Output = T> + Send + 'static,
798    ) -> Self {
799        OnceResource::new_with_options(fut, false)
800    }
801
802    /// Creates a blocking resource using [`MiniserdeCodec`] for encoding/decoding the value.
803    ///
804    /// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
805    /// This is useful if you need their data to set HTML document metadata or information that
806    /// needs to appear in HTTP headers.
807    #[track_caller]
808    pub fn new_miniserde_blocking(
809        fut: impl Future<Output = T> + Send + 'static,
810    ) -> Self {
811        OnceResource::new_with_options(fut, true)
812    }
813}
814
815#[cfg(feature = "serde-lite")]
816impl<T> OnceResource<T, SerdeLite<JsonSerdeCodec>>
817where
818T: Send + Sync + 'static,
819    SerdeLite<JsonSerdeCodec>: Encoder<T> + Decoder<T>,
820    <SerdeLite<JsonSerdeCodec> as Encoder<T>>::Error: Debug, <SerdeLite<JsonSerdeCodec> as Decoder<T>>::Error: Debug,
821    <<SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
822    <SerdeLite<JsonSerdeCodec> as Encoder<T>>::Encoded: IntoEncodedString,
823    <SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded: FromEncodedStr,
824{
825    /// Creates a resource using [`SerdeLite`] for encoding/decoding the value.
826    #[track_caller]
827    pub fn new_serde_lite(
828fut: impl Future<Output = T> + Send + 'static
829    ) -> Self
830    {
831        OnceResource::new_with_options(fut, false)
832    }
833
834    /// Creates a blocking resource using [`SerdeLite`] for encoding/decoding the value.
835    ///
836    /// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
837    /// This is useful if you need their data to set HTML document metadata or information that
838    /// needs to appear in HTTP headers.
839    #[track_caller]
840    pub fn new_serde_lite_blocking(
841fut: impl Future<Output = T> + Send + 'static
842    ) -> Self
843    {
844        OnceResource::new_with_options(fut, true)
845    }
846}
847
848#[cfg(feature = "rkyv")]
849impl<T> OnceResource<T, RkyvCodec>
850where
851    T: Send + Sync + 'static,
852    RkyvCodec: Encoder<T> + Decoder<T>,
853    <RkyvCodec as Encoder<T>>::Error: Debug,
854    <RkyvCodec as Decoder<T>>::Error: Debug,
855    <<RkyvCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
856        Debug,
857    <RkyvCodec as Encoder<T>>::Encoded: IntoEncodedString,
858    <RkyvCodec as Decoder<T>>::Encoded: FromEncodedStr,
859{
860    /// Creates a resource using [`RkyvCodec`] for encoding/decoding the value.
861    #[track_caller]
862    pub fn new_rkyv(fut: impl Future<Output = T> + Send + 'static) -> Self {
863        OnceResource::new_with_options(fut, false)
864    }
865
866    /// Creates a blocking resource using [`RkyvCodec`] for encoding/decoding the value.
867    ///
868    /// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
869    /// This is useful if you need their data to set HTML document metadata or information that
870    /// needs to appear in HTTP headers.
871    #[track_caller]
872    pub fn new_rkyv_blocking(
873        fut: impl Future<Output = T> + Send + 'static,
874    ) -> Self {
875        OnceResource::new_with_options(fut, true)
876    }
877}