mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 15:44:42 -05:00
Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28af33d511 | ||
|
|
5a3413d3b3 | ||
|
|
702d2e247b | ||
|
|
71f698f322 | ||
|
|
8d4776bf5f | ||
|
|
65798e430f | ||
|
|
d46d1a4fab | ||
|
|
f9533ab75b | ||
|
|
d08f8822c0 | ||
|
|
7357839efb | ||
|
|
1b8ad58114 | ||
|
|
ef72f1ce96 | ||
|
|
6a5bfe9a5d | ||
|
|
1661fe2412 | ||
|
|
2324853155 | ||
|
|
28a3859365 | ||
|
|
6b50179189 | ||
|
|
0bb825f6bd | ||
|
|
a6aa111122 | ||
|
|
753cfe8e44 | ||
|
|
bc9c3add87 | ||
|
|
d0bb45dbc4 | ||
|
|
2a4b80cf22 | ||
|
|
881734b43a | ||
|
|
68fdc8d9c4 | ||
|
|
03ed805137 | ||
|
|
5d4603e988 | ||
|
|
e65969cfcd | ||
|
|
e1e90b8595 | ||
|
|
71a136c7af | ||
|
|
49366be2a5 | ||
|
|
6f5bdcc675 | ||
|
|
726a99441f | ||
|
|
775bea46d9 | ||
|
|
2aa9827a1f | ||
|
|
be3c1811fc | ||
|
|
8df4f3c71d | ||
|
|
3028a9acd0 | ||
|
|
4b167fb809 | ||
|
|
46ae8ef9b2 | ||
|
|
21f26e7a6b | ||
|
|
204648d388 | ||
|
|
a04bca55a2 | ||
|
|
1fec678174 | ||
|
|
4dbb8d1a42 | ||
|
|
fc2c52eb04 | ||
|
|
1ae35ce5b3 | ||
|
|
8edb11f324 | ||
|
|
5a01a7f2ed | ||
|
|
f252460d02 | ||
|
|
6331b488e4 | ||
|
|
fcba8b3b17 | ||
|
|
d665dd4b89 | ||
|
|
be740b38ee | ||
|
|
d2803c938c | ||
|
|
f29224415a | ||
|
|
292772c4d6 | ||
|
|
5947aa299e | ||
|
|
6098836cf7 | ||
|
|
2dfa61ff6a | ||
|
|
4f39b0b0ef | ||
|
|
980595f1f0 | ||
|
|
14eb707e82 | ||
|
|
3de0414ed5 | ||
|
|
75cae91661 | ||
|
|
8b258b0d26 | ||
|
|
84bdd6b568 | ||
|
|
809023a2ad | ||
|
|
1477ae2cfb | ||
|
|
26995f8efd | ||
|
|
3c174b26a5 | ||
|
|
0c7e77800a | ||
|
|
fee2421047 | ||
|
|
89f26f6e9b | ||
|
|
e76b22bec8 | ||
|
|
cff277b3db | ||
|
|
0258ac6df4 | ||
|
|
36132a5823 | ||
|
|
6f35f8d197 | ||
|
|
3973c4f420 | ||
|
|
7f2237b91e | ||
|
|
aa6cd08387 | ||
|
|
15fc7dd7be | ||
|
|
eac979e309 | ||
|
|
f7ac7be32b | ||
|
|
2462a1dc92 | ||
|
|
2c6d790cdb | ||
|
|
1c26261fd7 | ||
|
|
c2b239dba2 | ||
|
|
d4044cd5a1 | ||
|
|
43912f4fd0 | ||
|
|
a5293f0b79 | ||
|
|
998eefb8c5 | ||
|
|
63f3508818 | ||
|
|
24308bb0eb | ||
|
|
f804a69f2f | ||
|
|
b596af45f0 | ||
|
|
5206755124 | ||
|
|
53a55a1ef2 | ||
|
|
0f74332d38 | ||
|
|
bb0dff6af5 | ||
|
|
d204ac6d5e | ||
|
|
b0ad85e624 |
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
@@ -1,12 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "cargo"
|
||||
directories:
|
||||
- "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
54
.github/workflows/autofix.yml
vendored
Normal file
54
.github/workflows/autofix.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: autofix.ci
|
||||
on:
|
||||
pull_request:
|
||||
# Running this workflow on main branch pushes requires write permission to apply changes.
|
||||
# Leave it alone for future uses.
|
||||
# push:
|
||||
# branches: ["main"]
|
||||
permissions:
|
||||
contents: read
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUST_BACKTRACE: 1
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
jobs:
|
||||
autofix:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: {toolchain: nightly, components: "rustfmt, clippy", target: "wasm32-unknown-unknown", rustflags: ""}
|
||||
- name: Install Glib
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libglib2.0-dev
|
||||
- name: Install jq
|
||||
run: sudo apt-get install jq
|
||||
- run: |
|
||||
echo "Formatting the workspace"
|
||||
cargo fmt --all
|
||||
|
||||
echo "Running Clippy against each member's features (default features included)"
|
||||
for member in $(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | .name'); do
|
||||
echo "Working on member $member":
|
||||
echo -e "\tdefault-features/no-features:"
|
||||
# this will also run on members with no features or default features
|
||||
cargo clippy --allow-dirty --fix --lib --package "$member"
|
||||
|
||||
features=$(cargo metadata --no-deps --format-version 1 | jq -r ".packages[] | select(.name == \"$member\") | .features | keys[]")
|
||||
for feature in $features; do
|
||||
if [ "$feature" = "default" ]; then
|
||||
continue
|
||||
fi
|
||||
echo -e "\tfeature $feature"
|
||||
cargo clippy --allow-dirty --fix --lib --package "$member" --features "$feature"
|
||||
done
|
||||
done
|
||||
- uses: autofix-ci/action@v1.3.1
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
fail-fast: false
|
||||
8
.github/workflows/ci-semver.yml
vendored
8
.github/workflows/ci-semver.yml
vendored
@@ -8,15 +8,21 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
jobs:
|
||||
get-leptos-changed:
|
||||
uses: ./.github/workflows/get-leptos-changed.yml
|
||||
test:
|
||||
needs: [get-leptos-changed]
|
||||
if: github.event.pull_request.labels[0].name == 'semver' # needs.get-leptos-changed.outputs.leptos_changed == 'true' && github.event.pull_request.labels[0].name != 'breaking'
|
||||
if: needs.get-leptos-changed.outputs.leptos_changed == 'true' && github.event.pull_request.labels[0].name != 'breaking'
|
||||
name: Run semver check (nightly-2024-08-01)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Glib
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libglib2.0-dev
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Semver Checks
|
||||
|
||||
5
.github/workflows/run-cargo-make-task.yml
vendored
5
.github/workflows/run-cargo-make-task.yml
vendored
@@ -14,6 +14,7 @@ on:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
jobs:
|
||||
test:
|
||||
name: Run ${{ inputs.cargo_make_task }} (${{ inputs.toolchain }})
|
||||
@@ -33,6 +34,10 @@ jobs:
|
||||
echo "Disk space after cleanup:"
|
||||
df -h
|
||||
# Setup environment
|
||||
- name: Install Glib
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libglib2.0-dev
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@ blob.rs
|
||||
|
||||
.vscode
|
||||
vendor
|
||||
hash.txt
|
||||
|
||||
654
Cargo.lock
generated
654
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
50
Cargo.toml
50
Cargo.toml
@@ -40,36 +40,36 @@ members = [
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.7.0-rc1"
|
||||
version = "0.7.2"
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
|
||||
[workspace.dependencies]
|
||||
throw_error = { path = "./any_error/", version = "0.2.0-rc1" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.1.0" }
|
||||
throw_error = { path = "./any_error/", version = "0.2.0" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.2.0" }
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1.0" }
|
||||
either_of = { path = "./either_of/", version = "0.1.0" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0-rc1" }
|
||||
leptos = { path = "./leptos", version = "0.7.0-rc1" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.0-rc1" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.0-rc1" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-rc1" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-rc1" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.0-rc1" }
|
||||
leptos_router = { path = "./router", version = "0.7.0-rc1" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.0-rc1" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.0-rc1" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.0-rc1" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0-rc1" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0" }
|
||||
leptos = { path = "./leptos", version = "0.7.2" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.2" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.2" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.2" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.2" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.2" }
|
||||
leptos_router = { path = "./router", version = "0.7.2" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.2" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.2" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.2" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0" }
|
||||
oco_ref = { path = "./oco", version = "0.2.0" }
|
||||
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.0-rc1" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0-rc1" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0-rc1" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.0-rc1" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-rc1" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-rc1" }
|
||||
tachys = { path = "./tachys", version = "0.1.0-rc1" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.0" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.2" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.2" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.2" }
|
||||
tachys = { path = "./tachys", version = "0.1.0" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
@@ -78,3 +78,9 @@ opt-level = 'z'
|
||||
|
||||
[workspace.metadata.cargo-all-features]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = [
|
||||
'cfg(leptos_debuginfo)',
|
||||
'cfg(erase_components)',
|
||||
] }
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
|
||||
You can find a list of useful libraries and example projects at [`awesome-leptos`](https://github.com/leptos-rs/awesome-leptos).
|
||||
|
||||
# The `main` branch is currently undergoing major changes in preparation for the [0.7](https://github.com/leptos-rs/leptos/milestone/4) release. For a stable version, please use the [v0.6.13 tag](https://github.com/leptos-rs/leptos/tree/v0.6.13)
|
||||
|
||||
# Leptos
|
||||
|
||||
```rust
|
||||
@@ -159,9 +157,7 @@ Sure! Obviously the `view` macro is for generating DOM nodes but you can use the
|
||||
- Use event listeners to update signals
|
||||
- Create effects to update the UI
|
||||
|
||||
I've put together a [very simple GTK example](https://github.com/leptos-rs/leptos/blob/main/examples/gtk/src/main.rs) so you can see what I mean.
|
||||
|
||||
The new rendering approach being developed for 0.7 supports “universal rendering,” i.e., it can use any rendering library that supports a small set of 6-8 functions. (This is intended as a layer over typical retained-mode, OOP-style GUI toolkits like the DOM, GTK, etc.) That future rendering work will allow creating native UI in a way that is much more similar to the declarative approach used by the web framework.
|
||||
The 0.7 update originally set out to create a "generic rendering" approach that would allow us to reuse most of the same view logic to do all of the above. Unfortunately, this has had to be shelved for now due to difficulties encountered by the Rust compiler when building larger-scale applications with the number of generics spread throughout the codebase that this required. It's an approach I'm looking forward to exploring again in the future; feel free to reach out if you're interested in this kind of work.
|
||||
|
||||
### How is this different from Yew?
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "throw_error"
|
||||
version = "0.2.0-rc1"
|
||||
version = "0.2.0"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "any_spawner"
|
||||
version = "0.1.1"
|
||||
version = "0.2.1"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -11,13 +11,13 @@ edition.workspace = true
|
||||
[dependencies]
|
||||
async-executor = { version = "1.13.1", optional = true }
|
||||
futures = "0.3.31"
|
||||
glib = { version = "0.20.5", optional = true }
|
||||
glib = { version = "0.20.6", optional = true }
|
||||
thiserror = "2.0"
|
||||
tokio = { version = "1.41", optional = true, default-features = false, features = [
|
||||
"rt",
|
||||
] }
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4.45", optional = true }
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4.47", optional = true }
|
||||
|
||||
[features]
|
||||
async-executor = ["dep:async-executor"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "either_of"
|
||||
version = "0.1.0"
|
||||
version = "0.1.2"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -134,7 +134,7 @@ tuples!(EitherOf14 + EitherOf14Future + EitherOf14FutureProj => A, B, C, D, E, F
|
||||
tuples!(EitherOf15 + EitherOf15Future + EitherOf15FutureProj => A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
|
||||
tuples!(EitherOf16 + EitherOf16Future + EitherOf16FutureProj => A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
|
||||
|
||||
/// Matches over the first expression and returns an either ([`Either`], [`EitherOf3`], ... [`EitherOf6`])
|
||||
/// Matches over the first expression and returns an either ([`Either`], [`EitherOf3`], ... [`EitherOf8`])
|
||||
/// composed of the values returned by the match arms.
|
||||
///
|
||||
/// The pattern syntax is exactly the same as found in a match arm.
|
||||
@@ -197,6 +197,29 @@ macro_rules! either {
|
||||
$e_pattern => $crate::EitherOf6::E($e_expression),
|
||||
$f_pattern => $crate::EitherOf6::F($f_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr, $g_pattern:pat => $g_expression:expr,) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf7::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf7::B($b_expression),
|
||||
$c_pattern => $crate::EitherOf7::C($c_expression),
|
||||
$d_pattern => $crate::EitherOf7::D($d_expression),
|
||||
$e_pattern => $crate::EitherOf7::E($e_expression),
|
||||
$f_pattern => $crate::EitherOf7::F($f_expression),
|
||||
$g_pattern => $crate::EitherOf7::G($g_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr, $g_pattern:pat => $g_expression:expr, $h_pattern:pat => $h_expression:expr,) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf8::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf8::B($b_expression),
|
||||
$c_pattern => $crate::EitherOf8::C($c_expression),
|
||||
$d_pattern => $crate::EitherOf8::D($d_expression),
|
||||
$e_pattern => $crate::EitherOf8::E($e_expression),
|
||||
$f_pattern => $crate::EitherOf8::F($f_expression),
|
||||
$g_pattern => $crate::EitherOf8::G($g_expression),
|
||||
$h_pattern => $crate::EitherOf8::H($h_expression),
|
||||
}
|
||||
}; // if you need more eithers feel free to open a PR ;-)
|
||||
}
|
||||
|
||||
@@ -233,4 +256,23 @@ fn either_macro() {
|
||||
16 => 24u8,
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf7<&str, f64, char, f32, u8, i8, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
15 => 0.0f32,
|
||||
16 => 24u8,
|
||||
17 => 2i8,
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf8<&str, f64, char, f32, u8, i8, u32, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
15 => 0.0f32,
|
||||
16 => 24u8,
|
||||
17 => 2i8,
|
||||
18 => 42u32,
|
||||
_ => 12,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ lto = true
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
leptos = { path = "../../leptos", features = ["experimental-islands"] }
|
||||
leptos = { path = "../../leptos", features = ["islands"] }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
|
||||
@@ -12,7 +12,7 @@ futures = "0.3.30"
|
||||
http = "1.1"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"tracing",
|
||||
"experimental-islands",
|
||||
"islands",
|
||||
] }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
|
||||
@@ -12,7 +12,7 @@ futures = "0.3.30"
|
||||
http = "1.1"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"tracing",
|
||||
"experimental-islands",
|
||||
"islands",
|
||||
] }
|
||||
leptos_router = { path = "../../router" }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
|
||||
|
||||
@@ -44,8 +44,8 @@ window.addEventListener("click", async (ev) => {
|
||||
// TODO parse from the request stream instead?
|
||||
const doc = parser.parseFromString(htmlString, 'text/html');
|
||||
|
||||
// The 'doc' variable now contains the parsed DOM
|
||||
const transition = document.startViewTransition(async () => {
|
||||
// The 'doc' variable now contains the parsed DOM
|
||||
const transition = async () => {
|
||||
const oldDocWalker = document.createTreeWalker(document);
|
||||
const newDocWalker = doc.createTreeWalker(doc);
|
||||
let oldNode = oldDocWalker.currentNode;
|
||||
@@ -128,8 +128,13 @@ window.addEventListener("click", async (ev) => {
|
||||
}
|
||||
} }
|
||||
}
|
||||
});
|
||||
await transition;
|
||||
};
|
||||
// Not all browsers support startViewTransition; see https://caniuse.com/?search=startViewTransition
|
||||
if (document.startViewTransition) {
|
||||
await document.startViewTransition(transition);
|
||||
} else {
|
||||
await transition()
|
||||
}
|
||||
window.history.pushState(undefined, null, url);
|
||||
});
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ crate-type = ["cdylib", "rlib"]
|
||||
actix-files = { version = "0.6.6", optional = true }
|
||||
actix-web = { version = "4.8", optional = true, features = ["macros"] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
js-sys = { version = "0.3.70", optional = true }
|
||||
js-sys = { version = "0.3.72" }
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
@@ -21,7 +21,7 @@ tokio = { version = "1.39", features = ["time", "rt"], optional = true }
|
||||
|
||||
[features]
|
||||
hydrate = [
|
||||
"dep:js-sys",
|
||||
|
||||
"leptos/hydrate",
|
||||
]
|
||||
ssr = [
|
||||
|
||||
121
examples/suspense_tests/e2e/features/check_aria_current.feature
Normal file
121
examples/suspense_tests/e2e/features/check_aria_current.feature
Normal file
@@ -0,0 +1,121 @@
|
||||
@check_aria_current
|
||||
Feature: Check aria-current being applied to make links bolded
|
||||
|
||||
Background:
|
||||
|
||||
Given I see the app
|
||||
|
||||
Scenario: Should see the base case working
|
||||
Then I see the Out-of-Order link being bolded
|
||||
And I see the following links being bolded
|
||||
| Out-of-Order |
|
||||
| Nested |
|
||||
And I see the In-Order link not being bolded
|
||||
And I see the following links not being bolded
|
||||
| In-Order |
|
||||
| Single |
|
||||
|
||||
Scenario: Should see client-side render the correct bolded links
|
||||
When I select the link In-Order
|
||||
And I select the link Single
|
||||
Then I see the following links being bolded
|
||||
| In-Order |
|
||||
| Single |
|
||||
And I see the following links not being bolded
|
||||
| Out-of-Order |
|
||||
| Nested |
|
||||
|
||||
Scenario: Should see server-side render the correct bolded links
|
||||
When I select the link In-Order
|
||||
And I select the link Single
|
||||
And I reload the page
|
||||
Then I see the following links being bolded
|
||||
| In-Order |
|
||||
| Single |
|
||||
And I see the following links not being bolded
|
||||
| Out-of-Order |
|
||||
| Nested |
|
||||
|
||||
Scenario: Check that the base nested route links are working
|
||||
When I select the link Instrumented
|
||||
Then I see the Instrumented link being bolded
|
||||
And I see the Item Listing link not being bolded
|
||||
|
||||
Scenario: Should see going deep down into nested routes bold links
|
||||
When I select the link Instrumented
|
||||
And I select the link Target 421
|
||||
Then I see the following links being bolded
|
||||
| Instrumented |
|
||||
| Item Listing |
|
||||
| Target 4## |
|
||||
| Target 42# |
|
||||
| Target 421 |
|
||||
| field1 |
|
||||
|
||||
Scenario: Should see going deep down into nested routes in SSR bold links
|
||||
When I select the link Instrumented
|
||||
And I select the link Target 421
|
||||
And I reload the page
|
||||
Then I see the following links being bolded
|
||||
| Instrumented |
|
||||
| Item Listing |
|
||||
| Target 4## |
|
||||
| Target 42# |
|
||||
| Target 421 |
|
||||
| field1 |
|
||||
|
||||
Scenario: Going deep down navigate around nested links bold correctly
|
||||
When I select the link Instrumented
|
||||
And I select the link Target 421
|
||||
And I select the link Inspect path2/field3
|
||||
Then I see the following links being bolded
|
||||
| Instrumented |
|
||||
| Item Listing |
|
||||
| Target 4## |
|
||||
| Target 42# |
|
||||
| field3 |
|
||||
And I see the following links not being bolded
|
||||
| Target 421 |
|
||||
| field1 |
|
||||
|
||||
Scenario: Going deep down navigate around nested links bold correctly, SSR
|
||||
When I select the link Instrumented
|
||||
And I select the link Target 421
|
||||
And I select the link Inspect path2/field3
|
||||
And I reload the page
|
||||
Then I see the following links being bolded
|
||||
| Instrumented |
|
||||
| Item Listing |
|
||||
| Target 4## |
|
||||
| Target 42# |
|
||||
| field3 |
|
||||
And I see the following links not being bolded
|
||||
| Target 421 |
|
||||
| field1 |
|
||||
|
||||
Scenario: Going deep down back out nested routes reset bolded states
|
||||
When I select the link Instrumented
|
||||
And I select the link Target 421
|
||||
And I select the link Counters
|
||||
Then I see the following links being bolded
|
||||
| Instrumented |
|
||||
| Counters |
|
||||
And I see the following links not being bolded
|
||||
| Item Listing |
|
||||
| Target 4## |
|
||||
| Target 42# |
|
||||
| Target 421 |
|
||||
|
||||
Scenario: Going deep down back out nested routes reset bolded states, SSR
|
||||
When I select the link Instrumented
|
||||
And I select the link Target 421
|
||||
And I select the link Counters
|
||||
And I reload the page
|
||||
Then I see the following links being bolded
|
||||
| Instrumented |
|
||||
| Counters |
|
||||
And I see the following links not being bolded
|
||||
| Item Listing |
|
||||
| Target 4## |
|
||||
| Target 42# |
|
||||
| Target 421 |
|
||||
@@ -81,3 +81,20 @@ pub async fn instrumented_counts(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn link_text_is_aria_current(client: &Client, text: &str) -> Result<()> {
|
||||
let link = find::link_with_text(client, text).await?;
|
||||
|
||||
link.attr("aria-current").await?
|
||||
.expect(format!("aria-current missing for {text}").as_str());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn link_text_is_not_aria_current(client: &Client, text: &str) -> Result<()> {
|
||||
let link = find::link_with_text(client, text).await?;
|
||||
|
||||
link.attr("aria-current").await?
|
||||
.map(|_| anyhow::bail!("aria-current mistakenly set for {text}"))
|
||||
.unwrap_or(Ok(()))
|
||||
}
|
||||
|
||||
@@ -124,3 +124,12 @@ async fn component_message(client: &Client, id: &str) -> Result<String> {
|
||||
|
||||
Ok(text)
|
||||
}
|
||||
|
||||
pub async fn link_with_text(client: &Client, text: &str) -> Result<Element> {
|
||||
let link = client
|
||||
.wait()
|
||||
.for_element(Locator::LinkText(text))
|
||||
.await
|
||||
.expect(format!("Link not found by `{}`", text).as_str());
|
||||
Ok(link)
|
||||
}
|
||||
|
||||
@@ -80,6 +80,58 @@ async fn i_see_the_second_count_is(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[then(regex = r"^I see the (.*) link being bolded$")]
|
||||
async fn i_see_the_link_being_bolded(
|
||||
world: &mut AppWorld,
|
||||
text: String,
|
||||
) -> Result<()> {
|
||||
let client = &world.client;
|
||||
check::link_text_is_aria_current(client, &text).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[then(expr = "I see the following links being bolded")]
|
||||
async fn i_see_the_following_links_being_bolded(
|
||||
world: &mut AppWorld,
|
||||
step: &Step,
|
||||
) -> Result<()> {
|
||||
let client = &world.client;
|
||||
if let Some(table) = step.table.as_ref() {
|
||||
for row in table.rows.iter() {
|
||||
check::link_text_is_aria_current(client, &row[0]).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[then(regex = r"^I see the (.*) link not being bolded$")]
|
||||
async fn i_see_the_link_being_not_bolded(
|
||||
world: &mut AppWorld,
|
||||
text: String,
|
||||
) -> Result<()> {
|
||||
let client = &world.client;
|
||||
check::link_text_is_not_aria_current(client, &text).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[then(expr = "I see the following links not being bolded")]
|
||||
async fn i_see_the_following_links_not_being_bolded(
|
||||
world: &mut AppWorld,
|
||||
step: &Step,
|
||||
) -> Result<()> {
|
||||
let client = &world.client;
|
||||
if let Some(table) = step.table.as_ref() {
|
||||
for row in table.rows.iter() {
|
||||
check::link_text_is_not_aria_current(client, &row[0]).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[then(expr = "I see the following counters under section")]
|
||||
#[then(expr = "the following counters under section")]
|
||||
async fn i_see_the_following_counters_under_section(
|
||||
|
||||
@@ -4,25 +4,6 @@ use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use server_fn::ServerFnError;
|
||||
|
||||
pub fn shell(leptos_options: &LeptosOptions) -> impl IntoView {
|
||||
view! {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<AutoReload options=leptos_options.clone() />
|
||||
<HydrationScripts options=leptos_options.clone()/>
|
||||
<link rel="stylesheet" id="leptos" href="/pkg/todo_app_sqlite_csr.css"/>
|
||||
<link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
|
||||
</head>
|
||||
<body>
|
||||
<TodoApp/>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
|
||||
pub struct Todo {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "hydration_context"
|
||||
version = "0.2.0-rc1"
|
||||
version = "0.2.1"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -14,8 +14,8 @@ throw_error = { workspace = true }
|
||||
or_poisoned = { workspace = true }
|
||||
futures = "0.3.31"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
wasm-bindgen = { version = "0.2.95", optional = true }
|
||||
js-sys = { version = "0.3.72", optional = true }
|
||||
wasm-bindgen = { version = "0.2.97", optional = true }
|
||||
js-sys = { version = "0.3.74", optional = true }
|
||||
once_cell = "1.20"
|
||||
pin-project-lite = "0.2.15"
|
||||
|
||||
@@ -25,3 +25,6 @@ browser = ["dep:wasm-bindgen", "dep:js-sys"]
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// #[wasm_bindgen(thread_local)] is deprecated in wasm-bindgen 0.2.96
|
||||
// but the replacement is also only shipped in that version
|
||||
// as a result, we'll just allow deprecated for now
|
||||
#![allow(deprecated)]
|
||||
|
||||
use super::{SerializedDataId, SharedContext};
|
||||
use crate::{PinnedFuture, PinnedStream};
|
||||
use core::fmt::Debug;
|
||||
|
||||
@@ -83,15 +83,14 @@ pub trait SharedContext: Debug {
|
||||
|
||||
/// Reads the current value of some data from the shared context, if it has been
|
||||
/// sent from the server. This returns the serialized data as a `String` that should
|
||||
/// be deserialized using [`Serializable::de`].
|
||||
/// be deserialized.
|
||||
///
|
||||
/// On the server and in client-side rendered implementations, this should
|
||||
/// always return [`None`].
|
||||
fn read_data(&self, id: &SerializedDataId) -> Option<String>;
|
||||
|
||||
/// Returns a [`Future`] that resolves with a `String` that should
|
||||
/// be deserialized using [`Serializable::de`] once the given piece of server
|
||||
/// data has resolved.
|
||||
/// be deserialized once the given piece of server data has resolved.
|
||||
///
|
||||
/// On the server and in client-side rendered implementations, this should
|
||||
/// return a [`Future`] that is immediately ready with [`None`].
|
||||
@@ -148,8 +147,8 @@ pub trait SharedContext: Debug {
|
||||
|
||||
/// Adds a `Future` to the set of “blocking resources” that should prevent the server’s
|
||||
/// response stream from beginning until all are resolved. The `Future` returned by
|
||||
/// [`blocking_resources`](Self::blocking_resources) will not resolve until every `Future`
|
||||
/// added by this method has resolved.
|
||||
/// blocking resources will not resolve until every `Future` added by this method
|
||||
/// has resolved.
|
||||
///
|
||||
/// In browser implementations, this should be a no-op.
|
||||
fn defer_stream(&self, wait_for: PinnedFuture<()>);
|
||||
|
||||
@@ -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 {
|
||||
@@ -80,16 +82,18 @@ impl ResponseParts {
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper for an Actix [`HttpRequest`](actix_web::HttpRequest) that allows it to be used in an
|
||||
/// A wrapper for an Actix [`HttpRequest`] that allows it to be used in an
|
||||
/// `Send`/`Sync` setting like Leptos's Context API.
|
||||
#[derive(Debug, Clone)]
|
||||
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)
|
||||
@@ -371,8 +375,8 @@ pub fn handle_server_fns_with_context(
|
||||
.take(),
|
||||
);
|
||||
|
||||
// it it accepts text/html (i.e., is a plain form post) and doesn't already have a
|
||||
// Location set, then redirect to to Referer
|
||||
// if it accepts text/html (i.e., is a plain form post) and doesn't already have a
|
||||
// Location set, then redirect to the Referer
|
||||
if accepts_html {
|
||||
if let Some(referrer) = referrer {
|
||||
let has_location =
|
||||
@@ -386,7 +390,20 @@ pub fn handle_server_fns_with_context(
|
||||
}
|
||||
}
|
||||
|
||||
// apply status code and headers if used changed them
|
||||
// the Location header may have been set to Referer, so any redirection by the
|
||||
// user must overwrite it
|
||||
{
|
||||
let mut res_options = res_options.0.write();
|
||||
let headers = res.0.headers_mut();
|
||||
|
||||
for location in
|
||||
res_options.headers.remove(header::LOCATION)
|
||||
{
|
||||
headers.insert(header::LOCATION, location);
|
||||
}
|
||||
}
|
||||
|
||||
// apply status code and headers if user changed them
|
||||
res.extend_response(&res_options);
|
||||
res.0
|
||||
})
|
||||
@@ -415,12 +432,6 @@ pub fn handle_server_fns_with_context(
|
||||
/// will include fallback content for any `<Suspense/>` nodes, and be immediately interactive,
|
||||
/// but requires some client-side JavaScript.
|
||||
///
|
||||
/// The provides a [MetaContext] and a [RouterIntegrationContext] to app’s context before
|
||||
/// rendering it, and includes any meta tags injected using [leptos_meta].
|
||||
///
|
||||
/// The HTML stream is rendered using [render_to_stream](leptos::ssr::render_to_stream), and
|
||||
/// includes everything described in the documentation for that function.
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
/// use actix_web::{App, HttpServer};
|
||||
@@ -459,9 +470,8 @@ 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(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -481,13 +491,6 @@ where
|
||||
/// This stream will pause at each `<Suspense/>` node and wait for it to resolve before
|
||||
/// sending down its HTML. The app will become interactive once it has fully loaded.
|
||||
///
|
||||
/// The provides a [MetaContext] and a [RouterIntegrationContext] to app’s context before
|
||||
/// rendering it, and includes any meta tags injected using [leptos_meta].
|
||||
///
|
||||
/// The HTML stream is rendered using
|
||||
/// [render_to_stream_in_order](leptos::ssr::render_to_stream_in_order),
|
||||
/// and includes everything described in the documentation for that function.
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
/// use actix_web::{App, HttpServer};
|
||||
@@ -529,9 +532,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)
|
||||
@@ -548,13 +549,7 @@ where
|
||||
|
||||
/// Returns an Actix [struct@Route](actix_web::Route) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], asynchronously rendering an HTML page after all
|
||||
/// `async` [Resource](leptos::Resource)s have loaded.
|
||||
///
|
||||
/// The provides a [MetaContext] and a [RouterIntegrationContext] to the app’s context before
|
||||
/// rendering it, and includes any meta tags injected using [leptos_meta].
|
||||
///
|
||||
/// The HTML stream is rendered using [render_to_string_async](leptos::ssr::render_to_string_async), and
|
||||
/// includes everything described in the documentation for that function.
|
||||
/// `async` resources have loaded.
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
@@ -594,9 +589,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 +613,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 +648,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,9 +680,8 @@ 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(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -716,7 +704,7 @@ where
|
||||
|
||||
/// Returns an Actix [struct@Route](actix_web::Route) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], asynchronously serving the page once all `async`
|
||||
/// [Resource](leptos::Resource)s have loaded.
|
||||
/// resources have loaded.
|
||||
///
|
||||
/// This function allows you to provide additional information to Leptos for your route.
|
||||
/// It could be used to pass in Path Info, Connection Info, or anything your heart desires.
|
||||
@@ -724,9 +712,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 +1305,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 +1319,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>,
|
||||
|
||||
@@ -11,7 +11,7 @@ edition.workspace = true
|
||||
[dependencies]
|
||||
any_spawner = { workspace = true, features = ["tokio"] }
|
||||
hydration_context = { workspace = true }
|
||||
axum = { version = "0.7.7", default-features = false, features = [
|
||||
axum = { version = "0.7.9", default-features = false, features = [
|
||||
"matched-path",
|
||||
] }
|
||||
dashmap = "6"
|
||||
@@ -26,11 +26,11 @@ once_cell = "1"
|
||||
parking_lot = "0.12.3"
|
||||
tokio = { version = "1.41", default-features = false }
|
||||
tower = { version = "0.5.1", features = ["util"] }
|
||||
tower-http = "0.6.1"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
tower-http = "0.6.2"
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
axum = "0.7.7"
|
||||
axum = "0.7.9"
|
||||
tokio = { version = "1.41", features = ["net", "rt-multi-thread"] }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! Provides functions to easily integrate Leptos with Axum.
|
||||
//!
|
||||
//! ## JS Fetch Integration
|
||||
@@ -14,7 +16,7 @@
|
||||
//! - `default`: supports running in a typical native Tokio/Axum environment
|
||||
//! - `wasm`: with `default-features = false`, supports running in a JS Fetch-based
|
||||
//! environment
|
||||
//! - `experimental-islands`: activates Leptos [islands mode](https://leptos-rs.github.io/leptos/islands.html)
|
||||
//! - `islands`: activates Leptos [islands mode](https://leptos-rs.github.io/leptos/islands.html)
|
||||
//!
|
||||
//! ### Important Note
|
||||
//! Prior to 0.5, using `default-features = false` on `leptos_axum` simply did nothing. Now, it actively
|
||||
@@ -63,10 +65,9 @@ use leptos_meta::ServerMetaContext;
|
||||
#[cfg(feature = "default")]
|
||||
use leptos_router::static_routes::ResolvedStaticPath;
|
||||
use leptos_router::{
|
||||
components::provide_server_redirect,
|
||||
location::RequestUrl,
|
||||
static_routes::{RegenerationFn, StaticParamsMap},
|
||||
ExpandOptionals, PathSegment, RouteList, RouteListing, SsrMode,
|
||||
components::provide_server_redirect, location::RequestUrl,
|
||||
static_routes::RegenerationFn, ExpandOptionals, PathSegment, RouteList,
|
||||
RouteListing, SsrMode,
|
||||
};
|
||||
#[cfg(feature = "default")]
|
||||
use once_cell::sync::Lazy;
|
||||
@@ -85,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,
|
||||
}
|
||||
|
||||
@@ -194,9 +197,9 @@ impl ExtendResponse for AxumResponse {
|
||||
/// 2. A server function that is called from WASM running in the client (e.g., a dispatched action
|
||||
/// or a spawned `Future`).
|
||||
/// 3. A `<form>` submitted to the server function endpoint using default browser APIs (often due
|
||||
/// to using [`ActionForm`](leptos::form::ActionForm) without JS/WASM present.)
|
||||
/// to using [`ActionForm`] without JS/WASM present.)
|
||||
///
|
||||
/// Using it with a non-blocking [`Resource`](leptos::server::Resource) will not work if you are using streaming rendering,
|
||||
/// Using it with a non-blocking [`Resource`] will not work if you are using streaming rendering,
|
||||
/// as the response's headers will already have been sent by the time the server function calls `redirect()`.
|
||||
///
|
||||
/// ### Implementation
|
||||
@@ -396,8 +399,8 @@ async fn handle_server_fns_inner(
|
||||
// actually run the server fn
|
||||
let mut res = AxumResponse(service.run(req).await);
|
||||
|
||||
// it it accepts text/html (i.e., is a plain form post) and doesn't already have a
|
||||
// Location set, then redirect to to Referer
|
||||
// if it accepts text/html (i.e., is a plain form post) and doesn't already have a
|
||||
// Location set, then redirect to the Referer
|
||||
if accepts_html {
|
||||
if let Some(referrer) = referrer {
|
||||
let has_location =
|
||||
@@ -409,7 +412,7 @@ async fn handle_server_fns_inner(
|
||||
}
|
||||
}
|
||||
|
||||
// apply status code and headers if used changed them
|
||||
// apply status code and headers if user changed them
|
||||
res.extend_response(&res_options);
|
||||
Ok(res.0)
|
||||
})
|
||||
@@ -432,18 +435,13 @@ 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>>;
|
||||
|
||||
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], serving an HTML stream of your application.
|
||||
///
|
||||
/// The provides a [MetaContext] and a [RouterIntegrationContext] to app’s context before
|
||||
/// rendering it, and includes any meta tags injected using [leptos_meta].
|
||||
///
|
||||
/// The HTML stream is rendered using [render_to_stream](leptos::ssr::render_to_stream), and
|
||||
/// includes everything described in the documentation for that function.
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
/// use axum::{handler::Handler, Router};
|
||||
@@ -481,8 +479,7 @@ pub type PinnedHtmlStream =
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`](leptos_meta::ServerMetaContext)
|
||||
/// - [`RouterIntegrationContext`](leptos_router::RouterIntegrationContext)
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -533,12 +530,6 @@ where
|
||||
/// This stream will pause at each `<Suspense/>` node and wait for it to resolve before
|
||||
/// sending down its HTML. The app will become interactive once it has fully loaded.
|
||||
///
|
||||
/// The provides a [MetaContext] and a [RouterIntegrationContext] to app’s context before
|
||||
/// rendering it, and includes any meta tags injected using [leptos_meta].
|
||||
///
|
||||
/// The HTML stream is rendered using [render_to_stream_in_order], and includes everything described in
|
||||
/// the documentation for that function.
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
/// use axum::{handler::Handler, Router};
|
||||
@@ -576,8 +567,7 @@ where
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`](leptos_meta::ServerMetaContext)
|
||||
/// - [`RouterIntegrationContext`](leptos_router::RouterIntegrationContext)
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -630,8 +620,7 @@ where
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`](leptos_meta::ServerMetaContext)
|
||||
/// - [`RouterIntegrationContext`](leptos_router::RouterIntegrationContext)
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -762,8 +751,7 @@ where
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`](leptos_meta::ServerMetaContext)
|
||||
/// - [`RouterIntegrationContext`](leptos_router::RouterIntegrationContext)
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -830,8 +818,7 @@ where
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`](leptos_meta::ServerMetaContext)
|
||||
/// - [`RouterIntegrationContext`](leptos_router::RouterIntegrationContext)
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -948,13 +935,7 @@ fn provide_contexts(
|
||||
|
||||
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], asynchronously rendering an HTML page after all
|
||||
/// `async` [Resource](leptos::Resource)s have loaded.
|
||||
///
|
||||
/// The provides a [MetaContext] and a [RouterIntegrationContext] to app’s context before
|
||||
/// rendering it, and includes any meta tags injected using [leptos_meta].
|
||||
///
|
||||
/// The HTML stream is rendered using [render_to_string_async], and includes everything described in
|
||||
/// the documentation for that function.
|
||||
/// `async` resources have loaded.
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
@@ -994,8 +975,7 @@ fn provide_contexts(
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`](leptos_meta::ServerMetaContext)
|
||||
/// - [`RouterIntegrationContext`](leptos_router::RouterIntegrationContext)
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -1016,7 +996,7 @@ where
|
||||
|
||||
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], asynchronously rendering an HTML page after all
|
||||
/// `async` [Resource](leptos::Resource)s have loaded.
|
||||
/// `async` resources have loaded.
|
||||
///
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other infro from Axum or network
|
||||
/// layers above Leptos itself. To use it, you'll need to write your own handler function that provides
|
||||
@@ -1049,8 +1029,7 @@ where
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`](leptos_meta::ServerMetaContext)
|
||||
/// - [`RouterIntegrationContext`](leptos_router::RouterIntegrationContext)
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -1084,7 +1063,7 @@ where
|
||||
|
||||
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], asynchronously rendering an HTML page after all
|
||||
/// `async` [Resource](leptos::Resource)s have loaded.
|
||||
/// `async` resources have loaded.
|
||||
///
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other infro from Axum or network
|
||||
/// layers above Leptos itself. To use it, you'll need to write your own handler function that provides
|
||||
@@ -1117,8 +1096,7 @@ where
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [`Parts`]
|
||||
/// - [`ResponseOptions`]
|
||||
/// - [`ServerMetaContext`](leptos_meta::ServerMetaContext)
|
||||
/// - [`RouterIntegrationContext`](leptos_router::RouterIntegrationContext)
|
||||
/// - [`ServerMetaContext`]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -1207,32 +1185,6 @@ where
|
||||
generate_route_list_with_exclusions_and_ssg(app_fn, excluded_routes).0
|
||||
}
|
||||
|
||||
/// Builds all routes that have been defined using [`StaticRoute`].
|
||||
#[allow(unused)]
|
||||
pub async fn build_static_routes<IV>(
|
||||
options: &LeptosOptions,
|
||||
app_fn: impl Fn() -> IV + 'static + Send + Clone,
|
||||
routes: &[RouteListing],
|
||||
static_data_map: StaticParamsMap,
|
||||
) where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
todo!()
|
||||
/*
|
||||
let options = options.clone();
|
||||
let routes = routes.to_owned();
|
||||
spawn_task!(async move {
|
||||
leptos_router::build_static_routes(
|
||||
&options,
|
||||
app_fn,
|
||||
&routes,
|
||||
&static_data_map,
|
||||
)
|
||||
.await
|
||||
.expect("could not build static routes")
|
||||
});*/
|
||||
}
|
||||
|
||||
/// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically
|
||||
/// create routes in Axum's Router without having to use wildcard matching or fallbacks. Takes in your root app Element
|
||||
/// as an argument so it can walk you app tree. This version is tailored to generate Axum compatible paths. Adding excluded_routes
|
||||
@@ -1683,6 +1635,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,
|
||||
@@ -1692,6 +1647,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,
|
||||
@@ -1702,6 +1663,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>,
|
||||
@@ -2014,6 +1977,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,
|
||||
|
||||
@@ -40,15 +40,28 @@ pub trait ExtendResponse: Sized {
|
||||
let (owner, stream) =
|
||||
build_response(app_fn, additional_context, stream_builder);
|
||||
|
||||
let sc = owner.shared_context().unwrap();
|
||||
|
||||
let stream = stream.await.ready_chunks(32).map(|n| n.join(""));
|
||||
|
||||
let sc = owner.shared_context().unwrap();
|
||||
while let Some(pending) = sc.await_deferred() {
|
||||
pending.await;
|
||||
}
|
||||
|
||||
let mut stream =
|
||||
Box::pin(meta_context.inject_meta_context(stream).await);
|
||||
let mut stream = Box::pin(
|
||||
meta_context.inject_meta_context(stream).await.then({
|
||||
let sc = Arc::clone(&sc);
|
||||
move |chunk| {
|
||||
let sc = Arc::clone(&sc);
|
||||
async move {
|
||||
while let Some(pending) = sc.await_deferred() {
|
||||
pending.await;
|
||||
}
|
||||
chunk
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
// wait for the first chunk of the stream, then set the status and headers
|
||||
let first_chunk = stream.next().await.unwrap_or_default();
|
||||
|
||||
@@ -30,7 +30,7 @@ reactive_graph = { workspace = true, features = ["serde"] }
|
||||
rustc-hash = "2.0"
|
||||
tachys = { workspace = true, features = ["reactive_graph", "reactive_stores", "oco"] }
|
||||
thiserror = "2.0"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
typed-builder = "0.20.0"
|
||||
typed-builder-macro = "0.20.0"
|
||||
serde = "1.0"
|
||||
@@ -45,7 +45,7 @@ web-sys = { version = "0.3.72", features = [
|
||||
"ShadowRootInit",
|
||||
"ShadowRootMode",
|
||||
] }
|
||||
wasm-bindgen = "0.2.95"
|
||||
wasm-bindgen = "0.2.97"
|
||||
serde_qs = "0.13.0"
|
||||
slotmap = "1.0"
|
||||
futures = "0.3.31"
|
||||
@@ -86,7 +86,7 @@ tracing = [
|
||||
]
|
||||
nonce = ["base64", "rand"]
|
||||
spin = ["leptos-spin-macro"]
|
||||
experimental-islands = ["leptos_macro/experimental-islands", "dep:serde_json"]
|
||||
islands = ["leptos_macro/islands", "dep:serde_json"]
|
||||
trace-component-props = [
|
||||
"leptos_macro/trace-component-props",
|
||||
"leptos_dom/trace-component-props"
|
||||
@@ -104,7 +104,7 @@ denylist = [
|
||||
"rkyv", # was causing clippy issues on nightly
|
||||
"trace-component-props",
|
||||
"spin",
|
||||
"experimental-islands",
|
||||
"islands",
|
||||
]
|
||||
skip_feature_sets = [
|
||||
["csr", "ssr"],
|
||||
@@ -121,3 +121,6 @@ skip_feature_sets = [
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
150
leptos/src/attribute_interceptor.rs
Normal file
150
leptos/src/attribute_interceptor.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
use crate::attr::{
|
||||
any_attribute::{AnyAttribute, IntoAnyAttribute},
|
||||
Attribute, NextAttribute,
|
||||
};
|
||||
use leptos::prelude::*;
|
||||
|
||||
/// Function stored to build/rebuild the wrapped children when attributes are added.
|
||||
type ChildBuilder<T> = dyn Fn(AnyAttribute) -> T + Send + Sync + 'static;
|
||||
|
||||
/// Intercepts attributes passed to your component, allowing passing them to any element.
|
||||
///
|
||||
/// By default, Leptos passes any attributes passed to your component (e.g. `<MyComponent
|
||||
/// attr:class="some-class"/>`) to the top-level element in the view returned by your component.
|
||||
/// [`AttributeInterceptor`] allows you to intercept this behavior and pass it onto any element in
|
||||
/// your component instead.
|
||||
///
|
||||
/// Must be the top level element in your component's view.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// Any attributes passed to MyComponent will be passed to the #inner element.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::prelude::*;
|
||||
/// use leptos::attribute_interceptor::AttributeInterceptor;
|
||||
///
|
||||
/// #[component]
|
||||
/// pub fn MyComponent() -> impl IntoView {
|
||||
/// view! {
|
||||
/// <AttributeInterceptor let:attrs>
|
||||
/// <div id="wrapper">
|
||||
/// <div id="inner" {..attrs} />
|
||||
/// </div>
|
||||
/// </AttributeInterceptor>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[component]
|
||||
pub fn AttributeInterceptor<Chil, T>(
|
||||
/// The elements that will be rendered, with the attributes this component received as a
|
||||
/// parameter.
|
||||
children: Chil,
|
||||
) -> impl IntoView
|
||||
where
|
||||
Chil: Fn(AnyAttribute) -> T + Send + Sync + 'static,
|
||||
T: IntoView,
|
||||
{
|
||||
AttributeInterceptorInner::new(children)
|
||||
}
|
||||
|
||||
/// Wrapper to intercept attributes passed to a component so you can apply them to a different
|
||||
/// element.
|
||||
struct AttributeInterceptorInner<T: IntoView, A> {
|
||||
children_builder: Box<ChildBuilder<T>>,
|
||||
children: T,
|
||||
attributes: A,
|
||||
}
|
||||
|
||||
impl<T: IntoView> AttributeInterceptorInner<T, ()> {
|
||||
/// Use this as the returned view from your component to collect the attributes that are passed
|
||||
/// to your component so you can manually handle them.
|
||||
pub fn new<F>(children: F) -> Self
|
||||
where
|
||||
F: Fn(AnyAttribute) -> T + Send + Sync + 'static,
|
||||
{
|
||||
let children_builder = Box::new(children);
|
||||
let children = children_builder(().into_any_attr());
|
||||
|
||||
Self {
|
||||
children_builder,
|
||||
children,
|
||||
attributes: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoView, A: Attribute> Render for AttributeInterceptorInner<T, A> {
|
||||
type State = <T as Render>::State;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
self.children.build()
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
self.children.rebuild(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoView, A> AddAnyAttr for AttributeInterceptorInner<T, A>
|
||||
where
|
||||
A: Attribute,
|
||||
{
|
||||
type Output<SomeNewAttr: leptos::attr::Attribute> =
|
||||
AttributeInterceptorInner<T, <<A as NextAttribute>::Output<SomeNewAttr> as Attribute>::CloneableOwned>;
|
||||
|
||||
fn add_any_attr<NewAttr: leptos::attr::Attribute>(
|
||||
self,
|
||||
attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
let attributes =
|
||||
self.attributes.add_any_attr(attr).into_cloneable_owned();
|
||||
|
||||
let children =
|
||||
(self.children_builder)(attributes.clone().into_any_attr());
|
||||
|
||||
AttributeInterceptorInner {
|
||||
children_builder: self.children_builder,
|
||||
children,
|
||||
attributes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoView, A: Attribute> RenderHtml for AttributeInterceptorInner<T, A> {
|
||||
type AsyncOutput = T::AsyncOutput;
|
||||
|
||||
const MIN_LENGTH: usize = T::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
self.children.dry_resolve()
|
||||
}
|
||||
|
||||
fn resolve(
|
||||
self,
|
||||
) -> impl std::future::Future<Output = Self::AsyncOutput> + Send {
|
||||
self.children.resolve()
|
||||
}
|
||||
|
||||
fn to_html_with_buf(
|
||||
self,
|
||||
buf: &mut String,
|
||||
position: &mut leptos::tachys::view::Position,
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
self.children
|
||||
.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &leptos::tachys::hydration::Cursor,
|
||||
position: &leptos::tachys::view::PositionState,
|
||||
) -> Self::State {
|
||||
self.children.hydrate::<FROM_SERVER>(cursor, position)
|
||||
}
|
||||
}
|
||||
@@ -31,13 +31,13 @@
|
||||
//! *Notes*:
|
||||
//! - The `render_number` prop can receive any type that implements `Fn(i32) -> String`.
|
||||
//! - Callbacks are most useful when you want optional generic props.
|
||||
//! - All callbacks implement the [`Callable`] trait, and can be invoked with `my_callback.run(input)`.
|
||||
//! - All callbacks implement the [`Callable`](leptos::callback::Callable) trait, and can be invoked with `my_callback.run(input)`.
|
||||
//! - The callback types implement [`Copy`], so they can easily be moved into and out of other closures, just like signals.
|
||||
//!
|
||||
//! # Types
|
||||
//! This modules implements 2 callback types:
|
||||
//! - [`Callback`]
|
||||
//! - [`UnsyncCallback`]
|
||||
//! - [`Callback`](leptos::callback::Callback)
|
||||
//! - [`UnsyncCallback`](leptos::callback::UnsyncCallback)
|
||||
//!
|
||||
//! Use `SyncCallback` if the function is not `Sync` and `Send`.
|
||||
|
||||
@@ -223,14 +223,14 @@ mod tests {
|
||||
#[test]
|
||||
fn clone_callback() {
|
||||
let callback = Callback::new(move |_no_clone: NoClone| NoClone {});
|
||||
let _cloned = callback.clone();
|
||||
let _cloned = callback;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone_unsync_callback() {
|
||||
let callback =
|
||||
UnsyncCallback::new(move |_no_clone: NoClone| NoClone {});
|
||||
let _cloned = callback.clone();
|
||||
let _cloned = callback;
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -85,7 +85,7 @@ type BoxedChildrenFn = Box<dyn Fn() -> AnyView + Send>;
|
||||
/// )
|
||||
/// }
|
||||
pub trait ToChildren<F> {
|
||||
/// Convert the provided type to (generally a closure) to Self (generally a "children" type,
|
||||
/// Convert the provided type (generally a closure) to Self (generally a "children" type,
|
||||
/// e.g., [Children]). See the implementations to see exactly which input types are supported
|
||||
/// and which "children" type they are converted to.
|
||||
fn to_children(f: F) -> Self;
|
||||
@@ -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
|
||||
}
|
||||
@@ -245,7 +246,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A typed equivalent to [`ChildrenMut`], which takes a generic but preserves type information to
|
||||
/// A typed equivalent to [`ChildrenFnMut`], which takes a generic but preserves type information to
|
||||
/// allow the compiler to optimize the view more effectively.
|
||||
pub struct TypedChildrenMut<T>(Box<dyn FnMut() -> View<T> + Send>);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -283,7 +285,15 @@ impl<T> Debug for TypedChildrenFn<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for TypedChildrenFn<T> {
|
||||
// Manual implementation to avoid the `T: Clone` bound.
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TypedChildrenFn<T> {
|
||||
/// Extracts the inner `children` function.
|
||||
pub fn into_inner(self) -> Arc<dyn Fn() -> View<T> + Send + Sync> {
|
||||
self.0
|
||||
}
|
||||
|
||||
@@ -44,6 +44,67 @@ use tachys::{reactive_graph::OwnedView, view::keyed::keyed};
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// For convenience, you can also choose to write template code directly in the `<For>`
|
||||
/// component, using the `let` syntax:
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::prelude::*;
|
||||
///
|
||||
/// # #[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
/// # struct Counter {
|
||||
/// # id: usize,
|
||||
/// # count: RwSignal<i32>
|
||||
/// # }
|
||||
/// #
|
||||
/// # #[component]
|
||||
/// # fn Counters() -> impl IntoView {
|
||||
/// # let (counters, set_counters) = create_signal::<Vec<Counter>>(vec![]);
|
||||
/// #
|
||||
/// view! {
|
||||
/// <div>
|
||||
/// <For
|
||||
/// each=move || counters.get()
|
||||
/// key=|counter| counter.id
|
||||
/// let(counter)
|
||||
/// >
|
||||
/// <button>"Value: " {move || counter.count.get()}</button>
|
||||
/// </For>
|
||||
/// </div>
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// The `let` syntax also supports destructuring the pattern of your data.
|
||||
/// `let((one, two))` in the case of tuples, and `let(Struct { field_one, field_two })`
|
||||
/// in the case of structs.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::prelude::*;
|
||||
///
|
||||
/// # #[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
/// # struct Counter {
|
||||
/// # id: usize,
|
||||
/// # count: RwSignal<i32>
|
||||
/// # }
|
||||
/// #
|
||||
/// # #[component]
|
||||
/// # fn Counters() -> impl IntoView {
|
||||
/// # let (counters, set_counters) = create_signal::<Vec<Counter>>(vec![]);
|
||||
/// #
|
||||
/// view! {
|
||||
/// <div>
|
||||
/// <For
|
||||
/// each=move || counters.get()
|
||||
/// key=|counter| counter.id
|
||||
/// let(Counter { id, count })
|
||||
/// >
|
||||
/// <button>"Value: " {move || count.get()}</button>
|
||||
/// </For>
|
||||
/// </div>
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||
#[component]
|
||||
pub fn For<IF, I, T, EF, N, KF, K>(
|
||||
|
||||
@@ -72,9 +72,7 @@ use web_sys::{
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||
#[component]
|
||||
pub fn ActionForm<ServFn>(
|
||||
/// The action from which to build the form. This should include a URL, which can be generated
|
||||
/// by default using [`create_server_action`](leptos_server::create_server_action) or added
|
||||
/// manually using [`using_server_fn`](leptos_server::Action::using_server_fn).
|
||||
/// The action from which to build the form.
|
||||
action: ServerAction<ServFn>,
|
||||
/// A [`NodeRef`] in which the `<form>` element should be stored.
|
||||
#[prop(optional)]
|
||||
@@ -149,9 +147,7 @@ where
|
||||
/// progressively enhanced to use client-side routing.
|
||||
#[component]
|
||||
pub fn MultiActionForm<ServFn>(
|
||||
/// The action from which to build the form. This should include a URL, which can be generated
|
||||
/// by default using [create_server_action](leptos_server::create_server_action) or added
|
||||
/// manually using [leptos_server::Action::using_server_fn].
|
||||
/// The action from which to build the form.
|
||||
action: ServerMultiAction<ServFn>,
|
||||
/// A [`NodeRef`] in which the `<form>` element should be stored.
|
||||
#[prop(optional)]
|
||||
@@ -251,12 +247,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),
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(function (root, pkg_path, output_name, wasm_output_name) {
|
||||
import(`${root}/${pkg_path}/${output_name}.js`)
|
||||
.then(mod => {
|
||||
mod.default(`${root}/${pkg_path}/${wasm_output_name}.wasm`).then(() => {
|
||||
mod.default({module_or_path: `${root}/${pkg_path}/${wasm_output_name}.wasm`}).then(() => {
|
||||
mod.hydrate();
|
||||
});
|
||||
})
|
||||
|
||||
@@ -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 `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
|
||||
@@ -38,7 +39,7 @@
|
||||
//! server actions, forms, and server-sent events (SSE).
|
||||
//! - **[`todomvc`]** shows the basics of building an isomorphic web app. Both the server and the client import the same app code.
|
||||
//! The server renders the app directly to an HTML string, and the client hydrates that HTML to make it interactive.
|
||||
//! You might also want to see how we use [`Effect::new`](leptos::prelude::Effect::new) to
|
||||
//! You might also want to see how we use [`Effect::new`](leptos::prelude::Effect) to
|
||||
//! [serialize JSON to `localStorage`](https://github.com/leptos-rs/leptos/blob/20af4928b2fffe017408d3f4e7330db22cf68277/examples/todomvc/src/lib.rs#L191-L209)
|
||||
//! and [reactively call DOM methods](https://github.com/leptos-rs/leptos/blob/16f084a71268ac325fbc4a5e50c260df185eadb6/examples/todomvc/src/lib.rs#L292-L296)
|
||||
//! on [references to elements](https://github.com/leptos-rs/leptos/blob/20af4928b2fffe017408d3f4e7330db22cf68277/examples/todomvc/src/lib.rs#L228).
|
||||
@@ -77,7 +78,7 @@
|
||||
//! + `async` interop: [`Resource`](leptos::prelude::Resource) for loading data using `async` functions
|
||||
//! and [`Action`](leptos::prelude::Action) to mutate data or imperatively call `async` functions.
|
||||
//! + reactions: [`Effect`](leptos::prelude::Effect) and [`RenderEffect`](leptos::prelude::RenderEffect).
|
||||
//! - **Templating/Views**: the [`view`] macro and [`IntoView`](leptos::IntoView) trait.
|
||||
//! - **Templating/Views**: the [`view`] macro and [`IntoView`] trait.
|
||||
//! - **Routing**: the [`leptos_router`](https://docs.rs/leptos_router/latest/leptos_router/) crate
|
||||
//! - **Server Functions**: the [`server`](macro@leptos::prelude::server) macro and [`ServerAction`](leptos::prelude::ServerAction).
|
||||
//!
|
||||
@@ -191,6 +192,9 @@ pub mod callback;
|
||||
/// Types that can be passed as the `children` prop of a component.
|
||||
pub mod children;
|
||||
|
||||
/// Wrapper for intercepting component attributes.
|
||||
pub mod attribute_interceptor;
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Traits used to implement component constructors.
|
||||
pub mod component;
|
||||
@@ -287,8 +291,9 @@ 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;
|
||||
pub use any_spawner::{self, CustomExecutor, Executor};
|
||||
use std::future::Future;
|
||||
|
||||
/// Spawns a thread-safe [`Future`].
|
||||
@@ -316,10 +321,10 @@ pub mod task {
|
||||
}
|
||||
|
||||
// these reexports are used in islands
|
||||
#[cfg(feature = "experimental-islands")]
|
||||
#[cfg(feature = "islands")]
|
||||
#[doc(hidden)]
|
||||
pub use serde;
|
||||
#[cfg(feature = "experimental-islands")]
|
||||
#[cfg(feature = "islands")]
|
||||
#[doc(hidden)]
|
||||
pub use serde_json;
|
||||
#[cfg(feature = "tracing")]
|
||||
|
||||
@@ -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
|
||||
|
||||
2851
leptos/tests/test_examples/suspense-tests/Cargo.lock
generated
Normal file
2851
leptos/tests/test_examples/suspense-tests/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -21,8 +21,11 @@ typed-builder = "0.20.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.41", features = ["rt", "macros"] }
|
||||
tempfile = "3.13"
|
||||
tempfile = "3.14"
|
||||
temp-env = { version = "0.3.6", features = ["async_closure"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
@@ -30,14 +30,14 @@ fn ws_from_str_test() {
|
||||
|
||||
#[test]
|
||||
fn env_w_default_test() {
|
||||
_ = temp_env::with_var("LEPTOS_CONFIG_ENV_TEST", Some("custom"), || {
|
||||
temp_env::with_var("LEPTOS_CONFIG_ENV_TEST", Some("custom"), || {
|
||||
assert_eq!(
|
||||
env_w_default("LEPTOS_CONFIG_ENV_TEST", "default").unwrap(),
|
||||
String::from("custom")
|
||||
);
|
||||
});
|
||||
|
||||
_ = temp_env::with_var_unset("LEPTOS_CONFIG_ENV_TEST", || {
|
||||
temp_env::with_var_unset("LEPTOS_CONFIG_ENV_TEST", || {
|
||||
assert_eq!(
|
||||
env_w_default("LEPTOS_CONFIG_ENV_TEST", "default").unwrap(),
|
||||
String::from("default")
|
||||
@@ -47,14 +47,14 @@ fn env_w_default_test() {
|
||||
|
||||
#[test]
|
||||
fn env_wo_default_test() {
|
||||
_ = temp_env::with_var("LEPTOS_CONFIG_ENV_TEST", Some("custom"), || {
|
||||
temp_env::with_var("LEPTOS_CONFIG_ENV_TEST", Some("custom"), || {
|
||||
assert_eq!(
|
||||
env_wo_default("LEPTOS_CONFIG_ENV_TEST").unwrap(),
|
||||
Some(String::from("custom"))
|
||||
);
|
||||
});
|
||||
|
||||
_ = temp_env::with_var_unset("LEPTOS_CONFIG_ENV_TEST", || {
|
||||
temp_env::with_var_unset("LEPTOS_CONFIG_ENV_TEST", || {
|
||||
assert_eq!(env_wo_default("LEPTOS_CONFIG_ENV_TEST").unwrap(), None);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ edition.workspace = true
|
||||
tachys = { workspace = true }
|
||||
reactive_graph = { workspace = true }
|
||||
or_poisoned = { workspace = true }
|
||||
js-sys = "0.3.72"
|
||||
js-sys = "0.3.74"
|
||||
send_wrapper = "0.6.0"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
wasm-bindgen = "0.2.95"
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
wasm-bindgen = "0.2.97"
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
serde = { version = "1.0", optional = true }
|
||||
|
||||
@@ -37,3 +37,6 @@ rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["tracing"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_macro"
|
||||
version = "0.7.0-rc1"
|
||||
version = "0.7.2"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
@@ -13,7 +13,7 @@ edition.workspace = true
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
attribute-derive = { version = "0.10.2", features = ["syn-full"] }
|
||||
attribute-derive = { version = "0.10.3", features = ["syn-full"] }
|
||||
cfg-if = "1.0"
|
||||
html-escape = "0.2.13"
|
||||
itertools = "0.13.0"
|
||||
@@ -27,7 +27,7 @@ leptos_hot_reload = { workspace = true }
|
||||
server_fn_macro = { workspace = true }
|
||||
convert_case = "0.6.0"
|
||||
uuid = { version = "1.11", features = ["v4"] }
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
log = "0.4.22"
|
||||
@@ -44,7 +44,7 @@ hydrate = []
|
||||
ssr = ["server_fn_macro/ssr", "leptos/ssr"]
|
||||
nightly = ["server_fn_macro/nightly"]
|
||||
tracing = ["dep:tracing"]
|
||||
experimental-islands = []
|
||||
islands = []
|
||||
trace-component-props = []
|
||||
actix = ["server_fn_macro/actix"]
|
||||
axum = ["server_fn_macro/axum"]
|
||||
@@ -83,4 +83,7 @@ skip_feature_sets = [
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(erase_components)'] }
|
||||
unexpected_cfgs = { level = "warn", check-cfg = [
|
||||
'cfg(leptos_debuginfo)',
|
||||
'cfg(erase_components)',
|
||||
] }
|
||||
|
||||
@@ -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;
|
||||
@@ -269,8 +272,8 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
||||
view_macro_impl(tokens, false)
|
||||
}
|
||||
|
||||
/// The `template` macro behaves like [`view`], except that it wraps the entire tree in a
|
||||
/// [`ViewTemplate`](leptos::prelude::ViewTemplate). This optimizes creation speed by rendering
|
||||
/// The `template` macro behaves like [`view`](view!), except that it wraps the entire tree in a
|
||||
/// [`ViewTemplate`](https://docs.rs/leptos/0.7.0-gamma3/leptos/prelude/struct.ViewTemplate.html). This optimizes creation speed by rendering
|
||||
/// most of the view into a `<template>` tag with HTML rendered at compile time, then hydrating it.
|
||||
/// In exchange, there is a small binary size overhead.
|
||||
#[proc_macro_error2::proc_macro_error]
|
||||
@@ -363,7 +366,7 @@ fn normalized_call_site(site: proc_macro::Span) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// This behaves like the [`view`] macro, but loads the view from an external file instead of
|
||||
/// This behaves like the [`view`](view!) macro, but loads the view from an external file instead of
|
||||
/// parsing it inline.
|
||||
///
|
||||
/// This is designed to allow editing views in a separate file, if this improves a user's workflow.
|
||||
@@ -556,10 +559,10 @@ pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
}
|
||||
|
||||
/// Defines a component as an interactive island when you are using the
|
||||
/// `experimental-islands` feature of Leptos. Apart from the macro name,
|
||||
/// `islands` feature of Leptos. Apart from the macro name,
|
||||
/// the API is the same as the [`component`](macro@component) macro.
|
||||
///
|
||||
/// When you activate the `experimental-islands` feature, every `#[component]`
|
||||
/// When you activate the `islands` feature, every `#[component]`
|
||||
/// is server-only by default. This "default to server" behavior is important:
|
||||
/// you opt into shipping code to the client, rather than opting out. You can
|
||||
/// opt into client-side interactivity for any given component by changing from
|
||||
@@ -636,7 +639,7 @@ pub fn island(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
abort!(
|
||||
transparent,
|
||||
"only `transparent` is supported";
|
||||
help = "try `#[component(transparent)]` or `#[component]`"
|
||||
help = "try `#[island(transparent)]` or `#[island]`"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -108,9 +108,12 @@ pub(crate) fn component_to_tokens(
|
||||
let KeyedAttributeValue::Binding(binding) = &attr.possible_value
|
||||
else {
|
||||
if let Some(ident) = attr.key.to_string().strip_prefix("let:") {
|
||||
let ident1 =
|
||||
format_ident!("{ident}", span = attr.key.span());
|
||||
return Some(quote! { #ident1 });
|
||||
let span = match &attr.key {
|
||||
NodeName::Punctuated(path) => path[1].span(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let ident1 = format_ident!("{ident}", span = span);
|
||||
return Some(quote_spanned! { span => #ident1 });
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -23,8 +23,11 @@ use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
};
|
||||
use syn::{
|
||||
spanned::Spanned, Expr, Expr::Tuple, ExprArray, ExprLit, ExprRange, Lit,
|
||||
LitStr, RangeLimits, Stmt,
|
||||
punctuated::Pair::{End, Punctuated},
|
||||
spanned::Spanned,
|
||||
Expr,
|
||||
Expr::Tuple,
|
||||
ExprArray, ExprLit, ExprRange, Lit, LitStr, RangeLimits, Stmt,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
@@ -194,7 +197,7 @@ enum InertElementBuilder<'a> {
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> ToTokens for InertElementBuilder<'a> {
|
||||
impl ToTokens for InertElementBuilder<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
InertElementBuilder::GlobalClass { strs, .. } => {
|
||||
@@ -216,7 +219,7 @@ enum GlobalClassItem<'a> {
|
||||
String(String),
|
||||
}
|
||||
|
||||
impl<'a> ToTokens for GlobalClassItem<'a> {
|
||||
impl ToTokens for GlobalClassItem<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let addl_tokens = match self {
|
||||
GlobalClassItem::Global(v) => v.to_token_stream(),
|
||||
@@ -649,6 +652,18 @@ pub(crate) fn element_to_tokens(
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let NodeAttribute::Attribute(a) = a {
|
||||
if let Some(Tuple(_)) = a.value() {
|
||||
return Ordering::Greater;
|
||||
}
|
||||
}
|
||||
if let NodeAttribute::Attribute(b) = b {
|
||||
if let Some(Tuple(_)) = b.value() {
|
||||
return Ordering::Less;
|
||||
}
|
||||
}
|
||||
|
||||
match (key_a.as_deref(), key_b.as_deref()) {
|
||||
(Some("class"), Some("class")) | (Some("style"), Some("style")) => {
|
||||
Ordering::Equal
|
||||
@@ -752,7 +767,7 @@ pub(crate) fn element_to_tokens(
|
||||
let name = node.name().to_string();
|
||||
// link custom ident to name span for IDE docs
|
||||
let custom = Ident::new("custom", name.span());
|
||||
quote! { ::leptos::tachys::html::element::#custom(#name) }
|
||||
quote_spanned! { node.name().span() => ::leptos::tachys::html::element::#custom(#name) }
|
||||
} else if is_svg_element(&tag) {
|
||||
parent_type = TagType::Svg;
|
||||
let name = if tag == "use" || tag == "use_" {
|
||||
@@ -760,33 +775,33 @@ pub(crate) fn element_to_tokens(
|
||||
} else {
|
||||
name.to_token_stream()
|
||||
};
|
||||
quote! { ::leptos::tachys::svg::#name() }
|
||||
quote_spanned! { node.name().span() => ::leptos::tachys::svg::#name() }
|
||||
} else if is_math_ml_element(&tag) {
|
||||
parent_type = TagType::Math;
|
||||
quote! { ::leptos::tachys::mathml::#name() }
|
||||
quote_spanned! { node.name().span() => ::leptos::tachys::mathml::#name() }
|
||||
} else if is_ambiguous_element(&tag) {
|
||||
match parent_type {
|
||||
TagType::Unknown => {
|
||||
// We decided this warning was too aggressive, but I'll leave it here in case we want it later
|
||||
/* proc_macro_error2::emit_warning!(name.span(), "The view macro is assuming this is an HTML element, \
|
||||
but it is ambiguous; if it is an SVG or MathML element, prefix with svg:: or math::"); */
|
||||
quote! {
|
||||
quote_spanned! { node.name().span() =>
|
||||
::leptos::tachys::html::element::#name()
|
||||
}
|
||||
}
|
||||
TagType::Html => {
|
||||
quote! { ::leptos::tachys::html::element::#name() }
|
||||
quote_spanned! { node.name().span() => ::leptos::tachys::html::element::#name() }
|
||||
}
|
||||
TagType::Svg => {
|
||||
quote! { ::leptos::tachys::svg::#name() }
|
||||
quote_spanned! { node.name().span() => ::leptos::tachys::svg::#name() }
|
||||
}
|
||||
TagType::Math => {
|
||||
quote! { ::leptos::tachys::math::#name() }
|
||||
quote_spanned! { node.name().span() => ::leptos::tachys::math::#name() }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parent_type = TagType::Html;
|
||||
quote! { ::leptos::tachys::html::element::#name() }
|
||||
quote_spanned! { name.span() => ::leptos::tachys::html::element::#name() }
|
||||
};
|
||||
|
||||
/* TODO restore this
|
||||
@@ -998,10 +1013,14 @@ pub(crate) fn attribute_absolute(
|
||||
) -> Option<TokenStream> {
|
||||
let key = node.key.to_string();
|
||||
let contains_dash = key.contains('-');
|
||||
let attr_aira = key.starts_with("attr:aria-");
|
||||
let attr_colon = key.starts_with("attr:")
|
||||
|| key.starts_with("style:")
|
||||
|| key.starts_with("class:")
|
||||
|| key.starts_with("prop:")
|
||||
|| key.starts_with("use:");
|
||||
// anything that follows the x:y pattern
|
||||
match &node.key {
|
||||
NodeName::Punctuated(parts) if !contains_dash || attr_aira => {
|
||||
NodeName::Punctuated(parts) if !contains_dash || attr_colon => {
|
||||
if parts.len() >= 2 {
|
||||
let id = &parts[0];
|
||||
match id {
|
||||
@@ -1010,7 +1029,8 @@ pub(crate) fn attribute_absolute(
|
||||
if id == "let" || id == "clone" {
|
||||
None
|
||||
} else if id == "attr" {
|
||||
let value = attribute_value(node, true);
|
||||
let value = attribute_value(node, true);
|
||||
let multipart = parts.len() > 2;
|
||||
let key = &parts[1];
|
||||
let key_name = key.to_string();
|
||||
if key_name == "class" || key_name == "style" {
|
||||
@@ -1026,6 +1046,15 @@ pub(crate) fn attribute_absolute(
|
||||
Some(
|
||||
quote! { ::leptos::tachys::html::attribute::#key(#value) },
|
||||
)
|
||||
} else if multipart {
|
||||
// e.g., attr:data-foo="bar"
|
||||
let key_name = parts.pairs().skip(1).map(|p| match p {
|
||||
Punctuated(n, p) => format!("{n}{p}"),
|
||||
End(n) => n.to_string(),
|
||||
}).collect::<String>();
|
||||
Some(
|
||||
quote! { ::leptos::tachys::html::attribute::custom::custom_attribute(#key_name, #value) },
|
||||
)
|
||||
} else {
|
||||
Some(
|
||||
quote! { ::leptos::tachys::html::attribute::#key(#value) },
|
||||
@@ -1693,7 +1722,7 @@ fn tuple_name(name: &str, node: &KeyedAttribute) -> TupleName {
|
||||
TupleName::None
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum TupleName {
|
||||
None,
|
||||
Str(String),
|
||||
|
||||
@@ -15,7 +15,7 @@ codee = { version = "0.2.0", features = ["json_serde"] }
|
||||
hydration_context = { workspace = true }
|
||||
reactive_graph = { workspace = true, features = ["hydration"] }
|
||||
server_fn = { workspace = true }
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
futures = "0.3.31"
|
||||
|
||||
any_spawner = { workspace = true }
|
||||
@@ -25,8 +25,8 @@ send_wrapper = "0.6"
|
||||
|
||||
# serialization formats
|
||||
serde = { version = "1.0" }
|
||||
js-sys = { version = "0.3.72", optional = true }
|
||||
wasm-bindgen = { version = "0.2.95", optional = true }
|
||||
js-sys = { version = "0.3.74", optional = true }
|
||||
wasm-bindgen = { version = "0.2.97", optional = true }
|
||||
serde_json = { version = "1.0" }
|
||||
|
||||
[features]
|
||||
@@ -44,3 +44,6 @@ denylist = ["tracing"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
@@ -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,22 +25,25 @@ 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,
|
||||
S::Output: 'static,
|
||||
{
|
||||
inner: ArcAction<S, Result<S::Output, ServerFnError<S::Error>>>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'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| {
|
||||
@@ -56,7 +65,7 @@ where
|
||||
inner: ArcAction::new_with_value(err, |input: &S| {
|
||||
S::run_on_client(input.clone())
|
||||
}),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -82,7 +91,7 @@ where
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
}
|
||||
}
|
||||
@@ -105,24 +114,25 @@ where
|
||||
S::Output: 'static,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`Action`] that can be used to call a server function.
|
||||
pub struct ServerAction<S>
|
||||
where
|
||||
S: ServerFn + 'static,
|
||||
S::Output: 'static,
|
||||
{
|
||||
inner: Action<S, Result<S::Output, ServerFnError<S::Error>>>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'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)
|
||||
@@ -142,7 +153,7 @@ where
|
||||
inner: Action::new_with_value(err, |input: &S| {
|
||||
S::run_on_client(input.clone())
|
||||
}),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -206,11 +217,11 @@ where
|
||||
S::Output: 'static,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -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,9 +17,10 @@ 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)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -27,13 +28,17 @@ impl<T> Clone for ArcLocalResource<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
data: self.data.clone(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -65,7 +70,7 @@ impl<T> ArcLocalResource<T> {
|
||||
let fut = fetcher();
|
||||
SendWrapper::new(async move { SendWrapper::new(fut.await) })
|
||||
}),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -99,11 +104,11 @@ where
|
||||
|
||||
impl<T> DefinedAt for ArcLocalResource<T> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -192,9 +197,10 @@ 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)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -243,7 +253,7 @@ impl<T> LocalResource<T> {
|
||||
SendWrapper::new(async move { SendWrapper::new(fut.await) })
|
||||
})
|
||||
},
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -277,11 +287,11 @@ where
|
||||
|
||||
impl<T> DefinedAt for LocalResource<T> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -388,7 +398,7 @@ impl<T: 'static> From<ArcLocalResource<T>> for LocalResource<T> {
|
||||
fn from(arc: ArcLocalResource<T>) -> Self {
|
||||
Self {
|
||||
data: arc.data.into(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: arc.defined_at,
|
||||
}
|
||||
}
|
||||
@@ -398,7 +408,7 @@ impl<T: 'static> From<LocalResource<T>> for ArcLocalResource<T> {
|
||||
fn from(local: LocalResource<T>) -> Self {
|
||||
Self {
|
||||
data: local.data.into(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: local.defined_at,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@ 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,
|
||||
S::Output: 'static,
|
||||
{
|
||||
inner: ArcMultiAction<S, Result<S::Output, ServerFnError<S::Error>>>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -21,13 +22,14 @@ 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 {
|
||||
inner: ArcMultiAction::new(|input: &S| {
|
||||
S::run_on_client(input.clone())
|
||||
}),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -53,7 +55,7 @@ where
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
}
|
||||
}
|
||||
@@ -76,24 +78,25 @@ where
|
||||
S::Output: 'static,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`MultiAction`] that can be used to call a server function.
|
||||
pub struct ServerMultiAction<S>
|
||||
where
|
||||
S: ServerFn + 'static,
|
||||
S::Output: 'static,
|
||||
{
|
||||
inner: MultiAction<S, Result<S::Output, ServerFnError<S::Error>>>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -114,12 +117,13 @@ 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| {
|
||||
S::run_on_client(input.clone())
|
||||
}),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -172,11 +176,11 @@ where
|
||||
S::Output: 'static,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -51,7 +60,7 @@ pub struct ArcOnceResource<T, Ser = JsonSerdeCodec> {
|
||||
suspenses: Arc<RwLock<Vec<SuspenseContext>>>,
|
||||
loading: Arc<AtomicBool>,
|
||||
ser: PhantomData<fn() -> Ser>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -64,7 +73,7 @@ impl<T, Ser> Clone for ArcOnceResource<T, Ser> {
|
||||
suspenses: self.suspenses.clone(),
|
||||
loading: self.loading.clone(),
|
||||
ser: self.ser,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
@@ -125,7 +140,7 @@ where
|
||||
wakers,
|
||||
suspenses,
|
||||
ser: PhantomData,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
};
|
||||
|
||||
@@ -168,11 +183,11 @@ impl<T, Ser> ArcOnceResource<T, Ser> {
|
||||
|
||||
impl<T, Ser> DefinedAt for ArcOnceResource<T, Ser> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
@@ -238,7 +253,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Future`] that is ready when an [`ArcAsyncDerived`] is finished loading or reloading,
|
||||
/// A [`Future`] that is ready when an
|
||||
/// [`ArcAsyncDerived`](reactive_graph::computed::ArcAsyncDerived) is finished loading or reloading,
|
||||
/// and contains its value. `.await`ing this clones the value `T`.
|
||||
pub struct OnceResourceFuture<T> {
|
||||
source: AnySource,
|
||||
@@ -256,7 +272,7 @@ where
|
||||
|
||||
#[track_caller]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
let _guard = SpecialNonReactiveZone::enter();
|
||||
let waker = cx.waker();
|
||||
self.source.track();
|
||||
@@ -287,11 +303,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 +329,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 +337,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 +360,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 +369,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 +394,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 +402,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 +425,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 +434,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 +460,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,10 +479,19 @@ 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>>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -452,18 +513,24 @@ 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,
|
||||
blocking: bool,
|
||||
) -> Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
let defined_at = Location::caller();
|
||||
Self {
|
||||
inner: ArenaItem::new(ArcOnceResource::new_with_options(
|
||||
fut, blocking,
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at,
|
||||
}
|
||||
}
|
||||
@@ -484,11 +551,11 @@ where
|
||||
|
||||
impl<T, Ser> DefinedAt for OnceResource<T, Ser> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
@@ -567,11 +634,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 +660,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 +668,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 +691,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 +700,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 +725,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 +733,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 +756,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 +765,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 +791,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,11 +61,20 @@ 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>,
|
||||
data: ArcAsyncDerived<T>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -70,7 +82,7 @@ impl<T, Ser> Debug for ArcResource<T, Ser> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut d = f.debug_struct("ArcResource");
|
||||
d.field("ser", &self.ser).field("data", &self.data);
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
d.field("defined_at", self.defined_at);
|
||||
d.finish_non_exhaustive()
|
||||
}
|
||||
@@ -86,7 +98,7 @@ where
|
||||
ser: PhantomData,
|
||||
data: arc_resource.data.into(),
|
||||
refetch: arc_resource.refetch.into(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -102,7 +114,7 @@ where
|
||||
ser: PhantomData,
|
||||
data: resource.data.into(),
|
||||
refetch: resource.refetch.into(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -110,11 +122,11 @@ where
|
||||
|
||||
impl<T, Ser> DefinedAt for ArcResource<T, Ser> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -127,7 +139,7 @@ impl<T, Ser> Clone for ArcResource<T, Ser> {
|
||||
ser: self.ser,
|
||||
refetch: self.refetch.clone(),
|
||||
data: self.data.clone(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
@@ -273,11 +300,13 @@ where
|
||||
ser: PhantomData,
|
||||
data,
|
||||
refetch,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
@@ -602,7 +781,7 @@ where
|
||||
ser: PhantomData<Ser>,
|
||||
data: AsyncDerived<T>,
|
||||
refetch: RwSignal<usize>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -613,7 +792,7 @@ where
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut d = f.debug_struct("ArcResource");
|
||||
d.field("ser", &self.ser).field("data", &self.data);
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
d.field("defined_at", self.defined_at);
|
||||
d.finish_non_exhaustive()
|
||||
}
|
||||
@@ -624,11 +803,11 @@ where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -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,
|
||||
@@ -932,11 +1270,13 @@ where
|
||||
ser: PhantomData,
|
||||
data: data.into(),
|
||||
refetch: refetch.into(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.7.0-rc1"
|
||||
version = "0.7.2"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
@@ -14,8 +14,8 @@ once_cell = "1.20"
|
||||
or_poisoned = { workspace = true }
|
||||
indexmap = "2.6"
|
||||
send_wrapper = "0.6.0"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
wasm-bindgen = "0.2.95"
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
wasm-bindgen = "0.2.97"
|
||||
futures = "0.3.31"
|
||||
|
||||
[dependencies.web-sys]
|
||||
@@ -32,3 +32,6 @@ rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["tracing"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
//! using the [`Leptos`](https://github.com/leptos-rs/leptos) web framework.
|
||||
//!
|
||||
//! Document metadata is updated automatically when running in the browser. For server-side
|
||||
//! rendering, after the component tree is rendered to HTML, [`MetaContext::dehydrate`] can generate
|
||||
//! HTML that should be injected into the `<head>` of the HTML document being rendered.
|
||||
//! rendering, after the component tree is rendered to HTML, [`ServerMetaContextOutput::inject_meta_context`] will inject meta tags into a stream of HTML inside the `<head>`.
|
||||
//!
|
||||
//! ```
|
||||
//! use leptos::prelude::*;
|
||||
@@ -38,14 +37,10 @@
|
||||
//! }
|
||||
//! ```
|
||||
//! # Feature Flags
|
||||
//! - `csr` Client-side rendering: Generate DOM nodes in the browser
|
||||
//! - `ssr` Server-side rendering: Generate an HTML string (typically on the server)
|
||||
//! - `hydrate` Hydration: use this to add interactivity to an SSRed Leptos app
|
||||
//! - `stable` By default, Leptos requires `nightly` Rust, which is what allows the ergonomics
|
||||
//! of calling signals as functions. Enable this feature to support `stable` Rust.
|
||||
//! - `tracing` Adds integration with the `tracing` crate.
|
||||
//!
|
||||
//! **Important Note:** You must enable one of `csr`, `hydrate`, or `ssr` to tell Leptos
|
||||
//! which mode your app is operating in.
|
||||
//! **Important Note:** If you’re using server-side rendering, you should enable `ssr`.
|
||||
|
||||
use futures::{Stream, StreamExt};
|
||||
use leptos::{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "next_tuple"
|
||||
version = "0.1.0-rc1"
|
||||
version = "0.1.0"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -70,7 +70,7 @@ pub enum Oco<'a, T: ?Sized + ToOwned + 'a> {
|
||||
Owned(<T as ToOwned>::Owned),
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + ToOwned> Oco<'a, T> {
|
||||
impl<T: ?Sized + ToOwned> Oco<'_, T> {
|
||||
/// Converts the value into an owned value.
|
||||
pub fn into_owned(self) -> <T as ToOwned>::Owned {
|
||||
match self {
|
||||
@@ -339,7 +339,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, A: ?Sized, B: ?Sized> PartialEq<Oco<'b, B>> for Oco<'a, A>
|
||||
impl<'b, A: ?Sized, B: ?Sized> PartialEq<Oco<'b, B>> for Oco<'_, A>
|
||||
where
|
||||
A: PartialEq<B>,
|
||||
A: ToOwned,
|
||||
@@ -352,7 +352,7 @@ where
|
||||
|
||||
impl<T: ?Sized + ToOwned + Eq> Eq for Oco<'_, T> {}
|
||||
|
||||
impl<'a, 'b, A: ?Sized, B: ?Sized> PartialOrd<Oco<'b, B>> for Oco<'a, A>
|
||||
impl<'b, A: ?Sized, B: ?Sized> PartialOrd<Oco<'b, B>> for Oco<'_, A>
|
||||
where
|
||||
A: PartialOrd<B>,
|
||||
A: ToOwned,
|
||||
@@ -551,7 +551,7 @@ impl_slice_eq!(['a, 'b, T: PartialEq] (where [T]: ToOwned), Oco<'a, [T]>, &'b [T
|
||||
impl_slice_eq!([T: PartialEq] (where [T]: ToOwned), Oco<'_, [T]>, Vec<T>);
|
||||
impl_slice_eq!(['a, 'b, T: PartialEq] (where [T]: ToOwned), Oco<'a, [T]>, Cow<'b, [T]>);
|
||||
|
||||
impl<'a, 'b> Add<&'b str> for Oco<'a, str> {
|
||||
impl<'b> Add<&'b str> for Oco<'_, str> {
|
||||
type Output = Oco<'static, str>;
|
||||
|
||||
fn add(self, rhs: &'b str) -> Self::Output {
|
||||
@@ -559,7 +559,7 @@ impl<'a, 'b> Add<&'b str> for Oco<'a, str> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Add<Cow<'b, str>> for Oco<'a, str> {
|
||||
impl<'b> Add<Cow<'b, str>> for Oco<'_, str> {
|
||||
type Output = Oco<'static, str>;
|
||||
|
||||
fn add(self, rhs: Cow<'b, str>) -> Self::Output {
|
||||
@@ -567,7 +567,7 @@ impl<'a, 'b> Add<Cow<'b, str>> for Oco<'a, str> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Add<Oco<'b, str>> for Oco<'a, str> {
|
||||
impl<'b> Add<Oco<'b, str>> for Oco<'_, str> {
|
||||
type Output = Oco<'static, str>;
|
||||
|
||||
fn add(self, rhs: Oco<'b, str>) -> Self::Output {
|
||||
|
||||
@@ -21,6 +21,6 @@ CREATE TABLE IF NOT EXISTS google_tokens (
|
||||
access_secret TEXT NOT NULL,
|
||||
refresh_secret TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) CONFLICT REPLACE
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ pub async fn refresh_token(email: String) -> Result<u64, ServerFnError> {
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
sqlx::query(
|
||||
"INSERT INTO google_tokens (user_id,access_secret,refresh_secret) \
|
||||
"INSERT OR REPLACE INTO google_tokens (user_id,access_secret,refresh_secret) \
|
||||
VALUES (?,?,?)",
|
||||
)
|
||||
.bind(user.id)
|
||||
|
||||
@@ -96,7 +96,7 @@ async fn main() {
|
||||
let client = oauth2::basic::BasicClient::new(
|
||||
oauth2::ClientId::new(
|
||||
std::env::var("G_AUTH_CLIENT_ID")
|
||||
.expect("G_AUTH_CLIENT Env var to be set."),
|
||||
.expect("G_AUTH_CLIENT_ID Env var to be set."),
|
||||
),
|
||||
Some(oauth2::ClientSecret::new(
|
||||
std::env::var("G_AUTH_SECRET")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_graph"
|
||||
version = "0.1.0-rc1"
|
||||
version = "0.1.1"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -19,7 +19,7 @@ rustc-hash = "2.0"
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
slotmap = "1.0"
|
||||
thiserror = "2.0"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
guardian = "1.2"
|
||||
async-lock = "3.4.0"
|
||||
send_wrapper = { version = "0.6.0", features = ["futures"] }
|
||||
@@ -47,3 +47,6 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["tracing"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
|
||||
@@ -101,7 +101,7 @@ pub struct ArcAction<I, O> {
|
||||
action_fn: Arc<
|
||||
dyn Fn(&I) -> Pin<Box<dyn Future<Output = O> + Send>> + Send + Sync,
|
||||
>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ impl<I, O> Clone for ArcAction<I, O> {
|
||||
version: self.version.clone(),
|
||||
dispatched: self.dispatched.clone(),
|
||||
action_fn: self.action_fn.clone(),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,7 @@ where
|
||||
version: Default::default(),
|
||||
dispatched: Default::default(),
|
||||
action_fn: Arc::new(move |input| Box::pin(action_fn(input))),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -370,7 +370,7 @@ where
|
||||
action_fn: Arc::new(move |input| {
|
||||
Box::pin(SendWrapper::new(action_fn(input)))
|
||||
}),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -502,11 +502,11 @@ where
|
||||
O: 'static,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -592,7 +592,7 @@ where
|
||||
/// ```
|
||||
pub struct Action<I, O, S = SyncStorage> {
|
||||
inner: ArenaItem<ArcAction<I, O>, S>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -656,7 +656,7 @@ where
|
||||
{
|
||||
Self {
|
||||
inner: ArenaItem::new(ArcAction::new(action_fn)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -681,7 +681,7 @@ where
|
||||
{
|
||||
Self {
|
||||
inner: ArenaItem::new(ArcAction::new_with_value(value, action_fn)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -715,11 +715,11 @@ where
|
||||
pub fn new_local<F, Fu>(action_fn: F) -> Self
|
||||
where
|
||||
F: Fn(&I) -> Fu + 'static,
|
||||
Fu: Future<Output = O> + Send + 'static,
|
||||
Fu: Future<Output = O> + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: ArenaItem::new_local(ArcAction::new_unsync(action_fn)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -737,7 +737,7 @@ where
|
||||
inner: ArenaItem::new_local(ArcAction::new_unsync_with_value(
|
||||
value, action_fn,
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -979,7 +979,7 @@ where
|
||||
inner: ArenaItem::new_with_storage(ArcAction::new_unsync(
|
||||
action_fn,
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -998,7 +998,7 @@ where
|
||||
inner: ArenaItem::new_with_storage(
|
||||
ArcAction::new_unsync_with_value(value, action_fn),
|
||||
),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -1006,11 +1006,11 @@ where
|
||||
|
||||
impl<I, O, S> DefinedAt for Action<I, O, S> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ use std::{fmt::Debug, future::Future, panic::Location, pin::Pin, sync::Arc};
|
||||
/// ```
|
||||
pub struct MultiAction<I, O, S = SyncStorage> {
|
||||
inner: ArenaItem<ArcMultiAction<I, O>, S>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -62,11 +62,11 @@ where
|
||||
O: 'static,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -130,7 +130,7 @@ where
|
||||
{
|
||||
Self {
|
||||
inner: ArenaItem::new_with_storage(ArcMultiAction::new(action_fn)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ pub(crate) use inner::MemoInner;
|
||||
pub use memo::*;
|
||||
pub use selector::*;
|
||||
|
||||
/// Derives a reactive slice of an [`RwSignal`](crate::signal::RwSignal).
|
||||
/// Derives a reactive slice of an [`RwSignal`].
|
||||
///
|
||||
/// Slices have the same guarantees as [`Memo`s](crate::computed::Memo):
|
||||
/// they only emit their value when it has actually been changed.
|
||||
|
||||
@@ -93,7 +93,7 @@ pub struct ArcMemo<T, S = SyncStorage>
|
||||
where
|
||||
S: Storage<T>,
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: Arc<RwLock<MemoInner<T, S>>>,
|
||||
}
|
||||
@@ -164,7 +164,7 @@ where
|
||||
RwLock::new(MemoInner::new(Arc::new(fun), subscriber))
|
||||
});
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner,
|
||||
}
|
||||
@@ -177,7 +177,7 @@ where
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
inner: Arc::clone(&self.inner),
|
||||
}
|
||||
@@ -190,11 +190,11 @@ where
|
||||
{
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -272,7 +272,7 @@ where
|
||||
AnySource(
|
||||
Arc::as_ptr(&self.inner) as usize,
|
||||
Arc::downgrade(&self.inner) as Weak<dyn Source + Send + Sync>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
self.defined_at,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ use std::{
|
||||
/// - [`IntoFuture`](std::future::Future) allows you to create a [`Future`] that resolves
|
||||
/// when this resource is done loading.
|
||||
pub struct ArcAsyncDerived<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
// the current state of this signal
|
||||
pub(crate) value: Arc<AsyncRwLock<Option<T>>>,
|
||||
@@ -117,6 +117,7 @@ pub struct ArcAsyncDerived<T> {
|
||||
pub(crate) loading: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) trait BlockingLock<T> {
|
||||
fn blocking_read_arc(self: &Arc<Self>)
|
||||
-> async_lock::RwLockReadGuardArc<T>;
|
||||
@@ -183,7 +184,7 @@ impl<T> BlockingLock<T> for AsyncRwLock<T> {
|
||||
impl<T> Clone for ArcAsyncDerived<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
value: Arc::clone(&self.value),
|
||||
wakers: Arc::clone(&self.wakers),
|
||||
@@ -196,7 +197,7 @@ impl<T> Clone for ArcAsyncDerived<T> {
|
||||
impl<T> Debug for ArcAsyncDerived<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut f = f.debug_struct("ArcAsyncDerived");
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
f.field("defined_at", &self.defined_at);
|
||||
f.finish_non_exhaustive()
|
||||
}
|
||||
@@ -205,11 +206,11 @@ impl<T> Debug for ArcAsyncDerived<T> {
|
||||
impl<T> DefinedAt for ArcAsyncDerived<T> {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -240,7 +241,7 @@ macro_rules! spawn_derived {
|
||||
let wakers = Arc::new(RwLock::new(Vec::new()));
|
||||
|
||||
let this = ArcAsyncDerived {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::clone(&value),
|
||||
wakers,
|
||||
@@ -583,19 +584,17 @@ impl<T: 'static> ReadUntracked for ArcAsyncDerived<T> {
|
||||
|
||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||
if let Some(suspense_context) = use_context::<SuspenseContext>() {
|
||||
if self.value.blocking_read().is_none() {
|
||||
let handle = suspense_context.task_id();
|
||||
let ready = SpecialNonReactiveFuture::new(self.ready());
|
||||
crate::spawn(async move {
|
||||
ready.await;
|
||||
drop(handle);
|
||||
});
|
||||
self.inner
|
||||
.write()
|
||||
.or_poisoned()
|
||||
.suspenses
|
||||
.push(suspense_context);
|
||||
}
|
||||
let handle = suspense_context.task_id();
|
||||
let ready = SpecialNonReactiveFuture::new(self.ready());
|
||||
crate::spawn(async move {
|
||||
ready.await;
|
||||
drop(handle);
|
||||
});
|
||||
self.inner
|
||||
.write()
|
||||
.or_poisoned()
|
||||
.suspenses
|
||||
.push(suspense_context);
|
||||
}
|
||||
AsyncPlain::try_new(&self.value).map(ReadGuard::new)
|
||||
}
|
||||
@@ -633,7 +632,7 @@ impl<T: 'static> ToAnySource for ArcAsyncDerived<T> {
|
||||
AnySource(
|
||||
Arc::as_ptr(&self.inner) as usize,
|
||||
Arc::downgrade(&self.inner) as Weak<dyn Source + Send + Sync>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
self.defined_at,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ use std::{future::Future, ops::DerefMut, panic::Location};
|
||||
/// - [`IntoFuture`](std::future::Future) allows you to create a [`Future`] that resolves
|
||||
/// when this resource is done loading.
|
||||
pub struct AsyncDerived<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: ArenaItem<ArcAsyncDerived<T>, S>,
|
||||
}
|
||||
@@ -99,10 +99,10 @@ where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
fn from(value: ArcAsyncDerived<T>) -> Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
let defined_at = value.defined_at;
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at,
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
@@ -127,10 +127,10 @@ where
|
||||
T: 'static,
|
||||
{
|
||||
fn from_local(value: ArcAsyncDerived<T>) -> Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
let defined_at = value.defined_at;
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at,
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
@@ -152,7 +152,7 @@ where
|
||||
Fut: Future<Output = T> + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcAsyncDerived::new(fun)),
|
||||
}
|
||||
@@ -170,7 +170,7 @@ where
|
||||
Fut: Future<Output = T> + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(
|
||||
ArcAsyncDerived::new_with_initial(initial_value, fun),
|
||||
@@ -187,7 +187,7 @@ impl<T> AsyncDerived<SendWrapper<T>> {
|
||||
Fut: Future<Output = T> + 'static,
|
||||
{
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcAsyncDerived::new_mock(fun)),
|
||||
}
|
||||
@@ -209,7 +209,7 @@ where
|
||||
Fut: Future<Output = T> + 'static,
|
||||
{
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcAsyncDerived::new_unsync(
|
||||
fun,
|
||||
@@ -230,7 +230,7 @@ where
|
||||
Fut: Future<Output = T> + 'static,
|
||||
{
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(
|
||||
ArcAsyncDerived::new_unsync_with_initial(initial_value, fun),
|
||||
@@ -278,11 +278,11 @@ where
|
||||
impl<T, S> DefinedAt for AsyncDerived<T, S> {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -67,7 +67,9 @@ where
|
||||
fn mark_check(&self) {
|
||||
{
|
||||
let mut lock = self.write().or_poisoned();
|
||||
lock.state = ReactiveNodeState::Check;
|
||||
if lock.state != ReactiveNodeState::Dirty {
|
||||
lock.state = ReactiveNodeState::Check;
|
||||
}
|
||||
}
|
||||
for sub in (&self.read().or_poisoned().subscribers).into_iter() {
|
||||
sub.mark_check();
|
||||
|
||||
@@ -100,7 +100,7 @@ pub struct Memo<T, S = SyncStorage>
|
||||
where
|
||||
S: Storage<T>,
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: ArenaItem<ArcMemo<T, S>, S>,
|
||||
}
|
||||
@@ -121,7 +121,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: ArcMemo<T, SyncStorage>) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
@@ -135,7 +135,7 @@ where
|
||||
#[track_caller]
|
||||
fn from_local(value: ArcMemo<T, LocalStorage>) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
@@ -175,7 +175,7 @@ where
|
||||
T: PartialEq,
|
||||
{
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcMemo::new(fun)),
|
||||
}
|
||||
@@ -197,7 +197,7 @@ where
|
||||
changed: fn(Option<&T>, Option<&T>) -> bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcMemo::new_with_compare(
|
||||
fun, changed,
|
||||
@@ -207,7 +207,7 @@ where
|
||||
|
||||
/// Creates a new memo by passing a function that computes the value.
|
||||
///
|
||||
/// Unlike [`ArcMemo::new`](), this receives ownership of the previous value. As a result, it
|
||||
/// Unlike [`Memo::new`](), this receives ownership of the previous value. As a result, it
|
||||
/// must return both the new value and a `bool` that is `true` if the value has changed.
|
||||
///
|
||||
/// This is lazy: the function will not be called until the memo's value is read for the first
|
||||
@@ -221,7 +221,7 @@ where
|
||||
fun: impl Fn(Option<T>) -> (T, bool) + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcMemo::new_owning(fun)),
|
||||
}
|
||||
@@ -276,11 +276,11 @@ where
|
||||
S: Storage<T>,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ impl<'a> IntoIterator for &'a SourceSet {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
#[derive(Default, Clone)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct SubscriberSet(Vec<AnySubscriber>);
|
||||
|
||||
impl SubscriberSet {
|
||||
|
||||
@@ -26,16 +26,17 @@ pub trait Source: ReactiveNode {
|
||||
pub struct AnySource(
|
||||
pub(crate) usize,
|
||||
pub(crate) Weak<dyn Source + Send + Sync>,
|
||||
#[cfg(debug_assertions)] pub(crate) &'static Location<'static>,
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) &'static Location<'static>,
|
||||
);
|
||||
|
||||
impl DefinedAt for AnySource {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.2)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ pub fn log_warning(text: Arguments) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls [`Executor::spawn`], but ensures that the task also runs in the current arena, if
|
||||
/// Calls [`Executor::spawn`](any_spawner::Executor), but ensures that the task also runs in the current arena, if
|
||||
/// multithreaded arena sandboxing is enabled.
|
||||
pub fn spawn(task: impl Future<Output = ()> + Send + 'static) {
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
@@ -133,8 +133,9 @@ pub fn spawn(task: impl Future<Output = ()> + Send + 'static) {
|
||||
any_spawner::Executor::spawn(task);
|
||||
}
|
||||
|
||||
/// Calls [`Executor::spawn_local`], but ensures that the task runs under the current reactive [`Owner`]
|
||||
/// and [`Observed`]. Does not cancel the task if the owner is cleaned up.
|
||||
/// Calls [`Executor::spawn_local`](any_spawner::Executor), but ensures that the task runs under the current reactive [`Owner`](crate::owner::Owner) and observer.
|
||||
///
|
||||
/// Does not cancel the task if the owner is cleaned up.
|
||||
pub fn spawn_local_scoped(task: impl Future<Output = ()> + 'static) {
|
||||
let task = ScopedFuture::new(task);
|
||||
|
||||
@@ -144,8 +145,9 @@ pub fn spawn_local_scoped(task: impl Future<Output = ()> + 'static) {
|
||||
any_spawner::Executor::spawn_local(task);
|
||||
}
|
||||
|
||||
/// Calls [`Executor::spawn_local`], but ensures that the task runs under the current reactive [`Owner`]
|
||||
/// and [`Observed`]. Cancels the task if the owner is cleaned up.
|
||||
/// Calls [`Executor::spawn_local`](any_spawner::Executor), but ensures that the task runs under the current reactive [`Owner`](crate::owner::Owner) and observer.
|
||||
///
|
||||
/// Cancels the task if the owner is cleaned up.
|
||||
pub fn spawn_local_scoped_with_cancellation(
|
||||
task: impl Future<Output = ()> + 'static,
|
||||
) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
signal::guards::{Plain, ReadGuard, UntrackedWriteGuard},
|
||||
traits::{DefinedAt, IsDisposed, ReadValue, WriteValue},
|
||||
traits::{DefinedAt, IntoInner, IsDisposed, ReadValue, WriteValue},
|
||||
};
|
||||
use std::{
|
||||
fmt::{Debug, Formatter},
|
||||
@@ -19,7 +19,7 @@ use std::{
|
||||
/// accessing it does not cause effects to subscribe, and
|
||||
/// updating it does not notify anything else.
|
||||
pub struct ArcStoredValue<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
value: Arc<RwLock<T>>,
|
||||
}
|
||||
@@ -27,7 +27,7 @@ pub struct ArcStoredValue<T> {
|
||||
impl<T> Clone for ArcStoredValue<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
value: Arc::clone(&self.value),
|
||||
}
|
||||
@@ -47,7 +47,7 @@ impl<T: Default> Default for ArcStoredValue<T> {
|
||||
#[track_caller]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::new(RwLock::new(T::default())),
|
||||
}
|
||||
@@ -70,11 +70,11 @@ impl<T> Hash for ArcStoredValue<T> {
|
||||
|
||||
impl<T> DefinedAt for ArcStoredValue<T> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -90,7 +90,7 @@ impl<T> ArcStoredValue<T> {
|
||||
#[track_caller]
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::new(RwLock::new(value)),
|
||||
}
|
||||
@@ -124,3 +124,12 @@ impl<T> IsDisposed for ArcStoredValue<T> {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoInner for ArcStoredValue<T> {
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
Some(Arc::into_inner(self.value)?.into_inner().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,8 +121,11 @@ pub mod sandboxed {
|
||||
}
|
||||
|
||||
impl<T> Sandboxed<T> {
|
||||
/// Wraps the given [`Future`], ensuring that any [`ArenaItem`] created while it is being
|
||||
/// polled will be associated with the same arena that was active when this was called.
|
||||
/// Wraps the given [`Future`], ensuring that any [`ArenaItem`][item] created while it is
|
||||
/// being polled will be associated with the same arena that was active when this was
|
||||
/// called.
|
||||
///
|
||||
/// [item]:[crate::owner::ArenaItem]
|
||||
pub fn new(inner: T) -> Self {
|
||||
let arena = MAP.with_borrow(|n| n.as_ref().and_then(Weak::upgrade));
|
||||
Self { arena, inner }
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::{
|
||||
arena::{Arena, NodeId},
|
||||
LocalStorage, Storage, SyncStorage, OWNER,
|
||||
};
|
||||
use crate::traits::{Dispose, IsDisposed};
|
||||
use crate::traits::{Dispose, IntoInner, IsDisposed};
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{any::Any, hash::Hash, marker::PhantomData};
|
||||
|
||||
@@ -134,3 +134,12 @@ impl<T, S> Dispose for ArenaItem<T, S> {
|
||||
Arena::with_mut(|arena| arena.remove(self.node));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: Storage<T>> IntoInner for ArenaItem<T, S> {
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
S::take(self.node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,35 +15,14 @@ impl Owner {
|
||||
}
|
||||
|
||||
fn use_context<T: Clone + 'static>(&self) -> Option<T> {
|
||||
let ty = TypeId::of::<T>();
|
||||
let inner = self.inner.read().or_poisoned();
|
||||
let mut parent = inner.parent.as_ref().and_then(|p| p.upgrade());
|
||||
let contexts = &self.inner.read().or_poisoned().contexts;
|
||||
if let Some(context) = contexts.get(&ty) {
|
||||
context.downcast_ref::<T>().cloned()
|
||||
} else {
|
||||
while let Some(ref this_parent) = parent.clone() {
|
||||
let this_parent = this_parent.read().or_poisoned();
|
||||
let contexts = &this_parent.contexts;
|
||||
let value = contexts.get(&ty);
|
||||
let downcast = value
|
||||
.and_then(|context| context.downcast_ref::<T>().cloned());
|
||||
if let Some(value) = downcast {
|
||||
return Some(value);
|
||||
} else {
|
||||
parent =
|
||||
this_parent.parent.as_ref().and_then(|p| p.upgrade());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
self.with_context(Clone::clone)
|
||||
}
|
||||
|
||||
fn take_context<T: 'static>(&self) -> Option<T> {
|
||||
let ty = TypeId::of::<T>();
|
||||
let inner = self.inner.read().or_poisoned();
|
||||
let mut inner = self.inner.write().or_poisoned();
|
||||
let mut parent = inner.parent.as_ref().and_then(|p| p.upgrade());
|
||||
let contexts = &mut self.inner.write().or_poisoned().contexts;
|
||||
let contexts = &mut inner.contexts;
|
||||
if let Some(context) = contexts.remove(&ty) {
|
||||
context.downcast::<T>().ok().map(|n| *n)
|
||||
} else {
|
||||
@@ -64,6 +43,64 @@ impl Owner {
|
||||
}
|
||||
}
|
||||
|
||||
fn with_context<T: 'static, R>(
|
||||
&self,
|
||||
cb: impl FnOnce(&T) -> R,
|
||||
) -> Option<R> {
|
||||
let ty = TypeId::of::<T>();
|
||||
let inner = self.inner.read().or_poisoned();
|
||||
let mut parent = inner.parent.as_ref().and_then(|p| p.upgrade());
|
||||
let contexts = &inner.contexts;
|
||||
let reference = if let Some(context) = contexts.get(&ty) {
|
||||
context.downcast_ref::<T>()
|
||||
} else {
|
||||
while let Some(ref this_parent) = parent.clone() {
|
||||
let this_parent = this_parent.read().or_poisoned();
|
||||
let contexts = &this_parent.contexts;
|
||||
let value = contexts.get(&ty);
|
||||
let downcast =
|
||||
value.and_then(|context| context.downcast_ref::<T>());
|
||||
if let Some(value) = downcast {
|
||||
return Some(cb(value));
|
||||
} else {
|
||||
parent =
|
||||
this_parent.parent.as_ref().and_then(|p| p.upgrade());
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
reference.map(cb)
|
||||
}
|
||||
|
||||
fn update_context<T: 'static, R>(
|
||||
&self,
|
||||
cb: impl FnOnce(&mut T) -> R,
|
||||
) -> Option<R> {
|
||||
let ty = TypeId::of::<T>();
|
||||
let mut inner = self.inner.write().or_poisoned();
|
||||
let mut parent = inner.parent.as_ref().and_then(|p| p.upgrade());
|
||||
let contexts = &mut inner.contexts;
|
||||
let reference = if let Some(context) = contexts.get_mut(&ty) {
|
||||
context.downcast_mut::<T>()
|
||||
} else {
|
||||
while let Some(ref this_parent) = parent.clone() {
|
||||
let mut this_parent = this_parent.write().or_poisoned();
|
||||
let contexts = &mut this_parent.contexts;
|
||||
let value = contexts.get_mut(&ty);
|
||||
let downcast =
|
||||
value.and_then(|context| context.downcast_mut::<T>());
|
||||
if let Some(value) = downcast {
|
||||
return Some(cb(value));
|
||||
} else {
|
||||
parent =
|
||||
this_parent.parent.as_ref().and_then(|p| p.upgrade());
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
reference.map(cb)
|
||||
}
|
||||
|
||||
/// Searches for items stored in context in either direction, either among parents or among
|
||||
/// descendants.
|
||||
pub fn use_context_bidirectional<T: Clone + 'static>(&self) -> Option<T> {
|
||||
@@ -319,3 +356,86 @@ pub fn expect_context<T: Clone + 'static>() -> T {
|
||||
pub fn take_context<T: 'static>() -> Option<T> {
|
||||
Owner::current().and_then(|owner| owner.take_context())
|
||||
}
|
||||
|
||||
/// Access a reference to a context value of type `T` in the reactive system.
|
||||
///
|
||||
/// This traverses the reactive ownership graph, beginning from the current reactive
|
||||
/// [`Owner`] and iterating through its parents, if any. When the value is found,
|
||||
/// the function that you pass is applied to an immutable reference to it.
|
||||
///
|
||||
/// The context value should have been provided elsewhere using
|
||||
/// [`provide_context`](provide_context).
|
||||
///
|
||||
/// This is useful for passing values down to components or functions lower in a
|
||||
/// hierarchy without needs to “prop drill” by passing them through each layer as
|
||||
/// arguments to a function or properties of a component.
|
||||
///
|
||||
/// Context works similarly to variable scope: a context that is provided higher in
|
||||
/// the reactive graph can be used lower down, but a context that is provided lower
|
||||
/// in the tree cannot be used higher up.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::owner::*;
|
||||
/// # let owner = Owner::new(); owner.set();
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # futures::executor::block_on(async move {
|
||||
/// # any_spawner::Executor::init_futures_executor();
|
||||
/// Effect::new(move |_| {
|
||||
/// provide_context(String::from("foo"));
|
||||
///
|
||||
/// Effect::new(move |_| {
|
||||
/// let value = with_context::<String, _>(|val| val.to_string())
|
||||
/// .expect("could not find String in context");
|
||||
/// assert_eq!(value, "foo");
|
||||
/// });
|
||||
/// });
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn with_context<T: 'static, R>(cb: impl FnOnce(&T) -> R) -> Option<R> {
|
||||
Owner::current().and_then(|owner| owner.with_context(cb))
|
||||
}
|
||||
|
||||
/// Update a context value of type `T` in the reactive system.
|
||||
///
|
||||
/// This traverses the reactive ownership graph, beginning from the current reactive
|
||||
/// [`Owner`] and iterating through its parents, if any. When the value is found,
|
||||
/// the function that you pass is applied to a mutable reference to it.
|
||||
///
|
||||
/// The context value should have been provided elsewhere using
|
||||
/// [`provide_context`](provide_context).
|
||||
///
|
||||
/// This is useful for passing values down to components or functions lower in a
|
||||
/// hierarchy without needs to “prop drill” by passing them through each layer as
|
||||
/// arguments to a function or properties of a component.
|
||||
///
|
||||
/// Context works similarly to variable scope: a context that is provided higher in
|
||||
/// the reactive graph can be used lower down, but a context that is provided lower
|
||||
/// in the tree cannot be used higher up.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::owner::*;
|
||||
/// # let owner = Owner::new(); owner.set();
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # futures::executor::block_on(async move {
|
||||
/// # any_spawner::Executor::init_futures_executor();
|
||||
/// Effect::new(move |_| {
|
||||
/// provide_context(String::from("foo"));
|
||||
///
|
||||
/// Effect::new(move |_| {
|
||||
/// let value = update_context::<String, _>(|val| {
|
||||
/// std::mem::replace(val, "bar".to_string())
|
||||
/// })
|
||||
/// .expect("could not find String in context");
|
||||
/// assert_eq!(value, "foo");
|
||||
/// assert_eq!(expect_context::<String>(), "bar");
|
||||
/// });
|
||||
/// });
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn update_context<T: 'static, R>(
|
||||
cb: impl FnOnce(&mut T) -> R,
|
||||
) -> Option<R> {
|
||||
Owner::current().and_then(|owner| owner.update_context(cb))
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ impl<T> StorageAccess<T> for SendWrapper<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A way of storing a [`ArenaItem`], either as itself or with a wrapper to make it threadsafe.
|
||||
/// A way of storing an [`ArenaItem`](super::arena_item::ArenaItem), either as itself or with a wrapper to make it threadsafe.
|
||||
///
|
||||
/// This exists because all items stored in the arena must be `Send + Sync`, but in single-threaded
|
||||
/// environments you might want or need to use thread-unsafe types.
|
||||
@@ -54,6 +54,10 @@ pub trait Storage<T>: Send + Sync + 'static {
|
||||
|
||||
/// Sets a new value for the stored value. If it has been disposed, returns `Some(T)`.
|
||||
fn try_set(node: NodeId, value: T) -> Option<T>;
|
||||
|
||||
/// Takes an item from the arena if it exists and can be accessed from this thread.
|
||||
/// If it cannot be casted, it will still be removed from the arena.
|
||||
fn take(node: NodeId) -> Option<T>;
|
||||
}
|
||||
|
||||
/// A form of [`Storage`] that stores the type as itself, with no wrapper.
|
||||
@@ -100,6 +104,16 @@ where
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn take(node: NodeId) -> Option<T> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.remove(node)?;
|
||||
match m.downcast::<T>() {
|
||||
Ok(inner) => Some(*inner),
|
||||
Err(_) => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A form of [`Storage`] that stores the type with a wrapper that makes it `Send + Sync`, but only
|
||||
@@ -148,4 +162,14 @@ where
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn take(node: NodeId) -> Option<T> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.remove(node)?;
|
||||
match m.downcast::<SendWrapper<T>>() {
|
||||
Ok(inner) => Some(inner.take()),
|
||||
Err(_) => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
signal::guards::{Plain, ReadGuard, UntrackedWriteGuard},
|
||||
traits::{DefinedAt, Dispose, IsDisposed, ReadValue, WriteValue},
|
||||
traits::{
|
||||
DefinedAt, Dispose, IntoInner, IsDisposed, ReadValue, WriteValue,
|
||||
},
|
||||
unwrap_signal,
|
||||
};
|
||||
use std::{
|
||||
@@ -22,7 +24,7 @@ use std::{
|
||||
/// updating it does not notify anything else.
|
||||
pub struct StoredValue<T, S = SyncStorage> {
|
||||
value: ArenaItem<ArcStoredValue<T>, S>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
|
||||
@@ -62,11 +64,11 @@ impl<T, S> Hash for StoredValue<T, S> {
|
||||
|
||||
impl<T, S> DefinedAt for StoredValue<T, S> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -83,7 +85,7 @@ where
|
||||
pub fn new_with_storage(value: T) -> Self {
|
||||
Self {
|
||||
value: ArenaItem::new_with_storage(ArcStoredValue::new(value)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -162,6 +164,19 @@ impl<T, S> Dispose for StoredValue<T, S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> IntoInner for StoredValue<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
S: Storage<ArcStoredValue<T>>,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
self.value.into_inner()?.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<ArcStoredValue<T>> for StoredValue<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
@@ -169,7 +184,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: ArcStoredValue<T>) -> Self {
|
||||
StoredValue {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: ArenaItem::new(value),
|
||||
}
|
||||
|
||||
@@ -113,7 +113,8 @@ pub fn arc_signal<T>(value: T) -> (ArcReadSignal<T>, ArcWriteSignal<T>) {
|
||||
pub fn signal<T: Send + Sync + 'static>(
|
||||
value: T,
|
||||
) -> (ReadSignal<T>, WriteSignal<T>) {
|
||||
RwSignal::new(value).split()
|
||||
let (r, w) = arc_signal(value);
|
||||
(r.into(), w.into())
|
||||
}
|
||||
|
||||
/// Creates an arena-allocated signal.
|
||||
|
||||
@@ -4,7 +4,7 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
graph::SubscriberSet,
|
||||
traits::{DefinedAt, IsDisposed, ReadUntracked},
|
||||
traits::{DefinedAt, IntoInner, IsDisposed, ReadUntracked},
|
||||
};
|
||||
use core::fmt::{Debug, Formatter, Result};
|
||||
use std::{
|
||||
@@ -54,7 +54,7 @@ use std::{
|
||||
/// assert_eq!(count.read(), 0);
|
||||
/// ```
|
||||
pub struct ArcReadSignal<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) value: Arc<RwLock<T>>,
|
||||
pub(crate) inner: Arc<RwLock<SubscriberSet>>,
|
||||
@@ -64,7 +64,7 @@ impl<T> Clone for ArcReadSignal<T> {
|
||||
#[track_caller]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
value: Arc::clone(&self.value),
|
||||
inner: Arc::clone(&self.inner),
|
||||
@@ -85,7 +85,7 @@ impl<T: Default> Default for ArcReadSignal<T> {
|
||||
#[track_caller]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::new(RwLock::new(T::default())),
|
||||
inner: Arc::new(RwLock::new(SubscriberSet::new())),
|
||||
@@ -110,11 +110,11 @@ impl<T> Hash for ArcReadSignal<T> {
|
||||
impl<T> DefinedAt for ArcReadSignal<T> {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -128,6 +128,15 @@ impl<T> IsDisposed for ArcReadSignal<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoInner for ArcReadSignal<T> {
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
Some(Arc::into_inner(self.value)?.into_inner().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsSubscriberSet for ArcReadSignal<T> {
|
||||
type Output = Arc<RwLock<SubscriberSet>>;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use super::{
|
||||
use crate::{
|
||||
graph::{ReactiveNode, SubscriberSet},
|
||||
prelude::{IsDisposed, Notify},
|
||||
traits::{DefinedAt, ReadUntracked, UntrackableGuard, Write},
|
||||
traits::{DefinedAt, IntoInner, ReadUntracked, UntrackableGuard, Write},
|
||||
};
|
||||
use core::fmt::{Debug, Formatter, Result};
|
||||
use std::{
|
||||
@@ -94,7 +94,7 @@ use std::{
|
||||
/// assert_eq!(double_count(), 2);
|
||||
/// ```
|
||||
pub struct ArcRwSignal<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) value: Arc<RwLock<T>>,
|
||||
pub(crate) inner: Arc<RwLock<SubscriberSet>>,
|
||||
@@ -104,7 +104,7 @@ impl<T> Clone for ArcRwSignal<T> {
|
||||
#[track_caller]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
value: Arc::clone(&self.value),
|
||||
inner: Arc::clone(&self.inner),
|
||||
@@ -154,7 +154,7 @@ impl<T> ArcRwSignal<T> {
|
||||
#[track_caller]
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::new(RwLock::new(value)),
|
||||
inner: Arc::new(RwLock::new(SubscriberSet::new())),
|
||||
@@ -165,7 +165,7 @@ impl<T> ArcRwSignal<T> {
|
||||
#[track_caller]
|
||||
pub fn read_only(&self) -> ArcReadSignal<T> {
|
||||
ArcReadSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::clone(&self.value),
|
||||
inner: Arc::clone(&self.inner),
|
||||
@@ -176,7 +176,7 @@ impl<T> ArcRwSignal<T> {
|
||||
#[track_caller]
|
||||
pub fn write_only(&self) -> ArcWriteSignal<T> {
|
||||
ArcWriteSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::clone(&self.value),
|
||||
inner: Arc::clone(&self.inner),
|
||||
@@ -198,7 +198,7 @@ impl<T> ArcRwSignal<T> {
|
||||
) -> Option<Self> {
|
||||
if Arc::ptr_eq(&read.inner, &write.inner) {
|
||||
Some(Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: read.value,
|
||||
inner: read.inner,
|
||||
@@ -212,11 +212,11 @@ impl<T> ArcRwSignal<T> {
|
||||
impl<T> DefinedAt for ArcRwSignal<T> {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -230,6 +230,15 @@ impl<T> IsDisposed for ArcRwSignal<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoInner for ArcRwSignal<T> {
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
Some(Arc::into_inner(self.value)?.into_inner().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsSubscriberSet for ArcRwSignal<T> {
|
||||
type Output = Arc<RwLock<SubscriberSet>>;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::{
|
||||
///
|
||||
/// This can be useful for when using external data not stored in signals, for example.
|
||||
pub struct ArcTrigger {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: Arc<RwLock<SubscriberSet>>,
|
||||
}
|
||||
@@ -23,7 +23,7 @@ impl ArcTrigger {
|
||||
#[track_caller]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: Default::default(),
|
||||
}
|
||||
@@ -40,7 +40,7 @@ impl Clone for ArcTrigger {
|
||||
#[track_caller]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
inner: Arc::clone(&self.inner),
|
||||
}
|
||||
@@ -72,11 +72,11 @@ impl AsSubscriberSet for ArcTrigger {
|
||||
impl DefinedAt for ArcTrigger {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::guards::{UntrackedWriteGuard, WriteGuard};
|
||||
use crate::{
|
||||
graph::{ReactiveNode, SubscriberSet},
|
||||
prelude::{IsDisposed, Notify},
|
||||
traits::{DefinedAt, UntrackableGuard, Write},
|
||||
traits::{DefinedAt, IntoInner, UntrackableGuard, Write},
|
||||
};
|
||||
use core::fmt::{Debug, Formatter, Result};
|
||||
use std::{
|
||||
@@ -54,7 +54,7 @@ use std::{
|
||||
/// assert_eq!(count.get(), 3);
|
||||
/// ```
|
||||
pub struct ArcWriteSignal<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) value: Arc<RwLock<T>>,
|
||||
pub(crate) inner: Arc<RwLock<SubscriberSet>>,
|
||||
@@ -64,7 +64,7 @@ impl<T> Clone for ArcWriteSignal<T> {
|
||||
#[track_caller]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
value: Arc::clone(&self.value),
|
||||
inner: Arc::clone(&self.inner),
|
||||
@@ -98,11 +98,11 @@ impl<T> Hash for ArcWriteSignal<T> {
|
||||
impl<T> DefinedAt for ArcWriteSignal<T> {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -116,6 +116,15 @@ impl<T> IsDisposed for ArcWriteSignal<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoInner for ArcWriteSignal<T> {
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
Some(Arc::into_inner(self.value)?.into_inner().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Notify for ArcWriteSignal<T> {
|
||||
fn notify(&self) {
|
||||
self.inner.mark_dirty();
|
||||
|
||||
@@ -6,7 +6,7 @@ use super::{
|
||||
use crate::{
|
||||
graph::SubscriberSet,
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
traits::{DefinedAt, Dispose, IsDisposed, ReadUntracked},
|
||||
traits::{DefinedAt, Dispose, IntoInner, IsDisposed, ReadUntracked},
|
||||
unwrap_signal,
|
||||
};
|
||||
use core::fmt::Debug;
|
||||
@@ -58,7 +58,7 @@ use std::{
|
||||
/// assert_eq!(count.read(), 0);
|
||||
/// ```
|
||||
pub struct ReadSignal<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: ArenaItem<ArcReadSignal<T>, S>,
|
||||
}
|
||||
@@ -105,11 +105,11 @@ impl<T, S> Hash for ReadSignal<T, S> {
|
||||
|
||||
impl<T, S> DefinedAt for ReadSignal<T, S> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -122,6 +122,18 @@ impl<T, S> IsDisposed for ReadSignal<T, S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> IntoInner for ReadSignal<T, S>
|
||||
where
|
||||
S: Storage<ArcReadSignal<T>>,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
self.inner.into_inner()?.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> AsSubscriberSet for ReadSignal<T, S>
|
||||
where
|
||||
S: Storage<ArcReadSignal<T>>,
|
||||
@@ -156,7 +168,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: ArcReadSignal<T>) -> Self {
|
||||
ReadSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
@@ -170,7 +182,7 @@ where
|
||||
#[track_caller]
|
||||
fn from_local(value: ArcReadSignal<T>) -> Self {
|
||||
ReadSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
signal::guards::{UntrackedWriteGuard, WriteGuard},
|
||||
traits::{
|
||||
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked,
|
||||
DefinedAt, Dispose, IntoInner, IsDisposed, Notify, ReadUntracked,
|
||||
UntrackableGuard, Write,
|
||||
},
|
||||
unwrap_signal,
|
||||
@@ -100,7 +100,7 @@ use std::{
|
||||
/// assert_eq!(double_count(), 2);
|
||||
/// ```
|
||||
pub struct RwSignal<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: ArenaItem<ArcRwSignal<T>, S>,
|
||||
}
|
||||
@@ -139,7 +139,7 @@ where
|
||||
#[track_caller]
|
||||
pub fn new_with_storage(value: T) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcRwSignal::new(value)),
|
||||
}
|
||||
@@ -172,7 +172,7 @@ where
|
||||
#[track_caller]
|
||||
pub fn read_only(&self) -> ReadSignal<T, S> {
|
||||
ReadSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(
|
||||
self.inner
|
||||
@@ -194,7 +194,7 @@ where
|
||||
#[track_caller]
|
||||
pub fn write_only(&self) -> WriteSignal<T, S> {
|
||||
WriteSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(
|
||||
self.inner
|
||||
@@ -231,10 +231,10 @@ where
|
||||
(Some(read), Some(write)) => {
|
||||
if Arc::ptr_eq(&read.inner, &write.inner) {
|
||||
Some(Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(ArcRwSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::clone(&read.value),
|
||||
inner: Arc::clone(&read.inner),
|
||||
@@ -296,11 +296,11 @@ impl<T, S> Hash for RwSignal<T, S> {
|
||||
|
||||
impl<T, S> DefinedAt for RwSignal<T, S> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -313,6 +313,18 @@ impl<T: 'static, S> IsDisposed for RwSignal<T, S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> IntoInner for RwSignal<T, S>
|
||||
where
|
||||
S: Storage<ArcRwSignal<T>>,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
self.inner.into_inner()?.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> AsSubscriberSet for RwSignal<T, S>
|
||||
where
|
||||
S: Storage<ArcRwSignal<T>>,
|
||||
@@ -378,7 +390,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: ArcRwSignal<T>) -> Self {
|
||||
RwSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
@@ -402,7 +414,7 @@ where
|
||||
#[track_caller]
|
||||
fn from_local(value: ArcRwSignal<T>) -> Self {
|
||||
RwSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ where
|
||||
AnySource(
|
||||
Arc::as_ptr(subs) as usize,
|
||||
Arc::downgrade(subs) as Weak<dyn Source + Send + Sync>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
self.defined_at().expect("no DefinedAt in debug mode"),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -18,7 +18,7 @@ use std::{
|
||||
/// [`Owner`](crate::owner::Owner) cleans up. For a reference-counted trigger that lives
|
||||
/// as long as a reference to it is alive, see [`ArcTrigger`].
|
||||
pub struct Trigger {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: ArenaItem<ArcTrigger>,
|
||||
}
|
||||
@@ -28,7 +28,7 @@ impl Trigger {
|
||||
#[track_caller]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new(ArcTrigger::new()),
|
||||
}
|
||||
@@ -83,11 +83,11 @@ impl AsSubscriberSet for Trigger {
|
||||
impl DefinedAt for Trigger {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use super::{guards::WriteGuard, ArcWriteSignal};
|
||||
use crate::{
|
||||
owner::{ArenaItem, Storage, SyncStorage},
|
||||
traits::{DefinedAt, Dispose, IsDisposed, Notify, UntrackableGuard, Write},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
traits::{
|
||||
DefinedAt, Dispose, IntoInner, IsDisposed, Notify, UntrackableGuard,
|
||||
Write,
|
||||
},
|
||||
};
|
||||
use core::fmt::Debug;
|
||||
use guardian::ArcRwLockWriteGuardian;
|
||||
@@ -50,7 +53,7 @@ use std::{hash::Hash, ops::DerefMut, panic::Location, sync::Arc};
|
||||
/// assert_eq!(count.get(), 3);
|
||||
/// ```
|
||||
pub struct WriteSignal<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: ArenaItem<ArcWriteSignal<T>, S>,
|
||||
}
|
||||
@@ -97,23 +100,63 @@ impl<T, S> Hash for WriteSignal<T, S> {
|
||||
|
||||
impl<T, S> DefinedAt for WriteSignal<T, S> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<ArcWriteSignal<T>> for WriteSignal<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: ArcWriteSignal<T>) -> Self {
|
||||
WriteSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromLocal<ArcWriteSignal<T>> for WriteSignal<T, LocalStorage>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from_local(value: ArcWriteSignal<T>) -> Self {
|
||||
WriteSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> IsDisposed for WriteSignal<T, S> {
|
||||
fn is_disposed(&self) -> bool {
|
||||
self.inner.is_disposed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> IntoInner for WriteSignal<T, S>
|
||||
where
|
||||
S: Storage<ArcWriteSignal<T>>,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_inner(self) -> Option<Self::Value> {
|
||||
self.inner.into_inner()?.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> Notify for WriteSignal<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
|
||||
@@ -220,7 +220,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait to implement flatten() on Option<&Option<T>>.
|
||||
/// Helper trait to implement flatten() on `Option<&Option<T>>`.
|
||||
pub trait FlattenOptionRefOption {
|
||||
/// The type of the value contained in the double option.
|
||||
type Value;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
//! | Trait | Mode | Description |
|
||||
//! |-------------------|-------|---------------------------------------------------------------------------------------|
|
||||
//! | [`Track`] | — | Tracks changes to this value, adding it as a source of the current reactive observer. |
|
||||
//! | [`Trigger`] | — | Notifies subscribers that this value has changed. |
|
||||
//! | [`Notify`] | — | Notifies subscribers that this value has changed. |
|
||||
//! | [`ReadUntracked`] | Guard | Gives immutable access to the value of this signal. |
|
||||
//! | [`Write`] | Guard | Gives mutable access to the value of this signal.
|
||||
//!
|
||||
@@ -34,7 +34,7 @@
|
||||
//! | Trait | Mode | Composition | Description
|
||||
//! |---------------------|---------------|-----------------------------------|------------
|
||||
//! | [`UpdateUntracked`] | `fn(&mut T)` | [`Write`] | Applies closure to the current value to update it, but doesn't notify subscribers.
|
||||
//! | [`Update`] | `fn(&mut T)` | [`UpdateUntracked`] + [`Trigger`] | Applies closure to the current value to update it, and notifies subscribers.
|
||||
//! | [`Update`] | `fn(&mut T)` | [`UpdateUntracked`] + [`Notify`] | Applies closure to the current value to update it, and notifies subscribers.
|
||||
//! | [`Set`] | `T` | [`Update`] | Sets the value to a new value, and notifies subscribers.
|
||||
//!
|
||||
//! ## Using the Traits
|
||||
@@ -67,10 +67,10 @@ use std::{
|
||||
#[macro_export]
|
||||
macro_rules! unwrap_signal {
|
||||
($signal:ident) => {{
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
let location = std::panic::Location::caller();
|
||||
|| {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
panic!(
|
||||
"{}",
|
||||
@@ -80,7 +80,7 @@ macro_rules! unwrap_signal {
|
||||
)
|
||||
);
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
panic!(
|
||||
"Tried to access a reactive value that has already been \
|
||||
@@ -630,6 +630,18 @@ pub trait IsDisposed {
|
||||
fn is_disposed(&self) -> bool;
|
||||
}
|
||||
|
||||
/// Turns a signal back into a raw value.
|
||||
pub trait IntoInner {
|
||||
/// The type of the value contained in the signal.
|
||||
type Value;
|
||||
|
||||
/// Returns the inner value if this is the only reference to to the signal.
|
||||
/// Otherwise, returns `None` and drops this reference.
|
||||
/// # Panics
|
||||
/// Panics if the inner lock is poisoned.
|
||||
fn into_inner(self) -> Option<Self::Value>;
|
||||
}
|
||||
|
||||
/// Describes where the signal was defined. This is used for diagnostic warnings and is purely a
|
||||
/// debug-mode tool.
|
||||
pub trait DefinedAt {
|
||||
|
||||
@@ -93,17 +93,35 @@ pub mod read {
|
||||
}
|
||||
|
||||
/// A wrapper for any kind of reference-counted reactive signal:
|
||||
/// an [`ArcReadSignal`], [`ArcMemo`], [`ArcRwSignal`],
|
||||
/// or derived signal closure.
|
||||
/// an [`ArcReadSignal`], [`ArcMemo`], [`ArcRwSignal`], or derived signal closure,
|
||||
/// or a plain value of the same type
|
||||
///
|
||||
/// This allows you to create APIs that take any kind of `ArcSignal<T>` as an argument,
|
||||
/// rather than adding a generic `F: Fn() -> T`. Values can be access with the same
|
||||
/// function call, `with()`, and `get()` APIs as other signals.
|
||||
/// This allows you to create APIs that take `T` or any reactive value that returns `T`
|
||||
/// as an argument, rather than adding a generic `F: Fn() -> T`.
|
||||
///
|
||||
/// Values can be accessed with the same function call, `read()`, `with()`, and `get()`
|
||||
/// APIs as other signals.
|
||||
///
|
||||
/// ## Important Notes about Derived Signals
|
||||
///
|
||||
/// `Signal::derive()` is simply a way to box and type-erase a “derived signal,” which
|
||||
/// is a plain closure that accesses one or more signals. It does *not* cache the value
|
||||
/// of that computation. Accessing the value of a `Signal<_>` that is created using `Signal::derive()`
|
||||
/// will run the closure again every time you call `.read()`, `.with()`, or `.get()`.
|
||||
///
|
||||
/// If you want the closure to run the minimal number of times necessary to update its state,
|
||||
/// and then to cache its value, you should use a [`Memo`] (and convert it into a `Signal<_>`)
|
||||
/// rather than using `Signal::derive()`.
|
||||
///
|
||||
/// Note that for many computations, it is nevertheless less expensive to use a derived signal
|
||||
/// than to create a separate memo and to cache the value: creating a new reactive node and
|
||||
/// taking the lock on that cached value whenever you access the signal is *more* expensive than
|
||||
/// simply re-running the calculation in many cases.
|
||||
pub struct ArcSignal<T: 'static, S = SyncStorage>
|
||||
where
|
||||
S: Storage<T>,
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: SignalTypes<T, S>,
|
||||
}
|
||||
@@ -114,7 +132,7 @@ pub mod read {
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
@@ -128,7 +146,7 @@ pub mod read {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let mut s = f.debug_struct("ArcSignal");
|
||||
s.field("inner", &self.inner);
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
s.field("defined_at", &self.defined_at);
|
||||
s.finish()
|
||||
}
|
||||
@@ -184,7 +202,7 @@ pub mod read {
|
||||
|
||||
Self {
|
||||
inner: SignalTypes::DerivedSignal(Arc::new(derived_signal)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -194,7 +212,7 @@ pub mod read {
|
||||
pub fn stored(value: T) -> Self {
|
||||
Self {
|
||||
inner: SignalTypes::Stored(ArcStoredValue::new(value)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -214,7 +232,7 @@ pub mod read {
|
||||
fn from(value: ArcReadSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: SignalTypes::ReadSignal(value),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -225,7 +243,7 @@ pub mod read {
|
||||
fn from(value: ArcRwSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: SignalTypes::ReadSignal(value.read_only()),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -239,7 +257,7 @@ pub mod read {
|
||||
fn from(value: ArcMemo<T, S>) -> Self {
|
||||
Self {
|
||||
inner: SignalTypes::Memo(value),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -250,11 +268,11 @@ pub mod read {
|
||||
S: Storage<T>,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -330,16 +348,35 @@ pub mod read {
|
||||
}
|
||||
|
||||
/// A wrapper for any kind of arena-allocated reactive signal:
|
||||
/// an [`ReadSignal`], [`Memo`], [`RwSignal`], or derived signal closure.
|
||||
/// a [`ReadSignal`], [`Memo`], [`RwSignal`], or derived signal closure,
|
||||
/// or a plain value of the same type
|
||||
///
|
||||
/// This allows you to create APIs that take any kind of `Signal<T>` as an argument,
|
||||
/// rather than adding a generic `F: Fn() -> T`. Values can be access with the same
|
||||
/// function call, `with()`, and `get()` APIs as other signals.
|
||||
/// This allows you to create APIs that take `T` or any reactive value that returns `T`
|
||||
/// as an argument, rather than adding a generic `F: Fn() -> T`.
|
||||
///
|
||||
/// Values can be accessed with the same function call, `read()`, `with()`, and `get()`
|
||||
/// APIs as other signals.
|
||||
///
|
||||
/// ## Important Notes about Derived Signals
|
||||
///
|
||||
/// `Signal::derive()` is simply a way to box and type-erase a “derived signal,” which
|
||||
/// is a plain closure that accesses one or more signals. It does *not* cache the value
|
||||
/// of that computation. Accessing the value of a `Signal<_>` that is created using `Signal::derive()`
|
||||
/// will run the closure again every time you call `.read()`, `.with()`, or `.get()`.
|
||||
///
|
||||
/// If you want the closure to run the minimal number of times necessary to update its state,
|
||||
/// and then to cache its value, you should use a [`Memo`] (and convert it into a `Signal<_>`)
|
||||
/// rather than using `Signal::derive()`.
|
||||
///
|
||||
/// Note that for many computations, it is nevertheless less expensive to use a derived signal
|
||||
/// than to create a separate memo and to cache the value: creating a new reactive node and
|
||||
/// taking the lock on that cached value whenever you access the signal is *more* expensive than
|
||||
/// simply re-running the calculation in many cases.
|
||||
pub struct Signal<T, S = SyncStorage>
|
||||
where
|
||||
S: Storage<T>,
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: ArenaItem<SignalTypes<T, S>, S>,
|
||||
}
|
||||
@@ -371,7 +408,7 @@ pub mod read {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let mut s = f.debug_struct("Signal");
|
||||
s.field("inner", &self.inner);
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
s.field("defined_at", &self.defined_at);
|
||||
s.finish()
|
||||
}
|
||||
@@ -393,11 +430,11 @@ pub mod read {
|
||||
S: Storage<T>,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -530,7 +567,7 @@ pub mod read {
|
||||
inner: ArenaItem::new_with_storage(SignalTypes::DerivedSignal(
|
||||
Arc::new(derived_signal),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -542,7 +579,7 @@ pub mod read {
|
||||
inner: ArenaItem::new_with_storage(SignalTypes::Stored(
|
||||
ArcStoredValue::new(value),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -569,7 +606,7 @@ pub mod read {
|
||||
inner: ArenaItem::new_local(SignalTypes::DerivedSignal(
|
||||
Arc::new(derived_signal),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -582,7 +619,7 @@ pub mod read {
|
||||
inner: ArenaItem::new_local(SignalTypes::Stored(
|
||||
ArcStoredValue::new(value),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -640,7 +677,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from(value: ArcSignal<T, SyncStorage>) -> Self {
|
||||
Signal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new(value.inner),
|
||||
}
|
||||
@@ -654,7 +691,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from_local(value: ArcSignal<T, LocalStorage>) -> Self {
|
||||
Signal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: ArenaItem::new_local(value.inner),
|
||||
}
|
||||
@@ -668,7 +705,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from(value: Signal<T, S>) -> Self {
|
||||
ArcSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
inner: value
|
||||
.inner
|
||||
@@ -686,7 +723,7 @@ pub mod read {
|
||||
fn from(value: ReadSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new(SignalTypes::ReadSignal(value.into())),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -702,7 +739,7 @@ pub mod read {
|
||||
inner: ArenaItem::new_local(SignalTypes::ReadSignal(
|
||||
value.into(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -716,7 +753,7 @@ pub mod read {
|
||||
fn from(value: ArcReadSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new(SignalTypes::ReadSignal(value)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -730,7 +767,7 @@ pub mod read {
|
||||
fn from(value: ArcReadSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new_local(SignalTypes::ReadSignal(value)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -746,7 +783,7 @@ pub mod read {
|
||||
inner: ArenaItem::new(SignalTypes::ReadSignal(
|
||||
value.read_only().into(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -762,7 +799,7 @@ pub mod read {
|
||||
inner: ArenaItem::new_local(SignalTypes::ReadSignal(
|
||||
value.read_only().into(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -778,7 +815,7 @@ pub mod read {
|
||||
inner: ArenaItem::new(SignalTypes::ReadSignal(
|
||||
value.read_only(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -794,7 +831,7 @@ pub mod read {
|
||||
inner: ArenaItem::new_local(SignalTypes::ReadSignal(
|
||||
value.read_only(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -808,7 +845,7 @@ pub mod read {
|
||||
fn from(value: Memo<T>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new(SignalTypes::Memo(value.into())),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -822,7 +859,7 @@ pub mod read {
|
||||
fn from(value: Memo<T, LocalStorage>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new_local(SignalTypes::Memo(value.into())),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -836,7 +873,7 @@ pub mod read {
|
||||
fn from(value: ArcMemo<T>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new(SignalTypes::Memo(value)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -850,7 +887,7 @@ pub mod read {
|
||||
fn from(value: ArcMemo<T, LocalStorage>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new_local(SignalTypes::Memo(value)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -1831,7 +1868,7 @@ pub mod write {
|
||||
T: 'static,
|
||||
{
|
||||
inner: SignalSetterTypes<T, S>,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static std::panic::Location<'static>,
|
||||
}
|
||||
|
||||
@@ -1846,7 +1883,7 @@ pub mod write {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: SignalSetterTypes::Default,
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -1899,7 +1936,7 @@ pub mod write {
|
||||
inner: SignalSetterTypes::Mapped(ArenaItem::new_with_storage(
|
||||
Box::new(mapped_setter),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -1910,7 +1947,7 @@ pub mod write {
|
||||
fn from(value: WriteSignal<T, S>) -> Self {
|
||||
Self {
|
||||
inner: SignalSetterTypes::Write(value),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@@ -1925,7 +1962,7 @@ pub mod write {
|
||||
fn from(value: RwSignal<T, S>) -> Self {
|
||||
Self {
|
||||
inner: SignalSetterTypes::Write(value.write_only()),
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,3 +443,43 @@ fn unsync_derived_signal_and_memo() {
|
||||
assert_eq!(f.with(|n| *n), 6);
|
||||
assert_eq!(f.get_untracked(), 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memo_updates_even_if_not_read_until_later() {
|
||||
#![allow(clippy::bool_assert_comparison)]
|
||||
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
// regression test for https://github.com/leptos-rs/leptos/issues/3339
|
||||
|
||||
let input = RwSignal::new(0);
|
||||
let first_memo = Memo::new(move |_| input.get() == 1);
|
||||
let second_memo = Memo::new(move |_| first_memo.get());
|
||||
|
||||
assert_eq!(input.get(), 0);
|
||||
assert_eq!(first_memo.get(), false);
|
||||
|
||||
println!("update to 1");
|
||||
input.set(1);
|
||||
assert_eq!(input.get(), 1);
|
||||
println!("read memo 1");
|
||||
assert_eq!(first_memo.get(), true);
|
||||
println!("read memo 2");
|
||||
assert_eq!(second_memo.get(), true);
|
||||
|
||||
// this time, we don't read the memo
|
||||
println!("\nupdate to 2");
|
||||
input.set(2);
|
||||
assert_eq!(input.get(), 2);
|
||||
println!("read memo 1");
|
||||
assert_eq!(first_memo.get(), false);
|
||||
|
||||
println!("\nupdate to 3");
|
||||
input.set(3);
|
||||
assert_eq!(input.get(), 3);
|
||||
println!("read memo 1");
|
||||
assert_eq!(first_memo.get(), false);
|
||||
println!("read memo 2");
|
||||
assert_eq!(second_memo.get(), false);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ use reactive_graph::{
|
||||
owner::Owner,
|
||||
signal::{arc_signal, signal, ArcRwSignal, RwSignal},
|
||||
traits::{
|
||||
Get, GetUntracked, Read, Set, Update, UpdateUntracked, With,
|
||||
WithUntracked, Write,
|
||||
Dispose, Get, GetUntracked, IntoInner, Read, Set, Update,
|
||||
UpdateUntracked, With, WithUntracked, Write,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -108,3 +108,35 @@ fn update_signal() {
|
||||
set_a.set(4);
|
||||
assert_eq!(a.get(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_inner_signal() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let rw_signal = RwSignal::new(1);
|
||||
assert_eq!(rw_signal.get(), 1);
|
||||
assert_eq!(rw_signal.into_inner(), Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_inner_arc_signal() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let (a, b) = arc_signal(2);
|
||||
assert_eq!(a.get(), 2);
|
||||
std::mem::drop(b);
|
||||
assert_eq!(a.into_inner(), Some(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_inner_non_arc_signal() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let (a, b) = signal(2);
|
||||
assert_eq!(a.get(), 2);
|
||||
b.dispose();
|
||||
assert_eq!(a.into_inner(), Some(2));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores"
|
||||
version = "0.1.0-rc1"
|
||||
version = "0.1.2"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -23,3 +23,6 @@ tokio = { version = "1.41", features = ["rt-multi-thread", "macros"] }
|
||||
tokio-test = { version = "0.4.4" }
|
||||
any_spawner = { workspace = true, features = ["futures-executor", "tokio"] }
|
||||
reactive_graph = { workspace = true, features = ["effects"] }
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
|
||||
@@ -7,6 +7,7 @@ use reactive_graph::{
|
||||
owner::Storage,
|
||||
traits::{
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
Write,
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
@@ -17,17 +18,22 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// Reference-counted access to a single field of type `T`.
|
||||
///
|
||||
/// This can be used to erase the chain of field-accessors, to make it easier to pass this into
|
||||
/// another component or function without needing to specify the full type signature.
|
||||
pub struct ArcField<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: &'static Location<'static>,
|
||||
path: StorePath,
|
||||
trigger: StoreFieldTrigger,
|
||||
get_trigger: Arc<dyn Fn(StorePath) -> StoreFieldTrigger + Send + Sync>,
|
||||
read: Arc<dyn Fn() -> Option<StoreFieldReader<T>> + Send + Sync>,
|
||||
write: Arc<dyn Fn() -> Option<StoreFieldWriter<T>> + Send + Sync>,
|
||||
pub(crate) write:
|
||||
Arc<dyn Fn() -> Option<StoreFieldWriter<T>> + Send + Sync>,
|
||||
keys: Arc<dyn Fn() -> Option<KeyMap> + Send + Sync>,
|
||||
track_field: Arc<dyn Fn() + Send + Sync>,
|
||||
}
|
||||
@@ -110,7 +116,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: Store<T, S>) -> Self {
|
||||
ArcField {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
path: value.path().into_iter().collect(),
|
||||
trigger: value.get_trigger(value.path().into_iter().collect()),
|
||||
@@ -130,7 +136,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: ArcStore<T>) -> Self {
|
||||
ArcField {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
path: value.path().into_iter().collect(),
|
||||
trigger: value.get_trigger(value.path().into_iter().collect()),
|
||||
@@ -168,7 +174,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: Subfield<Inner, Prev, T>) -> Self {
|
||||
ArcField {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
path: value.path().into_iter().collect(),
|
||||
trigger: value.get_trigger(value.path().into_iter().collect()),
|
||||
@@ -206,7 +212,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: AtIndex<Inner, Prev>) -> Self {
|
||||
ArcField {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
path: value.path().into_iter().collect(),
|
||||
trigger: value.get_trigger(value.path().into_iter().collect()),
|
||||
@@ -248,7 +254,7 @@ where
|
||||
#[track_caller]
|
||||
fn from(value: AtKeyed<Inner, Prev, K, T>) -> Self {
|
||||
ArcField {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: Location::caller(),
|
||||
path: value.path().into_iter().collect(),
|
||||
trigger: value.get_trigger(value.path().into_iter().collect()),
|
||||
@@ -279,7 +285,7 @@ where
|
||||
impl<T> Clone for ArcField<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
defined_at: self.defined_at,
|
||||
path: self.path.clone(),
|
||||
trigger: self.trigger.clone(),
|
||||
@@ -294,11 +300,11 @@ impl<T> Clone for ArcField<T> {
|
||||
|
||||
impl<T> DefinedAt for ArcField<T> {
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
{
|
||||
Some(self.defined_at)
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
@@ -325,6 +331,22 @@ impl<T> ReadUntracked for ArcField<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Write for ArcField<T> {
|
||||
type Value = T;
|
||||
|
||||
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
|
||||
(self.write)()
|
||||
}
|
||||
|
||||
fn try_write_untracked(
|
||||
&self,
|
||||
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||
let mut guard = (self.write)()?;
|
||||
guard.untrack();
|
||||
Some(guard)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IsDisposed for ArcField<T> {
|
||||
fn is_disposed(&self) -> bool {
|
||||
false
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user