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

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 (89) hide show
  1. package/dist/dev-shared.js +785 -367
  2. package/dist/dev.attribution.js +75 -52
  3. package/dist/dev.js +1120 -567
  4. package/dist/observe/affects-marks.js +86 -0
  5. package/dist/observe/affects.js +22 -73
  6. package/dist/observe/boundaries.js +191 -189
  7. package/dist/observe/core/async.js +175 -131
  8. package/dist/observe/core/attribution-queries.js +1 -1
  9. package/dist/observe/core/attribution.js +176 -159
  10. package/dist/observe/core/constants.js +17 -3
  11. package/dist/observe/core/context.js +3 -3
  12. package/dist/observe/core/core.js +503 -363
  13. package/dist/observe/core/dev.js +66 -38
  14. package/dist/observe/core/effect.js +48 -48
  15. package/dist/observe/core/error.js +5 -3
  16. package/dist/observe/core/external.js +5 -6
  17. package/dist/observe/core/graph.js +57 -43
  18. package/dist/observe/core/heap.js +33 -33
  19. package/dist/observe/core/identities.js +62 -0
  20. package/dist/observe/core/lanes.js +226 -259
  21. package/dist/observe/core/owner.js +62 -50
  22. package/dist/observe/core/scheduler.js +319 -171
  23. package/dist/observe/core/truth.js +45 -0
  24. package/dist/observe/core/verdict.js +88 -50
  25. package/dist/observe/deferred.js +184 -0
  26. package/dist/observe/index.js +7 -1
  27. package/dist/observe/map.js +345 -260
  28. package/dist/observe/reveal.js +76 -76
  29. package/dist/observe/signals.js +41 -30
  30. package/dist/observe/store/affects.js +6 -6
  31. package/dist/observe/store/edges.js +39 -0
  32. package/dist/observe/store/optimistic.js +41 -36
  33. package/dist/observe/store/projection.js +68 -55
  34. package/dist/observe/store/store.js +157 -104
  35. package/dist/prod/affects-marks.js +86 -0
  36. package/dist/prod/affects.js +23 -72
  37. package/dist/prod/boundaries.js +155 -153
  38. package/dist/prod/core/async.js +171 -127
  39. package/dist/prod/core/constants.js +17 -3
  40. package/dist/prod/core/context.js +3 -3
  41. package/dist/prod/core/core.js +478 -342
  42. package/dist/prod/core/effect.js +68 -68
  43. package/dist/prod/core/error.js +5 -3
  44. package/dist/prod/core/external.js +5 -6
  45. package/dist/prod/core/graph.js +57 -43
  46. package/dist/prod/core/heap.js +38 -38
  47. package/dist/prod/core/identities.js +62 -0
  48. package/dist/prod/core/lanes.js +217 -250
  49. package/dist/prod/core/owner.js +62 -51
  50. package/dist/prod/core/scheduler.js +310 -162
  51. package/dist/prod/core/truth.js +45 -0
  52. package/dist/prod/core/verdict.js +87 -49
  53. package/dist/prod/deferred.js +182 -0
  54. package/dist/prod/index.js +7 -1
  55. package/dist/prod/map.js +360 -275
  56. package/dist/prod/reveal.js +76 -76
  57. package/dist/prod/signals.js +41 -30
  58. package/dist/prod/store/affects.js +6 -6
  59. package/dist/prod/store/edges.js +39 -0
  60. package/dist/prod/store/optimistic.js +37 -33
  61. package/dist/prod/store/projection.js +25 -19
  62. package/dist/prod/store/store.js +142 -96
  63. package/dist/types/affects-marks.d.ts +14 -0
  64. package/dist/types/boundaries.d.ts +1 -1
  65. package/dist/types/core/async.d.ts +10 -0
  66. package/dist/types/core/constants.d.ts +13 -1
  67. package/dist/types/core/core.d.ts +44 -2
  68. package/dist/types/core/dev.d.ts +43 -13
  69. package/dist/types/core/graph.d.ts +11 -0
  70. package/dist/types/core/identities.d.ts +17 -0
  71. package/dist/types/core/index.d.ts +2 -1
  72. package/dist/types/core/lanes.d.ts +2 -14
  73. package/dist/types/core/owner.d.ts +5 -3
  74. package/dist/types/core/scheduler.d.ts +47 -28
  75. package/dist/types/core/truth.d.ts +16 -0
  76. package/dist/types/core/types.d.ts +21 -0
  77. package/dist/types/core/verdict.d.ts +32 -0
  78. package/dist/types/deferred.d.ts +28 -0
  79. package/dist/types/index.d.ts +6 -0
  80. package/dist/types/map.d.ts +2 -2
  81. package/dist/types/signals.d.ts +28 -7
  82. package/dist/types/store/edges.d.ts +9 -0
  83. package/dist/types/store/index.d.ts +10 -3
  84. package/dist/types/store/optimistic.d.ts +3 -2
  85. package/dist/types/store/projection.d.ts +4 -3
  86. package/dist/types/store/store.d.ts +1 -1
  87. package/dist/types/store/target.d.ts +16 -8
  88. package/dist/types/store/types.d.ts +14 -0
  89. package/package.json +1 -1
@@ -2,17 +2,19 @@ import { attrHooks } from "../core/attribution-hooks.js";
2
2
 
3
3
  import { CONFIG_MANUAL_WRITE, CONFIG_OVERRIDE, CONFIG_HELD, $REFRESH, NOT_PENDING, STATUS_PENDING, STATUS_ERROR, EFFECT_RENDER, REACTIVE_RECOMPUTING_DEPS, REACTIVE_STAGED_READ, CONFIG_VERDICT, CONFIG_CHILDREN_FORBIDDEN, CONFIG_PROMOTED, REACTIVE_DIRTY, REACTIVE_CHECK } from "../core/constants.js";
4
4
 
5
- import { setSignal, isEqual, read, verdict, snapshotCaptureActive, captureWriteSnapshot, context, notePromotedWrite, untrack, slotSignal, stagedScreen, stagedRead } from "../core/core.js";
5
+ import { writeHook, setSignal, isEqual, read, verdict, snapshotCaptureActive, captureWriteSnapshot, context, notePromotedWrite, untrack, slotSignal, stagedScreen, stagedRead } from "../core/core.js";
6
6
 
7
7
  import { registerGraph } from "../core/dev.js";
8
8
 
9
+ import { signalHook } from "../core/identities.js";
10
+
9
11
  import { setSlotUnobserved } from "../core/graph.js";
10
12
 
11
13
  import { getOwner, getObserver } from "../core/owner.js";
12
14
 
13
15
  import { insertIntoHeap } from "../core/heap.js";
14
16
 
15
- import { txOf, flushTransaction, holdNode, joinFuture, insertSubs, schedule, passLane, globalQueue, queuePendingNode, GlobalQueue, joinPassTx, dirtyQueue } from "../core/scheduler.js";
17
+ import { txOf, flushTransaction, holdNode, joinFuture, insertSubs, schedule, passLane, globalQueue, queuePendingNode, GlobalQueue, installStoreHooks, joinPassTx, dirtyQueue } from "../core/scheduler.js";
16
18
 
17
19
  import { lookupTarget, storeOwners, setLookup, $OWNER, isOwned, markDescendants, devAssertNeverUserMutation } from "./target.js";
18
20
 
@@ -170,7 +172,7 @@ function wrap(e, n = null, t = null, r = n?.fam ?? null) {
170
172
  // adoption swap raw identity without changing the logical value — only
171
173
  // changed leaves notify, R9).
172
174
  const slotNodeEquals = function(e, n) {
173
- return isEqual(e, n) || sameLogicalSlot(this.yt, e, n);
175
+ return isEqual(e, n) || sameLogicalSlot(this.O, e, n);
174
176
  };
175
177
 
176
178
  /** The deep witness: the store decides when it notifies. */ const never = false;
@@ -208,7 +210,7 @@ function sameKey(e, n) {
208
210
  /** The container node's comparator: a staging is a backing (identity — a
209
211
  * pending backing is never "equal" to the committed one); a lane value is
210
212
  * an arrangement (`LaneView`), judged by the optimistic machinery. */ const containerEquals = function(e, n) {
211
- return e === n || optHooks !== null && optHooks.arrangement(this.yt, e, n);
213
+ return e === n || optHooks !== null && optHooks.arrangement(this.O, e, n);
212
214
  };
213
215
 
214
216
  function sameLogicalSlot(e, n, t) {
@@ -221,8 +223,8 @@ function sameLogicalSlot(e, n, t) {
221
223
  * released at the commit that resolves it (`drainFolds`). */ const deferredReleases = new Set;
222
224
 
223
225
  function releaseSlot(e) {
224
- const n = e.yt;
225
- const t = e.bt;
226
+ const n = e.O;
227
+ const t = e.Vt;
226
228
  if (t === $TRACK) {
227
229
  if (n.k === e) n.k = null;
228
230
  } else if (t === $DEEP) {
@@ -245,10 +247,14 @@ function releaseSlot(e) {
245
247
  // dispatches CONFIG_SLOT_NODE nodes here (graph.ts) instead of holding a
246
248
  // per-node closure in a per-node NodeExtension. A dropped node is
247
249
  // unreachable through the store — a fresh read makes a fresh node.
250
+ // A staged write — or a lane's value (a guess, S4) — is state only the node
251
+ // holds: its release waits for the commit that resolves it. So does a leaf of
252
+ // a held container (#4146): a mainline write it carried is in the held
253
+ // staging too, where it would read as the hold's until the landing.
254
+ const slotKept = e => e.ue !== NOT_PENDING || e.l & CONFIG_OVERRIDE || e.O.k?.l & CONFIG_HELD;
255
+
248
256
  setSlotUnobserved(e => {
249
- // A staged write — or a lane's value (a guess, S4) — is state only the
250
- // node holds: defer the release to the commit that resolves it.
251
- if (e.Oe !== NOT_PENDING || e.i & CONFIG_OVERRIDE) deferredReleases.add(e); else releaseSlot(e);
257
+ if (slotKept(e)) deferredReleases.add(e); else releaseSlot(e);
252
258
  });
253
259
 
254
260
  /** A node born while its container has a staging is born from the two
@@ -256,19 +262,19 @@ setSlotUnobserved(e => {
256
262
  * pending one — and held by the container's transaction when the container
257
263
  * is held (#3706: the unit of the hold is the key — a key the batch left
258
264
  * unchanged stages nothing and holds no one). */ function bornStaged(e, n, t, r) {
259
- if (n.en && n.en(n.Ae, t)) return;
265
+ if (n.Ye && n.Ye(n.Ie, t)) return;
260
266
  queuePendingNode(n);
261
- n.Oe = t;
267
+ n.ue = t;
262
268
  const o = e.k;
263
269
  if (o === null) return;
264
270
  // a node-less staging holds nothing (`stageOn`)
265
271
  // Held by the container's transaction only for a key the HELD staging
266
272
  // changed — a mainline layer's change above it stages mainline. (A
267
273
  // container carrying a guess is a lane's: the flush's park decides.)
268
- if ((o.i & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && heldKeyChanged(e, r)) holdNode(n, txOf(o));
274
+ if ((o.l & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && heldKeyChanged(e, r)) holdNode(n, txOf(o));
269
275
  // A28 (4): a batch written inside a creation-time pass is promoted — its
270
276
  // late-materialized leaf with it.
271
- if (o.i & CONFIG_PROMOTED) notePromotedWrite(n);
277
+ if (o.l & CONFIG_PROMOTED) notePromotedWrite(n);
272
278
  }
273
279
 
274
280
  function getNode(e, n,
@@ -293,7 +299,9 @@ t = -1) {
293
299
  // the hottest store path, and the disabled cost must stay one null
294
300
  // check (nodes created before enable() stay generically named).
295
301
  if (attrHooks !== null) {
296
- l._name = storeLabel(e) + "." + String(n);
302
+ const t = storeRoot(e);
303
+ l._name = (storeNames.get(t) ?? "store") + "." + String(n);
304
+ l._loc = storeLocs.get(t);
297
305
  stampNodeOwner(l, e);
298
306
  }
299
307
  r[n] = o;
@@ -330,8 +338,8 @@ function getHasNode(e, n) {
330
338
  // pending backing staged — so whoever asked for it reads the frame it
331
339
  // would have read had the node been there from the write.
332
340
  if (e.pb !== null && !directCommit) {
333
- n.Ae = preBatch(e);
334
- n.Oe = e.pb;
341
+ n.Ie = preBatch(e);
342
+ n.ue = e.pb;
335
343
  queuePendingNode(n);
336
344
  }
337
345
  }
@@ -347,11 +355,11 @@ function getHasNode(e, n) {
347
355
  * staging: nothing allocates, nothing is swept at the commit (dbmon's tick
348
356
  * re-created, queued, swept and dropped ~7000 container nodes). If the
349
357
  * flush parks, the seam materializes and holds the node then
350
- * (`_storePark`) — the one case the staging home exists for. */ function stageOn(e, n) {
351
- if (e.k === null && passLane === null && (context === null || globalQueue.X) && !deriveHolds(e)) return;
358
+ * (`storeParkHook`) — the one case the staging home exists for. */ function stageOn(e, n) {
359
+ if (e.k === null && passLane === null && (context === null || globalQueue.q) && !deriveHolds(e)) return;
352
360
  const t = getContainerNode(e);
353
- if (t.Oe === NOT_PENDING) queuePendingNode(t);
354
- t.Oe = n;
361
+ if (t.ue === NOT_PENDING) queuePendingNode(t);
362
+ t.ue = n;
355
363
  // A28 (4): a write issued inside a pass outside a flush (a creation-time
356
364
  // derive) is promoted at the pass's end — the batch with it.
357
365
  if (context !== null) notePromotedWrite(t);
@@ -361,10 +369,10 @@ function getHasNode(e, n) {
361
369
  /** The projection's derive is held by a transaction and this is its own
362
370
  * write (not a user setter's): the staging is the derive's (`holdWithDerive`). */ function deriveHolds(e) {
363
371
  const n = e.fam?.node;
364
- return n != null && !userWrite && (n.i & CONFIG_HELD) !== 0;
372
+ return n != null && !userWrite && (n.l & CONFIG_HELD) !== 0;
365
373
  }
366
374
 
367
- GlobalQueue.An = e => {
375
+ const storePark = e => {
368
376
  for (let n = 0; n < foldList.length; n++) {
369
377
  const t = foldList[n];
370
378
  if (t.k === null && t.pb !== null) holdNode(getContainerNode(t), e);
@@ -393,9 +401,9 @@ function bumpDeep(e) {
393
401
  // on the guess (`laneWrite` → `supersede`, judged by `containerEquals`);
394
402
  // otherwise the structural readers are walked (the staging is already on
395
403
  // the node; the comparator is the store's, done).
396
- if (e.i & CONFIG_OVERRIDE) setSignal(e, n); else {
397
- if (e.i & CONFIG_HELD) joinFuture(txOf(e));
398
- if (snapshotCaptureActive) captureWriteSnapshot(e, e.Ae);
404
+ if (e.l & CONFIG_OVERRIDE) setSignal(e, n); else {
405
+ if (e.l & CONFIG_HELD) joinFuture(txOf(e));
406
+ if (snapshotCaptureActive) captureWriteSnapshot(e, e.Ie);
399
407
  insertSubs(e);
400
408
  schedule();
401
409
  }
@@ -413,17 +421,22 @@ function storeRootOwner(e) {
413
421
  }
414
422
 
415
423
  function stampNodeOwner(e, n) {
416
- e.L = storeRootOwner(n) ?? null;
424
+ e.H = storeRootOwner(n) ?? null;
417
425
  }
418
426
 
419
427
  const storeNames = new WeakMap;
420
428
 
421
- function nameStore(e, n) {
422
- if (attrHooks !== null && n) storeNames.set(e[$TARGET], n);
423
- }
429
+ // The store's `loc` option, carried onto every property node beside its
430
+ // label (the same gate: nodes created before the engine is installed carry
431
+ // neither).
432
+ const storeLocs = new WeakMap;
424
433
 
425
- function storeLabel(e) {
426
- return storeNames.get(storeRoot(e)) ?? "store";
434
+ function nameStore(e, n, t) {
435
+ if (attrHooks !== null) {
436
+ const r = e[$TARGET];
437
+ if (n) storeNames.set(r, n);
438
+ if (t) storeLocs.set(r, t);
439
+ }
427
440
  }
428
441
 
429
442
  // ---------------------------------------------------------------------------
@@ -512,7 +525,7 @@ const plainProto = e => {
512
525
  if (e.kc > OVERLAY_REBUILD_MAX_KEYS) return false;
513
526
  if (e.del !== null && e.del.size !== 0) return true;
514
527
  const t = e.wk;
515
- if (t !== null && t !== WK_ALL && t.size <= OVERLAY_REBUILD_MIN_ADDS) return false;
528
+ if (t !== null && t !== WK_ALL && t.u === undefined && t.size <= OVERLAY_REBUILD_MIN_ADDS) return false;
516
529
  const r = e.v;
517
530
  let o = 0;
518
531
  for (const e of Reflect.ownKeys(n)) if (!hasOwn.call(r, e) && ++o > OVERLAY_REBUILD_MIN_ADDS) return true;
@@ -532,7 +545,7 @@ const plainProto = e => {
532
545
  e.pb = t;
533
546
  e.ovl = false;
534
547
  // The staging IS the pending backing: re-point the container node.
535
- if (e.k !== null && e.k.Oe === n) e.k.Oe = t;
548
+ if (e.k !== null && e.k.ue === n) e.k.ue = t;
536
549
  return t;
537
550
  }
538
551
 
@@ -553,7 +566,7 @@ const plainProto = e => {
553
566
  // mainline setter mid-hold publishes mainline; the held keys stay held).
554
567
  n = e.pb = cloneRaw(n, e);
555
568
  const t = e.k;
556
- if (t !== null && !(t.i & CONFIG_HELD)) t.Oe = n;
569
+ if (t !== null && !(t.l & CONFIG_HELD)) t.ue = n;
557
570
  } else if (n === null) {
558
571
  const t = e.v;
559
572
  if (!directCommit) queueFold(e);
@@ -632,7 +645,7 @@ const plainProto = e => {
632
645
  } else r = e.v;
633
646
  if (directCommit) {
634
647
  e.pb = null;
635
- if (e.k !== null) e.k.Ae = n;
648
+ if (e.k !== null) e.k.Ie = n;
636
649
  } else {
637
650
  // Eager (INTERNALS §3): the backing swaps now — handlers read the
638
651
  // adopted object before the flush — while the container node, when
@@ -728,7 +741,7 @@ let foldIndex = null;
728
741
  if (r.pb === null) continue;
729
742
  // folded already (a direct commit)
730
743
  const o = r.k;
731
- if (o !== null && o.Oe !== NOT_PENDING) {
744
+ if (o !== null && o.ue !== NOT_PENDING) {
732
745
  // held: commits with its hold
733
746
  foldList.push(r);
734
747
  foldOlds.push(n[t]);
@@ -739,7 +752,7 @@ let foldIndex = null;
739
752
  }
740
753
  if (deferredReleases.size !== 0) {
741
754
  for (const e of deferredReleases) {
742
- if (e.Oe === NOT_PENDING && !(e.i & CONFIG_OVERRIDE) && e.t === null) {
755
+ if (e.o === null && !slotKept(e)) {
743
756
  deferredReleases.delete(e);
744
757
  releaseSlot(e);
745
758
  }
@@ -750,28 +763,28 @@ let foldIndex = null;
750
763
  /** Core asks: is a slot node's truth in flight? Its family's derive is —
751
764
  * lanes (A17, #2864: a guess mid-refetch stands in for the flight) and
752
765
  * marks (an ambient `affects()` over a declared reload lives to the
753
- * landing) read it. */ GlobalQueue.C = e => {
754
- const n = e.yt?.fam;
755
- return n != null && n.node !== null && (n.node.S & STATUS_PENDING) !== 0;
766
+ * landing) read it. */ GlobalQueue.k = e => {
767
+ const n = e.O?.fam;
768
+ return n != null && n.node !== null && (n.node.m & STATUS_PENDING) !== 0;
756
769
  };
757
770
 
758
771
  /** Core asks (lanes.ts `covered`): the value a slot node's guess covered.
759
772
  * A chained link's committed value is the inner store's at the last
760
773
  * read-through — refreshed here, since a guess served the reads since
761
- * (#3672: the revert compares against the base's live value). */ GlobalQueue.In = e => {
762
- const n = e.yt;
774
+ * (#3672: the revert compares against the base's live value). */ GlobalQueue.fn = e => {
775
+ const n = e.O;
763
776
  if (n.ch) {
764
- const t = e.bt;
777
+ const t = e.Vt;
765
778
  const r = n.v;
766
- if (n.h !== null && n.h[t] === e) e.Ae = untrack(() => t in r); else if (n.n !== null && n.n[t] === e) e.Ae = untrack(() => unwrapValue(r[t]));
779
+ if (n.h !== null && n.h[t] === e) e.Ie = untrack(() => t in r); else if (n.n !== null && n.n[t] === e) e.Ie = untrack(() => unwrapValue(r[t]));
767
780
  }
768
- return e.Ae;
781
+ return e.Ie;
769
782
  };
770
783
 
771
- GlobalQueue.Rn = () => {
784
+ installStoreHooks(storePark, () => {
772
785
  drainFolds();
773
786
  optHooks?.sweep();
774
- };
787
+ });
775
788
 
776
789
  /** The store half of `affects()` (store/affects.ts) makes its marks' nodes
777
790
  * and walks records through these: it never imports the engine. */ GlobalQueue.no = getNode;
@@ -804,7 +817,7 @@ function runDirect(e) {
804
817
  // the adopted object (shared ownership — never cloned).
805
818
  e.pb = null;
806
819
  e.wk = null;
807
- } else if (e.ovl && (e.v !== n || !overlayRebuilds(e, r) && t?.m?.ot !== n)) {
820
+ } else if (e.ovl && (e.v !== n || !overlayRebuilds(e, r) && t?.T?.lt !== n)) {
808
821
  // Overlay flatten (#3044): the backing keeps its identity, so the
809
822
  // `t.v === old` gate below skips path copying (the parent slot
810
823
  // already points here) and the adopted-notify (setter
@@ -818,8 +831,8 @@ function runDirect(e) {
818
831
  const t = e.v;
819
832
  const o = e.wk;
820
833
  if (o !== null && o !== WK_ALL) {
821
- for (const e of o) {
822
- if (hasOwn.call(r, e)) copyOwn(t, r, e); else delete t[e];
834
+ for (let e = o; e !== undefined; e = e.u) for (const n of e) {
835
+ if (hasOwn.call(r, n)) copyOwn(t, r, n); else delete t[n];
823
836
  }
824
837
  } else {
825
838
  for (const e of Reflect.ownKeys(r)) {
@@ -846,7 +859,7 @@ function runDirect(e) {
846
859
  // subscribes to was a transient staging home (INTERNALS §5: an
847
860
  // observer-less write holds a transient record until the fold,
848
861
  // discarded with it) — gone with the batch.
849
- if (t.t === null) e.k = null; else if (t.Ae instanceof LaneView) t.Ae.base = e.v; else t.Ae = e.v;
862
+ if (t.o === null) e.k = null; else if (t.Ie instanceof LaneView) t.Ie.base = e.v; else t.Ie = e.v;
850
863
  }
851
864
  if (e.v !== n && e.u) {
852
865
  const t = parentSlotKey(e, n);
@@ -919,9 +932,9 @@ function reportReplacedContainers(e, n, t, r) {
919
932
 
920
933
  function heldDerivation(e, n, t) {
921
934
  if (e.fam === null || !userWrite) return false;
922
- if (n !== undefined) return (n.i & (CONFIG_HELD | CONFIG_MANUAL_WRITE)) === CONFIG_HELD && txOf(n) !== flushTransaction;
935
+ if (n !== undefined) return (n.l & (CONFIG_HELD | CONFIG_MANUAL_WRITE)) === CONFIG_HELD && txOf(n) !== flushTransaction;
923
936
  const r = e.k;
924
- return r !== null && (r.i & CONFIG_HELD) !== 0 && txOf(r) !== flushTransaction && heldKeyChanged(e, t);
937
+ return r !== null && (r.l & CONFIG_HELD) !== 0 && txOf(r) !== flushTransaction && heldKeyChanged(e, t);
925
938
  }
926
939
 
927
940
  /** Setter exit: one node write per changed observed key (the leaves stage
@@ -951,7 +964,10 @@ function heldDerivation(e, n, t) {
951
964
  // value can change when ANY key is written), or a non-plain prototype
952
965
  // (class instances: prototype getters derive from arbitrary fields).
953
966
  // Overlay pbs chain to the COMMITTED object (#3044) — plainness is the
954
- // committed container's prototype, not the overlay's.
967
+ // committed container's prototype, not the overlay's. THIS setter's keys
968
+ // only (D-8, #3743): a key an earlier setter of the batch wrote and this
969
+ // one did not is no second proposal — its node was told then, and telling
970
+ // a held node its unchanged value again would join this tick to the hold.
955
971
  const o = e.wk;
956
972
  const i = o === WK_ALL || e.a === true || !plainProto(e.ovl ? e.v : n) ? null : o;
957
973
  if (attrHooks !== null) reportReplacedContainers(e, t, n, i);
@@ -987,9 +1003,9 @@ function heldDerivation(e, n, t) {
987
1003
  // The derive's write is a derivation; a user setter's is a proposal
988
1004
  // (A34 (3)'s discriminator).
989
1005
  if (!userWrite) {
990
- o.i &= ~CONFIG_MANUAL_WRITE;
1006
+ o.l &= ~CONFIG_MANUAL_WRITE;
991
1007
  holdWithDerive(e, o);
992
- } else o.i |= CONFIG_MANUAL_WRITE;
1008
+ } else o.l |= CONFIG_MANUAL_WRITE;
993
1009
  }
994
1010
  }
995
1011
  }
@@ -1020,7 +1036,7 @@ function heldDerivation(e, n, t) {
1020
1036
  // container carrying an arrangement guess is told whether or not anyone
1021
1037
  // subscribes: the write is a landing on the guess (`notifyContainer`).
1022
1038
  const u = e.k;
1023
- if (u !== null && (u.t !== null || u.i & CONFIG_OVERRIDE)) {
1039
+ if (u !== null && (u.o !== null || u.l & CONFIG_OVERRIDE)) {
1024
1040
  let r;
1025
1041
  if (e.ovl) {
1026
1042
  r = e.del !== null && e.del.size !== 0;
@@ -1122,7 +1138,7 @@ function hasAccessorFlag(e) {
1122
1138
  for (const o of Reflect.ownKeys(r)) if (e || o in n !== o in t) setSignal(r[o], o in t);
1123
1139
  }
1124
1140
  const o = e.k;
1125
- if (o !== null && (o.t !== null || o.i & CONFIG_OVERRIDE)) {
1141
+ if (o !== null && (o.o !== null || o.l & CONFIG_OVERRIDE)) {
1126
1142
  const e = Array.isArray(t) && Array.isArray(n) ? arrayStructureChanged(n, t) : membershipChanged(n, t);
1127
1143
  if (e) notifyContainer(o, t);
1128
1144
  }
@@ -1179,7 +1195,7 @@ function inDraft(e) {
1179
1195
  * "adoption is eager by contract" — and the node keeps the frame committed
1180
1196
  * readers under a hold still see), `t.v` otherwise. */ function committed(e) {
1181
1197
  const n = e.k;
1182
- if (n !== null) return n.Oe !== NOT_PENDING ? asBacking(n.Ae) : e.v;
1198
+ if (n !== null) return n.ue !== NOT_PENDING ? asBacking(n.Ie) : e.v;
1183
1199
  // A node-less staging (`stageOn`): the fold queue remembers the frame.
1184
1200
  return e.pb !== null ? preBatch(e) : e.v;
1185
1201
  }
@@ -1191,8 +1207,8 @@ function inDraft(e) {
1191
1207
  * committed frame. */ function stagingReader() {
1192
1208
  let e = context;
1193
1209
  if (e === null) return null;
1194
- if (e.Ht) e = e.gt ?? null;
1195
- return e === null || e.i & CONFIG_CHILDREN_FORBIDDEN ? null : e;
1210
+ if (e.Pt) e = e.Ht ?? null;
1211
+ return e === null || e.l & CONFIG_CHILDREN_FORBIDDEN ? null : e;
1196
1212
  }
1197
1213
 
1198
1214
  /** The backing a read serves (RUL-1, signal parity): the draft and the write
@@ -1230,10 +1246,10 @@ function inDraft(e) {
1230
1246
  if (writeOverride && !userWrite) {
1231
1247
  const t = n !== undefined ? e.n?.[n] : undefined;
1232
1248
  if (t !== undefined) {
1233
- if ((t.i & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD) joinPassTx(txOf(t));
1249
+ if ((t.l & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD) joinPassTx(txOf(t));
1234
1250
  } else {
1235
1251
  const t = e.k;
1236
- if (t !== null && (t.i & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && (n === undefined || heldKeyChanged(e, n))) joinPassTx(txOf(t));
1252
+ if (t !== null && (t.l & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && (n === undefined || heldKeyChanged(e, n))) joinPassTx(txOf(t));
1237
1253
  }
1238
1254
  }
1239
1255
  return r;
@@ -1245,9 +1261,9 @@ function inDraft(e) {
1245
1261
  // it).
1246
1262
  if (n !== undefined) {
1247
1263
  const t = e.n?.[n];
1248
- if (t !== undefined && t.i & CONFIG_OVERRIDE) return committed(e);
1264
+ if (t !== undefined && t.l & CONFIG_OVERRIDE) return committed(e);
1249
1265
  }
1250
- const i = o !== null && (o.i & CONFIG_HELD) !== 0;
1266
+ const i = o !== null && (o.l & CONFIG_HELD) !== 0;
1251
1267
  // A held container: the hold's unit is the key (#3706). A key the HELD
1252
1268
  // staging left unchanged reads the ambient view — a mainline layer's
1253
1269
  // write above the hold publishes mainline (#3688, #3612); a key it
@@ -1265,7 +1281,7 @@ function inDraft(e) {
1265
1281
  // probe) judges the container like any node — for a key the batch
1266
1282
  // changed; an unchanged key is committed and final (A22: pending is per
1267
1283
  // key). A held container's committed frame is the node's.
1268
- if (verdict === null || !i && n !== undefined && !keyChanged(e, n, t)) return i ? asBacking(o.Ae) : e.v;
1284
+ if (verdict === null || !i && n !== undefined && !keyChanged(e, n, t)) return i ? asBacking(o.Ie) : e.v;
1269
1285
  return o === null ? committed(e) : asBacking(read(o));
1270
1286
  }
1271
1287
  // A pass reading a key the batch left unchanged: the same value in both
@@ -1282,13 +1298,13 @@ function inDraft(e) {
1282
1298
  // frames, a changed one is what the mark already stands for. The
1283
1299
  // per-key question is asked once per pass, not once per key (a
1284
1300
  // `mapArray` over 1k shifted rows asked it 1k times).
1285
- if (l.R & REACTIVE_STAGED_READ && !(l.i & (CONFIG_VERDICT | CONFIG_CHILDREN_FORBIDDEN))) return r;
1301
+ if (l.R & REACTIVE_STAGED_READ && !(l.l & (CONFIG_VERDICT | CONFIG_CHILDREN_FORBIDDEN))) return r;
1286
1302
  if (n !== undefined && !keyChanged(e, n, t)) return committed(e);
1287
1303
  // A node-less staging (`stageOn`): what `read()` of its node would do
1288
1304
  // for this pass — a verdict reader sees the screen, any other derives
1289
1305
  // from the staging and is marked as having read it.
1290
1306
  if (o === null) {
1291
- if (l.i & CONFIG_VERDICT && stagedScreen(l)) return committed(e);
1307
+ if (l.l & CONFIG_VERDICT && stagedScreen(l)) return committed(e);
1292
1308
  stagedRead(l);
1293
1309
  return r;
1294
1310
  }
@@ -1298,7 +1314,7 @@ function inDraft(e) {
1298
1314
 
1299
1315
  /** Did the HELD staging (the container node's, not a mainline layer above
1300
1316
  * it) change `key`? */ function heldKeyChanged(e, n, t = true) {
1301
- return changedBetween(e, e.k.Ae, e.k.Oe, n, t) && !adoptChild(e, n);
1317
+ return changedBetween(e, e.k.Ie, e.k.ue, n, t) && !adoptChild(e, n);
1302
1318
  }
1303
1319
 
1304
1320
  /** The values of a held frame, built once per frame: neither the committed
@@ -1325,14 +1341,14 @@ function rawsOf(e) {
1325
1341
  * `version` holds. A record's keys pair by name; an array's by the family's
1326
1342
  * key — without a family the pairing is not known here and the hold stands. */ function adoptChild(e, n) {
1327
1343
  const t = e.k;
1328
- const r = asBacking(t.Ae);
1344
+ const r = asBacking(t.Ie);
1329
1345
  const o = r[n];
1330
- const i = t.Oe[n];
1346
+ const i = t.ue[n];
1331
1347
  const l = Array.isArray(r) ? e.fam?.key : null;
1332
1348
  if (e.s || l === undefined || !sameRow(o, i, l) || !isWrappable(o) || !isWrappable(i) || Array.isArray(o) !== Array.isArray(i) || lookupTarget(o, e.fam) !== undefined || lookupTarget(i, e.fam) !== undefined ||
1333
1349
  // A raw in both frames (the same objects swapped or reordered) is its
1334
1350
  // own row: pairing it with another would alias the two after the commit.
1335
- rawsOf(r).has(i) || rawsOf(t.Oe).has(o)) return false;
1351
+ rawsOf(r).has(i) || rawsOf(t.ue).has(o)) return false;
1336
1352
  // A raw-marked value wraps to itself, a proxy to its own target: neither
1337
1353
  // is a fresh target around `ov`.
1338
1354
  const u = wrap(o, e, n)[$TARGET];
@@ -1343,7 +1359,7 @@ function rawsOf(e) {
1343
1359
  }
1344
1360
 
1345
1361
  /** A family target's unheld staging, read with no pass (a handler). */ function familyAhead(e) {
1346
- return e.fam !== null && context === null && !(e.k !== null && e.k.i & CONFIG_HELD);
1362
+ return e.fam !== null && context === null && !(e.k !== null && e.k.l & CONFIG_HELD);
1347
1363
  }
1348
1364
 
1349
1365
  /** Did this batch change `key` — value (slot equality), presence, and (for
@@ -1377,7 +1393,7 @@ function changedBetween(e, n, t, r, o) {
1377
1393
  * tracked or not, applies the hold and A28 rules a signal's read does. A
1378
1394
  * projection's unheld staging is ahead for a handler (`familyAhead`). */ function nodeValue(e, n, t) {
1379
1395
  let r = read(e);
1380
- if (e.Oe !== NOT_PENDING && !(e.i & CONFIG_HELD) && context === null && t.fam !== null) r = e.Oe;
1396
+ if (e.ue !== NOT_PENDING && !(e.l & CONFIG_HELD) && context === null && t.fam !== null) r = e.ue;
1381
1397
  return r === FORCE ? n : r;
1382
1398
  }
1383
1399
 
@@ -1397,8 +1413,8 @@ function changedBetween(e, n, t, r, o) {
1397
1413
  if (l) {
1398
1414
  // The link's committed value follows the inner store's (what a lane
1399
1415
  // over it covers — its reveal and revert compare against it, S4).
1400
- o.Ae = t;
1401
- if (o.i & CONFIG_OVERRIDE) u = n;
1416
+ o.Ie = t;
1417
+ if (o.l & CONFIG_OVERRIDE) u = n;
1402
1418
  } else u = n;
1403
1419
  }
1404
1420
  }
@@ -1419,7 +1435,7 @@ function changedBetween(e, n, t, r, o) {
1419
1435
  }
1420
1436
 
1421
1437
  /** Is `n` among `c`'s dependencies (any pass's — a kept tail included)? */ function linkedTo(e, n) {
1422
- for (let t = e.G; t !== null; t = t._) if (t.I === n) return true;
1438
+ for (let t = e.C; t !== null; t = t.G) if (t.S === n) return true;
1423
1439
  return false;
1424
1440
  }
1425
1441
 
@@ -1438,7 +1454,7 @@ function changedBetween(e, n, t, r, o) {
1438
1454
  // reader of a memo would — linked, so it re-runs at the landing (the
1439
1455
  // next settled pass pulls without linking and the stale link trims), a
1440
1456
  // verdict reader registered, a tracked pass suspended.
1441
- if (n.S & (STATUS_PENDING | STATUS_ERROR)) {
1457
+ if (n.m & (STATUS_PENDING | STATUS_ERROR)) {
1442
1458
  // A render effect outside the flight's own flush is the frame, not a
1443
1459
  // derivation: it keeps what it shows and learns of the landing from the
1444
1460
  // leaves the landing changes (unchanged leaves say nothing — it is not a
@@ -1449,7 +1465,7 @@ function changedBetween(e, n, t, r, o) {
1449
1465
  // release the hold with the flight still up. An errored derive has no
1450
1466
  // landing to learn of: the error is what the frame shows.
1451
1467
  const t = context;
1452
- if (t !== null && t.u === EFFECT_RENDER && !(n.S & STATUS_ERROR) && n.i & CONFIG_HELD && flushTransaction !== txOf(n) && !linkedTo(t, n)) return;
1468
+ if (t !== null && t.i === EFFECT_RENDER && !(n.m & STATUS_ERROR) && n.l & CONFIG_HELD && flushTransaction !== txOf(n) && !linkedTo(t, n)) return;
1453
1469
  // An optimistic family carrying guesses that stand in for the flight
1454
1470
  // (A17): the guesses are the display — a reader is served the leaves
1455
1471
  // (committed plus the guesses) and does not suspend on the derive; a
@@ -1457,7 +1473,7 @@ function changedBetween(e, n, t, r, o) {
1457
1473
  // up). The family's, not the target's: the edit stands in for the one
1458
1474
  // flight the family has.
1459
1475
  const r = e.fam.overlaid;
1460
- if (verdict === null && n.S & STATUS_PENDING && r !== undefined && r.size !== 0) return;
1476
+ if (verdict === null && n.m & STATUS_PENDING && r !== undefined && r.size !== 0) return;
1461
1477
  read(n);
1462
1478
  } else untrack(() => read(n));
1463
1479
  }
@@ -1470,12 +1486,12 @@ function changedBetween(e, n, t, r, o) {
1470
1486
  * classification, as core reads it — `heldNotFinal`). */ function holdWithDerive(e, n) {
1471
1487
  const t = e.fam?.node;
1472
1488
  if (t !== undefined && t !== null && !userWrite && // the derive's own write (its draft or landing), not a user setter's
1473
- t.i & CONFIG_HELD && n.Oe !== NOT_PENDING &&
1489
+ t.l & CONFIG_HELD && n.ue !== NOT_PENDING &&
1474
1490
  // A node carrying a guess is the lane's: its staging is the truth beneath
1475
1491
  // the guess (`setSignal` → `laneWrite`), never re-homed by the store.
1476
- !(n.i & (CONFIG_HELD | CONFIG_OVERRIDE))) {
1492
+ !(n.l & (CONFIG_HELD | CONFIG_OVERRIDE))) {
1477
1493
  holdNode(n, txOf(t));
1478
- n.m.It = t.m.It;
1494
+ n.T.ut = t.T.ut;
1479
1495
  }
1480
1496
  }
1481
1497
 
@@ -1518,6 +1534,22 @@ function changedBetween(e, n, t, r, o) {
1518
1534
 
1519
1535
  // ---------------------------------------------------------------------------
1520
1536
  // traps
1537
+ /** A trap write: the target is told at the setter's exit, the key recorded
1538
+ * in this setter's `wk`. A setter's first write to a target still staged by
1539
+ * an earlier setter of the batch opens a set of its own (the notify visits
1540
+ * it, D-8) and chains the earlier one behind for the fold's union. */ function touch(e, n) {
1541
+ let t = e.wk;
1542
+ if (!pendingNotify.has(e)) {
1543
+ pendingNotify.add(e);
1544
+ if (t !== null && t !== WK_ALL) {
1545
+ t = new Set;
1546
+ t.u = e.wk;
1547
+ e.wk = t;
1548
+ }
1549
+ }
1550
+ if (t !== WK_ALL) (e.wk ??= new Set).add(n);
1551
+ }
1552
+
1521
1553
  const traps = {
1522
1554
  get(e, n, t) {
1523
1555
  // One typeof gates every brand-symbol compare off the hot string path.
@@ -1561,7 +1593,7 @@ const traps = {
1561
1593
  const r = e.n?.[n];
1562
1594
  if (writeScopes === null && !writeOverride) {
1563
1595
  const t = r;
1564
- if (t !== undefined && t.acc !== true && (!e.ch || t.i & CONFIG_OVERRIDE)) {
1596
+ if (t !== undefined && t.acc !== true && (!e.ch || t.l & CONFIG_OVERRIDE)) {
1565
1597
  const r = nodeValue(t, undefined, e);
1566
1598
  if (r === null || typeof r !== "object") return r;
1567
1599
  if (e.s) return serveShallow(e, n, r);
@@ -1656,9 +1688,9 @@ const traps = {
1656
1688
  const o = read(r);
1657
1689
  if (!e.ch || t !== e.v) return !!o;
1658
1690
  const i = n in t;
1659
- r.Ae = i;
1691
+ r.Ie = i;
1660
1692
  // the link follows the inner store (S4)
1661
- if (r.i & CONFIG_OVERRIDE) return !!o;
1693
+ if (r.l & CONFIG_OVERRIDE) return !!o;
1662
1694
  return i;
1663
1695
  } else if (optRead(e)) {
1664
1696
  const t = optHooks.has(e, n);
@@ -1693,7 +1725,7 @@ const traps = {
1693
1725
  const t = getHasNode(e, n);
1694
1726
  const r = read(t);
1695
1727
  i = readSource(e, n, true);
1696
- if (!r && (!e.ch || t.i & CONFIG_OVERRIDE || !(n in i))) return undefined;
1728
+ if (!r && (!e.ch || t.l & CONFIG_OVERRIDE || !(n in i))) return undefined;
1697
1729
  } else i = readSource(e, n, true);
1698
1730
  let l;
1699
1731
  if (!o && optRead(e)) {
@@ -1714,15 +1746,22 @@ const traps = {
1714
1746
  // pollution guard (core R30)
1715
1747
  const i = e.s ? t : unwrapValue(t);
1716
1748
  const l = ensurePB(e);
1717
- pendingNotify.add(e);
1749
+ // #4146 (R1: node existence is unobservable; #3706: the hold's unit is
1750
+ // the key): a user setter writing a key a HELD container's staging left
1751
+ // unchanged would write it into the held staging — read as the hold's
1752
+ // until its landing, while the same key with a leaf (a live reader)
1753
+ // publishes at the flush. The key gets its leaf first, born from the two
1754
+ // frames (`bornStaged`: a key the hold left unchanged — or added — is
1755
+ // unstaged and held by no one; a key the hold changed is born held, A34
1756
+ // (1)), and the write stages on it (A28; a write by the holder joins it,
1757
+ // A34) — as it would with a reader's leaf. Kept while the container is
1758
+ // held (`deferredReleases`).
1759
+ if (userWrite && e.k?.l & CONFIG_HELD) deferredReleases.add(getNode(e, n));
1718
1760
  if (Array.isArray(l)) {
1719
- if (n === "length") e.wk = WK_ALL; else if (e.wk !== WK_ALL) {
1720
- const t = e.wk ??= new Set;
1721
- t.add(n);
1722
- t.add("length");
1723
- }
1761
+ touch(e, n);
1762
+ if (n === "length") e.wk = WK_ALL; else touch(e, "length");
1724
1763
  } else {
1725
- if (e.wk !== WK_ALL) (e.wk ??= new Set).add(n);
1764
+ touch(e, n);
1726
1765
  if (!(n in l)) e.kc++;
1727
1766
  }
1728
1767
  if (UNSAFE_KEYS.has(n)) {
@@ -1760,8 +1799,7 @@ const traps = {
1760
1799
  };
1761
1800
  const i = ensurePB(e);
1762
1801
  if (e.a || !(t.enumerable && t.writable && t.configurable)) e.sc = 1;
1763
- pendingNotify.add(e);
1764
- if (e.wk !== WK_ALL) (e.wk ??= new Set).add(n);
1802
+ touch(e, n);
1765
1803
  Object.defineProperty(i, n, t);
1766
1804
  if (e.del !== null) e.del.delete(n);
1767
1805
  if (o) notifyWrites(e);
@@ -1772,10 +1810,22 @@ const traps = {
1772
1810
  const r = !t && writeOverride;
1773
1811
  if (!t && !r) return true;
1774
1812
  const o = ensurePB(e);
1775
- pendingNotify.add(e);
1776
- if (e.wk !== WK_ALL) (e.wk ??= new Set).add(n);
1777
- delete o[n];
1778
- if (e.ovl && hasOwn.call(e.v, n)) (e.del ??= new Set).add(n);
1813
+ touch(e, n);
1814
+ // The key count (`kc`, the overlay gate and the rebuild cap) follows a
1815
+ // delete the way it follows an add: an own key of the draft leaving — a
1816
+ // clone's key, or an overlay's own add — is one fewer now. An overlay's
1817
+ // override of a committed key is the exception: the committed key's
1818
+ // deletion is recorded in `del` and counted at the rebuild, as before.
1819
+ // Without the decrement the natural "delete the old key, set the new
1820
+ // one" shape drifted the count up by one per write until a three-key
1821
+ // object crossed the overlay threshold and paid a rebuild on every
1822
+ // commit after.
1823
+ const i = e.ovl && hasOwn.call(e.v, n);
1824
+ if (hasOwn.call(o, n)) {
1825
+ delete o[n];
1826
+ i || e.kc--;
1827
+ }
1828
+ if (i) (e.del ??= new Set).add(n);
1779
1829
  if (r) notifyWrites(e);
1780
1830
  return true;
1781
1831
  }
@@ -1786,11 +1836,12 @@ const traps = {
1786
1836
  * to the key's presence node or a later add never re-runs it. */ function observerHoldsContainer(e, n) {
1787
1837
  const t = e.k;
1788
1838
  if (t === null) return false;
1789
- const r = t.Dt;
1790
- return r !== null && r.l === n && (!(n.R & REACTIVE_RECOMPUTING_DEPS) || r.Pe === n.pe);
1839
+ const r = t.dt;
1840
+ return r !== null && r.u === n && (!(n.R & REACTIVE_RECOMPUTING_DEPS) || r.Te === n.Ne);
1791
1841
  }
1792
1842
 
1793
1843
  function storeSetter(e, n, t = true) {
1844
+ if (writeHook !== null) writeHook(e);
1794
1845
  const r = e[$TARGET];
1795
1846
  const o = writeScopes;
1796
1847
  writeScopes = new Set;
@@ -1844,6 +1895,8 @@ function storeSetter(e, n, t = true) {
1844
1895
  function createStore(e, n) {
1845
1896
  const t = n?.shallow === true;
1846
1897
  const r = wrap(e);
1898
+ // Dependency edges (store/edges.ts): the root is named like a plain signal.
1899
+ if (signalHook !== null) signalHook(r[$TARGET]);
1847
1900
  if (t) {
1848
1901
  r[$TARGET].s = true;
1849
1902
  markRawIngest(e);
@@ -1858,7 +1911,7 @@ function createStore(e, n) {
1858
1911
  // disabled, buys nothing.
1859
1912
  if (attrHooks !== null) {
1860
1913
  storeOwners.set(r[$TARGET], e);
1861
- nameStore(r, n?.name);
1914
+ nameStore(r, n?.name, n?.loc);
1862
1915
  }
1863
1916
  }
1864
1917
  const setter = e => storeSetter(r, e);
@@ -1987,7 +2040,7 @@ function snapshotWalk(e, n, t) {
1987
2040
  // R27: a snapshot sees the batch's pending backing — a held one (another
1988
2041
  // transaction's future) only from its own draft.
1989
2042
  let i = o.v;
1990
- if (o.pb !== null && (inDraft(o) || writeOverride || o.k === null || !(o.k.i & CONFIG_HELD))) {
2043
+ if (o.pb !== null && (inDraft(o) || writeOverride || o.k === null || !(o.k.l & CONFIG_HELD))) {
1991
2044
  if (o.ovl) materializePB(o);
1992
2045
  i = o.pb;
1993
2046
  }