1use crate::{FromEncodedStr, IntoEncodedString};
2#[cfg(feature = "rkyv")]
3use codee::binary::RkyvCodec;
4#[cfg(feature = "serde-wasm-bindgen")]
5use codee::string::JsonSerdeWasmCodec;
6#[cfg(feature = "miniserde")]
7use codee::string::MiniserdeCodec;
8#[cfg(feature = "serde-lite")]
9use codee::SerdeLite;
10use codee::{
11 string::{FromToStringCodec, JsonSerdeCodec},
12 Decoder, Encoder,
13};
14use core::{fmt::Debug, marker::PhantomData};
15use futures::Future;
16use hydration_context::{SerializedDataId, SharedContext};
17use reactive_graph::{
18 computed::{
19 ArcAsyncDerived, ArcMemo, AsyncDerived, AsyncDerivedFuture,
20 AsyncDerivedRefFuture,
21 },
22 graph::{Source, ToAnySubscriber},
23 owner::Owner,
24 prelude::*,
25 signal::{ArcRwSignal, RwSignal},
26};
27use std::{
28 future::{pending, IntoFuture},
29 ops::{Deref, DerefMut},
30 panic::Location,
31 sync::{
32 atomic::{AtomicBool, Ordering},
33 Arc,
34 },
35};
36
37pub(crate) static IS_SUPPRESSING_RESOURCE_LOAD: AtomicBool =
38 AtomicBool::new(false);
39
40pub struct SuppressResourceLoad;
43
44impl SuppressResourceLoad {
45 pub fn new() -> Self {
47 IS_SUPPRESSING_RESOURCE_LOAD.store(true, Ordering::Relaxed);
48 Self
49 }
50}
51
52impl Default for SuppressResourceLoad {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl Drop for SuppressResourceLoad {
59 fn drop(&mut self) {
60 IS_SUPPRESSING_RESOURCE_LOAD.store(false, Ordering::Relaxed);
61 }
62}
63
64pub struct ArcResource<T, Ser = JsonSerdeCodec> {
74 ser: PhantomData<Ser>,
75 refetch: ArcRwSignal<usize>,
76 data: ArcAsyncDerived<T>,
77 #[cfg(any(debug_assertions, leptos_debuginfo))]
78 defined_at: &'static Location<'static>,
79}
80
81impl<T, Ser> Debug for ArcResource<T, Ser> {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 let mut d = f.debug_struct("ArcResource");
84 d.field("ser", &self.ser).field("data", &self.data);
85 #[cfg(any(debug_assertions, leptos_debuginfo))]
86 d.field("defined_at", self.defined_at);
87 d.finish_non_exhaustive()
88 }
89}
90
91impl<T, Ser> From<ArcResource<T, Ser>> for Resource<T, Ser>
92where
93 T: Send + Sync,
94{
95 #[track_caller]
96 fn from(arc_resource: ArcResource<T, Ser>) -> Self {
97 Resource {
98 ser: PhantomData,
99 data: arc_resource.data.into(),
100 refetch: arc_resource.refetch.into(),
101 #[cfg(any(debug_assertions, leptos_debuginfo))]
102 defined_at: Location::caller(),
103 }
104 }
105}
106
107impl<T, Ser> From<Resource<T, Ser>> for ArcResource<T, Ser>
108where
109 T: Send + Sync,
110{
111 #[track_caller]
112 fn from(resource: Resource<T, Ser>) -> Self {
113 ArcResource {
114 ser: PhantomData,
115 data: resource.data.into(),
116 refetch: resource.refetch.into(),
117 #[cfg(any(debug_assertions, leptos_debuginfo))]
118 defined_at: Location::caller(),
119 }
120 }
121}
122
123impl<T, Ser> DefinedAt for ArcResource<T, Ser> {
124 fn defined_at(&self) -> Option<&'static Location<'static>> {
125 #[cfg(any(debug_assertions, leptos_debuginfo))]
126 {
127 Some(self.defined_at)
128 }
129 #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
130 {
131 None
132 }
133 }
134}
135
136impl<T, Ser> Clone for ArcResource<T, Ser> {
137 fn clone(&self) -> Self {
138 Self {
139 ser: self.ser,
140 refetch: self.refetch.clone(),
141 data: self.data.clone(),
142 #[cfg(any(debug_assertions, leptos_debuginfo))]
143 defined_at: self.defined_at,
144 }
145 }
146}
147
148impl<T, Ser> Deref for ArcResource<T, Ser> {
149 type Target = ArcAsyncDerived<T>;
150
151 fn deref(&self) -> &Self::Target {
152 &self.data
153 }
154}
155
156impl<T, Ser> Track for ArcResource<T, Ser>
157where
158 T: 'static,
159{
160 fn track(&self) {
161 self.data.track();
162 }
163}
164
165impl<T, Ser> Notify for ArcResource<T, Ser>
166where
167 T: 'static,
168{
169 fn notify(&self) {
170 self.data.notify()
171 }
172}
173
174impl<T, Ser> Write for ArcResource<T, Ser>
175where
176 T: 'static,
177{
178 type Value = Option<T>;
179
180 fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
181 self.data.try_write()
182 }
183
184 fn try_write_untracked(
185 &self,
186 ) -> Option<impl DerefMut<Target = Self::Value>> {
187 self.data.try_write_untracked()
188 }
189}
190
191impl<T, Ser> ReadUntracked for ArcResource<T, Ser>
192where
193 T: 'static,
194{
195 type Value = <ArcAsyncDerived<T> as ReadUntracked>::Value;
196
197 #[track_caller]
198 fn try_read_untracked(&self) -> Option<Self::Value> {
199 #[cfg(all(feature = "hydration", debug_assertions))]
200 {
201 use reactive_graph::{
202 computed::suspense::SuspenseContext, effect::in_effect_scope,
203 owner::use_context,
204 };
205 if !in_effect_scope() && use_context::<SuspenseContext>().is_none()
206 {
207 let location = std::panic::Location::caller();
208 reactive_graph::log_warning(format_args!(
209 "At {location}, you are reading a resource in `hydrate` \
210 mode outside a <Suspense/> or <Transition/> or effect. \
211 This can cause hydration mismatch errors and loses out \
212 on a significant performance optimization. To fix this \
213 issue, you can either: \n1. Wrap the place where you \
214 read the resource in a <Suspense/> or <Transition/> \
215 component, or \n2. Switch to using \
216 ArcLocalResource::new(), which will wait to load the \
217 resource until the app is hydrated on the client side. \
218 (This will have worse performance in most cases.)",
219 ));
220 }
221 }
222 self.data.try_read_untracked()
223 }
224}
225
226impl<T, Ser> ArcResource<T, Ser>
227where
228 Ser: Encoder<T> + Decoder<T>,
229 <Ser as Encoder<T>>::Error: Debug,
230 <Ser as Decoder<T>>::Error: Debug,
231 <<Ser as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
232 <Ser as Encoder<T>>::Encoded: IntoEncodedString,
233 <Ser as Decoder<T>>::Encoded: FromEncodedStr,
234{
235 #[track_caller]
251 pub fn new_with_options<S, Fut>(
252 source: impl Fn() -> S + Send + Sync + 'static,
253 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
254 #[allow(unused)] blocking: bool,
256 ) -> ArcResource<T, Ser>
257 where
258 S: PartialEq + Clone + Send + Sync + 'static,
259 T: Send + Sync + 'static,
260 Fut: Future<Output = T> + Send + 'static,
261 {
262 let shared_context = Owner::current_shared_context();
263 let id = shared_context
264 .as_ref()
265 .map(|sc| sc.next_id())
266 .unwrap_or_default();
267
268 let initial = initial_value::<T, Ser>(&id, shared_context.as_ref());
269 let is_ready = initial.is_some();
270
271 let refetch = ArcRwSignal::new(0);
272 let source = ArcMemo::new({
273 let refetch = refetch.clone();
274 move |_| (refetch.get(), source())
275 });
276 let fun = {
277 let source = source.clone();
278 move || {
279 let (_, source) = source.get();
280 let fut = fetcher(source);
281 async move {
282 if IS_SUPPRESSING_RESOURCE_LOAD.load(Ordering::Relaxed) {
283 pending().await
284 } else {
285 fut.await
286 }
287 }
288 }
289 };
290
291 let data = ArcAsyncDerived::new_with_manual_dependencies(
292 initial, fun, &source,
293 );
294 if is_ready {
295 source.with_untracked(|_| ());
296 source.add_subscriber(data.to_any_subscriber());
297 }
298
299 #[cfg(feature = "ssr")]
300 if let Some(shared_context) = shared_context {
301 let value = data.clone();
302 let ready_fut = data.ready();
303
304 if blocking {
305 shared_context.defer_stream(Box::pin(data.ready()));
306 }
307
308 if shared_context.get_is_hydrating() {
309 shared_context.write_async(
310 id,
311 Box::pin(async move {
312 ready_fut.await;
313 value.with_untracked(|data| match &data {
314 Some(val) => {
316 Ser::encode(val).unwrap().into_encoded_string()
317 }
318 _ => unreachable!(),
319 })
320 }),
321 );
322 }
323 }
324
325 ArcResource {
326 ser: PhantomData,
327 data,
328 refetch,
329 #[cfg(any(debug_assertions, leptos_debuginfo))]
330 defined_at: Location::caller(),
331 }
332 }
333
334 #[track_caller]
337 pub fn map<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U>
338 where
339 T: Send + Sync + 'static,
340 {
341 self.data.try_with(|n| n.as_ref().map(f))?
342 }
343
344 pub fn refetch(&self) {
346 *self.refetch.write() += 1;
347 }
348}
349
350#[inline(always)]
351#[allow(unused)]
352pub(crate) fn initial_value<T, Ser>(
353 id: &SerializedDataId,
354 shared_context: Option<&Arc<dyn SharedContext + Send + Sync>>,
355) -> Option<T>
356where
357 Ser: Encoder<T> + Decoder<T>,
358 <Ser as Encoder<T>>::Error: Debug,
359 <Ser as Decoder<T>>::Error: Debug,
360 <<Ser as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
361 <Ser as Encoder<T>>::Encoded: IntoEncodedString,
362 <Ser as Decoder<T>>::Encoded: FromEncodedStr,
363{
364 #[cfg(feature = "hydration")]
365 {
366 use std::borrow::Borrow;
367
368 let shared_context = Owner::current_shared_context();
369 if let Some(shared_context) = shared_context {
370 let value = shared_context.read_data(id);
371 if let Some(value) = value {
372 let encoded =
373 match <Ser as Decoder<T>>::Encoded::from_encoded_str(&value)
374 {
375 Ok(value) => value,
376 Err(e) => {
377 #[cfg(feature = "tracing")]
378 tracing::error!("couldn't deserialize: {e:?}");
379 return None;
380 }
381 };
382 let encoded = encoded.borrow();
383 match Ser::decode(encoded) {
384 Ok(value) => return Some(value),
385 #[allow(unused)]
386 Err(e) => {
387 #[cfg(feature = "tracing")]
388 tracing::error!("couldn't deserialize: {e:?}");
389 }
390 }
391 }
392 }
393 }
394 None
395}
396
397impl<T, E, Ser> ArcResource<Result<T, E>, Ser>
398where
399 Ser: Encoder<Result<T, E>> + Decoder<Result<T, E>>,
400 <Ser as Encoder<Result<T, E>>>::Error: Debug,
401 <Ser as Decoder<Result<T, E>>>::Error: Debug,
402 <<Ser as Decoder<Result<T, E>>>::Encoded as FromEncodedStr>::DecodingError:
403 Debug,
404 <Ser as Encoder<Result<T, E>>>::Encoded: IntoEncodedString,
405 <Ser as Decoder<Result<T, E>>>::Encoded: FromEncodedStr,
406 T: Send + Sync + 'static,
407 E: Send + Sync + Clone + 'static,
408{
409 #[track_caller]
417 pub fn and_then<U>(&self, f: impl FnOnce(&T) -> U) -> Option<Result<U, E>> {
418 self.map(|data| data.as_ref().map(f).map_err(|e| e.clone()))
419 }
420}
421
422impl<T> ArcResource<T, JsonSerdeCodec>
423where
424 JsonSerdeCodec: Encoder<T> + Decoder<T>,
425 <JsonSerdeCodec as Encoder<T>>::Error: Debug,
426 <JsonSerdeCodec as Decoder<T>>::Error: Debug,
427 <<JsonSerdeCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
428 Debug,
429 <JsonSerdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
430 <JsonSerdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
431{
432 #[track_caller]
442 pub fn new<S, Fut>(
443 source: impl Fn() -> S + Send + Sync + 'static,
444 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
445 ) -> Self
446 where
447 S: PartialEq + Clone + Send + Sync + 'static,
448 T: Send + Sync + 'static,
449 Fut: Future<Output = T> + Send + 'static,
450 {
451 ArcResource::new_with_options(source, fetcher, false)
452 }
453
454 #[track_caller]
468 pub fn new_blocking<S, Fut>(
469 source: impl Fn() -> S + Send + Sync + 'static,
470 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
471 ) -> Self
472 where
473 S: PartialEq + Clone + Send + Sync + 'static,
474 T: Send + Sync + 'static,
475 Fut: Future<Output = T> + Send + 'static,
476 {
477 ArcResource::new_with_options(source, fetcher, true)
478 }
479}
480
481impl<T> ArcResource<T, FromToStringCodec>
482where
483 FromToStringCodec: Encoder<T> + Decoder<T>,
484 <FromToStringCodec as Encoder<T>>::Error: Debug, <FromToStringCodec as Decoder<T>>::Error: Debug,
485 <<FromToStringCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
486 <FromToStringCodec as Encoder<T>>::Encoded: IntoEncodedString,
487 <FromToStringCodec as Decoder<T>>::Encoded: FromEncodedStr,
488{
489 pub fn new_str<S, Fut>(
499 source: impl Fn() -> S + Send + Sync + 'static,
500 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
501 ) -> Self
502 where
503 S: PartialEq + Clone + Send + Sync + 'static,
504 T: Send + Sync + 'static,
505 Fut: Future<Output = T> + Send + 'static,
506 {
507 ArcResource::new_with_options(source, fetcher, false)
508 }
509
510 pub fn new_str_blocking<S, Fut>(
524 source: impl Fn() -> S + Send + Sync + 'static,
525 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
526 ) -> Self
527 where
528 S: PartialEq + Clone + Send + Sync + 'static,
529 T: Send + Sync + 'static,
530 Fut: Future<Output = T> + Send + 'static,
531 {
532 ArcResource::new_with_options(source, fetcher, true)
533 }
534}
535
536#[cfg(feature = "serde-wasm-bindgen")]
537impl<T> ArcResource<T, JsonSerdeWasmCodec>
538where
539 JsonSerdeWasmCodec: Encoder<T> + Decoder<T>,
540 <JsonSerdeWasmCodec as Encoder<T>>::Error: Debug, <JsonSerdeWasmCodec as Decoder<T>>::Error: Debug,
541 <<JsonSerdeWasmCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
542 <JsonSerdeWasmCodec as Encoder<T>>::Encoded: IntoEncodedString,
543 <JsonSerdeWasmCodec as Decoder<T>>::Encoded: FromEncodedStr,
544{
545 #[track_caller]
555 pub fn new_serde_wb<S, Fut>(
556 source: impl Fn() -> S + Send + Sync + 'static,
557 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
558 ) -> Self
559 where
560 S: PartialEq + Clone + Send + Sync + 'static,
561 T: Send + Sync + 'static,
562 Fut: Future<Output = T> + Send + 'static,
563 {
564 ArcResource::new_with_options(source, fetcher, false)
565 }
566
567 #[track_caller]
581 pub fn new_serde_wb_blocking<S, Fut>(
582 source: impl Fn() -> S + Send + Sync + 'static,
583 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
584 ) -> Self
585 where
586 S: PartialEq + Clone + Send + Sync + 'static,
587 T: Send + Sync + 'static,
588 Fut: Future<Output = T> + Send + 'static,
589 {
590 ArcResource::new_with_options(source, fetcher, true)
591 }
592}
593#[cfg(feature = "miniserde")]
594impl<T> ArcResource<T, MiniserdeCodec>
595where
596 MiniserdeCodec: Encoder<T> + Decoder<T>,
597 <MiniserdeCodec as Encoder<T>>::Error: Debug,
598 <MiniserdeCodec as Decoder<T>>::Error: Debug,
599 <<MiniserdeCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
600 Debug,
601 <MiniserdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
602 <MiniserdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
603{
604 #[track_caller]
614 pub fn new_miniserde<S, Fut>(
615 source: impl Fn() -> S + Send + Sync + 'static,
616 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
617 ) -> Self
618 where
619 S: PartialEq + Clone + Send + Sync + 'static,
620 T: Send + Sync + 'static,
621 Fut: Future<Output = T> + Send + 'static,
622 {
623 ArcResource::new_with_options(source, fetcher, false)
624 }
625
626 #[track_caller]
640 pub fn new_miniserde_blocking<S, Fut>(
641 source: impl Fn() -> S + Send + Sync + 'static,
642 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
643 ) -> Self
644 where
645 S: PartialEq + Clone + Send + Sync + 'static,
646 T: Send + Sync + 'static,
647 Fut: Future<Output = T> + Send + 'static,
648 {
649 ArcResource::new_with_options(source, fetcher, true)
650 }
651}
652
653#[cfg(feature = "serde-lite")]
654impl<T> ArcResource<T, SerdeLite<JsonSerdeCodec>>
655where
656 SerdeLite<JsonSerdeCodec>: Encoder<T> + Decoder<T>,
657 <SerdeLite<JsonSerdeCodec> as Encoder<T>>::Error: Debug, <SerdeLite<JsonSerdeCodec> as Decoder<T>>::Error: Debug,
658 <<SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
659 <SerdeLite<JsonSerdeCodec> as Encoder<T>>::Encoded: IntoEncodedString,
660 <SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded: FromEncodedStr,
661{
662 #[track_caller]
672 pub fn new_serde_lite<S, Fut>(
673 source: impl Fn() -> S + Send + Sync + 'static,
674 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
675 ) -> Self
676 where
677 S: PartialEq + Clone + Send + Sync + 'static,
678 T: Send + Sync + 'static,
679 Fut: Future<Output = T> + Send + 'static,
680 {
681 ArcResource::new_with_options(source, fetcher, false)
682 }
683
684 #[track_caller]
698 pub fn new_serde_lite_blocking<S, Fut>(
699 source: impl Fn() -> S + Send + Sync + 'static,
700 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
701 ) -> Self
702 where
703 S: PartialEq + Clone + Send + Sync + 'static,
704 T: Send + Sync + 'static,
705 Fut: Future<Output = T> + Send + 'static,
706 {
707 ArcResource::new_with_options(source, fetcher, true)
708 }
709}
710
711#[cfg(feature = "rkyv")]
712impl<T> ArcResource<T, RkyvCodec>
713where
714 RkyvCodec: Encoder<T> + Decoder<T>,
715 <RkyvCodec as Encoder<T>>::Error: Debug,
716 <RkyvCodec as Decoder<T>>::Error: Debug,
717 <<RkyvCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
718 Debug,
719 <RkyvCodec as Encoder<T>>::Encoded: IntoEncodedString,
720 <RkyvCodec as Decoder<T>>::Encoded: FromEncodedStr,
721{
722 #[track_caller]
732 pub fn new_rkyv<S, Fut>(
733 source: impl Fn() -> S + Send + Sync + 'static,
734 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
735 ) -> Self
736 where
737 S: PartialEq + Clone + Send + Sync + 'static,
738 T: Send + Sync + 'static,
739 Fut: Future<Output = T> + Send + 'static,
740 {
741 ArcResource::new_with_options(source, fetcher, false)
742 }
743
744 #[track_caller]
758 pub fn new_rkyv_blocking<S, Fut>(
759 source: impl Fn() -> S + Send + Sync + 'static,
760 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
761 ) -> Self
762 where
763 S: PartialEq + Clone + Send + Sync + 'static,
764 T: Send + Sync + 'static,
765 Fut: Future<Output = T> + Send + 'static,
766 {
767 ArcResource::new_with_options(source, fetcher, true)
768 }
769}
770
771impl<T, Ser> IntoFuture for ArcResource<T, Ser>
772where
773 T: Clone + 'static,
774{
775 type Output = T;
776 type IntoFuture = AsyncDerivedFuture<T>;
777
778 fn into_future(self) -> Self::IntoFuture {
779 self.data.into_future()
780 }
781}
782
783impl<T, Ser> ArcResource<T, Ser>
784where
785 T: 'static,
786{
787 pub fn by_ref(&self) -> AsyncDerivedRefFuture<T> {
790 self.data.by_ref()
791 }
792}
793
794pub struct Resource<T, Ser = JsonSerdeCodec>
804where
805 T: Send + Sync + 'static,
806{
807 ser: PhantomData<Ser>,
808 data: AsyncDerived<T>,
809 refetch: RwSignal<usize>,
810 #[cfg(any(debug_assertions, leptos_debuginfo))]
811 defined_at: &'static Location<'static>,
812}
813
814impl<T, Ser> Debug for Resource<T, Ser>
815where
816 T: Send + Sync + 'static,
817{
818 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
819 let mut d = f.debug_struct("ArcResource");
820 d.field("ser", &self.ser).field("data", &self.data);
821 #[cfg(any(debug_assertions, leptos_debuginfo))]
822 d.field("defined_at", self.defined_at);
823 d.finish_non_exhaustive()
824 }
825}
826
827impl<T, Ser> DefinedAt for Resource<T, Ser>
828where
829 T: Send + Sync + 'static,
830{
831 fn defined_at(&self) -> Option<&'static Location<'static>> {
832 #[cfg(any(debug_assertions, leptos_debuginfo))]
833 {
834 Some(self.defined_at)
835 }
836 #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
837 {
838 None
839 }
840 }
841}
842
843impl<T: Send + Sync + 'static, Ser> Copy for Resource<T, Ser> {}
844
845impl<T: Send + Sync + 'static, Ser> Clone for Resource<T, Ser> {
846 fn clone(&self) -> Self {
847 *self
848 }
849}
850
851impl<T, Ser> Deref for Resource<T, Ser>
852where
853 T: Send + Sync + 'static,
854{
855 type Target = AsyncDerived<T>;
856
857 fn deref(&self) -> &Self::Target {
858 &self.data
859 }
860}
861
862impl<T, Ser> Track for Resource<T, Ser>
863where
864 T: Send + Sync + 'static,
865{
866 fn track(&self) {
867 self.data.track();
868 }
869}
870
871impl<T, Ser> Notify for Resource<T, Ser>
872where
873 T: Send + Sync + 'static,
874{
875 fn notify(&self) {
876 self.data.notify()
877 }
878}
879
880impl<T, Ser> Write for Resource<T, Ser>
881where
882 T: Send + Sync + 'static,
883{
884 type Value = Option<T>;
885
886 fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
887 self.data.try_write()
888 }
889
890 fn try_write_untracked(
891 &self,
892 ) -> Option<impl DerefMut<Target = Self::Value>> {
893 self.data.try_write_untracked()
894 }
895}
896
897impl<T, Ser> ReadUntracked for Resource<T, Ser>
898where
899 T: Send + Sync + 'static,
900{
901 type Value = <AsyncDerived<T> as ReadUntracked>::Value;
902
903 #[track_caller]
904 fn try_read_untracked(&self) -> Option<Self::Value> {
905 #[cfg(all(feature = "hydration", debug_assertions))]
906 {
907 use reactive_graph::{
908 computed::suspense::SuspenseContext, effect::in_effect_scope,
909 owner::use_context,
910 };
911 if !in_effect_scope() && use_context::<SuspenseContext>().is_none()
912 {
913 let location = std::panic::Location::caller();
914 reactive_graph::log_warning(format_args!(
915 "At {location}, you are reading a resource in `hydrate` \
916 mode outside a <Suspense/> or <Transition/> or effect. \
917 This can cause hydration mismatch errors and loses out \
918 on a significant performance optimization. To fix this \
919 issue, you can either: \n1. Wrap the place where you \
920 read the resource in a <Suspense/> or <Transition/> \
921 component, or \n2. Switch to using LocalResource::new(), \
922 which will wait to load the resource until the app is \
923 hydrated on the client side. (This will have worse \
924 performance in most cases.)",
925 ));
926 }
927 }
928 self.data.try_read_untracked()
929 }
930}
931
932impl<T> Resource<T, FromToStringCodec>
933where
934 FromToStringCodec: Encoder<T> + Decoder<T>,
935 <FromToStringCodec as Encoder<T>>::Error: Debug, <FromToStringCodec as Decoder<T>>::Error: Debug,
936 <<FromToStringCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
937 <FromToStringCodec as Encoder<T>>::Encoded: IntoEncodedString,
938 <FromToStringCodec as Decoder<T>>::Encoded: FromEncodedStr,
939 T: Send + Sync,
940{
941 #[track_caller]
951 pub fn new_str<S, Fut>(
952 source: impl Fn() -> S + Send + Sync + 'static,
953 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
954 ) -> Self
955 where
956 S: PartialEq + Clone + Send + Sync + 'static,
957 T: Send + Sync + 'static,
958 Fut: Future<Output = T> + Send + 'static,
959 {
960 Resource::new_with_options(source, fetcher, false)
961 }
962
963 #[track_caller]
977 pub fn new_str_blocking<S, Fut>(
978 source: impl Fn() -> S + Send + Sync + 'static,
979 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
980 ) -> Self
981 where
982 S: PartialEq + Clone + Send + Sync + 'static,
983 T: Send + Sync + 'static,
984 Fut: Future<Output = T> + Send + 'static,
985 {
986 Resource::new_with_options(source, fetcher, true)
987 }
988}
989
990impl<T> Resource<T, JsonSerdeCodec>
991where
992 JsonSerdeCodec: Encoder<T> + Decoder<T>,
993 <JsonSerdeCodec as Encoder<T>>::Error: Debug,
994 <JsonSerdeCodec as Decoder<T>>::Error: Debug,
995 <<JsonSerdeCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
996 Debug,
997 <JsonSerdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
998 <JsonSerdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
999 T: Send + Sync,
1000{
1001 #[track_caller]
1011 pub fn new<S, Fut>(
1012 source: impl Fn() -> S + Send + Sync + 'static,
1013 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
1014 ) -> Self
1015 where
1016 S: PartialEq + Clone + Send + Sync + 'static,
1017 T: Send + Sync + 'static,
1018 Fut: Future<Output = T> + Send + 'static,
1019 {
1020 Resource::new_with_options(source, fetcher, false)
1021 }
1022
1023 #[track_caller]
1037 pub fn new_blocking<S, Fut>(
1038 source: impl Fn() -> S + Send + Sync + 'static,
1039 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
1040 ) -> Self
1041 where
1042 S: PartialEq + Clone + Send + Sync + 'static,
1043 T: Send + Sync + 'static,
1044 Fut: Future<Output = T> + Send + 'static,
1045 {
1046 Resource::new_with_options(source, fetcher, true)
1047 }
1048}
1049
1050#[cfg(feature = "serde-wasm-bindgen")]
1051impl<T> Resource<T, JsonSerdeWasmCodec>
1052where
1053 JsonSerdeWasmCodec: Encoder<T> + Decoder<T>,
1054 <JsonSerdeWasmCodec as Encoder<T>>::Error: Debug, <JsonSerdeWasmCodec as Decoder<T>>::Error: Debug,
1055 <<JsonSerdeWasmCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
1056 <JsonSerdeWasmCodec as Encoder<T>>::Encoded: IntoEncodedString,
1057 <JsonSerdeWasmCodec as Decoder<T>>::Encoded: FromEncodedStr,
1058 T: Send + Sync,
1059{
1060 pub fn new_serde_wb<S, Fut>(
1070 source: impl Fn() -> S + Send + Sync + 'static,
1071 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
1072 ) -> Self
1073 where
1074 S: PartialEq + Clone + Send + Sync + 'static,
1075 T: Send + Sync + 'static,
1076 Fut: Future<Output = T> + Send + 'static,
1077 {
1078 Resource::new_with_options(source, fetcher, false)
1079 }
1080
1081 pub fn new_serde_wb_blocking<S, Fut>(
1095 source: impl Fn() -> S + Send + Sync + 'static,
1096 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
1097 ) -> Self
1098 where
1099 S: PartialEq + Clone + Send + Sync + 'static,
1100 T: Send + Sync + 'static,
1101 Fut: Future<Output = T> + Send + 'static,
1102 {
1103 Resource::new_with_options(source, fetcher, true)
1104 }
1105}
1106
1107#[cfg(feature = "miniserde")]
1108impl<T> Resource<T, MiniserdeCodec>
1109where
1110 MiniserdeCodec: Encoder<T> + Decoder<T>,
1111 <MiniserdeCodec as Encoder<T>>::Error: Debug,
1112 <MiniserdeCodec as Decoder<T>>::Error: Debug,
1113 <<MiniserdeCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
1114 Debug,
1115 <MiniserdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
1116 <MiniserdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
1117 T: Send + Sync,
1118{
1119 pub fn new_miniserde<S, Fut>(
1129 source: impl Fn() -> S + Send + Sync + 'static,
1130 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
1131 ) -> Self
1132 where
1133 S: PartialEq + Clone + Send + Sync + 'static,
1134 T: Send + Sync + 'static,
1135 Fut: Future<Output = T> + Send + 'static,
1136 {
1137 Resource::new_with_options(source, fetcher, false)
1138 }
1139
1140 pub fn new_miniserde_blocking<S, Fut>(
1154 source: impl Fn() -> S + Send + Sync + 'static,
1155 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
1156 ) -> Self
1157 where
1158 S: PartialEq + Clone + Send + Sync + 'static,
1159 T: Send + Sync + 'static,
1160 Fut: Future<Output = T> + Send + 'static,
1161 {
1162 Resource::new_with_options(source, fetcher, true)
1163 }
1164}
1165
1166#[cfg(feature = "serde-lite")]
1167impl<T> Resource<T, SerdeLite<JsonSerdeCodec>>
1168where
1169 SerdeLite<JsonSerdeCodec>: Encoder<T> + Decoder<T>,
1170 <SerdeLite<JsonSerdeCodec> as Encoder<T>>::Error: Debug, <SerdeLite<JsonSerdeCodec> as Decoder<T>>::Error: Debug,
1171 <<SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
1172 Debug,
1173 <SerdeLite<JsonSerdeCodec> as Encoder<T>>::Encoded: IntoEncodedString,
1174 <SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded: FromEncodedStr,
1175 T: Send + Sync,
1176{
1177 pub fn new_serde_lite<S, Fut>(
1187 source: impl Fn() -> S + Send + Sync + 'static,
1188 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
1189 ) -> Self
1190 where
1191 S: PartialEq + Clone + Send + Sync + 'static,
1192 T: Send + Sync + 'static,
1193 Fut: Future<Output = T> + Send + 'static,
1194 {
1195 Resource::new_with_options(source, fetcher, false)
1196 }
1197
1198 pub fn new_serde_lite_blocking<S, Fut>(
1212 source: impl Fn() -> S + Send + Sync + 'static,
1213 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
1214 ) -> Self
1215 where
1216 S: PartialEq + Clone + Send + Sync + 'static,
1217 T: Send + Sync + 'static,
1218 Fut: Future<Output = T> + Send + 'static,
1219 {
1220 Resource::new_with_options(source, fetcher, true)
1221 }
1222}
1223
1224#[cfg(feature = "rkyv")]
1225impl<T> Resource<T, RkyvCodec>
1226where
1227 RkyvCodec: Encoder<T> + Decoder<T>,
1228 <RkyvCodec as Encoder<T>>::Error: Debug,
1229 <RkyvCodec as Decoder<T>>::Error: Debug,
1230 <<RkyvCodec as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
1231 Debug,
1232 <RkyvCodec as Encoder<T>>::Encoded: IntoEncodedString,
1233 <RkyvCodec as Decoder<T>>::Encoded: FromEncodedStr,
1234 T: Send + Sync,
1235{
1236 pub fn new_rkyv<S, Fut>(
1246 source: impl Fn() -> S + Send + Sync + 'static,
1247 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
1248 ) -> Self
1249 where
1250 S: PartialEq + Clone + Send + Sync + 'static,
1251 T: Send + Sync + 'static,
1252 Fut: Future<Output = T> + Send + 'static,
1253 {
1254 Resource::new_with_options(source, fetcher, false)
1255 }
1256
1257 pub fn new_rkyv_blocking<S, Fut>(
1271 source: impl Fn() -> S + Send + Sync + 'static,
1272 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
1273 ) -> Self
1274 where
1275 S: PartialEq + Clone + Send + Sync + 'static,
1276 T: Send + Sync + 'static,
1277 Fut: Future<Output = T> + Send + 'static,
1278 {
1279 Resource::new_with_options(source, fetcher, true)
1280 }
1281}
1282
1283impl<T, Ser> Resource<T, Ser>
1284where
1285 Ser: Encoder<T> + Decoder<T>,
1286 <Ser as Encoder<T>>::Error: Debug,
1287 <Ser as Decoder<T>>::Error: Debug,
1288 <<Ser as Decoder<T>>::Encoded as FromEncodedStr>::DecodingError: Debug,
1289 <Ser as Encoder<T>>::Encoded: IntoEncodedString,
1290 <Ser as Decoder<T>>::Encoded: FromEncodedStr,
1291 T: Send + Sync,
1292{
1293 #[track_caller]
1309 pub fn new_with_options<S, Fut>(
1310 source: impl Fn() -> S + Send + Sync + 'static,
1311 fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
1312 blocking: bool,
1313 ) -> Resource<T, Ser>
1314 where
1315 S: Send + Sync + Clone + PartialEq + 'static,
1316 T: Send + Sync + 'static,
1317 Fut: Future<Output = T> + Send + 'static,
1318 {
1319 let ArcResource { data, refetch, .. }: ArcResource<T, Ser> =
1320 ArcResource::new_with_options(source, fetcher, blocking);
1321 Resource {
1322 ser: PhantomData,
1323 data: data.into(),
1324 refetch: refetch.into(),
1325 #[cfg(any(debug_assertions, leptos_debuginfo))]
1326 defined_at: Location::caller(),
1327 }
1328 }
1329
1330 pub fn map<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U> {
1333 self.data
1334 .try_with(|n| n.as_ref().map(|n| Some(f(n))))?
1335 .flatten()
1336 }
1337
1338 pub fn refetch(&self) {
1340 self.refetch.try_update(|n| *n += 1);
1341 }
1342}
1343
1344impl<T, E, Ser> Resource<Result<T, E>, Ser>
1345where
1346 Ser: Encoder<Result<T, E>> + Decoder<Result<T, E>>,
1347 <Ser as Encoder<Result<T, E>>>::Error: Debug,
1348 <Ser as Decoder<Result<T, E>>>::Error: Debug,
1349 <<Ser as Decoder<Result<T, E>>>::Encoded as FromEncodedStr>::DecodingError:
1350 Debug,
1351 <Ser as Encoder<Result<T, E>>>::Encoded: IntoEncodedString,
1352 <Ser as Decoder<Result<T, E>>>::Encoded: FromEncodedStr,
1353 T: Send + Sync,
1354 E: Send + Sync + Clone,
1355{
1356 #[track_caller]
1364 pub fn and_then<U>(&self, f: impl FnOnce(&T) -> U) -> Option<Result<U, E>> {
1365 self.map(|data| data.as_ref().map(f).map_err(|e| e.clone()))
1366 }
1367}
1368
1369impl<T, Ser> IntoFuture for Resource<T, Ser>
1370where
1371 T: Clone + Send + Sync + 'static,
1372{
1373 type Output = T;
1374 type IntoFuture = AsyncDerivedFuture<T>;
1375
1376 #[track_caller]
1377 fn into_future(self) -> Self::IntoFuture {
1378 self.data.into_future()
1379 }
1380}
1381
1382impl<T, Ser> Resource<T, Ser>
1383where
1384 T: Send + Sync + 'static,
1385{
1386 pub fn by_ref(&self) -> AsyncDerivedRefFuture<T> {
1389 self.data.by_ref()
1390 }
1391}