@solidjs/signals 2.0.0-beta.19 → 2.0.0-beta.20

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 (36) hide show
  1. package/dist/dev.js +670 -371
  2. package/dist/node.cjs +1878 -1606
  3. package/dist/prod/affects.js +17 -21
  4. package/dist/prod/core/action.js +19 -6
  5. package/dist/prod/core/async.js +57 -71
  6. package/dist/prod/core/constants.js +15 -1
  7. package/dist/prod/core/core.js +39 -39
  8. package/dist/prod/core/effect.js +14 -14
  9. package/dist/prod/core/graph.js +1 -1
  10. package/dist/prod/core/heap.js +2 -2
  11. package/dist/prod/core/lanes.js +23 -13
  12. package/dist/prod/core/optimistic.js +107 -99
  13. package/dist/prod/core/owner.js +23 -23
  14. package/dist/prod/core/scheduler.js +180 -180
  15. package/dist/prod/core/verdict.js +12 -13
  16. package/dist/prod/map.js +196 -167
  17. package/dist/prod/signals.js +1 -1
  18. package/dist/prod/store/optimistic.js +103 -68
  19. package/dist/prod/store/reconcile.js +80 -46
  20. package/dist/prod/store/store.js +204 -73
  21. package/dist/prod/store/utils.js +61 -42
  22. package/dist/types/core/action.d.ts +19 -6
  23. package/dist/types/core/constants.d.ts +12 -0
  24. package/dist/types/core/dev.d.ts +7 -0
  25. package/dist/types/core/lanes.d.ts +2 -0
  26. package/dist/types/core/scheduler.d.ts +2 -6
  27. package/dist/types/core/types.d.ts +9 -1
  28. package/dist/types/store/store.d.ts +8 -2
  29. package/dist/types-cjs/core/action.d.cts +19 -6
  30. package/dist/types-cjs/core/constants.d.cts +12 -0
  31. package/dist/types-cjs/core/dev.d.cts +7 -0
  32. package/dist/types-cjs/core/lanes.d.cts +2 -0
  33. package/dist/types-cjs/core/scheduler.d.cts +2 -6
  34. package/dist/types-cjs/core/types.d.cts +9 -1
  35. package/dist/types-cjs/store/store.d.cts +8 -2
  36. package/package.json +1 -1
@@ -1,12 +1,14 @@
1
- import { $REFRESH, STORE_SNAPSHOT_PROPS, NOT_PENDING, STATUS_PENDING, NO_SNAPSHOT } from "../core/constants.js";
1
+ import { $REFRESH, STORE_SNAPSHOT_PROPS, NOT_PENDING, unwrapOverride, STATUS_UNINITIALIZED, STATUS_PENDING, NO_SNAPSHOT } from "../core/constants.js";
2
2
 
3
3
  import { suppressComputedRecompute, isEqual, signal, pendingCheckActive, untrack, setSignal, read, snapshotCaptureActive, snapshotSources } from "../core/core.js";
4
4
 
5
5
  import { DEV } from "../core/dev.js";
6
6
 
7
+ import { NotReadyError } from "../core/error.js";
8
+
7
9
  import { getObserver } from "../core/owner.js";
8
10
 
9
- import { GlobalQueue, registerTransientStoreNode, projectionWriteActive, globalQueue } from "../core/scheduler.js";
11
+ import { GlobalQueue, registerTransientStoreNode, projectionWriteActive, activeTransition, globalQueue } from "../core/scheduler.js";
10
12
 
11
13
  import "../core/invariants.js";
12
14
 
@@ -26,7 +28,7 @@ import { createProjectionInternal } from "./projection.js";
26
28
  // the record witnesses it into the active isPending() probe.
27
29
  $AFFECTS = Symbol(0);
28
30
 
29
- const STORE_VALUE = "v", STORE_OVERRIDE = "o", STORE_OPTIMISTIC_OVERRIDE = "x", STORE_NODE = "n", STORE_HAS = "h", STORE_CUSTOM_PROTO = "c", STORE_WRAP = "w", STORE_LOOKUP = "l", STORE_FIREWALL = "f", STORE_OPTIMISTIC = "p";
31
+ const STORE_VALUE = "v", STORE_OVERRIDE = "o", STORE_OPTIMISTIC_OVERRIDE = "x", STORE_NODE = "n", STORE_HAS = "h", STORE_CUSTOM_PROTO = "c", STORE_WRAP = "w", STORE_LOOKUP = "l", STORE_FIREWALL = "f", STORE_OPTIMISTIC = "p", STORE_OPTIMISTIC_OWNERS = "t", STORE_PARENT = "u", STORE_DESC = "d";
30
32
 
31
33
  const STORE_SELF_PENDING = Symbol(0);
32
34
 
@@ -56,9 +58,17 @@ const storeLookup = new WeakMap;
56
58
  const symbolKeyedRecords = new WeakSet;
57
59
 
58
60
  function wrap(e, t) {
59
- if (t?.[STORE_WRAP]) return t[STORE_WRAP](e, t);
61
+ if (t?.[STORE_WRAP]) {
62
+ const r = t[STORE_WRAP](e, t);
63
+ const n = r[$TARGET];
64
+ if (n && !n[STORE_PARENT] && n !== t) n[STORE_PARENT] = t;
65
+ return r;
66
+ }
60
67
  let r = e[$PROXY] || storeLookup.get(e);
61
- if (!r) storeLookup.set(e, r = createStoreProxy(e));
68
+ if (!r) {
69
+ storeLookup.set(e, r = createStoreProxy(e));
70
+ if (t) r[$TARGET][STORE_PARENT] = t;
71
+ }
62
72
  return r;
63
73
  }
64
74
 
@@ -91,7 +101,7 @@ function unwrapStoreValue(e, t, r) {
91
101
  const E = s ? [] : Object.create(Object.getPrototypeOf(i));
92
102
  t.set(e, E);
93
103
  r = n[STORE_LOOKUP] ?? storeLookup;
94
- for (const e of getKeys(i, o)) {
104
+ for (const e of getStoreKeys(i, o)) {
95
105
  if (s && e === "length") continue;
96
106
  const n = e in o ? o[e] : i[e];
97
107
  if (n !== $DELETED) E[e] = unwrapStoreValue(n, t, r);
@@ -109,6 +119,23 @@ function ownEnumerableKeys(e) {
109
119
  return Reflect.ownKeys(e).filter(t => Object.prototype.propertyIsEnumerable.call(e, t));
110
120
  }
111
121
 
122
+ function ownEnumerableSymbols(e) {
123
+ const t = Object.getOwnPropertySymbols(e);
124
+ const r = [];
125
+ for (let n = 0, o = t.length; n < o; n++) {
126
+ const o = t[n];
127
+ if (Object.prototype.propertyIsEnumerable.call(e, o)) r.push(o);
128
+ }
129
+ return r;
130
+ }
131
+
132
+ // Plain-object variant that keeps Object.keys() as the fast path and only pays
133
+ // descriptor checks for symbols. Do not use this on store proxies: splitting
134
+ // strings/symbols would invoke their ownKeys trap twice.
135
+ function ownEnumerableKeysPlain(e) {
136
+ return Object.keys(e).concat(ownEnumerableSymbols(e));
137
+ }
138
+
112
139
  /**
113
140
  * Single chokepoint for the store's layered value resolution: returns the
114
141
  * override layer (optimistic first, then regular) that shadows `property`, or
@@ -127,7 +154,7 @@ function ownEnumerableKeys(e) {
127
154
  * The value a store leaf's backing signal currently shows to readers: active
128
155
  * override, else held pending value, else committed value.
129
156
  */ function visibleNodeValue(e) {
130
- return e.be !== undefined && e.be !== NOT_PENDING ? e.be : e.ge !== NOT_PENDING ? e.ge : e.Ue;
157
+ return e.be !== undefined && e.be !== NOT_PENDING ? unwrapOverride(e.be) : e.ge !== NOT_PENDING ? e.ge : e.Ue;
131
158
  }
132
159
 
133
160
  function hasOwnStoreProperty(e, t) {
@@ -186,24 +213,34 @@ function getNode(e, t, r, n, o = isEqual, i) {
186
213
  snapshotSources?.add(s);
187
214
  }
188
215
  if (typeof r === "symbol" && r !== $TRACK && r !== $AFFECTS) symbolKeyedRecords.add(t);
189
- // A node born inside a live keyless mark's identity scope inherits the mark
216
+ // A node born inside a live mark's identity scope inherits the mark
190
217
  // (the declaration walk could only cover nodes that existed then). The
191
218
  // record's own $AFFECTS carrier is the mark's channel, never a member.
192
- if (r !== $AFFECTS && affectsScopes.size) inheritAffectsMarks(s, e[STORE_VALUE]);
219
+ if (r !== $AFFECTS && affectsScopes.size) inheritAffectsMarks(s, e[STORE_VALUE], r);
220
+ // Node presence bubbles up the wrap chain (sticky), so reconcile can see
221
+ // "subscribers live somewhere below" through node-less intermediate
222
+ // records — the captured-proxy diff gate (#2902). Amortized O(1): stops at
223
+ // the first already-flagged ancestor.
224
+ let E = e;
225
+ while (E && !E[STORE_DESC]) {
226
+ E[STORE_DESC] = true;
227
+ E = E[STORE_PARENT];
228
+ }
193
229
  return t[r] = s;
194
230
  }
195
231
 
196
232
  /**
197
- * Scope inheritance for late-created nodes: every live keyless mark whose
198
- * identity scope contains the owning record's raw gets counted on the new
199
- * node. Inherited marks live exactly as long as the scope's carrier the
200
- * release hook below drops them with the entry.
201
- */ function inheritAffectsMarks(e, t) {
233
+ * Scope inheritance for late-created nodes: every live mark whose identity
234
+ * scope contains the owning record's raw and, for keyed marks, whose key
235
+ * is this property gets counted on the new node. Inherited marks live
236
+ * exactly as long as the scope's carrier — the release hook below drops
237
+ * them with the entry.
238
+ */ function inheritAffectsMarks(e, t, r) {
202
239
  // A live scope exists, so affects.ts already installed the mark engine.
203
- for (const [r, n] of affectsScopes) {
204
- if (r.P && n.scope.has(t)) {
240
+ for (const [n, o] of affectsScopes) {
241
+ if (n.M && o.scope.has(t) && (o.key === undefined || o.key === r)) {
205
242
  GlobalQueue.D(e);
206
- n.inherited.push(e);
243
+ o.inherited.push(e);
207
244
  }
208
245
  }
209
246
  }
@@ -235,7 +272,10 @@ o) {
235
272
  collectRecordNodes(i[STORE_NODE], r);
236
273
  collectRecordNodes(i[STORE_HAS], r);
237
274
  E = mergedOverlay(i);
238
- n = i[STORE_LOOKUP] ?? n;
275
+ // Carry the effective lookup into untouched descendants. Default stores
276
+ // use the global lookup just like snapshotImpl; without it, nested raw
277
+ // objects fall back to string-only enumeration and symbol branches vanish.
278
+ n = i[STORE_LOOKUP] ?? n ?? storeLookup;
239
279
  }
240
280
  if (Array.isArray(s)) {
241
281
  const e = E?.length ?? s.length;
@@ -243,8 +283,18 @@ o) {
243
283
  const e = E && i in E ? E[i] : s[i];
244
284
  if (e !== $DELETED) walkAffectsScope(e, t, r, n, o);
245
285
  }
286
+ // Arrays can also carry symbol metadata. Enumerate symbols separately to
287
+ // avoid scanning large index lists twice. Outside a store tree, retain the
288
+ // existing index-only walk.
289
+ const O = i || n ? getStoreSymbols(s, E) : [];
290
+ for (let e = 0, i = O.length; e < i; e++) {
291
+ const i = O[e];
292
+ const c = getPropertyDescriptor(s, E, i);
293
+ if (!c || c.get) continue;
294
+ walkAffectsScope(c.value, t, r, n, o);
295
+ }
246
296
  } else {
247
- const e = getKeys(s, E);
297
+ const e = i || n ? getStoreKeys(s, E) : getKeys(s, E);
248
298
  for (let i = 0, O = e.length; i < O; i++) {
249
299
  const O = getPropertyDescriptor(s, E, e[i]);
250
300
  if (!O || O.get) continue;
@@ -273,15 +323,15 @@ o) {
273
323
  * Callers guard on `pendingCheckActive`, so plain reads never pay for this.
274
324
  *
275
325
  * @internal
276
- */ function witnessAffectsMark(e) {
326
+ */ function witnessAffectsMark(e, t) {
277
327
  // Callers guard on `pendingCheckActive`, which only flips inside
278
328
  // isPending() — the verdict layer is loaded and its hook installed.
279
- const t = e[STORE_NODE]?.[$AFFECTS];
280
- if (t?.P) GlobalQueue.Zt(t);
329
+ const r = e[STORE_NODE]?.[$AFFECTS];
330
+ if (r?.M) GlobalQueue.wt(r);
281
331
  if (affectsScopes.size) {
282
- const r = e[STORE_VALUE];
283
- for (const [e, n] of affectsScopes) {
284
- if (e !== t && e.P && n.scope.has(r)) GlobalQueue.Zt(e);
332
+ const n = e[STORE_VALUE];
333
+ for (const [e, o] of affectsScopes) {
334
+ if (e !== r && e.M && o.scope.has(n) && (o.key === undefined || o.key === t)) GlobalQueue.wt(e);
285
335
  }
286
336
  }
287
337
  }
@@ -298,14 +348,14 @@ o) {
298
348
  * @internal
299
349
  */ function getStoreAffectsNodes(e, t) {
300
350
  const r = getNodes(e, STORE_NODE);
351
+ GlobalQueue.T ||= e => {
352
+ const t = affectsScopes.get(e);
353
+ if (!t) return;
354
+ affectsScopes.delete(e);
355
+ for (let e = 0; e < t.inherited.length; e++) GlobalQueue.F(t.inherited[e]);
356
+ };
301
357
  if (t === undefined) {
302
358
  const t = getNode(e, r, $AFFECTS, undefined, false);
303
- GlobalQueue.T ||= e => {
304
- const t = affectsScopes.get(e);
305
- if (!t) return;
306
- affectsScopes.delete(e);
307
- for (let e = 0; e < t.inherited.length; e++) GlobalQueue.F(t.inherited[e]);
308
- };
309
359
  let n = affectsScopes.get(t);
310
360
  if (!n) affectsScopes.set(t, n = {
311
361
  scope: new Set,
@@ -315,11 +365,25 @@ o) {
315
365
  walkAffectsScope(e[$PROXY], n, o, e[STORE_LOOKUP], new Set);
316
366
  return o;
317
367
  }
318
- if (r[t]) return [ r[t] ];
319
- const n = getOverlayLayer(e, t);
320
- const o = n ? n[t] : e[STORE_VALUE][t];
321
- const i = o === $DELETED ? undefined : o;
322
- return [ upsertStoreNode(e, r, t, i, e[STORE_SNAPSHOT_PROPS]) ];
368
+ let n = r[t];
369
+ if (!n) {
370
+ const o = getOverlayLayer(e, t);
371
+ const i = o ? o[t] : e[STORE_VALUE][t];
372
+ n = upsertStoreNode(e, r, t, i === $DELETED ? undefined : i, e[STORE_SNAPSHOT_PROPS]);
373
+ }
374
+ // Keyed marks resolve by identity too (#2904): another store family's
375
+ // proxy can share this record's raw (a derived store swaps its backing to
376
+ // the source's raw when its projection lands), and reads through it never
377
+ // touch this target's node map. Scope is exactly the owning record's raw,
378
+ // narrowed to this key for witness and birth inheritance.
379
+ let o = affectsScopes.get(n);
380
+ if (!o) affectsScopes.set(n, o = {
381
+ scope: new Set,
382
+ inherited: [],
383
+ key: t
384
+ });
385
+ o.scope.add(e[STORE_VALUE]);
386
+ return [ n ];
323
387
  }
324
388
 
325
389
  function trackSelf(e, t = $TRACK) {
@@ -354,17 +418,36 @@ function notifySelf(e) {
354
418
  } : r ?? t;
355
419
  }
356
420
 
357
- function getKeys(e, t, r = true) {
421
+ function getKeysImpl(e, t, r, n) {
358
422
  // Plain objects can't trigger proxy traps — only pay for the untrack
359
423
  // closure when the source is itself a wrapped store (store-in-store).
360
- const n = e[$TARGET] ? untrack(() => r ? Object.keys(e) : Reflect.ownKeys(e)) : r ? Object.keys(e) : Reflect.ownKeys(e);
361
- if (!t) return n;
362
- const o = new Set(n);
363
- const i = Reflect.ownKeys(t);
364
- for (const e of i) {
365
- if (t[e] !== $DELETED) o.add(e); else o.delete(e);
424
+ const o = e[$TARGET] ? untrack(() => r ? n ? ownEnumerableKeys(e) : Object.keys(e) : Reflect.ownKeys(e)) : r ? n ? ownEnumerableKeysPlain(e) : Object.keys(e) : Reflect.ownKeys(e);
425
+ return t ? mergeOverrideKeys(o, t) : o;
426
+ }
427
+
428
+ function getKeys(e, t, r = true) {
429
+ return getKeysImpl(e, t, r, false);
430
+ }
431
+
432
+ function getStoreKeys(e, t) {
433
+ return getKeysImpl(e, t, true, true);
434
+ }
435
+
436
+ function getStoreSymbols(e, t) {
437
+ const r = e[$TARGET] ? untrack(() => ownEnumerableSymbols(e)) : ownEnumerableSymbols(e);
438
+ return t ? mergeOverrideKeys(r, t, true) : r;
439
+ }
440
+
441
+ // Shared override-layer merge for key enumeration: adds live override keys,
442
+ // drops $DELETED ones. `symbolsOnly` scopes the override scan for the
443
+ // array-metadata passes.
444
+ function mergeOverrideKeys(e, t, r) {
445
+ const n = new Set(e);
446
+ const o = r ? Object.getOwnPropertySymbols(t) : Reflect.ownKeys(t);
447
+ for (const e of o) {
448
+ if (t[e] !== $DELETED) n.add(e); else n.delete(e);
366
449
  }
367
- return Array.from(o);
450
+ return Array.from(n);
368
451
  }
369
452
 
370
453
  function getPropertyDescriptor(e, t, r) {
@@ -424,10 +507,22 @@ function prepareStoreWrite(e, t, r) {
424
507
  // STORE_OPTIMISTIC is only set by createOptimisticStore, which installs the
425
508
  // optimistic engine before wrapping.
426
509
  if (e[STORE_OPTIMISTIC] && !projectionWriteActive) {
427
- GlobalQueue.Vt(t);
510
+ GlobalQueue.mn(t);
428
511
  }
429
512
  }
430
513
 
514
+ /**
515
+ * Records which transition owns an optimistic layer entry (#2899), so a
516
+ * settling action only consumes its own keys — the layer is store-wide, but
517
+ * concurrent actions writing disjoint keys must revert independently, exactly
518
+ * like optimistic signal nodes do via the transition's _optimisticNodes.
519
+ * `activeTransition` is the write's transaction (action() opens it before the
520
+ * body runs); null marks an ambient write that clears at plain flush end.
521
+ * Same-key writes across actions keep last-write-wins layer semantics.
522
+ */ function stampOptimisticOwner(e, t, r) {
523
+ if (t === STORE_OPTIMISTIC_OVERRIDE) (e[STORE_OPTIMISTIC_OWNERS] ??= Object.create(null))[r] = activeTransition;
524
+ }
525
+
431
526
  function upsertStoreNode(e, t, r, n, o) {
432
527
  if (t[r]) return t[r];
433
528
  const i = isWrappable(n) ? wrap(n, e) : n;
@@ -475,12 +570,25 @@ function notifyStoreProperty(e, t, r, n, o, i) {
475
570
 
476
571
  let Writing = null;
477
572
 
573
+ /**
574
+ * A derived store's seed is a draft for the derive function, never an
575
+ * observable value (#2897): until the firewall first resolves there is
576
+ * nothing to read, so every consumer path throws NotReady — tracked reads
577
+ * through their node (core read()), and the untracked fall-throughs in the
578
+ * traps through this guard. Returning the seed leaked it; returning
579
+ * `undefined` would break non-nullable types. Callers exempt the firewall
580
+ * itself (the derive function works its own draft while uninitialized).
581
+ */ function throwIfUninitialized(e) {
582
+ const t = e[STORE_FIREWALL];
583
+ if (t && t.i & STATUS_UNINITIALIZED) throw t.k ?? new NotReadyError(t);
584
+ }
585
+
478
586
  const storeTraps = {
479
587
  get(e, t, r) {
480
588
  if (t === $TARGET) return e;
481
589
  if (t === $PROXY) return r;
482
590
  if (t === $REFRESH) return e[STORE_FIREWALL];
483
- if (pendingCheckActive) witnessAffectsMark(e);
591
+ if (pendingCheckActive) witnessAffectsMark(e, t);
484
592
  if (t === $TRACK) {
485
593
  trackSelf(e);
486
594
  return r;
@@ -495,41 +603,44 @@ const storeTraps = {
495
603
  const E = getOverlayLayer(e, t);
496
604
  const O = !!E;
497
605
  const c = !!e[STORE_VALUE][$TARGET];
498
- const f = E ?? e[STORE_VALUE];
606
+ const S = E ?? e[STORE_VALUE];
499
607
  if (!i) {
500
- const n = Object.getOwnPropertyDescriptor(f, t);
608
+ const n = Object.getOwnPropertyDescriptor(S, t);
501
609
  if (n && n.get) return n.get.call(r);
502
610
  if (!n && !O && e[STORE_CUSTOM_PROTO]) {
503
- const e = unwrapStoreValue(f);
611
+ const e = unwrapStoreValue(S);
504
612
  if (hasInheritedAccessor(e, t)) {
505
- return Reflect.get(f, t, r);
613
+ return Reflect.get(S, t, r);
506
614
  }
507
615
  }
508
616
  }
509
617
  if (writeOnly(r)) {
510
618
  if (isPrototypePollutionKey(t) && !hasOwnStoreProperty(e, t)) return undefined;
511
- let r = i && (O || !c) ? visibleNodeValue(i) : f[t];
619
+ let r = i && (O || !c) ? visibleNodeValue(i) : S[t];
512
620
  r === $DELETED && (r = undefined);
513
621
  if (!isWrappable(r)) return r;
514
622
  const n = wrap(r, e);
515
623
  Writing?.add(n);
516
624
  return n;
517
625
  }
518
- let S = i ? O || !c ? read(o[t]) : (read(o[t]), f[t]) : f[t];
519
- S === $DELETED && (S = undefined);
626
+ let f = i ? O || !c ? read(o[t]) : (read(o[t]), S[t]) : S[t];
627
+ f === $DELETED && (f = undefined);
520
628
  if (!i) {
521
- if (!O && typeof S === "function" && !Object.prototype.hasOwnProperty.call(f, t)) {
629
+ if (!O && typeof f === "function" && !Object.prototype.hasOwnProperty.call(S, t)) {
522
630
  let t;
523
- return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ? S.bind(f) : S;
631
+ return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ? f.bind(S) : f;
524
632
  } else if (getObserver() && !n) {
525
- return read(getNode(e, o, t, isWrappable(S) ? wrap(S, e) : S, isEqual, e[STORE_SNAPSHOT_PROPS]));
633
+ return read(getNode(e, o, t, isWrappable(f) ? wrap(f, e) : f, isEqual, e[STORE_SNAPSHOT_PROPS]));
526
634
  }
527
635
  }
528
- return isWrappable(S) ? wrap(S, e) : S;
636
+ // Untracked fall-through (tracked reads already threw via their node in
637
+ // read(); the dev strictRead error above wins first for memo parity).
638
+ if (!n) throwIfUninitialized(e);
639
+ return isWrappable(f) ? wrap(f, e) : f;
529
640
  },
530
641
  has(e, t) {
531
642
  if (t === $PROXY || t === $TRACK || t === "__proto__") return true;
532
- if (pendingCheckActive) witnessAffectsMark(e);
643
+ if (pendingCheckActive) witnessAffectsMark(e, t);
533
644
  const r = getOverlayLayer(e, t);
534
645
  const n = r ? r[t] !== $DELETED : t in e[STORE_VALUE];
535
646
  if (writeOnly(e[$PROXY]) || getObserver() === e[STORE_FIREWALL]) return n;
@@ -544,6 +655,7 @@ const storeTraps = {
544
655
  if (getObserver()) {
545
656
  return read(getNode(e, o, t, n));
546
657
  }
658
+ throwIfUninitialized(e);
547
659
  return n;
548
660
  },
549
661
  set(e, t, r) {
@@ -555,33 +667,41 @@ const storeTraps = {
555
667
  const E = getOverlayLayer(e, t);
556
668
  const O = E ? E[t] : o;
557
669
  const c = E ? E[t] !== $DELETED : t in e[STORE_VALUE];
558
- const f = unwrapStoreValue(r);
670
+ const S = unwrapStoreValue(r);
559
671
  // Symbol-keyed writes on arrays are metadata, not index writes — never run
560
672
  // them through the numeric index/length machinery (`parseInt` on a symbol
561
673
  // throws). #2769
562
- const S = typeof t === "string" ? Number(t) : -1;
563
- const R = Array.isArray(s) && Number.isInteger(S) && S >= 0 && S < 4294967295 && String(S) === t;
564
- const T = R ? S + 1 : 0;
565
- const u = R && (getOverlayLayer(e, "length") ?? s).length;
566
- const l = R && T > u ? T : undefined;
567
- if (O === f && l === undefined) return true;
674
+ const f = typeof t === "string" ? Number(t) : -1;
675
+ const T = Array.isArray(s) && Number.isInteger(f) && f >= 0 && f < 4294967295 && String(f) === t;
676
+ const R = T ? f + 1 : 0;
677
+ const u = T && (getOverlayLayer(e, "length") ?? s).length;
678
+ const l = T && R > u ? R : undefined;
679
+ if (O === S && l === undefined) return true;
568
680
  armOptimisticStoreWrite(e, n);
569
- if (f !== undefined && f === o && l === undefined) delete e[i]?.[t]; else {
681
+ if (S !== undefined && S === o && l === undefined) {
682
+ delete e[i]?.[t];
683
+ if (i === STORE_OPTIMISTIC_OVERRIDE) delete e[STORE_OPTIMISTIC_OWNERS]?.[t];
684
+ } else {
570
685
  const r = e[i] || (e[i] = Object.create(null));
571
- r[t] = f;
572
- if (l !== undefined) r.length = l;
686
+ r[t] = S;
687
+ stampOptimisticOwner(e, i, t);
688
+ if (l !== undefined) {
689
+ r.length = l;
690
+ stampOptimisticOwner(e, i, "length");
691
+ }
573
692
  }
574
- notifyStoreProperty(e, t, "set", f, O, c);
693
+ notifyStoreProperty(e, t, "set", S, O, c);
575
694
  // Shrinking an array's length must remove the truncated indices, otherwise
576
695
  // they leak through `has`, `ownKeys`, and (tracked) index reads from the
577
696
  // underlying value. Mark each as deleted and notify so reactive reads update. #2768
578
- if (Array.isArray(s) && t === "length" && typeof f === "number" && typeof O === "number" && f < O) {
697
+ if (Array.isArray(s) && t === "length" && typeof S === "number" && typeof O === "number" && S < O) {
579
698
  const t = e[i] || (e[i] = Object.create(null));
580
- for (let r = f; r < O; r++) {
699
+ for (let r = S; r < O; r++) {
581
700
  if (t[r] === $DELETED) continue;
582
701
  const n = r in t ? t[r] : s[r];
583
702
  if (!(r in t) && !(r in s)) continue;
584
703
  t[r] = $DELETED;
704
+ stampOptimisticOwner(e, i, r);
585
705
  notifyStoreProperty(e, r, "delete", undefined, n, true);
586
706
  }
587
707
  }
@@ -612,6 +732,7 @@ const storeTraps = {
612
732
  value: unwrapStoreValue(r.value)
613
733
  } : r;
614
734
  Object.defineProperty(e[i] || (e[i] = Object.create(null)), t, s);
735
+ stampOptimisticOwner(e, i, t);
615
736
  notifyStoreProperty(e, t, "invalidate");
616
737
  if (false) ;
617
738
  });
@@ -632,9 +753,11 @@ const storeTraps = {
632
753
  if (t in e[STORE_VALUE] || e[STORE_OVERRIDE] && t in e[STORE_OVERRIDE]) {
633
754
  armOptimisticStoreWrite(e, e[$PROXY]);
634
755
  (e[n] || (e[n] = Object.create(null)))[t] = $DELETED;
756
+ stampOptimisticOwner(e, n, t);
635
757
  } else if (e[n] && t in e[n]) {
636
758
  armOptimisticStoreWrite(e, e[$PROXY]);
637
759
  delete e[n][t];
760
+ if (n === STORE_OPTIMISTIC_OVERRIDE) delete e[STORE_OPTIMISTIC_OWNERS]?.[t];
638
761
  } else return true;
639
762
  notifyStoreProperty(e, t, "delete", undefined, i, true);
640
763
  });
@@ -643,7 +766,15 @@ const storeTraps = {
643
766
  },
644
767
  ownKeys(e) {
645
768
  if (pendingCheckActive) witnessAffectsMark(e);
646
- if (getObserver() !== e[STORE_FIREWALL]) trackSelf(e);
769
+ if (getObserver() !== e[STORE_FIREWALL]) {
770
+ trackSelf(e);
771
+ // trackSelf no-ops untracked, so enumeration of an unresolved derived
772
+ // store would otherwise leak the seed's structure (#2897). The write
773
+ // path is exempt (like the get/has traps' writeOnly early returns):
774
+ // the first landing's reconcile enumerates the store while
775
+ // STATUS_UNINITIALIZED is still set — it IS the initialization.
776
+ if (!getObserver() && !writeOnly(e[$PROXY])) throwIfUninitialized(e);
777
+ }
647
778
  // Merge optimistic override with regular override for key enumeration
648
779
  let t = getKeys(e[STORE_VALUE], e[STORE_OVERRIDE], false);
649
780
  if (e[STORE_OPTIMISTIC_OVERRIDE]) {
@@ -736,4 +867,4 @@ function createStore(e, t, r) {
736
867
  } : e => storeSetter(o, e) ];
737
868
  }
738
869
 
739
- export { $AFFECTS, $DELETED, $PROXY, $TARGET, $TRACK, STORE_CUSTOM_PROTO, STORE_FIREWALL, STORE_HAS, STORE_LOOKUP, STORE_NODE, STORE_OPTIMISTIC, STORE_OPTIMISTIC_OVERRIDE, STORE_OVERRIDE, STORE_VALUE, STORE_WRAP, createStore, createStoreProxy, getKeys, getOverlayLayer, getPropertyDescriptor, getStoreAffectsNodes, isWrappable, mergedOverlay, notifySelf, ownEnumerableKeys, setWriteOverride, storeLookup, storeSetter, storeTraps, symbolKeyedRecords, trackSelf, visibleNodeValue, witnessAffectsMark, wrap };
870
+ export { $AFFECTS, $DELETED, $PROXY, $TARGET, $TRACK, STORE_CUSTOM_PROTO, STORE_DESC, STORE_FIREWALL, STORE_HAS, STORE_LOOKUP, STORE_NODE, STORE_OPTIMISTIC, STORE_OPTIMISTIC_OVERRIDE, STORE_OPTIMISTIC_OWNERS, STORE_OVERRIDE, STORE_PARENT, STORE_VALUE, STORE_WRAP, createStore, createStoreProxy, getKeys, getOverlayLayer, getPropertyDescriptor, getStoreAffectsNodes, getStoreKeys, getStoreSymbols, isWrappable, mergedOverlay, notifySelf, ownEnumerableKeys, setWriteOverride, storeLookup, storeSetter, storeTraps, symbolKeyedRecords, trackSelf, visibleNodeValue, witnessAffectsMark, wrap };
@@ -12,10 +12,10 @@ import "../core/effect.js";
12
12
 
13
13
  import { createMemo } from "../signals.js";
14
14
 
15
- import { ownEnumerableKeys, $PROXY, isWrappable, $TARGET, trackSelf, witnessAffectsMark, mergedOverlay, STORE_VALUE, storeLookup, STORE_LOOKUP, $DELETED, wrap, getKeys, getPropertyDescriptor, $TRACK } from "./store.js";
15
+ import { ownEnumerableKeys, $PROXY, isWrappable, $TARGET, trackSelf, witnessAffectsMark, mergedOverlay, STORE_VALUE, storeLookup, STORE_LOOKUP, $DELETED, wrap, getStoreSymbols, getPropertyDescriptor, getStoreKeys, getKeys, $TRACK } from "./store.js";
16
16
 
17
17
  function snapshotImpl(e, t, r, n) {
18
- let o, s, c, i, f, u;
18
+ let o, s, i, c, f, u;
19
19
  if (!isWrappable(e)) return e;
20
20
  if (r && r.has(e)) return r.get(e);
21
21
  if (!r) r = new Map;
@@ -26,9 +26,9 @@ function snapshotImpl(e, t, r, n) {
26
26
  // traps — witness the record's affects() channel like a trap read would.
27
27
  if (pendingCheckActive) witnessAffectsMark(o);
28
28
  }
29
- c = mergedOverlay(o);
29
+ i = mergedOverlay(o);
30
30
  s = Array.isArray(o[STORE_VALUE]);
31
- r.set(e, c ? i = s ? [] : Object.create(Object.getPrototypeOf(o[STORE_VALUE])) : o[STORE_VALUE]);
31
+ r.set(e, i ? c = s ? [] : Object.create(Object.getPrototypeOf(o[STORE_VALUE])) : o[STORE_VALUE]);
32
32
  e = o[STORE_VALUE];
33
33
  n = o[STORE_LOOKUP] ?? storeLookup;
34
34
  } else {
@@ -36,56 +36,75 @@ function snapshotImpl(e, t, r, n) {
36
36
  r.set(e, e);
37
37
  }
38
38
  if (s) {
39
- const s = c?.length ?? e.length;
39
+ const s = i?.length ?? e.length;
40
40
  for (let p = 0; p < s; p++) {
41
- u = c && p in c ? c[p] : e[p];
41
+ u = i && p in i ? i[p] : e[p];
42
42
  if (u === $DELETED) continue;
43
43
  if (t && isWrappable(u)) wrap(u, o);
44
- if ((f = snapshotImpl(u, t, r, n)) !== u || i) {
45
- if (!i) r.set(e, i = [ ...e ]);
46
- i[p] = f;
44
+ if ((f = snapshotImpl(u, t, r, n)) !== u || c) {
45
+ if (!c) r.set(e, c = [ ...e ]);
46
+ c[p] = f;
47
+ }
48
+ }
49
+ // Enumerate array symbols separately to avoid scanning indices twice.
50
+ // Spread copies omit symbols, so assign them after the numeric walk.
51
+ const p = n ? getStoreSymbols(e, i) : [];
52
+ for (let s = 0, l = p.length; s < l; s++) {
53
+ const l = p[s];
54
+ const a = getPropertyDescriptor(e, i, l);
55
+ if (!a || a.get) continue;
56
+ u = i && l in i ? i[l] : e[l];
57
+ if (t && isWrappable(u)) wrap(u, o);
58
+ f = snapshotImpl(u, t, r, n);
59
+ if (f !== u || c) {
60
+ if (!c) r.set(e, c = Object.assign([ ...e ], e));
61
+ c[l] = f;
47
62
  }
48
63
  }
49
64
  // Deleted trailing slots are skipped above, so restore length to preserve
50
65
  // holes instead of truncating the copy (#2846) — mirrors unwrapStoreValue.
51
- if (i) i.length = s;
52
- } else if (!c) {
66
+ if (c) c.length = s;
67
+ } else if (!i) {
53
68
  // Specialized walk for the common no-overlay case (from #2756): the own
54
69
  // descriptor gives the value directly, so each property is read once with
55
70
  // no overlay membership checks.
56
- const s = getKeys(e, undefined);
57
- for (let c = 0, p = s.length; c < p; c++) {
58
- const p = s[c];
71
+ // A lookup means this object belongs to an immutable store backing tree,
72
+ // even if that nested value has not needed its own proxy yet.
73
+ const s = n ? getStoreKeys(e, undefined) : getKeys(e, undefined);
74
+ for (let i = 0, p = s.length; i < p; i++) {
75
+ const p = s[i];
59
76
  const l = Object.getOwnPropertyDescriptor(e, p);
60
77
  if (l.get) continue;
61
78
  u = l.value;
62
79
  if (t && isWrappable(u)) wrap(u, o);
63
- if ((f = snapshotImpl(u, t, r, n)) !== u || i) {
64
- if (!i) {
65
- i = Object.create(Object.getPrototypeOf(e));
66
- Object.assign(i, e);
80
+ if ((f = snapshotImpl(u, t, r, n)) !== u || c) {
81
+ if (!c) {
82
+ c = Object.create(Object.getPrototypeOf(e));
83
+ Object.assign(c, e);
67
84
  }
68
- i[p] = f;
85
+ c[p] = f;
69
86
  }
70
87
  }
71
88
  } else {
72
- const s = getKeys(e, c);
89
+ // An override only exists on a store record, and the target branch above
90
+ // always set `lookup` alongside it — so this branch is always store-keyed.
91
+ const s = getStoreKeys(e, i);
73
92
  for (let p = 0, l = s.length; p < l; p++) {
74
93
  let l = s[p];
75
- const a = getPropertyDescriptor(e, c, l);
94
+ const a = getPropertyDescriptor(e, i, l);
76
95
  if (a.get) continue;
77
- u = l in c ? c[l] : e[l];
96
+ u = l in i ? i[l] : e[l];
78
97
  if (t && isWrappable(u)) wrap(u, o);
79
- if ((f = snapshotImpl(u, t, r, n)) !== e[l] || i) {
80
- if (!i) {
81
- i = Object.create(Object.getPrototypeOf(e));
82
- Object.assign(i, e);
98
+ if ((f = snapshotImpl(u, t, r, n)) !== e[l] || c) {
99
+ if (!c) {
100
+ c = Object.create(Object.getPrototypeOf(e));
101
+ Object.assign(c, e);
83
102
  }
84
- i[l] = f;
103
+ c[l] = f;
85
104
  }
86
105
  }
87
106
  }
88
- return i || e;
107
+ return c || e;
89
108
  }
90
109
 
91
110
  function snapshot(e, t, r) {
@@ -218,14 +237,14 @@ const $SOURCES = Symbol(0);
218
237
  e === s && s--;
219
238
  continue;
220
239
  }
221
- const c = Object.getOwnPropertyNames(t);
222
- for (let r = c.length - 1; r >= 0; r--) {
223
- const i = c[r];
224
- if (i === "__proto__" || i === "constructor") continue;
225
- if (!n[i]) {
240
+ const i = Object.getOwnPropertyNames(t);
241
+ for (let r = i.length - 1; r >= 0; r--) {
242
+ const c = i[r];
243
+ if (c === "__proto__" || c === "constructor") continue;
244
+ if (!n[c]) {
226
245
  o = o || e !== s;
227
- const r = Object.getOwnPropertyDescriptor(t, i);
228
- n[i] = r.get ? {
246
+ const r = Object.getOwnPropertyDescriptor(t, c);
247
+ n[c] = r.get ? {
229
248
  enumerable: true,
230
249
  configurable: true,
231
250
  get: r.get.bind(t)
@@ -234,14 +253,14 @@ const $SOURCES = Symbol(0);
234
253
  }
235
254
  }
236
255
  if (!o) return r[s];
237
- const c = {};
238
- const i = Object.keys(n);
239
- for (let e = i.length - 1; e >= 0; e--) {
240
- const t = i[e], r = n[t];
241
- if (r.get) Object.defineProperty(c, t, r); else c[t] = r.value;
256
+ const i = {};
257
+ const c = Object.keys(n);
258
+ for (let e = c.length - 1; e >= 0; e--) {
259
+ const t = c[e], r = n[t];
260
+ if (r.get) Object.defineProperty(i, t, r); else i[t] = r.value;
242
261
  }
243
- c[$SOURCES] = r;
244
- return c;
262
+ i[$SOURCES] = r;
263
+ return i;
245
264
  }
246
265
 
247
266
  /**