chore: add missing docs for 0.7 (#3203)

This commit is contained in:
Greg Johnston
2024-11-11 19:58:38 -05:00
committed by GitHub
parent 998eefb8c5
commit a5293f0b79
33 changed files with 928 additions and 545 deletions

View File

@@ -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<StatusCode>,
/// 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<HttpRequest>);
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<T> {
Data(T),
Response(actix_web::dev::Response<BoxBody>),
}
/// 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<IV>(
self,
paths: Vec<ActixRouteListing>,
@@ -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<IV>(
self,
paths: Vec<ActixRouteListing>,

View File

@@ -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<StatusCode>,
/// 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<Box<dyn Stream<Item = io::Result<Bytes>> + Send>>;
@@ -1656,6 +1661,9 @@ where
S: Clone + Send + Sync + 'static,
LeptosOptions: FromRef<S>,
{
/// Adds routes to the Axum router that have either
/// 1) been generated by `leptos_router`, or
/// 2) handle a server function.
fn leptos_routes<IV>(
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<IV>(
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<H, T>(
self,
paths: Vec<AxumRouteListing>,
@@ -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<S, IV>(
shell: fn(LeptosOptions) -> IV,

View File

@@ -228,6 +228,7 @@ impl ViewFnOnce {
pub struct TypedChildren<T>(Box<dyn FnOnce() -> View<T> + Send>);
impl<T> TypedChildren<T> {
/// Extracts the inner `children` function.
pub fn into_inner(self) -> impl FnOnce() -> View<T> + Send {
self.0
}
@@ -256,6 +257,7 @@ impl<T> Debug for TypedChildrenMut<T> {
}
impl<T> TypedChildrenMut<T> {
/// Extracts the inner `children` function.
pub fn into_inner(self) -> impl FnMut() -> View<T> + Send {
self.0
}
@@ -284,6 +286,7 @@ impl<T> Debug for TypedChildrenFn<T> {
}
impl<T> TypedChildrenFn<T> {
/// Extracts the inner `children` function.
pub fn into_inner(self) -> Arc<dyn Fn() -> View<T> + Send + Sync> {
self.0
}

View File

@@ -251,12 +251,16 @@ where
) -> Result<Self, serde_qs::Error>;
}
/// 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 `<form>` connected to the event.
#[error("Could not find <form> connected to event.")]
MissingForm(Event),
/// Could not create `FormData` from the form.
#[error("Could not create FormData from <form>: {0:?}")]
FormData(JsValue),
/// Failed to deserialize this Rust type from the form data.
#[error("Deserialization error: {0:?}")]
Deserialization(serde_qs::Error),
}

View File

@@ -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 `<head>` 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 `<head>` 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<String>,

View File

@@ -9,6 +9,7 @@ use tachys::{
},
};
/// A wrapper for any kind of view.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct View<T>
where
@@ -20,6 +21,7 @@ where
}
impl<T> View<T> {
/// Wraps the view.
pub fn new(inner: T) -> Self {
Self {
inner,
@@ -28,10 +30,12 @@ impl<T> View<T> {
}
}
/// 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<T> View<T> {
}
}
/// 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<Self>;
}
@@ -188,9 +194,15 @@ impl<T: AddAnyAttr> AddAnyAttr for View<T> {
}
}
/// Collects some iterator of views into a list, so they can be rendered.
///
/// This is a shorthand for `.collect::<Vec<_>>()`, 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<Self::View>;
}

View File

@@ -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;

View File

@@ -87,7 +87,12 @@ use throw_error::ErrorHookFuture;
/// ```
#[component]
pub fn Suspense<Chil>(
#[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<Chil>,
) -> impl IntoView
where

View File

@@ -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;

View File

@@ -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<str>,
@@ -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<S>
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::<ServerActionError>().and_then(|error| {
@@ -116,6 +125,7 @@ where
}
}
/// An [`Action`] that can be used to call a server function.
pub struct ServerAction<S>
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::<ServerActionError>().and_then(|error| {
(error.path() == S::PATH)

View File

@@ -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 its useful to write functions
////! that will only run on the server, and call them from the client.
////!
////! If youre creating anything beyond a toy app, youll 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 dont 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 thats stored on the server and definitely
////! shouldnt be shipped down to a users 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<Vec<Posts>, 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 `<form method="POST">`.
////!
////! Heres 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 clients perspective it involves an asynchronous
////! function call.
////! - **Server functions must return `Result<T, ServerFnError>`.** Even if the work being done
////! inside the function body cant 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 `<form>` support. For better or for worse,
////! HTML forms dont support `PUT` or `DELETE`, and they dont 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
////! didnt 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<Self>;
/// The type of an error encountered during decoding.
type DecodingError;
/// Decodes the string.
fn from_encoded_str(
data: &str,
) -> Result<Self::DecodedType<'_>, Self::DecodingError>;

View File

@@ -17,6 +17,7 @@ use std::{
panic::Location,
};
/// A reference-counted resource that only loads its data locally on the client.
pub struct ArcLocalResource<T> {
data: ArcAsyncDerived<SendWrapper<T>>,
#[cfg(debug_assertions)]
@@ -34,6 +35,10 @@ impl<T> Clone for ArcLocalResource<T> {
}
impl<T> ArcLocalResource<T> {
/// 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<Fut>(fetcher: impl Fn() -> Fut + 'static) -> Self
where
@@ -192,6 +197,7 @@ impl<T> Subscriber for ArcLocalResource<T> {
}
}
/// A resource that only loads its data locally on the client.
pub struct LocalResource<T> {
data: AsyncDerived<SendWrapper<T>>,
#[cfg(debug_assertions)]
@@ -207,6 +213,10 @@ impl<T> Clone for LocalResource<T> {
impl<T> Copy for LocalResource<T> {}
impl<T> LocalResource<T> {
/// 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<Fut>(fetcher: impl Fn() -> Fut + 'static) -> Self
where

View File

@@ -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<S>
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<S>
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| {

View File

@@ -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 youre trying to load data by running an `async` function reactively, you probably
/// want to use a [Resource](leptos_reactive::Resource) instead. If youre trying to occasionally
/// run an `async` function in response to something like a user adding a task to a todo list,
/// youre 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<I, O>(StoredValue<MultiActionState<I, O>>)
where
I: 'static,
O: 'static;
impl<I, O> MultiAction<I, O>
where
I: 'static,
O: 'static,
{
}
impl<I, O> Clone for MultiAction<I, O>
where
I: 'static,
O: 'static,
{
fn clone(&self) -> Self {
*self
}
}
impl<I, O> Copy for MultiAction<I, O>
where
I: 'static,
O: 'static,
{
}
impl<I, O> MultiAction<I, O>
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<Vec<Submission<I, O>>> {
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<String> {
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<usize> {
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<T: ServerFn>(self) -> Self {
self.0.update_value(|a| {
a.url = Some(T::url().to_string());
});
self
}
}
struct MultiActionState<I, O>
where
I: 'static,
O: 'static,
{
/// How many times an action has successfully resolved.
pub version: RwSignal<usize>,
submissions: RwSignal<Vec<Submission<I, O>>>,
url: Option<String>,
#[allow(clippy::complexity)]
action_fn: Rc<dyn Fn(&I) -> Pin<Box<dyn Future<Output = O>>>>,
}
/// An action that has been submitted by dispatching it to a [MultiAction](crate::MultiAction).
pub struct Submission<I, O>
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<Option<I>>,
/// The most recent return value of the `async` function.
pub value: RwSignal<Option<O>>,
pub(crate) pending: RwSignal<bool>,
/// Controls this submission has been canceled.
pub canceled: RwSignal<bool>,
}
impl<I, O> Clone for Submission<I, O> {
fn clone(&self) -> Self {
*self
}
}
impl<I, O> Copy for Submission<I, O> {}
impl<I, O> Submission<I, O>
where
I: 'static,
O: 'static,
{
/// Whether this submission is currently waiting to resolve.
pub fn pending(&self) -> ReadSignal<bool> {
self.pending.read_only()
}
/// Cancels the submission, preventing it from resolving.
pub fn cancel(&self) {
self.canceled.set(true);
}
}
impl<I, O> MultiActionState<I, O>
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<Vec<Submission<I, O>>> {
self.submissions.read_only()
}
}
/// Creates an [MultiAction] to synchronize an imperative `async` call to the synchronous reactive system.
///
/// If youre trying to load data by running an `async` function reactively, you probably
/// want to use a [create_resource](leptos_reactive::create_resource) instead. If youre 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<I, O, F, Fu>(action_fn: F) -> MultiAction<I, O>
where
I: 'static,
O: 'static,
F: Fn(&I) -> Fu + 'static,
Fu: Future<Output = O> + '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<Box<dyn Future<Output = O>>>
});
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::<MyServerFn>();
/// # runtime.dispose();
/// ```
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
pub fn create_server_multi_action<S>(
) -> MultiAction<S, Result<S::Output, ServerFnError<S::Error>>>
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::<S>()
}

View File

@@ -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<T, Ser = JsonSerdeCodec> {
trigger: ArcTrigger,
@@ -80,6 +89,12 @@ where
<Ser as Encoder<T>>::Encoded: IntoEncodedString,
<Ser as Decoder<T>>::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<Output = T> + Send + 'static,
@@ -287,11 +302,17 @@ where
<JsonSerdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
<JsonSerdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
/// Creates a resource using [`JsonSerdeCodec`] for encoding/decoding the value.
#[track_caller]
pub fn new(fut: impl Future<Output = T> + 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<Output = T> + Send + 'static) -> Self {
ArcOnceResource::new_with_options(fut, true)
@@ -307,6 +328,7 @@ T: Send + Sync + 'static,
<FromToStringCodec as Encoder<T>>::Encoded: IntoEncodedString,
<FromToStringCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
/// Creates a resource using [`FromToStringCodec`] for encoding/decoding the value.
pub fn new_str(
fut: impl Future<Output = T> + 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<Output = T> + Send + 'static
) -> Self
@@ -332,6 +359,7 @@ T: Send + Sync + 'static,
<JsonSerdeWasmCodec as Encoder<T>>::Encoded: IntoEncodedString,
<JsonSerdeWasmCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
/// Creates a resource using [`JsonSerdeWasmCodec`] for encoding/decoding the value.
#[track_caller]
pub fn new_serde_wb(
fut: impl Future<Output = T> + Send + 'static
@@ -340,6 +368,11 @@ fut: impl Future<Output = T> + 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<Output = T> + Send + 'static
@@ -360,6 +393,7 @@ where
<MiniserdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
<MiniserdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
/// Creates a resource using [`MiniserdeCodec`] for encoding/decoding the value.
#[track_caller]
pub fn new_miniserde(
fut: impl Future<Output = T> + 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<Output = T> + Send + 'static,
@@ -385,6 +424,7 @@ T: Send + Sync + 'static,
<SerdeLite<JsonSerdeCodec> as Encoder<T>>::Encoded: IntoEncodedString,
<SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded: FromEncodedStr,
{
/// Creates a resource using [`SerdeLite`] for encoding/decoding the value.
#[track_caller]
pub fn new_serde_lite(
fut: impl Future<Output = T> + Send + 'static
@@ -393,6 +433,11 @@ fut: impl Future<Output = T> + 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<Output = T> + Send + 'static
@@ -414,11 +459,17 @@ where
<RkyvCodec as Encoder<T>>::Encoded: IntoEncodedString,
<RkyvCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
/// Creates a resource using [`RkyvCodec`] for encoding/decoding the value.
#[track_caller]
pub fn new_rkyv(fut: impl Future<Output = T> + 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<Output = T> + 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<T, Ser = JsonSerdeCodec> {
inner: ArenaItem<ArcOnceResource<T, Ser>>,
@@ -452,6 +512,12 @@ where
<Ser as Encoder<T>>::Encoded: IntoEncodedString,
<Ser as Decoder<T>>::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<Output = T> + Send + 'static,
@@ -567,11 +633,17 @@ where
<JsonSerdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
<JsonSerdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
/// Creates a resource using [`JsonSerdeCodec`] for encoding/decoding the value.
#[track_caller]
pub fn new(fut: impl Future<Output = T> + 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<Output = T> + Send + 'static) -> Self {
OnceResource::new_with_options(fut, true)
@@ -587,6 +659,7 @@ T: Send + Sync + 'static,
<FromToStringCodec as Encoder<T>>::Encoded: IntoEncodedString,
<FromToStringCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
/// Creates a resource using [`FromToStringCodec`] for encoding/decoding the value.
pub fn new_str(
fut: impl Future<Output = T> + 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<Output = T> + Send + 'static
) -> Self
@@ -612,6 +690,7 @@ T: Send + Sync + 'static,
<JsonSerdeWasmCodec as Encoder<T>>::Encoded: IntoEncodedString,
<JsonSerdeWasmCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
/// Creates a resource using [`JsonSerdeWasmCodec`] for encoding/decoding the value.
#[track_caller]
pub fn new_serde_wb(
fut: impl Future<Output = T> + Send + 'static
@@ -620,6 +699,11 @@ fut: impl Future<Output = T> + 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<Output = T> + Send + 'static
@@ -640,6 +724,7 @@ where
<MiniserdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
<MiniserdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
/// Creates a resource using [`MiniserdeCodec`] for encoding/decoding the value.
#[track_caller]
pub fn new_miniserde(
fut: impl Future<Output = T> + 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<Output = T> + Send + 'static,
@@ -665,6 +755,7 @@ T: Send + Sync + 'static,
<SerdeLite<JsonSerdeCodec> as Encoder<T>>::Encoded: IntoEncodedString,
<SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded: FromEncodedStr,
{
/// Creates a resource using [`SerdeLite`] for encoding/decoding the value.
#[track_caller]
pub fn new_serde_lite(
fut: impl Future<Output = T> + Send + 'static
@@ -673,6 +764,11 @@ fut: impl Future<Output = T> + 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<Output = T> + Send + 'static
@@ -694,11 +790,17 @@ where
<RkyvCodec as Encoder<T>>::Encoded: IntoEncodedString,
<RkyvCodec as Decoder<T>>::Encoded: FromEncodedStr,
{
/// Creates a resource using [`RkyvCodec`] for encoding/decoding the value.
#[track_caller]
pub fn new_rkyv(fut: impl Future<Output = T> + 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<Output = T> + Send + 'static,

View File

@@ -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<T, Ser = JsonSerdeCodec> {
ser: PhantomData<Ser>,
refetch: ArcRwSignal<usize>,
@@ -194,6 +206,21 @@ where
<Ser as Encoder<T>>::Encoded: IntoEncodedString,
<Ser as Decoder<T>>::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<S, Fut>(
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<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U>
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<T, E>`
/// has resolved and loaded an `Ok(_)`, rather than requiring nested `.map()`
/// calls over the `Option<Result<_, _>>` returned by the resource.
///
/// This is useful when used with features like server functions, in conjunction
/// with `<ErrorBoundary/>` and `<Suspense/>`, when these other components are
/// left to handle the `None` and `Err(_)` states.
#[track_caller]
pub fn and_then<U>(&self, f: impl FnOnce(&T) -> U) -> Option<Result<U, E>> {
self.map(|data| data.as_ref().map(f).map_err(|e| e.clone()))
@@ -367,6 +403,15 @@ where
<JsonSerdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
<JsonSerdeCodec as Decoder<T>>::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<S, Fut>(
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<S, Fut>(
source: impl Fn() -> S + Send + Sync + 'static,
@@ -402,6 +460,15 @@ where
<FromToStringCodec as Encoder<T>>::Encoded: IntoEncodedString,
<FromToStringCodec as Decoder<T>>::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<S, Fut>(
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<S, Fut>(
source: impl Fn() -> S + Send + Sync + 'static,
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
@@ -436,6 +516,15 @@ where
<JsonSerdeWasmCodec as Encoder<T>>::Encoded: IntoEncodedString,
<JsonSerdeWasmCodec as Decoder<T>>::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<S, Fut>(
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<S, Fut>(
source: impl Fn() -> S + Send + Sync + 'static,
@@ -473,6 +575,15 @@ where
<MiniserdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
<MiniserdeCodec as Decoder<T>>::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<S, Fut>(
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<S, Fut>(
source: impl Fn() -> S + Send + Sync + 'static,
@@ -509,6 +633,15 @@ where
<SerdeLite<JsonSerdeCodec> as Encoder<T>>::Encoded: IntoEncodedString,
<SerdeLite<JsonSerdeCodec> as Decoder<T>>::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<S, Fut>(
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<S, Fut>(
source: impl Fn() -> S + Send + Sync + 'static,
@@ -547,6 +693,15 @@ where
<RkyvCodec as Encoder<T>>::Encoded: IntoEncodedString,
<RkyvCodec as Decoder<T>>::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<S, Fut>(
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<S, Fut>(
source: impl Fn() -> S + Send + Sync + 'static,
@@ -590,11 +758,22 @@ impl<T, Ser> ArcResource<T, Ser>
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<T> {
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<T, Ser = JsonSerdeCodec>
where
T: Send + Sync + 'static,
@@ -707,6 +886,15 @@ where
<FromToStringCodec as Decoder<T>>::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<S, Fut>(
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<S, Fut>(
source: impl Fn() -> S + Send + Sync + 'static,
@@ -745,6 +946,15 @@ where
<JsonSerdeCodec as Decoder<T>>::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<S, Fut>(
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<S, Fut>(
source: impl Fn() -> S + Send + Sync + 'static,
@@ -782,6 +1005,15 @@ where
<JsonSerdeWasmCodec as Decoder<T>>::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<S, Fut>(
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<S, Fut>(
source: impl Fn() -> S + Send + Sync + 'static,
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
@@ -819,6 +1064,15 @@ where
<MiniserdeCodec as Decoder<T>>::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<S, Fut>(
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<S, Fut>(
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<Output = T> + Send + 'static,
{
Resource::new_with_options(source, fetcher, true)
}
}
#[cfg(feature = "serde-lite")]
@@ -843,6 +1122,15 @@ where
<SerdeLite<JsonSerdeCodec> as Decoder<T>>::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<S, Fut>(
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<S, Fut>(
source: impl Fn() -> S + Send + Sync + 'static,
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
@@ -880,6 +1181,15 @@ where
<RkyvCodec as Decoder<T>>::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<S, Fut>(
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<S, Fut>(
source: impl Fn() -> S + Send + Sync + 'static,
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
@@ -915,6 +1238,21 @@ where
<Ser as Decoder<T>>::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<S, Fut>(
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<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U> {
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<T, E>`
/// has resolved and loaded an `Ok(_)`, rather than requiring nested `.map()`
/// calls over the `Option<Result<_, _>>` returned by the resource.
///
/// This is useful when used with features like server functions, in conjunction
/// with `<ErrorBoundary/>` and `<Suspense/>`, when these other components are
/// left to handle the `None` and `Err(_)` states.
#[track_caller]
pub fn and_then<U>(&self, f: impl FnOnce(&T) -> U) -> Option<Result<U, E>> {
self.map(|data| data.as_ref().map(f).map_err(|e| e.clone()))
@@ -984,6 +1331,8 @@ impl<T, Ser> Resource<T, Ser>
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<T> {
self.data.by_ref()
}

View File

@@ -31,6 +31,7 @@ pub struct SharedValue<T, Ser = JsonSerdeCodec> {
}
impl<T, Ser> SharedValue<T, Ser> {
/// Returns the inner value.
pub fn into_inner(self) -> T {
self.value
}
@@ -46,6 +47,12 @@ where
<<JsonSerdeCodec as codee::Decoder<T>>::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
<<FromToStringCodec as codee::Decoder<T>>::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
<<SerdeLite<JsonSerdeCodec> as codee::Decoder<T>>::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
<<JsonSerdeWasmCodec as codee::Decoder<T>>::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
<<MiniserdeCodec as codee::Decoder<T>>::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
<<RkyvCodec as codee::Decoder<T>>::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
<<Ser as codee::Decoder<T>>::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")]

View File

@@ -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 {

View File

@@ -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>(Children);
impl<Children> RouteChildren<Children> {
/// 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<Children, FallbackFn, Fallback>(
#[prop(optional, into)] base: Option<Cow<'static, str>>,
fallback: FallbackFn,
children: RouteChildren<Children>,
) -> FlatRouter<Dom, BrowserUrl, Children, FallbackFn>
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<Defs, FallbackFn, Fallback>(
/// 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<Defs>,
) -> 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<Defs, FallbackFn, Fallback>(
/// 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<Defs>,
) -> 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<Segments, View>(
/// 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<Segments, (), (), View>
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<Segments, View, Children>(
/// 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<Children>,
#[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<Segments, Children, (), View>
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
/// [`<Route/>`], 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<Segments, ViewFn, View, C, PathFn, P>(
/// 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<bool>`, 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<Segments, (), (), impl Fn() -> AnyView + Send + Clone>
where
ViewFn: Fn() -> View + Send + Clone + 'static,
@@ -403,12 +427,24 @@ where
#[component(transparent)]
pub fn ProtectedParentRoute<Segments, ViewFn, View, C, PathFn, P, Children>(
/// 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<bool>`, 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<Children>,
#[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<Segments, Children, (), impl Fn() -> AnyView + Send + Clone>
where
ViewFn: Fn() -> View + Send + Clone + 'static,

View File

@@ -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<Loc, Defs, FalFn> {
pub current_url: ArcRwSignal<Url>,
pub location: Option<Loc>,
pub routes: Routes<Defs>,
pub routes: RouteDefs<Defs>,
pub fallback: FalFn,
pub outer_owner: Owner,
pub set_is_routing: Option<SignalSetter<bool>>,
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<RouteMatchId>,

View File

@@ -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<Vec<ResolvedStaticPath>> {
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<Fut, WriterFut>(
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<RouteListing>);
@@ -158,24 +161,29 @@ impl From<Vec<RouteListing>> 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<RouteListing> {
self.0
}
/// Returns and iterator over the list of routes.
pub fn iter(&self) -> impl Iterator<Item = &RouteListing> {
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<ResolvedStaticPath> {
futures::future::join_all(
self.into_inner()
@@ -189,6 +197,7 @@ impl RouteList {
.collect::<Vec<_>>()
}
/// Generates static files for the inner list of route listings.
pub async fn generate_static_files<Fut, WriterFut>(
self,
render_fn: impl Fn(&ResolvedStaticPath) -> Fut + Send + Clone + 'static,
@@ -220,6 +229,7 @@ impl RouteList {
static GENERATED: RefCell<Option<RouteList>> = const { RefCell::new(None) };
}
/// Creates a list of routes, based on route definitions in the given app.
pub fn generate<T>(app: impl FnOnce() -> T) -> Option<Self>
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);

View File

@@ -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::<T>(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<T>(
key: impl Into<Oco<'static, str>>,
@@ -205,6 +210,7 @@ fn use_url_raw() -> ArcRwSignal<Url> {
})
}
/// Gives reactive access to the current URL.
#[track_caller]
pub fn use_url() -> ReadSignal<Url> {
use_url_raw().read_only().into()

View File

@@ -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 apps state. (Its 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 `<a>` and `<form>` elements, and (when possible)
//! upgrading them to handle those navigations with client-side routing. If youre 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! {
//!
//! <div id="root">
//! // we wrap the whole app in a <Router/> to allow client-side navigation
//! // from our nav links below
//! <Router>
//! <main>
//! // <Routes/> both defines our routes and shows them on the page
//! <Routes fallback=|| "Not found.">
//! // our root route: the contact list is always shown
//! <ParentRoute
//! path=path!("")
//! view=ContactList
//! >
//! // users like /gbj or /bob
//! <Route
//! path=path!(":id")
//! view=Contact
//! />
//! // a fallback if the /:id segment is missing from the URL
//! <Route
//! path=path!("")
//! view=move || view! { <p class="contact">"Select a contact."</p> }
//! />
//! </ParentRoute>
//! </Routes>
//! </main>
//! </Router>
//! </div>
//! }
//! }
//!
//! 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 <ContactList/> route
//! async fn contact_list_data() -> Vec<ContactSummary> {
//! 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! {
//!
//! <div>
//! // show the contacts
//! <ul>
//! {move || contacts.get().map(|contacts| view! { <li>"todo contact info"</li> } )}
//! </ul>
//!
//! // insert the nested child route here
//! <Outlet/>
//! </div>
//! }
//! }
//!
//! #[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`]: <https://github.com/leptos-rs/leptos>
//! [`router`]: <https://github.com/leptos-rs/leptos/blob/main/examples/router/src/lib.rs>
#![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::*;

View File

@@ -1,3 +1,5 @@
#![allow(missing_docs)]
use any_spawner::Executor;
use core::fmt::Debug;
use js_sys::Reflect;

View File

@@ -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<Children> {
pub struct RouteDefs<Children> {
base: Option<Cow<'static, str>>,
children: Children,
}
impl<Children> Clone for Routes<Children>
impl<Children> Clone for RouteDefs<Children>
where
Children: Clone,
{
@@ -30,7 +32,7 @@ where
}
}
impl<Children> Routes<Children> {
impl<Children> RouteDefs<Children> {
pub fn new(children: Children) -> Self {
Self {
base: None,
@@ -49,7 +51,7 @@ impl<Children> Routes<Children> {
}
}
impl<Children> Routes<Children>
impl<Children> RouteDefs<Children>
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("/"), || ()),

View File

@@ -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<Loc, Defs, FalFn> {
pub location: Option<Loc>,
pub routes: Routes<Defs>,
pub routes: RouteDefs<Defs>,
pub outer_owner: Owner,
pub current_url: ArcRwSignal<Url>,
pub base: Option<Oco<'static, str>>,
@@ -53,7 +53,8 @@ pub(crate) struct NestedRoutesView<Loc, Defs, FalFn> {
pub transition: bool,
}
pub struct NestedRouteViewState<Fal>
/// Retained view state for the nested router.
pub(crate) struct NestedRouteViewState<Fal>
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

View File

@@ -4,6 +4,7 @@ use thiserror::Error;
type ParamsMapInner = Vec<(Cow<'static, str>, Vec<String>)>;
/// 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<Self, ParamsError>;
}

View File

@@ -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 `<head>`). 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 `<head>`)
#[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 `<head>`). 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),
}

View File

@@ -13,10 +13,14 @@ use std::{
type PinnedFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
type PinnedStream<T> = Pin<Box<dyn Stream<Item = T> + Send>>;
/// A reference-counted pointer to a function that can generate a set of params for static site
/// generation.
pub type StaticParams = Arc<StaticParamsFn>;
/// A function that generates a set of params for generating a static route.
pub type StaticParamsFn =
dyn Fn() -> PinnedFuture<StaticParamsMap> + 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<StaticParams>,
@@ -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<Fut>(
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<St>(
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<StaticParamsMap> {
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<String>)>);
@@ -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(
<Vec<(String, Vec<String>)> 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<String>) -> 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<Fut, WriterFut>(
self,
render_fn: impl Fn(&ResolvedStaticPath) -> Fut + Send + Clone + 'static,

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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},

View File

@@ -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,