1use std::future::Future;
4use std::ops::Deref;
5
6use futures::future::{FutureExt, LocalBoxFuture};
7use sycamore_futures::{SuspenseScope, SuspenseTaskGuard};
8
9use crate::*;
10
11#[derive(Clone, Copy)]
13pub struct Resource<T: 'static> {
14 value: Signal<Option<T>>,
19 is_loading: Signal<bool>,
21 #[allow(clippy::complexity)]
23 refetch: Signal<Box<dyn FnMut() -> LocalBoxFuture<'static, T>>>,
24 scopes: Signal<Vec<SuspenseScope>>,
26 guards: Signal<Vec<SuspenseTaskGuard>>,
28}
29
30impl<T: 'static> Resource<T> {
31 fn new<F, Fut>(mut refetch: F) -> Self
33 where
34 F: FnMut() -> Fut + 'static,
35 Fut: Future<Output = T> + 'static,
36 {
37 Self {
38 value: create_signal(None),
39 is_loading: create_signal(true),
40 refetch: create_signal(Box::new(move || refetch().boxed_local())),
41 scopes: create_signal(Vec::new()),
42 guards: create_signal(Vec::new()),
43 }
44 }
45
46 fn fetch_on_client(self) -> Self {
48 if is_not_ssr!() {
49 create_effect(move || {
50 self.is_loading.set(true);
51 for scope in self.scopes.take() {
53 let guard = SuspenseTaskGuard::from_scope(scope);
54 self.guards.update(|guards| guards.push(guard));
55 }
56
57 let fut = self.refetch.update_silent(|f| f());
58
59 sycamore_futures::create_suspense_task(async move {
60 let value = fut.await;
61 batch(move || {
62 self.value.set(Some(value));
63 self.is_loading.set(false);
64 self.guards.update(|guards| guards.clear());
66 });
67 });
68 })
69 }
70
71 self
72 }
73
74 pub fn is_loading(&self) -> bool {
76 self.is_loading.get()
77 }
78}
79
80impl<T: 'static> Deref for Resource<T> {
82 type Target = ReadSignal<Option<T>>;
83
84 fn deref(&self) -> &Self::Target {
85 if self.is_loading.get() {
88 let guard = SuspenseTaskGuard::new();
89 self.guards.update(|guards| guards.push(guard));
90 } else if let Some(scope) = try_use_context::<SuspenseScope>() {
91 self.scopes.update(|scopes| scopes.push(scope));
92 }
93
94 &self.value
95 }
96}
97
98pub fn create_client_resource<F, Fut, T>(f: F) -> Resource<T>
106where
107 F: FnMut() -> Fut + 'static,
108 Fut: Future<Output = T> + 'static,
109 T: 'static,
110{
111 Resource::new(f).fetch_on_client()
112}