@solidjs/signals 2.0.0-beta.25 → 2.0.0-beta.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev.js +113 -7
- package/dist/node.cjs +140 -41
- package/dist/prod/core/async.js +180 -107
- package/dist/prod/core/core.js +162 -162
- package/dist/prod/core/effect.js +28 -28
- package/dist/prod/core/external.js +4 -4
- package/dist/prod/core/graph.js +28 -28
- package/dist/prod/core/heap.js +30 -30
- package/dist/prod/core/lanes.js +15 -15
- package/dist/prod/core/optimistic.js +38 -38
- package/dist/prod/core/owner.js +44 -44
- package/dist/prod/core/scheduler.js +94 -94
- package/dist/prod/core/verdict.js +55 -55
- package/dist/prod/map.js +60 -60
- package/dist/prod/signals.js +1 -1
- package/dist/prod/store/optimistic.js +6 -6
- package/dist/prod/store/reconcile.js +1 -1
- package/dist/prod/store/store.js +58 -32
- package/dist/types/core/async.d.ts +1 -0
- package/dist/types-cjs/core/async.d.cts +1 -0
- package/package.json +1 -1
package/dist/dev.js
CHANGED
|
@@ -1707,8 +1707,40 @@ function forEachDependent(el, fn) {
|
|
|
1707
1707
|
// Queue a node to re-run on the next flush (used both when a pending source
|
|
1708
1708
|
// settles and when an `isPending` observer must re-evaluate after a real error):
|
|
1709
1709
|
// shared scheduling helper in heap.ts (tracked effects bypass the heap).
|
|
1710
|
+
// Settle-time counterpart of unlinkSubs' last-one-out check. A lazy node that
|
|
1711
|
+
// loses its last subscriber while STATUS_PENDING is exempt from autodispose
|
|
1712
|
+
// (the in-flight work is an observer), so whatever CLEARS that pending state
|
|
1713
|
+
// must run the release — otherwise the node stays linked and recomputes
|
|
1714
|
+
// forever with zero subscribers (#2934). The node's own promise/iterator
|
|
1715
|
+
// callbacks handle their own release (settleAutodispose in handleAsync); this
|
|
1716
|
+
// covers derivatively-pending dependents, which have no callbacks of their own.
|
|
1717
|
+
function releaseIfSettledUnobserved(node) {
|
|
1718
|
+
node._fn &&
|
|
1719
|
+
node._config & CONFIG_AUTO_DISPOSE &&
|
|
1720
|
+
!node._subs &&
|
|
1721
|
+
!(node._flags & REACTIVE_ZOMBIE) &&
|
|
1722
|
+
!(node._statusFlags & STATUS_PENDING) &&
|
|
1723
|
+
unobserved(node);
|
|
1724
|
+
}
|
|
1725
|
+
// Error-path sweep: notifyStatus(STATUS_ERROR) clears dependents' pending
|
|
1726
|
+
// sources through its own recursion (no per-node settle callback), so after
|
|
1727
|
+
// the propagation completes, walk the same graph for stranded lazy nodes.
|
|
1728
|
+
// Collect-then-release so unobserved() never unlinks under the walk.
|
|
1729
|
+
function releaseSettledDependents(el) {
|
|
1730
|
+
let candidates;
|
|
1731
|
+
const visited = new Set();
|
|
1732
|
+
const visit = node => {
|
|
1733
|
+
if (visited.has(node)) return;
|
|
1734
|
+
visited.add(node);
|
|
1735
|
+
if (!node._subs && node._config & CONFIG_AUTO_DISPOSE) (candidates ??= []).push(node);
|
|
1736
|
+
forEachDependent(node, visit);
|
|
1737
|
+
};
|
|
1738
|
+
forEachDependent(el, visit);
|
|
1739
|
+
if (candidates) for (const node of candidates) releaseIfSettledUnobserved(node);
|
|
1740
|
+
}
|
|
1710
1741
|
function settlePendingSource(el) {
|
|
1711
1742
|
let scheduled = false;
|
|
1743
|
+
let released;
|
|
1712
1744
|
const visited = new Set();
|
|
1713
1745
|
// Companion updates no-op without the verdict layer (null hook).
|
|
1714
1746
|
const updateCompanions = GlobalQueue._updatePendingSignal;
|
|
@@ -1729,10 +1761,17 @@ function settlePendingSource(el) {
|
|
|
1729
1761
|
scheduled = true;
|
|
1730
1762
|
}
|
|
1731
1763
|
node._blocked = false;
|
|
1764
|
+
// Fully settled with nobody watching: release candidate (#2934). Checked
|
|
1765
|
+
// again at release time — deferred so unobserved() can't unlink subs
|
|
1766
|
+
// lists this walk is still iterating.
|
|
1767
|
+
if (!node._subs && node._config & CONFIG_AUTO_DISPOSE) (released ??= []).push(node);
|
|
1732
1768
|
}
|
|
1733
1769
|
forEachDependent(node, settle);
|
|
1734
1770
|
};
|
|
1735
1771
|
forEachDependent(el, settle);
|
|
1772
|
+
// Release before the flush schedule below: unobserved() pulls the node back
|
|
1773
|
+
// out of the heap, so the enqueueSub above never recomputes a released node.
|
|
1774
|
+
if (released) for (const node of released) releaseIfSettledUnobserved(node);
|
|
1736
1775
|
if (scheduled) schedule();
|
|
1737
1776
|
}
|
|
1738
1777
|
// Object-thenable detection (Promises/A+ shape).
|
|
@@ -1776,12 +1815,41 @@ function handleAsync(el, result, setter) {
|
|
|
1776
1815
|
}
|
|
1777
1816
|
el._inFlight = result;
|
|
1778
1817
|
let syncValue;
|
|
1818
|
+
// Settle-time transition re-entry. The loading rail is invisible to
|
|
1819
|
+
// transactions (#2933): a boundary-caught first load never registers as an
|
|
1820
|
+
// async reporter, so its settle — the boundary's fallback -> content
|
|
1821
|
+
// reveal — must flow ambiently. The node can still carry a `_transition`
|
|
1822
|
+
// stamp (pending-node bookkeeping rides through the stamping sites), and
|
|
1823
|
+
// blindly re-entering that stamped, still-incomplete transaction stashed
|
|
1824
|
+
// the reveal with it — a deadlock when the transaction's completion
|
|
1825
|
+
// depended on the reveal (#2937). An ESCAPED first load did register and
|
|
1826
|
+
// keeps transition scheduling; initialized (value-holding) pending settles
|
|
1827
|
+
// are the transaction's reveal machinery and always re-enter.
|
|
1828
|
+
const settleTransition = () => {
|
|
1829
|
+
const transition = resolveTransition(el);
|
|
1830
|
+
if (
|
|
1831
|
+
transition &&
|
|
1832
|
+
el._statusFlags & STATUS_UNINITIALIZED &&
|
|
1833
|
+
!currentTransition(transition)._asyncReporters.has(el)
|
|
1834
|
+
) {
|
|
1835
|
+
// Drop the stale stamp too: the plain settle write (setSignal) and the
|
|
1836
|
+
// stash-path restamp both re-enter the transaction through it.
|
|
1837
|
+
el._transition = null;
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
globalQueue.initTransition(transition);
|
|
1841
|
+
};
|
|
1779
1842
|
const handleError = error => {
|
|
1780
1843
|
if (el._inFlight !== result) return;
|
|
1781
|
-
|
|
1844
|
+
settleTransition();
|
|
1782
1845
|
// NotReadyError from rejected promises should be treated as pending, not error
|
|
1783
|
-
|
|
1846
|
+
const stillPending = error instanceof NotReadyError;
|
|
1847
|
+
notifyStatus(el, stillPending ? STATUS_PENDING : STATUS_ERROR, error);
|
|
1784
1848
|
el._time = clock;
|
|
1849
|
+
// A real error settles derivatively-pending dependents (notifyStatus
|
|
1850
|
+
// cleared their pending sources), so stranded lazy ones release here —
|
|
1851
|
+
// the error twin of settlePendingSource's release (#2934).
|
|
1852
|
+
if (!stillPending) releaseSettledDependents(el);
|
|
1785
1853
|
};
|
|
1786
1854
|
const asyncWrite = (value, then) => {
|
|
1787
1855
|
if (el._inFlight !== result) return;
|
|
@@ -1789,7 +1857,7 @@ function handleAsync(el, result, setter) {
|
|
|
1789
1857
|
// skip this stale async result — the upcoming flush will recompute the node
|
|
1790
1858
|
// with the new value, creating a fresh Promise that supersedes this one.
|
|
1791
1859
|
if (el._flags & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY)) return;
|
|
1792
|
-
|
|
1860
|
+
settleTransition();
|
|
1793
1861
|
const wasUninitialized = !!(el._statusFlags & STATUS_UNINITIALIZED);
|
|
1794
1862
|
trimStaleDeps(el);
|
|
1795
1863
|
clearStatus(el);
|
|
@@ -1861,10 +1929,14 @@ function handleAsync(el, result, setter) {
|
|
|
1861
1929
|
// work (a lazy async memo would otherwise tear down and re-execute — one
|
|
1862
1930
|
// fetch per suspended re-read). Settling is that observer's release, so
|
|
1863
1931
|
// it runs the same last-one-out check the other release sites run.
|
|
1932
|
+
// Returns whether the node released, so the iterator branch can stop
|
|
1933
|
+
// pulling values instead of pumping an unobserved stream forever (#2935).
|
|
1864
1934
|
const settleAutodispose = () => {
|
|
1865
1935
|
if (el._config & CONFIG_AUTO_DISPOSE && !el._subs && !(el._statusFlags & STATUS_PENDING)) {
|
|
1866
1936
|
unobserved(el);
|
|
1937
|
+
return true;
|
|
1867
1938
|
}
|
|
1939
|
+
return false;
|
|
1868
1940
|
};
|
|
1869
1941
|
if (thenable) {
|
|
1870
1942
|
let resolved = false,
|
|
@@ -1916,6 +1988,12 @@ function handleAsync(el, result, setter) {
|
|
|
1916
1988
|
if (isThenable(returned)) returned.then(undefined, () => {});
|
|
1917
1989
|
} catch {}
|
|
1918
1990
|
});
|
|
1991
|
+
// Release check before each next pull: an unobserved lazy node must tear
|
|
1992
|
+
// down (its cleanup above closes the iterator) instead of pumping the
|
|
1993
|
+
// stream forever with zero subscribers (#2935).
|
|
1994
|
+
const iterateOrRelease = () => {
|
|
1995
|
+
if (!settleAutodispose()) iterate();
|
|
1996
|
+
};
|
|
1919
1997
|
const iterate = () => {
|
|
1920
1998
|
let syncResult,
|
|
1921
1999
|
syncError,
|
|
@@ -1932,7 +2010,7 @@ function handleAsync(el, result, setter) {
|
|
|
1932
2010
|
return;
|
|
1933
2011
|
} else if (!r.done) {
|
|
1934
2012
|
hadValue = true;
|
|
1935
|
-
asyncWrite(r.value,
|
|
2013
|
+
asyncWrite(r.value, iterateOrRelease);
|
|
1936
2014
|
} else {
|
|
1937
2015
|
completed = true;
|
|
1938
2016
|
if (hadValue) {
|
|
@@ -1942,6 +2020,7 @@ function handleAsync(el, result, setter) {
|
|
|
1942
2020
|
// Empty completion settles like the immediately-done sync path.
|
|
1943
2021
|
asyncWrite(undefined);
|
|
1944
2022
|
}
|
|
2023
|
+
settleAutodispose();
|
|
1945
2024
|
}
|
|
1946
2025
|
},
|
|
1947
2026
|
e => {
|
|
@@ -1951,6 +2030,7 @@ function handleAsync(el, result, setter) {
|
|
|
1951
2030
|
} else if (el._inFlight === result) {
|
|
1952
2031
|
completed = true;
|
|
1953
2032
|
handleError(e);
|
|
2033
|
+
settleAutodispose();
|
|
1954
2034
|
}
|
|
1955
2035
|
}
|
|
1956
2036
|
);
|
|
@@ -5543,6 +5623,14 @@ function isRawValue(value) {
|
|
|
5543
5623
|
}
|
|
5544
5624
|
function markRawOne(v) {
|
|
5545
5625
|
if (isWrappable(v)) {
|
|
5626
|
+
// A store proxy is already tracked elsewhere: the shallow boundary passes
|
|
5627
|
+
// it through by reference (replaced, never edited — same slot semantics
|
|
5628
|
+
// as a raw) instead of claiming it raw. The sticky mark is global, so
|
|
5629
|
+
// marking a live proxy would make wrap() serve it verbatim through every
|
|
5630
|
+
// OTHER store too — downstream deep stores then captured it instead of
|
|
5631
|
+
// wrapping it in their own family, and their writes landed in the
|
|
5632
|
+
// upstream store's override layer (#2932).
|
|
5633
|
+
if (v[$TARGET] !== undefined) return;
|
|
5546
5634
|
if (storeLookup.has(v))
|
|
5547
5635
|
throw new Error(
|
|
5548
5636
|
"shallow store: an ingested record is already tracked as a deep store — one value cannot present both wrapped and raw"
|
|
@@ -6281,7 +6369,14 @@ const storeTraps = {
|
|
|
6281
6369
|
}
|
|
6282
6370
|
// Untracked fall-through (tracked reads already threw via their node in
|
|
6283
6371
|
// read(); the dev strictRead error above wins first for memo parity).
|
|
6284
|
-
|
|
6372
|
+
// Observer-present reads must NOT re-consult the flag here: during the
|
|
6373
|
+
// settle flush the firewall has recomputed (first values live on the
|
|
6374
|
+
// pending rail, served by read() above) but its UNINITIALIZED clear is
|
|
6375
|
+
// deferred to batch commit — vetoing read()'s verdict with the stale flag
|
|
6376
|
+
// threw a fresh NotReadyError for an already-settled source, which no
|
|
6377
|
+
// sweep would ever release (#2938: projection over an async store wedged
|
|
6378
|
+
// its Loading boundary on `undefined`).
|
|
6379
|
+
if (!selfRead && !getObserver()) throwIfUninitialized(target);
|
|
6285
6380
|
return isWrappable(value) ? wrap(value, target) : value;
|
|
6286
6381
|
},
|
|
6287
6382
|
has(target, property) {
|
|
@@ -6315,8 +6410,19 @@ const storeTraps = {
|
|
|
6315
6410
|
const prevHas = prevLayer
|
|
6316
6411
|
? prevLayer[property] !== $DELETED
|
|
6317
6412
|
: property in target[STORE_VALUE];
|
|
6318
|
-
|
|
6319
|
-
|
|
6413
|
+
// Shallow slots hold store proxies verbatim (pass-through reference,
|
|
6414
|
+
// never raw-marked — see markRawOne/#2932); everything else unwraps
|
|
6415
|
+
// and marks as usual.
|
|
6416
|
+
const passThrough = !!target[STORE_SHALLOW] && rawValue?.[$TARGET] !== undefined;
|
|
6417
|
+
const value = passThrough ? rawValue : unwrapStoreValue(rawValue);
|
|
6418
|
+
if (target[STORE_SHALLOW] && !passThrough && isWrappable(value)) {
|
|
6419
|
+
// Flip the live gate too: a bare add was inert unless something else
|
|
6420
|
+
// had already marked a raw somewhere (wrap() checks rawValuesUsed
|
|
6421
|
+
// first), so the documented set-trap ingest mark silently no-oped in
|
|
6422
|
+
// apps whose only shallow data arrived through writes.
|
|
6423
|
+
rawValuesUsed = true;
|
|
6424
|
+
rawValues.add(value);
|
|
6425
|
+
}
|
|
6320
6426
|
// Symbol-keyed writes on arrays are metadata, not index writes — never run
|
|
6321
6427
|
// them through the numeric index/length machinery (`parseInt` on a symbol
|
|
6322
6428
|
// throws). #2769
|
package/dist/node.cjs
CHANGED
|
@@ -1572,32 +1572,68 @@ function forEachDependent(e, t) {
|
|
|
1572
1572
|
// Queue a node to re-run on the next flush (used both when a pending source
|
|
1573
1573
|
// settles and when an `isPending` observer must re-evaluate after a real error):
|
|
1574
1574
|
// shared scheduling helper in heap.ts (tracked effects bypass the heap).
|
|
1575
|
+
// Settle-time counterpart of unlinkSubs' last-one-out check. A lazy node that
|
|
1576
|
+
// loses its last subscriber while STATUS_PENDING is exempt from autodispose
|
|
1577
|
+
// (the in-flight work is an observer), so whatever CLEARS that pending state
|
|
1578
|
+
// must run the release — otherwise the node stays linked and recomputes
|
|
1579
|
+
// forever with zero subscribers (#2934). The node's own promise/iterator
|
|
1580
|
+
// callbacks handle their own release (settleAutodispose in handleAsync); this
|
|
1581
|
+
// covers derivatively-pending dependents, which have no callbacks of their own.
|
|
1582
|
+
function releaseIfSettledUnobserved(e) {
|
|
1583
|
+
e.Ue && e.Ge & CONFIG_AUTO_DISPOSE && !e.G && !(e.Le & REACTIVE_ZOMBIE) && !(e.xe & STATUS_PENDING) && unobserved(e);
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// Error-path sweep: notifyStatus(STATUS_ERROR) clears dependents' pending
|
|
1587
|
+
// sources through its own recursion (no per-node settle callback), so after
|
|
1588
|
+
// the propagation completes, walk the same graph for stranded lazy nodes.
|
|
1589
|
+
// Collect-then-release so unobserved() never unlinks under the walk.
|
|
1590
|
+
function releaseSettledDependents(e) {
|
|
1591
|
+
let t;
|
|
1592
|
+
const n = new Set;
|
|
1593
|
+
const visit = e => {
|
|
1594
|
+
if (n.has(e)) return;
|
|
1595
|
+
n.add(e);
|
|
1596
|
+
if (!e.G && e.Ge & CONFIG_AUTO_DISPOSE) (t ??= []).push(e);
|
|
1597
|
+
forEachDependent(e, visit);
|
|
1598
|
+
};
|
|
1599
|
+
forEachDependent(e, visit);
|
|
1600
|
+
if (t) for (const e of t) releaseIfSettledUnobserved(e);
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1575
1603
|
function settlePendingSource(e) {
|
|
1576
1604
|
let t = false;
|
|
1577
|
-
|
|
1605
|
+
let n;
|
|
1606
|
+
const r = new Set;
|
|
1578
1607
|
// Companion updates no-op without the verdict layer (null hook).
|
|
1579
|
-
const
|
|
1580
|
-
const settle =
|
|
1581
|
-
if (
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
const
|
|
1585
|
-
if (
|
|
1586
|
-
setPendingError(
|
|
1587
|
-
|
|
1608
|
+
const i = GlobalQueue.le;
|
|
1609
|
+
const settle = o => {
|
|
1610
|
+
if (r.has(o) || !removePendingSource(o, e)) return;
|
|
1611
|
+
r.add(o);
|
|
1612
|
+
o.F = clock;
|
|
1613
|
+
const s = o.$e?.values().next().value;
|
|
1614
|
+
if (s) {
|
|
1615
|
+
setPendingError(o, s);
|
|
1616
|
+
i !== null && i(o);
|
|
1588
1617
|
} else {
|
|
1589
|
-
|
|
1590
|
-
setPendingError(
|
|
1591
|
-
|
|
1592
|
-
if (
|
|
1593
|
-
enqueueSub(
|
|
1618
|
+
o.xe &= ~STATUS_PENDING;
|
|
1619
|
+
setPendingError(o);
|
|
1620
|
+
i !== null && i(o);
|
|
1621
|
+
if (o.It) {
|
|
1622
|
+
enqueueSub(o);
|
|
1594
1623
|
t = true;
|
|
1595
1624
|
}
|
|
1596
|
-
|
|
1625
|
+
o.It = false;
|
|
1626
|
+
// Fully settled with nobody watching: release candidate (#2934). Checked
|
|
1627
|
+
// again at release time — deferred so unobserved() can't unlink subs
|
|
1628
|
+
// lists this walk is still iterating.
|
|
1629
|
+
if (!o.G && o.Ge & CONFIG_AUTO_DISPOSE) (n ??= []).push(o);
|
|
1597
1630
|
}
|
|
1598
|
-
forEachDependent(
|
|
1631
|
+
forEachDependent(o, settle);
|
|
1599
1632
|
};
|
|
1600
1633
|
forEachDependent(e, settle);
|
|
1634
|
+
// Release before the flush schedule below: unobserved() pulls the node back
|
|
1635
|
+
// out of the heap, so the enqueueSub above never recomputes a released node.
|
|
1636
|
+
if (n) for (const e of n) releaseIfSettledUnobserved(e);
|
|
1601
1637
|
if (t) schedule();
|
|
1602
1638
|
}
|
|
1603
1639
|
|
|
@@ -1621,12 +1657,37 @@ function handleAsync(e, t, n) {
|
|
|
1621
1657
|
}
|
|
1622
1658
|
e.ut = t;
|
|
1623
1659
|
let o;
|
|
1660
|
+
// Settle-time transition re-entry. The loading rail is invisible to
|
|
1661
|
+
// transactions (#2933): a boundary-caught first load never registers as an
|
|
1662
|
+
// async reporter, so its settle — the boundary's fallback -> content
|
|
1663
|
+
// reveal — must flow ambiently. The node can still carry a `_transition`
|
|
1664
|
+
// stamp (pending-node bookkeeping rides through the stamping sites), and
|
|
1665
|
+
// blindly re-entering that stamped, still-incomplete transaction stashed
|
|
1666
|
+
// the reveal with it — a deadlock when the transaction's completion
|
|
1667
|
+
// depended on the reveal (#2937). An ESCAPED first load did register and
|
|
1668
|
+
// keeps transition scheduling; initialized (value-holding) pending settles
|
|
1669
|
+
// are the transaction's reveal machinery and always re-enter.
|
|
1670
|
+
const settleTransition = () => {
|
|
1671
|
+
const t = resolveTransition(e);
|
|
1672
|
+
if (t && e.xe & STATUS_UNINITIALIZED && !currentTransition(t).M.has(e)) {
|
|
1673
|
+
// Drop the stale stamp too: the plain settle write (setSignal) and the
|
|
1674
|
+
// stash-path restamp both re-enter the transaction through it.
|
|
1675
|
+
e.T = null;
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
globalQueue.initTransition(t);
|
|
1679
|
+
};
|
|
1624
1680
|
const handleError = n => {
|
|
1625
1681
|
if (e.ut !== t) return;
|
|
1626
|
-
|
|
1682
|
+
settleTransition();
|
|
1627
1683
|
// NotReadyError from rejected promises should be treated as pending, not error
|
|
1628
|
-
|
|
1684
|
+
const r = n instanceof NotReadyError;
|
|
1685
|
+
notifyStatus(e, r ? STATUS_PENDING : STATUS_ERROR, n);
|
|
1629
1686
|
e.F = clock;
|
|
1687
|
+
// A real error settles derivatively-pending dependents (notifyStatus
|
|
1688
|
+
// cleared their pending sources), so stranded lazy ones release here —
|
|
1689
|
+
// the error twin of settlePendingSource's release (#2934).
|
|
1690
|
+
if (!r) releaseSettledDependents(e);
|
|
1630
1691
|
};
|
|
1631
1692
|
const asyncWrite = (r, i) => {
|
|
1632
1693
|
if (e.ut !== t) return;
|
|
@@ -1634,7 +1695,7 @@ function handleAsync(e, t, n) {
|
|
|
1634
1695
|
// skip this stale async result — the upcoming flush will recompute the node
|
|
1635
1696
|
// with the new value, creating a fresh Promise that supersedes this one.
|
|
1636
1697
|
if (e.Le & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY)) return;
|
|
1637
|
-
|
|
1698
|
+
settleTransition();
|
|
1638
1699
|
const o = !!(e.xe & STATUS_UNINITIALIZED);
|
|
1639
1700
|
trimStaleDeps(e);
|
|
1640
1701
|
clearStatus(e);
|
|
@@ -1706,10 +1767,14 @@ function handleAsync(e, t, n) {
|
|
|
1706
1767
|
// work (a lazy async memo would otherwise tear down and re-execute — one
|
|
1707
1768
|
// fetch per suspended re-read). Settling is that observer's release, so
|
|
1708
1769
|
// it runs the same last-one-out check the other release sites run.
|
|
1770
|
+
// Returns whether the node released, so the iterator branch can stop
|
|
1771
|
+
// pulling values instead of pumping an unobserved stream forever (#2935).
|
|
1709
1772
|
const settleAutodispose = () => {
|
|
1710
1773
|
if (e.Ge & CONFIG_AUTO_DISPOSE && !e.G && !(e.xe & STATUS_PENDING)) {
|
|
1711
1774
|
unobserved(e);
|
|
1775
|
+
return true;
|
|
1712
1776
|
}
|
|
1777
|
+
return false;
|
|
1713
1778
|
};
|
|
1714
1779
|
if (i) {
|
|
1715
1780
|
let n = false, r = false, i, s = true;
|
|
@@ -1755,6 +1820,12 @@ function handleAsync(e, t, n) {
|
|
|
1755
1820
|
if (isThenable(e)) e.then(undefined, () => {});
|
|
1756
1821
|
} catch {}
|
|
1757
1822
|
});
|
|
1823
|
+
// Release check before each next pull: an unobserved lazy node must tear
|
|
1824
|
+
// down (its cleanup above closes the iterator) instead of pumping the
|
|
1825
|
+
// stream forever with zero subscribers (#2935).
|
|
1826
|
+
const iterateOrRelease = () => {
|
|
1827
|
+
if (!settleAutodispose()) iterate();
|
|
1828
|
+
};
|
|
1758
1829
|
const iterate = () => {
|
|
1759
1830
|
let u, l, a = false, c = false, f = true;
|
|
1760
1831
|
n.next().then(n => {
|
|
@@ -1766,7 +1837,7 @@ function handleAsync(e, t, n) {
|
|
|
1766
1837
|
return;
|
|
1767
1838
|
} else if (!n.done) {
|
|
1768
1839
|
r = true;
|
|
1769
|
-
asyncWrite(n.value,
|
|
1840
|
+
asyncWrite(n.value, iterateOrRelease);
|
|
1770
1841
|
} else {
|
|
1771
1842
|
i = true;
|
|
1772
1843
|
if (r) {
|
|
@@ -1776,6 +1847,7 @@ function handleAsync(e, t, n) {
|
|
|
1776
1847
|
// Empty completion settles like the immediately-done sync path.
|
|
1777
1848
|
asyncWrite(undefined);
|
|
1778
1849
|
}
|
|
1850
|
+
settleAutodispose();
|
|
1779
1851
|
}
|
|
1780
1852
|
}, n => {
|
|
1781
1853
|
if (f) {
|
|
@@ -1784,6 +1856,7 @@ function handleAsync(e, t, n) {
|
|
|
1784
1856
|
} else if (e.ut === t) {
|
|
1785
1857
|
i = true;
|
|
1786
1858
|
handleError(n);
|
|
1859
|
+
settleAutodispose();
|
|
1787
1860
|
}
|
|
1788
1861
|
});
|
|
1789
1862
|
f = false;
|
|
@@ -4913,6 +4986,14 @@ function isRawValue(e) {
|
|
|
4913
4986
|
|
|
4914
4987
|
function markRawOne(e) {
|
|
4915
4988
|
if (isWrappable(e)) {
|
|
4989
|
+
// A store proxy is already tracked elsewhere: the shallow boundary passes
|
|
4990
|
+
// it through by reference (replaced, never edited — same slot semantics
|
|
4991
|
+
// as a raw) instead of claiming it raw. The sticky mark is global, so
|
|
4992
|
+
// marking a live proxy would make wrap() serve it verbatim through every
|
|
4993
|
+
// OTHER store too — downstream deep stores then captured it instead of
|
|
4994
|
+
// wrapping it in their own family, and their writes landed in the
|
|
4995
|
+
// upstream store's override layer (#2932).
|
|
4996
|
+
if (e[$TARGET] !== undefined) return;
|
|
4916
4997
|
rawValuesUsed = true;
|
|
4917
4998
|
rawValues.add(e);
|
|
4918
4999
|
}
|
|
@@ -5559,7 +5640,14 @@ const storeTraps = {
|
|
|
5559
5640
|
}
|
|
5560
5641
|
// Untracked fall-through (tracked reads already threw via their node in
|
|
5561
5642
|
// read(); the dev strictRead error above wins first for memo parity).
|
|
5562
|
-
|
|
5643
|
+
// Observer-present reads must NOT re-consult the flag here: during the
|
|
5644
|
+
// settle flush the firewall has recomputed (first values live on the
|
|
5645
|
+
// pending rail, served by read() above) but its UNINITIALIZED clear is
|
|
5646
|
+
// deferred to batch commit — vetoing read()'s verdict with the stale flag
|
|
5647
|
+
// threw a fresh NotReadyError for an already-settled source, which no
|
|
5648
|
+
// sweep would ever release (#2938: projection over an async store wedged
|
|
5649
|
+
// its Loading boundary on `undefined`).
|
|
5650
|
+
if (!r && !getObserver()) throwIfUninitialized(e);
|
|
5563
5651
|
return isWrappable(f) ? wrap(f, e) : f;
|
|
5564
5652
|
},
|
|
5565
5653
|
has(e, t) {
|
|
@@ -5591,37 +5679,48 @@ const storeTraps = {
|
|
|
5591
5679
|
const u = getOverlayLayer(e, t);
|
|
5592
5680
|
const l = u ? u[t] : i;
|
|
5593
5681
|
const a = u ? u[t] !== $DELETED : t in e[STORE_VALUE];
|
|
5594
|
-
|
|
5595
|
-
|
|
5682
|
+
// Shallow slots hold store proxies verbatim (pass-through reference,
|
|
5683
|
+
// never raw-marked — see markRawOne/#2932); everything else unwraps
|
|
5684
|
+
// and marks as usual.
|
|
5685
|
+
const c = !!e[STORE_SHALLOW] && n?.[$TARGET] !== undefined;
|
|
5686
|
+
const f = c ? n : unwrapStoreValue(n);
|
|
5687
|
+
if (e[STORE_SHALLOW] && !c && isWrappable(f)) {
|
|
5688
|
+
// Flip the live gate too: a bare add was inert unless something else
|
|
5689
|
+
// had already marked a raw somewhere (wrap() checks rawValuesUsed
|
|
5690
|
+
// first), so the documented set-trap ingest mark silently no-oped in
|
|
5691
|
+
// apps whose only shallow data arrived through writes.
|
|
5692
|
+
rawValuesUsed = true;
|
|
5693
|
+
rawValues.add(f);
|
|
5694
|
+
}
|
|
5596
5695
|
// Symbol-keyed writes on arrays are metadata, not index writes — never run
|
|
5597
5696
|
// them through the numeric index/length machinery (`parseInt` on a symbol
|
|
5598
5697
|
// throws). #2769
|
|
5599
|
-
const
|
|
5600
|
-
const
|
|
5601
|
-
const
|
|
5602
|
-
const
|
|
5603
|
-
const
|
|
5604
|
-
if (l ===
|
|
5698
|
+
const E = typeof t === "string" ? Number(t) : -1;
|
|
5699
|
+
const d = Array.isArray(s) && Number.isInteger(E) && E >= 0 && E < 4294967295 && String(E) === t;
|
|
5700
|
+
const S = d ? E + 1 : 0;
|
|
5701
|
+
const T = d && (getOverlayLayer(e, "length") ?? s).length;
|
|
5702
|
+
const O = d && S > T ? S : undefined;
|
|
5703
|
+
if (l === f && O === undefined) return true;
|
|
5605
5704
|
armOptimisticStoreWrite(e, r);
|
|
5606
|
-
if (
|
|
5705
|
+
if (f !== undefined && f === i && O === undefined) {
|
|
5607
5706
|
delete e[o]?.[t];
|
|
5608
5707
|
if (o === STORE_OPTIMISTIC_OVERRIDE) delete e[STORE_OPTIMISTIC_OWNERS]?.[t];
|
|
5609
5708
|
} else {
|
|
5610
5709
|
const n = e[o] || (e[o] = Object.create(null));
|
|
5611
|
-
n[t] =
|
|
5710
|
+
n[t] = f;
|
|
5612
5711
|
stampOptimisticOwner(e, o, t);
|
|
5613
|
-
if (
|
|
5614
|
-
n.length =
|
|
5712
|
+
if (O !== undefined) {
|
|
5713
|
+
n.length = O;
|
|
5615
5714
|
stampOptimisticOwner(e, o, "length");
|
|
5616
5715
|
}
|
|
5617
5716
|
}
|
|
5618
|
-
notifyStoreProperty(e, t, "set",
|
|
5717
|
+
notifyStoreProperty(e, t, "set", f, l, a);
|
|
5619
5718
|
// Shrinking an array's length must remove the truncated indices, otherwise
|
|
5620
5719
|
// they leak through `has`, `ownKeys`, and (tracked) index reads from the
|
|
5621
5720
|
// underlying value. Mark each as deleted and notify so reactive reads update. #2768
|
|
5622
|
-
if (Array.isArray(s) && t === "length" && typeof
|
|
5721
|
+
if (Array.isArray(s) && t === "length" && typeof f === "number" && typeof l === "number" && f < l) {
|
|
5623
5722
|
const t = e[o] || (e[o] = Object.create(null));
|
|
5624
|
-
for (let n =
|
|
5723
|
+
for (let n = f; n < l; n++) {
|
|
5625
5724
|
if (t[n] === $DELETED) continue;
|
|
5626
5725
|
const r = n in t ? t[n] : s[n];
|
|
5627
5726
|
if (!(n in t) && !(n in s)) continue;
|
|
@@ -5631,13 +5730,13 @@ const storeTraps = {
|
|
|
5631
5730
|
}
|
|
5632
5731
|
}
|
|
5633
5732
|
// notify length change
|
|
5634
|
-
if (Array.isArray(s) && t !== "length" &&
|
|
5733
|
+
if (Array.isArray(s) && t !== "length" && O !== undefined) {
|
|
5635
5734
|
const t = getNodes(e, STORE_NODE);
|
|
5636
5735
|
if (t.length) {
|
|
5637
|
-
setSignal(t.length,
|
|
5736
|
+
setSignal(t.length, O);
|
|
5638
5737
|
} else if (!projectionWriteActive && !e[STORE_OPTIMISTIC]) {
|
|
5639
|
-
const n = upsertStoreNode(e, t, "length",
|
|
5640
|
-
setSignal(n,
|
|
5738
|
+
const n = upsertStoreNode(e, t, "length", T, e[STORE_SNAPSHOT_PROPS]);
|
|
5739
|
+
setSignal(n, O);
|
|
5641
5740
|
}
|
|
5642
5741
|
}
|
|
5643
5742
|
if (false) ;
|