From a5293f0b792d67f37c03f0e255cb3eb6b7bca423 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Mon, 11 Nov 2024 19:58:38 -0500 Subject: [PATCH] chore: add missing docs for 0.7 (#3203) --- integrations/actix/src/lib.rs | 49 ++-- integrations/axum/src/lib.rs | 20 ++ leptos/src/children.rs | 3 + leptos/src/form.rs | 4 + leptos/src/hydration/mod.rs | 16 +- leptos/src/into_view.rs | 12 + leptos/src/lib.rs | 4 +- leptos/src/suspense_component.rs | 7 +- leptos_macro/src/lib.rs | 3 + leptos_server/src/action.rs | 11 + leptos_server/src/lib.rs | 130 +--------- leptos_server/src/local_resource.rs | 10 + leptos_server/src/multi_action.rs | 4 + leptos_server/src/multi_action_old.rs | 344 ------------------------- leptos_server/src/once_resource.rs | 102 ++++++++ leptos_server/src/resource.rs | 349 ++++++++++++++++++++++++++ leptos_server/src/shared.rs | 51 +++- next_tuple/src/lib.rs | 4 + router/src/components.rs | 86 +++++-- router/src/flat_router.rs | 7 +- router/src/generate_route_list.rs | 12 + router/src/hooks.rs | 6 + router/src/lib.rs | 128 ++++++++++ router/src/location/mod.rs | 2 + router/src/matching/mod.rs | 34 +-- router/src/nested_router.rs | 9 +- router/src/params.rs | 3 + router/src/ssr_mode.rs | 19 +- router/src/static_routes.rs | 15 ++ router_macro/src/lib.rs | 4 + tachys/src/lib.rs | 6 +- tachys/src/renderer/dom.rs | 4 + tachys/src/renderer/mod.rs | 15 ++ 33 files changed, 928 insertions(+), 545 deletions(-) delete mode 100644 leptos_server/src/multi_action_old.rs diff --git a/integrations/actix/src/lib.rs b/integrations/actix/src/lib.rs index 1035d1aa9..d4ca97514 100644 --- a/integrations/actix/src/lib.rs +++ b/integrations/actix/src/lib.rs @@ -1,4 +1,5 @@ #![forbid(unsafe_code)] +#![deny(missing_docs)] //! Provides functions to easily integrate Leptos with Actix. //! @@ -9,7 +10,6 @@ use actix_files::NamedFile; use actix_http::header::{HeaderName, HeaderValue, ACCEPT, LOCATION, REFERER}; use actix_web::{ - body::BoxBody, dev::{ServiceFactory, ServiceRequest}, http::header, test, @@ -56,8 +56,10 @@ use std::{ /// Typically contained inside of a ResponseOptions. Setting this is useful for cookies and custom responses. #[derive(Debug, Clone, Default)] pub struct ResponseParts { - pub headers: header::HeaderMap, + /// If provided, this will overwrite any other status code for this response. pub status: Option, + /// The map of headers that should be added to the response. + pub headers: header::HeaderMap, } impl ResponseParts { @@ -86,10 +88,12 @@ impl ResponseParts { pub struct Request(SendWrapper); impl Request { + /// Wraps an existing Actix request. pub fn new(req: &HttpRequest) -> Self { Self(SendWrapper::new(req.clone())) } + /// Consumes the wrapper and returns the inner Actix request. pub fn into_inner(self) -> HttpRequest { self.0.take() } @@ -299,7 +303,7 @@ pub fn redirect(path: &str) { /// ## Provided Context Types /// This function always provides context values including the following types: /// - [ResponseOptions] -/// - [HttpRequest](actix_web::HttpRequest) +/// - [Request] #[cfg_attr( feature = "tracing", tracing::instrument(level = "trace", fields(error), skip_all) @@ -326,7 +330,7 @@ pub fn handle_server_fns() -> Route { /// ## Provided Context Types /// This function always provides context values including the following types: /// - [ResponseOptions] -/// - [HttpRequest](actix_web::HttpRequest) +/// - [Request] #[cfg_attr( feature = "tracing", tracing::instrument(level = "trace", fields(error), skip_all) @@ -459,7 +463,7 @@ pub fn handle_server_fns_with_context( /// ## Provided Context Types /// This function always provides context values including the following types: /// - [ResponseOptions] -/// - [HttpRequest](actix_web::HttpRequest) +/// - [Request] /// - [MetaContext](leptos_meta::MetaContext) /// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext) #[cfg_attr( @@ -529,8 +533,7 @@ where /// ## Provided Context Types /// This function always provides context values including the following types: /// - [ResponseOptions] -/// - [HttpRequest](actix_web::HttpRequest) -/// - [MetaContext](leptos_meta::MetaContext) +/// - [Request] /// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext) #[cfg_attr( feature = "tracing", @@ -594,9 +597,7 @@ where /// ## Provided Context Types /// This function always provides context values including the following types: /// - [ResponseOptions] -/// - [HttpRequest](actix_web::HttpRequest) -/// - [MetaContext](leptos_meta::MetaContext) -/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext) +/// - [Request] #[cfg_attr( feature = "tracing", tracing::instrument(level = "trace", fields(error), skip_all) @@ -620,9 +621,7 @@ where /// ## Provided Context Types /// This function always provides context values including the following types: /// - [ResponseOptions] -/// - [HttpRequest](actix_web::HttpRequest) -/// - [MetaContext](leptos_meta::MetaContext) -/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext) +/// - [Request] #[cfg_attr( feature = "tracing", tracing::instrument(level = "trace", fields(error), skip_all) @@ -657,9 +656,7 @@ where /// ## Provided Context Types /// This function always provides context values including the following types: /// - [ResponseOptions] -/// - [HttpRequest](actix_web::HttpRequest) -/// - [MetaContext](leptos_meta::MetaContext) -/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext) +/// - [Request] #[cfg_attr( feature = "tracing", tracing::instrument(level = "trace", fields(error), skip_all) @@ -691,7 +688,7 @@ where /// ## Provided Context Types /// This function always provides context values including the following types: /// - [ResponseOptions] -/// - [HttpRequest](actix_web::HttpRequest) +/// - [Request] /// - [MetaContext](leptos_meta::MetaContext) /// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext) #[cfg_attr( @@ -724,9 +721,7 @@ where /// ## Provided Context Types /// This function always provides context values including the following types: /// - [ResponseOptions] -/// - [HttpRequest](actix_web::HttpRequest) -/// - [MetaContext](leptos_meta::MetaContext) -/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext) +/// - [Request] #[cfg_attr( feature = "tracing", tracing::instrument(level = "trace", fields(error), skip_all) @@ -1319,14 +1314,12 @@ where web::get().to(handler) } -pub enum DataResponse { - Data(T), - Response(actix_web::dev::Response), -} - /// This trait allows one to pass a list of routes and a render function to Actix's router, letting us avoid /// having to use wildcards or manually define all routes in multiple places. pub trait LeptosRoutes { + /// Adds routes to the Axum router that have either + /// 1) been generated by `leptos_router`, or + /// 2) handle a server function. fn leptos_routes( self, paths: Vec, @@ -1335,6 +1328,12 @@ pub trait LeptosRoutes { where IV: IntoView + 'static; + /// Adds routes to the Axum router that have either + /// 1) been generated by `leptos_router`, or + /// 2) handle a server function. + /// + /// Runs `additional_context` to provide additional data to the reactive system via context, + /// when handling a route. fn leptos_routes_with_context( self, paths: Vec, diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index 50a837a44..09041056f 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -1,4 +1,6 @@ #![forbid(unsafe_code)] +#![deny(missing_docs)] + //! Provides functions to easily integrate Leptos with Axum. //! //! ## JS Fetch Integration @@ -84,7 +86,9 @@ use tower_http::services::ServeDir; /// Typically contained inside of a ResponseOptions. Setting this is useful for cookies and custom responses. #[derive(Debug, Clone, Default)] pub struct ResponseParts { + /// If provided, this will overwrite any other status code for this response. pub status: Option, + /// The map of headers that should be added to the response. pub headers: HeaderMap, } @@ -431,6 +435,7 @@ async fn handle_server_fns_inner( .expect("could not build Response") } +/// A stream of bytes of HTML. pub type PinnedHtmlStream = Pin> + Send>>; @@ -1656,6 +1661,9 @@ where S: Clone + Send + Sync + 'static, LeptosOptions: FromRef, { + /// Adds routes to the Axum router that have either + /// 1) been generated by `leptos_router`, or + /// 2) handle a server function. fn leptos_routes( self, options: &S, @@ -1665,6 +1673,12 @@ where where IV: IntoView + 'static; + /// Adds routes to the Axum router that have either + /// 1) been generated by `leptos_router`, or + /// 2) handle a server function. + /// + /// Runs `additional_context` to provide additional data to the reactive system via context, + /// when handling a route. fn leptos_routes_with_context( self, options: &S, @@ -1675,6 +1689,8 @@ where where IV: IntoView + 'static; + /// Extends the Axum router with the given paths, and handles the requests with the given + /// handler. fn leptos_routes_with_handler( self, paths: Vec, @@ -1987,6 +2003,10 @@ where .map_err(|e| ServerFnError::ServerError(format!("{e:?}"))) } +/// A reasonable handler for serving static files (like JS/WASM/CSS) and 404 errors. +/// +/// This is provided as a convenience, but is a fairly simple function. If you need to adapt it, +/// simply reuse the source code of this function in your own application. #[cfg(feature = "default")] pub fn file_and_error_handler( shell: fn(LeptosOptions) -> IV, diff --git a/leptos/src/children.rs b/leptos/src/children.rs index 3e269c5f5..7d69757a1 100644 --- a/leptos/src/children.rs +++ b/leptos/src/children.rs @@ -228,6 +228,7 @@ impl ViewFnOnce { pub struct TypedChildren(Box View + Send>); impl TypedChildren { + /// Extracts the inner `children` function. pub fn into_inner(self) -> impl FnOnce() -> View + Send { self.0 } @@ -256,6 +257,7 @@ impl Debug for TypedChildrenMut { } impl TypedChildrenMut { + /// Extracts the inner `children` function. pub fn into_inner(self) -> impl FnMut() -> View + Send { self.0 } @@ -284,6 +286,7 @@ impl Debug for TypedChildrenFn { } impl TypedChildrenFn { + /// Extracts the inner `children` function. pub fn into_inner(self) -> Arc View + Send + Sync> { self.0 } diff --git a/leptos/src/form.rs b/leptos/src/form.rs index 29aa87bf5..648b9c901 100644 --- a/leptos/src/form.rs +++ b/leptos/src/form.rs @@ -251,12 +251,16 @@ where ) -> Result; } +/// Errors that can arise when coverting from an HTML event or form into a Rust data type. #[derive(Error, Debug)] pub enum FromFormDataError { + /// Could not find a `
` connected to the event. #[error("Could not find connected to event.")] MissingForm(Event), + /// Could not create `FormData` from the form. #[error("Could not create FormData from : {0:?}")] FormData(JsValue), + /// Failed to deserialize this Rust type from the form data. #[error("Deserialization error: {0:?}")] Deserialization(serde_qs::Error), } diff --git a/leptos/src/hydration/mod.rs b/leptos/src/hydration/mod.rs index 1a9810095..54ae17c49 100644 --- a/leptos/src/hydration/mod.rs +++ b/leptos/src/hydration/mod.rs @@ -4,9 +4,15 @@ use crate::prelude::*; use leptos_config::LeptosOptions; use leptos_macro::{component, view}; +/// Inserts auto-reloading code used in `cargo-leptos`. +/// +/// This should be included in the `` of your application shell during development. #[component] pub fn AutoReload( - #[prop(optional)] disable_watch: bool, + /// Whether the file-watching feature should be disabled. + #[prop(optional)] + disable_watch: bool, + /// Configuration options for this project. options: LeptosOptions, ) -> impl IntoView { (!disable_watch && std::env::var("LEPTOS_WATCH").is_ok()).then(|| { @@ -34,10 +40,16 @@ pub fn AutoReload( }) } +/// Inserts hydration scripts that add interactivity to your server-rendered HTML. +/// +/// This should be included in the `` of your application shell. #[component] pub fn HydrationScripts( + /// Configuration options for this project. options: LeptosOptions, - #[prop(optional)] islands: bool, + /// Should be `true` to hydrate in `experimental-islands` mode. + #[prop(optional)] + islands: bool, /// A base url, not including a trailing slash #[prop(optional, into)] root: Option, diff --git a/leptos/src/into_view.rs b/leptos/src/into_view.rs index 2d9d9661f..20e8fa602 100644 --- a/leptos/src/into_view.rs +++ b/leptos/src/into_view.rs @@ -9,6 +9,7 @@ use tachys::{ }, }; +/// A wrapper for any kind of view. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct View where @@ -20,6 +21,7 @@ where } impl View { + /// Wraps the view. pub fn new(inner: T) -> Self { Self { inner, @@ -28,10 +30,12 @@ impl View { } } + /// Unwraps the view, returning the inner type. pub fn into_inner(self) -> T { self.inner } + /// Adds a view marker, which is used for hot-reloading and debug purposes. #[inline(always)] pub fn with_view_marker( #[allow(unused_mut)] // used in debug @@ -47,10 +51,12 @@ impl View { } } +/// A trait that is implemented for types that can be rendered. pub trait IntoView where Self: Sized + Render + RenderHtml + Send, { + /// Wraps the inner type. fn into_view(self) -> View; } @@ -188,9 +194,15 @@ impl AddAnyAttr for View { } } +/// Collects some iterator of views into a list, so they can be rendered. +/// +/// This is a shorthand for `.collect::>()`, and allows any iterator of renderable +/// items to be collected into a renderable collection. pub trait CollectView { + /// The inner view type. type View: IntoView; + /// Collects the iterator into a list of views. fn collect_view(self) -> Vec; } diff --git a/leptos/src/lib.rs b/leptos/src/lib.rs index 9aa0843a8..dbb55636a 100644 --- a/leptos/src/lib.rs +++ b/leptos/src/lib.rs @@ -1,5 +1,6 @@ -#!rdeny(missing_docs)] +#![deny(missing_docs)] #![forbid(unsafe_code)] + //! # About Leptos //! //! Leptos is a full-stack framework for building web applications in Rust. You can use it to build @@ -287,6 +288,7 @@ pub mod logging { pub use leptos_dom::{debug_warn, error, log, warn}; } +/// Utilities for working with asynchronous tasks. pub mod task { pub use any_spawner::Executor; use std::future::Future; diff --git a/leptos/src/suspense_component.rs b/leptos/src/suspense_component.rs index fa207ac3c..2d14dd425 100644 --- a/leptos/src/suspense_component.rs +++ b/leptos/src/suspense_component.rs @@ -87,7 +87,12 @@ use throw_error::ErrorHookFuture; /// ``` #[component] pub fn Suspense( - #[prop(optional, into)] fallback: ViewFnOnce, + /// A function that returns a fallback that will be shown while resources are still loading. + /// By default this is an empty view. + #[prop(optional, into)] + fallback: ViewFnOnce, + /// Children will be rendered once initially to catch any resource reads, then hidden until all + /// data have loaded. children: TypedChildren, ) -> impl IntoView where diff --git a/leptos_macro/src/lib.rs b/leptos_macro/src/lib.rs index 5e592c40b..9c3699958 100644 --- a/leptos_macro/src/lib.rs +++ b/leptos_macro/src/lib.rs @@ -1,3 +1,5 @@ +//! Macros for use with the [`leptos`] framework. + #![cfg_attr(feature = "nightly", feature(proc_macro_span))] #![forbid(unsafe_code)] // to prevent warnings from popping up when a nightly feature is stabilized @@ -5,6 +7,7 @@ // FIXME? every use of quote! {} is warning here -- false positive? #![allow(unknown_lints)] #![allow(private_macro_use)] +#![deny(missing_docs)] #[macro_use] extern crate proc_macro_error2; diff --git a/leptos_server/src/action.rs b/leptos_server/src/action.rs index 8da815875..177fb9cff 100644 --- a/leptos_server/src/action.rs +++ b/leptos_server/src/action.rs @@ -6,6 +6,10 @@ use reactive_graph::{ use server_fn::{error::ServerFnErrorSerde, ServerFn, ServerFnError}; use std::{ops::Deref, panic::Location, sync::Arc}; +/// An error that can be caused by a server action. +/// +/// This is used for propagating errors from the server to the client when JS/WASM are not +/// supported. #[derive(Clone, Debug, PartialEq, Eq)] pub struct ServerActionError { path: Arc, @@ -13,6 +17,7 @@ pub struct ServerActionError { } impl ServerActionError { + /// Creates a new error associated with the given path. pub fn new(path: &str, err: &str) -> Self { Self { path: path.into(), @@ -20,15 +25,18 @@ impl ServerActionError { } } + /// The path with which this error is associated. pub fn path(&self) -> &str { &self.path } + /// The error message. pub fn err(&self) -> &str { &self.err } } +/// An [`ArcAction`] that can be used to call a server function. pub struct ArcServerAction where S: ServerFn + 'static, @@ -45,6 +53,7 @@ where S::Output: Send + Sync + 'static, S::Error: Send + Sync + 'static, { + /// Creates a new [`ArcAction`] that will call the server function `S` when dispatched. #[track_caller] pub fn new() -> Self { let err = use_context::().and_then(|error| { @@ -116,6 +125,7 @@ where } } +/// An [`Action`] that can be used to call a server function. pub struct ServerAction where S: ServerFn + 'static, @@ -132,6 +142,7 @@ where S::Output: Send + Sync + 'static, S::Error: Send + Sync + 'static, { + /// Creates a new [`Action`] that will call the server function `S` when dispatched. pub fn new() -> Self { let err = use_context::().and_then(|error| { (error.path() == S::PATH) diff --git a/leptos_server/src/lib.rs b/leptos_server/src/lib.rs index a637407e1..1488535a7 100644 --- a/leptos_server/src/lib.rs +++ b/leptos_server/src/lib.rs @@ -1,4 +1,6 @@ -//#![deny(missing_docs)] +//! Utilities for communicating between the server and the client with [`leptos`]. + +#![deny(missing_docs)] #![forbid(unsafe_code)] mod action; @@ -13,135 +15,25 @@ pub use once_resource::*; mod resource; pub use resource::*; mod shared; -////! # Leptos Server Functions -////! -////! This package is based on a simple idea: sometimes it’s useful to write functions -////! that will only run on the server, and call them from the client. -////! -////! If you’re creating anything beyond a toy app, you’ll need to do this all the time: -////! reading from or writing to a database that only runs on the server, running expensive -////! computations using libraries you don’t want to ship down to the client, accessing -////! APIs that need to be called from the server rather than the client for CORS reasons -////! or because you need a secret API key that’s stored on the server and definitely -////! shouldn’t be shipped down to a user’s browser. -////! -////! Traditionally, this is done by separating your server and client code, and by setting -////! up something like a REST API or GraphQL API to allow your client to fetch and mutate -////! data on the server. This is fine, but it requires you to write and maintain your code -////! in multiple separate places (client-side code for fetching, server-side functions to run), -////! as well as creating a third thing to manage, which is the API contract between the two. -////! -////! This package provides two simple primitives that allow you instead to write co-located, -////! isomorphic server functions. (*Co-located* means you can write them in your app code so -////! that they are “located alongside” the client code that calls them, rather than separating -////! the client and server sides. *Isomorphic* means you can call them from the client as if -////! you were simply calling a function; the function call has the “same shape” on the client -////! as it does on the server.) -////! -////! ### `#[server]` -////! -////! The [`#[server]`](https://docs.rs/leptos/latest/leptos/attr.server.html) macro allows you to annotate a function to -////! indicate that it should only run on the server (i.e., when you have an `ssr` feature in your -////! crate that is enabled). -////! -////! ```rust,ignore -////! use leptos::prelude::*; -////! #[server(ReadFromDB)] -////! async fn read_posts(how_many: usize, query: String) -> Result, ServerFnError> { -////! // do some server-only work here to access the database -////! let posts = todo!();; -////! Ok(posts) -////! } -////! -////! // call the function -////! spawn_local(async { -////! let posts = read_posts(3, "my search".to_string()).await; -////! log::debug!("posts = {posts:#?}"); -////! }); -////! ``` -////! -////! If you call this function from the client, it will serialize the function arguments and `POST` -////! them to the server as if they were the inputs in ``. -////! -////! Here’s what you need to remember: -////! - **Server functions must be `async`.** Even if the work being done inside the function body -////! can run synchronously on the server, from the client’s perspective it involves an asynchronous -////! function call. -////! - **Server functions must return `Result`.** Even if the work being done -////! inside the function body can’t fail, the processes of serialization/deserialization and the -////! network call are fallible. -////! - **Return types must be [Serializable](leptos_reactive::Serializable).** -////! This should be fairly obvious: we have to serialize arguments to send them to the server, and we -////! need to deserialize the result to return it to the client. -////! - **Arguments must be implement [serde::Serialize].** They are serialized as an `application/x-www-form-urlencoded` -////! form data using [`serde_qs`](https://docs.rs/serde_qs/latest/serde_qs/) or as `application/cbor` -////! using [`cbor`](https://docs.rs/cbor/latest/cbor/). **Note**: You should explicitly include `serde` with the -////! `derive` feature enabled in your `Cargo.toml`. You can do this by running `cargo add serde --features=derive`. -////! - Context comes from the server. [`use_context`](leptos_reactive::use_context) can be used to access specific -////! server-related data, as documented in the server integrations. This allows accessing things like HTTP request -////! headers as needed. However, server functions *not* have access to reactive state that exists in the client. -////! -////! ## Server Function Encodings -////! -////! By default, the server function call is a `POST` request that serializes the arguments as URL-encoded form data in the body -////! of the request. But there are a few other methods supported. Optionally, we can provide another argument to the `#[server]` -////! macro to specify an alternate encoding: -////! -////! ```rust,ignore -////! #[server(AddTodo, "/api", "Url")] -////! #[server(AddTodo, "/api", "GetJson")] -////! #[server(AddTodo, "/api", "Cbor")] -////! #[server(AddTodo, "/api", "GetCbor")] -////! ``` -////! -////! The four options use different combinations of HTTP verbs and encoding methods: -////! -////! | Name | Method | Request | Response | -////! | ----------------- | ------ | ----------- | -------- | -////! | **Url** (default) | POST | URL encoded | JSON | -////! | **GetJson** | GET | URL encoded | JSON | -////! | **Cbor** | POST | CBOR | CBOR | -////! | **GetCbor** | GET | URL encoded | CBOR | -////! -////! In other words, you have two choices: -////! -////! - `GET` or `POST`? This has implications for things like browser or CDN caching; while `POST` requests should not be cached, -////! `GET` requests can be. -////! - Plain text (arguments sent with URL/form encoding, results sent as JSON) or a binary format (CBOR, encoded as a base64 -////! string)? -////! -////! ## Why not `PUT` or `DELETE`? Why URL/form encoding, and not JSON?** -////! -////! These are reasonable questions. Much of the web is built on REST API patterns that encourage the use of semantic HTTP -////! methods like `DELETE` to delete an item from a database, and many devs are accustomed to sending data to APIs in the -////! JSON format. -////! -////! The reason we use `POST` or `GET` with URL-encoded data by default is the `` support. For better or for worse, -////! HTML forms don’t support `PUT` or `DELETE`, and they don’t support sending JSON. This means that if you use anything -////! but a `GET` or `POST` request with URL-encoded data, it can only work once WASM has loaded. -////! -////! The CBOR encoding is supported for historical reasons; an earlier version of server functions used a URL encoding that -////! didn’t support nested objects like structs or vectors as server function arguments, which CBOR did. But note that the -////! CBOR forms encounter the same issue as `PUT`, `DELETE`, or JSON: they do not degrade gracefully if the WASM version of -////! your app is not available. -//pub use server_fn::{error::ServerFnErrorErr, ServerFnError}; - -//mod action; -//mod multi_action; -//pub use action::*; -//pub use multi_action::*; -//extern crate tracing; use base64::{engine::general_purpose::STANDARD_NO_PAD, DecodeError, Engine}; pub use shared::*; + +/// Encodes data into a string. pub trait IntoEncodedString { + /// Encodes the data. fn into_encoded_string(self) -> String; } +/// Decodes data from a string. pub trait FromEncodedStr { + /// The decoded data. type DecodedType<'a>: Borrow; + + /// The type of an error encountered during decoding. type DecodingError; + /// Decodes the string. fn from_encoded_str( data: &str, ) -> Result, Self::DecodingError>; diff --git a/leptos_server/src/local_resource.rs b/leptos_server/src/local_resource.rs index 66be092a5..d74427375 100644 --- a/leptos_server/src/local_resource.rs +++ b/leptos_server/src/local_resource.rs @@ -17,6 +17,7 @@ use std::{ panic::Location, }; +/// A reference-counted resource that only loads its data locally on the client. pub struct ArcLocalResource { data: ArcAsyncDerived>, #[cfg(debug_assertions)] @@ -34,6 +35,10 @@ impl Clone for ArcLocalResource { } impl ArcLocalResource { + /// Creates the resource. + /// + /// This will only begin loading data if you are on the client (i.e., if you do not have the + /// `ssr` feature activated). #[track_caller] pub fn new(fetcher: impl Fn() -> Fut + 'static) -> Self where @@ -192,6 +197,7 @@ impl Subscriber for ArcLocalResource { } } +/// A resource that only loads its data locally on the client. pub struct LocalResource { data: AsyncDerived>, #[cfg(debug_assertions)] @@ -207,6 +213,10 @@ impl Clone for LocalResource { impl Copy for LocalResource {} impl LocalResource { + /// Creates the resource. + /// + /// This will only begin loading data if you are on the client (i.e., if you do not have the + /// `ssr` feature activated). #[track_caller] pub fn new(fetcher: impl Fn() -> Fut + 'static) -> Self where diff --git a/leptos_server/src/multi_action.rs b/leptos_server/src/multi_action.rs index 212330782..d62a6a905 100644 --- a/leptos_server/src/multi_action.rs +++ b/leptos_server/src/multi_action.rs @@ -5,6 +5,7 @@ use reactive_graph::{ use server_fn::{ServerFn, ServerFnError}; use std::{ops::Deref, panic::Location}; +/// An [`ArcMultiAction`] that can be used to call a server function. pub struct ArcServerMultiAction where S: ServerFn + 'static, @@ -21,6 +22,7 @@ where S::Output: Send + Sync + 'static, S::Error: Send + Sync + 'static, { + /// Creates a new [`ArcMultiAction`] which, when dispatched, will call the server function `S`. #[track_caller] pub fn new() -> Self { Self { @@ -87,6 +89,7 @@ where } } +/// A [`MultiAction`] that can be used to call a server function. pub struct ServerMultiAction where S: ServerFn + 'static, @@ -114,6 +117,7 @@ where S::Output: Send + Sync + 'static, S::Error: Send + Sync + 'static, { + /// Creates a new [`MultiAction`] which, when dispatched, will call the server function `S`. pub fn new() -> Self { Self { inner: MultiAction::new(|input: &S| { diff --git a/leptos_server/src/multi_action_old.rs b/leptos_server/src/multi_action_old.rs deleted file mode 100644 index f08767ef4..000000000 --- a/leptos_server/src/multi_action_old.rs +++ /dev/null @@ -1,344 +0,0 @@ -use leptos_reactive::{ - is_suppressing_resource_load, signal_prelude::*, spawn_local, store_value, - untrack, StoredValue, -}; -use server_fn::{ServerFn, ServerFnError}; -use std::{future::Future, pin::Pin, rc::Rc}; - -/// An action that synchronizes multiple imperative `async` calls to the reactive system, -/// tracking the progress of each one. -/// -/// Where an [Action](crate::Action) fires a single call, a `MultiAction` allows you to -/// keep track of multiple in-flight actions. -/// -/// If you’re trying to load data by running an `async` function reactively, you probably -/// want to use a [Resource](leptos_reactive::Resource) instead. If you’re trying to occasionally -/// run an `async` function in response to something like a user adding a task to a todo list, -/// you’re in the right place. -/// -/// ```rust -/// # use leptos::*; -/// # let runtime = create_runtime(); -/// async fn send_new_todo_to_api(task: String) -> usize { -/// // do something... -/// // return a task id -/// 42 -/// } -/// let add_todo = create_multi_action(|task: &String| { -/// // `task` is given as `&String` because its value is available in `input` -/// send_new_todo_to_api(task.clone()) -/// }); -/// -/// # if false { -/// add_todo.dispatch("Buy milk".to_string()); -/// add_todo.dispatch("???".to_string()); -/// add_todo.dispatch("Profit!!!".to_string()); -/// # } -/// -/// # runtime.dispose(); -/// ``` -/// -/// The input to the `async` function should always be a single value, -/// but it can be of any type. The argument is always passed by reference to the -/// function, because it is stored in [Submission::input] as well. -/// -/// ```rust -/// # use leptos::*; -/// # let runtime = create_runtime(); -/// // if there's a single argument, just use that -/// let action1 = create_multi_action(|input: &String| { -/// let input = input.clone(); -/// async move { todo!() } -/// }); -/// -/// // if there are no arguments, use the unit type `()` -/// let action2 = create_multi_action(|input: &()| async { todo!() }); -/// -/// // if there are multiple arguments, use a tuple -/// let action3 = -/// create_multi_action(|input: &(usize, String)| async { todo!() }); -/// # runtime.dispose(); -/// ``` -pub struct MultiAction(StoredValue>) -where - I: 'static, - O: 'static; - -impl MultiAction -where - I: 'static, - O: 'static, -{ -} - -impl Clone for MultiAction -where - I: 'static, - O: 'static, -{ - fn clone(&self) -> Self { - *self - } -} - -impl Copy for MultiAction -where - I: 'static, - O: 'static, -{ -} - -impl MultiAction -where - I: 'static, - O: 'static, -{ - /// Calls the `async` function with a reference to the input type as its argument. - #[cfg_attr( - feature = "tracing", - tracing::instrument(level = "trace", skip_all) - )] - pub fn dispatch(&self, input: I) { - self.0.with_value(|a| a.dispatch(input)) - } - - /// The set of all submissions to this multi-action. - #[cfg_attr( - feature = "tracing", - tracing::instrument(level = "trace", skip_all) - )] - pub fn submissions(&self) -> ReadSignal>> { - self.0.with_value(|a| a.submissions()) - } - - /// The URL associated with the action (typically as part of a server function.) - /// This enables integration with the `MultiActionForm` component in `leptos_router`. - pub fn url(&self) -> Option { - self.0.with_value(|a| a.url.as_ref().cloned()) - } - - /// How many times an action has successfully resolved. - #[cfg_attr( - feature = "tracing", - tracing::instrument(level = "trace", skip_all) - )] - pub fn version(&self) -> RwSignal { - self.0.with_value(|a| a.version) - } - - /// Associates the URL of the given server function with this action. - /// This enables integration with the `MultiActionForm` component in `leptos_router`. - #[cfg_attr( - feature = "tracing", - tracing::instrument(level = "trace", skip_all) - )] - pub fn using_server_fn(self) -> Self { - self.0.update_value(|a| { - a.url = Some(T::url().to_string()); - }); - - self - } -} - -struct MultiActionState -where - I: 'static, - O: 'static, -{ - /// How many times an action has successfully resolved. - pub version: RwSignal, - submissions: RwSignal>>, - url: Option, - #[allow(clippy::complexity)] - action_fn: Rc Pin>>>, -} - -/// An action that has been submitted by dispatching it to a [MultiAction](crate::MultiAction). -pub struct Submission -where - I: 'static, - O: 'static, -{ - /// The current argument that was dispatched to the `async` function. - /// `Some` while we are waiting for it to resolve, `None` if it has resolved. - pub input: RwSignal>, - /// The most recent return value of the `async` function. - pub value: RwSignal>, - pub(crate) pending: RwSignal, - /// Controls this submission has been canceled. - pub canceled: RwSignal, -} - -impl Clone for Submission { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for Submission {} - -impl Submission -where - I: 'static, - O: 'static, -{ - /// Whether this submission is currently waiting to resolve. - pub fn pending(&self) -> ReadSignal { - self.pending.read_only() - } - - /// Cancels the submission, preventing it from resolving. - pub fn cancel(&self) { - self.canceled.set(true); - } -} - -impl MultiActionState -where - I: 'static, - O: 'static, -{ - /// Calls the `async` function with a reference to the input type as its argument. - #[cfg_attr( - feature = "tracing", - tracing::instrument(level = "trace", skip_all) - )] - pub fn dispatch(&self, input: I) { - if !is_suppressing_resource_load() { - let fut = (self.action_fn)(&input); - - let submission = Submission { - input: create_rw_signal(Some(input)), - value: create_rw_signal(None), - pending: create_rw_signal(true), - canceled: create_rw_signal(false), - }; - - self.submissions.update(|subs| subs.push(submission)); - - let canceled = submission.canceled; - let input = submission.input; - let pending = submission.pending; - let value = submission.value; - let version = self.version; - - spawn_local(async move { - let new_value = fut.await; - let canceled = untrack(move || canceled.get()); - if !canceled { - value.set(Some(new_value)); - } - input.set(None); - pending.set(false); - version.update(|n| *n += 1); - }) - } - } - - /// The set of all submissions to this multi-action. - pub fn submissions(&self) -> ReadSignal>> { - self.submissions.read_only() - } -} - -/// Creates an [MultiAction] to synchronize an imperative `async` call to the synchronous reactive system. -/// -/// If you’re trying to load data by running an `async` function reactively, you probably -/// want to use a [create_resource](leptos_reactive::create_resource) instead. If you’re trying -/// to occasionally run an `async` function in response to something like a user clicking a button, -/// you're in the right place. -/// -/// ```rust -/// # use leptos::*; -/// # let runtime = create_runtime(); -/// async fn send_new_todo_to_api(task: String) -> usize { -/// // do something... -/// // return a task id -/// 42 -/// } -/// let add_todo = create_multi_action(|task: &String| { -/// // `task` is given as `&String` because its value is available in `input` -/// send_new_todo_to_api(task.clone()) -/// }); -/// # if false { -/// -/// add_todo.dispatch("Buy milk".to_string()); -/// add_todo.dispatch("???".to_string()); -/// add_todo.dispatch("Profit!!!".to_string()); -/// -/// assert_eq!(add_todo.submissions().get().len(), 3); -/// # } -/// # runtime.dispose(); -/// ``` -/// -/// The input to the `async` function should always be a single value, -/// but it can be of any type. The argument is always passed by reference to the -/// function, because it is stored in [Submission::input] as well. -/// -/// ```rust -/// # use leptos::*; -/// # let runtime = create_runtime(); -/// // if there's a single argument, just use that -/// let action1 = create_multi_action(|input: &String| { -/// let input = input.clone(); -/// async move { todo!() } -/// }); -/// -/// // if there are no arguments, use the unit type `()` -/// let action2 = create_multi_action(|input: &()| async { todo!() }); -/// -/// // if there are multiple arguments, use a tuple -/// let action3 = -/// create_multi_action(|input: &(usize, String)| async { todo!() }); -/// # runtime.dispose(); -/// ``` -#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))] -pub fn create_multi_action(action_fn: F) -> MultiAction -where - I: 'static, - O: 'static, - F: Fn(&I) -> Fu + 'static, - Fu: Future + 'static, -{ - let version = create_rw_signal(0); - let submissions = create_rw_signal(Vec::new()); - let action_fn = Rc::new(move |input: &I| { - let fut = action_fn(input); - Box::pin(fut) as Pin>> - }); - - MultiAction(store_value(MultiActionState { - version, - submissions, - url: None, - action_fn, - })) -} - -/// Creates an [MultiAction] that can be used to call a server function. -/// -/// ```rust,ignore -/// # use leptos::*; -/// -/// #[server(MyServerFn)] -/// async fn my_server_fn() -> Result<(), ServerFnError> { -/// todo!() -/// } -/// -/// # let runtime = create_runtime(); -/// let my_server_multi_action = create_server_multi_action::(); -/// # runtime.dispose(); -/// ``` -#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))] -pub fn create_server_multi_action( -) -> MultiAction>> -where - S: Clone + ServerFn, -{ - #[cfg(feature = "ssr")] - let c = move |args: &S| S::run_body(args.clone()); - #[cfg(not(feature = "ssr"))] - let c = move |args: &S| S::run_on_client(args.clone()); - create_multi_action(c).using_server_fn::() -} diff --git a/leptos_server/src/once_resource.rs b/leptos_server/src/once_resource.rs index 8bcfd4bdd..06d28b37d 100644 --- a/leptos_server/src/once_resource.rs +++ b/leptos_server/src/once_resource.rs @@ -43,6 +43,15 @@ use std::{ task::{Context, Poll, Waker}, }; +/// A reference-counted resource that only loads once. +/// +/// Resources allow asynchronously loading data and serializing it from the server to the client, +/// so that it loads on the server, and is then deserialized on the client. This improves +/// performance by beginning data loading on the server when the request is made, rather than +/// beginning it on the client after WASM has been loaded. +/// +/// You can access the value of the resource either synchronously using `.get()` or asynchronously +/// using `.await`. #[derive(Debug)] pub struct ArcOnceResource { trigger: ArcTrigger, @@ -80,6 +89,12 @@ where >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a new resource with the encoding `Ser`. If `blocking` is `true`, this is a blocking + /// resource. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_with_options( fut: impl Future + Send + 'static, @@ -287,11 +302,17 @@ where >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a resource using [`JsonSerdeCodec`] for encoding/decoding the value. #[track_caller] pub fn new(fut: impl Future + Send + 'static) -> Self { ArcOnceResource::new_with_options(fut, false) } + /// Creates a blocking resource using [`JsonSerdeCodec`] for encoding/decoding the value. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_blocking(fut: impl Future + Send + 'static) -> Self { ArcOnceResource::new_with_options(fut, true) @@ -307,6 +328,7 @@ T: Send + Sync + 'static, >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a resource using [`FromToStringCodec`] for encoding/decoding the value. pub fn new_str( fut: impl Future + Send + 'static ) -> Self @@ -314,6 +336,11 @@ T: Send + Sync + 'static, ArcOnceResource::new_with_options(fut, false) } + /// Creates a blocking resource using [`FromToStringCodec`] for encoding/decoding the value. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. pub fn new_str_blocking( fut: impl Future + Send + 'static ) -> Self @@ -332,6 +359,7 @@ T: Send + Sync + 'static, >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a resource using [`JsonSerdeWasmCodec`] for encoding/decoding the value. #[track_caller] pub fn new_serde_wb( fut: impl Future + Send + 'static @@ -340,6 +368,11 @@ fut: impl Future + Send + 'static ArcOnceResource::new_with_options(fut, false) } + /// Creates a blocking resource using [`JsonSerdeWasmCodec`] for encoding/decoding the value. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_serde_wb_blocking( fut: impl Future + Send + 'static @@ -360,6 +393,7 @@ where >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a resource using [`MiniserdeCodec`] for encoding/decoding the value. #[track_caller] pub fn new_miniserde( fut: impl Future + Send + 'static, @@ -367,6 +401,11 @@ where ArcOnceResource::new_with_options(fut, false) } + /// Creates a blocking resource using [`MiniserdeCodec`] for encoding/decoding the value. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_miniserde_blocking( fut: impl Future + Send + 'static, @@ -385,6 +424,7 @@ T: Send + Sync + 'static, as Encoder>::Encoded: IntoEncodedString, as Decoder>::Encoded: FromEncodedStr, { + /// Creates a resource using [`SerdeLite`] for encoding/decoding the value. #[track_caller] pub fn new_serde_lite( fut: impl Future + Send + 'static @@ -393,6 +433,11 @@ fut: impl Future + Send + 'static ArcOnceResource::new_with_options(fut, false) } + /// Creates a blocking resource using [`SerdeLite`] for encoding/decoding the value. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_serde_lite_blocking( fut: impl Future + Send + 'static @@ -414,11 +459,17 @@ where >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a resource using [`RkyvCodec`] for encoding/decoding the value. #[track_caller] pub fn new_rkyv(fut: impl Future + Send + 'static) -> Self { ArcOnceResource::new_with_options(fut, false) } + /// Creates a blocking resource using [`RkyvCodec`] for encoding/decoding the value. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_rkyv_blocking( fut: impl Future + Send + 'static, @@ -427,6 +478,15 @@ where } } +/// A resource that only loads once. +/// +/// Resources allow asynchronously loading data and serializing it from the server to the client, +/// so that it loads on the server, and is then deserialized on the client. This improves +/// performance by beginning data loading on the server when the request is made, rather than +/// beginning it on the client after WASM has been loaded. +/// +/// You can access the value of the resource either synchronously using `.get()` or asynchronously +/// using `.await`. #[derive(Debug)] pub struct OnceResource { inner: ArenaItem>, @@ -452,6 +512,12 @@ where >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a new resource with the encoding `Ser`. If `blocking` is `true`, this is a blocking + /// resource. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_with_options( fut: impl Future + Send + 'static, @@ -567,11 +633,17 @@ where >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a resource using [`JsonSerdeCodec`] for encoding/decoding the value. #[track_caller] pub fn new(fut: impl Future + Send + 'static) -> Self { OnceResource::new_with_options(fut, false) } + /// Creates a blocking resource using [`JsonSerdeCodec`] for encoding/decoding the value. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_blocking(fut: impl Future + Send + 'static) -> Self { OnceResource::new_with_options(fut, true) @@ -587,6 +659,7 @@ T: Send + Sync + 'static, >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a resource using [`FromToStringCodec`] for encoding/decoding the value. pub fn new_str( fut: impl Future + Send + 'static ) -> Self @@ -594,6 +667,11 @@ T: Send + Sync + 'static, OnceResource::new_with_options(fut, false) } + /// Creates a blocking resource using [`FromToStringCodec`] for encoding/decoding the value. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. pub fn new_str_blocking( fut: impl Future + Send + 'static ) -> Self @@ -612,6 +690,7 @@ T: Send + Sync + 'static, >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a resource using [`JsonSerdeWasmCodec`] for encoding/decoding the value. #[track_caller] pub fn new_serde_wb( fut: impl Future + Send + 'static @@ -620,6 +699,11 @@ fut: impl Future + Send + 'static OnceResource::new_with_options(fut, false) } + /// Creates a blocking resource using [`JsonSerdeWasmCodec`] for encoding/decoding the value. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_serde_wb_blocking( fut: impl Future + Send + 'static @@ -640,6 +724,7 @@ where >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a resource using [`MiniserdeCodec`] for encoding/decoding the value. #[track_caller] pub fn new_miniserde( fut: impl Future + Send + 'static, @@ -647,6 +732,11 @@ where OnceResource::new_with_options(fut, false) } + /// Creates a blocking resource using [`MiniserdeCodec`] for encoding/decoding the value. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_miniserde_blocking( fut: impl Future + Send + 'static, @@ -665,6 +755,7 @@ T: Send + Sync + 'static, as Encoder>::Encoded: IntoEncodedString, as Decoder>::Encoded: FromEncodedStr, { + /// Creates a resource using [`SerdeLite`] for encoding/decoding the value. #[track_caller] pub fn new_serde_lite( fut: impl Future + Send + 'static @@ -673,6 +764,11 @@ fut: impl Future + Send + 'static OnceResource::new_with_options(fut, false) } + /// Creates a blocking resource using [`SerdeLite`] for encoding/decoding the value. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_serde_lite_blocking( fut: impl Future + Send + 'static @@ -694,11 +790,17 @@ where >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a resource using [`RkyvCodec`] for encoding/decoding the value. #[track_caller] pub fn new_rkyv(fut: impl Future + Send + 'static) -> Self { OnceResource::new_with_options(fut, false) } + /// Creates a blocking resource using [`RkyvCodec`] for encoding/decoding the value. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_rkyv_blocking( fut: impl Future + Send + 'static, diff --git a/leptos_server/src/resource.rs b/leptos_server/src/resource.rs index e9f657074..f5ca00017 100644 --- a/leptos_server/src/resource.rs +++ b/leptos_server/src/resource.rs @@ -37,9 +37,12 @@ use std::{ pub(crate) static IS_SUPPRESSING_RESOURCE_LOAD: AtomicBool = AtomicBool::new(false); +/// Used to prevent resources from actually loading, in environments (like server route generation) +/// where they are not needed. pub struct SuppressResourceLoad; impl SuppressResourceLoad { + /// Prevents resources from loading until this is dropped. pub fn new() -> Self { IS_SUPPRESSING_RESOURCE_LOAD.store(true, Ordering::Relaxed); Self @@ -58,6 +61,15 @@ impl Drop for SuppressResourceLoad { } } +/// A reference-counted asynchronous resource. +/// +/// Resources allow asynchronously loading data and serializing it from the server to the client, +/// so that it loads on the server, and is then deserialized on the client. This improves +/// performance by beginning data loading on the server when the request is made, rather than +/// beginning it on the client after WASM has been loaded. +/// +/// You can access the value of the resource either synchronously using `.get()` or asynchronously +/// using `.await`. pub struct ArcResource { ser: PhantomData, refetch: ArcRwSignal, @@ -194,6 +206,21 @@ where >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a new resource with the encoding `Ser`. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. + /// + /// If `blocking` is `true`, this is a blocking resource. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_with_options( source: impl Fn() -> S + Send + Sync + 'static, @@ -278,6 +305,8 @@ where } } + /// Synchronously, reactively reads the current value of the resource and applies the function + /// `f` to its value if it is `Some(_)`. #[track_caller] pub fn map(&self, f: impl FnOnce(&T) -> U) -> Option where @@ -351,6 +380,13 @@ where T: Send + Sync + 'static, E: Send + Sync + Clone + 'static, { + /// Applies the given function when a resource that returns `Result` + /// has resolved and loaded an `Ok(_)`, rather than requiring nested `.map()` + /// calls over the `Option>` returned by the resource. + /// + /// This is useful when used with features like server functions, in conjunction + /// with `` and ``, when these other components are + /// left to handle the `None` and `Err(_)` states. #[track_caller] pub fn and_then(&self, f: impl FnOnce(&T) -> U) -> Option> { self.map(|data| data.as_ref().map(f).map_err(|e| e.clone())) @@ -367,6 +403,15 @@ where >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a new resource with the encoding [`JsonSerdeCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. #[track_caller] pub fn new( source: impl Fn() -> S + Send + Sync + 'static, @@ -380,6 +425,19 @@ where ArcResource::new_with_options(source, fetcher, false) } + /// Creates a new blocking resource with the encoding [`JsonSerdeCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_blocking( source: impl Fn() -> S + Send + Sync + 'static, @@ -402,6 +460,15 @@ where >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a new resource with the encoding [`FromToStringCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. pub fn new_str( source: impl Fn() -> S + Send + Sync + 'static, fetcher: impl Fn(S) -> Fut + Send + Sync + 'static, @@ -414,6 +481,19 @@ where ArcResource::new_with_options(source, fetcher, false) } + /// Creates a new blocking resource with the encoding [`FromToStringCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. pub fn new_str_blocking( source: impl Fn() -> S + Send + Sync + 'static, fetcher: impl Fn(S) -> Fut + Send + Sync + 'static, @@ -436,6 +516,15 @@ where >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a new resource with the encoding [`JsonSerdeWasmCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. #[track_caller] pub fn new_serde_wb( source: impl Fn() -> S + Send + Sync + 'static, @@ -449,6 +538,19 @@ where ArcResource::new_with_options(source, fetcher, false) } + /// Creates a new blocking resource with the encoding [`JsonSerdeWasmCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_serde_wb_blocking( source: impl Fn() -> S + Send + Sync + 'static, @@ -473,6 +575,15 @@ where >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a new resource with the encoding [`MiniserdeCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. #[track_caller] pub fn new_miniserde( source: impl Fn() -> S + Send + Sync + 'static, @@ -486,6 +597,19 @@ where ArcResource::new_with_options(source, fetcher, false) } + /// Creates a new blocking resource with the encoding [`MiniserdeCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_miniserde_blocking( source: impl Fn() -> S + Send + Sync + 'static, @@ -509,6 +633,15 @@ where as Encoder>::Encoded: IntoEncodedString, as Decoder>::Encoded: FromEncodedStr, { + /// Creates a new resource with the encoding [`SerdeLite`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. #[track_caller] pub fn new_serde_lite( source: impl Fn() -> S + Send + Sync + 'static, @@ -522,6 +655,19 @@ where ArcResource::new_with_options(source, fetcher, false) } + /// Creates a new blocking resource with the encoding [`SerdeLite`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_serde_lite_blocking( source: impl Fn() -> S + Send + Sync + 'static, @@ -547,6 +693,15 @@ where >::Encoded: IntoEncodedString, >::Encoded: FromEncodedStr, { + /// Creates a new resource with the encoding [`RkyvCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. #[track_caller] pub fn new_rkyv( source: impl Fn() -> S + Send + Sync + 'static, @@ -560,6 +715,19 @@ where ArcResource::new_with_options(source, fetcher, false) } + /// Creates a new blocking resource with the encoding [`RkyvCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_rkyv_blocking( source: impl Fn() -> S + Send + Sync + 'static, @@ -590,11 +758,22 @@ impl ArcResource where T: 'static, { + /// Returns a new [`Future`] that is ready when the resource has loaded, and accesses its inner + /// value by reference. pub fn by_ref(&self) -> AsyncDerivedRefFuture { self.data.by_ref() } } +/// An asynchronous resource. +/// +/// Resources allow asynchronously loading data and serializing it from the server to the client, +/// so that it loads on the server, and is then deserialized on the client. This improves +/// performance by beginning data loading on the server when the request is made, rather than +/// beginning it on the client after WASM has been loaded. +/// +/// You can access the value of the resource either synchronously using `.get()` or asynchronously +/// using `.await`. pub struct Resource where T: Send + Sync + 'static, @@ -707,6 +886,15 @@ where >::Encoded: FromEncodedStr, T: Send + Sync, { + /// Creates a new resource with the encoding [`FromToStringCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. #[track_caller] pub fn new_str( source: impl Fn() -> S + Send + Sync + 'static, @@ -720,6 +908,19 @@ where Resource::new_with_options(source, fetcher, false) } + /// Creates a new blocking resource with the encoding [`FromToStringCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_str_blocking( source: impl Fn() -> S + Send + Sync + 'static, @@ -745,6 +946,15 @@ where >::Encoded: FromEncodedStr, T: Send + Sync, { + /// Creates a new resource with the encoding [`JsonSerdeCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. #[track_caller] pub fn new( source: impl Fn() -> S + Send + Sync + 'static, @@ -758,6 +968,19 @@ where Resource::new_with_options(source, fetcher, false) } + /// Creates a new blocking resource with the encoding [`JsonSerdeCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_blocking( source: impl Fn() -> S + Send + Sync + 'static, @@ -782,6 +1005,15 @@ where >::Encoded: FromEncodedStr, T: Send + Sync, { + /// Creates a new resource with the encoding [`JsonSerdeWasmCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. pub fn new_serde_wb( source: impl Fn() -> S + Send + Sync + 'static, fetcher: impl Fn(S) -> Fut + Send + Sync + 'static, @@ -794,6 +1026,19 @@ where Resource::new_with_options(source, fetcher, false) } + /// Creates a new blocking resource with the encoding [`JsonSerdeWasmCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. pub fn new_serde_wb_blocking( source: impl Fn() -> S + Send + Sync + 'static, fetcher: impl Fn(S) -> Fut + Send + Sync + 'static, @@ -819,6 +1064,15 @@ where >::Encoded: FromEncodedStr, T: Send + Sync, { + /// Creates a new resource with the encoding [`MiniserdeCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. pub fn new_miniserde( source: impl Fn() -> S + Send + Sync + 'static, fetcher: impl Fn(S) -> Fut + Send + Sync + 'static, @@ -830,6 +1084,31 @@ where { Resource::new_with_options(source, fetcher, false) } + + /// Creates a new blocking resource with the encoding [`MiniserdeCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. + pub fn new_miniserde_blocking( + source: impl Fn() -> S + Send + Sync + 'static, + fetcher: impl Fn(S) -> Fut + Send + Sync + 'static, + ) -> Self + where + S: PartialEq + Clone + Send + Sync + 'static, + T: Send + Sync + 'static, + Fut: Future + Send + 'static, + { + Resource::new_with_options(source, fetcher, true) + } } #[cfg(feature = "serde-lite")] @@ -843,6 +1122,15 @@ where as Decoder>::Encoded: FromEncodedStr, T: Send + Sync, { + /// Creates a new resource with the encoding [`SerdeLite`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. pub fn new_serde_lite( source: impl Fn() -> S + Send + Sync + 'static, fetcher: impl Fn(S) -> Fut + Send + Sync + 'static, @@ -855,6 +1143,19 @@ where Resource::new_with_options(source, fetcher, false) } + /// Creates a new blocking resource with the encoding [`SerdeLite`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. pub fn new_serde_lite_blocking( source: impl Fn() -> S + Send + Sync + 'static, fetcher: impl Fn(S) -> Fut + Send + Sync + 'static, @@ -880,6 +1181,15 @@ where >::Encoded: FromEncodedStr, T: Send + Sync, { + /// Creates a new resource with the encoding [`RkyvCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. pub fn new_rkyv( source: impl Fn() -> S + Send + Sync + 'static, fetcher: impl Fn(S) -> Fut + Send + Sync + 'static, @@ -892,6 +1202,19 @@ where Resource::new_with_options(source, fetcher, false) } + /// Creates a new blocking resource with the encoding [`RkyvCodec`]. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. pub fn new_rkyv_blocking( source: impl Fn() -> S + Send + Sync + 'static, fetcher: impl Fn(S) -> Fut + Send + Sync + 'static, @@ -915,6 +1238,21 @@ where >::Encoded: FromEncodedStr, T: Send + Sync, { + /// Creates a new resource with the encoding `Ser`. + /// + /// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks + /// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to + /// generate a new [`Future`] to load data. + /// + /// On creation, if you are on the server, this will run the `fetcher` once to generate + /// a `Future` whose value will be serialized from the server to the client. If you are on + /// the client, the initial value will be deserialized without re-running that async task. + /// + /// If `blocking` is `true`, this is a blocking resource. + /// + /// Blocking resources prevent any of the HTTP response from being sent until they have loaded. + /// This is useful if you need their data to set HTML document metadata or information that + /// needs to appear in HTTP headers. #[track_caller] pub fn new_with_options( source: impl Fn() -> S + Send + Sync + 'static, @@ -937,6 +1275,8 @@ where } } + /// Synchronously, reactively reads the current value of the resource and applies the function + /// `f` to its value if it is `Some(_)`. pub fn map(&self, f: impl FnOnce(&T) -> U) -> Option { self.data .try_with(|n| n.as_ref().map(|n| Some(f(n))))? @@ -961,6 +1301,13 @@ where T: Send + Sync, E: Send + Sync + Clone, { + /// Applies the given function when a resource that returns `Result` + /// has resolved and loaded an `Ok(_)`, rather than requiring nested `.map()` + /// calls over the `Option>` returned by the resource. + /// + /// This is useful when used with features like server functions, in conjunction + /// with `` and ``, when these other components are + /// left to handle the `None` and `Err(_)` states. #[track_caller] pub fn and_then(&self, f: impl FnOnce(&T) -> U) -> Option> { self.map(|data| data.as_ref().map(f).map_err(|e| e.clone())) @@ -984,6 +1331,8 @@ impl Resource where T: Send + Sync + 'static, { + /// Returns a new [`Future`] that is ready when the resource has loaded, and accesses its inner + /// value by reference. pub fn by_ref(&self) -> AsyncDerivedRefFuture { self.data.by_ref() } diff --git a/leptos_server/src/shared.rs b/leptos_server/src/shared.rs index 97f45d64a..ef2da41d2 100644 --- a/leptos_server/src/shared.rs +++ b/leptos_server/src/shared.rs @@ -31,6 +31,7 @@ pub struct SharedValue { } impl SharedValue { + /// Returns the inner value. pub fn into_inner(self) -> T { self.value } @@ -46,6 +47,12 @@ where <>::Encoded as FromEncodedStr>::DecodingError: Debug, { + /// Wraps the initial value. + /// + /// If this is on the server, the function will be invoked and the value serialized. When it runs + /// on the client, it will be deserialized without running the function again. + /// + /// This uses the [`JsonSerdeCodec`] encoding. pub fn new(initial: impl FnOnce() -> T) -> Self { SharedValue::new_with_encoding(initial) } @@ -61,6 +68,12 @@ where <>::Encoded as FromEncodedStr>::DecodingError: Debug, { + /// Wraps the initial value. + /// + /// If this is on the server, the function will be invoked and the value serialized. When it runs + /// on the client, it will be deserialized without running the function again. + /// + /// This uses the [`FromToStringCodec`] encoding. pub fn new_str(initial: impl FnOnce() -> T) -> Self { SharedValue::new_with_encoding(initial) } @@ -77,7 +90,13 @@ where < as codee::Decoder>::Encoded as FromEncodedStr>::DecodingError: Debug, { - pub fn new(initial: impl FnOnce() -> T) -> Self { + /// Wraps the initial value. + /// + /// If this is on the server, the function will be invoked and the value serialized. When it runs + /// on the client, it will be deserialized without running the function again. + /// + /// This uses the [`SerdeLite`] encoding. + pub fn new_serde_lite(initial: impl FnOnce() -> T) -> Self { SharedValue::new_with_encoding(initial) } } @@ -93,7 +112,13 @@ where <>::Encoded as FromEncodedStr>::DecodingError: Debug, { - pub fn new(initial: impl FnOnce() -> T) -> Self { + /// Wraps the initial value. + /// + /// If this is on the server, the function will be invoked and the value serialized. When it runs + /// on the client, it will be deserialized without running the function again. + /// + /// This uses the [`JsonSerdeWasmCodec`] encoding. + pub fn new_serde_wb(initial: impl FnOnce() -> T) -> Self { SharedValue::new_with_encoding(initial) } } @@ -109,7 +134,13 @@ where <>::Encoded as FromEncodedStr>::DecodingError: Debug, { - pub fn new(initial: impl FnOnce() -> T) -> Self { + /// Wraps the initial value. + /// + /// If this is on the server, the function will be invoked and the value serialized. When it runs + /// on the client, it will be deserialized without running the function again. + /// + /// This uses the [`MiniserdeCodec`] encoding. + pub fn new_miniserde(initial: impl FnOnce() -> T) -> Self { SharedValue::new_with_encoding(initial) } } @@ -125,7 +156,13 @@ where <>::Encoded as FromEncodedStr>::DecodingError: Debug, { - pub fn new(initial: impl FnOnce() -> T) -> Self { + /// Wraps the initial value. + /// + /// If this is on the server, the function will be invoked and the value serialized. When it runs + /// on the client, it will be deserialized without running the function again. + /// + /// This uses the [`RkyvCodec`] encoding. + pub fn new_rkyv(initial: impl FnOnce() -> T) -> Self { SharedValue::new_with_encoding(initial) } } @@ -140,6 +177,12 @@ where <>::Encoded as FromEncodedStr>::DecodingError: Debug, { + /// Wraps the initial value. + /// + /// If this is on the server, the function will be invoked and the value serialized. When it runs + /// on the client, it will be deserialized without running the function again. + /// + /// This uses `Ser` as an encoding. pub fn new_with_encoding(initial: impl FnOnce() -> T) -> Self { let value: T; #[cfg(feature = "hydration")] diff --git a/next_tuple/src/lib.rs b/next_tuple/src/lib.rs index 51d9a8610..ffc358c20 100644 --- a/next_tuple/src/lib.rs +++ b/next_tuple/src/lib.rs @@ -1,6 +1,10 @@ +//! Defines a trait that allows you to extend a tuple, by returning +//! a new tuple with an element of an arbitrary type added. + #![no_std] #![allow(non_snake_case)] #![forbid(unsafe_code)] +#![deny(missing_docs)] /// Allows extending a tuple, or creating a new tuple, by adding the next value. pub trait NextTuple { diff --git a/router/src/components.rs b/router/src/components.rs index 623b0a763..b84c2c711 100644 --- a/router/src/components.rs +++ b/router/src/components.rs @@ -11,7 +11,7 @@ use crate::{ navigate::NavigateOptions, nested_router::NestedRoutesView, resolve_path::resolve_path, - ChooseView, MatchNestedRoutes, NestedRoute, Routes, SsrMode, + ChooseView, MatchNestedRoutes, NestedRoute, RouteDefs, SsrMode, }; use either_of::Either; use leptos::prelude::*; @@ -30,10 +30,13 @@ use std::{ }; use tachys::view::any_view::AnyView; +/// A wrapper that allows passing route definitions as children to a component like [`Routes`], +/// [`FlatRoutes`], [`ParentRoute`], or [`ProtectedParentRoute`]. #[derive(Debug)] pub struct RouteChildren(Children); impl RouteChildren { + /// Extracts the inner route definition. pub fn into_inner(self) -> Children { self.0 } @@ -211,30 +214,15 @@ impl Debug for RouterContext { } } -/* -#[component] -pub fn FlatRouter( - #[prop(optional, into)] base: Option>, - fallback: FallbackFn, - children: RouteChildren, -) -> FlatRouter -where - FallbackFn: Fn() -> Fallback, -{ - let children = Routes::new(children.into_inner()); - if let Some(base) = base { - FlatRouter::new_with_base(base, children, fallback) - } else { - FlatRouter::new(children, fallback) - } -}*/ - #[component(transparent)] pub fn Routes( + /// A function that returns the view that should be shown if no route is matched. fallback: FallbackFn, /// Whether to use the View Transition API during navigation. #[prop(optional)] transition: bool, + /// The route definitions. This should consist of one or more [`ParentRoute`] or [`Route`] + /// components. children: RouteChildren, ) -> impl IntoView where @@ -255,7 +243,7 @@ where base.upgrade_inplace(); base }); - let routes = Routes::new_with_base( + let routes = RouteDefs::new_with_base( children.into_inner(), base.clone().unwrap_or_default(), ); @@ -281,10 +269,13 @@ where #[component(transparent)] pub fn FlatRoutes( + /// A function that returns the view that should be shown if no route is matched. fallback: FallbackFn, /// Whether to use the View Transition API during navigation. #[prop(optional)] transition: bool, + /// The route definitions. This should consist of one or more [`ParentRoute`] or [`Route`] + /// components. children: RouteChildren, ) -> impl IntoView where @@ -308,7 +299,7 @@ where base.upgrade_inplace(); base }); - let routes = Routes::new_with_base( + let routes = RouteDefs::new_with_base( children.into_inner(), base.clone().unwrap_or_default(), ); @@ -333,11 +324,20 @@ where } } +/// Describes a portion of the nested layout of the app, specifying the route it should match +/// and the element it should display. #[component(transparent)] pub fn Route( + /// The path fragment that this route should match. This can be created using the [`path`] + /// macro, or path segments ([`StaticSegment`], [`ParamSegment`], [`WildcardSegment`], and + /// [`OptionalParamSegment`]). path: Segments, + /// The view for this route. view: View, - #[prop(optional)] ssr: SsrMode, + /// The mode that this route prefers during server-side rendering. + /// Defaults to out-of-order streaming. + #[prop(optional)] + ssr: SsrMode, ) -> NestedRoute where View: ChooseView, @@ -345,12 +345,22 @@ where NestedRoute::new(path, view).ssr_mode(ssr) } +/// Describes a portion of the nested layout of the app, specifying the route it should match +/// and the element it should display. #[component(transparent)] pub fn ParentRoute( + /// The path fragment that this route should match. This can be created using the [`path`] + /// macro, or path segments ([`StaticSegment`], [`ParamSegment`], [`WildcardSegment`], and + /// [`OptionalParamSegment`]). path: Segments, + /// The view for this route. view: View, + /// Nested child routes. children: RouteChildren, - #[prop(optional)] ssr: SsrMode, + /// The mode that this route prefers during server-side rendering. + /// Defaults to out-of-order streaming. + #[prop(optional)] + ssr: SsrMode, ) -> NestedRoute where View: ChooseView, @@ -359,13 +369,27 @@ where NestedRoute::new(path, view).ssr_mode(ssr).child(children) } +/// Describes a route that is guarded by a certain condition. This works the same way as +/// [``], except that if the `condition` function evaluates to `Some(false)`, it +/// redirects to `redirect_path` instead of displaying its `view`. #[component(transparent)] pub fn ProtectedRoute( + /// The path fragment that this route should match. This can be created using the [`path`] + /// macro, or path segments ([`StaticSegment`], [`ParamSegment`], [`WildcardSegment`], and + /// [`OptionalParamSegment`]). path: Segments, + /// The view for this route. view: ViewFn, + /// A function that returns `Option`, where `Some(true)` means that the user can access + /// the page, `Some(false)` means the user cannot access the page, and `None` means this + /// information is still loading. condition: C, + /// The path that will be redirected to if the condition is `Some(false)`. redirect_path: PathFn, - #[prop(optional)] ssr: SsrMode, + /// The mode that this route prefers during server-side rendering. + /// Defaults to out-of-order streaming. + #[prop(optional)] + ssr: SsrMode, ) -> NestedRoute AnyView + Send + Clone> where ViewFn: Fn() -> View + Send + Clone + 'static, @@ -403,12 +427,24 @@ where #[component(transparent)] pub fn ProtectedParentRoute( + /// The path fragment that this route should match. This can be created using the [`path`] + /// macro, or path segments ([`StaticSegment`], [`ParamSegment`], [`WildcardSegment`], and + /// [`OptionalParamSegment`]). path: Segments, + /// The view for this route. view: ViewFn, + /// A function that returns `Option`, where `Some(true)` means that the user can access + /// the page, `Some(false)` means the user cannot access the page, and `None` means this + /// information is still loading. condition: C, + /// The path that will be redirected to if the condition is `Some(false)`. redirect_path: PathFn, + /// Nested child routes. children: RouteChildren, - #[prop(optional)] ssr: SsrMode, + /// The mode that this route prefers during server-side rendering. + /// Defaults to out-of-order streaming. + #[prop(optional)] + ssr: SsrMode, ) -> NestedRoute AnyView + Send + Clone> where ViewFn: Fn() -> View + Send + Clone + 'static, diff --git a/router/src/flat_router.rs b/router/src/flat_router.rs index ac4a582ee..0f17019e5 100644 --- a/router/src/flat_router.rs +++ b/router/src/flat_router.rs @@ -1,7 +1,7 @@ use crate::{ hooks::Matched, location::{LocationProvider, Url}, - matching::{MatchParams, Routes}, + matching::{MatchParams, RouteDefs}, params::ParamsMap, view_transition::start_view_transition, ChooseView, MatchInterface, MatchNestedRoutes, PathSegment, RouteList, @@ -33,14 +33,15 @@ use tachys::{ pub(crate) struct FlatRoutesView { pub current_url: ArcRwSignal, pub location: Option, - pub routes: Routes, + pub routes: RouteDefs, pub fallback: FalFn, pub outer_owner: Owner, pub set_is_routing: Option>, pub transition: bool, } -pub struct FlatRoutesViewState { +/// Retained view state for the flat router. +pub(crate) struct FlatRoutesViewState { #[allow(clippy::type_complexity)] view: AnyViewState, id: Option, diff --git a/router/src/generate_route_list.rs b/router/src/generate_route_list.rs index 4933007eb..5592be1e8 100644 --- a/router/src/generate_route_list.rs +++ b/router/src/generate_route_list.rs @@ -75,11 +75,13 @@ impl RouteListing { } } + /// Generates the set of static paths for this route listing, depending on prerendered params. pub async fn into_static_paths(self) -> Option> { let params = self.static_route()?.to_prerendered_params().await; Some(StaticPath::new(self.path).into_paths(params)) } + /// Generates static files for this route listing. pub async fn generate_static_files( mut self, render_fn: impl Fn(&ResolvedStaticPath) -> Fut + Send + Clone + 'static, @@ -148,6 +150,7 @@ impl RouteListing { */ } +/// A set of routes generated from the route definitions. #[derive(Debug, Default, Clone)] pub struct RouteList(Vec); @@ -158,24 +161,29 @@ impl From> for RouteList { } impl RouteList { + /// Adds a route listing. pub fn push(&mut self, data: RouteListing) { self.0.push(data); } } impl RouteList { + /// Creates an empty list of routes. pub fn new() -> Self { Self(Vec::new()) } + /// Returns the list of routes. pub fn into_inner(self) -> Vec { self.0 } + /// Returns and iterator over the list of routes. pub fn iter(&self) -> impl Iterator { self.0.iter() } + /// Generates a list of resolved static paths based on the inner list of route listings. pub async fn into_static_paths(self) -> Vec { futures::future::join_all( self.into_inner() @@ -189,6 +197,7 @@ impl RouteList { .collect::>() } + /// Generates static files for the inner list of route listings. pub async fn generate_static_files( self, render_fn: impl Fn(&ResolvedStaticPath) -> Fut + Send + Clone + 'static, @@ -220,6 +229,7 @@ impl RouteList { static GENERATED: RefCell> = const { RefCell::new(None) }; } + /// Creates a list of routes, based on route definitions in the given app. pub fn generate(app: impl FnOnce() -> T) -> Option where T: RenderHtml, @@ -233,10 +243,12 @@ impl RouteList { Self::GENERATED.take() } + /// Returns `true` if we are currently in a [`RouteList::generate`] call. pub fn is_generating() -> bool { Self::IS_GENERATING.get() } + /// Sets the given routes as the list of generated routes. pub fn register(routes: RouteList) { Self::GENERATED.with(|inner| { *inner.borrow_mut() = Some(routes); diff --git a/router/src/hooks.rs b/router/src/hooks.rs index 86f9fc22f..98d5b5d66 100644 --- a/router/src/hooks.rs +++ b/router/src/hooks.rs @@ -17,6 +17,7 @@ use std::{ sync::atomic::{AtomicBool, Ordering}, }; +/// See [`query_signal`]. #[track_caller] #[deprecated = "This has been renamed to `query_signal` to match Rust naming \ conventions."] @@ -29,6 +30,7 @@ where query_signal(key) } +/// See [`query_signal_with_options`]. #[track_caller] #[deprecated = "This has been renamed to `query_signal_with_options` to mtch \ Rust naming conventions."] @@ -88,6 +90,9 @@ where query_signal_with_options::(key, NavigateOptions::default()) } +/// Constructs a signal synchronized with a specific URL query parameter. +/// +/// This is the same as [`query_signal`], but allows you to specify additional navigation options. #[track_caller] pub fn query_signal_with_options( key: impl Into>, @@ -205,6 +210,7 @@ fn use_url_raw() -> ArcRwSignal { }) } +/// Gives reactive access to the current URL. #[track_caller] pub fn use_url() -> ReadSignal { use_url_raw().read_only().into() diff --git a/router/src/lib.rs b/router/src/lib.rs index 4e4c16c40..62245f890 100644 --- a/router/src/lib.rs +++ b/router/src/lib.rs @@ -1,20 +1,148 @@ +//! # Leptos Router +//! +//! Leptos Router is a router and state management tool for web applications +//! written in Rust using the Leptos web framework. +//! It is ”isomorphic”, i.e., it can be used for client-side applications/single-page +//! apps (SPAs), server-side rendering/multi-page apps (MPAs), or to synchronize +//! state between the two. +//! +//! ## Philosophy +//! +//! Leptos Router is built on a few simple principles: +//! 1. **URL drives state.** For web applications, the URL should be the ultimate +//! source of truth for most of your app’s state. (It’s called a **Universal +//! Resource Locator** for a reason!) +//! +//! 2. **Nested routing.** A URL can match multiple routes that exist in a nested tree +//! and are rendered by different components. This means you can navigate between siblings +//! in this tree without re-rendering or triggering any change in the parent routes. +//! +//! 3. **Progressive enhancement.** The [`A`] and [`Form`] components resolve any relative +//! nested routes, render actual `` and `` elements, and (when possible) +//! upgrading them to handle those navigations with client-side routing. If you’re using +//! them with server-side rendering (with or without hydration), they just work, +//! whether JS/WASM have loaded or not. +//! +//! ## Example +//! +//! ```rust +//! use leptos::prelude::*; +//! use leptos_router::components::*; +//! use leptos_router::path; +//! use leptos_router::hooks::use_params_map; +//! +//! #[component] +//! pub fn RouterExample() -> impl IntoView { +//! view! { +//! +//!
+//! // we wrap the whole app in a to allow client-side navigation +//! // from our nav links below +//! +//!
+//! // both defines our routes and shows them on the page +//! +//! // our root route: the contact list is always shown +//! +//! // users like /gbj or /bob +//! +//! // a fallback if the /:id segment is missing from the URL +//! "Select a contact."

} +//! /> +//!
+//!
+//!
+//!
+//!
+//! } +//! } +//! +//! type ContactSummary = (); // TODO! +//! type Contact = (); // TODO!() +//! +//! // contact_data reruns whenever the :id param changes +//! async fn contact_data(id: String) -> Contact { +//! todo!() +//! } +//! +//! // contact_list_data *doesn't* rerun when the :id changes, +//! // because that param is nested lower than the route +//! async fn contact_list_data() -> Vec { +//! todo!() +//! } +//! +//! #[component] +//! fn ContactList() -> impl IntoView { +//! // loads the contact list data once; doesn't reload when nested routes change +//! let contacts = Resource::new(|| (), |_| contact_list_data()); +//! view! { +//! +//!
+//! // show the contacts +//!
    +//! {move || contacts.get().map(|contacts| view! {
  • "todo contact info"
  • } )} +//!
+//! +//! // insert the nested child route here +//! +//!
+//! } +//! } +//! +//! #[component] +//! fn Contact() -> impl IntoView { +//! let params = use_params_map(); +//! let data = Resource::new( +//! move || params.read().get("id").unwrap_or_default(), +//! move |id| contact_data(id) +//! ); +//! todo!() +//! } +//! ``` +//! +//! You can find examples of additional APIs in the [`router`] example. +//! +//! # Feature Flags +//! - `ssr` Server-side rendering: Generate an HTML string (typically on the server) +//! - `nightly`: On `nightly` Rust, enables the function-call syntax for signal getters and setters. +//! - `tracing`: Enables support for the `tracing` crate. +//! +//! [`Leptos`]: +//! [`router`]: + #![forbid(unsafe_code)] +#![deny(missing_docs)] #![cfg_attr(feature = "nightly", feature(auto_traits))] #![cfg_attr(feature = "nightly", feature(negative_impls))] +/// Components for route definition and for enhanced links and forms. pub mod components; +/// An optimized "flat" router without nested routes. pub mod flat_router; mod form; mod generate_route_list; +/// Hooks that can be used to access router state inside your components. pub mod hooks; mod link; +/// Utilities for accessing the current location. pub mod location; mod matching; mod method; mod navigate; +/// A nested router that supports multiple levels of route definitions. pub mod nested_router; +/// Support for maps of parameters in the path or in the query. pub mod params; mod ssr_mode; +/// Support for static routing. pub mod static_routes; pub use generate_route_list::*; diff --git a/router/src/location/mod.rs b/router/src/location/mod.rs index 78d245aef..cedaf4f44 100644 --- a/router/src/location/mod.rs +++ b/router/src/location/mod.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs)] + use any_spawner::Executor; use core::fmt::Debug; use js_sys::Reflect; diff --git a/router/src/matching/mod.rs b/router/src/matching/mod.rs index 7e61d335a..b5247dc31 100644 --- a/router/src/matching/mod.rs +++ b/router/src/matching/mod.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs)] + mod choose_view; mod path_segment; pub(crate) mod resolve_path; @@ -13,12 +15,12 @@ use std::{borrow::Cow, collections::HashSet}; pub use vertical::*; #[derive(Debug)] -pub struct Routes { +pub struct RouteDefs { base: Option>, children: Children, } -impl Clone for Routes +impl Clone for RouteDefs where Children: Clone, { @@ -30,7 +32,7 @@ where } } -impl Routes { +impl RouteDefs { pub fn new(children: Children) -> Self { Self { base: None, @@ -49,7 +51,7 @@ impl Routes { } } -impl Routes +impl RouteDefs where Children: MatchNestedRoutes, { @@ -130,7 +132,7 @@ pub struct GeneratedRouteData { #[cfg(test)] mod tests { - use super::{NestedRoute, ParamSegment, Routes}; + use super::{NestedRoute, ParamSegment, RouteDefs}; use crate::{ matching::MatchParams, MatchInterface, PathSegment, StaticSegment, WildcardSegment, @@ -140,7 +142,7 @@ mod tests { #[test] pub fn matches_single_root_route() { let routes = - Routes::<_>::new(NestedRoute::new(StaticSegment("/"), || ())); + RouteDefs::<_>::new(NestedRoute::new(StaticSegment("/"), || ())); let matched = routes.match_route("/"); assert!(matched.is_some()); // this case seems like it should match, but implementing it interferes with @@ -156,13 +158,14 @@ mod tests { #[test] pub fn matches_nested_route() { - let routes: Routes<_> = - Routes::new(NestedRoute::new(StaticSegment(""), || "Home").child( + let routes: RouteDefs<_> = RouteDefs::new( + NestedRoute::new(StaticSegment(""), || "Home").child( NestedRoute::new( (StaticSegment("author"), StaticSegment("contact")), || "Contact Me", ), - )); + ), + ); // route generation let (base, paths) = routes.generate_routes(); @@ -188,7 +191,7 @@ mod tests { #[test] pub fn does_not_match_route_unless_full_param_matches() { - let routes = Routes::<_>::new(( + let routes = RouteDefs::<_>::new(( NestedRoute::new(StaticSegment("/property-api"), || ()), NestedRoute::new(StaticSegment("/property"), || ()), )); @@ -198,20 +201,21 @@ mod tests { #[test] pub fn does_not_match_incomplete_route() { - let routes: Routes<_> = - Routes::new(NestedRoute::new(StaticSegment(""), || "Home").child( + let routes: RouteDefs<_> = RouteDefs::new( + NestedRoute::new(StaticSegment(""), || "Home").child( NestedRoute::new( (StaticSegment("author"), StaticSegment("contact")), || "Contact Me", ), - )); + ), + ); let matched = routes.match_route("/"); assert!(matched.is_none()); } #[test] pub fn chooses_between_nested_routes() { - let routes: Routes<_> = Routes::new(( + let routes: RouteDefs<_> = RouteDefs::new(( NestedRoute::new(StaticSegment("/"), || ()).child(( NestedRoute::new(StaticSegment(""), || ()), NestedRoute::new(StaticSegment("about"), || ()), @@ -265,7 +269,7 @@ mod tests { #[test] pub fn arbitrary_nested_routes() { - let routes: Routes<_> = Routes::new_with_base( + let routes: RouteDefs<_> = RouteDefs::new_with_base( ( NestedRoute::new(StaticSegment("/"), || ()).child(( NestedRoute::new(StaticSegment("/"), || ()), diff --git a/router/src/nested_router.rs b/router/src/nested_router.rs index 823590a1c..056405595 100644 --- a/router/src/nested_router.rs +++ b/router/src/nested_router.rs @@ -1,7 +1,7 @@ use crate::{ hooks::Matched, location::{LocationProvider, Url}, - matching::Routes, + matching::RouteDefs, params::ParamsMap, view_transition::start_view_transition, ChooseView, MatchInterface, MatchNestedRoutes, MatchParams, PathSegment, @@ -44,7 +44,7 @@ use tachys::{ pub(crate) struct NestedRoutesView { pub location: Option, - pub routes: Routes, + pub routes: RouteDefs, pub outer_owner: Owner, pub current_url: ArcRwSignal, pub base: Option>, @@ -53,7 +53,8 @@ pub(crate) struct NestedRoutesView { pub transition: bool, } -pub struct NestedRouteViewState +/// Retained view state for the nested router. +pub(crate) struct NestedRouteViewState where Fal: Render, { @@ -873,6 +874,8 @@ where } } +/// Displays the child route nested in a parent route, allowing you to control exactly where +/// that child route is displayed. Renders nothing if there is no nested child. #[component] pub fn Outlet() -> impl RenderHtml where diff --git a/router/src/params.rs b/router/src/params.rs index 0951dbdba..e825a2bce 100644 --- a/router/src/params.rs +++ b/router/src/params.rs @@ -4,6 +4,7 @@ use thiserror::Error; type ParamsMapInner = Vec<(Cow<'static, str>, Vec)>; +/// A key-value map of the current named route params and their values. #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] pub struct ParamsMap(ParamsMapInner); @@ -169,10 +170,12 @@ impl Params for () { } } +/// Converts some parameter value from the URL into a typed parameter with the given name. pub trait IntoParam where Self: Sized, { + /// Converts the param. fn into_param(value: Option<&str>, name: &str) -> Result; } diff --git a/router/src/ssr_mode.rs b/router/src/ssr_mode.rs index 8ab78e826..37a6d8e4e 100644 --- a/router/src/ssr_mode.rs +++ b/router/src/ssr_mode.rs @@ -20,17 +20,34 @@ use crate::static_routes::StaticRoute; /// 5. **`Async`**: Load all resources on the server. Wait until all data are loaded, and render HTML in one sweep. /// - *Pros*: Better handling for meta tags (because you know async data even before you render the ``). Faster complete load than **synchronous** because async resources begin loading on server. /// - *Cons*: Slower load time/TTFB: you need to wait for all async resources to load before displaying anything on the client. -/// 6. **`Static`**: +/// 6. **`Static`**: Renders the page when the server starts up, or incrementally, using the +/// configuration provided by a [`StaticRoute`]. /// /// The mode defaults to out-of-order streaming. For a path that includes multiple nested routes, the most /// restrictive mode will be used: i.e., if even a single nested route asks for `Async` rendering, the whole initial /// request will be rendered `Async`. (`Async` is the most restricted requirement, followed by `InOrder`, `PartiallyBlocked`, and `OutOfOrder`.) #[derive(Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum SsrMode { + /// **Out-of-order streaming** (`OutOfOrder`, the default): Serve an HTML shell that includes `fallback` for any `Suspense`. Load data on the **server**, streaming it down to the client as it resolves, and streaming down HTML for `Suspense` nodes. + /// - *Pros*: Combines the best of **synchronous** and `Async`, with a very fast shell and resources that begin loading on the server. + /// - *Cons*: Requires JS for suspended fragments to appear in correct order. Weaker meta tag support when it depends on data that's under suspense (has already streamed down ``) #[default] OutOfOrder, + /// **In-order streaming** (`InOrder`): Walk through the tree, returning HTML synchronously as in synchronous rendering and out-of-order streaming until you hit a `Suspense`. At that point, wait for all its data to load, then render it, then the rest of the tree. + /// - *Pros*: Does not require JS for HTML to appear in correct order. + /// - *Cons*: Loads the shell more slowly than out-of-order streaming or synchronous rendering because it needs to pause at every `Suspense`. Cannot begin hydration until the entire page has loaded, so earlier pieces + /// of the page will not be interactive until the suspended chunks have loaded. PartiallyBlocked, + /// **In-order streaming** (`InOrder`): Walk through the tree, returning HTML synchronously as in synchronous rendering and out-of-order streaming until you hit a `Suspense`. At that point, wait for all its data to load, then render it, then the rest of the tree. + /// - *Pros*: Does not require JS for HTML to appear in correct order. + /// - *Cons*: Loads the shell more slowly than out-of-order streaming or synchronous rendering because it needs to pause at every `Suspense`. Cannot begin hydration until the entire page has loaded, so earlier pieces + /// of the page will not be interactive until the suspended chunks have loaded. InOrder, + /// **`Async`**: Load all resources on the server. Wait until all data are loaded, and render HTML in one sweep. + /// - *Pros*: Better handling for meta tags (because you know async data even before you render the ``). Faster complete load than **synchronous** because async resources begin loading on server. + /// - *Cons*: Slower load time/TTFB: you need to wait for all async resources to load before displaying anything on the client. Async, + /// **`Static`**: Renders the page when the server starts up, or incrementally, using the + /// configuration provided by a [`StaticRoute`]. Static(StaticRoute), } diff --git a/router/src/static_routes.rs b/router/src/static_routes.rs index 8e312bd73..ab6c60204 100644 --- a/router/src/static_routes.rs +++ b/router/src/static_routes.rs @@ -13,10 +13,14 @@ use std::{ type PinnedFuture = Pin + Send>>; type PinnedStream = Pin + Send>>; +/// A reference-counted pointer to a function that can generate a set of params for static site +/// generation. pub type StaticParams = Arc; +/// A function that generates a set of params for generating a static route. pub type StaticParamsFn = dyn Fn() -> PinnedFuture + Send + Sync + 'static; +/// A function that defines when a statically-generated page should be regenerated. #[derive(Clone)] #[allow(clippy::type_complexity)] pub struct RegenerationFn( @@ -43,6 +47,7 @@ impl PartialEq for RegenerationFn { } } +/// Defines how a static route should be generated. #[derive(Clone, Default)] pub struct StaticRoute { pub(crate) prerender_params: Option, @@ -50,10 +55,13 @@ pub struct StaticRoute { } impl StaticRoute { + /// Creates a new static route listing. pub fn new() -> Self { Self::default() } + /// Defines a set of params that should be prerendered on server start-up, depending on some + /// asynchronous function that returns their values. pub fn prerender_params( mut self, params: impl Fn() -> Fut + Send + Sync + 'static, @@ -65,6 +73,7 @@ impl StaticRoute { self } + /// Defines when the route should be regenerated. pub fn regenerate( mut self, invalidate: impl Fn(&ParamsMap) -> St + Send + Sync + 'static, @@ -78,6 +87,7 @@ impl StaticRoute { self } + /// Returns a set of params that should be prerendered. pub async fn to_prerendered_params(&self) -> Option { match &self.prerender_params { None => None, @@ -118,6 +128,7 @@ impl PartialEq for StaticRoute { impl Eq for StaticRoute {} +/// A map of params for static routes. #[derive(Debug, Clone, Default)] pub struct StaticParamsMap(pub Vec<(String, Vec)>); @@ -159,6 +170,7 @@ impl IntoIterator for StaticParamsMap { } } +/// An iterator over a set of (key, value) pairs for statically-routed params. #[derive(Debug)] pub struct StaticParamsIter( )> as IntoIterator>::IntoIter, @@ -254,12 +266,14 @@ impl StaticPath { } } +/// A path to be used in static route generation. #[derive(Debug, Clone, PartialEq)] pub struct ResolvedStaticPath { pub(crate) path: String, } impl ResolvedStaticPath { + /// Defines a path to be used for static route generation. pub fn new(path: impl Into) -> Self { Self { path: path.into() } } @@ -278,6 +292,7 @@ impl Display for ResolvedStaticPath { } impl ResolvedStaticPath { + /// Builds the page that corresponds to this path. pub async fn build( self, render_fn: impl Fn(&ResolvedStaticPath) -> Fut + Send + Clone + 'static, diff --git a/router_macro/src/lib.rs b/router_macro/src/lib.rs index 3863f3b43..a101965ae 100644 --- a/router_macro/src/lib.rs +++ b/router_macro/src/lib.rs @@ -1,3 +1,7 @@ +//! A macro to make path definitions easier with [`leptos_router`]. + +#![deny(missing_docs)] + use proc_macro::{TokenStream, TokenTree}; use proc_macro2::Span; use proc_macro_error2::abort; diff --git a/tachys/src/lib.rs b/tachys/src/lib.rs index d8553a869..15f9fe3b7 100644 --- a/tachys/src/lib.rs +++ b/tachys/src/lib.rs @@ -3,9 +3,11 @@ //! This view tree is generic over rendering backends, and agnostic about reactivity/change //! detection. -#![allow(incomplete_features)] // yolo +// this is specifically used for `unsized_const_params` below +// this allows us to use const generic &'static str for static text nodes and attributes +#![allow(incomplete_features)] #![cfg_attr(feature = "nightly", feature(unsized_const_params))] -//#![deny(missing_docs)] +#![deny(missing_docs)] /// Commonly-used traits. pub mod prelude { diff --git a/tachys/src/renderer/dom.rs b/tachys/src/renderer/dom.rs index be8e46be1..115d46cf0 100644 --- a/tachys/src/renderer/dom.rs +++ b/tachys/src/renderer/dom.rs @@ -1,3 +1,7 @@ +#![allow(missing_docs)] + +//! See [`Renderer`](super::Renderer) and [`Rndr`](super::Rndr) for additional information. + use super::{CastFrom, RemoveEventHandler}; use crate::{ dom::{document, window}, diff --git a/tachys/src/renderer/mod.rs b/tachys/src/renderer/mod.rs index e4f6a1dd1..2d643d7f6 100644 --- a/tachys/src/renderer/mod.rs +++ b/tachys/src/renderer/mod.rs @@ -5,7 +5,22 @@ use wasm_bindgen::JsValue; /// A DOM renderer. pub mod dom; +/// The renderer being used for the application. +/// +/// ### Note +/// This was designed to be included as a generic on view types, to support different rendering +/// backends using the same view tree structure. However, adding the number of generics that was +/// required to make this work caused catastrophic compile times and linker errors on larger +/// applications, so this "generic rendering" approach was removed before 0.7.0 release. +/// +/// It is possible that we will try a different approach to achieve the same functionality in the +/// future, so to the extent possible the rest of the crate tries to stick to using [`Renderer`] +/// methods rather than directly manipulating the DOM inline. pub type Rndr = dom::Dom; + +/// Types used by the renderer. +/// +/// See [`Rndr`] for additional information on this rendering approach. pub mod types { pub use super::dom::{ ClassList, CssStyleDeclaration, Element, Event, Node, Placeholder,