Merge pull request #35 from danheuck/unserializable-resources
Unserializable resources
This commit is contained in:
commit
88464fefe9
|
@ -6,7 +6,7 @@ edition = "2021"
|
|||
[dependencies]
|
||||
anyhow = "1"
|
||||
console_log = "0.2"
|
||||
leptos = { path = "../../../leptos", default-features = false }
|
||||
leptos = { path = "../../../leptos", default-features = false, features = ["serde"] }
|
||||
leptos_meta = { path = "../../../meta", default-features = false }
|
||||
leptos_router = { path = "../../../router", default-features = false }
|
||||
log = "0.4"
|
||||
|
|
|
@ -10,18 +10,24 @@ use std::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
create_isomorphic_effect, create_memo, create_signal, queue_microtask, runtime::Runtime,
|
||||
serialization::Serializable, spawn::spawn_local, use_context, Memo, ReadSignal, Scope,
|
||||
ScopeProperty, SuspenseContext, WriteSignal,
|
||||
create_effect, create_isomorphic_effect, create_memo, create_signal, queue_microtask,
|
||||
runtime::Runtime, serialization::Serializable, spawn::spawn_local, use_context, Memo,
|
||||
ReadSignal, Scope, ScopeProperty, SuspenseContext, WriteSignal,
|
||||
};
|
||||
|
||||
/// Creates [Resource](crate::Resource), which is a signal that reflects the
|
||||
/// current state of an asynchronous task, allowing you to integrate
|
||||
/// `async` [Future]s into the synchronous reactive system.
|
||||
/// current state of an asynchronous task, allowing you to integrate `async`
|
||||
/// [Future]s into the synchronous reactive system.
|
||||
///
|
||||
/// Takes a `fetcher` function that generates a [Future] when called and a
|
||||
/// `source` signal that provides the argument for the `fetcher`. Whenever
|
||||
/// the value of the `source` changes, a new [Future] will be created and run.
|
||||
/// `source` signal that provides the argument for the `fetcher`. Whenever the
|
||||
/// value of the `source` changes, a new [Future] will be created and run.
|
||||
///
|
||||
/// When server-side rendering is used, the server will handle running the
|
||||
/// [Future] and will stream the result to the client. This process requires the
|
||||
/// output type of the Future to be [Serializable]. If your output cannot be
|
||||
/// serialized, or you just want to make sure the [Future] runs locally, use
|
||||
/// [create_local_resource()].
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
|
@ -73,6 +79,12 @@ where
|
|||
|
||||
/// Creates a [Resource](crate::Resource) with the given initial value, which
|
||||
/// will only generate and run a [Future] using the `fetcher` when the `source` changes.
|
||||
///
|
||||
/// When server-side rendering is used, the server will handle running the
|
||||
/// [Future] and will stream the result to the client. This process requires the
|
||||
/// output type of the Future to be [Serializable]. If your output cannot be
|
||||
/// serialized, or you just want to make sure the [Future] runs locally, use
|
||||
/// [create_local_resource_with_initial_value()].
|
||||
pub fn create_resource_with_initial_value<S, T, Fu>(
|
||||
cx: Scope,
|
||||
source: impl Fn() -> S + 'static,
|
||||
|
@ -105,7 +117,7 @@ where
|
|||
suspense_contexts: Default::default(),
|
||||
});
|
||||
|
||||
let id = cx.runtime.create_resource(Rc::clone(&r));
|
||||
let id = cx.runtime.create_serializable_resource(Rc::clone(&r));
|
||||
|
||||
create_isomorphic_effect(cx, {
|
||||
let r = Rc::clone(&r);
|
||||
|
@ -124,11 +136,111 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a _local_ [Resource](crate::Resource), which is a signal that
|
||||
/// reflects the current state of an asynchronous task, allowing you to
|
||||
/// integrate `async` [Future]s into the synchronous reactive system.
|
||||
///
|
||||
/// Takes a `fetcher` function that generates a [Future] when called and a
|
||||
/// `source` signal that provides the argument for the `fetcher`. Whenever the
|
||||
/// value of the `source` changes, a new [Future] will be created and run.
|
||||
///
|
||||
/// Unlike [create_resource()], this [Future] is always run on the local system
|
||||
/// and therefore it's result type does not need to be [Serializable].
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// #[derive(Debug, Clone)] // doesn't implement Serialize, Deserialize
|
||||
/// struct ComplicatedUnserializableStruct {
|
||||
/// // something here that can't be serialized
|
||||
/// }
|
||||
/// // any old async function; maybe this is calling a REST API or something
|
||||
/// async fn setup_complicated_struct() -> ComplicatedUnserializableStruct {
|
||||
/// // do some work
|
||||
/// ComplicatedUnserializableStruct { }
|
||||
/// }
|
||||
///
|
||||
/// // create the resource that will
|
||||
/// let result = create_local_resource(cx, move || (), |_| setup_complicated_struct());
|
||||
/// # }).dispose();
|
||||
/// ```
|
||||
pub fn create_local_resource<S, T, Fu>(
|
||||
cx: Scope,
|
||||
source: impl Fn() -> S + 'static,
|
||||
fetcher: impl Fn(S) -> Fu + 'static,
|
||||
) -> Resource<S, T>
|
||||
where
|
||||
S: PartialEq + Debug + Clone + 'static,
|
||||
T: Debug + Clone + 'static,
|
||||
Fu: Future<Output = T> + 'static,
|
||||
{
|
||||
let initial_value = None;
|
||||
create_local_resource_with_initial_value(cx, source, fetcher, initial_value)
|
||||
}
|
||||
|
||||
/// Creates a _local_ [Resource](crate::Resource) with the given initial value,
|
||||
/// which will only generate and run a [Future] using the `fetcher` when the
|
||||
/// `source` changes.
|
||||
///
|
||||
/// Unlike [create_resource_with_initial_value()], this [Future] will always run
|
||||
/// on the local system and therefore its output type does not need to be
|
||||
/// [Serializable].
|
||||
pub fn create_local_resource_with_initial_value<S, T, Fu>(
|
||||
cx: Scope,
|
||||
source: impl Fn() -> S + 'static,
|
||||
fetcher: impl Fn(S) -> Fu + 'static,
|
||||
initial_value: Option<T>,
|
||||
) -> Resource<S, T>
|
||||
where
|
||||
S: PartialEq + Debug + Clone + 'static,
|
||||
T: Debug + Clone + 'static,
|
||||
Fu: Future<Output = T> + 'static,
|
||||
{
|
||||
let resolved = initial_value.is_some();
|
||||
let (value, set_value) = create_signal(cx, initial_value);
|
||||
|
||||
let (loading, set_loading) = create_signal(cx, false);
|
||||
|
||||
let fetcher = Rc::new(move |s| Box::pin(fetcher(s)) as Pin<Box<dyn Future<Output = T>>>);
|
||||
let source = create_memo(cx, move |_| source());
|
||||
|
||||
let r = Rc::new(ResourceState {
|
||||
scope: cx,
|
||||
value,
|
||||
set_value,
|
||||
loading,
|
||||
set_loading,
|
||||
source,
|
||||
fetcher,
|
||||
resolved: Rc::new(Cell::new(resolved)),
|
||||
scheduled: Rc::new(Cell::new(false)),
|
||||
suspense_contexts: Default::default(),
|
||||
});
|
||||
|
||||
let id = cx.runtime.create_unserializable_resource(Rc::clone(&r));
|
||||
|
||||
create_effect(cx, {
|
||||
let r = Rc::clone(&r);
|
||||
// This is a local resource, so we're always going to handle it on the
|
||||
// client
|
||||
move |_| r.load(false)
|
||||
});
|
||||
|
||||
cx.with_scope_property(|prop| prop.push(ScopeProperty::Resource(id)));
|
||||
|
||||
Resource {
|
||||
runtime: cx.runtime,
|
||||
id,
|
||||
source_ty: PhantomData,
|
||||
out_ty: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "hydrate"))]
|
||||
fn load_resource<S, T>(_cx: Scope, _id: ResourceId, r: Rc<ResourceState<S, T>>)
|
||||
where
|
||||
S: PartialEq + Debug + Clone + 'static,
|
||||
T: Debug + Clone + Serializable + 'static,
|
||||
T: Debug + Clone + 'static,
|
||||
{
|
||||
r.load(false)
|
||||
}
|
||||
|
@ -143,6 +255,8 @@ where
|
|||
|
||||
if let Some(ref mut context) = *cx.runtime.shared_context.borrow_mut() {
|
||||
if let Some(data) = context.resolved_resources.remove(&id) {
|
||||
// The server already sent us the serialized resource value, so
|
||||
// deserialize & set it now
|
||||
context.pending_resources.remove(&id); // no longer pending
|
||||
r.resolved.set(true);
|
||||
|
||||
|
@ -153,6 +267,9 @@ where
|
|||
// for reactivity
|
||||
_ = r.source.try_with(|n| n.clone());
|
||||
} else if context.pending_resources.remove(&id) {
|
||||
// We're still waiting for the resource, add a "resolver" closure so
|
||||
// that it will be set as soon as the server sends the serialized
|
||||
// value
|
||||
r.set_loading.update(|n| *n = true);
|
||||
|
||||
let resolve = {
|
||||
|
@ -174,7 +291,7 @@ where
|
|||
&wasm_bindgen::JsValue::from_str("__LEPTOS_RESOURCE_RESOLVERS"),
|
||||
)
|
||||
.expect_throw("no __LEPTOS_RESOURCE_RESOLVERS found in the JS global scope");
|
||||
let id = serde_json::to_string(&id).expect_throw("could not deserialize Resource ID");
|
||||
let id = serde_json::to_string(&id).expect_throw("could not serialize Resource ID");
|
||||
_ = js_sys::Reflect::set(
|
||||
&resource_resolvers,
|
||||
&wasm_bindgen::JsValue::from_str(&id),
|
||||
|
@ -184,6 +301,8 @@ where
|
|||
// for reactivity
|
||||
_ = r.source.get();
|
||||
} else {
|
||||
// Server didn't mark the resource as pending, so load it on the
|
||||
// client
|
||||
r.load(false);
|
||||
}
|
||||
} else {
|
||||
|
@ -421,7 +540,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) trait AnyResource {
|
||||
pub(crate) enum AnyResource {
|
||||
Unserializable(Rc<dyn UnserializableResource>),
|
||||
Serializable(Rc<dyn SerializableResource>),
|
||||
}
|
||||
|
||||
pub(crate) trait SerializableResource {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
|
@ -431,7 +555,7 @@ pub(crate) trait AnyResource {
|
|||
) -> Pin<Box<dyn Future<Output = (ResourceId, String)>>>;
|
||||
}
|
||||
|
||||
impl<S, T> AnyResource for ResourceState<S, T>
|
||||
impl<S, T> SerializableResource for ResourceState<S, T>
|
||||
where
|
||||
S: Debug + Clone,
|
||||
T: Clone + Debug + Serializable,
|
||||
|
@ -449,3 +573,17 @@ where
|
|||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait UnserializableResource {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
impl<S, T> UnserializableResource for ResourceState<S, T>
|
||||
where
|
||||
S: Debug + Clone,
|
||||
T: Clone + Debug,
|
||||
{
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ pub(crate) struct Runtime {
|
|||
pub signal_subscribers: RefCell<SecondaryMap<SignalId, RefCell<HashSet<EffectId>>>>,
|
||||
pub effects: RefCell<SlotMap<EffectId, Rc<RefCell<dyn AnyEffect>>>>,
|
||||
pub effect_sources: RefCell<SecondaryMap<EffectId, RefCell<HashSet<SignalId>>>>,
|
||||
pub resources: RefCell<SlotMap<ResourceId, Rc<dyn AnyResource>>>,
|
||||
pub resources: RefCell<SlotMap<ResourceId, AnyResource>>,
|
||||
}
|
||||
|
||||
impl Debug for Runtime {
|
||||
|
@ -147,12 +147,30 @@ impl Runtime {
|
|||
Memo(read)
|
||||
}
|
||||
|
||||
pub(crate) fn create_resource<S, T>(&self, state: Rc<ResourceState<S, T>>) -> ResourceId
|
||||
pub(crate) fn create_unserializable_resource<S, T>(
|
||||
&self,
|
||||
state: Rc<ResourceState<S, T>>,
|
||||
) -> ResourceId
|
||||
where
|
||||
S: Debug + Clone + 'static,
|
||||
T: Debug + Clone + 'static,
|
||||
{
|
||||
self.resources
|
||||
.borrow_mut()
|
||||
.insert(AnyResource::Unserializable(state))
|
||||
}
|
||||
|
||||
pub(crate) fn create_serializable_resource<S, T>(
|
||||
&self,
|
||||
state: Rc<ResourceState<S, T>>,
|
||||
) -> ResourceId
|
||||
where
|
||||
S: Debug + Clone + 'static,
|
||||
T: Debug + Clone + Serializable + 'static,
|
||||
{
|
||||
self.resources.borrow_mut().insert(state)
|
||||
self.resources
|
||||
.borrow_mut()
|
||||
.insert(AnyResource::Serializable(state))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "hydrate", not(feature = "ssr")))]
|
||||
|
@ -194,7 +212,13 @@ impl Runtime {
|
|||
let resources = self.resources.borrow();
|
||||
let res = resources.get(id);
|
||||
if let Some(res) = res {
|
||||
if let Some(n) = res.as_any().downcast_ref::<ResourceState<S, T>>() {
|
||||
let res_state = match res {
|
||||
AnyResource::Unserializable(res) => res.as_any(),
|
||||
AnyResource::Serializable(res) => res.as_any(),
|
||||
}
|
||||
.downcast_ref::<ResourceState<S, T>>();
|
||||
|
||||
if let Some(n) = res_state {
|
||||
f(n)
|
||||
} else {
|
||||
panic!(
|
||||
|
@ -225,7 +249,9 @@ impl Runtime {
|
|||
> {
|
||||
let f = futures::stream::futures_unordered::FuturesUnordered::new();
|
||||
for (id, resource) in self.resources.borrow().iter() {
|
||||
f.push(resource.to_serialization_resolver(id));
|
||||
if let AnyResource::Serializable(resource) = resource {
|
||||
f.push(resource.to_serialization_resolver(id));
|
||||
}
|
||||
}
|
||||
f
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue