@solidjs/signals 2.0.0-rc.16 → 2.0.0-rc.18

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.
Files changed (56) hide show
  1. package/dist/dev-shared.js +1972 -1751
  2. package/dist/dev.attribution.js +16 -16
  3. package/dist/dev.js +672 -319
  4. package/dist/observe/boundaries.js +2 -2
  5. package/dist/observe/core/async.js +151 -116
  6. package/dist/observe/core/constants.js +3 -1
  7. package/dist/observe/core/context.js +2 -0
  8. package/dist/observe/core/core.js +51 -35
  9. package/dist/observe/core/effect.js +29 -30
  10. package/dist/observe/core/graph.js +13 -11
  11. package/dist/observe/core/lanes.js +25 -14
  12. package/dist/observe/core/owner.js +77 -51
  13. package/dist/observe/core/scheduler.js +202 -175
  14. package/dist/observe/core/verdict.js +5 -2
  15. package/dist/observe/deferred.js +16 -13
  16. package/dist/observe/from.js +248 -0
  17. package/dist/observe/index.js +2 -0
  18. package/dist/observe/map.js +2 -2
  19. package/dist/observe/signals.js +6 -1
  20. package/dist/observe/store/affects.js +13 -13
  21. package/dist/observe/store/optimistic.js +112 -71
  22. package/dist/observe/store/reconcile.js +137 -121
  23. package/dist/observe/store/store.js +612 -626
  24. package/dist/observe/store/target.js +1 -5
  25. package/dist/prod/boundaries.js +2 -2
  26. package/dist/prod/core/async.js +211 -176
  27. package/dist/prod/core/constants.js +3 -1
  28. package/dist/prod/core/context.js +2 -0
  29. package/dist/prod/core/core.js +63 -47
  30. package/dist/prod/core/effect.js +24 -25
  31. package/dist/prod/core/graph.js +12 -10
  32. package/dist/prod/core/heap.js +1 -1
  33. package/dist/prod/core/lanes.js +39 -28
  34. package/dist/prod/core/owner.js +70 -44
  35. package/dist/prod/core/scheduler.js +78 -51
  36. package/dist/prod/core/truth.js +4 -4
  37. package/dist/prod/core/verdict.js +11 -8
  38. package/dist/prod/deferred.js +17 -14
  39. package/dist/prod/from.js +248 -0
  40. package/dist/prod/index.js +2 -0
  41. package/dist/prod/map.js +2 -2
  42. package/dist/prod/signals.js +18 -13
  43. package/dist/prod/store/optimistic.js +112 -71
  44. package/dist/prod/store/reconcile.js +137 -121
  45. package/dist/prod/store/store.js +109 -121
  46. package/dist/prod/store/target.js +13 -17
  47. package/dist/types/core/async.d.ts +12 -2
  48. package/dist/types/core/constants.d.ts +5 -0
  49. package/dist/types/core/core.d.ts +29 -6
  50. package/dist/types/core/dev.d.ts +9 -1
  51. package/dist/types/core/error.d.ts +9 -1
  52. package/dist/types/core/owner.d.ts +4 -0
  53. package/dist/types/core/scheduler.d.ts +38 -7
  54. package/dist/types/from.d.ts +47 -0
  55. package/dist/types/index.d.ts +1 -0
  56. package/package.json +1 -1
package/dist/dev.js CHANGED
@@ -64,121 +64,128 @@ import {
64
64
  a8 as link,
65
65
  a9 as pullComputed,
66
66
  aa as strictRead,
67
- ab as warnStrictReadUntracked,
68
- ac as staleReader,
69
- ad as globalQueue,
70
- ae as joinPass,
71
- af as markLateLinker,
72
- ag as spectating,
73
- ah as unflushedValue,
74
- ai as CONFIG_STAGED_ERROR,
75
- aj as stagedReaders,
76
- ak as bornInto,
77
- al as enterAction,
78
- am as setEffectCallback,
79
- an as exitAction,
80
- ao as isThenable,
81
- ap as actionDepth,
82
- aq as flush,
83
- ar as setQuestion,
84
- as as emitDiagnostic,
85
- at as enterCallback,
86
- au as exitCallback,
87
- av as getOwner,
88
- aw as inEffectCallback,
89
- ax as setExternalHooks,
90
- ay as signal,
91
- az as setSignal,
92
- aA as cleanup,
93
- aB as read,
94
- aC as DEV$1,
95
- aD as CONFIG_TRANSPARENT,
96
- aE as NoOwnerError,
97
- aF as ContextNotFoundError,
98
- aG as createEffectNode,
99
- aH as recompute,
100
- aI as EFFECT_USER,
101
- aJ as reportDiagnostic,
102
- aK as computed,
103
- aL as staleValues,
104
- aM as CONFIG_AUTO_DISPOSE,
105
- aN as EFFECT_TRACKED,
106
- aO as heldRunHook,
107
- aP as unwrapStatusError,
108
- aQ as haltReactivity,
109
- aR as setStrictRead,
110
- aS as StatusError,
111
- aT as trimStaleDeps,
112
- aU as setTrackedQueueCallback,
113
- aV as setEffectStatusNotify,
114
- aW as _hitUnhandledAsync,
115
- aX as resetUnhandledAsync,
116
- aY as ROOT_ERROR_HOOK,
117
- aZ as registerGraph,
118
- a_ as runWithOwner,
119
- a$ as setMemo,
120
- b0 as $REFRESH,
121
- b1 as markRefresh,
122
- b2 as createRoot,
123
- b3 as getObserver,
124
- b4 as dispose,
125
- b5 as TimeoutError,
126
- b6 as untrack,
127
- b7 as dirtyQueue,
128
- b8 as CONFIG_REDERIVE,
129
- b9 as passTx,
130
- ba as CONFIG_FRESH_READ,
131
- bb as createOwner,
132
- bc as writeHook,
133
- bd as REACTIVE_WRITE_IN_OWNED_SCOPE_SIGNAL_MESSAGE,
134
- be as ownedScopeWriteMessage,
135
- bf as isEqual,
136
- bg as CONFIG_MANUAL_WRITE,
137
- bh as snapshotCaptureActive,
138
- bi as captureWriteSnapshot,
139
- bj as notePromotedWrite,
140
- bk as slotSignal,
141
- bl as stagedScreen,
142
- bm as CONFIG_PROMOTED,
143
- bn as setSlotUnobserved,
144
- bo as installStoreHooks,
145
- bp as joinPassTx,
146
- bq as asyncTailFlights,
147
- br as untrackDepth,
148
- bs as checkPostAwaitRead,
149
- bt as disposalDepth,
150
- bu as insertIntoHeap,
151
- bv as isDisposed,
152
- bw as handleAsync,
153
- bx as addPendingSource,
154
- by as propagateStatus,
155
- bz as forEachDependent,
156
- bA as settlePendingSource,
157
- bB as notifyEpoch,
158
- bC as SUPPORTS_PROXY,
159
- bD as releaseMarksHook,
160
- bE as installMarkHooks,
161
- bF as CONFIG_LAGS,
162
- bG as REACTIVE_REASK,
163
- bH as heldUnder,
164
- bI as unhold,
165
- bJ as laneStageHook,
166
- bK as REACTIVE_ZOMBIE,
167
- bL as installBoundaryHooks,
168
- bM as CAUGHT_REARMED,
169
- bN as OBSERVE$1
67
+ ab as strictReadActive,
68
+ ac as warnStrictReadUntracked,
69
+ ad as staleReader,
70
+ ae as globalQueue,
71
+ af as joinPass,
72
+ ag as markLateLinker,
73
+ ah as spectating,
74
+ ai as unflushedValue,
75
+ aj as CONFIG_STAGED_ERROR,
76
+ ak as stagedReaders,
77
+ al as bornInto,
78
+ am as enterAction,
79
+ an as setEffectCallback,
80
+ ao as exitAction,
81
+ ap as isThenable,
82
+ aq as actionDepth,
83
+ ar as flush,
84
+ as as setQuestion,
85
+ at as emitDiagnostic,
86
+ au as enterCallback,
87
+ av as exitCallback,
88
+ aw as getOwner,
89
+ ax as inEffectCallback,
90
+ ay as effectCallbackEvent,
91
+ az as setExternalHooks,
92
+ aA as signal,
93
+ aB as setSignal,
94
+ aC as cleanup,
95
+ aD as read,
96
+ aE as DEV$1,
97
+ aF as CONFIG_TRANSPARENT,
98
+ aG as NoOwnerError,
99
+ aH as ContextNotFoundError,
100
+ aI as createEffectNode,
101
+ aJ as recompute,
102
+ aK as EFFECT_USER,
103
+ aL as reportDiagnostic,
104
+ aM as computed,
105
+ aN as staleValues,
106
+ aO as CONFIG_AUTO_DISPOSE,
107
+ aP as EFFECT_TRACKED,
108
+ aQ as heldRunHook,
109
+ aR as unwrapStatusError,
110
+ aS as setStrictRead,
111
+ aT as queueEvent,
112
+ aU as StatusError,
113
+ aV as trimStaleDeps,
114
+ aW as haltReactivityAt,
115
+ aX as strictReadEvent,
116
+ aY as setTrackedQueueCallback,
117
+ aZ as setEffectStatusNotify,
118
+ a_ as _hitUnhandledAsync,
119
+ a$ as resetUnhandledAsync,
120
+ b0 as ROOT_ERROR_HOOK,
121
+ b1 as registerGraph,
122
+ b2 as runWithOwner,
123
+ b3 as setMemo,
124
+ b4 as $REFRESH,
125
+ b5 as markRefresh,
126
+ b6 as createRoot,
127
+ b7 as getObserver,
128
+ b8 as dispose,
129
+ b9 as TimeoutError,
130
+ ba as untrack,
131
+ bb as dirtyQueue,
132
+ bc as CONFIG_REDERIVE,
133
+ bd as passTx,
134
+ be as CONFIG_FRESH_READ,
135
+ bf as createOwner,
136
+ bg as writeHook,
137
+ bh as REACTIVE_WRITE_IN_OWNED_SCOPE_SIGNAL_MESSAGE,
138
+ bi as ownedScopeWriteMessage,
139
+ bj as isEqual,
140
+ bk as CONFIG_MANUAL_WRITE,
141
+ bl as snapshotCaptureActive,
142
+ bm as captureWriteSnapshot,
143
+ bn as notePromotedWrite,
144
+ bo as slotSignal,
145
+ bp as stagedScreen,
146
+ bq as CONFIG_PROMOTED,
147
+ br as setSlotUnobserved,
148
+ bs as installStoreHooks,
149
+ bt as joinPassTx,
150
+ bu as asyncTailFlights,
151
+ bv as untrackDepth,
152
+ bw as checkPostAwaitRead,
153
+ bx as disposalDepth,
154
+ by as insertIntoHeap,
155
+ bz as isDisposed,
156
+ bA as handleAsync,
157
+ bB as addPendingSource,
158
+ bC as propagateStatus,
159
+ bD as forEachDependent,
160
+ bE as settlePendingSource,
161
+ bF as joinPassAt,
162
+ bG as notifyEpoch,
163
+ bH as SUPPORTS_PROXY,
164
+ bI as releaseMarksHook,
165
+ bJ as installMarkHooks,
166
+ bK as CONFIG_LAGS,
167
+ bL as REACTIVE_REASK,
168
+ bM as onSupersede,
169
+ bN as haltReactivity,
170
+ bO as heldUnder,
171
+ bP as unhold,
172
+ bQ as laneStageHook,
173
+ bR as REACTIVE_ZOMBIE,
174
+ bS as installBoundaryHooks,
175
+ bT as CAUGHT_REARMED,
176
+ bU as OBSERVE$1
170
177
  } from "./dev-shared.js";
171
178
  export {
172
- bO as clearSnapshots,
173
- bP as enforceLoadingBoundary,
174
- bQ as getNextChildId,
175
- bR as markSnapshotScope,
176
- bS as peekNextChildId,
177
- bT as releaseSnapshotScope,
178
- bU as resetErrorHalt,
179
- bV as setConsoleFooter,
180
- bW as setSnapshotCapture,
181
- bX as setWriteHook
179
+ bV as clearSnapshots,
180
+ bW as enforceLoadingBoundary,
181
+ bX as getNextChildId,
182
+ bY as markSnapshotScope,
183
+ bZ as peekNextChildId,
184
+ b_ as releaseSnapshotScope,
185
+ b$ as resetErrorHalt,
186
+ c0 as setConsoleFooter,
187
+ c1 as setSnapshotCapture,
188
+ c2 as setWriteHook
182
189
  } from "./dev-shared.js";
183
190
 
184
191
  // Truth reads (`until`, `refresh`; CONFIG_AUTHORITATIVE) over lane nodes.
@@ -696,12 +703,21 @@ function dissolveLane(l, into, except) {
696
703
  * member throws like any. */
697
704
  function laneRead(c, el) {
698
705
  const guess = el._config & CONFIG_GUESS;
706
+ const l = txOf(el);
707
+ // A lane a correction folded under (D-34): store/optimistic.ts reads it.
708
+ // Before the authoritative arm: an `until` outside the lane reads the
709
+ // same truth there, and joins the hold it folded into — its posture over
710
+ // a foreign hold (D-31), which the truth read alone would not take.
711
+ const folded = c !== null && l.folded;
712
+ if (folded) {
713
+ const v = folded(c, el);
714
+ if (v !== NOT_PENDING) return v;
715
+ }
699
716
  if (c !== null && c._config & CONFIG_AUTHORITATIVE) return GlobalQueue._truth(el);
700
717
  const status = el._statusFlags;
701
718
  // An errored member's lane value predates its error; the error is the answer (#4106).
702
719
  if (!guess && status & STATUS_ERROR) return NOT_PENDING;
703
720
  if (c === null) return laneValueOf(el);
704
- const l = txOf(el);
705
721
  if (!tracking || c._config & CONFIG_CHILDREN_FORBIDDEN) {
706
722
  // An untracked read inside lane work derives from the lane like a
707
723
  // tracked one, minus the subscription — `untrack` is about
@@ -869,10 +885,12 @@ function laneWrite(el, v) {
869
885
  // question is the one being asked (`question`: a slice's, a derive's
870
886
  // continuation's — the flight's); unstamped writes and mainline are
871
887
  // current.
872
- if (stale(el, question)) {
888
+ // A store re-bases a keyed arrangement guess over an older truth, and
889
+ // folds a current one beneath it (D-34): true.
890
+ const older = stale(el, question);
891
+ if (GlobalQueue._laneRebase?.(el, v, older) || older) {
873
892
  el._pendingValue = v;
874
893
  el._config |= CONFIG_HELD;
875
- GlobalQueue._laneRebase?.(el, v);
876
894
  schedule();
877
895
  return v;
878
896
  }
@@ -1325,6 +1343,9 @@ function verdictValue(el, c) {
1325
1343
  (!el._equals || !el._equals(el._x._lane, el._pendingValue))))
1326
1344
  )
1327
1345
  probeFound = true;
1346
+ // A lane a correction folded under (D-34): `latest()` shows the hold's
1347
+ // world whole — the truth beneath each guess, else what it covered.
1348
+ if (latestActive && l.folded) return l.folded(null, el);
1328
1349
  return el._x._lane;
1329
1350
  }
1330
1351
  if (probing && flying) probeFound = true;
@@ -1334,7 +1355,7 @@ function verdictValue(el, c) {
1334
1355
  // verdict read as on any untracked read; the pending throw they add for a
1335
1356
  // plain read does not apply — a verdict read is a sanctioned read of a
1336
1357
  // pending node (an uninitialized one still throws NotReady below).
1337
- if (strictRead)
1358
+ if (strictRead && strictReadActive())
1338
1359
  warnStrictReadUntracked(strictRead, {
1339
1360
  ownerId: c?.id,
1340
1361
  ownerName: c?._name,
@@ -1567,21 +1588,23 @@ function action(genFn) {
1567
1588
  // it may have been called from — a root created in it (`until`,
1568
1589
  // `resolve`) is deliberate (PRIMITIVE_IN_EFFECT_CALLBACK is off).
1569
1590
  let effectCallback = false;
1591
+ let effectEvent;
1570
1592
  {
1571
1593
  enterCallback();
1572
1594
  effectCallback = inEffectCallback;
1595
+ effectEvent = effectCallbackEvent;
1573
1596
  setEffectCallback(false);
1574
1597
  }
1575
1598
  try {
1576
1599
  r = err ? it.throw(v) : it.next(v);
1577
1600
  } catch (e) {
1578
1601
  exitAction();
1579
- (exitCallback(), setEffectCallback(effectCallback));
1602
+ (exitCallback(), setEffectCallback(effectCallback, effectEvent));
1580
1603
  if (attrHooks !== null) attrHooks.actionStepEnd(it);
1581
1604
  return done(undefined, e, true);
1582
1605
  }
1583
1606
  exitAction();
1584
- (exitCallback(), setEffectCallback(effectCallback));
1607
+ (exitCallback(), setEffectCallback(effectCallback, effectEvent));
1585
1608
  if (attrHooks !== null) attrHooks.actionStepEnd(it);
1586
1609
  // A rejected iterator result (async generators) means the error already
1587
1610
  // escaped the generator body — it is completed, and throwing back in
@@ -1776,6 +1799,10 @@ function setReadWatch(watch) {
1776
1799
  * via a `setContext` call.
1777
1800
  */
1778
1801
  function createContext(defaultValue, description) {
1802
+ // Dev: an explicit `undefined` default is still no default (`undefined`
1803
+ // means "not provided"); remembered so a failed read can say so (#4181).
1804
+ if (arguments.length && defaultValue === undefined)
1805
+ return { id: Symbol(description), defaultValue, _undefinedDefault: true };
1779
1806
  return { id: Symbol(description), defaultValue };
1780
1807
  }
1781
1808
  /**
@@ -1796,7 +1823,9 @@ function getContext(context, owner = getOwner()) {
1796
1823
  let value = owner._context[context.id];
1797
1824
  if (value === undefined) value = context.defaultValue;
1798
1825
  if (value === undefined) {
1799
- throw new ContextNotFoundError();
1826
+ // Dev names the context and why it has no value: no provider, or a
1827
+ // provider that passed `undefined` (the key is set, to `undefined`).
1828
+ throw new ContextNotFoundError(context, context.id in owner._context);
1800
1829
  }
1801
1830
  return value;
1802
1831
  }
@@ -1865,10 +1894,9 @@ function effect(compute, effect, error, options) {
1865
1894
  );
1866
1895
  }
1867
1896
  }
1868
- function notifyEffectStatus(status, error) {
1869
- // Use passed values if provided, otherwise read from node
1870
- const actualStatus = status !== undefined ? status : this._statusFlags;
1871
- const actualError = error !== undefined ? error : this._x?._error;
1897
+ function notifyEffectStatus() {
1898
+ const actualStatus = this._statusFlags;
1899
+ const actualError = this._x?._error;
1872
1900
  if (actualStatus & STATUS_ERROR) {
1873
1901
  globalQueue.notify(this, STATUS_PENDING, 0);
1874
1902
  if (this._type === EFFECT_USER) {
@@ -1876,19 +1904,22 @@ function notifyEffectStatus(status, error) {
1876
1904
  // queue it like the effect function. It runs in the same imperative,
1877
1905
  // writable scope and throws escalate the same way. No payload is queued — the node already carries `_statusFlags`/`_error`,
1878
1906
  // and the runner dispatches on them, so a recovery before the effect
1879
- // phase takes the success arm instead. Blocked forwards (explicit
1880
- // `status` arg without node-state writes) don't queue: the status
1881
- // re-propagates unblocked at commit.
1907
+ // phase takes the success arm instead. (Re-read: the boundary
1908
+ // notify above can run a redraw.)
1882
1909
  if (this._statusFlags & STATUS_ERROR) {
1883
1910
  this._modified = true;
1884
1911
  globalQueue.enqueue(this._type, (this._boundRunEffect ??= runEffect.bind(null, this)));
1885
1912
  }
1886
1913
  return;
1887
1914
  }
1888
- if (!globalQueue.notify(this, STATUS_ERROR, STATUS_ERROR)) {
1889
- haltReactivity(unwrapStatusError(actualError));
1890
- throw actualError;
1891
- }
1915
+ // Dev locates the halt at the node that threw: the StatusError's
1916
+ // source, upstream of this effect when a computed failed.
1917
+ escalate(
1918
+ this,
1919
+ unwrapStatusError(actualError),
1920
+ actualError,
1921
+ actualError instanceof StatusError ? actualError.source : this
1922
+ );
1892
1923
  } else if (this._type === EFFECT_RENDER) {
1893
1924
  globalQueue.notify(this, STATUS_PENDING | STATUS_ERROR, actualStatus, actualError);
1894
1925
  if (_hitUnhandledAsync && resetUnhandledAsync()) {
@@ -1917,6 +1948,14 @@ function notifyEffectStatus(status, error) {
1917
1948
  }
1918
1949
  }
1919
1950
  }
1951
+ /** An effect's error no boundary takes halts the system (boundary-or-halt):
1952
+ * halted with `cause`, `thrown` rethrown; dev locates it at `subject`. */
1953
+ function escalate(node, cause, thrown = cause, subject = node) {
1954
+ if (!globalQueue.notify(node, STATUS_ERROR, STATUS_ERROR)) {
1955
+ haltReactivityAt(cause, subject);
1956
+ throw thrown;
1957
+ }
1958
+ }
1920
1959
  function runEffect(node, type) {
1921
1960
  if (!node._modified || node._flags & REACTIVE_DISPOSED) return;
1922
1961
  // A queued run behind a fallback (boundaries.ts) waits for the reveal: a
@@ -1951,10 +1990,7 @@ function runEffect(node, type) {
1951
1990
  })
1952
1991
  : console.error(err);
1953
1992
  } catch (error) {
1954
- if (!globalQueue.notify(node, STATUS_ERROR, STATUS_ERROR)) {
1955
- haltReactivity(error);
1956
- throw error;
1957
- }
1993
+ escalate(node, error);
1958
1994
  }
1959
1995
  return;
1960
1996
  }
@@ -1962,9 +1998,13 @@ function runEffect(node, type) {
1962
1998
  // the compute pass that produced `_value` was clean, so its tail still goes.
1963
1999
  const cleanPass = node._x?._error == null;
1964
2000
  let prevStrictRead = false;
2001
+ let prevStrictReadEvent;
1965
2002
  {
1966
- prevStrictRead = setStrictRead("an effect callback");
1967
- setEffectCallback(true);
2003
+ prevStrictReadEvent = strictReadEvent;
2004
+ // A run outside a queue (a render effect's first run at creation) records
2005
+ // no event: its callback is the compiler's DOM writes.
2006
+ prevStrictRead = setStrictRead("an effect callback", queueEvent);
2007
+ setEffectCallback(true, queueEvent);
1968
2008
  enterCallback();
1969
2009
  }
1970
2010
  // Observe tier, like its `effectRunEnd` twin below: the frame the engine
@@ -1987,13 +2027,10 @@ function runEffect(node, type) {
1987
2027
  } catch (error) {
1988
2028
  ext(node)._error = new StatusError(node, error);
1989
2029
  node._statusFlags |= STATUS_ERROR;
1990
- if (!globalQueue.notify(node, STATUS_ERROR, STATUS_ERROR)) {
1991
- haltReactivity(error);
1992
- throw error;
1993
- }
2030
+ escalate(node, error);
1994
2031
  } finally {
1995
2032
  {
1996
- setStrictRead(prevStrictRead);
2033
+ setStrictRead(prevStrictRead, prevStrictReadEvent);
1997
2034
  setEffectCallback(false);
1998
2035
  exitCallback();
1999
2036
  }
@@ -2183,9 +2220,6 @@ function lookupTarget(raw, fam) {
2183
2220
  const owner = raw[$OWNER];
2184
2221
  return owner !== undefined && owner.fam === fam ? owner : (fam?.map ?? storeLookup).get(raw);
2185
2222
  }
2186
- function devAssertNeverUserMutation(target) {
2187
- return;
2188
- }
2189
2223
  /** Sticky descendants flag up the parent chain (§6d): adoption descends
2190
2224
  * into a changed child pair only where a target with nodes exists below. */
2191
2225
  function markDescendants(target) {
@@ -2617,7 +2651,12 @@ function createReaction(effectFn, options) {
2617
2651
  }
2618
2652
  runWithOwner(owner, () => {
2619
2653
  effect(
2620
- () => (tracking(), (arm = getOwner())),
2654
+ () => {
2655
+ const node = getOwner();
2656
+ arm = node;
2657
+ tracking();
2658
+ return node;
2659
+ },
2621
2660
  node => {
2622
2661
  arm = undefined;
2623
2662
  cl?.();
@@ -3355,7 +3394,7 @@ function updateKeyedMap() {
3355
3394
  this._rows && (this._rows = []);
3356
3395
  this._indexes && (this._indexes = []);
3357
3396
  }
3358
- if (this._fallback && !this._mappings[0]) {
3397
+ if (this._fallback && !this._mappings.length) {
3359
3398
  // an aborted fallback attempt leaves an owner without a mapping;
3360
3399
  // dispose it before re-creating
3361
3400
  this._nodes[0]?.dispose();
@@ -3636,7 +3675,7 @@ function updateRepeat() {
3636
3675
  // Reset offset to match the cleared data (#2767, repro 2).
3637
3676
  this._offset = 0;
3638
3677
  }
3639
- if (this._fallback && !this._mappings[0]) {
3678
+ if (this._fallback && !this._mappings.length) {
3640
3679
  // an aborted fallback attempt leaves an owner without a mapping;
3641
3680
  // dispose it before re-creating
3642
3681
  this._nodes[0]?.dispose();
@@ -3803,7 +3842,7 @@ function wrap(value, parent = null, parentKey = null, fam = parent?.fam ?? null)
3803
3842
  const existing = lookupTarget(value, fam);
3804
3843
  if (existing !== undefined) return existing.px;
3805
3844
  const t = value[$TARGET];
3806
- if (t !== undefined && t.px === value) {
3845
+ if (t?.px === value) {
3807
3846
  // Foreign-family proxies re-wrap into THIS family (writes stay isolated);
3808
3847
  // same-family and plain-store proxies pass through.
3809
3848
  if (fam === null || t.fam === fam) return value;
@@ -3875,7 +3914,7 @@ function asBacking(v) {
3875
3914
  * pending backing is never "equal" to the committed one); a lane value is
3876
3915
  * an arrangement (`LaneView`), judged by the optimistic machinery. */
3877
3916
  const containerEquals = function (a, b) {
3878
- return a === b || (optHooks !== null && optHooks.arrangement(this._host, a, b));
3917
+ return a === b || !!optHooks?.arrangement(this._host, a, b);
3879
3918
  };
3880
3919
  function sameLogicalSlot(target, a, b) {
3881
3920
  if (a === null || typeof a !== "object" || b === null || typeof b !== "object") return false;
@@ -3892,10 +3931,10 @@ function releaseSlot(node) {
3892
3931
  if (t.k === node) t.k = null;
3893
3932
  } else if (key === $DEEP) {
3894
3933
  if (t.dk === node) t.dk = null;
3895
- } else if (t.n !== null && t.n[key] === node) {
3934
+ } else if (t.n?.[key] === node) {
3896
3935
  delete t.n[key];
3897
3936
  t.nc--;
3898
- } else if (t.h !== null && t.h[key] === node) delete t.h[key];
3937
+ } else if (t.h?.[key] === node) delete t.h[key];
3899
3938
  // The family's index keeps only targets that still carry a node.
3900
3939
  if (
3901
3940
  t.fam !== null &&
@@ -3941,9 +3980,13 @@ function bornStaged(target, node, staged, key) {
3941
3980
  if (k === null) return; // a node-less staging holds nothing (`stageOn`)
3942
3981
  // Held by the container's transaction only for a key the HELD staging
3943
3982
  // changed — a mainline layer's change above it stages mainline. (A
3944
- // container carrying a guess is a lane's: the flush's park decides.)
3945
- if ((k._config & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && heldKeyChanged(target, key))
3946
- holdNode(node, txOf(k));
3983
+ // container carrying a guess is a lane's: the flush's park decides —
3984
+ // unless a correction folded under the guess, D-34: the key is that
3985
+ // correction's hold's.)
3986
+ if (k._config & CONFIG_HELD && heldKeyChanged(target, key)) {
3987
+ const l = txOf(k);
3988
+ if (!(k._config & CONFIG_OVERRIDE) || l.folded) holdNode(node, resolveTx(l._parent ?? l));
3989
+ }
3947
3990
  // A28 (4): a batch written inside a creation-time pass is promoted — its
3948
3991
  // late-materialized leaf with it.
3949
3992
  if (k._config & CONFIG_PROMOTED) notePromotedWrite(node);
@@ -3972,12 +4015,7 @@ function getNode(
3972
4015
  accKnown === -1 ? isOwnAccessor(pb ?? target.v, key) : accKnown === 1
3973
4016
  ));
3974
4017
  if (pb !== null && !created.acc)
3975
- bornStaged(
3976
- target,
3977
- created,
3978
- target.del !== null && target.del.has(key) ? undefined : pb[key],
3979
- key
3980
- );
4018
+ bornStaged(target, created, target.del?.has(key) ? undefined : pb[key], key);
3981
4019
  // Attribution-only: name store property nodes by path segment so
3982
4020
  // attribution chains and wide-scope warnings read "store.todos" (or
3983
4021
  // "todos.title" when the store was declared with a name), not
@@ -4002,8 +4040,7 @@ function getHasNode(target, key) {
4002
4040
  if (node === undefined) {
4003
4041
  const created = (node = slotSignal(key in committed(target), isEqual, target, key, false));
4004
4042
  const pb = target.pb;
4005
- if (pb !== null)
4006
- bornStaged(target, created, key in pb && !(target.del !== null && target.del.has(key)), key);
4043
+ if (pb !== null) bornStaged(target, created, key in pb && !target.del?.has(key), key);
4007
4044
  if (attrHooks !== null) stampNodeOwner(created, target);
4008
4045
  nodes[key] = node;
4009
4046
  noteNode(target, created, key);
@@ -4177,7 +4214,7 @@ function scanAccessorsOnce(target) {
4177
4214
  const keys = Reflect.ownKeys(src);
4178
4215
  let plain = Object.getPrototypeOf(src) === Object.prototype;
4179
4216
  for (const key of keys) {
4180
- if (lookupGetter.call(src, key) !== undefined || lookupSetter.call(src, key) !== undefined) {
4217
+ if (isOwnAccessor(src, key)) {
4181
4218
  target.a = true;
4182
4219
  plain = false;
4183
4220
  break;
@@ -4202,7 +4239,7 @@ const plainProto = o => {
4202
4239
  * container at flatten so its shape stays fast (#3689). */
4203
4240
  function overlayRebuilds(t, pb) {
4204
4241
  if (t.kc > OVERLAY_REBUILD_MAX_KEYS) return false;
4205
- if (t.del !== null && t.del.size !== 0) return true;
4242
+ if (t.del?.size) return true;
4206
4243
  const wk = t.wk;
4207
4244
  if (wk !== null && wk !== WK_ALL && wk.u === undefined && wk.size <= OVERLAY_REBUILD_MIN_ADDS)
4208
4245
  return false;
@@ -4216,18 +4253,14 @@ function overlayRebuilds(t, pb) {
4216
4253
  function materializePB(target) {
4217
4254
  const pb = target.pb;
4218
4255
  if (!target.ovl) return pb;
4219
- const clone = target.sc === 2 ? wideClone(target.v, target) : cloneRaw(target.v, target);
4220
- if (target.del !== null) {
4221
- for (const key of target.del) delete clone[key];
4222
- target.kc -= target.del.size;
4223
- target.del = null;
4224
- }
4225
- for (const key of Reflect.ownKeys(pb))
4226
- target.sc === 2 ? (clone[key] = pb[key]) : copyOwn(clone, pb, key);
4256
+ const clone = overlayOnto(
4257
+ target,
4258
+ target.sc === 2 ? wideClone(target.v, target) : cloneRaw(target.v, target),
4259
+ pb
4260
+ );
4227
4261
  target.pb = clone;
4228
- target.ovl = false;
4229
4262
  // The staging IS the pending backing: re-point the container node.
4230
- if (target.k !== null && target.k._pendingValue === pb) target.k._pendingValue = clone;
4263
+ if (target.k?._pendingValue === pb) target.k._pendingValue = clone;
4231
4264
  return clone;
4232
4265
  }
4233
4266
  /** The batch's pending backing, created on the first draft write: an overlay
@@ -4279,7 +4312,6 @@ function privatizeCommitted(target) {
4279
4312
  target.ch = false;
4280
4313
  if (target.u) {
4281
4314
  privatizeCommitted(target.u);
4282
- devAssertNeverUserMutation(target.u.v);
4283
4315
  const pv = target.u.v;
4284
4316
  const slot = parentSlotKey(target, before);
4285
4317
  if (pv[slot] === before) pv[slot] = target.v;
@@ -4303,16 +4335,19 @@ function parentSlotKey(target, expected) {
4303
4335
  * place — the backing keeps its identity. */
4304
4336
  function flattenOverlay(t, pb) {
4305
4337
  privatizeCommitted(t);
4306
- const v = t.v;
4307
- for (const key of Reflect.ownKeys(pb)) t.sc === 2 ? (v[key] = pb[key]) : copyOwn(v, pb, key);
4338
+ overlayOnto(t, t.v, pb); // the caller (`foldTarget`) closes the staging
4339
+ }
4340
+ /** An overlay's own keys and deletions onto `to` (the two are disjoint: a
4341
+ * write un-deletes its key); the overlay is closed. */
4342
+ function overlayOnto(t, to, pb) {
4343
+ for (const key of Reflect.ownKeys(pb)) t.sc === 2 ? (to[key] = pb[key]) : copyOwn(to, pb, key);
4308
4344
  if (t.del !== null) {
4309
- for (const key of t.del) delete v[key];
4345
+ for (const key of t.del) delete to[key];
4310
4346
  t.kc -= t.del.size;
4311
4347
  t.del = null;
4312
4348
  }
4313
- t.pb = null;
4314
4349
  t.ovl = false;
4315
- t.wk = null; // written-keys window closes with the commit
4350
+ return to;
4316
4351
  }
4317
4352
  /** Adoption (a setter's returned replacement; reconcile and projection
4318
4353
  * landings in S3): the incoming object becomes the backing by reference at
@@ -4351,7 +4386,7 @@ function adoptPB(target, incoming, notify = true) {
4351
4386
  target.a = false;
4352
4387
  target.wk = null; // adoption supersedes staged trap writes
4353
4388
  const owner = incoming[$OWNER];
4354
- if (owner !== undefined && owner.fam === target.fam) incoming[$OWNER] = target;
4389
+ if (owner?.fam === target.fam) incoming[$OWNER] = target;
4355
4390
  else setLookup(incoming, target);
4356
4391
  if (notify && base !== incoming) notifyFold(target, base, incoming);
4357
4392
  }
@@ -4448,7 +4483,7 @@ function drainFolds() {
4448
4483
  * landing) read it. */
4449
4484
  GlobalQueue._slotFlight = n => {
4450
4485
  const fam = n._host?.fam;
4451
- return fam != null && fam.node !== null && (fam.node._statusFlags & STATUS_PENDING) !== 0;
4486
+ return (fam?.node?._statusFlags & STATUS_PENDING) !== 0;
4452
4487
  };
4453
4488
  /** Core asks (lanes.ts `covered`): the value a slot node's guess covered.
4454
4489
  * A chained link's committed value is the inner store's at the last
@@ -4459,8 +4494,8 @@ GlobalQueue._slotCovered = n => {
4459
4494
  if (t.ch) {
4460
4495
  const key = n._key;
4461
4496
  const inner = t.v;
4462
- if (t.h !== null && t.h[key] === n) n._value = untrack(() => key in inner);
4463
- else if (t.n !== null && t.n[key] === n) n._value = untrack(() => unwrapValue(inner[key]));
4497
+ if (t.h?.[key] === n) n._value = untrack(() => key in inner);
4498
+ else if (t.n?.[key] === n) n._value = untrack(() => unwrapValue(inner[key]));
4464
4499
  }
4465
4500
  return n._value;
4466
4501
  };
@@ -4493,12 +4528,8 @@ function foldTarget(t, old) {
4493
4528
  const k = t.k;
4494
4529
  const pb = t.pb;
4495
4530
  if (pb !== null) {
4496
- if (pb === t.v) {
4497
- // An eager adoption nothing wrote after: the backing is already
4498
- // the adopted object (shared ownership — never cloned).
4499
- t.pb = null;
4500
- t.wk = null;
4501
- } else if (
4531
+ if (pb === t.v);
4532
+ else if (
4502
4533
  t.ovl &&
4503
4534
  (t.v !== old || (!overlayRebuilds(t, pb) && k?._x?._snapshotValue !== old))
4504
4535
  ) {
@@ -4521,26 +4552,19 @@ function foldTarget(t, old) {
4521
4552
  else delete v[key];
4522
4553
  }
4523
4554
  } else {
4524
- for (const key of Reflect.ownKeys(pb)) {
4525
- const d = Object.getOwnPropertyDescriptor(pb, key);
4526
- if (d.get || d.set || !d.enumerable || !d.writable || !d.configurable)
4527
- Object.defineProperty(v, key, d);
4528
- else if (d.value !== old[key] || !hasOwn$1.call(old, key)) v[key] = d.value;
4529
- }
4555
+ for (const key of Reflect.ownKeys(pb)) copyOwn(v, pb, key);
4530
4556
  for (const key of Reflect.ownKeys(old)) {
4531
4557
  if (!hasOwn$1.call(pb, key)) delete v[key];
4532
4558
  }
4533
4559
  }
4534
- t.pb = null;
4535
- t.wk = null;
4536
4560
  } else {
4537
4561
  // The clone (or adopted object) becomes the committed backing
4538
4562
  // (path copy below).
4539
4563
  t.v = t.ovl ? materializePB(t) : pb;
4540
4564
  t.ch = t.v[$TARGET] !== undefined;
4541
- t.pb = null;
4542
- t.wk = null;
4543
4565
  }
4566
+ t.pb = null;
4567
+ t.wk = null; // the written-keys window closes with the commit
4544
4568
  }
4545
4569
  if (k !== null) {
4546
4570
  // The node's committed value is the backing. A container nobody
@@ -4555,7 +4579,6 @@ function foldTarget(t, old) {
4555
4579
  const slot = parentSlotKey(t, old);
4556
4580
  if (t.u.v[slot] === old) {
4557
4581
  privatizeCommitted(t.u);
4558
- devAssertNeverUserMutation(t.u.v);
4559
4582
  t.u.v[slot] = t.v;
4560
4583
  }
4561
4584
  }
@@ -4583,7 +4606,7 @@ function reportReplacedContainers(t, old, pb, writtenKeys) {
4583
4606
  const owner = storeRootOwner(t);
4584
4607
  for (const key of keys) {
4585
4608
  if ((isArray && key === "length") || key === $OWNER) continue;
4586
- if (t.del !== null && t.del.has(key)) continue;
4609
+ if (t.del?.has(key)) continue;
4587
4610
  const ov = unwrapValue(old[key]);
4588
4611
  const nv = unwrapValue(pb[key]);
4589
4612
  if (
@@ -4731,7 +4754,7 @@ function notifyWrites(t) {
4731
4754
  if (od?.get || od?.set || !isEqual(od?.value, nd.value)) setSignal(node, () => nd.value);
4732
4755
  continue;
4733
4756
  }
4734
- const nv = t.del !== null && t.del.has(key) ? undefined : pb[key];
4757
+ const nv = t.del?.has(key) ? undefined : pb[key];
4735
4758
  setSignal(node, () => nv);
4736
4759
  if (t.fam !== null) {
4737
4760
  // The derive's write is a derivation; a user setter's is a proposal
@@ -4749,13 +4772,13 @@ function notifyWrites(t) {
4749
4772
  for (const key of keys) {
4750
4773
  const node = has[key];
4751
4774
  if (node !== undefined) {
4752
- setSignal(node, key in pb && !(t.del !== null && t.del.has(key)));
4775
+ setSignal(node, key in pb && !t.del?.has(key));
4753
4776
  if (t.fam !== null) holdWithDerive(t, node);
4754
4777
  }
4755
4778
  }
4756
4779
  }
4757
4780
  if (t.dk !== null) {
4758
- if (t.del !== null && t.del.size !== 0) bumpDeep(t);
4781
+ if (t.del?.size) bumpDeep(t);
4759
4782
  else
4760
4783
  for (const key of writtenKeys ?? Reflect.ownKeys(pb)) {
4761
4784
  if (key === $OWNER) continue;
@@ -4775,7 +4798,7 @@ function notifyWrites(t) {
4775
4798
  if (k !== null && (k._subs !== null || k._config & CONFIG_OVERRIDE)) {
4776
4799
  let changed;
4777
4800
  if (t.ovl) {
4778
- changed = t.del !== null && t.del.size !== 0;
4801
+ changed = !!t.del?.size;
4779
4802
  if (!changed) {
4780
4803
  for (const key of Reflect.ownKeys(pb)) {
4781
4804
  if (!hasOwn$1.call(old, key)) {
@@ -4930,7 +4953,7 @@ function serveShallow(target, key, v) {
4930
4953
  function draftServe(target, proxy) {
4931
4954
  if (writeScopes !== null && inDraft(target)) {
4932
4955
  const ct = proxy?.[$TARGET];
4933
- if (ct !== undefined && ct.v !== undefined) writeScopes.add(scopeKey(ct));
4956
+ if (ct?.v !== undefined) writeScopes.add(scopeKey(ct));
4934
4957
  }
4935
4958
  return proxy;
4936
4959
  }
@@ -4973,13 +4996,7 @@ function readSource(target, key, shape = false) {
4973
4996
  // sees — the lanes' values and the tick's own guesses over the truth
4974
4997
  // (#3665), never a staging awaiting its fold: the draft is born on the
4975
4998
  // read (and notified at the setter's exit like any).
4976
- if (
4977
- userWrite &&
4978
- target.fam !== null &&
4979
- target.fam.opt === true &&
4980
- !pendingNotify.has(target) &&
4981
- inDraft(target)
4982
- ) {
4999
+ if (userWrite && target.fam?.opt === true && !pendingNotify.has(target) && inDraft(target)) {
4983
5000
  const draft = ensurePB(target);
4984
5001
  pendingNotify.add(target);
4985
5002
  return draft;
@@ -5018,7 +5035,7 @@ function readSource(target, key, shape = false) {
5018
5035
  // it).
5019
5036
  if (key !== undefined) {
5020
5037
  const leaf = target.n?.[key];
5021
- if (leaf !== undefined && leaf._config & CONFIG_OVERRIDE) return committed(target);
5038
+ if (leaf?._config & CONFIG_OVERRIDE) return committed(target);
5022
5039
  }
5023
5040
  const held = k !== null && (k._config & CONFIG_HELD) !== 0;
5024
5041
  // A held container: the hold's unit is the key (#3706). A key the HELD
@@ -5132,18 +5149,14 @@ function adoptChild(target, key) {
5132
5149
  // A raw-marked value wraps to itself, a proxy to its own target: neither
5133
5150
  // is a fresh target around `ov`.
5134
5151
  const ct = wrap(ov, target, key)[$TARGET];
5135
- if (ct === undefined || ct.v !== ov) return false;
5152
+ if (ct?.v !== ov) return false;
5136
5153
  adoptPB(ct, nv, false);
5137
5154
  holdNode(getContainerNode(ct), txOf(k));
5138
5155
  return true;
5139
5156
  }
5140
5157
  /** A family target's unheld staging, read with no pass (a handler). */
5141
5158
  function familyAhead(target) {
5142
- return (
5143
- target.fam !== null &&
5144
- context === null &&
5145
- !(target.k !== null && target.k._config & CONFIG_HELD)
5146
- );
5159
+ return target.fam !== null && context === null && !(target.k?._config & CONFIG_HELD);
5147
5160
  }
5148
5161
  /** Did this batch change `key` — value (slot equality), presence, and (for
5149
5162
  * a `shape` reader — a descriptor read, a node's hold) enumerability or
@@ -5168,7 +5181,7 @@ function changedBetween(target, v, pb, key, shape) {
5168
5181
  !plainProto(v)
5169
5182
  )
5170
5183
  return true;
5171
- if (target.del !== null && target.del.has(key)) return key in v;
5184
+ if (target.del?.has(key)) return key in v;
5172
5185
  const inNew = hasOwn$1.call(pb, key) || (target.ovl && hasOwn$1.call(target.v, key));
5173
5186
  if (inNew !== hasOwn$1.call(v, key)) return true;
5174
5187
  if (!inNew) return false;
@@ -5349,7 +5362,7 @@ function enumerationSource(target) {
5349
5362
  * draft's (it composed already), not a derive's write-override read (the
5350
5363
  * derive composes on the truth). */
5351
5364
  function optRead(target) {
5352
- return target.fam !== null && target.fam.opt === true && !inDraft(target) && !writeOverride;
5365
+ return target.fam?.opt === true && !inDraft(target) && !writeOverride;
5353
5366
  }
5354
5367
  /** Pollution keys are never served from the prototype (core R30). */
5355
5368
  const UNSAFE_KEYS = new Set(["__proto__", "prototype", "constructor"]);
@@ -5371,6 +5384,47 @@ function touch(target, key) {
5371
5384
  }
5372
5385
  if (wk !== WK_ALL) (target.wk ??= new Set()).add(key);
5373
5386
  }
5387
+ /** Dev: targets (and their keys) already warned about for a write outside
5388
+ * the setter — #4179's once-per-target+key memory. */
5389
+ let warnedOutsideWrites;
5390
+ /** Dev: set while the runtime itself writes through a proxy it knows the
5391
+ * trap ignores (`registerGraph`'s `_owner` stamp). */
5392
+ let internalOutsideWrite = false;
5393
+ /** Dev: a write, defineProperty or delete on a store proxy outside its
5394
+ * setter's draft is ignored (the trap returns `true` so strict-mode code
5395
+ * does not throw) — say so once per target+key instead of silently dropping
5396
+ * it (#4179). */
5397
+ function warnWriteOutsideSetter(target, key, operation) {
5398
+ if (internalOutsideWrite) return;
5399
+ warnedOutsideWrites ??= new WeakMap();
5400
+ let keys = warnedOutsideWrites.get(target);
5401
+ if (keys === undefined) warnedOutsideWrites.set(target, (keys = new Set()));
5402
+ if (keys.has(key)) return;
5403
+ keys.add(key);
5404
+ const name = String(key);
5405
+ const access =
5406
+ typeof key === "string" && /^[A-Za-z_$][\w$]*$/.test(key)
5407
+ ? `.${key}`
5408
+ : typeof key === "string" && /^\d+$/.test(key)
5409
+ ? `[${key}]`
5410
+ : `[${typeof key === "string" ? JSON.stringify(key) : name}]`;
5411
+ const hint =
5412
+ operation === "deleteProperty"
5413
+ ? `setStore(s => { delete s${access} })`
5414
+ : `setStore(s => { s${access} = value })`;
5415
+ reportDiagnostic(
5416
+ emitDiagnostic({
5417
+ code: "STORE_WRITE_OUTSIDE_SETTER",
5418
+ kind: "write",
5419
+ severity: "warn",
5420
+ message:
5421
+ `[STORE_WRITE_OUTSIDE_SETTER] ${operation === "deleteProperty" ? "Deleting" : "Writing"} store property "${name}" outside its setter was ignored. ` +
5422
+ `Stores are read-only outside the setter's draft: use \`${hint}\`.`,
5423
+ nodeName: name,
5424
+ data: { operation, property: name }
5425
+ })
5426
+ );
5427
+ }
5374
5428
  const traps = {
5375
5429
  get(target, key, receiver) {
5376
5430
  // One typeof gates every brand-symbol compare off the hot string path.
@@ -5434,7 +5488,7 @@ const traps = {
5434
5488
  const src = readSource(target, key);
5435
5489
  // Overlay delete (#3044): a prototype overlay cannot shadow a delete, so
5436
5490
  // deleted keys are tracked aside and read as absent in the pending view.
5437
- if (target.del !== null && src === target.pb && target.del.has(key)) {
5491
+ if (src === target.pb && target.del?.has(key)) {
5438
5492
  if (!inDraft(target) && getObserver() !== null) read(getNode(target, key));
5439
5493
  return undefined;
5440
5494
  }
@@ -5462,7 +5516,8 @@ const traps = {
5462
5516
  !inDraft(target) &&
5463
5517
  typeof key === "string" &&
5464
5518
  key !== "then" &&
5465
- getObserver() === null
5519
+ getObserver() === null &&
5520
+ strictReadActive()
5466
5521
  ) {
5467
5522
  warnStrictReadUntracked(strictRead, {
5468
5523
  nodeName: key,
@@ -5566,7 +5621,7 @@ const traps = {
5566
5621
  if (guessed !== undefined) return guessed;
5567
5622
  }
5568
5623
  }
5569
- return key in src && !(target.del !== null && src === target.pb && target.del.has(key));
5624
+ return key in src && !(src === target.pb && target.del?.has(key));
5570
5625
  },
5571
5626
  ownKeys(target) {
5572
5627
  if (verdict !== null && affectsHooks !== null && getObserver() === null)
@@ -5613,8 +5668,10 @@ const traps = {
5613
5668
  },
5614
5669
  set(target, key, value) {
5615
5670
  const draft = inDraft(target);
5616
- const override = !draft && writeOverride;
5617
- if (!draft && !override) return true;
5671
+ if (!draft && !writeOverride) {
5672
+ warnWriteOutsideSetter(target, key, "set");
5673
+ return true;
5674
+ }
5618
5675
  if (key === "__proto__") return true; // pollution guard (core R30)
5619
5676
  const uv = target.s ? value : unwrapValue(value);
5620
5677
  const pb = ensurePB(target);
@@ -5637,17 +5694,8 @@ const traps = {
5637
5694
  touch(target, key);
5638
5695
  if (!(key in pb)) target.kc++;
5639
5696
  }
5640
- if (UNSAFE_KEYS.has(key)) {
5641
- Object.defineProperty(pb, key, {
5642
- value: uv,
5643
- writable: true,
5644
- enumerable: true,
5645
- configurable: true
5646
- });
5647
- if (target.del !== null) target.del.delete(key);
5648
- return true;
5649
- }
5650
- if (target.ovl && target.sc !== 2 && !hasOwn$1.call(pb, key)) {
5697
+ const unsafe = UNSAFE_KEYS.has(key);
5698
+ if (unsafe || (target.ovl && target.sc !== 2 && !hasOwn$1.call(pb, key))) {
5651
5699
  Object.defineProperty(pb, key, {
5652
5700
  value: uv,
5653
5701
  writable: true,
@@ -5655,15 +5703,18 @@ const traps = {
5655
5703
  configurable: true
5656
5704
  });
5657
5705
  } else pb[key] = uv;
5658
- if (target.del !== null) target.del.delete(key);
5706
+ target.del?.delete(key);
5707
+ if (unsafe) return true;
5659
5708
  if (target.s && uv !== null && typeof uv === "object") markRawOne(uv);
5660
- if (override) notifyWrites(target);
5709
+ if (!draft) notifyWrites(target); // a write-override write
5661
5710
  return true;
5662
5711
  },
5663
5712
  defineProperty(target, key, desc) {
5664
5713
  const draft = inDraft(target);
5665
- const override = !draft && writeOverride;
5666
- if (!draft && !override) return true;
5714
+ if (!draft && !writeOverride) {
5715
+ warnWriteOutsideSetter(target, key, "defineProperty");
5716
+ return true;
5717
+ }
5667
5718
  if (key === "__proto__") return true;
5668
5719
  if (desc.get || desc.set) target.a = true;
5669
5720
  if ("value" in desc) desc = { ...desc, value: unwrapValue(desc.value) };
@@ -5671,14 +5722,16 @@ const traps = {
5671
5722
  if (target.a || !(desc.enumerable && desc.writable && desc.configurable)) target.sc = 1;
5672
5723
  touch(target, key);
5673
5724
  Object.defineProperty(pb, key, desc);
5674
- if (target.del !== null) target.del.delete(key);
5675
- if (override) notifyWrites(target);
5725
+ target.del?.delete(key);
5726
+ if (!draft) notifyWrites(target); // a write-override write
5676
5727
  return true;
5677
5728
  },
5678
5729
  deleteProperty(target, key) {
5679
5730
  const draft = inDraft(target);
5680
- const override = !draft && writeOverride;
5681
- if (!draft && !override) return true;
5731
+ if (!draft && !writeOverride) {
5732
+ warnWriteOutsideSetter(target, key, "deleteProperty");
5733
+ return true;
5734
+ }
5682
5735
  const pb = ensurePB(target);
5683
5736
  touch(target, key);
5684
5737
  // The key count (`kc`, the overlay gate and the rebuild cap) follows a
@@ -5696,7 +5749,7 @@ const traps = {
5696
5749
  committed || target.kc--;
5697
5750
  }
5698
5751
  if (committed) (target.del ??= new Set()).add(key);
5699
- if (override) notifyWrites(target);
5752
+ if (!draft) notifyWrites(target); // a write-override write
5700
5753
  return true;
5701
5754
  }
5702
5755
  };
@@ -5836,8 +5889,11 @@ function createStore$1(initialValue, options) {
5836
5889
  {
5837
5890
  const owner = getOwner();
5838
5891
  // Dev-tier graph registration (owner signal lists, onGraph); the
5839
- // `_owner` write itself never reaches the proxy, see storeOwners.
5892
+ // `_owner` write itself never reaches the proxy, see storeOwners — an
5893
+ // internal ignored write, not the user's (#4179's warning skips it).
5894
+ internalOutsideWrite = true;
5840
5895
  registerGraph(proxy, owner);
5896
+ internalOutsideWrite = false;
5841
5897
  // Only once the engine is installed, like the node stamping it feeds: a
5842
5898
  // WeakMap.set per fresh store is a growing ephemeron table and,
5843
5899
  // disabled, buys nothing.
@@ -5854,17 +5910,17 @@ function createStore$1(initialValue, options) {
5854
5910
  * slot-patch channel (collected row bodies) over per-record registration. */
5855
5911
  function storeIsShallow(proxy) {
5856
5912
  const t = proxy?.[$TARGET];
5857
- return t !== undefined && t.s === true;
5913
+ return t?.s === true;
5858
5914
  }
5859
5915
  /** True when `proxy` belongs to a projection family (S3). */
5860
5916
  function storeHasFamily(proxy) {
5861
5917
  const t = proxy?.[$TARGET];
5862
- return t !== undefined && t.fam !== null;
5918
+ return t?.fam != null;
5863
5919
  }
5864
5920
  /** True when `proxy` belongs to an OPTIMISTIC family (S4). */
5865
5921
  function storeHasOptimisticFamily(proxy) {
5866
5922
  const t = proxy?.[$TARGET];
5867
- return t !== undefined && t.fam?.opt === true;
5923
+ return t?.fam?.opt === true;
5868
5924
  }
5869
5925
  // ---------------------------------------------------------------------------
5870
5926
  // enumeration views (one visibility rule for the traps AND the deep walk)
@@ -5873,7 +5929,7 @@ function visibleKeys(target, src) {
5873
5929
  if (target.ovl && src === target.pb) {
5874
5930
  keys = Reflect.ownKeys(target.v);
5875
5931
  const del = target.del;
5876
- if (del !== null && del.size !== 0) keys = keys.filter(key => !del.has(key));
5932
+ if (del?.size) keys = keys.filter(key => !del.has(key));
5877
5933
  for (const key of Reflect.ownKeys(src)) {
5878
5934
  if (!hasOwn$1.call(target.v, key)) keys.push(key);
5879
5935
  }
@@ -5890,7 +5946,7 @@ function visibleKeys(target, src) {
5890
5946
  function visibleDescriptor(target, src, key) {
5891
5947
  let desc = Object.getOwnPropertyDescriptor(src, key);
5892
5948
  if (target.ovl && src === target.pb) {
5893
- if (target.del !== null && target.del.has(key)) return undefined;
5949
+ if (target.del?.has(key)) return undefined;
5894
5950
  if (desc === undefined) desc = Object.getOwnPropertyDescriptor(target.v, key);
5895
5951
  }
5896
5952
  return desc;
@@ -5901,7 +5957,7 @@ function visibleDescriptor(target, src, key) {
5901
5957
  * witness (one node per record, not one per path), then snapshots. */
5902
5958
  function deep$1(value) {
5903
5959
  const t0 = value?.[$TARGET];
5904
- if (t0 === undefined || t0.px !== value) return value;
5960
+ if (t0?.px !== value) return value;
5905
5961
  const visited = new Set();
5906
5962
  const childTarget = (t, child, key) => {
5907
5963
  let ct = lookupTarget(child, t.fam);
@@ -6143,8 +6199,8 @@ const storePath = /* @__PURE__ */ Object.assign(
6143
6199
  */
6144
6200
  function reconcileState(value, state, key, replace = false) {
6145
6201
  if (state == null) throw new Error("Cannot reconcile null or undefined state");
6146
- const t = state?.[$TARGET];
6147
- if (t === undefined || t.px !== state) throw new Error("reconcile target is not a store proxy");
6202
+ const t = state[$TARGET];
6203
+ if (t?.px !== state) throw new Error("reconcile target is not a store proxy");
6148
6204
  const keyFn = key === null ? null : typeof key === "string" ? item => item?.[key] : key;
6149
6205
  // A user's reconcile on an optimistic family (S4): not an adoption of
6150
6206
  // truth but an optimistic edit — the keyed diff is written into the draft
@@ -6169,9 +6225,8 @@ function reconcileState(value, state, key, replace = false) {
6169
6225
  if (!replace) throw new Error("Cannot reconcile states with different identity");
6170
6226
  // A different entity at the root: the old backing is disowned (wraps
6171
6227
  // fresh if re-handed) and the new one adopted whole.
6172
- const out = t.pb ?? t.v;
6173
- if (isOwned(out)) delete out[$OWNER];
6174
- else (t.fam?.map ?? storeLookup).delete(out);
6228
+ if (isOwned(prev)) delete prev[$OWNER];
6229
+ else (t.fam?.map ?? storeLookup).delete(prev);
6175
6230
  adoptPB(t, incoming);
6176
6231
  return;
6177
6232
  }
@@ -6187,50 +6242,42 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6187
6242
  // as present through its prototype; materialized first — `adoptPB` does
6188
6243
  // so anyway, after the base was taken — so a deletion the draft made is
6189
6244
  // a deletion here (#3743: the presence and value diffs both read `old`).
6190
- const prev = t.ovl ? materializePB(t) : (t.pb ?? t.v);
6191
- if (incoming === prev && !isOwned(prev)) return;
6192
- const fam = t.fam;
6245
+ const old = t.ovl ? materializePB(t) : (t.pb ?? t.v);
6246
+ if (incoming === old && !isOwned(old)) return;
6193
6247
  // Q-D: a landing on an optimistic family is reconciled against the VIEW
6194
6248
  // — key matching from the arrangement the guesses made (R28), so a row
6195
6249
  // the guess added keeps its proxy when the server returns it.
6196
- const prevView = fam?.opt === true ? optHooks.view(t, prev) : prev;
6250
+ const prevView = t.fam?.opt === true ? optHooks.view(t, old) : old;
6197
6251
  const nextArr = Array.isArray(incoming);
6198
6252
  const shallow = t.s === true;
6199
- const old = prev;
6200
6253
  adoptPB(t, incoming, false);
6201
6254
  if (shallow) markRawIngest(incoming);
6255
+ // The deep witness is bumped once per adoption (none to bump: done).
6256
+ let bumped = t.dk === null;
6202
6257
  if (Array.isArray(prevView) !== nextArr) {
6203
6258
  notifyFold(t, old, incoming);
6204
6259
  return;
6205
6260
  }
6206
6261
  if (nextArr) {
6207
- const prevRows = prevView;
6208
- const nextRows = incoming;
6209
6262
  const nodes = t.n;
6210
6263
  let nodesHit = 0;
6211
6264
  if (keyFn && !shallow) {
6212
- const plen = prevRows.length;
6213
- const nlen = nextRows.length;
6214
- let dkBumpedA = false;
6265
+ const plen = prevView.length;
6266
+ const nlen = incoming.length;
6215
6267
  let i = 0;
6216
6268
  // Aligned prefix: same row (by reference or key) at the same index.
6217
6269
  for (const end = Math.min(plen, nlen); i < end; i++) {
6218
- const nv = nextRows[i];
6219
- const pvRaw = prevRows[i];
6270
+ const nv = incoming[i];
6271
+ const pvRaw = prevView[i];
6220
6272
  if (pvRaw !== nv && !sameRow$1(pvRaw, nv, keyFn)) break; // misaligned: fall to the keyed remainder below
6273
+ if (nv !== null && typeof nv === "object" && (pvRaw !== nv || isOwned(nv)))
6274
+ descend(unwrapValue(pvRaw), nv, keyFn, proj, t, i);
6221
6275
  if (
6222
- (pvRaw !== nv || (nv !== null && typeof nv === "object" && isOwned(nv))) &&
6223
- nv !== null &&
6224
- typeof nv === "object"
6225
- )
6226
- descend(unwrapValue(pvRaw), nv, keyFn, fam, proj);
6227
- if (
6228
- t.dk !== null &&
6229
- !dkBumpedA &&
6276
+ !bumped &&
6230
6277
  !(nv !== null && typeof nv === "object" ? targetsEqual(pvRaw, nv) : isEqual(pvRaw, nv))
6231
6278
  ) {
6232
6279
  bumpDeep(t);
6233
- dkBumpedA = true;
6280
+ bumped = true;
6234
6281
  }
6235
6282
  if (nodes !== null) {
6236
6283
  const node = nodes[i];
@@ -6240,19 +6287,19 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6240
6287
  }
6241
6288
  }
6242
6289
  }
6243
- if (t.dk !== null && !dkBumpedA && i < nextRows.length) bumpDeep(t);
6290
+ if (!bumped && i < incoming.length) bumpDeep(t);
6244
6291
  const structStart = i; // misalignment point (== nlen on aligned ticks)
6245
6292
  let prevByKey = null;
6246
- for (; i < nextRows.length; i++) {
6247
- const nv = nextRows[i];
6293
+ for (; i < incoming.length; i++) {
6294
+ const nv = incoming[i];
6248
6295
  if (nv !== null && typeof nv === "object") {
6249
6296
  const nk = keyFn(nv);
6250
6297
  let pv;
6251
6298
  if (nk !== undefined) {
6252
6299
  if (prevByKey === null) {
6253
6300
  prevByKey = new Map();
6254
- for (let j = structStart; j < prevRows.length; j++) {
6255
- const p = unwrapValue(prevRows[j]);
6301
+ for (let j = structStart; j < prevView.length; j++) {
6302
+ const p = unwrapValue(prevView[j]);
6256
6303
  if (p !== null && typeof p === "object") {
6257
6304
  const pk = keyFn(p);
6258
6305
  if (pk === undefined) continue;
@@ -6266,16 +6313,16 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6266
6313
  const m = prevByKey.get(nk);
6267
6314
  if (m === undefined) pv = undefined;
6268
6315
  else if (Array.isArray(m)) {
6269
- pv = unwrapValue(prevRows[m.shift()]);
6316
+ pv = unwrapValue(prevView[m.shift()]);
6270
6317
  if (m.length === 1) prevByKey.set(nk, m[0]);
6271
6318
  } else {
6272
- pv = unwrapValue(prevRows[m]);
6319
+ pv = unwrapValue(prevView[m]);
6273
6320
  prevByKey.delete(nk);
6274
6321
  }
6275
6322
  } else {
6276
- pv = unwrapValue(prevRows[i]); // keyless item: positional fallback
6323
+ pv = unwrapValue(prevView[i]); // keyless item: positional fallback
6277
6324
  }
6278
- descend(pv, nv, keyFn, fam, proj);
6325
+ descend(pv, nv, keyFn, proj, t, i);
6279
6326
  }
6280
6327
  if (nodes !== null) {
6281
6328
  const node = nodes[i];
@@ -6286,22 +6333,20 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6286
6333
  }
6287
6334
  }
6288
6335
  } else {
6289
- const dlen = Math.min(prevRows.length, nextRows.length);
6290
- const nlen = nextRows.length;
6291
- let dkBumpedP = false;
6336
+ const dlen = Math.min(prevView.length, incoming.length);
6337
+ const nlen = incoming.length;
6292
6338
  for (let i = 0; i < nlen; i++) {
6293
- const nvP = nextRows[i];
6339
+ const nvP = incoming[i];
6294
6340
  if (!shallow && i < dlen && nvP !== null && typeof nvP === "object")
6295
- descend(unwrapValue(prevRows[i]), nvP, keyFn, fam, proj);
6341
+ descend(unwrapValue(prevView[i]), nvP, keyFn, proj, t, i);
6296
6342
  if (
6297
- t.dk !== null &&
6298
- !dkBumpedP &&
6343
+ !bumped &&
6299
6344
  !(nvP !== null && typeof nvP === "object"
6300
- ? targetsEqual(prevRows[i], nvP)
6301
- : isEqual(prevRows[i], nvP))
6345
+ ? targetsEqual(prevView[i], nvP)
6346
+ : isEqual(prevView[i], nvP))
6302
6347
  ) {
6303
6348
  bumpDeep(t);
6304
- dkBumpedP = true;
6349
+ bumped = true;
6305
6350
  }
6306
6351
  if (nodes !== null) {
6307
6352
  const node = nodes[i];
@@ -6316,7 +6361,7 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6316
6361
  if (nodes !== null && nodesHit < t.nc) {
6317
6362
  for (const key of Reflect.ownKeys(nodes)) {
6318
6363
  const idx = typeof key === "string" ? +key : NaN;
6319
- if (!(idx >= 0 && idx < nextRows.length))
6364
+ if (!(idx >= 0 && idx < incoming.length))
6320
6365
  notifyKeyDiff(nodes[key], key, old, incoming, false);
6321
6366
  }
6322
6367
  }
@@ -6325,7 +6370,6 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6325
6370
  }
6326
6371
  const nodes = t.n;
6327
6372
  let nodesHit = 0;
6328
- let dkBumped = false;
6329
6373
  for (const k in incoming) {
6330
6374
  const nv = incoming[k];
6331
6375
  const ov = old[k];
@@ -6335,13 +6379,13 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6335
6379
  (!isObj || !isOwned(nv)) &&
6336
6380
  (nodes === null || nodes[k] === undefined || !hasAccessorFlag(nodes[k]))
6337
6381
  ) {
6338
- if (nodes !== null && nodes[k] !== undefined) nodesHit++;
6382
+ if (nodes?.[k] !== undefined) nodesHit++;
6339
6383
  continue;
6340
6384
  }
6341
- if (isObj && !shallow) descend(unwrapValue(prevView[k]), nv, keyFn, fam, proj);
6342
- if (t.dk !== null && !dkBumped && !(isObj ? targetsEqual(ov, nv) : isEqual(ov, nv))) {
6385
+ if (isObj && !shallow) descend(unwrapValue(prevView[k]), nv, keyFn, proj, t, k);
6386
+ if (!bumped && !(isObj ? targetsEqual(ov, nv) : isEqual(ov, nv))) {
6343
6387
  bumpDeep(t);
6344
- dkBumped = true;
6388
+ bumped = true;
6345
6389
  }
6346
6390
  if (nodes !== null) {
6347
6391
  const node = nodes[k];
@@ -6357,7 +6401,7 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6357
6401
  if (k === $OWNER) continue;
6358
6402
  const nv = incoming[k];
6359
6403
  if (!shallow && nv !== null && typeof nv === "object")
6360
- descend(unwrapValue(old[k]), nv, keyFn, fam, proj);
6404
+ descend(unwrapValue(old[k]), nv, keyFn, proj, t, k);
6361
6405
  if (nodes !== null) {
6362
6406
  const node = nodes[k];
6363
6407
  if (node !== undefined) {
@@ -6377,16 +6421,39 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6377
6421
  const hasOwnP = Object.prototype.hasOwnProperty;
6378
6422
  /** Descend into a changed child pair — only where something is proxied
6379
6423
  * below (§6d), never into a raw-marked leaf, never across array/object or a
6380
- * key mismatch (that is a replacement, handled by the parent's slot). */
6381
- function descend(pv, nv, keyFn, fam, proj = false) {
6382
- if (pv === null || typeof pv !== "object" || nv === null || typeof nv !== "object") return;
6383
- const ct = lookupTarget(pv, fam);
6424
+ * key mismatch (that is a replacement, handled by the parent's slot).
6425
+ *
6426
+ * A raw in both frames (the same objects swapped, reordered or shifted,
6427
+ * #4001) must not leave two slots on one row. A raw already a live row keeps
6428
+ * it at the slot it moved to; a row's `pk` follows the slot it sits at (a
6429
+ * move or a pairing writes it), so a row whose raw is at that slot of the
6430
+ * incoming frame is spoken for and not paired again here; a raw still
6431
+ * resolving to a row that moved on drops the lookup (it wraps fresh, or is
6432
+ * paired here). One lookup of `nv` on the common path, no allocation. */
6433
+ function descend(pv, nv, keyFn, proj, t, k) {
6434
+ // `nv` is an object at every call site.
6435
+ if (pv === null || typeof pv !== "object") return;
6436
+ const fam = t.fam;
6437
+ nv = unwrapValue(nv);
6438
+ let ct = lookupTarget(pv, fam);
6439
+ const nt = lookupTarget(nv, fam);
6440
+ if (ct !== undefined && ct.pk !== k) {
6441
+ if (ct.pk != k && t.v[ct.pk] === ct.v) ct = undefined;
6442
+ else if (ct.u === t) ct.pk = k;
6443
+ }
6444
+ if (nt !== undefined && nt !== ct) {
6445
+ if (nt.v === nv) {
6446
+ if (nt.u === t) nt.pk = k;
6447
+ return;
6448
+ }
6449
+ (fam?.map ?? storeLookup).delete(nv);
6450
+ }
6384
6451
  if (ct === undefined) return; // nothing proxied below this pair
6385
6452
  if (!isWrappable(nv)) return;
6386
6453
  if (rawValuesUsed && isRawValue(nv)) return;
6387
- nv = unwrapValue(nv);
6388
6454
  if (Array.isArray(pv) !== Array.isArray(nv)) return;
6389
- if (keyFn) {
6455
+ // An array slot was paired by key already (or both rows are keyless).
6456
+ if (typeof k !== "number" && keyFn) {
6390
6457
  const pk = keyFn(pv);
6391
6458
  const nk = keyFn(nv);
6392
6459
  if (pk !== undefined && nk !== undefined && !sameKey(pk, nk)) return;
@@ -6809,10 +6876,12 @@ function notifyOptimisticWrites(t, pb) {
6809
6876
  if (structural) {
6810
6877
  const rows = isArr ? pb.map(unwrapValue) : shallowKeys(pb);
6811
6878
  // The rows the setter removed from the view it saw, by key: a later
6812
- // re-base over another action's truth must not bring them back.
6879
+ // re-base over another action's truth must not bring them back. A set
6880
+ // (empty or not) marks a keyed arrangement guess (`fold`).
6813
6881
  let removed = null;
6814
6882
  const keyFn = t.fam.key;
6815
6883
  if (isArr && keyFn !== null && keyFn !== undefined) {
6884
+ removed = new Set();
6816
6885
  const kept = new Set();
6817
6886
  for (let i = 0; i < rows.length; i++) {
6818
6887
  const k = isWrappable(rows[i]) ? keyFn(rows[i]) : undefined;
@@ -6821,7 +6890,7 @@ function notifyOptimisticWrites(t, pb) {
6821
6890
  for (let i = 0; i < old.length; i++) {
6822
6891
  const r = unwrapValue(old[i]);
6823
6892
  const k = isWrappable(r) ? keyFn(r) : undefined;
6824
- if (k !== undefined && !kept.has(k)) (removed ??= new Set()).add(k);
6893
+ if (k !== undefined && !kept.has(k)) removed.add(k);
6825
6894
  }
6826
6895
  }
6827
6896
  optimisticWrite(getContainerNode(t), new LaneView(rows, base, removed));
@@ -6957,7 +7026,8 @@ function reconcileDraft(draft, incoming, keyFn) {
6957
7026
  * committed length differs, and as the `LaneView`'s base for structural
6958
7027
  * reads. Unkeyed families compose
6959
7028
  * positionally — the guess stands as written (the documented residue). */
6960
- GlobalQueue._laneRebase = (el, truth) => {
7029
+ GlobalQueue._laneRebase = (el, truth, older) => {
7030
+ if (!older) return fold(el, truth);
6961
7031
  const t = el._host;
6962
7032
  if (t === undefined || el._key !== $TRACK) return;
6963
7033
  const view = el._x._lane;
@@ -7018,6 +7088,51 @@ GlobalQueue._laneRebase = (el, truth) => {
7018
7088
  laneSet(len, rows.length, lane);
7019
7089
  }
7020
7090
  };
7091
+ /** D-34 for a keyed arrangement guess: rows by key, not positions. A
7092
+ * current truth that differs folds into the hold beneath it instead of
7093
+ * voiding the lane: the guess stays the lane's value and the screen's until
7094
+ * the hold commits, where rows match by key, and the graph outside the lane
7095
+ * re-derives from the truth now (`foldedRead`). A confirmation is silent
7096
+ * (A17): only truth readers are told. True: the lane stages the truth
7097
+ * beneath (`laneWrite`). */
7098
+ function fold(el, truth) {
7099
+ const k = el._host?.k;
7100
+ const l = txOf(el);
7101
+ // A guess the screen never showed is void at the correction (D-34); a
7102
+ // keyed arrangement guess's view carries its removed-keys set.
7103
+ if (!l._shown || !(k?._config & CONFIG_OVERRIDE) || !k._x._lane?.removed) return false;
7104
+ const guess = el._x._lane;
7105
+ if ((!el._equals || !el._equals(guess, truth)) && !GlobalQueue._echoes(guess, truth)) {
7106
+ l.folded = foldedRead;
7107
+ insertSubs(el);
7108
+ } else GlobalQueue._confirmed?.(l);
7109
+ joinFuture(resolveTx(l._parent ?? l));
7110
+ return true;
7111
+ }
7112
+ /** A folded lane's guess read by a deriving pass outside the lane, tracked
7113
+ * or not (`untrack` is about dependencies, not about which world a pass
7114
+ * derives from), or by `latest()` (`c` null): the hold's world — the truth
7115
+ * staged beneath the guess, else the value it covered — and the pass joins
7116
+ * the hold. Lane passes and the screen keep the guess: held truth is masked
7117
+ * from lane passes only (#3164). The store's own derive reconciles its
7118
+ * landings against the arrangement the guesses made: it is the author, not
7119
+ * a reader. */
7120
+ function foldedRead(c, el) {
7121
+ if (c !== null) {
7122
+ if (
7123
+ !(el._config & CONFIG_GUESS) ||
7124
+ passLane !== null ||
7125
+ c._type === EFFECT_RENDER ||
7126
+ c._config & CONFIG_CHILDREN_FORBIDDEN ||
7127
+ el._host?.fam?.node === c
7128
+ )
7129
+ return NOT_PENDING;
7130
+ const l = txOf(el);
7131
+ joinPassAt(c, resolveTx(l._parent ?? l));
7132
+ }
7133
+ if (el._pendingValue !== NOT_PENDING) return el._pendingValue;
7134
+ return el._config & CONFIG_SLOT_NODE ? GlobalQueue._slotCovered(el) : el._value;
7135
+ }
7021
7136
  function sameRow(a, b, keyFn) {
7022
7137
  if (a === b) return true;
7023
7138
  if (!isWrappable(a) || !isWrappable(b)) return false;
@@ -8774,6 +8889,7 @@ function createDeferred(fn, options) {
8774
8889
  // The wrapping form: an unready source parks the node in the window,
8775
8890
  // and the wait is its flight until the source's settle re-runs it.
8776
8891
  if (open && e instanceof NotReadyError) ask(e);
8892
+ else land(flight);
8777
8893
  throw e;
8778
8894
  }
8779
8895
  if (open && typeof r === "object" && r !== null) {
@@ -8794,7 +8910,11 @@ function createDeferred(fn, options) {
8794
8910
  // promise, as for a plain flight: verdict and value in one flush.
8795
8911
  next: v => {
8796
8912
  const p = it.next(v);
8797
- if (first) ((first = false), p.then(done, done));
8913
+ if (first) {
8914
+ first = false;
8915
+ if (isThenable(p)) p.then(done, done);
8916
+ else done();
8917
+ }
8798
8918
  return p;
8799
8919
  },
8800
8920
  return: v => it.return?.(v) ?? Promise.resolve({ done: true, value: v })
@@ -8809,6 +8929,238 @@ function createDeferred(fn, options) {
8809
8929
  );
8810
8930
  }
8811
8931
 
8932
+ // `from` — a push source (callback subscription, Observable, AsyncIterable)
8933
+ // as an AsyncIterable a computation can return or `for await` over:
8934
+ //
8935
+ // const value = createMemo(() => from(source));
8936
+ //
8937
+ // A subscription keeps the latest value: values that arrive between pulls
8938
+ // coalesce. An AsyncIterable streams through unchanged. Either way the source
8939
+ // opens when the result is iterated and closes on return(), on supersede (the
8940
+ // owning computation re-runs) and on owner disposal, whichever comes first.
8941
+ function toSubscribe(source) {
8942
+ if (typeof source === "function") return source;
8943
+ // TC39 / RxJS interop: `Symbol.observable` (or "@@observable") returns the observable.
8944
+ // Read per call, not at module scope, so the module stays side-effect free.
8945
+ const $$observable = Symbol.observable;
8946
+ const interop = ($$observable && source?.[$$observable]) ?? source?.["@@observable"];
8947
+ const obs = typeof interop === "function" ? interop.call(source) : source;
8948
+ if (obs && typeof obs.subscribe === "function")
8949
+ return (next, error, complete) => obs.subscribe({ next, error, complete });
8950
+ return null;
8951
+ }
8952
+ const DONE = { value: undefined, done: true };
8953
+ /**
8954
+ * Ties an iterator's `finish` to the computation iterating it. Solid reaches
8955
+ * an iterator's return() only when the compute returns the iterable directly.
8956
+ * Inside an async generator the generator's own return() is queued behind its
8957
+ * pending `await`, and owner cleanups wait for the replacing pass to commit,
8958
+ * so close on supersede, with owner disposal as the backstop. No owner (an
8959
+ * async compute iterating it after its first `await`, or plain iteration):
8960
+ * no registration and no diagnostic, so only return() closes it. Scopes that
8961
+ * forbid cleanups rely on supersede and return().
8962
+ */
8963
+ function bindToOwner(finish) {
8964
+ const owner = getOwner();
8965
+ if (!owner) return;
8966
+ onSupersede(finish);
8967
+ if (!(owner._config & CONFIG_CHILDREN_FORBIDDEN)) cleanup(finish);
8968
+ }
8969
+ /**
8970
+ * An AsyncIterable as a stream bound to the iterating computation: every
8971
+ * value passes through (no coalescing). `finish` calls the inner return()
8972
+ * once and settles pending pulls as done, so a `for await` over it exits as
8973
+ * soon as the computation re-runs — even when the inner iterator is an async
8974
+ * generator whose own return() the language queues behind its pending
8975
+ * `await`.
8976
+ */
8977
+ function wrapIterable(source) {
8978
+ return {
8979
+ [Symbol.asyncIterator]() {
8980
+ const it = source[Symbol.asyncIterator]();
8981
+ let closed = false;
8982
+ const pending = new Set();
8983
+ const finish = () => {
8984
+ if (closed) return DONE;
8985
+ closed = true;
8986
+ for (const resolve of pending) resolve(DONE);
8987
+ pending.clear();
8988
+ try {
8989
+ const returned = it.return?.();
8990
+ if (isThenable(returned)) returned.then(undefined, () => {});
8991
+ } catch {}
8992
+ return DONE;
8993
+ };
8994
+ bindToOwner(finish);
8995
+ return {
8996
+ next(...args) {
8997
+ if (closed) return Promise.resolve(DONE);
8998
+ const step = it.next(...args);
8999
+ // A bare result (the producer's buffered fast path) passes through.
9000
+ if (!isThenable(step)) return step;
9001
+ return new Promise((resolve, reject) => {
9002
+ pending.add(resolve);
9003
+ step.then(
9004
+ r => pending.delete(resolve) && resolve(r),
9005
+ e => pending.delete(resolve) && reject(e)
9006
+ );
9007
+ });
9008
+ },
9009
+ return() {
9010
+ return Promise.resolve(finish());
9011
+ }
9012
+ };
9013
+ }
9014
+ };
9015
+ }
9016
+ const toCleanup = t =>
9017
+ typeof t === "function"
9018
+ ? t
9019
+ : t && typeof t.unsubscribe === "function"
9020
+ ? () => t.unsubscribe()
9021
+ : () => {};
9022
+ /**
9023
+ * Converts a callback subscription, an Observable or an AsyncIterable into an
9024
+ * AsyncIterable for a Solid computation. A subscription keeps only the latest
9025
+ * value, and a value the source emits during `subscribe` is served without a
9026
+ * loading frame; an AsyncIterable streams every value through.
9027
+ *
9028
+ * The source closes on supersede — the owning computation re-running — and
9029
+ * on owner disposal. Inside an async generator memo that holds for a
9030
+ * `for await` that starts before the first `await`; one that starts after it
9031
+ * has no owner to bind to, so its source closes only when the loop ends.
9032
+ * Prefer returning `from(...)` from its own memo, with awaited inputs read
9033
+ * from upstream memos and transforms done in a downstream memo.
9034
+ *
9035
+ * Iterate the result with `for await` or return it from a computation: its
9036
+ * `next()` returns an already-buffered value as a bare result, not a promise.
9037
+ *
9038
+ * @example
9039
+ * ```ts
9040
+ * const position = createMemo(() =>
9041
+ * from<Position>((next, error) => {
9042
+ * const id = navigator.geolocation.watchPosition(next, error);
9043
+ * return () => navigator.geolocation.clearWatch(id);
9044
+ * })
9045
+ * );
9046
+ * const ticks = createMemo(() => from(interval$(rate())));
9047
+ * ```
9048
+ */
9049
+ function from(source) {
9050
+ const subscribe = toSubscribe(source);
9051
+ if (!subscribe) {
9052
+ if (source && typeof source[Symbol.asyncIterator] === "function") return wrapIterable(source);
9053
+ throw new TypeError("from(): expected a subscribe function, an Observable or an AsyncIterable");
9054
+ }
9055
+ return {
9056
+ [Symbol.asyncIterator]() {
9057
+ let latest;
9058
+ let has = false;
9059
+ let done = false;
9060
+ let failed = false;
9061
+ let failure;
9062
+ let waiting = null;
9063
+ let unsubscribe = null;
9064
+ let closed = false;
9065
+ const close = () => {
9066
+ if (closed) return;
9067
+ closed = true;
9068
+ done = true;
9069
+ const c = unsubscribe;
9070
+ unsubscribe = null;
9071
+ // A throwing unsubscribe must not escape: at supersede this runs
9072
+ // inside flush(), ahead of the replacing pass (the returned-iterator
9073
+ // path swallows it the same way).
9074
+ try {
9075
+ c?.();
9076
+ } catch {}
9077
+ };
9078
+ const settle = () => {
9079
+ if (!waiting) return;
9080
+ const w = waiting;
9081
+ if (has) {
9082
+ waiting = null;
9083
+ has = false;
9084
+ w.resolve({ value: latest, done: false });
9085
+ } else if (failed) {
9086
+ waiting = null;
9087
+ failed = false;
9088
+ w.reject(failure);
9089
+ } else if (done) {
9090
+ waiting = null;
9091
+ w.resolve({ value: undefined, done: true });
9092
+ }
9093
+ };
9094
+ const teardown = subscribe(
9095
+ value => {
9096
+ if (done) return;
9097
+ latest = value;
9098
+ has = true;
9099
+ settle();
9100
+ },
9101
+ err => {
9102
+ if (done) return;
9103
+ failed = true;
9104
+ failure = err;
9105
+ done = true;
9106
+ settle();
9107
+ close();
9108
+ },
9109
+ () => {
9110
+ if (done) return;
9111
+ done = true;
9112
+ settle();
9113
+ close();
9114
+ }
9115
+ );
9116
+ unsubscribe = toCleanup(teardown);
9117
+ // Completed or errored during subscribe: release what it just returned.
9118
+ if (closed) {
9119
+ const c = unsubscribe;
9120
+ unsubscribe = null;
9121
+ try {
9122
+ c();
9123
+ } catch {}
9124
+ }
9125
+ // Idempotent: return(), supersede and disposal may all reach it.
9126
+ const finish = () => {
9127
+ close();
9128
+ has = false;
9129
+ if (waiting) {
9130
+ const w = waiting;
9131
+ waiting = null;
9132
+ w.resolve(DONE);
9133
+ }
9134
+ return DONE;
9135
+ };
9136
+ bindToOwner(finish);
9137
+ return {
9138
+ next() {
9139
+ // A buffered value is returned as a bare result, not a promise: the
9140
+ // iterator consumer lands it in the same pass, so a source that
9141
+ // emits during subscribe (BehaviorSubject-style) shows its value
9142
+ // with no loading frame. `for await` accepts either form.
9143
+ if (has) {
9144
+ has = false;
9145
+ return { value: latest, done: false };
9146
+ }
9147
+ if (failed) {
9148
+ failed = false;
9149
+ return Promise.reject(failure);
9150
+ }
9151
+ if (done) return Promise.resolve({ value: undefined, done: true });
9152
+ return new Promise((resolve, reject) => {
9153
+ waiting = { resolve, reject };
9154
+ });
9155
+ },
9156
+ return() {
9157
+ return Promise.resolve(finish());
9158
+ }
9159
+ };
9160
+ }
9161
+ };
9162
+ }
9163
+
8812
9164
  // CARVE 4: `flatten` lived in boundaries.ts but is the general children
8813
9165
  // resolver the renderers (`web`, `universal`, `solid`'s `children()`) walk
8814
9166
  // through — not a boundary feature. Moved verbatim so the boundary carve
@@ -9310,7 +9662,7 @@ function createBoundary(type, fn, show, onFn) {
9310
9662
  } catch (e) {
9311
9663
  if (!(e instanceof NotReadyError)) {
9312
9664
  if (!globalQueue.notify(getOwner(), STATUS_ERROR, STATUS_ERROR, e)) {
9313
- haltReactivity(e);
9665
+ if (true) haltReactivityAt(e, getOwner());
9314
9666
  throw e;
9315
9667
  }
9316
9668
  return;
@@ -9847,6 +10199,7 @@ export {
9847
10199
  enableExternalSource,
9848
10200
  flatten,
9849
10201
  flush,
10202
+ from,
9850
10203
  getContext,
9851
10204
  getObserver,
9852
10205
  getOwner,