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

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 (121) hide show
  1. package/dist/dev-shared.js +2059 -3768
  2. package/dist/dev.attribution.js +237 -101
  3. package/dist/dev.js +7466 -8497
  4. package/dist/observe/affects.js +128 -104
  5. package/dist/observe/boundaries.js +492 -665
  6. package/dist/observe/core/action.js +63 -66
  7. package/dist/observe/core/async.js +243 -379
  8. package/dist/observe/core/attribution-queries.js +1 -1
  9. package/dist/observe/core/attribution.js +394 -253
  10. package/dist/observe/core/constants.js +120 -187
  11. package/dist/observe/core/context.js +3 -3
  12. package/dist/observe/core/core.js +874 -1440
  13. package/dist/observe/core/dev.js +3 -3
  14. package/dist/observe/core/effect.js +73 -84
  15. package/dist/observe/core/external.js +5 -5
  16. package/dist/observe/core/graph.js +62 -71
  17. package/dist/observe/core/heap.js +75 -98
  18. package/dist/observe/core/invariants.js +1 -34
  19. package/dist/observe/core/lanes.js +750 -181
  20. package/dist/observe/core/owner.js +80 -101
  21. package/dist/observe/core/scheduler.js +719 -1356
  22. package/dist/observe/core/verdict.js +314 -621
  23. package/dist/observe/flatten.js +79 -0
  24. package/dist/observe/index.js +15 -13
  25. package/dist/observe/map.js +232 -235
  26. package/dist/observe/reveal.js +212 -0
  27. package/dist/observe/signals.js +144 -216
  28. package/dist/observe/store/affects.js +142 -0
  29. package/dist/observe/store/hooks.js +13 -0
  30. package/dist/observe/store/index.js +27 -24
  31. package/dist/observe/store/optimistic.js +571 -0
  32. package/dist/observe/store/projection.js +308 -0
  33. package/dist/observe/store/reconcile.js +259 -0
  34. package/dist/observe/store/store.js +1988 -277
  35. package/dist/observe/store/storePath.js +1 -1
  36. package/dist/{prod/store/next → observe/store}/target.js +17 -18
  37. package/dist/observe/store/types.js +73 -0
  38. package/dist/observe/store/utils.js +1 -13
  39. package/dist/prod/affects.js +130 -106
  40. package/dist/prod/boundaries.js +476 -654
  41. package/dist/prod/core/action.js +71 -74
  42. package/dist/prod/core/async.js +306 -444
  43. package/dist/prod/core/constants.js +117 -186
  44. package/dist/prod/core/context.js +3 -3
  45. package/dist/prod/core/core.js +888 -1462
  46. package/dist/prod/core/effect.js +88 -99
  47. package/dist/prod/core/external.js +5 -5
  48. package/dist/prod/core/graph.js +62 -71
  49. package/dist/prod/core/heap.js +75 -98
  50. package/dist/prod/core/invariants.js +1 -32
  51. package/dist/prod/core/lanes.js +712 -181
  52. package/dist/prod/core/owner.js +75 -96
  53. package/dist/prod/core/scheduler.js +686 -1335
  54. package/dist/prod/core/verdict.js +309 -621
  55. package/dist/prod/flatten.js +77 -0
  56. package/dist/prod/index.js +15 -13
  57. package/dist/prod/map.js +142 -145
  58. package/dist/prod/reveal.js +212 -0
  59. package/dist/prod/signals.js +155 -227
  60. package/dist/prod/store/affects.js +142 -0
  61. package/dist/prod/store/hooks.js +13 -0
  62. package/dist/prod/store/index.js +26 -19
  63. package/dist/prod/store/optimistic.js +566 -0
  64. package/dist/prod/store/projection.js +301 -0
  65. package/dist/prod/store/reconcile.js +259 -0
  66. package/dist/prod/store/store.js +1883 -272
  67. package/dist/prod/store/storePath.js +1 -1
  68. package/dist/{observe/store/next → prod/store}/target.js +13 -17
  69. package/dist/prod/store/types.js +73 -0
  70. package/dist/prod/store/utils.js +1 -11
  71. package/dist/types/affects.d.ts +11 -30
  72. package/dist/types/boundaries.d.ts +66 -176
  73. package/dist/types/carved.d.ts +1 -0
  74. package/dist/types/core/action.d.ts +5 -6
  75. package/dist/types/core/async.d.ts +10 -5
  76. package/dist/types/core/attribution-hooks.d.ts +20 -1
  77. package/dist/types/core/attribution.d.ts +26 -11
  78. package/dist/types/core/constants.d.ts +130 -180
  79. package/dist/types/core/core.d.ts +134 -264
  80. package/dist/types/core/dev.d.ts +2 -2
  81. package/dist/types/core/effect.d.ts +0 -7
  82. package/dist/types/core/graph.d.ts +7 -1
  83. package/dist/types/core/heap.d.ts +5 -7
  84. package/dist/types/core/index.d.ts +5 -5
  85. package/dist/types/core/invariants.d.ts +0 -29
  86. package/dist/types/core/lanes.d.ts +74 -86
  87. package/dist/types/core/owner.d.ts +3 -3
  88. package/dist/types/core/scheduler.d.ts +255 -331
  89. package/dist/types/core/types.d.ts +45 -85
  90. package/dist/types/core/verdict.d.ts +3 -0
  91. package/dist/types/flatten.d.ts +8 -0
  92. package/dist/types/index.d.ts +6 -4
  93. package/dist/types/reveal.d.ts +19 -0
  94. package/dist/types/signals.d.ts +35 -91
  95. package/dist/types/store/affects.d.ts +12 -0
  96. package/dist/types/store/hooks.d.ts +48 -0
  97. package/dist/types/store/index.d.ts +34 -15
  98. package/dist/types/store/optimistic.d.ts +23 -0
  99. package/dist/types/store/projection.d.ts +18 -0
  100. package/dist/types/store/reconcile.d.ts +3 -0
  101. package/dist/types/store/store.d.ts +132 -145
  102. package/dist/types/store/storePath.d.ts +1 -1
  103. package/dist/types/store/target.d.ts +151 -0
  104. package/dist/types/store/types.d.ts +84 -0
  105. package/package.json +1 -1
  106. package/dist/observe/core/optimistic.js +0 -669
  107. package/dist/observe/store/next/optimistic.js +0 -717
  108. package/dist/observe/store/next/projection.js +0 -266
  109. package/dist/observe/store/next/reconcile.js +0 -375
  110. package/dist/observe/store/next/store.js +0 -2608
  111. package/dist/prod/core/optimistic.js +0 -658
  112. package/dist/prod/store/next/optimistic.js +0 -710
  113. package/dist/prod/store/next/projection.js +0 -257
  114. package/dist/prod/store/next/reconcile.js +0 -373
  115. package/dist/prod/store/next/store.js +0 -2484
  116. package/dist/types/core/optimistic.d.ts +0 -6
  117. package/dist/types/store/next/optimistic.d.ts +0 -25
  118. package/dist/types/store/next/projection.d.ts +0 -27
  119. package/dist/types/store/next/reconcile.d.ts +0 -10
  120. package/dist/types/store/next/store.d.ts +0 -145
  121. package/dist/types/store/next/target.d.ts +0 -196
@@ -1,2484 +0,0 @@
1
- import { unwrapOverride, $REFRESH, CONFIG_CHILDREN_FORBIDDEN, CONFIG_OWNED_WRITE, NOT_PENDING, CONFIG_OPTIMISTIC, STATUS_UNINITIALIZED, STATUS_ERROR, REACTIVE_RECOMPUTING_DEPS, CONFIG_OVERRIDE_SUPERSEDED, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
2
-
3
- import { setSlotUnobserved, setSignal, isEqual, rederiveHeld, suppressComputedRecompute, read, hasActiveOverride, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, unlinkFirewallChild, ext, visibleOverride, ownsHold, serve, setLatestReadActive, prepareComputed, slotSignal, stale, recordStaleReplay, enterStagedRead, context, currentOptimisticLane, heldDerivation } from "../../core/core.js";
4
-
5
- import { projectionWriteActive, setProjectionWriteActive, activeTransition, schedule, currentTransition, deferSlotRelease, setStoreCommitHook } from "../../core/scheduler.js";
6
-
7
- import "../../core/invariants.js";
8
-
9
- import { getObserver, getOwner } from "../../core/owner.js";
10
-
11
- import { $TARGET, isWrappable, markRawIngest, $PROXY, getWriteOverride, markRawOne, $RECORD, witnessAffectsMark, $TRACK, affectsScopesLive, inheritAffectsMarks, $AFFECTS, rawValuesUsed, isRawValue, setNextAffectsNodeResolver } from "../store.js";
12
-
13
- import { lookupTarget, optHooks, storeNextLookup, $OWNER, isOwned, markDescendants, devAssertNeverUserMutation } from "./target.js";
14
-
15
- /**
16
- * Store rewrite — increment 2: plain deep stores with pending-backing writes.
17
- * Contract: INTERNALS-STORE-STATE.md.
18
- *
19
- * Write model (RUL-1, unified): the first draft write to a target creates its
20
- * pending backing `pb` — a descriptor-preserving CoW clone. The draft mutates
21
- * `pb` natively (array methods, defineProperty, deletes all just work).
22
- * Reads: drafts and owner-context reads see `pb`; context-free reads see the
23
- * committed `b` until flush. At flush commit (core's storeCommitHook), each
24
- * written target folds: diff old `b` vs new backing notifies exactly the
25
- * changed keys through equality-gated nodes, then `b` becomes the new
26
- * backing. Setter return-value replacement parks the UNOWNED incoming object
27
- * in `pb` — adoption: fold swaps it in, ownership resets (2026-08-16c).
28
- *
29
- * Nodes carry no pending state — they are pure subscription points; `pb` is
30
- * the pending home. Laziness: a written target with no subscriptions folds as
31
- * a pointer swap with zero node work.
32
- */
33
- // ---------------------------------------------------------------------------
34
- // wrap / dedupe
35
- /** Pre-shaped constructor for OBJECT proxy targets: V8 tips a bare `{}` into
36
- * dictionary mode once ~19 named properties are assigned onto it (the #3044
37
- * `ovl`/`del` fields crossed that line — every trap's field read became a
38
- * hash lookup, a 15% deep-dbmon tick regression). Declaring every field in a
39
- * constructor pre-allocates in-object slots so the map stays fast, with
40
- * headroom for future fields. The prototype is reset to `Object.prototype`
41
- * so proxy-forwarded semantics (getPrototypeOf, constructor) are exactly a
42
- * plain object's. Array targets keep the bare-`[]` path — they must carry
43
- * the array exotic class for `Array.isArray(proxy)`.
44
- *
45
- * ARRAY SHAPE RULE: arrays normalize their named properties to dictionary
46
- * mode as the count grows (V8 13.x: counts ≡ 0 mod 3 from 18 up), so the
47
- * target's named field count is capped at 20 — any future write-side state
48
- * beyond `wk` must ride an extension object (see target.ts), never new
49
- * named fields here. */
50
- function TargetShape() {
51
- this.v = undefined;
52
- this.ch = undefined;
53
- this.pb = undefined;
54
- this.n = undefined;
55
- this.h = undefined;
56
- this.k = undefined;
57
- this.dk = undefined;
58
- this.u = undefined;
59
- this.pk = undefined;
60
- this.px = undefined;
61
- this.d = undefined;
62
- this.a = undefined;
63
- this.sc = undefined;
64
- this.kc = undefined;
65
- this.nc = undefined;
66
- this.ab = undefined;
67
- this.fam = undefined;
68
- this.s = undefined;
69
- this.ovl = undefined;
70
- this.del = undefined;
71
- this.wk = undefined;
72
- this.hv = undefined;
73
- this.ht = undefined;
74
- }
75
-
76
- TargetShape.prototype = Object.prototype;
77
-
78
- function createTarget(e, t, n, r = t?.fam ?? null) {
79
- // The proxy target carries the array exotic class when the value is an
80
- // array, so Array.isArray(proxy) is true; the fields live on it directly.
81
- // Direct field assignment in one fixed order (no Object.assign literal
82
- // copy): every target shares a hidden-class transition chain — createTarget
83
- // was the #2 store cost in the uibench creation profile.
84
- const i = Array.isArray(e) ? [] : new TargetShape;
85
- i.v = e;
86
- // Chained-backing flag (backing IS another store's proxy, §7b) — cached so
87
- // the hot read path never does a per-read symbol lookup on the backing.
88
- i.ch = e[$TARGET] !== undefined;
89
- i.pb = null;
90
- i.n = null;
91
- i.h = null;
92
- i.k = null;
93
- i.dk = null;
94
- i.wk = null;
95
- i.u = t;
96
- i.pk = n;
97
- i.px = null;
98
- i.d = false;
99
- i.a = false;
100
- i.sc = 0;
101
- i.kc = 0;
102
- i.nc = 0;
103
- i.ab = null;
104
- i.fam = r;
105
- i.s = false;
106
- i.ovl = false;
107
- i.del = null;
108
- i.hv = null;
109
- i.ht = null;
110
- i.px = new Proxy(i, traps);
111
- // Legacy interop: shared machinery (affects walks, wrap dedupe) reads the
112
- // proxy off looked-up targets as a field.
113
- i[$PROXY] = i.px;
114
- (r?.map ?? storeNextLookup).set(e, i);
115
- return i;
116
- }
117
-
118
- function wrapNext(e, t = null, n = null, r = t?.fam ?? null) {
119
- // markRaw'd values never wrap through ANY store (R42; sticky raw-marking
120
- // is one half of the never-both-wrapped-and-raw invariant, RUL-12).
121
- if (rawValuesUsed && isRawValue(e)) return e;
122
- const i = lookupTarget(e, r);
123
- if (i !== undefined) return i.px;
124
- const l = e[$TARGET];
125
- if (l !== undefined && l.px === e) {
126
- // Foreign-family proxies re-wrap into THIS family (writes stay isolated);
127
- // same-family and plain-store proxies pass through.
128
- if (r === null || l.fam === r) return e;
129
- return createTarget(e, t, n, r).px;
130
- }
131
- return createTarget(e, t, n, r).px;
132
- }
133
-
134
- /** Unwrap our own proxies to their current backing; leave everything else. */ function unwrapValue(e) {
135
- if (e == null || typeof e !== "object") return e;
136
- const t = e[$TARGET];
137
- if (t !== undefined && t.px === e && t.v !== undefined) {
138
- // A draft escaping into other storage must be a REAL container that
139
- // becomes this target's committed backing at fold (the shared-raw
140
- // contract) — a prototype overlay is neither.
141
- if (t.ovl) materializePB(t);
142
- return t.pb ?? t.v;
143
- }
144
- return e;
145
- }
146
-
147
- // ---------------------------------------------------------------------------
148
- // nodes: pure subscription points (values used only for equality gating)
149
- // Shared slot-node equality (create-floor diet): ONE function for every
150
- // store node — `this` is the node (method-call convention at every _equals
151
- // site), `_host` is the baked-in target backref. Logical-slot equality:
152
- // values resolving to the same child target are the same slot
153
- // (privatization/adoption swap raw identity without changing the logical
154
- // value — only changed leaves notify, R9).
155
- const slotNodeEquals = function(e, t) {
156
- return isEqual(e, t) || sameLogicalSlot(this.Bn, e, t);
157
- };
158
-
159
- // Shared slot-node unobserved handler (create-floor diet): registered once;
160
- // the core sweep dispatches CONFIG_SLOT_NODE nodes here instead of holding a
161
- // per-node closure in a per-node NodeExtension.
162
- setSlotUnobserved(e => {
163
- // A live affects() mark keeps the node addressable (sweep parity).
164
- if (e.o?.t) return;
165
- // An active override or a staged write is state only the node holds (an
166
- // optimistic signal keeps its override whether or not anything reads it —
167
- // store parity, posture-store-parity S7): defer the release to the flush
168
- // that resolves it (the scheduler's transient-node sweep).
169
- if (hasActiveOverride(e) || e._e !== NOT_PENDING) return deferSlotRelease(e);
170
- const t = e.Bn;
171
- const n = e.Kn;
172
- if (t.n && t.n[n] === e) {
173
- delete t.n[n];
174
- t.nc--;
175
- // Projection leaves also leave the firewall child chain (#3351): a
176
- // dropped node is unreachable through the store — a fresh read makes a
177
- // fresh node — so the chain would only retain it and its last value.
178
- unlinkFirewallChild(e);
179
- }
180
- });
181
-
182
- function getNode(e, t, n,
183
- // First-read dedupe (create-floor slice 2): the get trap probes
184
- // accessor-ness right before creating the node — pass the verdict through
185
- // so creation skips the second descriptor scan. -1 = unknown (other
186
- // callers), 0/1 = probed.
187
- r = -1) {
188
- const i = e.n ??= Object.create(null);
189
- let l = i[t];
190
- if (l === undefined) {
191
- // Born holding (#3330 store twin): a key first read while a live
192
- // transaction holds an ADOPTION on this target (adoptPB's `ht`) is
193
- // created as if it had existed when the hold was notified — the adopted
194
- // value already swapped into `v`, so committed is the held view
195
- // `hv[key]` and the adopted `v[key]` is staged under the transaction
196
- // (stageHeldKey). Its held-adoption notification (stageHeldAdoptions)
197
- // ran before the node existed and the drain has nothing left to say.
198
- // Without this the node was born from whichever view its first reader
199
- // saw and never learned the other. Two kinds of hold, one rule (#3336):
200
- // - a held FOLD (pb): a setter's write to an unobserved key landed only
201
- // in the pending backing; committed is `v[key]`, staged is `pb[key]`
202
- // (undefined for a deleted key);
203
- // - a held ADOPTION (ht): the adopted value already swapped into `v`;
204
- // committed is the held view `hv[key]`, staged is `v[key]`.
205
- const o = heldFoldTransition(e);
206
- let s = heldAdoptionTransition(e);
207
- // A key the adoption left unchanged is not born holding (#3706).
208
- if (s !== null && !heldKey(e, t)) s = null;
209
- if (s !== null) n = e.hv[t]; else if ((s = o) !== null) n = e.v[t];
210
- // Create-floor diet: slotSignal bakes the whole node into one literal —
211
- // no options object, no equals/unobserved closures, no NodeExtension,
212
- // no post-construction expandos (acc + the wrap cache px/pxv are
213
- // pre-shaped fields: the proxy last served for this key and the raw it
214
- // wrapped — one pointer compare replaces the per-read WeakMap lookup in
215
- // wrapNext). ownedWrite rides the literal's config: the setter carries
216
- // the owned-scope write guard; node-level setSignals are internal
217
- // notification machinery. Projection nodes carry the projection
218
- // computed as their firewall: reads through them link the derive's
219
- // status/lifecycle (§7b).
220
- const f = l = slotSignal(n, slotNodeEquals, e, t,
221
- // Accessor-ness resolved ONCE per node (no per-object descriptor
222
- // scan on reads): accessor keys serve through Reflect.get with the
223
- // proxy receiver.
224
- r === -1 ? isOwnAccessor(e.pb ?? e.v, t) : r === 1, e.fam?.node ?? undefined);
225
- // Optimistic families: arm the override slot — setSignal routes armed
226
- // nodes through the core engine (lanes, ownership, reverts all native).
227
- if (e.fam?.opt) {
228
- ext(f).Fe = NOT_PENDING;
229
- f.C |= CONFIG_OPTIMISTIC;
230
- }
231
- // A node born inside a live mark's identity scope inherits the mark
232
- // (the declaration walk could only cover nodes existing then).
233
- if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(f, e.v, t);
234
- if (s !== null) stageHeldKey(f, o !== null ? e.del !== null && e.del.has(t) ? undefined : e.pb[t] : e.v[t], s);
235
- i[t] = l;
236
- e.nc++;
237
- markDescendants(e);
238
- }
239
- return l;
240
- }
241
-
242
- /** The live transaction holding an adoption on `target` (adoptPB's `ht`;
243
- * a latest()-pull PLAIN_HOLD is not a transaction), else null. */ function heldAdoptionTransition(e) {
244
- if (e.ht === null || e.ht === PLAIN_HOLD || heldMaskView(e) === null) return null;
245
- const t = currentTransition(e.ht);
246
- return t.Rn === false ? t : null;
247
- }
248
-
249
- /**
250
- * Materialization under a hold. A node created by a first tracked read while
251
- * a transaction holds this target must carry the same state a node that
252
- * existed when the hold was notified carries — a transaction-stamped
253
- * `_pendingValue` that core read() serves by ITS rules (a stale render reader
254
- * of a foreign transaction's write sees committed, an owner-context reader
255
- * inside the transaction sees the write) — or the reader's answer depends on
256
- * whether some OTHER reader had materialized the key before the hold. Stage
257
- * `nv` (the held value for the key — see getNode for which view it comes
258
- * from) as the holding transaction's write — directly, not through
259
- * setSignal: it is not a new write (it staged with the adoption's batch) and
260
- * it walks no subscriber (the node has none yet). Transition-stamped now, as
261
- * `runFolded` does — no parked-flush pass will stamp it.
262
- */
263
- /** The live transaction holding `target`'s pending backing (the #3089
264
- * write-time stamp, resolved through merges), else null. */ function liveFoldTransition(e) {
265
- if (e.pb === null) return null;
266
- const t = foldBatches.get(e);
267
- if (t === undefined) return null;
268
- const n = currentTransition(t);
269
- return n.Rn === false ? n : null;
270
- }
271
-
272
- /** liveFoldTransition for node materialization (stageHeldKey). Inside the
273
- * draft the backing is the setter's working copy, not a flushed hold — its
274
- * nodes take their writes at setter exit (notifyWrites). Optimistic families
275
- * hold at the backing: tentative writes are node overrides over a discarded
276
- * clone, and a truth-staged landing is served through the fold's own hold
277
- * (heldTruthMasked for lane passes, pendingBackingVisible's hold arms for
278
- * the rest) — neither is a plain staged write to mirror. Chained
279
- * backings serve the inner store's live value, never a node value. */ function heldFoldTransition(e) {
280
- if (e.ch || e.fam?.opt === true || inDraft(e)) return null;
281
- return liveFoldTransition(e);
282
- }
283
-
284
- /**
285
- * Rule 1 at the backing, the hold half (core serve()'s stale-of-foreign and
286
- * A29 arms, for a container instead of a node): given the transaction
287
- * holding what a reader `c` (a pass; callers serve context-free readers the
288
- * committed container themselves) is about to be served, is the STAGED
289
- * container its to see? Its own hold, or none — yes. A foreign hold — a
290
- * stale pass (render effect) keeps the committed frame and is recorded for
291
- * replay at the hold's commit (recordStaleReplay, A15 / A26); a deriving
292
- * pass takes the staged world and enters the transaction (enterStagedRead,
293
- * A29). Shared by both hold kinds: a setter's fold (committed `v`, staged
294
- * `pb`, the write-time stamp) and an adoption under a transaction
295
- * (committed = the held view `hv`, staged = the adopted `v`, #3074).
296
- */ function holdVisible(e, t) {
297
- if (e === null || ownsHold(e)) return true;
298
- if (stale) {
299
- recordStaleReplay(e, t);
300
- return false;
301
- }
302
- enterStagedRead(null, e);
303
- return true;
304
- }
305
-
306
- // #3706: the keys an adoption under a live hold changed against the held
307
- // view — the adoption twin of `wk` (#3688), keyed on the target. adoptPB
308
- // records `[adopted object]`; the keys are diffed against IT, once, on the
309
- // first held read (its children are re-pointed by then), and replace the
310
- // entry. Never against the live backing — a mainline write during the hold
311
- // replaces that, and its key is not the adoption's. WK_ALL holds the whole
312
- // container.
313
- const heldKeys = new WeakMap;
314
-
315
- function heldKey(e, t) {
316
- let n = heldKeys.get(e);
317
- if (Array.isArray(n)) heldKeys.set(e, n = adoptionChangedKeys(e, n[0]));
318
- return n === WK_ALL || n.has(t);
319
- }
320
-
321
- function adoptionChangedKeys(e, t) {
322
- const n = e.hv;
323
- // A chained backing, an optimistic family, a chained held view, a swapped
324
- // or non-plain prototype (inherited accessors read through `this`): whole.
325
- if (t[$TARGET] !== undefined || e.fam?.opt === true || n[$TARGET] !== undefined || Object.getPrototypeOf(n) !== Object.getPrototypeOf(t) || !plainProto(t)) return WK_ALL;
326
- const r = new Set;
327
- // Accessors are never invoked: they and a flipped enumerability are changes.
328
- for (const i of Reflect.ownKeys(n)) if (!hasOwn.call(t, i) || isOwnAccessor(n, i) || isOwnAccessor(t, i) || propertyIsEnumerable.call(n, i) !== propertyIsEnumerable.call(t, i) || !(isEqual(n[i], t[i]) || sameLogicalSlot(e, n[i], t[i]))) r.add(i);
329
- for (const e of Reflect.ownKeys(t)) if (!hasOwn.call(n, e)) r.add(e);
330
- return r;
331
- }
332
-
333
- function stageHeldKey(e, t, n) {
334
- if (slotNodeEquals.call(e, e.ce, t)) return;
335
- e._e = t;
336
- e.we = n;
337
- n.Gn.push(e);
338
- }
339
-
340
- function sameLogicalSlot(e, t, n) {
341
- if (t === null || typeof t !== "object" || n === null || typeof n !== "object") return false;
342
- const r = lookupTarget(t, e.fam);
343
- return r !== undefined && r === lookupTarget(n, e.fam);
344
- }
345
-
346
- function getHasNode(e, t, n) {
347
- const r = e.h ??= Object.create(null);
348
- let i = r[t];
349
- if (i === undefined) {
350
- const l = i = signal(n, {
351
- equals: isEqual,
352
- unobserved() {
353
- if (l.o?.t) return;
354
- // The structural twin of the value slot's rule (setSlotUnobserved,
355
- // S7): an optimistic add/delete lives on this presence node as its
356
- // override — releasing it with the override on would let `in`,
357
- // `Object.keys` and descriptors fall back to committed structure
358
- // while the action is live. Defer to the flush that resolves it.
359
- if (hasActiveOverride(l) || l._e !== NOT_PENDING) return deferSlotRelease(l);
360
- if (e.h && e.h[t] === l) {
361
- delete e.h[t];
362
- unlinkFirewallChild(l);
363
- }
364
- }
365
- }, e.fam?.node ?? undefined);
366
- l.C |= CONFIG_OWNED_WRITE;
367
- if (e.fam?.opt) {
368
- ext(l).Fe = NOT_PENDING;
369
- l.C |= CONFIG_OPTIMISTIC;
370
- }
371
- if (affectsScopesLive()) inheritAffectsMarks(l, e.v, t);
372
- r[t] = i;
373
- markDescendants(e);
374
- }
375
- return i;
376
- }
377
-
378
- /** Did `obs` link `target`'s key-set node in its CURRENT pass? The mirror of
379
- * link()'s O(1) repeat-touch check: the node's newest subscriber link is
380
- * this observer's, and — during a recompute — carries this pass's dep
381
- * generation (a link left from a previous pass is stale: the observer may
382
- * not re-read the key set this time). No dep-list scan, no allocation. A
383
- * probe observer (isPending()/latest() sentinel) never links, so it always
384
- * falls through to the presence read. */ function observerHoldsKeySet(e, t) {
385
- const n = e.k;
386
- if (n === null) return false;
387
- const r = n.mn;
388
- return r !== null && r.ge === t && (!(t.oe & REACTIVE_RECOMPUTING_DEPS) || r.Be === t.Ye);
389
- }
390
-
391
- function getKeySetNode(e) {
392
- let t = e.k;
393
- if (t === null) {
394
- const n = t = signal(0, {
395
- equals: false,
396
- unobserved() {
397
- if (e.k === n) {
398
- e.k = null;
399
- unlinkFirewallChild(n);
400
- }
401
- }
402
- }, e.fam?.node ?? undefined);
403
- n.C |= CONFIG_OWNED_WRITE;
404
- if (e.fam?.opt) {
405
- ext(n).Fe = NOT_PENDING;
406
- n.C |= CONFIG_OPTIMISTIC;
407
- }
408
- e.k = t;
409
- markDescendants(e);
410
- }
411
- return t;
412
- }
413
-
414
- function getDeepNode(e) {
415
- let t = e.dk;
416
- if (t === null) {
417
- const n = t = signal(0, {
418
- equals: false,
419
- unobserved() {
420
- if (e.dk === n) {
421
- e.dk = null;
422
- unlinkFirewallChild(n);
423
- }
424
- }
425
- }, e.fam?.node ?? undefined);
426
- n.C |= CONFIG_OWNED_WRITE;
427
- if (e.fam?.opt) {
428
- ext(n).Fe = NOT_PENDING;
429
- n.C |= CONFIG_OPTIMISTIC;
430
- }
431
- if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
432
- e.dk = t;
433
- markDescendants(e);
434
- }
435
- return t;
436
- }
437
-
438
- /** Deep-witness bump: any value/shape change on a record with a live deep()
439
- * subscriber notifies it. One null check when unused. */ function bumpDeep(e) {
440
- if (e.dk !== null) setSignal(e.dk, 1);
441
- }
442
-
443
- // ---------------------------------------------------------------------------
444
- // pending backing + fold (the single mutation point)
445
- /** target → committed backing at batch start (the fold diff's old side). */ const foldOlds = new Map;
446
-
447
- let hookInstalled = false;
448
-
449
- /** Shallow-clone `source` as a backing OWNED by `t` (stamped `$OWNER`, see
450
- * target.ts). Callers always pass their own committed backing as `source`. */ function cloneRaw(e, t) {
451
- // Plain-data fast path (#3360): a scanned `Object.prototype` container
452
- // whose own keys are all enumerable data clones by spread — the same
453
- // result as the descriptor walk below (own enumerable string+symbol keys,
454
- // normalized writable+configurable), at ~1/50th the cost.
455
- t.sc || scanAccessorsOnce(t);
456
- if (t.sc === 2) {
457
- const n = {
458
- ...e
459
- };
460
- n[$OWNER] = t;
461
- return n;
462
- }
463
- // Descriptor-preserving shallow clone (R29: installed getters stay live;
464
- // ruled 2026-08-17: frozen sources clone unfrozen — theirs stays frozen).
465
- // Data descriptors normalize to writable+configurable (the clone is OURS to
466
- // mutate — R51's "source-non-configurable is writable through the store");
467
- // enumerability and accessors are preserved. The scan doubles as the
468
- // accessor-flag detector (free — we're enumerating descriptors anyway).
469
- const n = Object.getOwnPropertyDescriptors(e);
470
- for (const r of Reflect.ownKeys(n)) {
471
- const i = n[r];
472
- if (r === "length" && Array.isArray(e)) continue;
473
- i.configurable = true;
474
- if (!i.get && !i.set) i.writable = true; else t.a = true;
475
- }
476
- const r = Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
477
- r[$OWNER] = t;
478
- return r;
479
- }
480
-
481
- /** Shallow copy of a WIDE plain-data (sc 2) backing for the overlay rebuild
482
- * (#3689). Own keys land on a null-prototype object — V8 creates those in
483
- * dictionary mode, so the copy is O(keys) hash inserts — and the prototype
484
- * is attached after. A spread builds a fast-mode object one map transition
485
- * per key; from a source in dictionary mode (a backing that took deletes)
486
- * whose key order differs from the last copy's, every transition is a fresh
487
- * map with an O(keys) descriptor copy: measured 270 µs vs 28 µs at 400 keys,
488
- * 1.3 ms vs 75 µs at 1000. Narrow, stable-shaped backings keep the spread
489
- * (cloneRaw): there the clone IC copies the property store at once and is
490
- * 3–8× cheaper than any per-key loop. */ function wideClone(e, t) {
491
- const n = Object.create(null);
492
- for (const t of Reflect.ownKeys(e)) n[t] = e[t];
493
- Object.setPrototypeOf(n, Object.prototype);
494
- n[$OWNER] = t;
495
- return n;
496
- }
497
-
498
- /** Copy own `key` from `from` onto `to`. A plain data slot (enumerable,
499
- * writable, configurable, no accessor) is a bare assignment — the common case
500
- * and the cheap one; anything else goes through defineProperty so accessors
501
- * and attribute flags survive the copy. */ function copyOwn(e, t, n) {
502
- const r = Object.getOwnPropertyDescriptor(t, n);
503
- if (r.get || r.set || !r.enumerable || !r.writable || !r.configurable) Object.defineProperty(e, n, r); else e[n] = r.value;
504
- }
505
-
506
- /** One-time own-accessor scan (Annex-B probes, no descriptor allocation);
507
- * returns true when the container is plain data (overlay-safe). Also grades
508
- * the container for the plain-data fast paths (`sc` = 2): `Object.prototype`
509
- * and every own key an enumerable data property — what a spread copies
510
- * exactly and what a bare assignment lands exactly. */ function scanAccessorsOnce(e) {
511
- const t = e.v;
512
- const n = Reflect.ownKeys(t);
513
- let r = Object.getPrototypeOf(t) === Object.prototype;
514
- for (const i of n) {
515
- // Own keys shadow prototype accessors, so the lookups are exact here.
516
- if (lookupGetter.call(t, i) !== undefined || lookupSetter.call(t, i) !== undefined) {
517
- e.a = true;
518
- r = false;
519
- break;
520
- }
521
- if (r && !propertyIsEnumerable.call(t, i)) r = false;
522
- }
523
- e.sc = r ? 2 : 1;
524
- e.kc = n.length;
525
- return !e.a;
526
- }
527
-
528
- /** Own-key count above which a draft opens as a prototype overlay rather
529
- * than a clone (#3360). Below it a spread clone of a plain container is
530
- * cheaper than the overlay's `Object.create` (V8 converts the committed
531
- * backing into a prototype, and every later flatten writes into that
532
- * prototype); above it the clone's O(keys) copy dominates (#3044). */ const OVERLAY_MIN_KEYS = 32;
533
-
534
- /** Overlay commit choice (#3689): flatten in place, or rebuild the backing
535
- * (materializePB, then the clone path's swap)? Measured on V8 (Node 26): a
536
- * committed backing that has served as an overlay prototype is kept as a
537
- * FAST-MODE prototype object up to V8's descriptor limit (1020 own
538
- * properties), and there every in-place DELETE is O(keys) — normalize to
539
- * dictionary plus re-optimize as a prototype, ~0.08 µs × keys — and every
540
- * in-place ADD is a map transition copying the descriptors, ~0.006 µs ×
541
- * keys. Detaching the overlay first does not help: prototype-ness is a
542
- * property of the object's map. One rebuild (wideClone, deletes, writes) is
543
- * ~0.07–0.09 µs × keys — the price of ONE in-place delete. So a fold that
544
- * deleted anything, or added more than ~16 keys, commits cheaper rebuilt (a
545
- * 400-key record churning 100 keys per commit, 10,000 membership readers:
546
- * 4.7 ms → 0.22 ms per step); a pure rewrite of existing keys — the
547
- * overlay's home case — stays in place at O(written).
548
- * Past the descriptor limit V8 keeps the backing in dictionary mode where
549
- * every in-place op is O(1), and a rebuild would be a pure O(keys) loss on
550
- * exactly the growing-record pattern the overlay exists for (#3044) — the
551
- * count gate keeps those in place. The gate reads `kc`, which the set trap
552
- * counts up for keys new to the container and the delete commit counts down. */ const OVERLAY_REBUILD_MAX_KEYS = 1024;
553
-
554
- const OVERLAY_REBUILD_MIN_ADDS = 16;
555
-
556
- function overlayRebuilds(e, t) {
557
- if (e.kc > OVERLAY_REBUILD_MAX_KEYS) return false;
558
- if (e.del !== null && e.del.size !== 0) return true;
559
- // Every trap write records its key, so the written-key count bounds the
560
- // adds — the common few-key fold (#3044's growing record) answers here
561
- // without enumerating the overlay.
562
- const n = e.wk;
563
- if (n !== null && n !== WK_ALL && n.size <= OVERLAY_REBUILD_MIN_ADDS) return false;
564
- const r = e.v;
565
- let i = 0;
566
- for (const e of Reflect.ownKeys(t)) if (!hasOwn.call(r, e) && ++i > OVERLAY_REBUILD_MIN_ADDS) return true;
567
- return false;
568
- }
569
-
570
- /** Downgrade a prototype-overlay pending backing to the clone path: builds
571
- * the real container (committed + overlay writes − deletes) that fold will
572
- * SWAP in as the committed backing, exactly as if the draft had started on
573
- * the clone path. Consumers that need a complete container (reconcile's
574
- * diff walks, drafts escaping into other storage) call this, and so does the
575
- * commit itself when the fold changed the key set (overlayRebuilds). Returns
576
- * the pending backing (the clone, or the pb as-is when not an overlay). */ function materializePB(e) {
577
- const t = e.pb;
578
- if (!e.ovl) return t;
579
- const n = e.sc === 2 ? wideClone(e.v, e) : cloneRaw(e.v, e);
580
- // Deletes before writes (#3689): on a fast-mode clone (the descriptor
581
- // path) the first delete normalizes it once, and the writes then land as
582
- // O(1) dictionary adds instead of O(keys) map transitions each. Order is
583
- // free semantically — a rewritten key left `del` at write time, so no
584
- // write targets a deleted key.
585
- if (e.del !== null) {
586
- for (const t of e.del) delete n[t];
587
- e.kc -= e.del.size;
588
- e.del = null;
589
- }
590
- // Plain-data grade (sc 2): bare assignment, as flattenOverlay does.
591
- for (const r of Reflect.ownKeys(t)) e.sc === 2 ? n[r] = t[r] : copyOwn(n, t, r);
592
- e.pb = n;
593
- e.ovl = false;
594
- return n;
595
- }
596
-
597
- function ensurePB(e) {
598
- let t = e.pb;
599
- // Truth-staged backing hand-off (#3164 fold): a TENTATIVE draft opening on
600
- // a target whose pending backing is truth-staged (a landing folded into a
601
- // retaining transaction — it carries a foldBatches stamp) must not share
602
- // the container. Tentative writes would pollute staged truth, and the
603
- // tentative discard (notifyOptimisticWrites nulls pb) would destroy the
604
- // landing. Park the staged backing and open a fresh draft seeded from the
605
- // optimistic view below; the tentative discard restores it. The
606
- // tentativePBs guard scopes this to draft OPEN: the draft's own backing
607
- // (foldBatches-stamped by its first write when an action's transition is
608
- // ambient) must not be parked by its own later writes.
609
- if (t !== null && !tentativePBs.has(t) && e.fam?.opt === true && !projectionWriteActive && !getWriteOverride() && foldBatches.has(e)) {
610
- stagedTruthPB.set(e, t);
611
- t = e.pb = null;
612
- }
613
- if (activeTransition !== null) foldBatches.set(e, activeTransition);
614
- if (t === null) {
615
- // Prototype-chain overlay (#3044): plain-data non-array containers open
616
- // drafts in O(1) — own keys are the writes, reads fall through to
617
- // committed. Projection and derived-store families take it too (#3352:
618
- // a derive touching one root key of a wide keyed record paid an
619
- // O(keys) clone per recompute). Everything else keeps the descriptor
620
- // clone: arrays (splice/length semantics), OPTIMISTIC families (the
621
- // view-seeding below writes and deletes on the draft container, and the
622
- // tentative discard/truth-park hand whole backings around), chained
623
- // backings (the committed layer is another store's proxy — an overlay
624
- // would route every read-through into its traps), accessor containers
625
- // (live getters).
626
- // The overlay pays off only for a WIDE container over an OWNED committed
627
- // backing (#3360). Narrow containers clone cheaper than they overlay (a
628
- // spread is a fast-path copy; `Object.create` turns the backing into a
629
- // V8 prototype and every later flatten writes into that prototype). An
630
- // unowned backing has to be privatized (cloned) at commit anyway, so an
631
- // overlay there would cost the create PLUS the clone PLUS a per-key
632
- // copy — the clone path does the one clone and swaps it in. The first
633
- // write on a fresh store is exactly that case.
634
- const n = e.v;
635
- if (!e.fam?.opt && !e.ch && !Array.isArray(n) && (e.sc !== 0 ? !e.a : scanAccessorsOnce(e)) && e.kc > OVERLAY_MIN_KEYS && isOwned(n)) {
636
- // Inherits `v`'s $OWNER stamp — the overlay resolves and reads as
637
- // owned without a registration of its own.
638
- t = e.pb = Object.create(n);
639
- e.ovl = true;
640
- } else t = e.pb = cloneRaw(n, e);
641
- // Optimistic families: seed USER drafts from the OPTIMISTIC VIEW
642
- // (committed + active node overrides), so follow-up writes compose on
643
- // optimism instead of clobbering from base (#2951's compose half).
644
- // AUTHORITATIVE drafts (projection recompute / write-override landings)
645
- // seed from committed truth — seeding overrides there would fold a lane
646
- // value into the committed home ("authority wins at reveal" would break).
647
- if (e.fam?.opt && !projectionWriteActive && !getWriteOverride()) {
648
- tentativePBs.add(t);
649
- const n = e.n;
650
- if (n !== null) {
651
- for (const e of Reflect.ownKeys(n)) {
652
- const r = n[e];
653
- if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.Fe);
654
- }
655
- }
656
- const r = e.h;
657
- if (r !== null) {
658
- for (const e of Reflect.ownKeys(r)) {
659
- const n = r[e];
660
- if (hasActiveOverride(n) && !unwrapOverride(n.o?.Fe)) delete t[e];
661
- }
662
- }
663
- }
664
- queueFold(e);
665
- }
666
- return t;
667
- }
668
-
669
- /** Sentinel holder for `t.ht`: a latest()-pull staged this adoption outside
670
- * any transition — the hold lasts until the fold commit (drainFolds). */ const PLAIN_HOLD = Symbol("plainHold");
671
-
672
- /** True while a latest() read is pulling the projection computed up to date
673
- * (see the get trap): adoptions landing during the pull are speculative
674
- * against the un-flushed batch and stage a held view. (Not injectable — the
675
- * derived createStore overload retains projection machinery in every store
676
- * bundle, see treeshake.test.ts.) */ let latestPullActive = false;
677
-
678
- /** Resolve the held committed view (#3074): answers the masked old backing
679
- * while the hold is live, and lazily clears a hold whose transition has
680
- * committed (transitions merge — resolve through currentTransition, same as
681
- * foldHeld's node stamps). */ function heldMaskView(e) {
682
- const t = e.ht;
683
- if (t === null) return null;
684
- if (t !== PLAIN_HOLD && currentTransition(t)?.Rn === true) return e.ht = e.hv = null;
685
- return e.hv;
686
- }
687
-
688
- /**
689
- * Adoption (2026-08-16c): the incoming object becomes the committed backing
690
- * IMMEDIATELY — reconcile is eagerly visible to every reader (shipped
691
- * contract; only its notifications batch), unlike setter writes which stay
692
- * pending until flush. Ownership resets (incoming is unowned/user data). Any
693
- * staged draft clone folds into the diff and is discarded — next is the
694
- * authoritative base (R21/R32).
695
- */ function adoptPB(e, t, n = false) {
696
- // Eager mode (plain-store adoption): the caller notifies inline after its
697
- // descent — no foldOlds queue/drain round trip (the reconcile diff IS the
698
- // fold diff; ~half of dbmon tick time was this duplication).
699
- if (!n) {
700
- queueFold(e);
701
- // records the pre-batch old before we swap
702
- // Diff base = the view the nodes were last told (#3296). A draft's
703
- // setter-exit notifications already moved them to its pending backing,
704
- // so a later adoption diffs against THAT — against committed, a key the
705
- // draft changed and the adoption restores would never re-notify. An
706
- // adoption with no draft leaves nodes where they were: keep an existing
707
- // base, else the pre-batch committed (foldOlds' entry itself stays the
708
- // committed identity for the path-copy CAS). Eager callers read t.pb
709
- // directly; this hand-off exists because pb is gone by drain time.
710
- if (e.pb !== null) {
711
- if (e.ovl) materializePB(e);
712
- e.ab = e.pb;
713
- } else e.ab ??= foldOlds.get(e);
714
- }
715
- // #3074/#3075: a projection recompute deriving from uncommitted inputs
716
- // swaps the backing SPECULATIVELY — committed-visibility readers must
717
- // keep the pre-hold view until the hold resolves (a source held by a
718
- // live transition, or a latest()-pull ahead of the flush). Post-await
719
- // landings (write-override) stay immediately visible — landed truth —
720
- // and clear any hold. Optimistic families hold too (#3330 store twin):
721
- // their tentative edits ride the lane machinery, but a sync derive
722
- // adopting under a transaction is held TRUTH like any projection's —
723
- // unheld, handlers read it early and an optimistic write equal to it
724
- // compared as a no-op against the swapped-in backing. A plain store's
725
- // reconcile inside an action holds the same way: its nodes stage under the
726
- // transaction (the inline notify), and the backing must not show handlers
727
- // and stale readers what the tracked read masks (signal parity, #3336).
728
- if (getWriteOverride()) {
729
- e.ht = e.hv = null;
730
- } else if (activeTransition !== null || !n && latestPullActive) {
731
- if (heldMaskView(e) === null) e.hv = e.v;
732
- e.ht = activeTransition ?? PLAIN_HOLD;
733
- if (!n && activeTransition !== null) heldAdoptions.add(e);
734
- heldKeys.set(e, [ t ]);
735
- }
736
- e.pb = null;
737
- // Overlay and accessor-scan state describe the OUTGOING backing — a
738
- // swapped container must not inherit them: a stale `ovl` beside a nulled
739
- // pb crashes materializePB (unwrapValue consults ovl before the
740
- // null-coalesce), a stale `del` would read the adoptee's keys as deleted
741
- // in the next draft, and a stale plain-data verdict (`sc`/`a`) could
742
- // admit an accessor-bearing adoptee to the overlay path. Reset; the next
743
- // draft rescans once (#3044 audit follow-up).
744
- e.ovl = false;
745
- e.del = null;
746
- e.sc = 0;
747
- e.a = false;
748
- e.wk = null;
749
- // adoption supersedes staged trap writes
750
- e.v = t;
751
- e.ch = t[$TARGET] !== undefined;
752
- // An adoptee we own within this family (a draft aliasing one of the
753
- // family's own backings) re-stamps to its new owner — the stamp is the
754
- // family's registration for it; everything else registers in the map.
755
- const r = t[$OWNER];
756
- if (r !== undefined && r.fam === e.fam) t[$OWNER] = e; else (e.fam?.map ?? storeNextLookup).set(t, e);
757
- }
758
-
759
- /** Sentinel for `t.wk`: the written-keys bound is unusable this batch (an
760
- * array length write implicitly deleted indices) — consumers full-scan. */ const WK_ALL = new Set;
761
-
762
- const plainProto = e => {
763
- const t = Object.getPrototypeOf(e);
764
- return t === Object.prototype || t === Array.prototype || t === null;
765
- };
766
-
767
- function queueFold(e) {
768
- if (foldOlds.has(e)) return;
769
- if (!hookInstalled) {
770
- hookInstalled = true;
771
- setStoreCommitHook(drainFolds);
772
- }
773
- // Always arm — "map non-empty ⇒ drain scheduled" is NOT an invariant: a
774
- // held re-queue, or an incomplete-transition flush (which skips
775
- // commitPendingNodes entirely), leaves entries behind after `scheduled`
776
- // was consumed. A size-gated arm then strands every LATER fold — queued
777
- // silently, never drained, committed base frozen at stale state while its
778
- // nodes commit (#3089). schedule() early-returns when already armed.
779
- schedule();
780
- foldOlds.set(e, e.v);
781
- }
782
-
783
- /** Fold write-attribution (#3089): a draft written while a transition is
784
- * active belongs to that transition — its fold must not commit before the
785
- * transition settles. Observed keys already defer through the held check in
786
- * drainFolds (their nodes carry _pendingValue); this write-time stamp is the
787
- * equivalent hold for UNOBSERVED keys, which have no node to consult.
788
- * Refreshed on every write; resolved through currentTransition at drain
789
- * (transitions merge — same rule as heldMaskView). */ const foldBatches = new WeakMap;
790
-
791
- /** Parked truth-staged pending backings (#3164 fold): a tentative draft that
792
- * opens while a folded landing's backing is live moves the staged container
793
- * here (see ensurePB); the tentative discard in notifyOptimisticWrites
794
- * restores it in place of the usual null. */ const stagedTruthPB = new WeakMap;
795
-
796
- /** Backings opened by TENTATIVE drafts (optimistic user setters): ensurePB's
797
- * truth-park must not fire against the draft's own container on its second
798
- * and later writes (the first write stamps foldBatches whenever an action's
799
- * transition is ambient). Entries die with their draft — tentative backings
800
- * are consumed at setter exit. */ const tentativePBs = new WeakSet;
801
-
802
- /** A draft read composes the live optimistic view until the draft has opened
803
- * its OWN view-seeded backing (ensurePB seeds that clone from the view and
804
- * registers it in tentativePBs; from then on reads must see the draft's
805
- * writes, not the overrides they superseded). A pending backing that exists
806
- * for any other reason is not that clone — a truth landing staged into a
807
- * retaining transaction (#3164 fold) is authoritative truth WITHOUT the
808
- * live overrides. ensurePB parks such a backing on the draft's first WRITE
809
- * and reseeds from the view, but the reads that precede that write went to
810
- * the staged truth: `votes++` read base, wrote base+1, and the override it
811
- * emitted landed on the value already displayed — a second in-flight
812
- * increment made after a sibling's landing was invisible (#2951's compose
813
- * half, one landing later). */ function draftSeesOverrides(e) {
814
- return e.pb === null || !tentativePBs.has(e.pb);
815
- }
816
-
817
- /** Committed-time privatization for parent-chain slot updates (path copying). */ function privatizeCommitted(e) {
818
- if (isOwned(e.v)) return;
819
- const t = e.v;
820
- const n = cloneRaw(t, e);
821
- e.v = n;
822
- e.ch = false;
823
- if (e.u) {
824
- privatizeCommitted(e.u);
825
- devAssertNeverUserMutation(e.u.v);
826
- // CAS, same as drainFolds' path copy: re-point the parent slot only
827
- // while it still holds the raw we cloned. A parent that folded EARLIER
828
- // in this drain may have replaced or deleted this slot (the same batch
829
- // wrote the child and then `parent.row = fresh` / `parent.length = 0`)
830
- // — an unconditional write resurrected the dropped child over it.
831
- const n = e.u.v;
832
- const r = parentSlotKey(e, t);
833
- if (n[r] === t) n[r] = e.v;
834
- }
835
- }
836
-
837
- /** Resolve the slot this child currently occupies in its parent's committed
838
- * backing (#3282). `pk` is stamped at wrap time and arrays MOVE: a reverse/
839
- * unshift/splice relocates the raw, and a fold that re-points the wrap-time
840
- * slot writes the clone over whichever row lives there now. Objects never
841
- * move keys, so the stamp is authoritative; for arrays, verify and re-locate
842
- * by identity when stale (fold-time only — never on a read path). */ function parentSlotKey(e, t) {
843
- const n = e.pk;
844
- const r = e.u.v;
845
- if (r[n] === t || !Array.isArray(r)) return n;
846
- const i = r.indexOf(t);
847
- if (i === -1) return n;
848
- e.pk = i;
849
- return i;
850
- }
851
-
852
- /** Overlay commit (#3044): apply the batch's writes onto an OWNED committed
853
- * backing in place — O(written), not O(container). Unowned backings
854
- * privatize first (clone once, parents re-slotted) — the never-mutate-user-
855
- * data contract holds. Shared by the deferred fold (drainFolds) and the
856
- * projection landing's immediate commit (notifyWrites). */ function flattenOverlay(e, t) {
857
- privatizeCommitted(e);
858
- const n = e.v;
859
- // Plain-data grade (sc 2): every own key on the overlay is an enumerable
860
- // writable data slot (set-trap writes; a non-plain defineProperty
861
- // downgrades the grade) — a bare assignment lands it without the
862
- // descriptor round trip.
863
- for (const r of Reflect.ownKeys(t)) e.sc === 2 ? n[r] = t[r] : copyOwn(n, t, r);
864
- if (e.del !== null) {
865
- for (const t of e.del) delete n[t];
866
- e.kc -= e.del.size;
867
- e.del = null;
868
- }
869
- e.pb = null;
870
- e.ovl = false;
871
- e.wk = null;
872
- // written-keys window closes with the commit
873
- }
874
-
875
- function drainFolds() {
876
- if (foldOlds.size === 0) return;
877
- const e = [ ...foldOlds ];
878
- foldOlds.clear();
879
- for (const [t, n] of e) {
880
- // A latest()-pull staging holds only until the fold commit: this flush
881
- // is committing the batch the pull ran ahead of. Transition holds stay —
882
- // they clear when their transition is done (heldMaskView).
883
- if (t.ht === PLAIN_HOLD) t.ht = t.hv = null;
884
- if (t.pb !== null) {
885
- // #3089: a fold written under a still-running transition defers to
886
- // that transition's settle (the write-time stamp covers unobserved
887
- // keys; observed keys also hit the pending-node held check below).
888
- const e = foldBatches.get(t);
889
- if (e !== undefined) {
890
- if (currentTransition(e).Rn === false) {
891
- foldOlds.set(t, n);
892
- continue;
893
- }
894
- foldBatches.delete(t);
895
- }
896
- // Setter path: nodes were setSignal'd at setter exit (write-time
897
- // notification — transitions/holds ride core machinery). Commit the
898
- // backing only for keys whose nodes have committed; a still-pending
899
- // node (transition-held) re-queues the target for the settling flush.
900
- let r = false;
901
- const i = t.pb;
902
- const l = t.n;
903
- if (l !== null) {
904
- // Only written keys can hold (their nodes took the setSignal); the
905
- // wk bound keeps this O(written) — see notifyWrites. Same fallback
906
- // rules as the notify (WK_ALL / accessors / non-plain prototypes).
907
- const e = t.wk;
908
- const n = e === null || e === WK_ALL || t.a === true ||
909
- // Overlay pbs chain to the COMMITTED object (#3044) — plainness is
910
- // the committed container's prototype, not the overlay's.
911
- !plainProto(t.ovl ? t.v : i) ? Reflect.ownKeys(l) : e;
912
- for (const e of n) {
913
- const t = l[e];
914
- if (t !== undefined && t._e !== NOT_PENDING) {
915
- r = true;
916
- break;
917
- }
918
- }
919
- }
920
- if (r) {
921
- foldOlds.set(t, n);
922
- // re-queue: commit happens when the hold settles
923
- continue;
924
- }
925
- if (t.ovl && (t.v !== n || !overlayRebuilds(t, i))) {
926
- // Overlay flatten (#3044): the backing keeps its identity, so the
927
- // `t.v === old` gate below skips path copying (the parent slot
928
- // already points here) and the adopted-notify (setter notifications
929
- // happened at write time). A backing privatized mid-batch is a fresh
930
- // clone, never a prototype — in-place writes into it are cheap, and
931
- // the merge branch below is what a materialized pb would need anyway.
932
- flattenOverlay(t, i);
933
- } else if (t.v !== n) {
934
- // Privatized mid-batch (#3271): an earlier fold in this drain
935
- // path-copied THROUGH this target — privatizeCommitted cloned the
936
- // committed backing, re-pointed the parent slot at the clone, and
937
- // stitched the descendant's fold into it. The draft's pb predates
938
- // that: swapping it in would clobber the descendant's fold, and the
939
- // parent CAS below (still comparing against `old`) would fail and
940
- // orphan this fold entirely — a projection draft writing descendant-
941
- // then-ancestor silently lost the ancestor write. Merge the batch's
942
- // writes onto the current container instead (the parent slot already
943
- // points at it). privatize first: an adopt-then-write batch can land
944
- // here with an unowned adoptee as t.v.
945
- privatizeCommitted(t);
946
- const e = t.v;
947
- const r = t.wk;
948
- if (r !== null && r !== WK_ALL) {
949
- // The trap records every write/delete key — apply exactly those.
950
- for (const t of r) {
951
- if (hasOwn.call(i, t)) copyOwn(e, i, t); else delete e[t];
952
- }
953
- } else {
954
- // Array length write poisoned the bound (WK_ALL) — value-diff
955
- // against the pre-batch old. Slots the draft never touched hold
956
- // the same raw reference in both, so descendant folds stay put.
957
- // Not copyOwn: the plain-value write is GATED on "the draft changed
958
- // this slot" — an untouched slot in pb holds the pre-batch reference,
959
- // and writing it back would clobber a descendant fold stitched into v.
960
- for (const t of Reflect.ownKeys(i)) {
961
- const r = Object.getOwnPropertyDescriptor(i, t);
962
- if (r.get || r.set || !r.enumerable || !r.writable || !r.configurable) Object.defineProperty(e, t, r); else if (r.value !== n[t] || !hasOwn.call(n, t)) e[t] = r.value;
963
- }
964
- for (const t of Reflect.ownKeys(n)) {
965
- if (!hasOwn.call(i, t)) delete e[t];
966
- }
967
- }
968
- t.pb = null;
969
- t.wk = null;
970
- // written-keys window closes with the fold commit
971
- } else {
972
- // An overlay whose fold changed the key set rebuilds (#3689) and
973
- // takes the swap like a clone-path draft: the parent slot re-points
974
- // in the path copy below.
975
- t.v = t.ovl ? materializePB(t) : i;
976
- t.ch = false;
977
- // pb is always a plain clone
978
- t.pb = null;
979
- t.wk = null;
980
- // written-keys window closes with the fold commit
981
- }
982
- }
983
- const e = t.ab;
984
- t.ab = null;
985
- if (t.v !== n) {
986
- // Path copying (CAS: see the eager-fold twin above). Slot resolved by
987
- // identity (#3282): an array move relocated the raw, so the wrap-time
988
- // pk may point at a sibling — a raw-slot CAS there both failed to
989
- // re-point AND (via privatizeCommitted's unguarded write) clobbered
990
- // the sibling.
991
- if (t.u) {
992
- const e = parentSlotKey(t, n);
993
- if (t.u.v[e] === n) {
994
- privatizeCommitted(t.u);
995
- devAssertNeverUserMutation(t.u.v);
996
- t.u.v[e] = t.v;
997
- }
998
- }
999
- }
1000
- // Adoption notify against the base the nodes were last told (#3296). A
1001
- // no-op adoption (A -> B -> A before flush, no draft) has base === v and
1002
- // nothing to say; a draft superseded by an adoption back to the SAME raw
1003
- // still has (base = pending backing) !== v and must notify.
1004
- if (e !== null && e !== t.v) notifyFold(t, e, t.v);
1005
- }
1006
- }
1007
-
1008
- /**
1009
- * Setter-exit notification (write channel): diff the draft's pending backing
1010
- * against committed and setSignal every changed OBSERVED key — write-time
1011
- * notification with commit deferred to node commit, so transition holds,
1012
- * isPending, affects, and lane machinery ride the core natively (§3's
1013
- * "pending home = the node when a node exists"). Unobserved keys stay in the
1014
- * pending backing and fold directly at commit.
1015
- */ function notifyWrites(e) {
1016
- let t = e.pb;
1017
- if (t === null) return;
1018
- // Optimistic channel: user writes on an optimistic family become node-level
1019
- // engine writes (armed nodes route setSignal through optimisticWrite) — the
1020
- // committed backing is NEVER touched; the draft clone is discarded. Reverts,
1021
- // per-transaction ownership, and flash-at-flush are all core-native.
1022
- // Projection recompute writes (projectionWriteActive) and projection draft
1023
- // writes (write-override, incl. post-await async landings) are
1024
- // authoritative and take the plain channel below (they commit silently
1025
- // under overrides per the engine's no-revert-stash contract).
1026
- if (e.fam?.opt) {
1027
- if (!projectionWriteActive && !getWriteOverride()) {
1028
- optHooks.notifyOptimisticWrites(e, t);
1029
- return;
1030
- }
1031
- // Authoritative path on an optimistic family: armed nodes must commit
1032
- // silently (engine bypass) — without this, a landing's setSignals would
1033
- // create lanes and block their own transition's settle.
1034
- if (!projectionWriteActive) {
1035
- setProjectionWriteActive(true);
1036
- try {
1037
- notifyWrites(e);
1038
- } finally {
1039
- setProjectionWriteActive(false);
1040
- }
1041
- return;
1042
- }
1043
- }
1044
- const n = e.v;
1045
- const r = e.n;
1046
- // Written-keys bound: trap writes record their keys, so the notify visits
1047
- // O(written) nodes instead of every subscription on the record (a selection
1048
- // map with thousands of per-key subscribers pays two visits per select,
1049
- // not a full scan). Falls back to the full node scan when the bound can't
1050
- // hold: no trap granularity (wk null), an array length write (WK_ALL —
1051
- // implicit index deletes), accessors on the record (t.a — a getter node's
1052
- // value can change when ANY key is written), or a non-plain prototype
1053
- // (class instances: prototype getters derive from arbitrary fields).
1054
- const i = e.wk;
1055
- // Overlay pbs chain to the COMMITTED object (#3044): a prototype-overlay
1056
- // draft is plain data on its own layer, but its getPrototypeOf is the
1057
- // committed container — judge plainness by the COMMITTED prototype or the
1058
- // bound never engages for overlay writes (every plain-object setter batch
1059
- // would full-scan: the exact selection-map workload wk exists for; jf
1060
- // `select` regressed 2x on this).
1061
- const l = i === WK_ALL || e.a === true || !plainProto(e.ovl ? e.v : t) ? null : i;
1062
- if (r !== null) {
1063
- const i = l ?? Reflect.ownKeys(r);
1064
- for (const l of i) {
1065
- const i = r[l];
1066
- if (i === undefined) continue;
1067
- // Per-key accessor handling: the node's cached flag plus ONE getter
1068
- // probe on the incoming side (getters arriving via merge/adoption).
1069
- // Setter-only props read as data (value undefined) so lookupSetter is
1070
- // not consulted on this hot path; prototype getters never own nodes.
1071
- if (i.acc === true || hasOwn.call(t, l) && lookupGetter.call(t, l) !== undefined) {
1072
- i.acc = isOwnAccessor(t, l);
1073
- const e = Object.getOwnPropertyDescriptor(n, l);
1074
- const r = Object.getOwnPropertyDescriptor(t, l);
1075
- if (e && (e.get || e.set) || r && (r.get || r.set)) {
1076
- if (e?.get !== r?.get || e?.set !== r?.set || e?.value !== r?.value) setSignal(i, () => FORCE);
1077
- continue;
1078
- }
1079
- if (!isEqual(e?.value, r?.value)) setSignal(i, () => r?.value);
1080
- continue;
1081
- }
1082
- // No old-side pre-compare: t.v lags across multi-batch windows (a
1083
- // projection recompute can run before the prior fold commits) — the
1084
- // node's OWN current value is the true old side, and setSignal's
1085
- // internal equality already checks exactly that.
1086
- const o = e.del !== null && e.del.has(l) ? undefined : t[l];
1087
- // The derived store's setter reaching a leaf its own fold staged under
1088
- // another transaction: not a proposal — the fold re-runs (#3612).
1089
- if (derivedSetter !== null && i.De === derivedSetter && heldDerivation(i)) heldDerivationHit = true;
1090
- setSignal(i, () => o);
1091
- }
1092
- }
1093
- const o = e.h;
1094
- if (o !== null) {
1095
- const n = l ?? Reflect.ownKeys(o);
1096
- for (const r of n) {
1097
- const n = o[r];
1098
- if (n !== undefined) setSignal(n, r in t && !(e.del !== null && e.del.has(r)));
1099
- }
1100
- }
1101
- // Deep-witness (dk): setter writes must notify a deep() subscriber even on
1102
- // keys with no node. O(written/pb keys) equality only when a witness exists.
1103
- if (e.dk !== null) {
1104
- if (e.del !== null && e.del.size !== 0) bumpDeep(e); else for (const r of l ?? Reflect.ownKeys(t)) {
1105
- if (r === $OWNER) continue;
1106
- const i = t[r];
1107
- const l = n[r];
1108
- if (i !== null && typeof i === "object" ? !targetsEqual(l, i) : !isEqual(l, i)) {
1109
- bumpDeep(e);
1110
- break;
1111
- }
1112
- }
1113
- }
1114
- if (e.k !== null) {
1115
- let r;
1116
- if (e.ovl) {
1117
- // Overlay membership: only NEW own keys or deletes can change it.
1118
- r = e.del !== null && e.del.size !== 0;
1119
- if (!r) {
1120
- for (const e of Reflect.ownKeys(t)) {
1121
- if (!hasOwn.call(n, e)) {
1122
- r = true;
1123
- break;
1124
- }
1125
- }
1126
- }
1127
- } else {
1128
- r = Array.isArray(t) && Array.isArray(n) ? arrayStructureChanged(n, t) : membershipChanged(n, t);
1129
- }
1130
- if (r) setSignal(e.k, e => e + 1);
1131
- }
1132
- // Projection backing folds split by channel (two pinned contracts):
1133
- // - sync-derive drafts (recompute body): NEVER eager — a downstream async
1134
- // hold can form LATER in the same flush and the leaf must stay at stale
1135
- // committed for context-free readers (spec-async "pends only the written
1136
- // leaf"). drainFolds commits when held-ness is knowable.
1137
- // - post-await async LANDINGS (write-override per-op, microtask context —
1138
- // no enclosing flush can capture them): the data-level commit is
1139
- // IMMEDIATE — landed truth shows to untracked readers even while a
1140
- // downstream consumer's own async still holds the effect-level reveal
1141
- // (spec-async "verdicts never inherit consumers' in-flight state").
1142
- // EXCEPT under an active transaction (#3164 fold): a landing riding a
1143
- // retaining transaction (the optimistic module's aroundWrite binds it)
1144
- // stages instead — ensurePB stamped foldBatches, so the backing commits
1145
- // with the transaction and the reveal is atomic at settle. The pinned
1146
- // immediate-commit contract is stated over the no-transaction microtask
1147
- // posture, which `activeTransition === null` is exactly.
1148
- if (e.fam !== null && e.pb !== null && getWriteOverride() && activeTransition === null) {
1149
- // Landed truth (post-await write-override): immediately visible to every
1150
- // reader — any staged held view is superseded.
1151
- if (e.ht !== null) e.ht = e.hv = null;
1152
- // Overlay landing (#3352): flatten in place — identity-stable, and
1153
- // privatizeCommitted re-slots the parent itself when it has to clone an
1154
- // unowned seed, so no path copy is needed. A landing that changed the
1155
- // key set rebuilds instead (#3689) and swaps like a clone-path landing.
1156
- if (e.ovl) {
1157
- if (!overlayRebuilds(e, t)) return flattenOverlay(e, t);
1158
- t = materializePB(e);
1159
- }
1160
- const n = e.v;
1161
- e.pb = null;
1162
- e.v = t;
1163
- e.ch = false;
1164
- if (e.u) {
1165
- // Identity-resolved slot (#3282) — see drainFolds' path-copy twin.
1166
- const r = parentSlotKey(e, n);
1167
- if (e.u.v[r] === n) {
1168
- privatizeCommitted(e.u);
1169
- devAssertNeverUserMutation(e.u.v);
1170
- e.u.v[r] = t;
1171
- }
1172
- }
1173
- }
1174
- }
1175
-
1176
- const FORCE = Symbol();
1177
-
1178
- /** Same logical slot: both values resolve to one (re-pointed) child target —
1179
- * adoption preserved identity, so the slot did not change (R9). */ function targetsEqual(e, t) {
1180
- if (e === null || typeof e !== "object" || t === null || typeof t !== "object") return false;
1181
- const n = lookupTarget(e, null);
1182
- return n !== undefined && n === lookupTarget(t, null);
1183
- }
1184
-
1185
- function arrayStructureChanged(e, t) {
1186
- if (e.length !== t.length) return true;
1187
- for (let n = 0; n < t.length; n++) {
1188
- const r = e[n];
1189
- const i = t[n];
1190
- if (!isEqual(r, i) && !targetsEqual(r, i)) return true;
1191
- }
1192
- return false;
1193
- }
1194
-
1195
- function membershipChanged(e, t) {
1196
- const n = Reflect.ownKeys(t);
1197
- // The $OWNER stamp is not membership: an owned side counts one key more.
1198
- if (Reflect.ownKeys(e).length - +isOwned(e) !== n.length - +isOwned(t)) return true;
1199
- for (const t of n) if (t !== $OWNER && !(t in e)) return true;
1200
- return false;
1201
- }
1202
-
1203
- /**
1204
- * The fold diff walks SUBSCRIPTION KEYS ONLY (legacy parity: `for key in
1205
- * nodes`): nodes exist exactly where something tracked, so unobserved data
1206
- * costs nothing here regardless of object size. Accessor safety rides the
1207
- * sticky `t.a` flag — a node's key was necessarily read, so the get trap has
1208
- * already seen whether it is an accessor.
1209
- */
1210
- /** One node's fold notification (shared by notifyFold's walk and the fused
1211
- * adoption walk): accessor-aware compare + equality/identity-gated setSignal. */ function notifyKeyDiff(e, t, n, r,
1212
- // The incoming-side getter probe covers SETTER-channel arrivals (return-
1213
- // form merges, defineProperty) — those flow through notifyWrites/
1214
- // notifyFold, which probe. The RECONCILE channel (fused walk) passes
1215
- // false: reconcile adopts immutable data by contract (R2a) and the pinned
1216
- // getter-preservation tests are all setter-channel; skipping ~2 Annex-B
1217
- // calls per key per tick is a measured dbmon win.
1218
- i = true) {
1219
- if (e.acc === true || i && hasOwn.call(r, t) && lookupGetter.call(r, t) !== undefined) {
1220
- e.acc = isOwnAccessor(r, t);
1221
- const i = Object.getOwnPropertyDescriptor(n, t);
1222
- const l = Object.getOwnPropertyDescriptor(r, t);
1223
- if (i && (i.get || i.set) || l && (l.get || l.set)) {
1224
- // Accessor involved: never invoke; force-notify on shape change so
1225
- // subscribers re-read (and re-track) through the trap.
1226
- if (i?.get !== l?.get || i?.set !== l?.set || i?.value !== l?.value) setSignal(e, () => FORCE);
1227
- return;
1228
- }
1229
- const o = i?.value;
1230
- const s = l?.value;
1231
- if (!isEqual(o, s) && !targetsEqual(o, s)) setSignal(e, typeof s === "function" ? () => s : s);
1232
- } else {
1233
- const i = n[t];
1234
- const l = r[t];
1235
- // Direct value write when not a function (setSignal treats functions as
1236
- // updaters) — saves a closure allocation per changed key on the fold
1237
- // hot path.
1238
- if (!isEqual(i, l) && !targetsEqual(i, l)) setSignal(e, typeof l === "function" ? () => l : l);
1239
- }
1240
- }
1241
-
1242
- /** Accessor-flag probe for the fused walk's early-continue (accessor keys
1243
- * can never identity-skip: their VALUE is the descriptor's product). */ function hasAccessorFlag(e) {
1244
- return e.acc === true;
1245
- }
1246
-
1247
- /** Fused-walk per-key notification with values already in hand: the caller
1248
- * fetched both sides and handled the identity skip; this applies the
1249
- * accessor branch (cached flag only — reconcile channel) or the plain
1250
- * equality/identity-gated write. */ function notifyKeyValue(e, t, n, r, i, l) {
1251
- if (e.acc === true) {
1252
- notifyKeyDiff(e, t, i, l, false);
1253
- return;
1254
- }
1255
- // The pre-compare is NOT redundant with the node's equals: setSignal parks
1256
- // a pending value and registers with the batch before equality applies at
1257
- // commit (RUL-1), so identity-preserved slots (adopted child containers —
1258
- // every row's fresh `queries` array) must be gated out HERE or each one
1259
- // pays the full write machinery every tick (measured: +0.5ms/tick dbmon).
1260
- if (!isEqual(n, r) && !targetsEqual(n, r)) setSignal(e, typeof r === "function" ? () => r : r);
1261
- }
1262
-
1263
- /** Presence + membership halves of a fold notification (shared tail). */ function notifyFoldTail(e, t, n) {
1264
- const r = e.h;
1265
- if (r !== null) {
1266
- for (const e of Reflect.ownKeys(r)) setSignal(r[e], e in n);
1267
- }
1268
- if (e.k !== null) {
1269
- const r = Array.isArray(n) && Array.isArray(t) ? arrayStructureChanged(t, n) : membershipChanged(t, n);
1270
- if (r) setSignal(e.k, e => e + 1);
1271
- }
1272
- }
1273
-
1274
- function notifyFold(e, t, n) {
1275
- if (e.dk !== null && t !== n) bumpDeep(e);
1276
- // Optimistic targets: adoption notifications are authoritative landings —
1277
- // bypass the engine (commit into _value; active overrides keep shadowing
1278
- // until their transaction settles, per the no-revert-stash contract).
1279
- if (e.fam?.opt && !projectionWriteActive) {
1280
- setProjectionWriteActive(true);
1281
- try {
1282
- notifyFold(e, t, n);
1283
- } finally {
1284
- setProjectionWriteActive(false);
1285
- }
1286
- return;
1287
- }
1288
- const r = e.n;
1289
- if (r !== null) {
1290
- for (const e of Reflect.ownKeys(r)) {
1291
- notifyKeyDiff(r[e], e, t, n);
1292
- }
1293
- }
1294
- const i = e.h;
1295
- if (i !== null) {
1296
- for (const e of Reflect.ownKeys(i)) setSignal(i[e], e in n);
1297
- }
1298
- if (e.k !== null) {
1299
- // Key-set/$TRACK: objects notify on membership; arrays on any index or
1300
- // length change (mapArray and iteration re-read values — R15).
1301
- const r = Array.isArray(n) && Array.isArray(t) ? arrayStructureChanged(t, n) : membershipChanged(t, n);
1302
- if (r) setSignal(e.k, e => e + 1);
1303
- }
1304
- }
1305
-
1306
- // ---------------------------------------------------------------------------
1307
- // traps
1308
- /** >0 while inside a setter: writes allowed, reads are read-your-writes. */ let writing = 0;
1309
-
1310
- /** Write scope keys (a family object or a plain store's root target): draft
1311
- * semantics — write permission, read-your-writes, tracking suppression —
1312
- * apply ONLY to targets under a scope being written. Reads of OTHER stores
1313
- * inside a setter track normally (they are dependencies: a projection derive
1314
- * reading another store must link it). */ let writeScopes = null;
1315
-
1316
- function scopeKey(e) {
1317
- if (e.fam !== null) return e.fam;
1318
- let t = e;
1319
- while (t.u !== null) t = t.u;
1320
- return t;
1321
- }
1322
-
1323
- function inDraft(e) {
1324
- return writeScopes !== null && writeScopes.has(scopeKey(e));
1325
- }
1326
-
1327
- /** Shallow serve rule (#2932): raw-marked data serves VERBATIM, but a
1328
- * store-proxy slot value gets a boundary wrapper in THIS store's own family —
1329
- * write isolation through derived chains (downstream writes must never land
1330
- * upstream). markRawOne skips proxies for exactly this reason. */ function serveShallow(e, t, n) {
1331
- if (n !== null && typeof n === "object" && n[$TARGET] !== undefined) return draftServe(e, wrapNext(n, e, t));
1332
- return n;
1333
- }
1334
-
1335
- /** Draft reads extend write permission to reachable stores (legacy Writing
1336
- * semantics: wrapping a child through a draft get admits it — cross-store
1337
- * writes like `s.inner.a = 10` work when `inner` is another store's proxy). */ function draftServe(e, t) {
1338
- if (writeScopes !== null && inDraft(e)) {
1339
- const e = t?.[$TARGET];
1340
- if (e !== undefined && e.v !== undefined) writeScopes.add(scopeKey(e));
1341
- }
1342
- return t;
1343
- }
1344
-
1345
- /** Targets written during the current (outermost) setter — notified at exit. */ const pendingNotify = new Set;
1346
-
1347
- /** Targets adopted under a live transaction this setter (adoptPB set a
1348
- * transaction hold) — their nodes are notified at the outermost exit. */ const heldAdoptions = new Set;
1349
-
1350
- /**
1351
- * Write-time notification for a transaction-held adoption (#3330 store twin;
1352
- * the setter path's notifyWrites twin for adoptions). A projection's fold
1353
- * normally notifies its nodes at the drain — and the drain of a batch parked
1354
- * in a live transaction is the transaction's COMMIT, so the nodes took the
1355
- * adopted values as fresh writes at commit time: every subscriber was
1356
- * re-marked and re-ran against a frame the lane had already published (a
1357
- * third `v=1 d=2`), where a signal's write had staged at write time and
1358
- * promoted silently. Staging here, inside the transaction's batch, makes the
1359
- * two paths one: the nodes carry transition-stamped `_pendingValue`s, their
1360
- * subscribers recompute in this flush and park with the transaction, and the
1361
- * commit promotes without re-notifying. `ab` moves to the adopted backing —
1362
- * the view the nodes were last told (#3296) — so the drain has nothing left
1363
- * to say and only path-copies.
1364
- */ function stageHeldAdoptions() {
1365
- const e = [ ...heldAdoptions ];
1366
- heldAdoptions.clear();
1367
- for (const t of e) {
1368
- const e = t.ab;
1369
- if (e === null || e === t.v) continue;
1370
- notifyFold(t, e, t.v);
1371
- t.ab = t.v;
1372
- }
1373
- }
1374
-
1375
- const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1376
-
1377
- /** Mirror of core read()'s context rule: the OWNER context (not the tracking
1378
- * observer) decides pending visibility, with Roots resolving to their parent
1379
- * computed (#2687 — untracked reads inside mapArray Roots see in-flight
1380
- * values mid-flush). CHILDREN_FORBIDDEN execution scopes (createTrackedEffect
1381
- * / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */
1382
- /** Core read()'s reader: the current computation, a root reading as its
1383
- * parent computed (`context` persists under untrack — an untracked read
1384
- * inside an effect is still that effect's read). */ function readerContext() {
1385
- const e = getOwner();
1386
- return e === null ? null : e.Wn ? e.qn ?? null : e;
1387
- }
1388
-
1389
- /** A pending fold is transition-held when any written node's parked value is
1390
- * stamped by a live transition (a plain batch parking — the lazy-recompute
1391
- * read case — has no transition stamp and serves fresh). */ function foldHeld(e) {
1392
- const t = e.n;
1393
- if (t === null) return false;
1394
- for (const e of Reflect.ownKeys(t)) {
1395
- const n = t[e];
1396
- if (n._e !== NOT_PENDING && n.we != null && n.we.Rn !== true) return true;
1397
- }
1398
- return false;
1399
- }
1400
-
1401
- /** The backing a reader is served. `key` (the get/has/descriptor traps)
1402
- * scopes a fold hold to the keys the fold touched — see pendingBackingVisible —
1403
- * and an adoption hold to the keys the adoption changed (heldKey). */ function readSource(e, t) {
1404
- // Adoption hold first (#3074): an adoption staged under a live transaction
1405
- // (or a latest()-pull, PLAIN_HOLD) serves the pre-hold committed view to
1406
- // committed-visibility readers — context-free and children-forbidden ones,
1407
- // and stale passes off a foreign hold. Drafts, write-override and latest()
1408
- // see the adopted backing.
1409
- const n = e.ht;
1410
- if (n !== null && !latestReadActive && !inDraft(e) && !getWriteOverride()) {
1411
- const r = heldMaskView(e);
1412
- // A key the adoption left unchanged derives nothing from the hold, for
1413
- // any reader (#3706): the backing serves it.
1414
- if (r !== null && (t === undefined || heldKey(e, t))) {
1415
- const e = readerContext();
1416
- if (e === null || e.C & CONFIG_CHILDREN_FORBIDDEN || !holdVisible(n === PLAIN_HOLD ? null : currentTransition(n), e)) return r;
1417
- }
1418
- }
1419
- return pendingBackingVisible(e, false, t) ? e.pb : e.v;
1420
- }
1421
-
1422
- /** The single pb-vs-committed visibility decision (#3147), shared by per-key
1423
- * backing reads (readSource) and deep()/snapshot composition (snapshotWalk)
1424
- * so the two reader families can never disagree about a HELD landing.
1425
- *
1426
- * Signal-parity visibility (core read(): owner-context reads serve
1427
- * _pendingValue, context-free reads serve committed — effects recompute
1428
- * BEFORE commitPendingNodes in the flush, so the pending view must be
1429
- * servable). Drafts (setter window OR projection write-override) and
1430
- * owner-context reads see the pending backing; context-free reads see
1431
- * committed. Node reads apply the same rule, so all homes agree.
1432
- *
1433
- * `speculative` is deep()/snapshot's posture: an untrack/deep PEEK that sees
1434
- * ordinary pending staging regardless of owner context (the documented
1435
- * divergence from context-free per-key reads) — but never through a hold:
1436
- * held truth stays masked exactly as it is for per-key readers. */ function pendingBackingVisible(e, t, n) {
1437
- if (e.pb === null) return false;
1438
- // The writer's own channels compose on the pending backing regardless.
1439
- if (inDraft(e) || getWriteOverride()) return true;
1440
- // HELD truth on an optimistic family (#3164 fold) is masked from LANE
1441
- // passes until the transaction's reveal (the backing-level twin of core
1442
- // serve()'s CONFIG_HELD_TRUTH arm; authoritative postures and latest()
1443
- // tunnel through inside heldTruthMasked). Every other reader takes the
1444
- // hold arms below: committed for no pass, the staged backing for the
1445
- // holding pass, stale-of-foreign or A29 for a foreign one.
1446
- if (heldTruthMasked(e)) return false;
1447
- const r = readerContext();
1448
- if (r === null || r.C & CONFIG_CHILDREN_FORBIDDEN) {
1449
- // No pass, or a children-forbidden one: the committed frame (A32) —
1450
- // except the speculative peek (deep()/snapshot()), which sees ordinary
1451
- // pending staging but never through a live foreign hold, and a
1452
- // projection's pending backing, authoritative-elect for context-free
1453
- // readers UNLESS a transition holds the node commits (downstream async
1454
- // hold — stale committed is the contract; the write-time stamp covers
1455
- // keys with no node, #3336) or the scope is children-forbidden (#3082).
1456
- const n = liveFoldTransition(e);
1457
- if (t) return n === null || ownsHold(n);
1458
- return e.fam !== null && r === null && !foldHeld(e) && n === null;
1459
- }
1460
- const i = liveFoldTransition(e);
1461
- // A hold changes what a reader of ONE key sees only through the fold's
1462
- // writes to that key. A key the fold left alone reads the same from either
1463
- // backing, so a pass reading it derives nothing from the hold: serve
1464
- // committed, with no transaction entry and no stale replay (#3688 — a memo
1465
- // reading an unchanged key beside an independent signal was held with
1466
- // someone else's action). Per-node reads already have this precision (a
1467
- // node with nothing staged enters nothing; stageHeldKey skips an equal
1468
- // value); this is the same rule at the container gate. `wk` is the trap's
1469
- // record of every write and delete this batch (deletes included, on the
1470
- // overlay and clone paths alike); null (a fold with no trap writes) or
1471
- // WK_ALL (an array length write) leaves the whole container held. Same
1472
- // exclusions as heldFoldTransition: an optimistic family's draft is seeded
1473
- // from node overrides the trap never saw, and a chained backing's
1474
- // committed layer is a live proxy, not a frozen twin of the clone.
1475
- if (i !== null && n !== undefined && !e.ch && e.fam?.opt !== true) {
1476
- const t = e.wk;
1477
- if (t != null && t !== WK_ALL && !t.has(n)) return false;
1478
- }
1479
- return holdVisible(i, r);
1480
- }
1481
-
1482
- /** #3164 fold: HELD truth on an optimistic family — a pending backing
1483
- * stamped by a live transition that retains optimism — is masked from LANE
1484
- * passes only (a lane paints display-ahead at the park, so it keeps
1485
- * committed until the transaction's reveal — owning transaction or not);
1486
- * the authoritative postures and latest() tunnel through, and every other
1487
- * reader is served by the ordinary hold arms (a deriving pass is held with
1488
- * the truth, A29). Un-stamped backings and optimism-free transitions keep
1489
- * ordinary mid-batch/speculation visibility. */ function heldTruthMasked(e) {
1490
- if (e.fam?.opt !== true || currentOptimisticLane === null || latestReadActive || authoritativeServe()) return false;
1491
- const t = foldBatches.get(e);
1492
- // opt families are only created by createOptimisticStore, whose module
1493
- // install populates optHooks — the assertion holds by construction.
1494
- return t !== undefined && optHooks.retainsOptimism(t);
1495
- }
1496
-
1497
- const hasOwn = Object.prototype.hasOwnProperty;
1498
-
1499
- // Allocation-free own-accessor probe (replaces eager descriptor scans — the
1500
- // single biggest creation cost in the uibench profile): Annex-B lookups
1501
- // return the fn or undefined with no descriptor object. Own data properties
1502
- // shadow prototype accessors, so hasOwn + lookup is an exact own-check.
1503
- const lookupGetter = Object.prototype.__lookupGetter__;
1504
-
1505
- const lookupSetter = Object.prototype.__lookupSetter__;
1506
-
1507
- const propertyIsEnumerable = Object.prototype.propertyIsEnumerable;
1508
-
1509
- function isOwnAccessor(e, t) {
1510
- return hasOwn.call(e, t) && (lookupGetter.call(e, t) !== undefined || lookupSetter.call(e, t) !== undefined);
1511
- }
1512
-
1513
- /** Authoritative-write wrapper exported for the optimistic module: sets the
1514
- * scheduler's projectionWriteActive through THIS module's binding (proven to
1515
- * share the instance core reads — cross-module live-binding writes from other
1516
- * store modules were observed not to propagate under the test transform). */ function runAuthoritative(e) {
1517
- const t = projectionWriteActive;
1518
- setProjectionWriteActive(true);
1519
- try {
1520
- return e();
1521
- } finally {
1522
- setProjectionWriteActive(t);
1523
- }
1524
- }
1525
-
1526
- /** The reading computation is until()'s authoritative-view predicate — same
1527
- * source of truth as core read()'s A17 carve-out (`context`, which persists
1528
- * under untrack). optimisticView()'s composition gate consults exactly this:
1529
- * write-side machinery (patch emission, tentative re-application) must keep
1530
- * composing even when it runs inside an authoritative-write bracket. */ function authoritativeRead() {
1531
- const e = context;
1532
- return e !== null && (e.C & CONFIG_AUTHORITATIVE_READ) !== 0;
1533
- }
1534
-
1535
- /** Serve-side authoritative gate: until()'s predicate PLUS truth authors —
1536
- * the projection derive's draft (wrapDraft trap brackets, runAuthoritative;
1537
- * the same posture pair ensurePB classifies drafts by). A source computing
1538
- * the next truth must never read its callers' tentative overlays: a derive
1539
- * continuation's `store.push` computing its index from an action's
1540
- * optimistic row landed truth in the wrong slot and corrupted committed
1541
- * state (#3108). Trap-level overlay serves gate on this so values, length,
1542
- * membership, and keys leave the authoritative view together. */ function authoritativeServe() {
1543
- return projectionWriteActive || getWriteOverride() || authoritativeRead();
1544
- }
1545
-
1546
- /** Context-aware node view for reads outside tracking: active override >
1547
- * held pending (owner context) > the BACKING value. Committed truth lives in
1548
- * the backing (single-home rule, O6) — node `_value` is never served here,
1549
- * so a lazy recompute's landing is immediately visible to the untracked
1550
- * reader that forced it (backing commits eagerly; node values fold at flush).
1551
- * FORCE sentinels never surface (they only bump subscribers of accessor
1552
- * keys, which are served by the trap, not the node). */ function nodeValue(e, t) {
1553
- // Store-only tunnels, ahead of Rule 1: truth authors (authoritativeServe —
1554
- // the projection derive's draft, the write-override continuation) see
1555
- // staged truth and never an override; latest() reaching this untracked
1556
- // path for a store key sees the in-flight parked value like an
1557
- // owner-context reader does (#3075), the visible override first.
1558
- let n;
1559
- if (authoritativeServe()) n = e._e !== NOT_PENDING ? e._e : t; else if (latestReadActive) n = visibleOverride(e) ? unwrapOverride(e.o?.Fe) : e._e !== NOT_PENDING ? e._e : t;
1560
- // Otherwise the one slow selection core read() uses (serve): override,
1561
- // lane gate, A28, readerSeesCommitted / A29 — with the BACKING as the
1562
- // committed value (single-home rule, O6).
1563
- else n = serve(e, readerContext(), e.De || e, t);
1564
- return n === FORCE ? t : n;
1565
- }
1566
-
1567
- /** The override a composed READER view (keys, descriptors, snapshot/deep,
1568
- * optimisticView) takes from an armed node whose override is active — the
1569
- * override itself, unless the node's own source superseded it (#3331,
1570
- * CONFIG_OVERRIDE_SUPERSEDED): then the reader-aware selection `serve`
1571
- * makes through nodeValue, so the composed view agrees with what `get`,
1572
- * `in` and `length` serve the same reader (a deriving pass: the staged
1573
- * truth; a lane pass or a context-free read: the override, A18). Composing
1574
- * the raw override left Object.keys / snapshot() / deep() one row behind the
1575
- * traps at a landing whose shape differed from the optimistic frame (F5
1576
- * parity cases). Draft and authoritative callers never reach here — the
1577
- * writer composes on hasActiveOverride, truth authors on the backing. */ function readerOverride(e, t) {
1578
- return e.C & CONFIG_OVERRIDE_SUPERSEDED ? nodeValue(e, t) : unwrapOverride(e.o?.Fe);
1579
- }
1580
-
1581
- /** §7b: a chained target's child found as a RAW — from its pending backing
1582
- * (a cloneRaw of the inner proxy, whose descriptors yield the inner store's
1583
- * raws) or from the deep() walk's descriptor read through the chain — must
1584
- * resolve to the inner family's proxy for that object before this family
1585
- * wraps it, so the overlay serves the same chained targets as the settled
1586
- * state. Otherwise every row re-wrapped as a fresh non-chained target keyed
1587
- * by the raw: identities churned for the life of an optimistic action and
1588
- * snapped back at settle, and writes landed on those orphans while deep()'s
1589
- * witnesses sat on the chained targets (#3323). The inner family owns the
1590
- * raw iff it has served it or its backing holds that exact object at `key`;
1591
- * anything else is a draft's own replacement object — view-owned, correctly
1592
- * non-chained — and is returned as is. Recurses down further chains. */ function resolveChainedRaw(e, t, n) {
1593
- const r = e.v[$TARGET];
1594
- if (r.ch) {
1595
- const e = resolveChainedRaw(r, t, n);
1596
- return e === n ? n : wrapNext(e, r, t);
1597
- }
1598
- const i = lookupTarget(n, r.fam);
1599
- if (i !== undefined) return i.px;
1600
- if ((r.v[t] === n || r.pb?.[t] === n) && isWrappable(n)) return wrapNext(n, r, t);
1601
- return n;
1602
- }
1603
-
1604
- /** Serve an own data key: node-first when a node exists (pending visibility,
1605
- * holds, lanes ride the node); backing otherwise. Chained backings (§7b: the
1606
- * backing IS another store's proxy) serve the read-through value — the outer
1607
- * node is linked only for adoption-swap notification, its value never
1608
- * shadows the live chain. */ function serveDataKey(e, t, n, r, i, l = -1) {
1609
- const o = e.ch && r === e.v;
1610
- let s = n;
1611
- // §6: on optimistic arrays LENGTH IS A VIEW, not a node value — one home
1612
- // (backing ± presence overrides) for both length and indices makes torn
1613
- // iteration impossible (a length node's value rides different visibility
1614
- // rails than index overrides mid-settle). The node carries subscriptions;
1615
- // its committed `_value` is never served here.
1616
- if (t === "length" && e.fam?.opt === true && !o && Array.isArray(r)) {
1617
- if (!inDraft(e)) {
1618
- const i = e.n?.length;
1619
- if (i !== undefined) {
1620
- // An override-covered length node answers through the node's
1621
- // reader-aware selection (serve: A17, the lane gate, A18
1622
- // supersession) with the backing's length as committed — the rule
1623
- // every index node takes through `get`, so `length` and indices
1624
- // resolve against ONE rule for this reader. The view composition
1625
- // below used to answer here and knows nothing of supersession
1626
- // (#3331): at a landing whose length differs from the optimistic
1627
- // frame, mapArray's tracked `length` (the get trap's node path) saw
1628
- // the staged truth while its untracked `slice` inside its owner saw
1629
- // the override, so `_items` came up short and the next pass keyed an
1630
- // undefined row (F5, optimistic-list-mutation-matrix `differ`).
1631
- if (getObserver() !== null) {
1632
- const e = read(i);
1633
- if (hasActiveOverride(i) && !authoritativeServe()) return e === FORCE ? r.length : e;
1634
- } else if (hasActiveOverride(i) && !authoritativeServe()) {
1635
- return nodeValue(i, r.length);
1636
- }
1637
- } else if (getObserver() !== null) {
1638
- read(getNode(e, t, n));
1639
- }
1640
- }
1641
- // Truth authors read the backing's own length — an optimistic row from
1642
- // the caller's transaction must not shift where the author's next write
1643
- // lands (#3108). A tentative draft that has seeded its backing from the
1644
- // view already carries the overrides in `src` (the draft arm every other
1645
- // channel gates on draftSeesOverrides — `get`, `has`, visibleKeys,
1646
- // snapshotWalk, #3665): composing them again put a slot the draft had
1647
- // just spliced out back on top of its shrunken backing, so `length` read
1648
- // one too long mid-splice and the second splice left a hole (F3,
1649
- // optimistic-list-mutation-matrix "move head->tail + move middle").
1650
- return (authoritativeServe() || inDraft(e) && !draftSeesOverrides(e) ? r : optHooks.optimisticView(e, r, inDraft(e))).length;
1651
- }
1652
- if (inDraft(e)) {
1653
- // Optimistic drafts before their first write have no pending backing yet;
1654
- // reads must still see the live optimistic view (compose, not clobber —
1655
- // #2951). Once ensurePB runs, the seeded clone carries the view.
1656
- // AUTHORITATIVE drafts (projection derive) never overlay — ensurePB's
1657
- // seeding rule, applied to the read side (#3108).
1658
- if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
1659
- const n = e.n?.[t];
1660
- if (n !== undefined && hasActiveOverride(n)) s = unwrapOverride(n.o?.Fe);
1661
- }
1662
- } else {
1663
- // §7b: a lane value on the outer node SHADOWS read-through — an active
1664
- // override pierces the chained gate; otherwise chained backings always
1665
- // serve the live inner value.
1666
- if (getObserver() !== null) {
1667
- // First tracked read: create + link (the wrap-cache branch below
1668
- // populates px/pxv so read #2 skips wrapNext, slice 2). The value is
1669
- // served THROUGH the node from this read on — a node born under a held
1670
- // fold carries the hold (getNode, #3336), and the backing it was read
1671
- // from does not.
1672
- if (i === undefined) i = getNode(e, t, n, l);
1673
- // read()'s plain-signal fast path hoisted over the call (legacy trap
1674
- // parity): READ_SLOW = a global read window or non-plain node.
1675
- let r = readNodeFast(i);
1676
- if (r === READ_SLOW) r = read(i);
1677
- if (!o || hasActiveOverride(i)) s = r === FORCE ? n : r;
1678
- } else if (i !== undefined && (!o || hasActiveOverride(i))) {
1679
- s = nodeValue(i, n);
1680
- }
1681
- }
1682
- // Shallow stores serve data raw; store-proxy slots get boundary wrappers.
1683
- if (e.s) return serveShallow(e, t, s);
1684
- if (e.ch && !o && s !== null && typeof s === "object" && s[$TARGET] === undefined) s = resolveChainedRaw(e, t, s);
1685
- if (i !== undefined) {
1686
- // Wrap cache (see getNode): only wrappables are ever cached, so a hit
1687
- // skips isWrappable too — pointer-compare replaces both checks.
1688
- if (i.pxv === s && s !== undefined) return draftServe(e, i.px);
1689
- if (!isWrappable(s)) return s;
1690
- const n = wrapNext(s, e, t);
1691
- i.px = n;
1692
- i.pxv = s;
1693
- return draftServe(e, n);
1694
- }
1695
- if (!isWrappable(s)) return s;
1696
- return draftServe(e, wrapNext(s, e, t));
1697
- }
1698
-
1699
- /** §6c store-wide status gate for reads that DON'T flow through a node:
1700
- * untracked/raw fallthrough must still throw while the derive is
1701
- * uninitialized (seed invisibility, proj R23) or errored (memo parity).
1702
- * TRACKED reads never call this — store nodes carry `_firewall`, so core
1703
- * read() links the node and throws the firewall's error itself (the node
1704
- * link is what wakes async-memo readers when the landing writes values;
1705
- * the firewall link rides the same read). */ function firewallGate(e) {
1706
- // Own-draft ops are exempt: an async derive's continuation (generator body
1707
- // after an `await`/`yield`) runs OUTSIDE the sync write scope (inDraft is
1708
- // already false), but its draft-proxy traps mark every op with the write
1709
- // override. Those reads are the derive working its own draft (state.push
1710
- // reading .length) — gating them throws NotReadyError back into the derive
1711
- // itself, which the post-await read diagnostic (#2987) then escalates to a
1712
- // reactivity halt. The gate exists for EXTERNAL readers (seed invisibility,
1713
- // proj R23); the derive is the author.
1714
- if (projectionWriteActive || getWriteOverride()) return;
1715
- const t = e.fam?.node;
1716
- if (t != null && t.h & (STATUS_UNINITIALIZED | STATUS_ERROR)) read(t);
1717
- }
1718
-
1719
- /** latest() pull (#3075): bring the projection computed up to date so the
1720
- * read serves the IN-FLIGHT derivation — signal/memo parity, where core
1721
- * read() routes latest() through a companion that recomputes speculatively.
1722
- * The latest flag is suspended for the recompute (the derive's own reads
1723
- * are normal reads), and latestPullActive marks any adoption it commits as
1724
- * staged (see adoptPB) — the speculative swap must not leak to
1725
- * committed-visibility readers before the flush. */ function pullProjectionForLatest(e) {
1726
- const t = e.fam.node;
1727
- if (t == null) return;
1728
- const n = latestReadActive;
1729
- setLatestReadActive(false);
1730
- const r = latestPullActive;
1731
- latestPullActive = true;
1732
- try {
1733
- prepareComputed(t, true);
1734
- } finally {
1735
- latestPullActive = r;
1736
- setLatestReadActive(n);
1737
- }
1738
- }
1739
-
1740
- const traps = {
1741
- get(e, t, n) {
1742
- // One typeof gates every brand-symbol compare off the hot string path
1743
- // (four symbol comparisons per property read otherwise).
1744
- if (typeof t !== "string") {
1745
- if (t === $TARGET) return e;
1746
- if (t === $PROXY) return n;
1747
- if (t === $OWNER) return undefined;
1748
- // ownership stamp: never a user key
1749
- if (t === $RECORD) return undefined;
1750
- // a store is no view (see `viewOf`)
1751
- // refresh()/isPending resolve the projection computed through $REFRESH.
1752
- if (t === $REFRESH) return e.fam?.node ?? undefined;
1753
- if (t === $TRACK) {
1754
- if (pendingCheckActive) witnessAffectsMark(e, t);
1755
- if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
1756
- if (!inDraft(e) && getObserver() !== null) {
1757
- read(getKeySetNode(e));
1758
- // Structural chaining (§7b, #2864 / core R21): a chained backing's
1759
- // $TRACK reads through to the INNER store's key-set — structural
1760
- // notifications land on the source's own node, never on this
1761
- // wrapper view's.
1762
- const t = readSource(e);
1763
- if (t[$TARGET] !== undefined) t[$TRACK];
1764
- }
1765
- return undefined;
1766
- }
1767
- // user symbols fall through to the generic path
1768
- }
1769
- if (pendingCheckActive) witnessAffectsMark(e, t);
1770
- if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
1771
- // latest() pull (#3075): store traps never reach core read() without an
1772
- // observer, so bring the projection computed up to date here — signal/
1773
- // memo parity for latest() reads through a projection.
1774
- if (e.fam !== null && latestReadActive && !inDraft(e) && !getWriteOverride()) pullProjectionForLatest(e);
1775
- const r = readSource(e, t);
1776
- // Overlay delete (#3044): a prototype overlay cannot shadow a delete, so
1777
- // deleted keys are tracked aside and read as absent in the pending view.
1778
- if (e.del !== null && r === e.pb && e.del.has(t)) {
1779
- if (!inDraft(e) && getObserver() !== null) read(getNode(e, t, undefined));
1780
- return undefined;
1781
- }
1782
- // Hot inline case: existing PLAIN node (non-accessor), unchained backing,
1783
- // tracked read of a present data key — the dbmon/uibench effect re-read
1784
- // shape. Skips serveDataKey's frame, the FORCE compare (only accessor
1785
- // keys ever hold the sentinel), and isWrappable for primitives.
1786
- // ONE node-map lookup serves this block and the accessor probe below
1787
- // (nothing between them creates nodes).
1788
- const i = e.n?.[t];
1789
- if (e.ch === false && writeScopes === null) {
1790
- const n = i;
1791
- if (n !== undefined && n.acc !== true && getObserver() !== null) {
1792
- let r = readNodeFast(n);
1793
- if (r === READ_SLOW) r = read(n);
1794
- if (r === null || typeof r !== "object") return r;
1795
- if (e.s) return serveShallow(e, t, r);
1796
- if (n.pxv === r) return n.px;
1797
- if (isWrappable(r)) {
1798
- const i = wrapNext(r, e, t);
1799
- n.px = i;
1800
- n.pxv = r;
1801
- return i;
1802
- }
1803
- return r;
1804
- }
1805
- }
1806
- // Accessor keys serve through Reflect.get with the PROXY receiver
1807
- // (R20/R29: internal reads track; the node is linked for shape-change
1808
- // notification but its value is never served). Accessor-ness comes from
1809
- // the node's cached flag; the first TRACKED read (which creates the
1810
- // node) probes once — untracked node-less reads take the plain path,
1811
- // where a raw-receiver getter still returns correct committed values.
1812
- // Tracking suppression is PER-TARGET (inDraft), never global: `writing`
1813
- // counts every open setter anywhere, and a projection derive runs its
1814
- // whole body inside one — a global gate silently swallowed EXTERNAL
1815
- // absent-key/accessor subscriptions for every store read during any
1816
- // derive, leaving nested projections permanently dependency-less when
1817
- // their sources hadn't materialized yet (#3037).
1818
- // First-read dedupe (create-floor slice 2): remember the probe verdict —
1819
- // node creation downstream reuses it instead of re-scanning the
1820
- // descriptor, but only when the probed object IS the one getNode would
1821
- // scan (pb ?? v).
1822
- let l = -1;
1823
- {
1824
- let o;
1825
- if (i !== undefined) o = i.acc === true; else if (!inDraft(e) && getObserver() !== null) {
1826
- o = isOwnAccessor(r, t);
1827
- if (r === (e.pb ?? e.v)) l = o ? 1 : 0;
1828
- } else o = false;
1829
- if (o) {
1830
- if (!inDraft(e) && getObserver() !== null) read(i ?? getNode(e, t, undefined, l));
1831
- const o = Reflect.get(r, t, n);
1832
- if (e.s) return serveShallow(e, t, o);
1833
- return isWrappable(o) ? draftServe(e, wrapNext(o, e, t)) : o;
1834
- }
1835
- }
1836
- // Plain-data fast path: no descriptor allocation per read.
1837
- // Inherited pollution keys are never served (core R30) — checked before
1838
- // the proto-function branch can leak `constructor`. Interned-string
1839
- // compares beat a Set hash on this per-read path. Overlay pending
1840
- // backings chain to the committed backing, so "own in the view" means
1841
- // own on either layer (ownInView) — a genuine prototype method is one
1842
- // that is own on NEITHER.
1843
- const o = e.ovl && r === e.pb;
1844
- if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return undefined;
1845
- let s = r[t];
1846
- if (s === undefined ? !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t)) : false) {
1847
- // Inherited: prototype getters/methods run with the proxy receiver.
1848
- s = Reflect.get(r, t, n);
1849
- if (typeof s === "function") return s;
1850
- // proto methods untracked
1851
- // Reading a currently-absent own key subscribes to it (R12) — for any
1852
- // target OUTSIDE its own draft scope, even mid-setter (#3037, above).
1853
- if (s === undefined && !inDraft(e)) {
1854
- if (getObserver() !== null) read(getNode(e, t, undefined, l));
1855
- const n = e.n?.[t];
1856
- if (n) {
1857
- const r = nodeValue(n, undefined);
1858
- if (e.s) return serveShallow(e, t, r);
1859
- return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
1860
- }
1861
- } else if (s === undefined && inDraft(e) && e.fam?.opt && draftSeesOverrides(e) &&
1862
- // AUTHORITATIVE drafts (landing folds) never seed from overrides —
1863
- // the caller's optimism is not truth (has-trap twin below).
1864
- !authoritativeServe()) {
1865
- const n = e.n?.[t];
1866
- // The draft is a WRITER channel (hasActiveOverride's rule): it
1867
- // composes on the tick's own unflushed adds, as the has trap's draft
1868
- // arm, visibleKeys and optimisticView do. Reader-gating this arm
1869
- // (visibleOverride) made a second setter in the same action read
1870
- // `undefined` where the first had pushed a row (#3665, rc.9).
1871
- if (n !== undefined && hasActiveOverride(n)) s = unwrapOverride(n.o?.Fe);
1872
- }
1873
- if (e.s) return serveShallow(e, t, s);
1874
- return isWrappable(s) ? draftServe(e, wrapNext(s, e, t)) : s;
1875
- }
1876
- if (typeof s === "function" && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return s;
1877
- // proto method
1878
- return serveDataKey(e, t, s, r, i, l);
1879
- },
1880
- has(e, t) {
1881
- if (t === $TARGET || t === $PROXY || t === $TRACK) return true;
1882
- if (t === $OWNER || t === $RECORD) return false;
1883
- if (pendingCheckActive) witnessAffectsMark(e, t);
1884
- if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
1885
- const n = readSource(e, t);
1886
- let r = t in n;
1887
- // Overlay deletes read as absent in the pending view (#3044).
1888
- if (r && e.del !== null && n === e.pb && e.del.has(t)) r = false;
1889
- if (!inDraft(e)) {
1890
- if (getObserver() !== null) {
1891
- const n = getHasNode(e, t, r);
1892
- // Authoritative-view readers get the right answer for free: core read()
1893
- // skips the override arm for them, so nv is authoritative presence.
1894
- const i = read(n);
1895
- if (hasActiveOverride(n)) r = !!i;
1896
- } else if (!authoritativeServe()) {
1897
- const n = e.h?.[t];
1898
- // The get trap's untracked selection (nodeValue → serve), so `in`
1899
- // agrees with the tracked branch above and with `get`: a presence
1900
- // override the landing superseded (#3331 — an optimistic delete
1901
- // whose slot the truth refilled) answers the staged truth for a
1902
- // deriving reader. Reading the override directly said "absent" while
1903
- // `get` served the landed row, and HasProperty-driven copies (slice,
1904
- // spread, map) left a hole (F5 `differ: delete *`).
1905
- if (n !== undefined && hasActiveOverride(n)) r = !!nodeValue(n, r);
1906
- }
1907
- } else if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
1908
- const n = e.h?.[t];
1909
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Fe);
1910
- }
1911
- return r;
1912
- },
1913
- ownKeys(e) {
1914
- if (pendingCheckActive) witnessAffectsMark(e);
1915
- if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
1916
- if (!inDraft(e) && getObserver() !== null) read(getKeySetNode(e));
1917
- return visibleKeys(e, readSource(e));
1918
- },
1919
- getOwnPropertyDescriptor(e, t) {
1920
- if (t === $OWNER || t === $RECORD) return undefined;
1921
- // A descriptor read is a PRESENCE read: it subscribes to the key's
1922
- // presence node and witnesses affects()/isPending() exactly as `in` does
1923
- // (structural oracle, 2026-09-17 — the trap read no node before, so a
1924
- // render effect inspecting a key through getOwnPropertyDescriptor never
1925
- // re-ran for an optimistic add or delete, and an isPending() probe over
1926
- // it witnessed nothing). The value it reports rides the value node's
1927
- // view through visibleDescriptor.
1928
-
1929
- // EXCEPT for an enumerator (#3664): Object.keys / for...in / spread /
1930
- // Object.entries / JSON.stringify take this trap once per key right
1931
- // after `ownKeys`, which already subscribed the observer to the key-set
1932
- // node — and that node bumps on every membership change, committed or
1933
- // optimistic, so a presence node per key adds nothing the enumerator
1934
- // can observe (rc.9 birthed one per key per object: ~640 B and a graph
1935
- // node each, 10x the memory of a 30-key row's reader). When the
1936
- // observer holds the key-set node in THIS pass, skip the presence read;
1937
- // a lone descriptor read keeps its per-key precision.
1938
- if (pendingCheckActive) witnessAffectsMark(e, t);
1939
- const n = getObserver();
1940
- if (e.fam !== null && n === null && !inDraft(e)) firewallGate(e);
1941
- const r = readSource(e, t);
1942
- const i = visibleDescriptor(e, r, t);
1943
- if (!inDraft(e) && n !== null && !observerHoldsKeySet(e, n)) {
1944
- // The node is born from the source's presence (as `has` births it),
1945
- // not the override-adjusted answer.
1946
- let n = t in r;
1947
- if (n && e.del !== null && r === e.pb && e.del.has(t)) n = false;
1948
- read(getHasNode(e, t, n));
1949
- }
1950
- if (i === undefined) return undefined;
1951
- // Array targets carry a real non-configurable `length` the proxy
1952
- // invariant forces us to report faithfully; everything else reports
1953
- // configurable via target indirection (core R51).
1954
- if (!(t === "length" && Array.isArray(e))) i.configurable = true;
1955
- return i;
1956
- },
1957
- set(e, t, n) {
1958
- // Writes require the target's draft scope OR the projection write
1959
- // override (post-await async draft writes arrive outside any window);
1960
- // everything else is silently ignored (R23).
1961
- const r = inDraft(e);
1962
- const i = !r && getWriteOverride();
1963
- if (!r && !i) return true;
1964
- if (t === "__proto__") return true;
1965
- // pollution guard (core R30)
1966
- // Unwrap BEFORE ensurePB: unwrapValue materializes a self-referencing
1967
- // draft's overlay (replacing target.pb), so a pb local captured earlier
1968
- // would be the abandoned overlay and the write would vanish.
1969
- // Shallow slots store what was written VERBATIM — another store's proxy
1970
- // passes through by reference (#2932; markRawOne skips proxies), while
1971
- // deep stores unwrap to raw backings.
1972
- const l = e.s ? n : unwrapValue(n);
1973
- const o = ensurePB(e);
1974
- pendingNotify.add(e);
1975
- // Array length writes implicitly delete indices — the written-keys bound
1976
- // can't see them, so poison to the full scan for this batch. Index
1977
- // writes implicitly GROW length, so arrays always record it alongside.
1978
- if (Array.isArray(o)) {
1979
- if (t === "length") e.wk = WK_ALL; else if (e.wk !== WK_ALL) {
1980
- const n = e.wk ??= new Set;
1981
- n.add(t);
1982
- n.add("length");
1983
- }
1984
- } else {
1985
- if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1986
- // Live own-key estimate for the overlay/clone choice (#3360) and the
1987
- // overlay's commit choice (#3689): `in` sees through an overlay to the
1988
- // committed keys, so this counts keys NEW to the container. Overlay
1989
- // deletes are counted down when they commit (`del` is exact); clone-
1990
- // path deletes are not (a stale high count on a narrow container only
1991
- // picks the overlay a little early).
1992
- if (!(t in o)) e.kc++;
1993
- }
1994
- // Own data keys literally named "prototype"/"constructor" land as data —
1995
- // defineProperty sidesteps a proto-chain setter named the same.
1996
- if (UNSAFE_KEYS.has(t)) {
1997
- Object.defineProperty(o, t, {
1998
- value: l,
1999
- writable: true,
2000
- enumerable: true,
2001
- configurable: true
2002
- });
2003
- if (e.del !== null) e.del.delete(t);
2004
- return true;
2005
- }
2006
- // Overlay first-write DEFINES the own key: assignment through the proto
2007
- // chain would reject on a non-writable committed property (the clone
2008
- // path normalized descriptors for exactly this — R51 parity). A
2009
- // plain-data-graded backing (sc 2, owned: every slot writable) takes the
2010
- // bare assignment — it lands as an own key on the overlay all the same.
2011
- if (e.ovl && e.sc !== 2 && !hasOwn.call(o, t)) {
2012
- Object.defineProperty(o, t, {
2013
- value: l,
2014
- writable: true,
2015
- enumerable: true,
2016
- configurable: true
2017
- });
2018
- } else o[t] = l;
2019
- if (e.del !== null) e.del.delete(t);
2020
- // Shallow ingest: written records are sticky raw-marked (one entity is
2021
- // never both deep-wrapped and raw — R41/#2932, shared invariant).
2022
- if (e.s && l !== null && typeof l === "object") markRawOne(l);
2023
- // Override-mode (post-await draft) writes have no setter exit — notify
2024
- // per-op (setSignal equality-gates repeats).
2025
- if (i) notifyWrites(e);
2026
- return true;
2027
- },
2028
- defineProperty(e, t, n) {
2029
- const r = inDraft(e);
2030
- const i = !r && getWriteOverride();
2031
- if (!r && !i) return true;
2032
- if (t === "__proto__") return true;
2033
- if (n.get || n.set) e.a = true;
2034
- // Unwrap before ensurePB (see the set trap: self-reference materializes).
2035
- if ("value" in n) n = {
2036
- ...n,
2037
- value: unwrapValue(n.value)
2038
- };
2039
- const l = ensurePB(e);
2040
- // A non-default data descriptor (or an accessor) leaves the plain-data
2041
- // grade: the key reaches the committed backing as defined, so the spread
2042
- // clone and bare-assignment paths no longer describe it.
2043
- if (e.a || !(n.enumerable && n.writable && n.configurable)) e.sc = 1;
2044
- pendingNotify.add(e);
2045
- if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
2046
- Object.defineProperty(l, t, n);
2047
- if (e.del !== null) e.del.delete(t);
2048
- if (i) notifyWrites(e);
2049
- return true;
2050
- },
2051
- deleteProperty(e, t) {
2052
- const n = inDraft(e);
2053
- const r = !n && getWriteOverride();
2054
- if (!n && !r) return true;
2055
- const i = ensurePB(e);
2056
- pendingNotify.add(e);
2057
- if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
2058
- delete i[t];
2059
- // A prototype overlay cannot shadow a delete of a committed key —
2060
- // record it aside (#3044); reads/has/ownKeys/commit consult the set.
2061
- if (e.ovl && hasOwn.call(e.v, t)) (e.del ??= new Set).add(t);
2062
- if (r) notifyWrites(e);
2063
- return true;
2064
- }
2065
- };
2066
-
2067
- /** Low-level setter primitive: opens write mode on a next proxy, runs `fn`,
2068
- * emits write-time notifications at outermost exit, applies returned
2069
- * replacements as adoptions. `guard=false` skips the dev guards (owned-scope
2070
- * write, thenable result) — projection recomputes legitimately write from
2071
- * inside their computed, and their async derive is handled by the recompute,
2072
- * not returned through here. */
2073
- /** The derived store (`createStore(fn)`) whose user setter is running, and
2074
- * whether one of its leaf notifications hit a staging another transaction
2075
- * holds as the fold's result (A34 amendment, #3612; core heldDerivation). */ let derivedSetter = null;
2076
-
2077
- let heldDerivationHit = false;
2078
-
2079
- /** The derived store's setter (CS-R31): within a synchronous frame the manual
2080
- * write wins — the projection's recompute is masked for the tick. Across a
2081
- * hold the write is not a proposal: a leaf the fold staged under another
2082
- * transaction re-runs the fold under it, the write being the draft's prior
2083
- * state (#3612). Decided from the leaf notifications, so the mask lands
2084
- * after them (in the `finally`: a throwing setter's writes before the throw
2085
- * were notified, and are masked as before). */ function derivedStoreWrite(e, t, n) {
2086
- derivedSetter = e;
2087
- heldDerivationHit = false;
2088
- try {
2089
- storeSetterNext(t, n);
2090
- } finally {
2091
- derivedSetter = null;
2092
- heldDerivationHit ? rederiveHeld(e) : suppressComputedRecompute(e);
2093
- }
2094
- }
2095
-
2096
- function storeSetterNext(e, t, n = true) {
2097
- const r = e[$TARGET];
2098
- const i = writeScopes;
2099
- writeScopes = new Set;
2100
- writeScopes.add(scopeKey(r));
2101
- writing++;
2102
- let l;
2103
- try {
2104
- // No untrack: the writing flag already disables store-node linking
2105
- // (draft reads never self-track, proj R2), while EXTERNAL reads (signals
2106
- // inside a projection derive) must keep tracking — they are the derive's
2107
- // dependencies.
2108
- l = t(e);
2109
- } finally {
2110
- writing--;
2111
- writeScopes = i;
2112
- // Outermost setter exit: emit write-time notifications (setSignal per
2113
- // changed observed key) so transition holds and lanes engage now.
2114
- if (writing === 0 && pendingNotify.size) {
2115
- const e = [ ...pendingNotify ];
2116
- pendingNotify.clear();
2117
- for (const t of e) notifyWrites(t);
2118
- }
2119
- }
2120
- if (l !== undefined && l !== e && isWrappable(l)) {
2121
- // Returned replacement: on an optimistic family (outside authoritative
2122
- // writes) the replacement is itself an optimistic edit — diff it against
2123
- // the visible view as engine writes (reverts at settle). Otherwise it is
2124
- // an adoption of the incoming object (unowned).
2125
- if (r.fam?.opt && !projectionWriteActive && !getWriteOverride()) {
2126
- optHooks.notifyOptimisticWrites(r, unwrapValue(l));
2127
- } else {
2128
- adoptPB(r, unwrapValue(l));
2129
- }
2130
- }
2131
- if (writing === 0 && heldAdoptions.size) stageHeldAdoptions();
2132
- }
2133
-
2134
- // Affects integration: the legacy affects machinery reads next targets
2135
- // structurally (aliased field names); only node CREATION dispatches here.
2136
- setNextAffectsNodeResolver((e, t) => t === $AFFECTS ? getNode(e, $AFFECTS, undefined) : getNode(e, t, (e.pb ?? e.v)[t]));
2137
-
2138
- function createStoreNext(e, t = false) {
2139
- const n = wrapNext(e);
2140
- if (t) {
2141
- n[$TARGET].s = true;
2142
- markRawIngest(e);
2143
- }
2144
- const setter = e => storeSetterNext(n, e);
2145
- return [ n, setter ];
2146
- }
2147
-
2148
- // ---------------------------------------------------------------------------
2149
- // snapshot (next targets): the backing IS the plain raw graph — zero copy.
2150
- // Sees pending (R27) by reading pb. Chained/owned-copy caching lands with the
2151
- // utilities increment; this covers the createStore-suite contract.
2152
- /** True when `proxy` is a SHALLOW store (children served verbatim, slots
2153
- * replaced by reference — #2932). The list driver uses this to choose the
2154
- * slot-patch channel (collected row bodies) over per-record registration. */ function storeIsShallow(e) {
2155
- const t = e?.[$TARGET];
2156
- return t !== undefined && t.s === true;
2157
- }
2158
-
2159
- /** True when `proxy` belongs to a projection/optimistic FAMILY. The list
2160
- * driver must DECLINE family arrays (external audit finding): family
2161
- * structural changes never emit row/slot ops (the setter channel is
2162
- * fam-gated; optimistic writes ride node overrides), and the proxy identity
2163
- * is stable so the each-watch cannot catch the change either — an engaged
2164
- * list would freeze on optimistic/projection structural updates. Record-
2165
- * level family patches are unaffected (they have their own emission). */ function storeHasFamily(e) {
2166
- const t = e?.[$TARGET];
2167
- return t !== undefined && t.fam !== null;
2168
- }
2169
-
2170
- /** True when `proxy` belongs to an OPTIMISTIC family specifically. The list
2171
- * driver declines these (audit finding, narrowed): optimistic user writes
2172
- * ride node-level overrides — they never enter the reconcile walk, so no
2173
- * row/slot ops are emitted and an engaged list would freeze on optimistic
2174
- * structural changes. PROJECTION (non-optimistic) families are drivable:
2175
- * their recomputes go through the reconcile walk, whose emissions are
2176
- * transition-stamped in the apply queue like any other (equivalence-matrix
2177
- * gated). Re-admitting optimistic families requires a lane-timed structural
2178
- * emission mirroring emitPatchOptimistic, plus revert resync. */ function storeHasOptimisticFamily(e) {
2179
- const t = e?.[$TARGET];
2180
- return t !== undefined && t.fam?.opt === true;
2181
- }
2182
-
2183
- // ---------------------------------------------------------------------------
2184
- // visibility: what a reader sees on a record — ONE rule for the ownKeys /
2185
- // getOwnPropertyDescriptor traps and the deep() walk (#3323). The walk used
2186
- // to re-derive the trap rules over raw backings and missed each new one in
2187
- // turn (the #3044 overlay merge, then #3323's chain, then optimistic presence
2188
- // and value overrides); sharing the body makes parity structural.
2189
- /** Keys visible on `target` served from `src` (= readSource(target)): the
2190
- * pending-overlay merge (#3044) and, on optimistic families, presence-node
2191
- * overrides (§6, FINDING-2's fix). Draft reads before the first write overlay
2192
- * too (pb, once created, is seeded with the view). Authoritative-view reads
2193
- * (until()'s predicate, truth-author drafts) skip the overlay. */ function visibleKeys(e, t) {
2194
- let n;
2195
- if (e.ovl && t === e.pb) {
2196
- // Overlay merge (#3044): committed keys in their order, then this
2197
- // batch's NEW keys, minus deletes.
2198
- n = Reflect.ownKeys(e.v);
2199
- const r = e.del;
2200
- if (r !== null && r.size !== 0) n = n.filter(e => !r.has(e));
2201
- for (const r of Reflect.ownKeys(t)) {
2202
- if (!hasOwn.call(e.v, r)) n.push(r);
2203
- }
2204
- } else n = Reflect.ownKeys(t);
2205
- // Drop the $OWNER stamp (owned backings carry it as an own enumerable
2206
- // symbol). Symbols enumerate last, so the scan stops at the first string.
2207
- for (let e = n.length - 1; e >= 0 && typeof n[e] === "symbol"; e--) {
2208
- if (n[e] === $OWNER) {
2209
- n.splice(e, 1);
2210
- break;
2211
- }
2212
- }
2213
- if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || draftSeesOverrides(e))) {
2214
- let t = null;
2215
- const r = inDraft(e);
2216
- for (const i of Reflect.ownKeys(e.h)) {
2217
- const l = e.h[i];
2218
- if (!(r ? hasActiveOverride(l) : visibleOverride(l))) continue;
2219
- t ??= new Set(n);
2220
- // Reader arm: a superseded presence override answers as `in` does
2221
- // (readerOverride, #3331) — Object.keys listed one row fewer than the
2222
- // traps served after a landing refilled an optimistically deleted slot.
2223
- const o = r ? unwrapOverride(l.o?.Fe) : readerOverride(l, t.has(i));
2224
- if (o) t.add(i); else t.delete(i);
2225
- }
2226
- if (t !== null) return [ ...t ];
2227
- }
2228
- return n;
2229
- }
2230
-
2231
- /** The data/accessor descriptor visible for `key` on `target` served from
2232
- * `src`: overlay (#3044 — unwritten keys live on the committed backing,
2233
- * deleted keys are absent) and optimistic presence overrides (an opt delete
2234
- * hides the key; an opt add synthesizes a data descriptor from the value
2235
- * node). `configurable` is the trap's concern (proxy invariant). */ function visibleDescriptor(e, t, n) {
2236
- let r = Object.getOwnPropertyDescriptor(t, n);
2237
- if (e.ovl && t === e.pb) {
2238
- if (e.del !== null && e.del.has(n)) return undefined;
2239
- if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, n);
2240
- }
2241
- // Draft arm, the twin of visibleKeys' (#3665): a draft still composing on
2242
- // the live view (no view-seeded backing yet) serves the writer rule
2243
- // (hasActiveOverride); once ensurePB has seeded the draft's own backing,
2244
- // `src` carries the view. Without it ownKeys listed a key the descriptor
2245
- // reported absent, and every enumerator — Object.keys, spread, entries,
2246
- // JSON.stringify, deep() — dropped the row a previous setter had added.
2247
- const i = inDraft(e);
2248
- if (!authoritativeServe() && e.fam?.opt && (!i || draftSeesOverrides(e))) {
2249
- const t = e.h?.[n];
2250
- if (t !== undefined && (i ? hasActiveOverride(t) : visibleOverride(t))) {
2251
- // Reader arm: presence as `in` serves it (readerOverride, #3331).
2252
- const l = i ? unwrapOverride(t.o?.Fe) : readerOverride(t, r !== undefined);
2253
- if (!l) return undefined;
2254
- // opt delete
2255
- if (r === undefined) {
2256
- const t = e.n?.[n];
2257
- return {
2258
- // In a draft the value is the override itself (nodeValue routes
2259
- // through serve — the reader rule — and would answer undefined).
2260
- value: t === undefined ? undefined : i ? hasActiveOverride(t) ? unwrapOverride(t.o?.Fe) : undefined : nodeValue(t, undefined),
2261
- writable: true,
2262
- enumerable: true,
2263
- configurable: true
2264
- };
2265
- }
2266
- }
2267
- }
2268
- return r;
2269
- }
2270
-
2271
- /** Tracking deep snapshot (`deep()` for next targets): subscribes to the
2272
- * key-set and deep-witness node at every reachable level, then returns the
2273
- * plain view. Shared references and cycles handled via the visited set. */ function deepNext(e) {
2274
- const t = e?.[$TARGET];
2275
- if (t === undefined || t.px !== e) return e;
2276
- const n = new Set;
2277
- // One membership node + one deep-witness node PER RECORD (legacy $TRACK
2278
- // parity): the walk stays O(records) in subscriptions instead of O(paths)
2279
- // in per-key nodes, and it walks TARGETS directly — no per-child proxy
2280
- // round-trip (wrapNext → proxy → $TARGET trap) on the re-walk every
2281
- // effect run performs.
2282
- // Resolve `child` (a raw or a stored proxy found under `t`) to the target
2283
- // that `t`'s family serves for it — created on first visit, as the get trap
2284
- // would. Undefined = leaf (unwrappable or raw-marked).
2285
- const childTarget = (e, t, n) => {
2286
- let r = lookupTarget(t, e.fam);
2287
- if (r === undefined) {
2288
- if (!isWrappable(t)) return undefined;
2289
- wrapNext(t, e, n);
2290
- // Stored proxies (chained slots) that this family passes through
2291
- // resolve to their own target.
2292
- r = lookupTarget(t, e.fam) ?? t[$TARGET];
2293
- }
2294
- return r;
2295
- };
2296
- const walkT = e => {
2297
- const t = readSource(e);
2298
- if (n.has(t)) return;
2299
- n.add(t);
2300
- read(getKeySetNode(e));
2301
- read(getDeepNode(e));
2302
- // Chained backing (§7b): the committed backing IS another store's proxy.
2303
- // Writes to the inner store bump the INNER record's witnesses; this
2304
- // target's k/dk hear only its own family's writes (optimistic overrides).
2305
- // Read through the whole chain — the $TRACK trap's rule (#2864 / R21)
2306
- // applied to deep() (#3323). From `t.v`, not `src`: a pending backing is a
2307
- // raw clone and would hide the chain. Non-chained targets pay one flag.
2308
- for (let t = e; t.ch; ) {
2309
- t = t.v[$TARGET];
2310
- read(getKeySetNode(t));
2311
- read(getDeepNode(t));
2312
- }
2313
- // Keys and children exactly as the traps serve them — the overlay merge
2314
- // (#3044/#3283: a bare ownKeys over a pending backing mid-flush dropped
2315
- // every untouched child from the re-subscribing effect's dependencies)
2316
- // and optimistic presence overrides (a row added under a held action
2317
- // lives in `h`/`n`, not the committed backing; the walk never reached its
2318
- // record, so deep() was deaf to every write on it until settle).
2319
- const r = e.fam?.opt === true;
2320
- for (const n of visibleKeys(e, t)) {
2321
- const i = visibleDescriptor(e, t, n);
2322
- if (i === undefined) continue;
2323
- if (i.get || i.set) {
2324
- e.a = true;
2325
- continue;
2326
- // accessors track through their own reads when invoked
2327
- }
2328
- let l = i.value;
2329
- // An active value override on an optimistic node shadows the backing's
2330
- // child (an optimistic replacement `d[i] = {...}`) — follow what reads
2331
- // serve, as nodeValue's leading arm does.
2332
- if (r) {
2333
- const t = e.n?.[n];
2334
- if (t !== undefined && hasActiveOverride(t) && !authoritativeServe()) l = unwrapOverride(t.o.Fe);
2335
- }
2336
- if (l === null || typeof l !== "object") continue;
2337
- // Through a chain the descriptor yields the INNERMOST raw: resolve it to
2338
- // the inner family's proxy so this level wraps the chained `view[i]` the
2339
- // get trap serves, never a fresh non-chained wrapper of the base raw.
2340
- if (e.ch && l[$TARGET] === undefined) l = resolveChainedRaw(e, n, l);
2341
- const o = childTarget(e, l, n);
2342
- if (o === undefined) continue;
2343
- // raw-marked: leaf by contract
2344
- walkT(o);
2345
- }
2346
- };
2347
- walkT(t);
2348
- return snapshotNext(e);
2349
- }
2350
-
2351
- /**
2352
- * Snapshot with per-object registration resolution (RUL-12 DAG ruling): every
2353
- * reachable wrappable resolves through its target's CURRENT backing, so
2354
- * privatized subtrees are seen through any parent path. Identity-preserving:
2355
- * a subtree with no substitutions below returns its own object (zero copy for
2356
- * settled, never-diverged graphs).
2357
- */ function snapshotNext(e) {
2358
- const t = e?.[$TARGET];
2359
- return snapshotWalk(e, new Map, t?.fam ?? null);
2360
- }
2361
-
2362
- function snapshotWalk(e, t, n) {
2363
- if (e === null || typeof e !== "object") return e;
2364
- // Resolve through the registration: proxies AND raws map to their target's
2365
- // current backing (stale raw pointers through other parents resolve here).
2366
- // Loops for chained backings (§7b: a projection's backing can be another
2367
- // store's proxy — snapshot unwraps to the base raw).
2368
- let r = e;
2369
- // Chained backings can pass through several targets; optimistic overrides
2370
- // on OUTER targets shadow the chain (§7b), so collect every opt target
2371
- // encountered and compose their views over the resolved base, innermost
2372
- // outward.
2373
- let i = null;
2374
- for (let e = true; ;e = false) {
2375
- const t = r?.[$TARGET]?.v !== undefined;
2376
- let l = t ? r[$TARGET] : undefined;
2377
- if (l === undefined && n !== null) l = lookupTarget(r, n);
2378
- if (l === undefined) l = lookupTarget(r, null);
2379
- if (l === undefined) break;
2380
- // Entering a level from a RAW below a chained family: the raw resolves to
2381
- // the INNER store's target, but this family's wrapper for it — keyed by
2382
- // the inner proxy, §7b — is where the outer overrides live. Start from
2383
- // the wrapper so they compose (#3323: a nested optimistic write on a view
2384
- // row was served by reads but missing from deep()/snapshot()). Entry only:
2385
- // the descent then runs wrapper → inner → raw and terminates.
2386
- if (e && !t && n !== null && l.fam !== n) {
2387
- const e = n.map.get(l.px);
2388
- if (e !== undefined) l = e;
2389
- }
2390
- if (l.fam !== null) n = l.fam;
2391
- if (l.fam?.opt === true) (i ??= []).push(l);
2392
- // The shared visibility decision (#3147): the speculative peek serves
2393
- // pending staging, but a HELD landing is masked to committed exactly as
2394
- // it is for per-key readers — the two families must answer alike while
2395
- // a transaction holds store landings.
2396
- const o = pendingBackingVisible(l, true);
2397
- // Snapshot runs mid-flush (tracked memos execute before commit), so a
2398
- // pending prototype overlay must present as a REAL merged container.
2399
- if (o && l.ovl) materializePB(l);
2400
- const s = o ? l.pb : l.v;
2401
- if (s === r) break;
2402
- r = s;
2403
- }
2404
- if (!isWrappable(r)) return r;
2405
- // Optimistic families: compose the visible view; a composed view is a fresh
2406
- // object and snapshots via the owned/copy path (pinned `not.toBe` identity).
2407
- if (i !== null) {
2408
- let e = r;
2409
- for (let t = i.length - 1; t >= 0; t--) {
2410
- const n = i[t];
2411
- // Draft twin (#3665): deep()/snapshot() inside a setter is the writer's
2412
- // channel and composes on the tick's own unflushed adds
2413
- // (hasActiveOverride). A draft that has seeded its own backing from
2414
- // the view already carries them in `src` — composing again would
2415
- // clobber the draft's later writes with the overrides they superseded.
2416
- const r = inDraft(n);
2417
- if (r && !draftSeesOverrides(n)) continue;
2418
- e = optHooks.optimisticView(n, e, r);
2419
- }
2420
- if (e !== r) {
2421
- const i = t.get(r);
2422
- if (i !== undefined) return i;
2423
- const l = Array.isArray(e);
2424
- const o = l ? [] : Object.create(Object.getPrototypeOf(e));
2425
- t.set(r, o);
2426
- for (const r of Reflect.ownKeys(e)) {
2427
- if (l && r === "length" || r === $OWNER) continue;
2428
- const i = e[r];
2429
- o[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
2430
- }
2431
- if (l) o.length = e.length;
2432
- return o;
2433
- }
2434
- }
2435
- const l = t.get(r);
2436
- if (l !== undefined) return l;
2437
- // OWNED (written) subtrees snapshot as copies (§7b: identity is only for
2438
- // subtrees "unmodified relative to source"): non-enumerable symbols are
2439
- // excluded (recon-snap R29), and the copy registers BEFORE descent so
2440
- // cycles keep identity (FINDING-3).
2441
- if (isOwned(r)) {
2442
- const e = Array.isArray(r);
2443
- const i = e ? [] : Object.create(Object.getPrototypeOf(r));
2444
- t.set(r, i);
2445
- for (const l of Reflect.ownKeys(r)) {
2446
- if (e && l === "length" || l === $OWNER) continue;
2447
- const o = Object.getOwnPropertyDescriptor(r, l);
2448
- if (typeof l === "symbol" && !o.enumerable) continue;
2449
- if (o.get || o.set) {
2450
- Object.defineProperty(i, l, o);
2451
- continue;
2452
- }
2453
- const s = o.value;
2454
- const f = s !== null && typeof s === "object" ? snapshotWalk(s, t, n) : s;
2455
- if (o.enumerable && o.writable && o.configurable) i[l] = f; else Object.defineProperty(i, l, {
2456
- ...o,
2457
- value: f
2458
- });
2459
- }
2460
- if (e && i.length !== r.length) i.length = r.length;
2461
- return i;
2462
- }
2463
- // UNOWNED (shared/user) subtrees keep identity unless a descendant
2464
- // substituted; copy-on-substitution preserves the documented CoW contract.
2465
- t.set(r, r);
2466
- let o = null;
2467
- for (const e of Reflect.ownKeys(r)) {
2468
- const i = Object.getOwnPropertyDescriptor(r, e);
2469
- if (!i || i.get || i.set) continue;
2470
- const l = i.value;
2471
- if (l === null || typeof l !== "object") continue;
2472
- const s = snapshotWalk(l, t, n);
2473
- if (s !== l) {
2474
- if (o === null) {
2475
- o = Array.isArray(r) ? [ ...r ] : Object.create(Object.getPrototypeOf(r), Object.getOwnPropertyDescriptors(r));
2476
- t.set(r, o);
2477
- }
2478
- o[e] = s;
2479
- }
2480
- }
2481
- return o ?? r;
2482
- }
2483
-
2484
- export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, derivedStoreWrite, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, heldMaskView, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, readerOverride, runAuthoritative, snapshotNext, stagedTruthPB, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetsEqual, unwrapValue, visibleOverride, wrapNext };