@solidjs/signals 2.0.0-rc.7 → 2.0.0-rc.8

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 (117) hide show
  1. package/dist/dev-shared.js +5243 -0
  2. package/dist/dev.attribution.js +2333 -0
  3. package/dist/dev.js +1229 -6834
  4. package/dist/observe/affects.js +127 -0
  5. package/dist/observe/attribution.js +1 -0
  6. package/dist/observe/boundaries.js +586 -0
  7. package/dist/observe/core/action.js +167 -0
  8. package/dist/observe/core/async.js +736 -0
  9. package/dist/observe/core/attribution-hooks.js +60 -0
  10. package/dist/observe/core/attribution.js +2318 -0
  11. package/dist/{types-cjs/core/constants.d.cts → observe/core/constants.js} +129 -73
  12. package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
  13. package/dist/observe/core/core.js +1289 -0
  14. package/dist/observe/core/dev.js +232 -0
  15. package/dist/observe/core/effect.js +179 -0
  16. package/dist/observe/core/error.js +80 -0
  17. package/dist/observe/core/external.js +98 -0
  18. package/dist/observe/core/graph.js +155 -0
  19. package/dist/observe/core/heap.js +147 -0
  20. package/dist/observe/core/invariants.js +43 -0
  21. package/dist/observe/core/lanes.js +168 -0
  22. package/dist/observe/core/optimistic.js +438 -0
  23. package/dist/observe/core/owner.js +308 -0
  24. package/dist/observe/core/scheduler.js +1190 -0
  25. package/dist/observe/core/verdict.js +551 -0
  26. package/dist/observe/index.js +55 -0
  27. package/dist/observe/map.js +482 -0
  28. package/dist/{types-cjs/signals.d.cts → observe/signals.js} +295 -317
  29. package/dist/observe/store/index.js +38 -0
  30. package/dist/observe/store/next/optimistic.js +651 -0
  31. package/dist/observe/store/next/projection.js +242 -0
  32. package/dist/observe/store/next/reconcile.js +373 -0
  33. package/dist/observe/store/next/store.js +2213 -0
  34. package/dist/observe/store/next/target.js +51 -0
  35. package/dist/observe/store/store.js +323 -0
  36. package/dist/observe/store/storePath.js +103 -0
  37. package/dist/observe/store/utils.js +210 -0
  38. package/dist/prod/attribution.js +32 -0
  39. package/dist/prod/core/action.js +52 -32
  40. package/dist/prod/core/async.js +171 -127
  41. package/dist/prod/core/constants.js +24 -1
  42. package/dist/prod/core/context.js +3 -3
  43. package/dist/prod/core/core.js +434 -291
  44. package/dist/prod/core/effect.js +54 -34
  45. package/dist/prod/core/external.js +2 -2
  46. package/dist/prod/core/graph.js +32 -32
  47. package/dist/prod/core/heap.js +49 -44
  48. package/dist/prod/core/lanes.js +45 -38
  49. package/dist/prod/core/optimistic.js +207 -73
  50. package/dist/prod/core/owner.js +43 -39
  51. package/dist/prod/core/scheduler.js +319 -190
  52. package/dist/prod/core/verdict.js +67 -67
  53. package/dist/prod/index.js +9 -3
  54. package/dist/prod/map.js +292 -141
  55. package/dist/prod/signals.js +7 -10
  56. package/dist/prod/store/next/optimistic.js +140 -125
  57. package/dist/prod/store/next/projection.js +3 -3
  58. package/dist/prod/store/next/reconcile.js +87 -84
  59. package/dist/prod/store/next/store.js +524 -280
  60. package/dist/prod/store/next/target.js +32 -10
  61. package/dist/prod/store/store.js +12 -14
  62. package/dist/prod/store/utils.js +36 -27
  63. package/dist/types/attribution.d.ts +15 -0
  64. package/dist/types/attribution.prod.d.ts +13 -0
  65. package/dist/types/core/async.d.ts +1 -1
  66. package/dist/types/core/attribution-hooks.d.ts +119 -6
  67. package/dist/types/core/attribution.d.ts +208 -53
  68. package/dist/types/core/constants.d.ts +23 -0
  69. package/dist/types/core/core.d.ts +7 -0
  70. package/dist/types/core/dev.d.ts +124 -26
  71. package/dist/types/core/effect.d.ts +8 -1
  72. package/dist/types/core/index.d.ts +2 -1
  73. package/dist/types/core/lanes.d.ts +8 -1
  74. package/dist/types/core/scheduler.d.ts +60 -0
  75. package/dist/types/core/types.d.ts +29 -9
  76. package/dist/types/index.d.ts +9 -2
  77. package/dist/types/map.d.ts +2 -0
  78. package/dist/types/store/index.d.ts +1 -0
  79. package/dist/types/store/next/store.d.ts +5 -0
  80. package/dist/types/store/next/target.d.ts +36 -9
  81. package/dist/types/store/utils.d.ts +7 -0
  82. package/package.json +21 -17
  83. package/dist/node.cjs +0 -10541
  84. package/dist/node.dev.cjs +0 -13724
  85. package/dist/types-cjs/affects.d.cts +0 -47
  86. package/dist/types-cjs/boundaries.d.cts +0 -175
  87. package/dist/types-cjs/core/action.d.cts +0 -64
  88. package/dist/types-cjs/core/async.d.cts +0 -23
  89. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -127
  90. package/dist/types-cjs/core/attribution.d.cts +0 -495
  91. package/dist/types-cjs/core/core.d.cts +0 -185
  92. package/dist/types-cjs/core/dev.d.cts +0 -136
  93. package/dist/types-cjs/core/effect.d.cts +0 -30
  94. package/dist/types-cjs/core/error.d.cts +0 -56
  95. package/dist/types-cjs/core/external.d.cts +0 -15
  96. package/dist/types-cjs/core/graph.d.cts +0 -28
  97. package/dist/types-cjs/core/heap.d.cts +0 -24
  98. package/dist/types-cjs/core/index.d.cts +0 -13
  99. package/dist/types-cjs/core/invariants.d.cts +0 -59
  100. package/dist/types-cjs/core/lanes.d.cts +0 -54
  101. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  102. package/dist/types-cjs/core/owner.d.cts +0 -124
  103. package/dist/types-cjs/core/scheduler.d.cts +0 -236
  104. package/dist/types-cjs/core/types.d.cts +0 -204
  105. package/dist/types-cjs/core/verdict.d.cts +0 -2
  106. package/dist/types-cjs/index.d.cts +0 -10
  107. package/dist/types-cjs/map.d.cts +0 -69
  108. package/dist/types-cjs/package.json +0 -3
  109. package/dist/types-cjs/store/index.d.cts +0 -18
  110. package/dist/types-cjs/store/next/optimistic.d.cts +0 -25
  111. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  112. package/dist/types-cjs/store/next/reconcile.d.cts +0 -10
  113. package/dist/types-cjs/store/next/store.d.cts +0 -121
  114. package/dist/types-cjs/store/next/target.d.cts +0 -166
  115. package/dist/types-cjs/store/store.d.cts +0 -143
  116. package/dist/types-cjs/store/storePath.d.cts +0 -58
  117. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -1,14 +1,16 @@
1
- import { unwrapOverride, $REFRESH, NOT_PENDING, CONFIG_OWNED_WRITE, CONFIG_OPTIMISTIC, CONFIG_HELD_TRUTH, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
1
+ import { unwrapOverride, $REFRESH, NOT_PENDING, CONFIG_OWNED_WRITE, CONFIG_OPTIMISTIC, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_HELD_TRUTH, CONFIG_CHILDREN_FORBIDDEN, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
2
2
 
3
- import { setSlotUnobserved, setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, ext, setLatestReadActive, prepareComputed, slotSignal, context } from "../../core/core.js";
3
+ import { setSlotUnobserved, setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, unlinkFirewallChild, ext, setLatestReadActive, prepareComputed, slotSignal, context } from "../../core/core.js";
4
4
 
5
5
  import { projectionWriteActive, setProjectionWriteActive, activeTransition, schedule, currentTransition, setStoreCommitHook } from "../../core/scheduler.js";
6
6
 
7
+ import "../../core/invariants.js";
8
+
7
9
  import { getObserver, getOwner } from "../../core/owner.js";
8
10
 
9
11
  import { $TARGET, isWrappable, markRawIngest, $PROXY, getWriteOverride, markRawOne, witnessAffectsMark, $TRACK, affectsScopesLive, inheritAffectsMarks, $AFFECTS, rawValuesUsed, isRawValue, setNextAffectsNodeResolver } from "../store.js";
10
12
 
11
- import { storeNextLookup, optHooks, ownedRaw, markDescendants, devAssertNeverUserMutation } from "./target.js";
13
+ import { lookupTarget, optHooks, storeNextLookup, $OWNER, isOwned, markDescendants, devAssertNeverUserMutation } from "./target.js";
12
14
 
13
15
  /**
14
16
  * Store rewrite — increment 2: plain deep stores with pending-backing writes.
@@ -59,6 +61,7 @@ function TargetShape() {
59
61
  this.d = undefined;
60
62
  this.a = undefined;
61
63
  this.sc = undefined;
64
+ this.kc = undefined;
62
65
  this.nc = undefined;
63
66
  this.ab = undefined;
64
67
  this.fam = undefined;
@@ -94,7 +97,8 @@ function createTarget(e, t, n, r = t?.fam ?? null) {
94
97
  i.px = null;
95
98
  i.d = false;
96
99
  i.a = false;
97
- i.sc = false;
100
+ i.sc = 0;
101
+ i.kc = 0;
98
102
  i.nc = 0;
99
103
  i.ab = null;
100
104
  i.fam = r;
@@ -115,7 +119,7 @@ function wrapNext(e, t = null, n = null, r = t?.fam ?? null) {
115
119
  // markRaw'd values never wrap through ANY store (R42; sticky raw-marking
116
120
  // is one half of the never-both-wrapped-and-raw invariant, RUL-12).
117
121
  if (rawValuesUsed && isRawValue(e)) return e;
118
- const i = (r?.map ?? storeNextLookup).get(e);
122
+ const i = lookupTarget(e, r);
119
123
  if (i !== undefined) return i.px;
120
124
  const o = e[$TARGET];
121
125
  if (o !== undefined && o.px === e) {
@@ -149,7 +153,7 @@ function wrapNext(e, t = null, n = null, r = t?.fam ?? null) {
149
153
  // (privatization/adoption swap raw identity without changing the logical
150
154
  // value — only changed leaves notify, R9).
151
155
  const slotNodeEquals = function(e, t) {
152
- return isEqual(e, t) || sameLogicalSlot(this.ht, e, t);
156
+ return isEqual(e, t) || sameLogicalSlot(this.Ut, e, t);
153
157
  };
154
158
 
155
159
  // Shared slot-node unobserved handler (create-floor diet): registered once;
@@ -158,11 +162,15 @@ const slotNodeEquals = function(e, t) {
158
162
  setSlotUnobserved(e => {
159
163
  // A live affects() mark keeps the node addressable (sweep parity).
160
164
  if (e.o?.t) return;
161
- const t = e.ht;
162
- const n = e.Ht;
165
+ const t = e.Ut;
166
+ const n = e.xt;
163
167
  if (t.n && t.n[n] === e) {
164
168
  delete t.n[n];
165
169
  t.nc--;
170
+ // Projection leaves also leave the firewall child chain (#3351): a
171
+ // dropped node is unreachable through the store — a fresh read makes a
172
+ // fresh node — so the chain would only retain it and its last value.
173
+ unlinkFirewallChild(e);
166
174
  }
167
175
  });
168
176
 
@@ -175,6 +183,19 @@ r = -1) {
175
183
  const i = e.n ??= Object.create(null);
176
184
  let o = i[t];
177
185
  if (o === undefined) {
186
+ // Born holding (#3330 store twin): a key first read while a live
187
+ // transaction holds an ADOPTION on this target (adoptPB's `ht`) is
188
+ // created as if it had existed when the hold was notified — the adopted
189
+ // value already swapped into `v`, so committed is the held view
190
+ // `hv[key]` and the adopted `v[key]` is staged under the transaction
191
+ // (stageHeldKey). Its held-adoption notification (stageHeldAdoptions)
192
+ // ran before the node existed and the drain has nothing left to say.
193
+ // Without this the node was born from whichever view its first reader
194
+ // saw and never learned the other. (The held-FOLD twin — a setter's
195
+ // write to an unobserved key landing only in the pending backing — is
196
+ // #3336's, and lands with #3337.)
197
+ const l = heldAdoptionTransition(e);
198
+ if (l !== null) n = e.hv[t];
178
199
  // Create-floor diet: slotSignal bakes the whole node into one literal —
179
200
  // no options object, no equals/unobserved closures, no NodeExtension,
180
201
  // no post-construction expandos (acc + the wrap cache px/pxv are
@@ -185,7 +206,7 @@ r = -1) {
185
206
  // notification machinery. Projection nodes carry the projection
186
207
  // computed as their firewall: reads through them link the derive's
187
208
  // status/lifecycle (§7b).
188
- const f = o = slotSignal(n, slotNodeEquals, e, t,
209
+ const f = o = slotSignal(n, slotNodeEquals, e, t,
189
210
  // Accessor-ness resolved ONCE per node (no per-object descriptor
190
211
  // scan on reads): accessor keys serve through Reflect.get with the
191
212
  // proxy receiver.
@@ -193,12 +214,13 @@ r = -1) {
193
214
  // Optimistic families: arm the override slot — setSignal routes armed
194
215
  // nodes through the core engine (lanes, ownership, reverts all native).
195
216
  if (e.fam?.opt) {
196
- ext(f).Pe = NOT_PENDING;
217
+ ext(f).be = NOT_PENDING;
197
218
  f.T |= CONFIG_OPTIMISTIC;
198
219
  }
199
220
  // A node born inside a live mark's identity scope inherits the mark
200
221
  // (the declaration walk could only cover nodes existing then).
201
222
  if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(f, e.v, t);
223
+ if (l !== null) stageHeldKey(f, e.v[t], l);
202
224
  i[t] = o;
203
225
  e.nc++;
204
226
  markDescendants(e);
@@ -206,11 +228,37 @@ r = -1) {
206
228
  return o;
207
229
  }
208
230
 
231
+ /** The live transaction holding an adoption on `target` (adoptPB's `ht`;
232
+ * a latest()-pull PLAIN_HOLD is not a transaction), else null. */ function heldAdoptionTransition(e) {
233
+ if (e.ht === null || e.ht === PLAIN_HOLD || heldMaskView(e) === null) return null;
234
+ const t = currentTransition(e.ht);
235
+ return t.ft === false ? t : null;
236
+ }
237
+
238
+ /**
239
+ * Materialization under a hold. A node created by a first tracked read while
240
+ * a transaction holds this target must carry the same state a node that
241
+ * existed when the hold was notified carries — a transaction-stamped
242
+ * `_pendingValue` that core read() serves by ITS rules (a stale render reader
243
+ * of a foreign transaction's write sees committed, an owner-context reader
244
+ * inside the transaction sees the write) — or the reader's answer depends on
245
+ * whether some OTHER reader had materialized the key before the hold. Stage
246
+ * `nv` (the held value for the key — see getNode for which view it comes
247
+ * from) as the holding transaction's write — directly, not through
248
+ * setSignal: it is not a new write (it staged with the adoption's batch) and
249
+ * it walks no subscriber (the node has none yet). Transition-stamped now, as
250
+ * `runFolded` does — no parked-flush pass will stamp it.
251
+ */ function stageHeldKey(e, t, n) {
252
+ if (slotNodeEquals.call(e, e.me, t)) return;
253
+ e.Ge = t;
254
+ e.ge = n;
255
+ n.$t.push(e);
256
+ }
257
+
209
258
  function sameLogicalSlot(e, t, n) {
210
259
  if (t === null || typeof t !== "object" || n === null || typeof n !== "object") return false;
211
- const r = e.fam?.map ?? storeNextLookup;
212
- const i = r.get(t);
213
- return i !== undefined && i === r.get(n);
260
+ const r = lookupTarget(t, e.fam);
261
+ return r !== undefined && r === lookupTarget(n, e.fam);
214
262
  }
215
263
 
216
264
  function getHasNode(e, t, n) {
@@ -221,12 +269,15 @@ function getHasNode(e, t, n) {
221
269
  equals: isEqual,
222
270
  unobserved() {
223
271
  if (o.o?.t) return;
224
- if (e.h && e.h[t] === o) delete e.h[t];
272
+ if (e.h && e.h[t] === o) {
273
+ delete e.h[t];
274
+ unlinkFirewallChild(o);
275
+ }
225
276
  }
226
277
  }, e.fam?.node ?? undefined);
227
278
  o.T |= CONFIG_OWNED_WRITE;
228
279
  if (e.fam?.opt) {
229
- ext(o).Pe = NOT_PENDING;
280
+ ext(o).be = NOT_PENDING;
230
281
  o.T |= CONFIG_OPTIMISTIC;
231
282
  }
232
283
  if (affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
@@ -242,12 +293,15 @@ function getKeySetNode(e) {
242
293
  const n = t = signal(0, {
243
294
  equals: false,
244
295
  unobserved() {
245
- if (e.k === n) e.k = null;
296
+ if (e.k === n) {
297
+ e.k = null;
298
+ unlinkFirewallChild(n);
299
+ }
246
300
  }
247
301
  }, e.fam?.node ?? undefined);
248
302
  n.T |= CONFIG_OWNED_WRITE;
249
303
  if (e.fam?.opt) {
250
- ext(n).Pe = NOT_PENDING;
304
+ ext(n).be = NOT_PENDING;
251
305
  n.T |= CONFIG_OPTIMISTIC;
252
306
  }
253
307
  e.k = t;
@@ -262,12 +316,15 @@ function getDeepNode(e) {
262
316
  const n = t = signal(0, {
263
317
  equals: false,
264
318
  unobserved() {
265
- if (e.dk === n) e.dk = null;
319
+ if (e.dk === n) {
320
+ e.dk = null;
321
+ unlinkFirewallChild(n);
322
+ }
266
323
  }
267
324
  }, e.fam?.node ?? undefined);
268
325
  n.T |= CONFIG_OWNED_WRITE;
269
326
  if (e.fam?.opt) {
270
- ext(n).Pe = NOT_PENDING;
327
+ ext(n).be = NOT_PENDING;
271
328
  n.T |= CONFIG_OPTIMISTIC;
272
329
  }
273
330
  if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
@@ -288,21 +345,36 @@ function getDeepNode(e) {
288
345
 
289
346
  let hookInstalled = false;
290
347
 
291
- function cloneRaw(e, t) {
348
+ /** Shallow-clone `source` as a backing OWNED by `t` (stamped `$OWNER`, see
349
+ * target.ts). Callers always pass their own committed backing as `source`. */ function cloneRaw(e, t) {
350
+ // Plain-data fast path (#3360): a scanned `Object.prototype` container
351
+ // whose own keys are all enumerable data clones by spread — the same
352
+ // result as the descriptor walk below (own enumerable string+symbol keys,
353
+ // normalized writable+configurable), at ~1/50th the cost.
354
+ t.sc || scanAccessorsOnce(t);
355
+ if (t.sc === 2) {
356
+ const n = {
357
+ ...e
358
+ };
359
+ n[$OWNER] = t;
360
+ return n;
361
+ }
292
362
  // Descriptor-preserving shallow clone (R29: installed getters stay live;
293
363
  // ruled 2026-08-17: frozen sources clone unfrozen — theirs stays frozen).
294
364
  // Data descriptors normalize to writable+configurable (the clone is OURS to
295
365
  // mutate — R51's "source-non-configurable is writable through the store");
296
366
  // enumerability and accessors are preserved. The scan doubles as the
297
367
  // accessor-flag detector (free — we're enumerating descriptors anyway).
298
- const n = Object.getOwnPropertyDescriptors(e);
368
+ const n = Object.getOwnPropertyDescriptors(e);
299
369
  for (const r of Reflect.ownKeys(n)) {
300
370
  const i = n[r];
301
371
  if (r === "length" && Array.isArray(e)) continue;
302
372
  i.configurable = true;
303
- if (!i.get && !i.set) i.writable = true; else if (t) t.a = true;
373
+ if (!i.get && !i.set) i.writable = true; else t.a = true;
304
374
  }
305
- return Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
375
+ const r = Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
376
+ r[$OWNER] = t;
377
+ return r;
306
378
  }
307
379
 
308
380
  /** Copy own `key` from `from` onto `to`. A plain data slot (enumerable,
@@ -314,19 +386,33 @@ function cloneRaw(e, t) {
314
386
  }
315
387
 
316
388
  /** One-time own-accessor scan (Annex-B probes, no descriptor allocation);
317
- * returns true when the container is plain data (overlay-safe). */ function scanAccessorsOnce(e) {
389
+ * returns true when the container is plain data (overlay-safe). Also grades
390
+ * the container for the plain-data fast paths (`sc` = 2): `Object.prototype`
391
+ * and every own key an enumerable data property — what a spread copies
392
+ * exactly and what a bare assignment lands exactly. */ function scanAccessorsOnce(e) {
318
393
  const t = e.v;
319
- for (const n of Reflect.ownKeys(t)) {
394
+ const n = Reflect.ownKeys(t);
395
+ let r = Object.getPrototypeOf(t) === Object.prototype;
396
+ for (const i of n) {
320
397
  // Own keys shadow prototype accessors, so the lookups are exact here.
321
- if (lookupGetter.call(t, n) !== undefined || lookupSetter.call(t, n) !== undefined) {
398
+ if (lookupGetter.call(t, i) !== undefined || lookupSetter.call(t, i) !== undefined) {
322
399
  e.a = true;
400
+ r = false;
323
401
  break;
324
402
  }
403
+ if (r && !propertyIsEnumerable.call(t, i)) r = false;
325
404
  }
326
- e.sc = true;
405
+ e.sc = r ? 2 : 1;
406
+ e.kc = n.length;
327
407
  return !e.a;
328
408
  }
329
409
 
410
+ /** Own-key count above which a draft opens as a prototype overlay rather
411
+ * than a clone (#3360). Below it a spread clone of a plain container is
412
+ * cheaper than the overlay's `Object.create` (V8 converts the committed
413
+ * backing into a prototype, and every later flatten writes into that
414
+ * prototype); above it the clone's O(keys) copy dominates (#3044). */ const OVERLAY_MIN_KEYS = 32;
415
+
330
416
  /** Downgrade a prototype-overlay pending backing to the clone path: builds
331
417
  * the real container (committed + overlay writes − deletes) that fold will
332
418
  * SWAP in as the committed backing, exactly as if the draft had started on
@@ -340,10 +426,6 @@ function cloneRaw(e, t) {
340
426
  for (const t of e.del) delete n[t];
341
427
  e.del = null;
342
428
  }
343
- const r = e.fam?.map ?? storeNextLookup;
344
- r.delete(t);
345
- ownedRaw.add(n);
346
- r.set(n, e);
347
429
  e.pb = n;
348
430
  e.ovl = false;
349
431
  }
@@ -366,15 +448,32 @@ function ensurePB(e) {
366
448
  }
367
449
  if (activeTransition !== null) foldBatches.set(e, activeTransition);
368
450
  if (t === null) {
369
- // Prototype-chain overlay (#3044): plain-data non-array containers
370
- // outside projection/optimistic families open drafts in O(1) — own keys
371
- // are the writes, reads fall through to committed. Everything else
372
- // (arrays: splice/length semantics; families: seeding/revert machinery;
373
- // accessor containers: live getters) keeps the descriptor clone.
374
- if (e.fam === null && !Array.isArray(e.v) && (e.sc ? !e.a : scanAccessorsOnce(e))) {
375
- t = e.pb = Object.create(e.v);
451
+ // Prototype-chain overlay (#3044): plain-data non-array containers open
452
+ // drafts in O(1) — own keys are the writes, reads fall through to
453
+ // committed. Projection and derived-store families take it too (#3352:
454
+ // a derive touching one root key of a wide keyed record paid an
455
+ // O(keys) clone per recompute). Everything else keeps the descriptor
456
+ // clone: arrays (splice/length semantics), OPTIMISTIC families (the
457
+ // view-seeding below writes and deletes on the draft container, and the
458
+ // tentative discard/truth-park hand whole backings around), chained
459
+ // backings (the committed layer is another store's proxy — an overlay
460
+ // would route every read-through into its traps), accessor containers
461
+ // (live getters).
462
+ // The overlay pays off only for a WIDE container over an OWNED committed
463
+ // backing (#3360). Narrow containers clone cheaper than they overlay (a
464
+ // spread is a fast-path copy; `Object.create` turns the backing into a
465
+ // V8 prototype and every later flatten writes into that prototype). An
466
+ // unowned backing has to be privatized (cloned) at commit anyway, so an
467
+ // overlay there would cost the create PLUS the clone PLUS a per-key
468
+ // copy — the clone path does the one clone and swaps it in. The first
469
+ // write on a fresh store is exactly that case.
470
+ const n = e.v;
471
+ if (!e.fam?.opt && !e.ch && !Array.isArray(n) && (e.sc !== 0 ? !e.a : scanAccessorsOnce(e)) && e.kc > OVERLAY_MIN_KEYS && isOwned(n)) {
472
+ // Inherits `v`'s $OWNER stamp — the overlay resolves and reads as
473
+ // owned without a registration of its own.
474
+ t = e.pb = Object.create(n);
376
475
  e.ovl = true;
377
- } else t = e.pb = cloneRaw(e.v, e);
476
+ } else t = e.pb = cloneRaw(n, e);
378
477
  // Optimistic families: seed USER drafts from the OPTIMISTIC VIEW
379
478
  // (committed + active node overrides), so follow-up writes compose on
380
479
  // optimism instead of clobbering from base (#2951's compose half).
@@ -387,19 +486,17 @@ function ensurePB(e) {
387
486
  if (n !== null) {
388
487
  for (const e of Reflect.ownKeys(n)) {
389
488
  const r = n[e];
390
- if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.Pe);
489
+ if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.be);
391
490
  }
392
491
  }
393
492
  const r = e.h;
394
493
  if (r !== null) {
395
494
  for (const e of Reflect.ownKeys(r)) {
396
495
  const n = r[e];
397
- if (hasActiveOverride(n) && !unwrapOverride(n.o?.Pe)) delete t[e];
496
+ if (hasActiveOverride(n) && !unwrapOverride(n.o?.be)) delete t[e];
398
497
  }
399
498
  }
400
499
  }
401
- ownedRaw.add(t);
402
- (e.fam?.map ?? storeNextLookup).set(t, e);
403
500
  queueFold(e);
404
501
  }
405
502
  return t;
@@ -420,7 +517,7 @@ function ensurePB(e) {
420
517
  * foldHeld's node stamps). */ function heldMaskView(e) {
421
518
  const t = e.ht;
422
519
  if (t === null) return null;
423
- if (t !== PLAIN_HOLD && currentTransition(t)?.sn === true) return e.ht = e.hv = null;
520
+ if (t !== PLAIN_HOLD && currentTransition(t)?.ft === true) return e.ht = e.hv = null;
424
521
  return e.hv;
425
522
  }
426
523
 
@@ -450,20 +547,26 @@ function ensurePB(e) {
450
547
  if (e.ovl) materializePB(e);
451
548
  e.ab = e.pb;
452
549
  } else e.ab ??= foldOlds.get(e);
453
- // #3074/#3075: a projection recompute deriving from uncommitted inputs
454
- // swaps the backing SPECULATIVELY committed-visibility readers must
455
- // keep the pre-hold view until the hold resolves (a source held by a
456
- // live transition, or a latest()-pull ahead of the flush). Post-await
457
- // landings (write-override) stay immediately visible landed truth —
458
- // and clear any hold; optimistic families ride the lane machinery.
459
- if (e.fam?.opt !== true) {
460
- if (getWriteOverride()) {
461
- e.ht = e.hv = null;
462
- } else if (activeTransition !== null || latestPullActive) {
463
- if (heldMaskView(e) === null) e.hv = e.v;
464
- e.ht = activeTransition ?? PLAIN_HOLD;
465
- }
466
- }
550
+ }
551
+ // #3074/#3075: a projection recompute deriving from uncommitted inputs
552
+ // swaps the backing SPECULATIVELY committed-visibility readers must
553
+ // keep the pre-hold view until the hold resolves (a source held by a
554
+ // live transition, or a latest()-pull ahead of the flush). Post-await
555
+ // landings (write-override) stay immediately visible landed truth
556
+ // and clear any hold. Optimistic families hold too (#3330 store twin):
557
+ // their tentative edits ride the lane machinery, but a sync derive
558
+ // adopting under a transaction is held TRUTH like any projection's —
559
+ // unheld, handlers read it early and an optimistic write equal to it
560
+ // compared as a no-op against the swapped-in backing. A plain store's
561
+ // reconcile inside an action holds the same way: its nodes stage under the
562
+ // transaction (the inline notify), and the backing must not show handlers
563
+ // and stale readers what the tracked read masks (signal parity, #3336).
564
+ if (getWriteOverride()) {
565
+ e.ht = e.hv = null;
566
+ } else if (activeTransition !== null || !n && latestPullActive) {
567
+ if (heldMaskView(e) === null) e.hv = e.v;
568
+ e.ht = activeTransition ?? PLAIN_HOLD;
569
+ if (!n && activeTransition !== null) heldAdoptions.add(e);
467
570
  }
468
571
  e.pb = null;
469
572
  // Overlay and accessor-scan state describe the OUTGOING backing — a
@@ -475,13 +578,17 @@ function ensurePB(e) {
475
578
  // draft rescans once (#3044 audit follow-up).
476
579
  e.ovl = false;
477
580
  e.del = null;
478
- e.sc = false;
581
+ e.sc = 0;
479
582
  e.a = false;
480
583
  e.wk = null;
481
584
  // adoption supersedes staged trap writes
482
585
  e.v = t;
483
586
  e.ch = t[$TARGET] !== undefined;
484
- (e.fam?.map ?? storeNextLookup).set(t, e);
587
+ // An adoptee we own within this family (a draft aliasing one of the
588
+ // family's own backings) re-stamps to its new owner — the stamp is the
589
+ // family's registration for it; everything else registers in the map.
590
+ const r = t[$OWNER];
591
+ if (r !== undefined && r.fam === e.fam) t[$OWNER] = e; else (e.fam?.map ?? storeNextLookup).set(t, e);
485
592
  }
486
593
 
487
594
  /** Sentinel for `t.wk`: the written-keys bound is unusable this batch (an
@@ -543,22 +650,22 @@ function queueFold(e) {
543
650
  }
544
651
 
545
652
  /** Committed-time privatization for parent-chain slot updates (path copying). */ function privatizeCommitted(e) {
546
- if (ownedRaw.has(e.v)) return;
653
+ if (isOwned(e.v)) return;
547
654
  const t = e.v;
548
655
  const n = cloneRaw(t, e);
549
- ownedRaw.add(n);
550
- // Register in the target's OWN registration map (#3284): family targets
551
- // (derived stores, projections, optimistic) resolve children through
552
- // fam.map — a clone parked only in the global lookup makes the next parent
553
- // read miss, wrap a fresh target, and orphan every node (subscribers) on
554
- // this one.
555
- (e.fam?.map ?? storeNextLookup).set(n, e);
556
656
  e.v = n;
557
657
  e.ch = false;
558
658
  if (e.u) {
559
659
  privatizeCommitted(e.u);
560
660
  devAssertNeverUserMutation(e.u.v);
561
- e.u.v[parentSlotKey(e, t)] = e.v;
661
+ // CAS, same as drainFolds' path copy: re-point the parent slot only
662
+ // while it still holds the raw we cloned. A parent that folded EARLIER
663
+ // in this drain may have replaced or deleted this slot (the same batch
664
+ // wrote the child and then `parent.row = fresh` / `parent.length = 0`)
665
+ // — an unconditional write resurrected the dropped child over it.
666
+ const n = e.u.v;
667
+ const r = parentSlotKey(e, t);
668
+ if (n[r] === t) n[r] = e.v;
562
669
  }
563
670
  }
564
671
 
@@ -577,6 +684,28 @@ function queueFold(e) {
577
684
  return i;
578
685
  }
579
686
 
687
+ /** Overlay commit (#3044): apply the batch's writes onto an OWNED committed
688
+ * backing in place — O(written), not O(container). Unowned backings
689
+ * privatize first (clone once, parents re-slotted) — the never-mutate-user-
690
+ * data contract holds. Shared by the deferred fold (drainFolds) and the
691
+ * projection landing's immediate commit (notifyWrites). */ function flattenOverlay(e, t) {
692
+ privatizeCommitted(e);
693
+ const n = e.v;
694
+ // Plain-data grade (sc 2): every own key on the overlay is an enumerable
695
+ // writable data slot (set-trap writes; a non-plain defineProperty
696
+ // downgrades the grade) — a bare assignment lands it without the
697
+ // descriptor round trip.
698
+ for (const r of Reflect.ownKeys(t)) e.sc === 2 ? n[r] = t[r] : copyOwn(n, t, r);
699
+ if (e.del !== null) {
700
+ for (const t of e.del) delete n[t];
701
+ e.del = null;
702
+ }
703
+ e.pb = null;
704
+ e.ovl = false;
705
+ e.wk = null;
706
+ // written-keys window closes with the commit
707
+ }
708
+
580
709
  function drainFolds() {
581
710
  if (foldOlds.size === 0) return;
582
711
  const e = [ ...foldOlds ];
@@ -592,7 +721,7 @@ function drainFolds() {
592
721
  // keys; observed keys also hit the pending-node held check below).
593
722
  const e = foldBatches.get(t);
594
723
  if (e !== undefined) {
595
- if (currentTransition(e).sn === false) {
724
+ if (currentTransition(e).ft === false) {
596
725
  foldOlds.set(t, n);
597
726
  continue;
598
727
  }
@@ -616,7 +745,7 @@ function drainFolds() {
616
745
  !plainProto(t.ovl ? t.v : i) ? Reflect.ownKeys(o) : e;
617
746
  for (const e of n) {
618
747
  const t = o[e];
619
- if (t !== undefined && t.Re !== NOT_PENDING) {
748
+ if (t !== undefined && t.Ge !== NOT_PENDING) {
620
749
  r = true;
621
750
  break;
622
751
  }
@@ -628,26 +757,12 @@ function drainFolds() {
628
757
  continue;
629
758
  }
630
759
  if (t.ovl) {
631
- // Overlay flatten (#3044): apply this batch's writes onto an OWNED
632
- // committed backing in place O(written), not O(container). The
633
- // backing keeps its identity, so the `t.v === old` gate below skips
634
- // path copying (the parent slot already points here) and the
635
- // adopted-notify (setter notifications happened at write time).
636
- // Unowned backings privatize first (clone once, parents re-slotted)
637
- // — the never-mutate-user-data contract holds.
638
- privatizeCommitted(t);
639
- const e = t.v;
640
- for (const t of Reflect.ownKeys(i)) copyOwn(e, i, t);
641
- if (t.del !== null) {
642
- for (const n of t.del) delete e[n];
643
- t.del = null;
644
- }
645
- (t.fam?.map ?? storeNextLookup).delete(i);
646
- t.pb = null;
647
- t.ovl = false;
648
- t.wk = null;
649
- // written-keys window closes with the fold commit
650
- } else if (t.v !== n) {
760
+ // Overlay flatten (#3044): the backing keeps its identity, so the
761
+ // `t.v === old` gate below skips path copying (the parent slot
762
+ // already points here) and the adopted-notify (setter notifications
763
+ // happened at write time).
764
+ flattenOverlay(t, i);
765
+ } else if (t.v !== n) {
651
766
  // Privatized mid-batch (#3271): an earlier fold in this drain
652
767
  // path-copied THROUGH this target — privatizeCommitted cloned the
653
768
  // committed backing, re-pointed the parent slot at the clone, and
@@ -682,7 +797,6 @@ function drainFolds() {
682
797
  if (!hasOwn.call(i, t)) delete e[t];
683
798
  }
684
799
  }
685
- (t.fam?.map ?? storeNextLookup).delete(i);
686
800
  t.pb = null;
687
801
  t.wk = null;
688
802
  // written-keys window closes with the fold commit
@@ -798,15 +912,15 @@ function drainFolds() {
798
912
  // projection recompute can run before the prior fold commits) — the
799
913
  // node's OWN current value is the true old side, and setSignal's
800
914
  // internal equality already checks exactly that.
801
- const f = e.del !== null && e.del.has(o) ? undefined : t[o];
802
- setSignal(i, () => f);
915
+ const l = e.del !== null && e.del.has(o) ? undefined : t[o];
916
+ setSignal(i, () => l);
803
917
  }
804
918
  }
805
- const f = e.h;
806
- if (f !== null) {
807
- const n = o ?? Reflect.ownKeys(f);
919
+ const l = e.h;
920
+ if (l !== null) {
921
+ const n = o ?? Reflect.ownKeys(l);
808
922
  for (const r of n) {
809
- const n = f[r];
923
+ const n = l[r];
810
924
  if (n !== undefined) setSignal(n, r in t && !(e.del !== null && e.del.has(r)));
811
925
  }
812
926
  }
@@ -814,6 +928,7 @@ function drainFolds() {
814
928
  // keys with no node. O(written/pb keys) equality only when a witness exists.
815
929
  if (e.dk !== null) {
816
930
  if (e.del !== null && e.del.size !== 0) bumpDeep(e); else for (const r of o ?? Reflect.ownKeys(t)) {
931
+ if (r === $OWNER) continue;
817
932
  const i = t[r];
818
933
  const o = n[r];
819
934
  if (i !== null && typeof i === "object" ? !targetsEqual(o, i) : !isEqual(o, i)) {
@@ -860,6 +975,10 @@ function drainFolds() {
860
975
  // Landed truth (post-await write-override): immediately visible to every
861
976
  // reader — any staged held view is superseded.
862
977
  if (e.ht !== null) e.ht = e.hv = null;
978
+ // Overlay landing (#3352): flatten in place — identity-stable, and
979
+ // privatizeCommitted re-slots the parent itself when it has to clone an
980
+ // unowned seed, so no path copy is needed.
981
+ if (e.ovl) return flattenOverlay(e, t);
863
982
  const n = e.v;
864
983
  e.pb = null;
865
984
  e.v = t;
@@ -880,9 +999,9 @@ const FORCE = Symbol();
880
999
 
881
1000
  /** Same logical slot: both values resolve to one (re-pointed) child target —
882
1001
  * adoption preserved identity, so the slot did not change (R9). */ function targetsEqual(e, t) {
883
- if (e === null || typeof e !== "object") return false;
884
- const n = storeNextLookup.get(e);
885
- return n !== undefined && n === storeNextLookup.get(t);
1002
+ if (e === null || typeof e !== "object" || t === null || typeof t !== "object") return false;
1003
+ const n = lookupTarget(e, null);
1004
+ return n !== undefined && n === lookupTarget(t, null);
886
1005
  }
887
1006
 
888
1007
  function arrayStructureChanged(e, t) {
@@ -897,8 +1016,9 @@ function arrayStructureChanged(e, t) {
897
1016
 
898
1017
  function membershipChanged(e, t) {
899
1018
  const n = Reflect.ownKeys(t);
900
- if (Reflect.ownKeys(e).length !== n.length) return true;
901
- for (const t of n) if (!(t in e)) return true;
1019
+ // The $OWNER stamp is not membership: an owned side counts one key more.
1020
+ if (Reflect.ownKeys(e).length - +isOwned(e) !== n.length - +isOwned(t)) return true;
1021
+ for (const t of n) if (t !== $OWNER && !(t in e)) return true;
902
1022
  return false;
903
1023
  }
904
1024
 
@@ -928,9 +1048,9 @@ i = true) {
928
1048
  if (i?.get !== o?.get || i?.set !== o?.set || i?.value !== o?.value) setSignal(e, () => FORCE);
929
1049
  return;
930
1050
  }
931
- const f = i?.value;
932
- const l = o?.value;
933
- if (!isEqual(f, l) && !targetsEqual(f, l)) setSignal(e, typeof l === "function" ? () => l : l);
1051
+ const l = i?.value;
1052
+ const f = o?.value;
1053
+ if (!isEqual(l, f) && !targetsEqual(l, f)) setSignal(e, typeof f === "function" ? () => f : f);
934
1054
  } else {
935
1055
  const i = n[t];
936
1056
  const o = r[t];
@@ -1046,6 +1166,34 @@ function inDraft(e) {
1046
1166
 
1047
1167
  /** Targets written during the current (outermost) setter — notified at exit. */ const pendingNotify = new Set;
1048
1168
 
1169
+ /** Targets adopted under a live transaction this setter (adoptPB set a
1170
+ * transaction hold) — their nodes are notified at the outermost exit. */ const heldAdoptions = new Set;
1171
+
1172
+ /**
1173
+ * Write-time notification for a transaction-held adoption (#3330 store twin;
1174
+ * the setter path's notifyWrites twin for adoptions). A projection's fold
1175
+ * normally notifies its nodes at the drain — and the drain of a batch parked
1176
+ * in a live transaction is the transaction's COMMIT, so the nodes took the
1177
+ * adopted values as fresh writes at commit time: every subscriber was
1178
+ * re-marked and re-ran against a frame the lane had already published (a
1179
+ * third `v=1 d=2`), where a signal's write had staged at write time and
1180
+ * promoted silently. Staging here, inside the transaction's batch, makes the
1181
+ * two paths one: the nodes carry transition-stamped `_pendingValue`s, their
1182
+ * subscribers recompute in this flush and park with the transaction, and the
1183
+ * commit promotes without re-notifying. `ab` moves to the adopted backing —
1184
+ * the view the nodes were last told (#3296) — so the drain has nothing left
1185
+ * to say and only path-copies.
1186
+ */ function stageHeldAdoptions() {
1187
+ const e = [ ...heldAdoptions ];
1188
+ heldAdoptions.clear();
1189
+ for (const t of e) {
1190
+ const e = t.ab;
1191
+ if (e === null || e === t.v) continue;
1192
+ notifyFold(t, e, t.v);
1193
+ t.ab = t.v;
1194
+ }
1195
+ }
1196
+
1049
1197
  const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1050
1198
 
1051
1199
  /** Mirror of core read()'s context rule: the OWNER context (not the tracking
@@ -1055,7 +1203,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1055
1203
  * / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */ function inOwnerContext() {
1056
1204
  const e = getOwner();
1057
1205
  if (e === null) return false;
1058
- const t = e.Dt ? e.Pt : e;
1206
+ const t = e.gt ? e.bt : e;
1059
1207
  return t != null && !(t.T & CONFIG_CHILDREN_FORBIDDEN);
1060
1208
  }
1061
1209
 
@@ -1066,7 +1214,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1066
1214
  * committed to them regardless of staged writes. */ function inForbiddenScope() {
1067
1215
  const e = getOwner();
1068
1216
  if (e === null) return false;
1069
- const t = e.Dt ? e.Pt : e;
1217
+ const t = e.gt ? e.bt : e;
1070
1218
  return t != null && !!(t.T & CONFIG_CHILDREN_FORBIDDEN);
1071
1219
  }
1072
1220
 
@@ -1077,7 +1225,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1077
1225
  if (t === null) return false;
1078
1226
  for (const e of Reflect.ownKeys(t)) {
1079
1227
  const n = t[e];
1080
- if (n.Re !== NOT_PENDING && n.Ae != null && n.Ae.sn !== true) return true;
1228
+ if (n.Ge !== NOT_PENDING && n.ge != null && n.ge.ft !== true) return true;
1081
1229
  }
1082
1230
  return false;
1083
1231
  }
@@ -1149,6 +1297,8 @@ const lookupGetter = Object.prototype.__lookupGetter__;
1149
1297
 
1150
1298
  const lookupSetter = Object.prototype.__lookupSetter__;
1151
1299
 
1300
+ const propertyIsEnumerable = Object.prototype.propertyIsEnumerable;
1301
+
1152
1302
  function isOwnAccessor(e, t) {
1153
1303
  return hasOwn.call(e, t) && (lookupGetter.call(e, t) !== undefined || lookupSetter.call(e, t) !== undefined);
1154
1304
  }
@@ -1168,7 +1318,7 @@ function isOwnAccessor(e, t) {
1168
1318
 
1169
1319
  /** Active optimistic override on an armed node (armed slot idles at
1170
1320
  * NOT_PENDING; undefined = unarmed plain node). */ function hasActiveOverride(e) {
1171
- return e.o?.Pe !== undefined && e.o?.Pe !== NOT_PENDING;
1321
+ return e.o?.be !== undefined && e.o?.be !== NOT_PENDING;
1172
1322
  }
1173
1323
 
1174
1324
  /** The reading computation is until()'s authoritative-view predicate — same
@@ -1203,30 +1353,53 @@ function isOwnAccessor(e, t) {
1203
1353
  // Authoritative-view reads (until()'s predicate) skip the override arm
1204
1354
  // only: staged pending values are authoritative, overrides are the
1205
1355
  // caller's optimism.
1206
- const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.Pe) : e.Re !== NOT_PENDING && (latestReadActive ||
1356
+ const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.be) : e.Ge !== NOT_PENDING && (latestReadActive ||
1207
1357
  // Owner-context pending visibility — except HELD truth (#3164,
1208
1358
  // see CONFIG_HELD_TRUTH: fold-staged or entangle-stolen
1209
1359
  // confirming truth), which only authoritative/latest readers
1210
1360
  // see (core read()'s A17-for-held-truth twin; ordinary readers
1211
1361
  // keep committed until the transaction's reveal — latest() is
1212
1362
  // exempted by the leading arm above).
1213
- (inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Re : t;
1363
+ (inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Ge : t;
1214
1364
  return n === FORCE ? t : n;
1215
1365
  }
1216
1366
 
1367
+ /** §7b: a chained target's child found as a RAW — from its pending backing
1368
+ * (a cloneRaw of the inner proxy, whose descriptors yield the inner store's
1369
+ * raws) or from the deep() walk's descriptor read through the chain — must
1370
+ * resolve to the inner family's proxy for that object before this family
1371
+ * wraps it, so the overlay serves the same chained targets as the settled
1372
+ * state. Otherwise every row re-wrapped as a fresh non-chained target keyed
1373
+ * by the raw: identities churned for the life of an optimistic action and
1374
+ * snapped back at settle, and writes landed on those orphans while deep()'s
1375
+ * witnesses sat on the chained targets (#3323). The inner family owns the
1376
+ * raw iff it has served it or its backing holds that exact object at `key`;
1377
+ * anything else is a draft's own replacement object — view-owned, correctly
1378
+ * non-chained — and is returned as is. Recurses down further chains. */ function resolveChainedRaw(e, t, n) {
1379
+ const r = e.v[$TARGET];
1380
+ if (r.ch) {
1381
+ const e = resolveChainedRaw(r, t, n);
1382
+ return e === n ? n : wrapNext(e, r, t);
1383
+ }
1384
+ const i = lookupTarget(n, r.fam);
1385
+ if (i !== undefined) return i.px;
1386
+ if ((r.v[t] === n || r.pb?.[t] === n) && isWrappable(n)) return wrapNext(n, r, t);
1387
+ return n;
1388
+ }
1389
+
1217
1390
  /** Serve an own data key: node-first when a node exists (pending visibility,
1218
1391
  * holds, lanes ride the node); backing otherwise. Chained backings (§7b: the
1219
1392
  * backing IS another store's proxy) serve the read-through value — the outer
1220
1393
  * node is linked only for adoption-swap notification, its value never
1221
1394
  * shadows the live chain. */ function serveDataKey(e, t, n, r, i, o = -1) {
1222
- const f = e.ch && r === e.v;
1223
- let l = n;
1395
+ const l = e.ch && r === e.v;
1396
+ let f = n;
1224
1397
  // §6: on optimistic arrays LENGTH IS A VIEW, not a node value — one home
1225
1398
  // (backing ± presence overrides) for both length and indices makes torn
1226
1399
  // iteration impossible (a length node's value rides different visibility
1227
1400
  // rails than index overrides mid-settle). The node still carries
1228
1401
  // subscriptions; its value is never served here.
1229
- if (t === "length" && e.fam?.opt === true && !f && Array.isArray(r)) {
1402
+ if (t === "length" && e.fam?.opt === true && !l && Array.isArray(r)) {
1230
1403
  if (!inDraft(e)) {
1231
1404
  const r = e.n?.length;
1232
1405
  if (r !== undefined) {
@@ -1248,7 +1421,7 @@ function isOwnAccessor(e, t) {
1248
1421
  // seeding rule, applied to the read side (#3108).
1249
1422
  if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
1250
1423
  const n = e.n?.[t];
1251
- if (n !== undefined && hasActiveOverride(n)) l = unwrapOverride(n.o?.Pe);
1424
+ if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.be);
1252
1425
  }
1253
1426
  } else {
1254
1427
  if (i !== undefined) {
@@ -1260,9 +1433,9 @@ function isOwnAccessor(e, t) {
1260
1433
  // parity): READ_SLOW = a global read window or non-plain node.
1261
1434
  let e = readNodeFast(i);
1262
1435
  if (e === READ_SLOW) e = read(i);
1263
- if (!f || hasActiveOverride(i)) l = e === FORCE ? n : e;
1264
- } else if (!f || hasActiveOverride(i)) {
1265
- l = nodeValue(i, n);
1436
+ if (!l || hasActiveOverride(i)) f = e === FORCE ? n : e;
1437
+ } else if (!l || hasActiveOverride(i)) {
1438
+ f = nodeValue(i, n);
1266
1439
  }
1267
1440
  } else if (getObserver() !== null) {
1268
1441
  // First tracked read: create + link, and let the wrap-cache branch
@@ -1271,19 +1444,20 @@ function isOwnAccessor(e, t) {
1271
1444
  }
1272
1445
  }
1273
1446
  // Shallow stores serve data raw; store-proxy slots get boundary wrappers.
1274
- if (e.s) return serveShallow(e, t, l);
1447
+ if (e.s) return serveShallow(e, t, f);
1448
+ if (e.ch && !l && f !== null && typeof f === "object" && f[$TARGET] === undefined) f = resolveChainedRaw(e, t, f);
1275
1449
  if (i !== undefined) {
1276
1450
  // Wrap cache (see getNode): only wrappables are ever cached, so a hit
1277
1451
  // skips isWrappable too — pointer-compare replaces both checks.
1278
- if (i.pxv === l && l !== undefined) return draftServe(e, i.px);
1279
- if (!isWrappable(l)) return l;
1280
- const n = wrapNext(l, e, t);
1452
+ if (i.pxv === f && f !== undefined) return draftServe(e, i.px);
1453
+ if (!isWrappable(f)) return f;
1454
+ const n = wrapNext(f, e, t);
1281
1455
  i.px = n;
1282
- i.pxv = l;
1456
+ i.pxv = f;
1283
1457
  return draftServe(e, n);
1284
1458
  }
1285
- if (!isWrappable(l)) return l;
1286
- return draftServe(e, wrapNext(l, e, t));
1459
+ if (!isWrappable(f)) return f;
1460
+ return draftServe(e, wrapNext(f, e, t));
1287
1461
  }
1288
1462
 
1289
1463
  /** §6c store-wide status gate for reads that DON'T flow through a node:
@@ -1334,6 +1508,8 @@ const traps = {
1334
1508
  if (typeof t !== "string") {
1335
1509
  if (t === $TARGET) return e;
1336
1510
  if (t === $PROXY) return n;
1511
+ if (t === $OWNER) return undefined;
1512
+ // ownership stamp: never a user key
1337
1513
  // refresh()/isPending resolve the projection computed through $REFRESH.
1338
1514
  if (t === $REFRESH) return e.fam?.node ?? undefined;
1339
1515
  if (t === $TRACK) {
@@ -1407,16 +1583,16 @@ const traps = {
1407
1583
  // scan (pb ?? v).
1408
1584
  let o = -1;
1409
1585
  {
1410
- let f;
1411
- if (i !== undefined) f = i.acc === true; else if (!inDraft(e) && getObserver() !== null) {
1412
- f = isOwnAccessor(r, t);
1413
- if (r === (e.pb ?? e.v)) o = f ? 1 : 0;
1414
- } else f = false;
1415
- if (f) {
1586
+ let l;
1587
+ if (i !== undefined) l = i.acc === true; else if (!inDraft(e) && getObserver() !== null) {
1588
+ l = isOwnAccessor(r, t);
1589
+ if (r === (e.pb ?? e.v)) o = l ? 1 : 0;
1590
+ } else l = false;
1591
+ if (l) {
1416
1592
  if (!inDraft(e) && getObserver() !== null) read(i ?? getNode(e, t, undefined, o));
1417
- const f = Reflect.get(r, t, n);
1418
- if (e.s) return serveShallow(e, t, f);
1419
- return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
1593
+ const l = Reflect.get(r, t, n);
1594
+ if (e.s) return serveShallow(e, t, l);
1595
+ return isWrappable(l) ? draftServe(e, wrapNext(l, e, t)) : l;
1420
1596
  }
1421
1597
  }
1422
1598
  // Plain-data fast path: no descriptor allocation per read.
@@ -1426,17 +1602,17 @@ const traps = {
1426
1602
  // backings chain to the committed backing, so "own in the view" means
1427
1603
  // own on either layer (ownInView) — a genuine prototype method is one
1428
1604
  // that is own on NEITHER.
1429
- const f = e.ovl && r === e.pb;
1430
- if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(f && hasOwn.call(e.v, t))) return undefined;
1431
- let l = r[t];
1432
- if (l === undefined ? !hasOwn.call(r, t) && !(f && hasOwn.call(e.v, t)) : false) {
1605
+ const l = e.ovl && r === e.pb;
1606
+ if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(l && hasOwn.call(e.v, t))) return undefined;
1607
+ let f = r[t];
1608
+ if (f === undefined ? !hasOwn.call(r, t) && !(l && hasOwn.call(e.v, t)) : false) {
1433
1609
  // Inherited: prototype getters/methods run with the proxy receiver.
1434
- l = Reflect.get(r, t, n);
1435
- if (typeof l === "function") return l;
1610
+ f = Reflect.get(r, t, n);
1611
+ if (typeof f === "function") return f;
1436
1612
  // proto methods untracked
1437
1613
  // Reading a currently-absent own key subscribes to it (R12) — for any
1438
1614
  // target OUTSIDE its own draft scope, even mid-setter (#3037, above).
1439
- if (l === undefined && !inDraft(e)) {
1615
+ if (f === undefined && !inDraft(e)) {
1440
1616
  if (getObserver() !== null) read(getNode(e, t, undefined, o));
1441
1617
  const n = e.n?.[t];
1442
1618
  if (n) {
@@ -1444,22 +1620,23 @@ const traps = {
1444
1620
  if (e.s) return serveShallow(e, t, r);
1445
1621
  return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
1446
1622
  }
1447
- } else if (l === undefined && inDraft(e) && e.fam?.opt && draftSeesOverrides(e) &&
1623
+ } else if (f === undefined && inDraft(e) && e.fam?.opt && draftSeesOverrides(e) &&
1448
1624
  // AUTHORITATIVE drafts (landing folds) never seed from overrides —
1449
1625
  // the caller's optimism is not truth (has-trap twin below).
1450
1626
  !authoritativeServe()) {
1451
1627
  const n = e.n?.[t];
1452
- if (n !== undefined && hasActiveOverride(n)) l = unwrapOverride(n.o?.Pe);
1628
+ if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.be);
1453
1629
  }
1454
- if (e.s) return serveShallow(e, t, l);
1455
- return isWrappable(l) ? draftServe(e, wrapNext(l, e, t)) : l;
1630
+ if (e.s) return serveShallow(e, t, f);
1631
+ return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
1456
1632
  }
1457
- if (typeof l === "function" && !hasOwn.call(r, t) && !(f && hasOwn.call(e.v, t))) return l;
1633
+ if (typeof f === "function" && !hasOwn.call(r, t) && !(l && hasOwn.call(e.v, t))) return f;
1458
1634
  // proto method
1459
- return serveDataKey(e, t, l, r, i, o);
1635
+ return serveDataKey(e, t, f, r, i, o);
1460
1636
  },
1461
1637
  has(e, t) {
1462
1638
  if (t === $TARGET || t === $PROXY || t === $TRACK) return true;
1639
+ if (t === $OWNER) return false;
1463
1640
  if (pendingCheckActive) witnessAffectsMark(e, t);
1464
1641
  if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
1465
1642
  const n = readSource(e);
@@ -1475,11 +1652,11 @@ const traps = {
1475
1652
  if (hasActiveOverride(n)) r = !!i;
1476
1653
  } else if (!authoritativeServe()) {
1477
1654
  const n = e.h?.[t];
1478
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
1655
+ if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.be);
1479
1656
  }
1480
1657
  } else if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
1481
1658
  const n = e.h?.[t];
1482
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
1659
+ if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.be);
1483
1660
  }
1484
1661
  return r;
1485
1662
  },
@@ -1487,66 +1664,17 @@ const traps = {
1487
1664
  if (pendingCheckActive) witnessAffectsMark(e);
1488
1665
  if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
1489
1666
  if (!inDraft(e) && getObserver() !== null) read(getKeySetNode(e));
1490
- const t = readSource(e);
1491
- let n;
1492
- if (e.ovl && t === e.pb) {
1493
- // Overlay merge (#3044): committed keys in their order, then this
1494
- // batch's NEW keys, minus deletes.
1495
- n = Reflect.ownKeys(e.v);
1496
- const r = e.del;
1497
- if (r !== null && r.size !== 0) n = n.filter(e => !r.has(e));
1498
- for (const r of Reflect.ownKeys(t)) {
1499
- if (!hasOwn.call(e.v, r)) n.push(r);
1500
- }
1501
- } else n = Reflect.ownKeys(t);
1502
- // Optimistic membership overlay: presence-node overrides add/remove keys
1503
- // (per-transaction lifecycle rides the nodes — §6, FINDING-2's fix).
1504
- // Draft reads before the first write overlay too (pb, once created, is
1505
- // seeded with the view). Authoritative-view reads (until()'s predicate,
1506
- // truth-author drafts) skip the overlay.
1507
- if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || draftSeesOverrides(e))) {
1508
- let t = null;
1509
- for (const r of Reflect.ownKeys(e.h)) {
1510
- const i = e.h[r];
1511
- if (!hasActiveOverride(i)) continue;
1512
- t ??= new Set(n);
1513
- if (unwrapOverride(i.o?.Pe)) t.add(r); else t.delete(r);
1514
- }
1515
- if (t !== null) return [ ...t ];
1516
- }
1517
- return n;
1667
+ return visibleKeys(e, readSource(e));
1518
1668
  },
1519
1669
  getOwnPropertyDescriptor(e, t) {
1520
- const n = readSource(e);
1521
- let r = Object.getOwnPropertyDescriptor(n, t);
1522
- // Overlay (#3044): unwritten keys live on the committed backing;
1523
- // deleted keys are absent from the pending view.
1524
- if (e.ovl && n === e.pb) {
1525
- if (e.del !== null && e.del.has(t)) return undefined;
1526
- if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, t);
1527
- }
1528
- if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
1529
- const n = e.h?.[t];
1530
- if (n !== undefined && hasActiveOverride(n)) {
1531
- if (!unwrapOverride(n.o?.Pe)) return undefined;
1532
- // opt delete
1533
- if (r === undefined) {
1534
- const n = e.n?.[t];
1535
- return {
1536
- value: n !== undefined ? nodeValue(n, undefined) : undefined,
1537
- writable: true,
1538
- enumerable: true,
1539
- configurable: true
1540
- };
1541
- }
1542
- }
1543
- }
1544
- if (r === undefined) return undefined;
1670
+ if (t === $OWNER) return undefined;
1671
+ const n = visibleDescriptor(e, readSource(e), t);
1672
+ if (n === undefined) return undefined;
1545
1673
  // Array targets carry a real non-configurable `length` the proxy
1546
1674
  // invariant forces us to report faithfully; everything else reports
1547
1675
  // configurable via target indirection (core R51).
1548
- if (!(t === "length" && Array.isArray(e))) r.configurable = true;
1549
- return r;
1676
+ if (!(t === "length" && Array.isArray(e))) n.configurable = true;
1677
+ return n;
1550
1678
  },
1551
1679
  set(e, t, n) {
1552
1680
  // Writes require the target's draft scope OR the projection write
@@ -1564,22 +1692,29 @@ const traps = {
1564
1692
  // passes through by reference (#2932; markRawOne skips proxies), while
1565
1693
  // deep stores unwrap to raw backings.
1566
1694
  const o = e.s ? n : unwrapValue(n);
1567
- const f = ensurePB(e);
1695
+ const l = ensurePB(e);
1568
1696
  pendingNotify.add(e);
1569
1697
  // Array length writes implicitly delete indices — the written-keys bound
1570
1698
  // can't see them, so poison to the full scan for this batch. Index
1571
1699
  // writes implicitly GROW length, so arrays always record it alongside.
1572
- if (Array.isArray(f)) {
1700
+ if (Array.isArray(l)) {
1573
1701
  if (t === "length") e.wk = WK_ALL; else if (e.wk !== WK_ALL) {
1574
1702
  const n = e.wk ??= new Set;
1575
1703
  n.add(t);
1576
1704
  n.add("length");
1577
1705
  }
1578
- } else if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1706
+ } else {
1707
+ if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1708
+ // Live own-key estimate for the overlay/clone choice (#3360): `in`
1709
+ // sees through an overlay to the committed keys, so this counts keys
1710
+ // NEW to the container. Deletes are not un-counted (a stale high
1711
+ // count only picks the overlay a little early).
1712
+ if (!(t in l)) e.kc++;
1713
+ }
1579
1714
  // Own data keys literally named "prototype"/"constructor" land as data —
1580
1715
  // defineProperty sidesteps a proto-chain setter named the same.
1581
1716
  if (UNSAFE_KEYS.has(t)) {
1582
- Object.defineProperty(f, t, {
1717
+ Object.defineProperty(l, t, {
1583
1718
  value: o,
1584
1719
  writable: true,
1585
1720
  enumerable: true,
@@ -1590,15 +1725,17 @@ const traps = {
1590
1725
  }
1591
1726
  // Overlay first-write DEFINES the own key: assignment through the proto
1592
1727
  // chain would reject on a non-writable committed property (the clone
1593
- // path normalized descriptors for exactly this — R51 parity).
1594
- if (e.ovl && !hasOwn.call(f, t)) {
1595
- Object.defineProperty(f, t, {
1728
+ // path normalized descriptors for exactly this — R51 parity). A
1729
+ // plain-data-graded backing (sc 2, owned: every slot writable) takes the
1730
+ // bare assignment — it lands as an own key on the overlay all the same.
1731
+ if (e.ovl && e.sc !== 2 && !hasOwn.call(l, t)) {
1732
+ Object.defineProperty(l, t, {
1596
1733
  value: o,
1597
1734
  writable: true,
1598
1735
  enumerable: true,
1599
1736
  configurable: true
1600
1737
  });
1601
- } else f[t] = o;
1738
+ } else l[t] = o;
1602
1739
  if (e.del !== null) e.del.delete(t);
1603
1740
  // Shallow ingest: written records are sticky raw-marked (one entity is
1604
1741
  // never both deep-wrapped and raw — R41/#2932, shared invariant).
@@ -1620,6 +1757,10 @@ const traps = {
1620
1757
  value: unwrapValue(n.value)
1621
1758
  };
1622
1759
  const o = ensurePB(e);
1760
+ // A non-default data descriptor (or an accessor) leaves the plain-data
1761
+ // grade: the key reaches the committed backing as defined, so the spread
1762
+ // clone and bare-assignment paths no longer describe it.
1763
+ if (e.a || !(n.enumerable && n.writable && n.configurable)) e.sc = 1;
1623
1764
  pendingNotify.add(e);
1624
1765
  if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1625
1766
  Object.defineProperty(o, t, n);
@@ -1681,6 +1822,7 @@ const traps = {
1681
1822
  adoptPB(r, unwrapValue(o));
1682
1823
  }
1683
1824
  }
1825
+ if (writing === 0 && heldAdoptions.size) stageHeldAdoptions();
1684
1826
  }
1685
1827
 
1686
1828
  // Affects integration: the legacy affects machinery reads next targets
@@ -1732,6 +1874,78 @@ function createStoreNext(e, t = false) {
1732
1874
  return t !== undefined && t.fam?.opt === true;
1733
1875
  }
1734
1876
 
1877
+ // ---------------------------------------------------------------------------
1878
+ // visibility: what a reader sees on a record — ONE rule for the ownKeys /
1879
+ // getOwnPropertyDescriptor traps and the deep() walk (#3323). The walk used
1880
+ // to re-derive the trap rules over raw backings and missed each new one in
1881
+ // turn (the #3044 overlay merge, then #3323's chain, then optimistic presence
1882
+ // and value overrides); sharing the body makes parity structural.
1883
+ /** Keys visible on `target` served from `src` (= readSource(target)): the
1884
+ * pending-overlay merge (#3044) and, on optimistic families, presence-node
1885
+ * overrides (§6, FINDING-2's fix). Draft reads before the first write overlay
1886
+ * too (pb, once created, is seeded with the view). Authoritative-view reads
1887
+ * (until()'s predicate, truth-author drafts) skip the overlay. */ function visibleKeys(e, t) {
1888
+ let n;
1889
+ if (e.ovl && t === e.pb) {
1890
+ // Overlay merge (#3044): committed keys in their order, then this
1891
+ // batch's NEW keys, minus deletes.
1892
+ n = Reflect.ownKeys(e.v);
1893
+ const r = e.del;
1894
+ if (r !== null && r.size !== 0) n = n.filter(e => !r.has(e));
1895
+ for (const r of Reflect.ownKeys(t)) {
1896
+ if (!hasOwn.call(e.v, r)) n.push(r);
1897
+ }
1898
+ } else n = Reflect.ownKeys(t);
1899
+ // Drop the $OWNER stamp (owned backings carry it as an own enumerable
1900
+ // symbol). Symbols enumerate last, so the scan stops at the first string.
1901
+ for (let e = n.length - 1; e >= 0 && typeof n[e] === "symbol"; e--) {
1902
+ if (n[e] === $OWNER) {
1903
+ n.splice(e, 1);
1904
+ break;
1905
+ }
1906
+ }
1907
+ if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || draftSeesOverrides(e))) {
1908
+ let t = null;
1909
+ for (const r of Reflect.ownKeys(e.h)) {
1910
+ const i = e.h[r];
1911
+ if (!hasActiveOverride(i)) continue;
1912
+ t ??= new Set(n);
1913
+ if (unwrapOverride(i.o?.be)) t.add(r); else t.delete(r);
1914
+ }
1915
+ if (t !== null) return [ ...t ];
1916
+ }
1917
+ return n;
1918
+ }
1919
+
1920
+ /** The data/accessor descriptor visible for `key` on `target` served from
1921
+ * `src`: overlay (#3044 — unwritten keys live on the committed backing,
1922
+ * deleted keys are absent) and optimistic presence overrides (an opt delete
1923
+ * hides the key; an opt add synthesizes a data descriptor from the value
1924
+ * node). `configurable` is the trap's concern (proxy invariant). */ function visibleDescriptor(e, t, n) {
1925
+ let r = Object.getOwnPropertyDescriptor(t, n);
1926
+ if (e.ovl && t === e.pb) {
1927
+ if (e.del !== null && e.del.has(n)) return undefined;
1928
+ if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, n);
1929
+ }
1930
+ if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
1931
+ const t = e.h?.[n];
1932
+ if (t !== undefined && hasActiveOverride(t)) {
1933
+ if (!unwrapOverride(t.o?.be)) return undefined;
1934
+ // opt delete
1935
+ if (r === undefined) {
1936
+ const t = e.n?.[n];
1937
+ return {
1938
+ value: t !== undefined ? nodeValue(t, undefined) : undefined,
1939
+ writable: true,
1940
+ enumerable: true,
1941
+ configurable: true
1942
+ };
1943
+ }
1944
+ }
1945
+ }
1946
+ return r;
1947
+ }
1948
+
1735
1949
  /** Tracking deep snapshot (`deep()` for next targets): subscribes to the
1736
1950
  * key-set and deep-witness node at every reachable level, then returns the
1737
1951
  * plain view. Shared references and cycles handled via the visited set. */ function deepNext(e) {
@@ -1743,50 +1957,69 @@ function createStoreNext(e, t = false) {
1743
1957
  // in per-key nodes, and it walks TARGETS directly — no per-child proxy
1744
1958
  // round-trip (wrapNext → proxy → $TARGET trap) on the re-walk every
1745
1959
  // effect run performs.
1746
- const walkT = e => {
1960
+ // Resolve `child` (a raw or a stored proxy found under `t`) to the target
1961
+ // that `t`'s family serves for it — created on first visit, as the get trap
1962
+ // would. Undefined = leaf (unwrappable or raw-marked).
1963
+ const childTarget = (e, t, n) => {
1964
+ let r = lookupTarget(t, e.fam);
1965
+ if (r === undefined) {
1966
+ if (!isWrappable(t)) return undefined;
1967
+ wrapNext(t, e, n);
1968
+ // Stored proxies (chained slots) that this family passes through
1969
+ // resolve to their own target.
1970
+ r = lookupTarget(t, e.fam) ?? t[$TARGET];
1971
+ }
1972
+ return r;
1973
+ };
1974
+ const walkT = e => {
1747
1975
  const t = readSource(e);
1748
1976
  if (n.has(t)) return;
1749
1977
  n.add(t);
1750
1978
  read(getKeySetNode(e));
1751
1979
  read(getDeepNode(e));
1752
- const r = e.fam?.map ?? storeNextLookup;
1753
- // Overlay pending backings chain to the committed object (#3044): their
1754
- // OWN keys are only this batch's writes. A bare ownKeys walk mid-flush
1755
- // (effects recompute before the fold commits) missed every untouched
1756
- // child, so the re-subscribing effect dropped those records from its
1757
- // dependency set later child edits never notified it (#3283). Merge
1758
- // committed keys, minus deletes, exactly as the ownKeys trap does.
1759
- let i = Reflect.ownKeys(t);
1760
- if (e.ovl && t === e.pb) {
1761
- const t = Reflect.ownKeys(e.v);
1762
- const n = e.del;
1763
- const r = n !== null && n.size !== 0 ? t.filter(e => !n.has(e)) : t;
1764
- for (const t of i) {
1765
- if (!hasOwn.call(e.v, t)) r.push(t);
1766
- }
1767
- i = r;
1980
+ // Chained backing (§7b): the committed backing IS another store's proxy.
1981
+ // Writes to the inner store bump the INNER record's witnesses; this
1982
+ // target's k/dk hear only its own family's writes (optimistic overrides).
1983
+ // Read through the whole chain — the $TRACK trap's rule (#2864 / R21)
1984
+ // applied to deep() (#3323). From `t.v`, not `src`: a pending backing is a
1985
+ // raw clone and would hide the chain. Non-chained targets pay one flag.
1986
+ for (let t = e; t.ch; ) {
1987
+ t = t.v[$TARGET];
1988
+ read(getKeySetNode(t));
1989
+ read(getDeepNode(t));
1768
1990
  }
1769
- for (const n of i) {
1770
- const i = Object.getOwnPropertyDescriptor(t, n) ?? Object.getOwnPropertyDescriptor(e.v, n);
1991
+ // Keys and children exactly as the traps serve them — the overlay merge
1992
+ // (#3044/#3283: a bare ownKeys over a pending backing mid-flush dropped
1993
+ // every untouched child from the re-subscribing effect's dependencies)
1994
+ // and optimistic presence overrides (a row added under a held action
1995
+ // lives in `h`/`n`, not the committed backing; the walk never reached its
1996
+ // record, so deep() was deaf to every write on it until settle).
1997
+ const r = e.fam?.opt === true;
1998
+ for (const n of visibleKeys(e, t)) {
1999
+ const i = visibleDescriptor(e, t, n);
1771
2000
  if (i === undefined) continue;
1772
2001
  if (i.get || i.set) {
1773
2002
  e.a = true;
1774
2003
  continue;
1775
2004
  // accessors track through their own reads when invoked
1776
2005
  }
1777
- const o = i.value;
2006
+ let o = i.value;
2007
+ // An active value override on an optimistic node shadows the backing's
2008
+ // child (an optimistic replacement `d[i] = {...}`) — follow what reads
2009
+ // serve, as nodeValue's leading arm does.
2010
+ if (r) {
2011
+ const t = e.n?.[n];
2012
+ if (t !== undefined && hasActiveOverride(t) && !authoritativeServe()) o = unwrapOverride(t.o.be);
2013
+ }
1778
2014
  if (o === null || typeof o !== "object") continue;
1779
- // Stored proxies (chained slots) resolve through their own target;
1780
- // raw children through the family map, created on first visit.
1781
- let f = o[$TARGET] ?? r.get(o);
1782
- if (f === undefined) {
1783
- if (!isWrappable(o)) continue;
1784
- wrapNext(o, e, n);
1785
- f = r.get(o);
1786
- if (f === undefined) continue;
2015
+ // Through a chain the descriptor yields the INNERMOST raw: resolve it to
2016
+ // the inner family's proxy so this level wraps the chained `view[i]` the
2017
+ // get trap serves, never a fresh non-chained wrapper of the base raw.
2018
+ if (e.ch && o[$TARGET] === undefined) o = resolveChainedRaw(e, n, o);
2019
+ const l = childTarget(e, o, n);
2020
+ if (l === undefined) continue;
1787
2021
  // raw-marked: leaf by contract
1788
- }
1789
- walkT(f);
2022
+ walkT(l);
1790
2023
  }
1791
2024
  };
1792
2025
  walkT(t);
@@ -1816,24 +2049,35 @@ function snapshotWalk(e, t, n) {
1816
2049
  // encountered and compose their views over the resolved base, innermost
1817
2050
  // outward.
1818
2051
  let i = null;
1819
- for (;;) {
1820
- let e = r?.[$TARGET]?.v !== undefined ? r[$TARGET] : undefined;
1821
- if (e === undefined && n !== null) e = n.map.get(r);
1822
- if (e === undefined) e = storeNextLookup.get(r);
1823
- if (e === undefined) break;
1824
- if (e.fam !== null) n = e.fam;
1825
- if (e.fam?.opt === true) (i ??= []).push(e);
2052
+ for (let e = true; ;e = false) {
2053
+ const t = r?.[$TARGET]?.v !== undefined;
2054
+ let o = t ? r[$TARGET] : undefined;
2055
+ if (o === undefined && n !== null) o = lookupTarget(r, n);
2056
+ if (o === undefined) o = lookupTarget(r, null);
2057
+ if (o === undefined) break;
2058
+ // Entering a level from a RAW below a chained family: the raw resolves to
2059
+ // the INNER store's target, but this family's wrapper for it — keyed by
2060
+ // the inner proxy, §7b — is where the outer overrides live. Start from
2061
+ // the wrapper so they compose (#3323: a nested optimistic write on a view
2062
+ // row was served by reads but missing from deep()/snapshot()). Entry only:
2063
+ // the descent then runs wrapper → inner → raw and terminates.
2064
+ if (e && !t && n !== null && o.fam !== n) {
2065
+ const e = n.map.get(o.px);
2066
+ if (e !== undefined) o = e;
2067
+ }
2068
+ if (o.fam !== null) n = o.fam;
2069
+ if (o.fam?.opt === true) (i ??= []).push(o);
1826
2070
  // The shared visibility decision (#3147): the speculative peek serves
1827
2071
  // pending staging, but a HELD landing is masked to committed exactly as
1828
2072
  // it is for per-key readers — the two families must answer alike while
1829
2073
  // a transaction holds store landings.
1830
- const t = pendingBackingVisible(e, true);
2074
+ const l = pendingBackingVisible(o, true);
1831
2075
  // Snapshot runs mid-flush (tracked memos execute before commit), so a
1832
2076
  // pending prototype overlay must present as a REAL merged container.
1833
- if (t && e.ovl) materializePB(e);
1834
- const o = t ? e.pb : e.v;
1835
- if (o === r) break;
1836
- r = o;
2077
+ if (l && o.ovl) materializePB(o);
2078
+ const f = l ? o.pb : o.v;
2079
+ if (f === r) break;
2080
+ r = f;
1837
2081
  }
1838
2082
  if (!isWrappable(r)) return r;
1839
2083
  // Optimistic families: compose the visible view; a composed view is a fresh
@@ -1845,15 +2089,15 @@ function snapshotWalk(e, t, n) {
1845
2089
  const i = t.get(r);
1846
2090
  if (i !== undefined) return i;
1847
2091
  const o = Array.isArray(e);
1848
- const f = o ? [] : Object.create(Object.getPrototypeOf(e));
1849
- t.set(r, f);
2092
+ const l = o ? [] : Object.create(Object.getPrototypeOf(e));
2093
+ t.set(r, l);
1850
2094
  for (const r of Reflect.ownKeys(e)) {
1851
- if (o && r === "length") continue;
2095
+ if (o && r === "length" || r === $OWNER) continue;
1852
2096
  const i = e[r];
1853
- f[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
2097
+ l[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
1854
2098
  }
1855
- if (o) f.length = e.length;
1856
- return f;
2099
+ if (o) l.length = e.length;
2100
+ return l;
1857
2101
  }
1858
2102
  }
1859
2103
  const o = t.get(r);
@@ -1862,22 +2106,22 @@ function snapshotWalk(e, t, n) {
1862
2106
  // subtrees "unmodified relative to source"): non-enumerable symbols are
1863
2107
  // excluded (recon-snap R29), and the copy registers BEFORE descent so
1864
2108
  // cycles keep identity (FINDING-3).
1865
- if (ownedRaw.has(r)) {
2109
+ if (isOwned(r)) {
1866
2110
  const e = Array.isArray(r);
1867
2111
  const i = e ? [] : Object.create(Object.getPrototypeOf(r));
1868
2112
  t.set(r, i);
1869
2113
  for (const o of Reflect.ownKeys(r)) {
1870
- if (e && o === "length") continue;
1871
- const f = Object.getOwnPropertyDescriptor(r, o);
1872
- if (typeof o === "symbol" && !f.enumerable) continue;
1873
- if (f.get || f.set) {
1874
- Object.defineProperty(i, o, f);
2114
+ if (e && o === "length" || o === $OWNER) continue;
2115
+ const l = Object.getOwnPropertyDescriptor(r, o);
2116
+ if (typeof o === "symbol" && !l.enumerable) continue;
2117
+ if (l.get || l.set) {
2118
+ Object.defineProperty(i, o, l);
1875
2119
  continue;
1876
2120
  }
1877
- const l = f.value;
1878
- const s = l !== null && typeof l === "object" ? snapshotWalk(l, t, n) : l;
1879
- if (f.enumerable && f.writable && f.configurable) i[o] = s; else Object.defineProperty(i, o, {
1880
- ...f,
2121
+ const f = l.value;
2122
+ const s = f !== null && typeof f === "object" ? snapshotWalk(f, t, n) : f;
2123
+ if (l.enumerable && l.writable && l.configurable) i[o] = s; else Object.defineProperty(i, o, {
2124
+ ...l,
1881
2125
  value: s
1882
2126
  });
1883
2127
  }
@@ -1887,22 +2131,22 @@ function snapshotWalk(e, t, n) {
1887
2131
  // UNOWNED (shared/user) subtrees keep identity unless a descendant
1888
2132
  // substituted; copy-on-substitution preserves the documented CoW contract.
1889
2133
  t.set(r, r);
1890
- let f = null;
2134
+ let l = null;
1891
2135
  for (const e of Reflect.ownKeys(r)) {
1892
2136
  const i = Object.getOwnPropertyDescriptor(r, e);
1893
2137
  if (!i || i.get || i.set) continue;
1894
2138
  const o = i.value;
1895
2139
  if (o === null || typeof o !== "object") continue;
1896
- const l = snapshotWalk(o, t, n);
1897
- if (l !== o) {
1898
- if (f === null) {
1899
- f = Array.isArray(r) ? [ ...r ] : Object.create(Object.getPrototypeOf(r), Object.getOwnPropertyDescriptors(r));
1900
- t.set(r, f);
2140
+ const f = snapshotWalk(o, t, n);
2141
+ if (f !== o) {
2142
+ if (l === null) {
2143
+ l = Array.isArray(r) ? [ ...r ] : Object.create(Object.getPrototypeOf(r), Object.getOwnPropertyDescriptors(r));
2144
+ t.set(r, l);
1901
2145
  }
1902
- f[e] = l;
2146
+ l[e] = f;
1903
2147
  }
1904
2148
  }
1905
- return f ?? r;
2149
+ return l ?? r;
1906
2150
  }
1907
2151
 
1908
- export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, runAuthoritative, snapshotNext, stagedTruthPB, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetsEqual, unwrapValue, wrapNext };
2152
+ export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, heldMaskView, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, runAuthoritative, snapshotNext, stagedTruthPB, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetsEqual, unwrapValue, wrapNext };