@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 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
- globalQueue.initTransition(resolveTransition(el));
1844
+ settleTransition();
1782
1845
  // NotReadyError from rejected promises should be treated as pending, not error
1783
- notifyStatus(el, error instanceof NotReadyError ? STATUS_PENDING : STATUS_ERROR, error);
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
- globalQueue.initTransition(resolveTransition(el));
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, iterate);
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
- if (!selfRead) throwIfUninitialized(target);
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
- const value = unwrapStoreValue(rawValue);
6319
- if (target[STORE_SHALLOW] && isWrappable(value)) rawValues.add(value);
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
- const n = new Set;
1605
+ let n;
1606
+ const r = new Set;
1578
1607
  // Companion updates no-op without the verdict layer (null hook).
1579
- const r = GlobalQueue.le;
1580
- const settle = i => {
1581
- if (n.has(i) || !removePendingSource(i, e)) return;
1582
- n.add(i);
1583
- i.F = clock;
1584
- const o = i.$e?.values().next().value;
1585
- if (o) {
1586
- setPendingError(i, o);
1587
- r !== null && r(i);
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
- i.xe &= ~STATUS_PENDING;
1590
- setPendingError(i);
1591
- r !== null && r(i);
1592
- if (i.It) {
1593
- enqueueSub(i);
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
- i.It = false;
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(i, settle);
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
- globalQueue.initTransition(resolveTransition(e));
1682
+ settleTransition();
1627
1683
  // NotReadyError from rejected promises should be treated as pending, not error
1628
- notifyStatus(e, n instanceof NotReadyError ? STATUS_PENDING : STATUS_ERROR, n);
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
- globalQueue.initTransition(resolveTransition(e));
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, iterate);
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
- if (!r) throwIfUninitialized(e);
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
- const c = unwrapStoreValue(n);
5595
- if (e[STORE_SHALLOW] && isWrappable(c)) rawValues.add(c);
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 f = typeof t === "string" ? Number(t) : -1;
5600
- const E = Array.isArray(s) && Number.isInteger(f) && f >= 0 && f < 4294967295 && String(f) === t;
5601
- const d = E ? f + 1 : 0;
5602
- const S = E && (getOverlayLayer(e, "length") ?? s).length;
5603
- const T = E && d > S ? d : undefined;
5604
- if (l === c && T === undefined) return true;
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 (c !== undefined && c === i && T === undefined) {
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] = c;
5710
+ n[t] = f;
5612
5711
  stampOptimisticOwner(e, o, t);
5613
- if (T !== undefined) {
5614
- n.length = T;
5712
+ if (O !== undefined) {
5713
+ n.length = O;
5615
5714
  stampOptimisticOwner(e, o, "length");
5616
5715
  }
5617
5716
  }
5618
- notifyStoreProperty(e, t, "set", c, l, a);
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 c === "number" && typeof l === "number" && c < l) {
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 = c; n < l; 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" && T !== undefined) {
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, T);
5736
+ setSignal(t.length, O);
5638
5737
  } else if (!projectionWriteActive && !e[STORE_OPTIMISTIC]) {
5639
- const n = upsertStoreNode(e, t, "length", S, e[STORE_SNAPSHOT_PROPS]);
5640
- setSignal(n, T);
5738
+ const n = upsertStoreNode(e, t, "length", T, e[STORE_SNAPSHOT_PROPS]);
5739
+ setSignal(n, O);
5641
5740
  }
5642
5741
  }
5643
5742
  if (false) ;