mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 07:34:35 -05:00
chore: add missing docs for 0.7 (#3203)
This commit is contained in:
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
//#![deny(missing_docs)]
|
||||
//! Utilities for communicating between the server and the client with [`leptos`].
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod action;
|
||||
@@ -13,135 +15,25 @@ pub use once_resource::*;
|
||||
mod resource;
|
||||
pub use resource::*;
|
||||
mod shared;
|
||||
////! # Leptos Server Functions
|
||||
////!
|
||||
////! This package is based on a simple idea: sometimes it’s useful to write functions
|
||||
////! that will only run on the server, and call them from the client.
|
||||
////!
|
||||
////! If you’re creating anything beyond a toy app, you’ll need to do this all the time:
|
||||
////! reading from or writing to a database that only runs on the server, running expensive
|
||||
////! computations using libraries you don’t want to ship down to the client, accessing
|
||||
////! APIs that need to be called from the server rather than the client for CORS reasons
|
||||
////! or because you need a secret API key that’s stored on the server and definitely
|
||||
////! shouldn’t be shipped down to a user’s browser.
|
||||
////!
|
||||
////! Traditionally, this is done by separating your server and client code, and by setting
|
||||
////! up something like a REST API or GraphQL API to allow your client to fetch and mutate
|
||||
////! data on the server. This is fine, but it requires you to write and maintain your code
|
||||
////! in multiple separate places (client-side code for fetching, server-side functions to run),
|
||||
////! as well as creating a third thing to manage, which is the API contract between the two.
|
||||
////!
|
||||
////! This package provides two simple primitives that allow you instead to write co-located,
|
||||
////! isomorphic server functions. (*Co-located* means you can write them in your app code so
|
||||
////! that they are “located alongside” the client code that calls them, rather than separating
|
||||
////! the client and server sides. *Isomorphic* means you can call them from the client as if
|
||||
////! you were simply calling a function; the function call has the “same shape” on the client
|
||||
////! as it does on the server.)
|
||||
////!
|
||||
////! ### `#[server]`
|
||||
////!
|
||||
////! The [`#[server]`](https://docs.rs/leptos/latest/leptos/attr.server.html) macro allows you to annotate a function to
|
||||
////! indicate that it should only run on the server (i.e., when you have an `ssr` feature in your
|
||||
////! crate that is enabled).
|
||||
////!
|
||||
////! ```rust,ignore
|
||||
////! use leptos::prelude::*;
|
||||
////! #[server(ReadFromDB)]
|
||||
////! async fn read_posts(how_many: usize, query: String) -> Result<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">`.
|
||||
////!
|
||||
////! Here’s what you need to remember:
|
||||
////! - **Server functions must be `async`.** Even if the work being done inside the function body
|
||||
////! can run synchronously on the server, from the client’s perspective it involves an asynchronous
|
||||
////! function call.
|
||||
////! - **Server functions must return `Result<T, ServerFnError>`.** Even if the work being done
|
||||
////! inside the function body can’t fail, the processes of serialization/deserialization and the
|
||||
////! network call are fallible.
|
||||
////! - **Return types must be [Serializable](leptos_reactive::Serializable).**
|
||||
////! This should be fairly obvious: we have to serialize arguments to send them to the server, and we
|
||||
////! need to deserialize the result to return it to the client.
|
||||
////! - **Arguments must be implement [serde::Serialize].** They are serialized as an `application/x-www-form-urlencoded`
|
||||
////! form data using [`serde_qs`](https://docs.rs/serde_qs/latest/serde_qs/) or as `application/cbor`
|
||||
////! using [`cbor`](https://docs.rs/cbor/latest/cbor/). **Note**: You should explicitly include `serde` with the
|
||||
////! `derive` feature enabled in your `Cargo.toml`. You can do this by running `cargo add serde --features=derive`.
|
||||
////! - Context comes from the server. [`use_context`](leptos_reactive::use_context) can be used to access specific
|
||||
////! server-related data, as documented in the server integrations. This allows accessing things like HTTP request
|
||||
////! headers as needed. However, server functions *not* have access to reactive state that exists in the client.
|
||||
////!
|
||||
////! ## Server Function Encodings
|
||||
////!
|
||||
////! By default, the server function call is a `POST` request that serializes the arguments as URL-encoded form data in the body
|
||||
////! of the request. But there are a few other methods supported. Optionally, we can provide another argument to the `#[server]`
|
||||
////! macro to specify an alternate encoding:
|
||||
////!
|
||||
////! ```rust,ignore
|
||||
////! #[server(AddTodo, "/api", "Url")]
|
||||
////! #[server(AddTodo, "/api", "GetJson")]
|
||||
////! #[server(AddTodo, "/api", "Cbor")]
|
||||
////! #[server(AddTodo, "/api", "GetCbor")]
|
||||
////! ```
|
||||
////!
|
||||
////! The four options use different combinations of HTTP verbs and encoding methods:
|
||||
////!
|
||||
////! | Name | Method | Request | Response |
|
||||
////! | ----------------- | ------ | ----------- | -------- |
|
||||
////! | **Url** (default) | POST | URL encoded | JSON |
|
||||
////! | **GetJson** | GET | URL encoded | JSON |
|
||||
////! | **Cbor** | POST | CBOR | CBOR |
|
||||
////! | **GetCbor** | GET | URL encoded | CBOR |
|
||||
////!
|
||||
////! In other words, you have two choices:
|
||||
////!
|
||||
////! - `GET` or `POST`? This has implications for things like browser or CDN caching; while `POST` requests should not be cached,
|
||||
////! `GET` requests can be.
|
||||
////! - Plain text (arguments sent with URL/form encoding, results sent as JSON) or a binary format (CBOR, encoded as a base64
|
||||
////! string)?
|
||||
////!
|
||||
////! ## Why not `PUT` or `DELETE`? Why URL/form encoding, and not JSON?**
|
||||
////!
|
||||
////! These are reasonable questions. Much of the web is built on REST API patterns that encourage the use of semantic HTTP
|
||||
////! methods like `DELETE` to delete an item from a database, and many devs are accustomed to sending data to APIs in the
|
||||
////! JSON format.
|
||||
////!
|
||||
////! The reason we use `POST` or `GET` with URL-encoded data by default is the `<form>` support. For better or for worse,
|
||||
////! HTML forms don’t support `PUT` or `DELETE`, and they don’t support sending JSON. This means that if you use anything
|
||||
////! but a `GET` or `POST` request with URL-encoded data, it can only work once WASM has loaded.
|
||||
////!
|
||||
////! The CBOR encoding is supported for historical reasons; an earlier version of server functions used a URL encoding that
|
||||
////! didn’t support nested objects like structs or vectors as server function arguments, which CBOR did. But note that the
|
||||
////! CBOR forms encounter the same issue as `PUT`, `DELETE`, or JSON: they do not degrade gracefully if the WASM version of
|
||||
////! your app is not available.
|
||||
|
||||
//pub use server_fn::{error::ServerFnErrorErr, ServerFnError};
|
||||
|
||||
//mod action;
|
||||
//mod multi_action;
|
||||
//pub use action::*;
|
||||
//pub use multi_action::*;
|
||||
//extern crate tracing;
|
||||
use base64::{engine::general_purpose::STANDARD_NO_PAD, DecodeError, Engine};
|
||||
pub use shared::*;
|
||||
|
||||
/// Encodes data into a string.
|
||||
pub trait IntoEncodedString {
|
||||
/// Encodes the data.
|
||||
fn into_encoded_string(self) -> String;
|
||||
}
|
||||
|
||||
/// Decodes data from a string.
|
||||
pub trait FromEncodedStr {
|
||||
/// The decoded data.
|
||||
type DecodedType<'a>: Borrow<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>;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -1,344 +0,0 @@
|
||||
use leptos_reactive::{
|
||||
is_suppressing_resource_load, signal_prelude::*, spawn_local, store_value,
|
||||
untrack, StoredValue,
|
||||
};
|
||||
use server_fn::{ServerFn, ServerFnError};
|
||||
use std::{future::Future, pin::Pin, rc::Rc};
|
||||
|
||||
/// An action that synchronizes multiple imperative `async` calls to the reactive system,
|
||||
/// tracking the progress of each one.
|
||||
///
|
||||
/// Where an [Action](crate::Action) fires a single call, a `MultiAction` allows you to
|
||||
/// keep track of multiple in-flight actions.
|
||||
///
|
||||
/// If you’re trying to load data by running an `async` function reactively, you probably
|
||||
/// want to use a [Resource](leptos_reactive::Resource) instead. If you’re trying to occasionally
|
||||
/// run an `async` function in response to something like a user adding a task to a todo list,
|
||||
/// you’re in the right place.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos::*;
|
||||
/// # let runtime = create_runtime();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
/// // return a task id
|
||||
/// 42
|
||||
/// }
|
||||
/// let add_todo = create_multi_action(|task: &String| {
|
||||
/// // `task` is given as `&String` because its value is available in `input`
|
||||
/// send_new_todo_to_api(task.clone())
|
||||
/// });
|
||||
///
|
||||
/// # if false {
|
||||
/// add_todo.dispatch("Buy milk".to_string());
|
||||
/// add_todo.dispatch("???".to_string());
|
||||
/// add_todo.dispatch("Profit!!!".to_string());
|
||||
/// # }
|
||||
///
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
///
|
||||
/// The input to the `async` function should always be a single value,
|
||||
/// but it can be of any type. The argument is always passed by reference to the
|
||||
/// function, because it is stored in [Submission::input] as well.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos::*;
|
||||
/// # let runtime = create_runtime();
|
||||
/// // if there's a single argument, just use that
|
||||
/// let action1 = create_multi_action(|input: &String| {
|
||||
/// let input = input.clone();
|
||||
/// async move { todo!() }
|
||||
/// });
|
||||
///
|
||||
/// // if there are no arguments, use the unit type `()`
|
||||
/// let action2 = create_multi_action(|input: &()| async { todo!() });
|
||||
///
|
||||
/// // if there are multiple arguments, use a tuple
|
||||
/// let action3 =
|
||||
/// create_multi_action(|input: &(usize, String)| async { todo!() });
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
pub struct MultiAction<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 you’re trying to load data by running an `async` function reactively, you probably
|
||||
/// want to use a [create_resource](leptos_reactive::create_resource) instead. If you’re trying
|
||||
/// to occasionally run an `async` function in response to something like a user clicking a button,
|
||||
/// you're in the right place.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos::*;
|
||||
/// # let runtime = create_runtime();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
/// // return a task id
|
||||
/// 42
|
||||
/// }
|
||||
/// let add_todo = create_multi_action(|task: &String| {
|
||||
/// // `task` is given as `&String` because its value is available in `input`
|
||||
/// send_new_todo_to_api(task.clone())
|
||||
/// });
|
||||
/// # if false {
|
||||
///
|
||||
/// add_todo.dispatch("Buy milk".to_string());
|
||||
/// add_todo.dispatch("???".to_string());
|
||||
/// add_todo.dispatch("Profit!!!".to_string());
|
||||
///
|
||||
/// assert_eq!(add_todo.submissions().get().len(), 3);
|
||||
/// # }
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
///
|
||||
/// The input to the `async` function should always be a single value,
|
||||
/// but it can be of any type. The argument is always passed by reference to the
|
||||
/// function, because it is stored in [Submission::input] as well.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos::*;
|
||||
/// # let runtime = create_runtime();
|
||||
/// // if there's a single argument, just use that
|
||||
/// let action1 = create_multi_action(|input: &String| {
|
||||
/// let input = input.clone();
|
||||
/// async move { todo!() }
|
||||
/// });
|
||||
///
|
||||
/// // if there are no arguments, use the unit type `()`
|
||||
/// let action2 = create_multi_action(|input: &()| async { todo!() });
|
||||
///
|
||||
/// // if there are multiple arguments, use a tuple
|
||||
/// let action3 =
|
||||
/// create_multi_action(|input: &(usize, String)| async { todo!() });
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||
pub fn create_multi_action<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>()
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,20 +1,148 @@
|
||||
//! # Leptos Router
|
||||
//!
|
||||
//! Leptos Router is a router and state management tool for web applications
|
||||
//! written in Rust using the Leptos web framework.
|
||||
//! It is ”isomorphic”, i.e., it can be used for client-side applications/single-page
|
||||
//! apps (SPAs), server-side rendering/multi-page apps (MPAs), or to synchronize
|
||||
//! state between the two.
|
||||
//!
|
||||
//! ## Philosophy
|
||||
//!
|
||||
//! Leptos Router is built on a few simple principles:
|
||||
//! 1. **URL drives state.** For web applications, the URL should be the ultimate
|
||||
//! source of truth for most of your app’s state. (It’s called a **Universal
|
||||
//! Resource Locator** for a reason!)
|
||||
//!
|
||||
//! 2. **Nested routing.** A URL can match multiple routes that exist in a nested tree
|
||||
//! and are rendered by different components. This means you can navigate between siblings
|
||||
//! in this tree without re-rendering or triggering any change in the parent routes.
|
||||
//!
|
||||
//! 3. **Progressive enhancement.** The [`A`] and [`Form`] components resolve any relative
|
||||
//! nested routes, render actual `<a>` and `<form>` elements, and (when possible)
|
||||
//! upgrading them to handle those navigations with client-side routing. If you’re using
|
||||
//! them with server-side rendering (with or without hydration), they just work,
|
||||
//! whether JS/WASM have loaded or not.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use leptos::prelude::*;
|
||||
//! use leptos_router::components::*;
|
||||
//! use leptos_router::path;
|
||||
//! use leptos_router::hooks::use_params_map;
|
||||
//!
|
||||
//! #[component]
|
||||
//! pub fn RouterExample() -> impl IntoView {
|
||||
//! view! {
|
||||
//!
|
||||
//! <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::*;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use any_spawner::Executor;
|
||||
use core::fmt::Debug;
|
||||
use js_sys::Reflect;
|
||||
|
||||
@@ -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("/"), || ()),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user