Compare commits

..

2 Commits

Author SHA1 Message Date
Greg Johnston
ea3d772eb7 fix: ensure that store subfield mutations notify from the root down (closes #3704) 2025-03-14 17:50:42 -04:00
Zak Stucke
2a19515c41 Allow LocalResource sync methods to be used outside Suspense 2025-03-13 10:24:37 +00:00
24 changed files with 197 additions and 439 deletions

42
Cargo.lock generated
View File

@@ -1734,7 +1734,7 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
[[package]]
name = "leptos"
version = "0.7.8"
version = "0.7.7"
dependencies = [
"any_spawner",
"base64",
@@ -1784,7 +1784,7 @@ dependencies = [
[[package]]
name = "leptos_actix"
version = "0.7.8"
version = "0.7.7"
dependencies = [
"actix-files",
"actix-http",
@@ -1809,7 +1809,7 @@ dependencies = [
[[package]]
name = "leptos_axum"
version = "0.7.8"
version = "0.7.7"
dependencies = [
"any_spawner",
"axum",
@@ -1832,7 +1832,7 @@ dependencies = [
[[package]]
name = "leptos_config"
version = "0.7.8"
version = "0.7.7"
dependencies = [
"config",
"regex",
@@ -1846,7 +1846,7 @@ dependencies = [
[[package]]
name = "leptos_dom"
version = "0.7.8"
version = "0.7.7"
dependencies = [
"js-sys",
"leptos",
@@ -1863,7 +1863,7 @@ dependencies = [
[[package]]
name = "leptos_hot_reload"
version = "0.7.8"
version = "0.7.7"
dependencies = [
"anyhow",
"camino",
@@ -1879,7 +1879,7 @@ dependencies = [
[[package]]
name = "leptos_integration_utils"
version = "0.7.8"
version = "0.7.7"
dependencies = [
"futures",
"hydration_context",
@@ -1892,7 +1892,7 @@ dependencies = [
[[package]]
name = "leptos_macro"
version = "0.7.8"
version = "0.7.7"
dependencies = [
"attribute-derive",
"cfg-if",
@@ -1911,7 +1911,7 @@ dependencies = [
"rstml",
"serde",
"server_fn",
"server_fn_macro 0.7.8",
"server_fn_macro 0.7.7",
"syn 2.0.98",
"tracing",
"trybuild",
@@ -1921,7 +1921,7 @@ dependencies = [
[[package]]
name = "leptos_meta"
version = "0.7.8"
version = "0.7.7"
dependencies = [
"futures",
"indexmap",
@@ -1936,7 +1936,7 @@ dependencies = [
[[package]]
name = "leptos_router"
version = "0.7.8"
version = "0.7.7"
dependencies = [
"any_spawner",
"either_of",
@@ -1960,7 +1960,7 @@ dependencies = [
[[package]]
name = "leptos_router_macro"
version = "0.7.8"
version = "0.7.7"
dependencies = [
"leptos_macro",
"leptos_router",
@@ -1972,7 +1972,7 @@ dependencies = [
[[package]]
name = "leptos_server"
version = "0.7.8"
version = "0.7.7"
dependencies = [
"any_spawner",
"base64",
@@ -2658,7 +2658,7 @@ dependencies = [
[[package]]
name = "reactive_graph"
version = "0.1.8"
version = "0.1.7"
dependencies = [
"any_spawner",
"async-lock",
@@ -2680,7 +2680,7 @@ dependencies = [
[[package]]
name = "reactive_stores"
version = "0.1.8"
version = "0.1.7"
dependencies = [
"any_spawner",
"guardian",
@@ -2697,7 +2697,7 @@ dependencies = [
[[package]]
name = "reactive_stores_macro"
version = "0.1.8"
version = "0.1.7"
dependencies = [
"convert_case 0.7.1",
"proc-macro-error2",
@@ -3155,7 +3155,7 @@ dependencies = [
[[package]]
name = "server_fn"
version = "0.7.8"
version = "0.7.7"
dependencies = [
"actix-web",
"axum",
@@ -3211,7 +3211,7 @@ dependencies = [
[[package]]
name = "server_fn_macro"
version = "0.7.8"
version = "0.7.7"
dependencies = [
"const_format",
"convert_case 0.6.0",
@@ -3223,9 +3223,9 @@ dependencies = [
[[package]]
name = "server_fn_macro_default"
version = "0.7.8"
version = "0.7.7"
dependencies = [
"server_fn_macro 0.7.8",
"server_fn_macro 0.7.7",
"syn 2.0.98",
]
@@ -3419,7 +3419,7 @@ dependencies = [
[[package]]
name = "tachys"
version = "0.1.8"
version = "0.1.7"
dependencies = [
"any_spawner",
"async-trait",

View File

@@ -40,7 +40,7 @@ members = [
exclude = ["benchmarks", "examples", "projects"]
[workspace.package]
version = "0.7.8"
version = "0.7.7"
edition = "2021"
rust-version = "1.76"
@@ -51,16 +51,16 @@ const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
either_of = { path = "./either_of/", version = "0.1.5" }
hydration_context = { path = "./hydration_context", version = "0.2.0" }
itertools = "0.14.0"
leptos = { path = "./leptos", version = "0.7.8" }
leptos_config = { path = "./leptos_config", version = "0.7.8" }
leptos_dom = { path = "./leptos_dom", version = "0.7.8" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.8" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.8" }
leptos_macro = { path = "./leptos_macro", version = "0.7.8" }
leptos_router = { path = "./router", version = "0.7.8" }
leptos_router_macro = { path = "./router_macro", version = "0.7.8" }
leptos_server = { path = "./leptos_server", version = "0.7.8" }
leptos_meta = { path = "./meta", version = "0.7.8" }
leptos = { path = "./leptos", version = "0.7.7" }
leptos_config = { path = "./leptos_config", version = "0.7.7" }
leptos_dom = { path = "./leptos_dom", version = "0.7.7" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.7" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.7" }
leptos_macro = { path = "./leptos_macro", version = "0.7.7" }
leptos_router = { path = "./router", version = "0.7.7" }
leptos_router_macro = { path = "./router_macro", version = "0.7.7" }
leptos_server = { path = "./leptos_server", version = "0.7.7" }
leptos_meta = { path = "./meta", version = "0.7.7" }
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" }
@@ -68,9 +68,9 @@ reactive_graph = { path = "./reactive_graph", version = "0.1.7" }
reactive_stores = { path = "./reactive_stores", version = "0.1.7" }
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.7" }
serde_json = "1.0.0"
server_fn = { path = "./server_fn", version = "0.7.8" }
server_fn_macro = { path = "./server_fn_macro", version = "0.7.8" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.8" }
server_fn = { path = "./server_fn", version = "0.7.7" }
server_fn_macro = { path = "./server_fn_macro", version = "0.7.7" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.7" }
tachys = { path = "./tachys", version = "0.1.7" }
wasm-bindgen = { version = "0.2.100" }

View File

@@ -25,7 +25,7 @@ pub fn RouterExample() -> impl IntoView {
// contexts are passed down through the route tree
provide_context(ExampleContext(0));
// this signal will be used to set whether we are allowed to access a protected route
// this signal will be ued to set whether we are allowed to access a protected route
let (logged_in, set_logged_in) = signal(true);
let (is_routing, set_is_routing) = signal(false);

View File

@@ -1,99 +0,0 @@
@check_instrumented_issue_3719
Feature: Using instrumented counters to test regression from #3502.
Check that the suspend/suspense and the underlying resources are
called with the expected number of times. If this was already in
place by #3502 (5c43c18) it should have caught this regression.
For a better minimum demonstration see #3719.
Background:
Given I see the app
And I select the mode Instrumented
Scenario: follow all paths via CSR avoids #3502
Given I select the following links
| Item Listing |
| Item 1 |
| Inspect path2 |
| Inspect path2/field3 |
And I click on Reset CSR Counters
When I select the following links
| Inspect path2/field1 |
| Inspect path2/field2 |
And I go check the Counters
Then I see the following counters under section
| Suspend Calls | |
| item_listing | 0 |
| item_overview | 0 |
| item_inspect | 2 |
And the following counters under section
| Server Calls (CSR) | |
| list_items | 0 |
| get_item | 0 |
| inspect_item_root | 0 |
| inspect_item_field | 2 |
# To show that starting directly from within a param will simply
# cause the problem.
Scenario: Quicker way to demonstrate regression caused by #3502
Given I select the link Target 123
# And I click on Reset CSR Counters
When I select the following links
| Inspect path2/field1 |
| Inspect path2/field2 |
And I go check the Counters
Then I see the following counters under section
| Suspend Calls | |
| item_listing | 0 |
| item_overview | 0 |
| item_inspect | 3 |
And the following counters under section
| Server Calls (CSR) | |
| list_items | 1 |
| get_item | 1 |
| inspect_item_root | 0 |
| inspect_item_field | 4 |
Scenario: Follow paths ordinarily down to a target
Given I select the following links
| Item Listing |
| Item 1 |
And I click on Reset CSR Counters
When I select the following links
| Target 4## |
| Target 3## |
And I go check the Counters
Then I see the following counters under section
| Suspend Calls | |
| item_listing | 0 |
| item_overview | 2 |
| item_inspect | 0 |
And the following counters under section
| Server Calls (CSR) | |
| list_items | 0 |
| get_item | 2 |
| inspect_item_root | 0 |
| inspect_item_field | 0 |
Scenario: Same as above, but add a refresh to test hydration
Given I select the following links
| Item Listing |
| Item 1 |
And I refresh the page
And I click on Reset CSR Counters
When I select the following links
| Target 4## |
| Target 3## |
And I go check the Counters
Then I see the following counters under section
| Suspend Calls | |
| item_listing | 0 |
| item_overview | 2 |
| item_inspect | 0 |
And the following counters under section
| Server Calls (CSR) | |
| list_items | 0 |
| get_item | 2 |
| inspect_item_root | 0 |
| inspect_item_field | 0 |

View File

@@ -3,28 +3,12 @@ mod fixtures;
use anyhow::Result;
use cucumber::World;
use fixtures::world::AppWorld;
use std::{ffi::OsStr, fs::read_dir};
#[tokio::main]
async fn main() -> Result<()> {
// Normally the below is done, but it's now gotten to the point of
// having a sufficient number of tests where the resource contention
// of the concurrently running browsers will cause failures on CI.
// AppWorld::cucumber()
// .fail_on_skipped()
// .run_and_exit("./features")
// .await;
// Mitigate the issue by manually stepping through each feature,
// rather than letting cucumber glob them and dispatch all at once.
for entry in read_dir("./features")? {
let path = entry?.path();
if path.extension() == Some(OsStr::new("feature")) {
AppWorld::cucumber()
.fail_on_skipped()
.run_and_exit(path)
.await;
}
}
AppWorld::cucumber()
.fail_on_skipped()
.run_and_exit("./features")
.await;
Ok(())
}

View File

@@ -4,7 +4,7 @@ use leptos_router::{
hooks::use_params,
nested_router::Outlet,
params::Params,
ParamSegment, SsrMode, StaticSegment, WildcardSegment,
MatchNestedRoutes, ParamSegment, SsrMode, StaticSegment, WildcardSegment,
};
#[cfg(feature = "ssr")]
@@ -21,7 +21,6 @@ pub(super) mod counter {
pub struct Counter(AtomicU32);
impl Counter {
#[allow(dead_code)]
pub const fn new() -> Self {
Self(AtomicU32::new(0))
}
@@ -204,20 +203,20 @@ pub struct SuspenseCounters {
}
#[component]
pub fn InstrumentedRoutes() -> impl leptos_router::MatchNestedRoutes + Clone {
pub fn InstrumentedRoutes() -> impl MatchNestedRoutes + Clone {
// TODO should make this mode configurable via feature flag?
let ssr = SsrMode::Async;
view! {
<ParentRoute path=StaticSegment("instrumented") view=InstrumentedRoot ssr>
<Route path=StaticSegment("/") view=InstrumentedTop />
<Route path=StaticSegment("/") view=InstrumentedTop/>
<ParentRoute path=StaticSegment("item") view=ItemRoot>
<Route path=StaticSegment("/") view=ItemListing />
<Route path=StaticSegment("/") view=ItemListing/>
<ParentRoute path=ParamSegment("id") view=ItemTop>
<Route path=StaticSegment("/") view=ItemOverview />
<Route path=WildcardSegment("path") view=ItemInspect />
<Route path=StaticSegment("/") view=ItemOverview/>
<Route path=WildcardSegment("path") view=ItemInspect/>
</ParentRoute>
</ParentRoute>
<Route path=StaticSegment("counters") view=ShowCounters />
<Route path=StaticSegment("counters") view=ShowCounters/>
</ParentRoute>
}
.into_inner()
@@ -280,41 +279,32 @@ fn InstrumentedRoot() -> impl IntoView {
<section id="instrumented">
<nav>
<a href="/">"Site Root"</a>
<A href="./" exact=true>
"Instrumented Root"
</A>
<A href="item/" strict_trailing_slash=true>
"Item Listing"
</A>
<A href="counters" strict_trailing_slash=true>
"Counters"
</A>
<A href="./" exact=true>"Instrumented Root"</A>
<A href="item/" strict_trailing_slash=true>"Item Listing"</A>
<A href="counters" strict_trailing_slash=true>"Counters"</A>
</nav>
<FieldNavPortlet />
<Outlet />
<Suspense>
{move || Suspend::new(async move {
<FieldNavPortlet/>
<Outlet/>
<Suspense>{
move || Suspend::new(async move {
let clear_suspense_counters = move |_| {
counters.update(|c| *c = SuspenseCounters::default());
};
csr_ticket
.get()
.map(|ticket| {
let ticket = ticket.0;
view! {
<ActionForm action=reset_counters>
<input type="hidden" name="ticket" value=format!("{ticket}") />
<input
id="reset-csr-counters"
type="submit"
value="Reset CSR Counters"
on:click=clear_suspense_counters
/>
</ActionForm>
}
})
})}
</Suspense>
csr_ticket.get().map(|ticket| {
let ticket = ticket.0;
view! {
<ActionForm action=reset_counters>
<input type="hidden" name="ticket" value=format!("{ticket}") />
<input
id="reset-csr-counters"
type="submit"
value="Reset CSR Counters"
on:click=clear_suspense_counters/>
</ActionForm>
}
})
})
}</Suspense>
<footer>
<nav>
<A href="item/3/">"Target 3##"</A>
@@ -333,17 +323,11 @@ fn InstrumentedRoot() -> impl IntoView {
fn InstrumentedTop() -> impl IntoView {
view! {
<h1>"Instrumented Tests"</h1>
<p>
"These tests validates the number of invocations of server functions and suspenses per access."
</p>
<p>"These tests validates the number of invocations of server functions and suspenses per access."</p>
<ul>
// not using `A` because currently some bugs with artix
<li>
<a href="item/">"Item Listing"</a>
</li>
<li>
<a href="item/4/path1/">"Target 41#"</a>
</li>
<li><a href="item/">"Item Listing"</a></li>
<li><a href="item/4/path1/">"Target 41#"</a></li>
</ul>
}
}
@@ -358,7 +342,7 @@ fn ItemRoot() -> impl IntoView {
view! {
<h2>"<ItemRoot/>"</h2>
<Outlet />
<Outlet/>
}
}
@@ -376,9 +360,7 @@ fn ItemListing() -> impl IntoView {
// adding an extra `/` in artix; manually construct `a` instead.
// <li><A href=format!("./{item}/")>"Item "{item}</A></li>
view! {
<li>
<a href=format!("/instrumented/item/{item}/")>"Item "{item}</a>
</li>
<li><a href=format!("/instrumented/item/{item}/")>"Item "{item}</a></li>
}
)
.collect_view()
@@ -391,7 +373,9 @@ fn ItemListing() -> impl IntoView {
view! {
<h3>"<ItemListing/>"</h3>
<ul>
<Suspense>{item_listing}</Suspense>
<Suspense>
{item_listing}
</Suspense>
</ul>
}
}
@@ -418,7 +402,7 @@ fn ItemTop() -> impl IntoView {
));
view! {
<h4>"<ItemTop/>"</h4>
<Outlet />
<Outlet/>
}
}
@@ -428,29 +412,24 @@ fn ItemOverview() -> impl IntoView {
let resource = expect_context::<Resource<Option<GetItemResult>>>();
let item_view = move || {
Suspend::new(async move {
let result = resource.await.map(|GetItemResult(item, names)| {
view! {
<p>{format!("Viewing {item:?}")}</p>
<ul>
{names
.into_iter()
.map(|name| {
let id = item.id;
// FIXME seems like relative link isn't working, it is currently
// adding an extra `/` in artix; manually construct `a` instead.
// <li><A href=format!("./{name}/")>{format!("Inspect {name}")}</A></li>
view! {
<li>
<a href=format!(
"/instrumented/item/{id}/{name}/",
)>"Inspect "{name.clone()}</a>
</li>
}
})
.collect_view()}
</ul>
}
});
let result = resource.await.map(|GetItemResult(item, names)| view! {
<p>{format!("Viewing {item:?}")}</p>
<ul>{
names.into_iter()
.map(|name| {
// FIXME seems like relative link isn't working, it is currently
// adding an extra `/` in artix; manually construct `a` instead.
// <li><A href=format!("./{name}/")>{format!("Inspect {name}")}</A></li>
let id = item.id;
view! {
<li><a href=format!("/instrumented/item/{id}/{name}/")>
"Inspect "{name.clone()}
</a></li>
}
})
.collect_view()
}</ul>
});
suspense_counters.update_untracked(|c| c.item_overview += 1);
result
})
@@ -458,7 +437,9 @@ fn ItemOverview() -> impl IntoView {
view! {
<h5>"<ItemOverview/>"</h5>
<Suspense>{item_view}</Suspense>
<Suspense>
{item_view}
</Suspense>
}
}
@@ -492,9 +473,8 @@ fn ItemInspect() -> impl IntoView {
// result
},
);
let ws = use_context::<WriteSignal<Option<FieldNavCtx>>>();
on_cleanup(move || {
if let Some(c) = ws {
on_cleanup(|| {
if let Some(c) = use_context::<WriteSignal<Option<FieldNavCtx>>>() {
c.set(None);
}
});
@@ -516,26 +496,23 @@ fn ItemInspect() -> impl IntoView {
));
view! {
<p>{format!("Inspecting {item:?}")}</p>
<ul>
{fields
.iter()
<ul>{
fields.iter()
.map(|field| {
// FIXME seems like relative link to root for a wildcard isn't
// working as expected, so manually construct `a` instead.
// let text = format!("Inspect {name}/{field}");
// view! {
// <li><A href=format!("{field}")>{text}</A></li>
// <li><A href=format!("{field}")>{text}</A></li>
// }
view! {
<li>
<a href=format!(
"/instrumented/item/{id}/{name}/{field}",
)>{format!("Inspect {name}/{field}")}</a>
</li>
<li><a href=format!("/instrumented/item/{id}/{name}/{field}")>{
format!("Inspect {name}/{field}")
}</a></li>
}
})
.collect_view()}
</ul>
.collect_view()
}</ul>
}
});
suspense_counters.update_untracked(|c| c.item_inspect += 1);
@@ -550,7 +527,9 @@ fn ItemInspect() -> impl IntoView {
view! {
<h5>"<ItemInspect/>"</h5>
<Suspense>{inspect_view}</Suspense>
<Suspense>
{inspect_view}
</Suspense>
}
}
@@ -611,8 +590,7 @@ fn ShowCounters() -> impl IntoView {
id="reset-counters"
type="submit"
value="Reset Counters"
on:click=clear_suspense_counters
/>
on:click=clear_suspense_counters/>
</ActionForm>
}
})
@@ -623,23 +601,20 @@ fn ShowCounters() -> impl IntoView {
<h2>"Counters"</h2>
<h3 id="suspend-calls">"Suspend Calls"</h3>
{move || {
suspense_counters
.with(|c| {
view! {
<dl>
<dt>"item_listing"</dt>
<dd id="item_listing">{c.item_listing}</dd>
<dt>"item_overview"</dt>
<dd id="item_overview">{c.item_overview}</dd>
<dt>"item_inspect"</dt>
<dd id="item_inspect">{c.item_inspect}</dd>
</dl>
}
})
}}
{move || suspense_counters.with(|c| view! {
<dl>
<dt>"item_listing"</dt>
<dd id="item_listing">{c.item_listing}</dd>
<dt>"item_overview"</dt>
<dd id="item_overview">{c.item_overview}</dd>
<dt>"item_inspect"</dt>
<dd id="item_inspect">{c.item_inspect}</dd>
</dl>
})}
<Suspense>{counter_view}</Suspense>
<Suspense>
{counter_view}
</Suspense>
}
}
@@ -667,17 +642,17 @@ pub fn FieldNavPortlet() -> impl IntoView {
view! {
<div id="FieldNavPortlet">
<span>"FieldNavPortlet:"</span>
<nav>
{ctx
.0
.map(|ctx| {
ctx.into_iter()
.map(|FieldNavItem { href, text }| {
view! { <A href=href>{text}</A> }
})
.collect_view()
})}
</nav>
<nav>{
ctx.0.map(|ctx| {
ctx.into_iter()
.map(|FieldNavItem { href, text }| {
view! {
<A href=href>{text}</A>
}
})
.collect_view()
})
}</nav>
</div>
}
})

View File

@@ -1 +1,3 @@
@import "tailwindcss";
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1 +1,3 @@
@import "tailwindcss";
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_meta"
version = "0.7.8"
version = "0.7.7"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"

View File

@@ -8,9 +8,9 @@ codegen-units = 1
lto = true
[dependencies]
leptos = { version = "0.7.8", features = ["csr"] }
leptos_meta = { version = "0.7.8" }
leptos_router = { version = "0.7.8" }
leptos = { version = "0.7.7", features = ["csr"] }
leptos_meta = { version = "0.7.7" }
leptos_router = { version = "0.7.7" }
console_log = "1.0"
log = "0.4.22"
console_error_panic_hook = "0.1.7"

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_graph"
version = "0.1.8"
version = "0.1.7"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -60,38 +60,6 @@ pub struct Owner {
pub(crate) shared_context: Option<Arc<dyn SharedContext + Send + Sync>>,
}
impl Owner {
fn downgrade(&self) -> WeakOwner {
WeakOwner {
inner: Arc::downgrade(&self.inner),
#[cfg(feature = "hydration")]
shared_context: self.shared_context.as_ref().map(Arc::downgrade),
}
}
}
#[derive(Clone)]
struct WeakOwner {
inner: Weak<RwLock<OwnerInner>>,
#[cfg(feature = "hydration")]
shared_context: Option<Weak<dyn SharedContext + Send + Sync>>,
}
impl WeakOwner {
fn upgrade(&self) -> Option<Owner> {
self.inner.upgrade().map(|inner| {
#[cfg(feature = "hydration")]
let shared_context =
self.shared_context.as_ref().and_then(|sc| sc.upgrade());
Owner {
inner,
#[cfg(feature = "hydration")]
shared_context,
}
})
}
}
impl PartialEq for Owner {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
@@ -99,7 +67,7 @@ impl PartialEq for Owner {
}
thread_local! {
static OWNER: RefCell<Option<WeakOwner>> = Default::default();
static OWNER: RefCell<Option<Owner>> = Default::default();
}
impl Owner {
@@ -139,16 +107,12 @@ impl Owner {
/// Creates a new `Owner` and registers it as a child of the current `Owner`, if there is one.
pub fn new() -> Self {
#[cfg(not(feature = "hydration"))]
let parent = OWNER.with(|o| {
o.borrow()
.as_ref()
.and_then(|o| o.upgrade())
.map(|o| Arc::downgrade(&o.inner))
});
let parent = OWNER
.with(|o| o.borrow().as_ref().map(|o| Arc::downgrade(&o.inner)));
#[cfg(feature = "hydration")]
let (parent, shared_context) = OWNER
.with(|o| {
o.borrow().as_ref().and_then(|o| o.upgrade()).map(|o| {
o.borrow().as_ref().map(|o| {
(Some(Arc::downgrade(&o.inner)), o.shared_context.clone())
})
})
@@ -236,7 +200,7 @@ impl Owner {
/// Sets this as the current `Owner`.
pub fn set(&self) {
OWNER.with_borrow_mut(|owner| *owner = Some(self.downgrade()));
OWNER.with_borrow_mut(|owner| *owner = Some(self.clone()));
#[cfg(feature = "sandboxed-arenas")]
Arena::set(&self.inner.read().or_poisoned().arena);
}
@@ -244,9 +208,7 @@ impl Owner {
/// Runs the given function with this as the current `Owner`.
pub fn with<T>(&self, fun: impl FnOnce() -> T) -> T {
let prev = {
OWNER.with(|o| {
Option::replace(&mut *o.borrow_mut(), self.downgrade())
})
OWNER.with(|o| Option::replace(&mut *o.borrow_mut(), self.clone()))
};
#[cfg(feature = "sandboxed-arenas")]
Arena::set(&self.inner.read().or_poisoned().arena);
@@ -293,7 +255,7 @@ impl Owner {
/// Returns the current `Owner`, if any.
pub fn current() -> Option<Owner> {
OWNER.with(|o| o.borrow().as_ref().and_then(|n| n.upgrade()))
OWNER.with(|o| o.borrow().clone())
}
/// Returns the [`SharedContext`] associated with this owner, if any.
@@ -307,7 +269,7 @@ impl Owner {
/// Removes this from its state as the thread-local owner and drops it.
pub fn unset(self) {
OWNER.with_borrow_mut(|owner| {
if owner.as_ref().and_then(|n| n.upgrade()) == Some(self) {
if owner.as_ref() == Some(&self) {
mem::take(owner);
}
})
@@ -320,7 +282,6 @@ impl Owner {
OWNER.with(|o| {
o.borrow()
.as_ref()
.and_then(|o| o.upgrade())
.and_then(|current| current.shared_context.clone())
})
}
@@ -334,7 +295,6 @@ impl Owner {
let sc = OWNER.with_borrow(|o| {
o.as_ref()
.and_then(|o| o.upgrade())
.and_then(|current| current.shared_context.clone())
});
match sc {
@@ -360,7 +320,6 @@ impl Owner {
let sc = OWNER.with_borrow(|o| {
o.as_ref()
.and_then(|o| o.upgrade())
.and_then(|current| current.shared_context.clone())
});
match sc {

View File

@@ -48,30 +48,20 @@ impl Arena {
fun(&MAP.get_or_init(Default::default).read().or_poisoned())
}
#[cfg(feature = "sandboxed-arenas")]
{
Arena::try_with(fun).unwrap_or_else(|| {
panic!(
"at {}, the `sandboxed-arenas` feature is active, but no \
Arena is active",
std::panic::Location::caller()
)
})
}
}
#[track_caller]
pub fn try_with<U>(fun: impl FnOnce(&ArenaMap) -> U) -> Option<U> {
#[cfg(not(feature = "sandboxed-arenas"))]
{
Some(fun(&MAP.get_or_init(Default::default).read().or_poisoned()))
}
#[cfg(feature = "sandboxed-arenas")]
{
MAP.with_borrow(|arena| {
arena
fun(&arena
.as_ref()
.and_then(Weak::upgrade)
.map(|n| fun(&n.read().or_poisoned()))
.unwrap_or_else(|| {
panic!(
"at {}, the `sandboxed-arenas` feature is active, \
but no Arena is active",
std::panic::Location::caller()
)
})
.read()
.or_poisoned())
})
}
}
@@ -84,32 +74,20 @@ impl Arena {
}
#[cfg(feature = "sandboxed-arenas")]
{
Arena::try_with_mut(fun).unwrap_or_else(|| {
panic!(
"at {}, the `sandboxed-arenas` feature is active, but no \
Arena is active",
std::panic::Location::caller()
)
})
}
}
#[track_caller]
pub fn try_with_mut<U>(fun: impl FnOnce(&mut ArenaMap) -> U) -> Option<U> {
#[cfg(not(feature = "sandboxed-arenas"))]
{
Some(fun(&mut MAP
.get_or_init(Default::default)
.write()
.or_poisoned()))
}
#[cfg(feature = "sandboxed-arenas")]
{
let caller = std::panic::Location::caller();
MAP.with_borrow(|arena| {
arena
fun(&mut arena
.as_ref()
.and_then(Weak::upgrade)
.map(|n| fun(&mut n.write().or_poisoned()))
.unwrap_or_else(|| {
panic!(
"at {}, the `sandboxed-arenas` feature is active, \
but no Arena is active",
caller
)
})
.write()
.or_poisoned())
})
}
}
@@ -148,7 +126,6 @@ pub mod sandboxed {
/// called.
///
/// [item]:[crate::owner::ArenaItem]
#[track_caller]
pub fn new(inner: T) -> Self {
let arena = MAP.with_borrow(|n| n.as_ref().and_then(Weak::upgrade));
Self { arena, inner }

View File

@@ -53,7 +53,7 @@ where
})
};
OWNER.with(|o| {
if let Some(owner) = o.borrow().as_ref().and_then(|o| o.upgrade()) {
if let Some(owner) = &*o.borrow() {
owner.register(node);
}
});

View File

@@ -76,26 +76,24 @@ where
}
fn try_with<U>(node: NodeId, fun: impl FnOnce(&T) -> U) -> Option<U> {
Arena::try_with(|arena| {
Arena::with(|arena| {
let m = arena.get(node);
m.and_then(|n| n.downcast_ref::<T>()).map(fun)
})
.flatten()
}
fn try_with_mut<U>(
node: NodeId,
fun: impl FnOnce(&mut T) -> U,
) -> Option<U> {
Arena::try_with_mut(|arena| {
Arena::with_mut(|arena| {
let m = arena.get_mut(node);
m.and_then(|n| n.downcast_mut::<T>()).map(fun)
})
.flatten()
}
fn try_set(node: NodeId, value: T) -> Option<T> {
Arena::try_with_mut(|arena| {
Arena::with_mut(|arena| {
let m = arena.get_mut(node);
match m.and_then(|n| n.downcast_mut::<T>()) {
Some(inner) => {
@@ -105,7 +103,6 @@ where
None => Some(value),
}
})
.flatten()
}
fn take(node: NodeId) -> Option<T> {

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_stores"
version = "0.1.8"
version = "0.1.7"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_stores_macro"
version = "0.1.8"
version = "0.1.7"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -604,9 +604,9 @@ impl ToTokens for PatchModel {
let Field {
attrs, ident, ..
} = &field;
let locator = match &ident {
Some(ident) => Either::Left(ident),
None => Either::Right(Index::from(idx)),
let field_name = match &ident {
Some(ident) => quote! { #ident },
None => quote! { #idx },
};
let closure = attrs
.iter()
@@ -639,9 +639,9 @@ impl ToTokens for PatchModel {
let params = closure.inputs;
let body = closure.body;
quote! {
if new.#locator != self.#locator {
if new.#field_name != self.#field_name {
_ = {
let (#params) = (&mut self.#locator, new.#locator);
let (#params) = (&mut self.#field_name, new.#field_name);
#body
};
notify(&new_path);
@@ -651,8 +651,8 @@ impl ToTokens for PatchModel {
} else {
quote! {
#library_path::PatchField::patch_field(
&mut self.#locator,
new.#locator,
&mut self.#field_name,
new.#field_name,
&new_path,
notify
);
@@ -684,17 +684,3 @@ impl ToTokens for PatchModel {
});
}
}
enum Either<A, B> {
Left(A),
Right(B),
}
impl<A: ToTokens, B: ToTokens> ToTokens for Either<A, B> {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Either::Left(a) => a.to_tokens(tokens),
Either::Right(b) => b.to_tokens(tokens),
}
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router"
version = "0.7.8"
version = "0.7.7"
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"

View File

@@ -318,28 +318,6 @@ mod tests {
assert!(params.is_empty());
}
#[test]
fn static_before_param() {
let path = "/foo/bar";
let def = (StaticSegment("foo"), ParamSegment("b"));
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo/bar");
assert_eq!(matched.remaining(), "");
let params = matched.params();
assert_eq!(params[0], ("b".into(), "bar".into()));
}
#[test]
fn static_before_optional_param() {
let path = "/foo/bar";
let def = (StaticSegment("foo"), OptionalParamSegment("b"));
let matched = def.test(path).expect("couldn't match route");
assert_eq!(matched.matched(), "/foo/bar");
assert_eq!(matched.remaining(), "");
let params = matched.params();
assert_eq!(params[0], ("b".into(), "bar".into()));
}
#[test]
fn multiple_optional_params_match_first() {
let path = "/foo/bar";

View File

@@ -25,10 +25,7 @@ macro_rules! tuples {
let mut p = Vec::new();
let mut m = String::new();
if $first::OPTIONAL {
nth_field += 1;
}
if !$first::OPTIONAL || nth_field <= include_optionals {
if !$first::OPTIONAL || nth_field < include_optionals {
match $first.test(r) {
None => {
return None;
@@ -46,7 +43,7 @@ macro_rules! tuples {
if $ty::OPTIONAL {
nth_field += 1;
}
if !$ty::OPTIONAL || nth_field <= include_optionals {
if !$ty::OPTIONAL || nth_field < include_optionals {
let PartialPathMatch {
remaining,
matched,

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router_macro"
version = "0.7.8"
version = "0.7.7"
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"

4
server_fn/Cargo.lock generated
View File

@@ -198,7 +198,7 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.7.8"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd"
dependencies = [
@@ -880,7 +880,7 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash 0.7.8",
"ahash 0.7.7",
]
[[package]]

View File

@@ -1,6 +1,6 @@
[package]
name = "tachys"
version = "0.1.8"
version = "0.1.7"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"