#![doc(
html_logo_url = "https://raw.githubusercontent.com/leptoskit/leptoskit/main/docs/src/assets/icon.svg"
)]
#![doc(
html_favicon_url = "https://raw.githubusercontent.com/leptoskit/leptoskit/main/docs/src/assets/mark.svg"
)]
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
pub use channel::Channel;
pub use channel::assert_channel_argument;
pub use channel::assert_channel_message;
pub use channel::assert_channel_presence;
#[cfg(feature = "client")]
pub use client::use_action;
#[cfg(feature = "client")]
pub use client::use_channel;
#[cfg(all(
feature = "client",
any(not(feature = "server"), feature = "postgres", feature = "sqlite")
))]
pub use client::use_inbox;
#[cfg(all(
feature = "client",
any(not(feature = "server"), feature = "postgres", feature = "sqlite")
))]
pub use client::use_job;
#[cfg(feature = "client")]
pub use client::use_live;
#[cfg(feature = "client")]
pub use client::use_live_action;
#[cfg(feature = "client")]
pub use client::use_live_mutation;
#[cfg(feature = "client")]
pub use client::use_live_paginated;
#[cfg(feature = "client")]
pub use client::use_live_status;
#[cfg(feature = "client")]
pub use client::use_stream;
pub use inbox::Inbox;
pub use inbox::InboxArgs;
pub use inbox::InboxNotice;
pub use inbox::MarkAllRead;
pub use inbox::MarkRead;
pub use inbox::Notice;
pub use inbox::NoticeAction;
pub use inbox::NoticeKind;
pub use jobs::Backoff;
pub use jobs::CancelJob;
pub use jobs::Failed;
pub use jobs::Job;
pub use jobs::JobOptions;
pub use jobs::JobStatus;
pub use jobs::JobStatusArgs;
pub use jobs::Visibility;
use leptos::prelude::Action;
use leptos::prelude::ArcAsyncDerived;
use leptos::prelude::ServerFnError;
pub use leptoskit_rpc_macros::action;
pub use leptoskit_rpc_macros::api;
pub use leptoskit_rpc_macros::channel;
pub use leptoskit_rpc_macros::cron;
pub use leptoskit_rpc_macros::job;
pub use leptoskit_rpc_macros::mutation;
pub use leptoskit_rpc_macros::query;
pub use leptoskit_rpc_macros::stream;
pub use operation::Error;
pub use operation::Kind;
pub use operation::Operation;
pub use operation::Result;
pub use page::MAX_PAGE_ROWS;
pub use page::MAX_PINNED_ROWS;
pub use page::Page;
pub use page::PageArgs;
pub use page::Paginated;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use page::paginate;
pub use scopes::ScopeInfo;
pub use scopes::Scopes;
pub use scopes::is_scope_name;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use server::ActionCx;
#[cfg(feature = "db")]
pub use server::Identify;
#[cfg(feature = "db")]
pub use server::Identity;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use server::JobCx;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use server::Jobs;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use server::JobsHandle;
#[cfg(feature = "live")]
pub use server::Live;
#[cfg(feature = "live")]
pub use server::LiveStats;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use server::MutationCx;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use server::QueryCx;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use server::call;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use server::cancel;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use server::inbox::Recipient;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use server::jobs::Enqueue;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use server::jobs::enqueue;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use server::jobs::job_registration;
pub use stream::Resumable;
pub use stream::StreamOperation;
mod channel;
#[cfg(feature = "client")]
pub mod client;
#[cfg(feature = "devtools")]
pub mod devtools;
mod inbox;
mod jobs;
pub mod live;
mod operation;
mod page;
pub mod scopes;
#[cfg(feature = "server")]
pub mod server;
#[cfg(feature = "ssr")]
pub mod spec;
mod stream;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub mod testing;
#[doc(hidden)]
pub mod __private {
pub use std::boxed::Box;
#[cfg(feature = "ssr")]
pub use inventory;
pub use leptos::server_fn::ServerFn;
pub use leptoskit_query::BoxFuture;
pub use schemars;
pub use serde;
pub use serde_json;
pub use crate::channel::assert_channel_argument;
pub use crate::channel::assert_channel_message;
pub use crate::channel::assert_channel_presence;
pub use crate::operation::assert_argument;
pub use crate::operation::assert_output;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use crate::server::handle;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use crate::server::jobs::assert_on_failure;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use crate::server::jobs::cron::enqueue_tick;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use crate::server::jobs::fail_job;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use crate::server::jobs::run_job;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use crate::server::stream::fallible as stream_fallible;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use crate::server::stream::open as open_stream;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use crate::server::stream::plain as stream_plain;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use crate::server::stream::resumable as stream_resumable;
#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub use crate::server::stream::resumable_fallible as stream_resumable_fallible;
pub use crate::stream::assert_cursor;
pub use crate::stream::assert_stream_item;
}
#[cfg(any(feature = "postgres", feature = "sqlite"))]
#[doc(hidden)]
#[macro_export]
macro_rules! __require_db {
() => {};
}
#[cfg(not(any(feature = "postgres", feature = "sqlite")))]
#[doc(hidden)]
#[macro_export]
macro_rules! __require_db {
() => {
::core::compile_error!(
"queries and mutations run on a database: turn on leptoskit_rpc's `postgres` or \
`sqlite` feature in the server build (`db-postgres` or `db-sqlite` through \
`leptoskit`)"
);
};
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FnError {
Network(String),
Invalid(String),
Server(String),
Decode(String),
Other(String),
}
impl FnError {
#[must_use]
pub fn retryable(&self) -> bool {
matches!(self, Self::Network(_))
}
#[must_use]
pub const fn class(&self) -> &'static str {
match self {
Self::Network(_) => "network",
Self::Invalid(_) => "invalid",
Self::Server(_) => "server",
Self::Decode(_) => "decode",
Self::Other(_) => "other",
}
}
#[must_use]
pub fn message(&self) -> &str {
match self {
Self::Network(message)
| Self::Invalid(message)
| Self::Server(message)
| Self::Decode(message)
| Self::Other(message) => message,
}
}
#[must_use]
pub fn from_wire(text: &str) -> Self {
let Some((kind, message)) = text.split_once('|') else {
return Self::Other(text.to_owned());
};
let message = message.to_owned();
match kind {
"Request" => Self::Network(message),
"Args" | "MissingArg" => Self::Invalid(message),
"ServerError" | "MiddlewareError" | "WrappedServerFn" => Self::Server(message),
"Deserialization" | "Serialization" => Self::Decode(message),
"Registration" | "Response" => Self::Other(message),
_ => Self::Other(text.to_owned()),
}
}
}
impl std::fmt::Display for FnError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} error: {}", self.class(), self.message())
}
}
impl std::error::Error for FnError {}
impl From<ServerFnError> for FnError {
fn from(error: ServerFnError) -> Self {
#[allow(deprecated)]
match error {
ServerFnError::Request(message) => Self::Network(message),
ServerFnError::Args(message) | ServerFnError::MissingArg(message) => {
Self::Invalid(message)
}
ServerFnError::ServerError(message) | ServerFnError::MiddlewareError(message) => {
Self::Server(message)
}
ServerFnError::Deserialization(message) | ServerFnError::Serialization(message) => {
Self::Decode(message)
}
ServerFnError::Registration(message) | ServerFnError::Response(message) => {
Self::Other(message)
}
ServerFnError::WrappedServerError(inner) => Self::Server(inner.to_string()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RetryPolicy {
pub attempts: u32,
pub base_delay_ms: u64,
pub max_delay_ms: u64,
}
impl RetryPolicy {
#[must_use]
pub const fn none() -> Self {
Self {
attempts: 1,
base_delay_ms: 0,
max_delay_ms: 0,
}
}
#[must_use]
pub const fn reads() -> Self {
Self {
attempts: 3,
base_delay_ms: 150,
max_delay_ms: 2_000,
}
}
#[must_use]
pub const fn aggressive() -> Self {
Self {
attempts: 5,
base_delay_ms: 250,
max_delay_ms: 10_000,
}
}
#[must_use]
pub fn delay_ms(&self, retry: u32) -> u64 {
if retry == 0 {
return 0;
}
let shift = retry.saturating_sub(1).min(16);
self.base_delay_ms
.saturating_mul(1_u64 << shift)
.min(self.max_delay_ms)
}
}
impl Default for RetryPolicy {
fn default() -> Self {
Self::reads()
}
}
pub type RetryFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T, FnError>> + Send + 'a>>;
pub fn call_with_retry<T, F>(policy: RetryPolicy, fetch: F) -> RetryFuture<'static, T>
where
T: Send + Sync + 'static,
F: Fn() -> Pin<Box<dyn Future<Output = Result<T, FnError>> + Send>> + Send + Sync + 'static,
{
retry_as(std::any::type_name::<F>(), policy, fetch)
}
fn retry_as<T, F>(function: &'static str, policy: RetryPolicy, fetch: F) -> RetryFuture<'static, T>
where
T: Send + Sync + 'static,
F: Fn() -> Pin<Box<dyn Future<Output = Result<T, FnError>> + Send>> + Send + Sync + 'static,
{
#[cfg(not(feature = "devtools"))]
let _ = function;
Box::pin(async move {
let mut attempt = 0_u32;
loop {
#[cfg(feature = "devtools")]
let trace = devtools::Attempt::start(function, attempt + 1);
let result = fetch().await;
#[cfg(feature = "devtools")]
trace.finish(result.as_ref().err());
match result {
Ok(value) => return Ok(value),
Err(error) => {
attempt += 1;
if attempt >= policy.attempts || !error.retryable() {
return Err(error);
}
leptoskit_query::sleep(Duration::from_millis(policy.delay_ms(attempt))).await;
}
}
}
})
}
pub type Query<T> = ArcAsyncDerived<Result<T, FnError>>;
pub fn query<T, Fu>(policy: RetryPolicy, fetch: impl Fn() -> Fu + Send + Sync + 'static) -> Query<T>
where
T: Send + Sync + 'static,
Fu: Future<Output = Result<T, FnError>> + Send + 'static,
{
let fetch = Arc::new(fetch);
ArcAsyncDerived::new(move || {
let fetch = Arc::clone(&fetch);
retry_as(std::any::type_name::<Fu>(), policy, move || {
let fut = fetch();
Box::pin(fut)
})
})
}
pub type Mutation<I, O> = Action<I, Result<O, FnError>>;
pub fn mutation<I, O, Fu>(action_fn: impl Fn(&I) -> Fu + Send + Sync + 'static) -> Mutation<I, O>
where
I: Send + Sync + 'static,
O: Send + Sync + 'static,
Fu: Future<Output = Result<O, FnError>> + Send + 'static,
{
Action::new(action_fn)
}
pub fn mutation_with_retry<I, O, Fu>(
policy: RetryPolicy,
action_fn: impl Fn(&I) -> Fu + Send + Sync + 'static,
) -> Mutation<I, O>
where
I: Send + Sync + Clone + 'static,
O: Send + Sync + 'static,
Fu: Future<Output = Result<O, FnError>> + Send + 'static,
{
let action_fn = Arc::new(action_fn);
Action::new(move |input: &I| {
let input = input.clone();
let action_fn = Arc::clone(&action_fn);
retry_as(std::any::type_name::<Fu>(), policy, move || {
Box::pin(action_fn(&input))
})
})
}