@solidjs/signals 2.0.0-rc.6 → 2.0.0-rc.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev-shared.js +5243 -0
- package/dist/dev.attribution.js +2333 -0
- package/dist/dev.js +1758 -6621
- package/dist/observe/affects.js +127 -0
- package/dist/observe/attribution.js +1 -0
- package/dist/observe/boundaries.js +586 -0
- package/dist/observe/core/action.js +167 -0
- package/dist/observe/core/async.js +736 -0
- package/dist/observe/core/attribution-hooks.js +60 -0
- package/dist/observe/core/attribution.js +2318 -0
- package/dist/{types-cjs/core/constants.d.cts → observe/core/constants.js} +135 -71
- package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
- package/dist/observe/core/core.js +1289 -0
- package/dist/observe/core/dev.js +232 -0
- package/dist/observe/core/effect.js +179 -0
- package/dist/observe/core/error.js +80 -0
- package/dist/observe/core/external.js +98 -0
- package/dist/observe/core/graph.js +155 -0
- package/dist/observe/core/heap.js +147 -0
- package/dist/observe/core/invariants.js +43 -0
- package/dist/observe/core/lanes.js +168 -0
- package/dist/observe/core/optimistic.js +438 -0
- package/dist/observe/core/owner.js +308 -0
- package/dist/observe/core/scheduler.js +1190 -0
- package/dist/observe/core/verdict.js +551 -0
- package/dist/observe/index.js +55 -0
- package/dist/observe/map.js +482 -0
- package/dist/{types-cjs/signals.d.cts → observe/signals.js} +305 -317
- package/dist/observe/store/index.js +38 -0
- package/dist/observe/store/next/optimistic.js +651 -0
- package/dist/observe/store/next/projection.js +242 -0
- package/dist/observe/store/next/reconcile.js +373 -0
- package/dist/observe/store/next/store.js +2213 -0
- package/dist/observe/store/next/target.js +51 -0
- package/dist/observe/store/store.js +323 -0
- package/dist/observe/store/storePath.js +103 -0
- package/dist/observe/store/utils.js +210 -0
- package/dist/prod/attribution.js +32 -0
- package/dist/prod/boundaries.js +3 -1
- package/dist/prod/core/action.js +52 -32
- package/dist/prod/core/async.js +177 -128
- package/dist/prod/core/attribution-hooks.js +3 -0
- package/dist/prod/core/constants.js +32 -1
- package/dist/prod/core/context.js +10 -16
- package/dist/prod/core/core.js +505 -286
- package/dist/prod/core/dev.js +2 -0
- package/dist/prod/core/effect.js +72 -43
- package/dist/prod/core/external.js +2 -2
- package/dist/prod/core/graph.js +40 -36
- package/dist/prod/core/heap.js +50 -51
- package/dist/prod/core/lanes.js +43 -22
- package/dist/prod/core/optimistic.js +224 -80
- package/dist/prod/core/owner.js +43 -39
- package/dist/prod/core/scheduler.js +347 -232
- package/dist/prod/core/verdict.js +71 -74
- package/dist/prod/index.js +9 -5
- package/dist/prod/map.js +311 -150
- package/dist/prod/signals.js +52 -38
- package/dist/prod/store/next/optimistic.js +127 -131
- package/dist/prod/store/next/projection.js +3 -3
- package/dist/prod/store/next/reconcile.js +149 -294
- package/dist/prod/store/next/store.js +694 -380
- package/dist/prod/store/next/target.js +32 -10
- package/dist/prod/store/store.js +12 -14
- package/dist/prod/store/utils.js +36 -27
- package/dist/types/attribution.d.ts +15 -0
- package/dist/types/attribution.prod.d.ts +13 -0
- package/dist/types/core/async.d.ts +1 -1
- package/dist/types/core/attribution-hooks.d.ts +183 -6
- package/dist/types/core/attribution.d.ts +435 -24
- package/dist/types/core/constants.d.ts +31 -0
- package/dist/types/core/core.d.ts +19 -3
- package/dist/types/core/dev.d.ts +165 -26
- package/dist/types/core/effect.d.ts +8 -1
- package/dist/types/core/heap.d.ts +5 -3
- package/dist/types/core/index.d.ts +2 -1
- package/dist/types/core/invariants.d.ts +1 -1
- package/dist/types/core/lanes.d.ts +17 -0
- package/dist/types/core/scheduler.d.ts +69 -4
- package/dist/types/core/types.d.ts +29 -9
- package/dist/types/index.d.ts +9 -2
- package/dist/types/map.d.ts +2 -0
- package/dist/types/signals.d.ts +10 -0
- package/dist/types/store/index.d.ts +4 -6
- package/dist/types/store/next/optimistic.d.ts +4 -2
- package/dist/types/store/next/reconcile.d.ts +5 -12
- package/dist/types/store/next/store.d.ts +8 -9
- package/dist/types/store/next/target.d.ts +56 -55
- package/dist/types/store/store.d.ts +14 -9
- package/dist/types/store/utils.d.ts +7 -0
- package/package.json +21 -16
- package/dist/node.cjs +0 -11121
- package/dist/prod/store/next/patch-hooks.js +0 -13
- package/dist/prod/store/next/patch.js +0 -614
- package/dist/types/store/next/patch-hooks.d.ts +0 -41
- package/dist/types/store/next/patch.d.ts +0 -91
- package/dist/types-cjs/affects.d.cts +0 -47
- package/dist/types-cjs/boundaries.d.cts +0 -175
- package/dist/types-cjs/core/action.d.cts +0 -64
- package/dist/types-cjs/core/async.d.cts +0 -23
- package/dist/types-cjs/core/attribution-hooks.d.cts +0 -63
- package/dist/types-cjs/core/attribution.d.cts +0 -239
- package/dist/types-cjs/core/core.d.cts +0 -176
- package/dist/types-cjs/core/dev.d.cts +0 -95
- package/dist/types-cjs/core/effect.d.cts +0 -30
- package/dist/types-cjs/core/error.d.cts +0 -56
- package/dist/types-cjs/core/external.d.cts +0 -15
- package/dist/types-cjs/core/graph.d.cts +0 -28
- package/dist/types-cjs/core/heap.d.cts +0 -22
- package/dist/types-cjs/core/index.d.cts +0 -13
- package/dist/types-cjs/core/invariants.d.cts +0 -59
- package/dist/types-cjs/core/lanes.d.cts +0 -44
- package/dist/types-cjs/core/optimistic.d.cts +0 -6
- package/dist/types-cjs/core/owner.d.cts +0 -124
- package/dist/types-cjs/core/scheduler.d.cts +0 -231
- package/dist/types-cjs/core/types.d.cts +0 -204
- package/dist/types-cjs/core/verdict.d.cts +0 -2
- package/dist/types-cjs/index.d.cts +0 -10
- package/dist/types-cjs/map.d.cts +0 -69
- package/dist/types-cjs/package.json +0 -3
- package/dist/types-cjs/store/index.d.cts +0 -21
- package/dist/types-cjs/store/next/optimistic.d.cts +0 -23
- package/dist/types-cjs/store/next/patch-hooks.d.cts +0 -41
- package/dist/types-cjs/store/next/patch.d.cts +0 -91
- package/dist/types-cjs/store/next/projection.d.cts +0 -8
- package/dist/types-cjs/store/next/reconcile.d.cts +0 -17
- package/dist/types-cjs/store/next/store.d.cts +0 -127
- package/dist/types-cjs/store/next/target.d.cts +0 -192
- package/dist/types-cjs/store/store.d.cts +0 -138
- package/dist/types-cjs/store/storePath.d.cts +0 -58
- package/dist/types-cjs/store/utils.d.cts +0 -74
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { unwrapOverride, $REFRESH, NOT_PENDING, CONFIG_OWNED_WRITE, CONFIG_OPTIMISTIC,
|
|
1
|
+
import { unwrapOverride, $REFRESH, NOT_PENDING, CONFIG_OWNED_WRITE, CONFIG_OPTIMISTIC, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_HELD_TRUTH, CONFIG_CHILDREN_FORBIDDEN, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
|
|
2
2
|
|
|
3
|
-
import { setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, ext, setLatestReadActive, prepareComputed, context } from "../../core/core.js";
|
|
3
|
+
import { setSlotUnobserved, setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, unlinkFirewallChild, ext, setLatestReadActive, prepareComputed, slotSignal, context } from "../../core/core.js";
|
|
4
4
|
|
|
5
5
|
import { projectionWriteActive, setProjectionWriteActive, activeTransition, schedule, currentTransition, setStoreCommitHook } from "../../core/scheduler.js";
|
|
6
6
|
|
|
7
|
+
import "../../core/invariants.js";
|
|
8
|
+
|
|
7
9
|
import { getObserver, getOwner } from "../../core/owner.js";
|
|
8
10
|
|
|
9
11
|
import { $TARGET, isWrappable, markRawIngest, $PROXY, getWriteOverride, markRawOne, witnessAffectsMark, $TRACK, affectsScopesLive, inheritAffectsMarks, $AFFECTS, rawValuesUsed, isRawValue, setNextAffectsNodeResolver } from "../store.js";
|
|
10
12
|
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
import { patchHooks, rowHooks } from "./patch-hooks.js";
|
|
13
|
+
import { lookupTarget, optHooks, storeNextLookup, $OWNER, isOwned, markDescendants, devAssertNeverUserMutation } from "./target.js";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Store rewrite — increment 2: plain deep stores with pending-backing writes.
|
|
@@ -44,9 +44,9 @@ import { patchHooks, rowHooks } from "./patch-hooks.js";
|
|
|
44
44
|
*
|
|
45
45
|
* ARRAY SHAPE RULE: arrays normalize their named properties to dictionary
|
|
46
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 — write-side
|
|
48
|
-
*
|
|
49
|
-
*
|
|
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
50
|
function TargetShape() {
|
|
51
51
|
this.v = undefined;
|
|
52
52
|
this.ch = undefined;
|
|
@@ -61,30 +61,20 @@ function TargetShape() {
|
|
|
61
61
|
this.d = undefined;
|
|
62
62
|
this.a = undefined;
|
|
63
63
|
this.sc = undefined;
|
|
64
|
+
this.kc = undefined;
|
|
64
65
|
this.nc = undefined;
|
|
65
|
-
this.
|
|
66
|
+
this.ab = undefined;
|
|
66
67
|
this.fam = undefined;
|
|
67
68
|
this.s = undefined;
|
|
68
69
|
this.ovl = undefined;
|
|
69
70
|
this.del = undefined;
|
|
70
|
-
this.
|
|
71
|
+
this.wk = undefined;
|
|
71
72
|
this.hv = undefined;
|
|
72
73
|
this.ht = undefined;
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
TargetShape.prototype = Object.prototype;
|
|
76
77
|
|
|
77
|
-
/** Lazily allocate the patch-channel extension (one literal shape). */ function pcOf(e) {
|
|
78
|
-
return e.pc ?? (e.pc = {
|
|
79
|
-
sp: null,
|
|
80
|
-
p: null,
|
|
81
|
-
ro: null,
|
|
82
|
-
wk: null,
|
|
83
|
-
qa: null,
|
|
84
|
-
qe: null
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
78
|
function createTarget(e, t, n, r = t?.fam ?? null) {
|
|
89
79
|
// The proxy target carries the array exotic class when the value is an
|
|
90
80
|
// array, so Array.isArray(proxy) is true; the fields live on it directly.
|
|
@@ -101,15 +91,16 @@ function createTarget(e, t, n, r = t?.fam ?? null) {
|
|
|
101
91
|
i.h = null;
|
|
102
92
|
i.k = null;
|
|
103
93
|
i.dk = null;
|
|
104
|
-
i.
|
|
94
|
+
i.wk = null;
|
|
105
95
|
i.u = t;
|
|
106
96
|
i.pk = n;
|
|
107
97
|
i.px = null;
|
|
108
98
|
i.d = false;
|
|
109
99
|
i.a = false;
|
|
110
|
-
i.sc =
|
|
100
|
+
i.sc = 0;
|
|
101
|
+
i.kc = 0;
|
|
111
102
|
i.nc = 0;
|
|
112
|
-
i.
|
|
103
|
+
i.ab = null;
|
|
113
104
|
i.fam = r;
|
|
114
105
|
i.s = false;
|
|
115
106
|
i.ovl = false;
|
|
@@ -128,7 +119,7 @@ function wrapNext(e, t = null, n = null, r = t?.fam ?? null) {
|
|
|
128
119
|
// markRaw'd values never wrap through ANY store (R42; sticky raw-marking
|
|
129
120
|
// is one half of the never-both-wrapped-and-raw invariant, RUL-12).
|
|
130
121
|
if (rawValuesUsed && isRawValue(e)) return e;
|
|
131
|
-
const i = (r
|
|
122
|
+
const i = lookupTarget(e, r);
|
|
132
123
|
if (i !== undefined) return i.px;
|
|
133
124
|
const o = e[$TARGET];
|
|
134
125
|
if (o !== undefined && o.px === e) {
|
|
@@ -155,62 +146,119 @@ function wrapNext(e, t = null, n = null, r = t?.fam ?? null) {
|
|
|
155
146
|
|
|
156
147
|
// ---------------------------------------------------------------------------
|
|
157
148
|
// nodes: pure subscription points (values used only for equality gating)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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.Ut, 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
|
+
const t = e.Ut;
|
|
166
|
+
const n = e.xt;
|
|
167
|
+
if (t.n && t.n[n] === e) {
|
|
168
|
+
delete t.n[n];
|
|
169
|
+
t.nc--;
|
|
170
|
+
// Projection leaves also leave the firewall child chain (#3351): a
|
|
171
|
+
// dropped node is unreachable through the store — a fresh read makes a
|
|
172
|
+
// fresh node — so the chain would only retain it and its last value.
|
|
173
|
+
unlinkFirewallChild(e);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
function getNode(e, t, n,
|
|
178
|
+
// First-read dedupe (create-floor slice 2): the get trap probes
|
|
179
|
+
// accessor-ness right before creating the node — pass the verdict through
|
|
180
|
+
// so creation skips the second descriptor scan. -1 = unknown (other
|
|
181
|
+
// callers), 0/1 = probed.
|
|
182
|
+
r = -1) {
|
|
183
|
+
const i = e.n ??= Object.create(null);
|
|
184
|
+
let o = i[t];
|
|
185
|
+
if (o === undefined) {
|
|
186
|
+
// Born holding (#3330 store twin): a key first read while a live
|
|
187
|
+
// transaction holds an ADOPTION on this target (adoptPB's `ht`) is
|
|
188
|
+
// created as if it had existed when the hold was notified — the adopted
|
|
189
|
+
// value already swapped into `v`, so committed is the held view
|
|
190
|
+
// `hv[key]` and the adopted `v[key]` is staged under the transaction
|
|
191
|
+
// (stageHeldKey). Its held-adoption notification (stageHeldAdoptions)
|
|
192
|
+
// ran before the node existed and the drain has nothing left to say.
|
|
193
|
+
// Without this the node was born from whichever view its first reader
|
|
194
|
+
// saw and never learned the other. (The held-FOLD twin — a setter's
|
|
195
|
+
// write to an unobserved key landing only in the pending backing — is
|
|
196
|
+
// #3336's, and lands with #3337.)
|
|
197
|
+
const l = heldAdoptionTransition(e);
|
|
198
|
+
if (l !== null) n = e.hv[t];
|
|
199
|
+
// Create-floor diet: slotSignal bakes the whole node into one literal —
|
|
200
|
+
// no options object, no equals/unobserved closures, no NodeExtension,
|
|
201
|
+
// no post-construction expandos (acc + the wrap cache px/pxv are
|
|
202
|
+
// pre-shaped fields: the proxy last served for this key and the raw it
|
|
203
|
+
// wrapped — one pointer compare replaces the per-read WeakMap lookup in
|
|
204
|
+
// wrapNext). ownedWrite rides the literal's config: the setter carries
|
|
205
|
+
// the owned-scope write guard; node-level setSignals are internal
|
|
206
|
+
// notification machinery. Projection nodes carry the projection
|
|
207
|
+
// computed as their firewall: reads through them link the derive's
|
|
208
|
+
// status/lifecycle (§7b).
|
|
209
|
+
const f = o = slotSignal(n, slotNodeEquals, e, t,
|
|
210
|
+
// Accessor-ness resolved ONCE per node (no per-object descriptor
|
|
211
|
+
// scan on reads): accessor keys serve through Reflect.get with the
|
|
212
|
+
// proxy receiver.
|
|
213
|
+
r === -1 ? isOwnAccessor(e.pb ?? e.v, t) : r === 1, e.fam?.node ?? undefined);
|
|
193
214
|
// Optimistic families: arm the override slot — setSignal routes armed
|
|
194
215
|
// nodes through the core engine (lanes, ownership, reverts all native).
|
|
195
216
|
if (e.fam?.opt) {
|
|
196
|
-
ext(
|
|
197
|
-
|
|
217
|
+
ext(f).be = NOT_PENDING;
|
|
218
|
+
f.T |= CONFIG_OPTIMISTIC;
|
|
198
219
|
}
|
|
199
220
|
// A node born inside a live mark's identity scope inherits the mark
|
|
200
221
|
// (the declaration walk could only cover nodes existing then).
|
|
201
|
-
if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(
|
|
202
|
-
|
|
222
|
+
if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(f, e.v, t);
|
|
223
|
+
if (l !== null) stageHeldKey(f, e.v[t], l);
|
|
224
|
+
i[t] = o;
|
|
203
225
|
e.nc++;
|
|
204
226
|
markDescendants(e);
|
|
205
227
|
}
|
|
206
|
-
return
|
|
228
|
+
return o;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** The live transaction holding an adoption on `target` (adoptPB's `ht`;
|
|
232
|
+
* a latest()-pull PLAIN_HOLD is not a transaction), else null. */ function heldAdoptionTransition(e) {
|
|
233
|
+
if (e.ht === null || e.ht === PLAIN_HOLD || heldMaskView(e) === null) return null;
|
|
234
|
+
const t = currentTransition(e.ht);
|
|
235
|
+
return t.ft === false ? t : null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Materialization under a hold. A node created by a first tracked read while
|
|
240
|
+
* a transaction holds this target must carry the same state a node that
|
|
241
|
+
* existed when the hold was notified carries — a transaction-stamped
|
|
242
|
+
* `_pendingValue` that core read() serves by ITS rules (a stale render reader
|
|
243
|
+
* of a foreign transaction's write sees committed, an owner-context reader
|
|
244
|
+
* inside the transaction sees the write) — or the reader's answer depends on
|
|
245
|
+
* whether some OTHER reader had materialized the key before the hold. Stage
|
|
246
|
+
* `nv` (the held value for the key — see getNode for which view it comes
|
|
247
|
+
* from) as the holding transaction's write — directly, not through
|
|
248
|
+
* setSignal: it is not a new write (it staged with the adoption's batch) and
|
|
249
|
+
* it walks no subscriber (the node has none yet). Transition-stamped now, as
|
|
250
|
+
* `runFolded` does — no parked-flush pass will stamp it.
|
|
251
|
+
*/ function stageHeldKey(e, t, n) {
|
|
252
|
+
if (slotNodeEquals.call(e, e.me, t)) return;
|
|
253
|
+
e.Ge = t;
|
|
254
|
+
e.ge = n;
|
|
255
|
+
n.$t.push(e);
|
|
207
256
|
}
|
|
208
257
|
|
|
209
258
|
function sameLogicalSlot(e, t, n) {
|
|
210
259
|
if (t === null || typeof t !== "object" || n === null || typeof n !== "object") return false;
|
|
211
|
-
const r = e.fam
|
|
212
|
-
|
|
213
|
-
return i !== undefined && i === r.get(n);
|
|
260
|
+
const r = lookupTarget(t, e.fam);
|
|
261
|
+
return r !== undefined && r === lookupTarget(n, e.fam);
|
|
214
262
|
}
|
|
215
263
|
|
|
216
264
|
function getHasNode(e, t, n) {
|
|
@@ -221,12 +269,15 @@ function getHasNode(e, t, n) {
|
|
|
221
269
|
equals: isEqual,
|
|
222
270
|
unobserved() {
|
|
223
271
|
if (o.o?.t) return;
|
|
224
|
-
if (e.h && e.h[t] === o)
|
|
272
|
+
if (e.h && e.h[t] === o) {
|
|
273
|
+
delete e.h[t];
|
|
274
|
+
unlinkFirewallChild(o);
|
|
275
|
+
}
|
|
225
276
|
}
|
|
226
277
|
}, e.fam?.node ?? undefined);
|
|
227
278
|
o.T |= CONFIG_OWNED_WRITE;
|
|
228
279
|
if (e.fam?.opt) {
|
|
229
|
-
ext(o).
|
|
280
|
+
ext(o).be = NOT_PENDING;
|
|
230
281
|
o.T |= CONFIG_OPTIMISTIC;
|
|
231
282
|
}
|
|
232
283
|
if (affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
|
|
@@ -242,12 +293,15 @@ function getKeySetNode(e) {
|
|
|
242
293
|
const n = t = signal(0, {
|
|
243
294
|
equals: false,
|
|
244
295
|
unobserved() {
|
|
245
|
-
if (e.k === n)
|
|
296
|
+
if (e.k === n) {
|
|
297
|
+
e.k = null;
|
|
298
|
+
unlinkFirewallChild(n);
|
|
299
|
+
}
|
|
246
300
|
}
|
|
247
301
|
}, e.fam?.node ?? undefined);
|
|
248
302
|
n.T |= CONFIG_OWNED_WRITE;
|
|
249
303
|
if (e.fam?.opt) {
|
|
250
|
-
ext(n).
|
|
304
|
+
ext(n).be = NOT_PENDING;
|
|
251
305
|
n.T |= CONFIG_OPTIMISTIC;
|
|
252
306
|
}
|
|
253
307
|
e.k = t;
|
|
@@ -262,12 +316,15 @@ function getDeepNode(e) {
|
|
|
262
316
|
const n = t = signal(0, {
|
|
263
317
|
equals: false,
|
|
264
318
|
unobserved() {
|
|
265
|
-
if (e.dk === n)
|
|
319
|
+
if (e.dk === n) {
|
|
320
|
+
e.dk = null;
|
|
321
|
+
unlinkFirewallChild(n);
|
|
322
|
+
}
|
|
266
323
|
}
|
|
267
324
|
}, e.fam?.node ?? undefined);
|
|
268
325
|
n.T |= CONFIG_OWNED_WRITE;
|
|
269
326
|
if (e.fam?.opt) {
|
|
270
|
-
ext(n).
|
|
327
|
+
ext(n).be = NOT_PENDING;
|
|
271
328
|
n.T |= CONFIG_OPTIMISTIC;
|
|
272
329
|
}
|
|
273
330
|
if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
|
|
@@ -288,43 +345,74 @@ function getDeepNode(e) {
|
|
|
288
345
|
|
|
289
346
|
let hookInstalled = false;
|
|
290
347
|
|
|
291
|
-
|
|
348
|
+
/** Shallow-clone `source` as a backing OWNED by `t` (stamped `$OWNER`, see
|
|
349
|
+
* target.ts). Callers always pass their own committed backing as `source`. */ function cloneRaw(e, t) {
|
|
350
|
+
// Plain-data fast path (#3360): a scanned `Object.prototype` container
|
|
351
|
+
// whose own keys are all enumerable data clones by spread — the same
|
|
352
|
+
// result as the descriptor walk below (own enumerable string+symbol keys,
|
|
353
|
+
// normalized writable+configurable), at ~1/50th the cost.
|
|
354
|
+
t.sc || scanAccessorsOnce(t);
|
|
355
|
+
if (t.sc === 2) {
|
|
356
|
+
const n = {
|
|
357
|
+
...e
|
|
358
|
+
};
|
|
359
|
+
n[$OWNER] = t;
|
|
360
|
+
return n;
|
|
361
|
+
}
|
|
292
362
|
// Descriptor-preserving shallow clone (R29: installed getters stay live;
|
|
293
363
|
// ruled 2026-08-17: frozen sources clone unfrozen — theirs stays frozen).
|
|
294
364
|
// Data descriptors normalize to writable+configurable (the clone is OURS to
|
|
295
365
|
// mutate — R51's "source-non-configurable is writable through the store");
|
|
296
366
|
// enumerability and accessors are preserved. The scan doubles as the
|
|
297
367
|
// accessor-flag detector (free — we're enumerating descriptors anyway).
|
|
298
|
-
|
|
368
|
+
const n = Object.getOwnPropertyDescriptors(e);
|
|
299
369
|
for (const r of Reflect.ownKeys(n)) {
|
|
300
370
|
const i = n[r];
|
|
301
371
|
if (r === "length" && Array.isArray(e)) continue;
|
|
302
372
|
i.configurable = true;
|
|
303
|
-
if (!i.get && !i.set) i.writable = true; else
|
|
373
|
+
if (!i.get && !i.set) i.writable = true; else t.a = true;
|
|
304
374
|
}
|
|
305
|
-
|
|
375
|
+
const r = Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
|
|
376
|
+
r[$OWNER] = t;
|
|
377
|
+
return r;
|
|
306
378
|
}
|
|
307
379
|
|
|
308
|
-
/**
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
|
|
380
|
+
/** Copy own `key` from `from` onto `to`. A plain data slot (enumerable,
|
|
381
|
+
* writable, configurable, no accessor) is a bare assignment — the common case
|
|
382
|
+
* and the cheap one; anything else goes through defineProperty so accessors
|
|
383
|
+
* and attribute flags survive the copy. */ function copyOwn(e, t, n) {
|
|
384
|
+
const r = Object.getOwnPropertyDescriptor(t, n);
|
|
385
|
+
if (r.get || r.set || !r.enumerable || !r.writable || !r.configurable) Object.defineProperty(e, n, r); else e[n] = r.value;
|
|
312
386
|
}
|
|
313
387
|
|
|
314
388
|
/** One-time own-accessor scan (Annex-B probes, no descriptor allocation);
|
|
315
|
-
* returns true when the container is plain data (overlay-safe).
|
|
389
|
+
* returns true when the container is plain data (overlay-safe). Also grades
|
|
390
|
+
* the container for the plain-data fast paths (`sc` = 2): `Object.prototype`
|
|
391
|
+
* and every own key an enumerable data property — what a spread copies
|
|
392
|
+
* exactly and what a bare assignment lands exactly. */ function scanAccessorsOnce(e) {
|
|
316
393
|
const t = e.v;
|
|
317
|
-
|
|
394
|
+
const n = Reflect.ownKeys(t);
|
|
395
|
+
let r = Object.getPrototypeOf(t) === Object.prototype;
|
|
396
|
+
for (const i of n) {
|
|
318
397
|
// Own keys shadow prototype accessors, so the lookups are exact here.
|
|
319
|
-
if (lookupGetter.call(t,
|
|
398
|
+
if (lookupGetter.call(t, i) !== undefined || lookupSetter.call(t, i) !== undefined) {
|
|
320
399
|
e.a = true;
|
|
400
|
+
r = false;
|
|
321
401
|
break;
|
|
322
402
|
}
|
|
403
|
+
if (r && !propertyIsEnumerable.call(t, i)) r = false;
|
|
323
404
|
}
|
|
324
|
-
e.sc =
|
|
405
|
+
e.sc = r ? 2 : 1;
|
|
406
|
+
e.kc = n.length;
|
|
325
407
|
return !e.a;
|
|
326
408
|
}
|
|
327
409
|
|
|
410
|
+
/** Own-key count above which a draft opens as a prototype overlay rather
|
|
411
|
+
* than a clone (#3360). Below it a spread clone of a plain container is
|
|
412
|
+
* cheaper than the overlay's `Object.create` (V8 converts the committed
|
|
413
|
+
* backing into a prototype, and every later flatten writes into that
|
|
414
|
+
* prototype); above it the clone's O(keys) copy dominates (#3044). */ const OVERLAY_MIN_KEYS = 32;
|
|
415
|
+
|
|
328
416
|
/** Downgrade a prototype-overlay pending backing to the clone path: builds
|
|
329
417
|
* the real container (committed + overlay writes − deletes) that fold will
|
|
330
418
|
* SWAP in as the committed backing, exactly as if the draft had started on
|
|
@@ -333,18 +421,11 @@ function cloneRaw(e, t) {
|
|
|
333
421
|
if (!e.ovl) return;
|
|
334
422
|
const t = e.pb;
|
|
335
423
|
const n = cloneRaw(e.v, e);
|
|
336
|
-
for (const e of Reflect.ownKeys(t))
|
|
337
|
-
const r = Object.getOwnPropertyDescriptor(t, e);
|
|
338
|
-
if (r.get || r.set || !r.enumerable || !r.writable || !r.configurable) Object.defineProperty(n, e, r); else n[e] = r.value;
|
|
339
|
-
}
|
|
424
|
+
for (const e of Reflect.ownKeys(t)) copyOwn(n, t, e);
|
|
340
425
|
if (e.del !== null) {
|
|
341
426
|
for (const t of e.del) delete n[t];
|
|
342
427
|
e.del = null;
|
|
343
428
|
}
|
|
344
|
-
const r = e.fam?.map ?? storeNextLookup;
|
|
345
|
-
r.delete(t);
|
|
346
|
-
ownedRaw.add(n);
|
|
347
|
-
r.set(n, e);
|
|
348
429
|
e.pb = n;
|
|
349
430
|
e.ovl = false;
|
|
350
431
|
}
|
|
@@ -367,15 +448,32 @@ function ensurePB(e) {
|
|
|
367
448
|
}
|
|
368
449
|
if (activeTransition !== null) foldBatches.set(e, activeTransition);
|
|
369
450
|
if (t === null) {
|
|
370
|
-
// Prototype-chain overlay (#3044): plain-data non-array containers
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
//
|
|
375
|
-
|
|
376
|
-
|
|
451
|
+
// Prototype-chain overlay (#3044): plain-data non-array containers open
|
|
452
|
+
// drafts in O(1) — own keys are the writes, reads fall through to
|
|
453
|
+
// committed. Projection and derived-store families take it too (#3352:
|
|
454
|
+
// a derive touching one root key of a wide keyed record paid an
|
|
455
|
+
// O(keys) clone per recompute). Everything else keeps the descriptor
|
|
456
|
+
// clone: arrays (splice/length semantics), OPTIMISTIC families (the
|
|
457
|
+
// view-seeding below writes and deletes on the draft container, and the
|
|
458
|
+
// tentative discard/truth-park hand whole backings around), chained
|
|
459
|
+
// backings (the committed layer is another store's proxy — an overlay
|
|
460
|
+
// would route every read-through into its traps), accessor containers
|
|
461
|
+
// (live getters).
|
|
462
|
+
// The overlay pays off only for a WIDE container over an OWNED committed
|
|
463
|
+
// backing (#3360). Narrow containers clone cheaper than they overlay (a
|
|
464
|
+
// spread is a fast-path copy; `Object.create` turns the backing into a
|
|
465
|
+
// V8 prototype and every later flatten writes into that prototype). An
|
|
466
|
+
// unowned backing has to be privatized (cloned) at commit anyway, so an
|
|
467
|
+
// overlay there would cost the create PLUS the clone PLUS a per-key
|
|
468
|
+
// copy — the clone path does the one clone and swaps it in. The first
|
|
469
|
+
// write on a fresh store is exactly that case.
|
|
470
|
+
const n = e.v;
|
|
471
|
+
if (!e.fam?.opt && !e.ch && !Array.isArray(n) && (e.sc !== 0 ? !e.a : scanAccessorsOnce(e)) && e.kc > OVERLAY_MIN_KEYS && isOwned(n)) {
|
|
472
|
+
// Inherits `v`'s $OWNER stamp — the overlay resolves and reads as
|
|
473
|
+
// owned without a registration of its own.
|
|
474
|
+
t = e.pb = Object.create(n);
|
|
377
475
|
e.ovl = true;
|
|
378
|
-
} else t = e.pb = cloneRaw(
|
|
476
|
+
} else t = e.pb = cloneRaw(n, e);
|
|
379
477
|
// Optimistic families: seed USER drafts from the OPTIMISTIC VIEW
|
|
380
478
|
// (committed + active node overrides), so follow-up writes compose on
|
|
381
479
|
// optimism instead of clobbering from base (#2951's compose half).
|
|
@@ -388,19 +486,17 @@ function ensurePB(e) {
|
|
|
388
486
|
if (n !== null) {
|
|
389
487
|
for (const e of Reflect.ownKeys(n)) {
|
|
390
488
|
const r = n[e];
|
|
391
|
-
if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.
|
|
489
|
+
if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.be);
|
|
392
490
|
}
|
|
393
491
|
}
|
|
394
492
|
const r = e.h;
|
|
395
493
|
if (r !== null) {
|
|
396
494
|
for (const e of Reflect.ownKeys(r)) {
|
|
397
495
|
const n = r[e];
|
|
398
|
-
if (hasActiveOverride(n) && !unwrapOverride(n.o?.
|
|
496
|
+
if (hasActiveOverride(n) && !unwrapOverride(n.o?.be)) delete t[e];
|
|
399
497
|
}
|
|
400
498
|
}
|
|
401
499
|
}
|
|
402
|
-
ownedRaw.add(t);
|
|
403
|
-
(e.fam?.map ?? storeNextLookup).set(t, e);
|
|
404
500
|
queueFold(e);
|
|
405
501
|
}
|
|
406
502
|
return t;
|
|
@@ -421,7 +517,7 @@ function ensurePB(e) {
|
|
|
421
517
|
* foldHeld's node stamps). */ function heldMaskView(e) {
|
|
422
518
|
const t = e.ht;
|
|
423
519
|
if (t === null) return null;
|
|
424
|
-
if (t !== PLAIN_HOLD && currentTransition(t)?.
|
|
520
|
+
if (t !== PLAIN_HOLD && currentTransition(t)?.ft === true) return e.ht = e.hv = null;
|
|
425
521
|
return e.hv;
|
|
426
522
|
}
|
|
427
523
|
|
|
@@ -439,21 +535,38 @@ function ensurePB(e) {
|
|
|
439
535
|
if (!n) {
|
|
440
536
|
queueFold(e);
|
|
441
537
|
// records the pre-batch old before we swap
|
|
442
|
-
|
|
443
|
-
//
|
|
444
|
-
//
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
//
|
|
448
|
-
//
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
538
|
+
// Diff base = the view the nodes were last told (#3296). A draft's
|
|
539
|
+
// setter-exit notifications already moved them to its pending backing,
|
|
540
|
+
// so a later adoption diffs against THAT — against committed, a key the
|
|
541
|
+
// draft changed and the adoption restores would never re-notify. An
|
|
542
|
+
// adoption with no draft leaves nodes where they were: keep an existing
|
|
543
|
+
// base, else the pre-batch committed (foldOlds' entry itself stays the
|
|
544
|
+
// committed identity for the path-copy CAS). Eager callers read t.pb
|
|
545
|
+
// directly; this hand-off exists because pb is gone by drain time.
|
|
546
|
+
if (e.pb !== null) {
|
|
547
|
+
if (e.ovl) materializePB(e);
|
|
548
|
+
e.ab = e.pb;
|
|
549
|
+
} else e.ab ??= foldOlds.get(e);
|
|
550
|
+
}
|
|
551
|
+
// #3074/#3075: a projection recompute deriving from uncommitted inputs
|
|
552
|
+
// swaps the backing SPECULATIVELY — committed-visibility readers must
|
|
553
|
+
// keep the pre-hold view until the hold resolves (a source held by a
|
|
554
|
+
// live transition, or a latest()-pull ahead of the flush). Post-await
|
|
555
|
+
// landings (write-override) stay immediately visible — landed truth —
|
|
556
|
+
// and clear any hold. Optimistic families hold too (#3330 store twin):
|
|
557
|
+
// their tentative edits ride the lane machinery, but a sync derive
|
|
558
|
+
// adopting under a transaction is held TRUTH like any projection's —
|
|
559
|
+
// unheld, handlers read it early and an optimistic write equal to it
|
|
560
|
+
// compared as a no-op against the swapped-in backing. A plain store's
|
|
561
|
+
// reconcile inside an action holds the same way: its nodes stage under the
|
|
562
|
+
// transaction (the inline notify), and the backing must not show handlers
|
|
563
|
+
// and stale readers what the tracked read masks (signal parity, #3336).
|
|
564
|
+
if (getWriteOverride()) {
|
|
565
|
+
e.ht = e.hv = null;
|
|
566
|
+
} else if (activeTransition !== null || !n && latestPullActive) {
|
|
567
|
+
if (heldMaskView(e) === null) e.hv = e.v;
|
|
568
|
+
e.ht = activeTransition ?? PLAIN_HOLD;
|
|
569
|
+
if (!n && activeTransition !== null) heldAdoptions.add(e);
|
|
457
570
|
}
|
|
458
571
|
e.pb = null;
|
|
459
572
|
// Overlay and accessor-scan state describe the OUTGOING backing — a
|
|
@@ -465,13 +578,17 @@ function ensurePB(e) {
|
|
|
465
578
|
// draft rescans once (#3044 audit follow-up).
|
|
466
579
|
e.ovl = false;
|
|
467
580
|
e.del = null;
|
|
468
|
-
e.sc =
|
|
581
|
+
e.sc = 0;
|
|
469
582
|
e.a = false;
|
|
470
|
-
|
|
583
|
+
e.wk = null;
|
|
471
584
|
// adoption supersedes staged trap writes
|
|
472
585
|
e.v = t;
|
|
473
586
|
e.ch = t[$TARGET] !== undefined;
|
|
474
|
-
|
|
587
|
+
// An adoptee we own within this family (a draft aliasing one of the
|
|
588
|
+
// family's own backings) re-stamps to its new owner — the stamp is the
|
|
589
|
+
// family's registration for it; everything else registers in the map.
|
|
590
|
+
const r = t[$OWNER];
|
|
591
|
+
if (r !== undefined && r.fam === e.fam) t[$OWNER] = e; else (e.fam?.map ?? storeNextLookup).set(t, e);
|
|
475
592
|
}
|
|
476
593
|
|
|
477
594
|
/** Sentinel for `t.wk`: the written-keys bound is unusable this batch (an
|
|
@@ -517,20 +634,78 @@ function queueFold(e) {
|
|
|
517
634
|
* transition is ambient). Entries die with their draft — tentative backings
|
|
518
635
|
* are consumed at setter exit. */ const tentativePBs = new WeakSet;
|
|
519
636
|
|
|
637
|
+
/** A draft read composes the live optimistic view until the draft has opened
|
|
638
|
+
* its OWN view-seeded backing (ensurePB seeds that clone from the view and
|
|
639
|
+
* registers it in tentativePBs; from then on reads must see the draft's
|
|
640
|
+
* writes, not the overrides they superseded). A pending backing that exists
|
|
641
|
+
* for any other reason is not that clone — a truth landing staged into a
|
|
642
|
+
* retaining transaction (#3164 fold) is authoritative truth WITHOUT the
|
|
643
|
+
* live overrides. ensurePB parks such a backing on the draft's first WRITE
|
|
644
|
+
* and reseeds from the view, but the reads that precede that write went to
|
|
645
|
+
* the staged truth: `votes++` read base, wrote base+1, and the override it
|
|
646
|
+
* emitted landed on the value already displayed — a second in-flight
|
|
647
|
+
* increment made after a sibling's landing was invisible (#2951's compose
|
|
648
|
+
* half, one landing later). */ function draftSeesOverrides(e) {
|
|
649
|
+
return e.pb === null || !tentativePBs.has(e.pb);
|
|
650
|
+
}
|
|
651
|
+
|
|
520
652
|
/** Committed-time privatization for parent-chain slot updates (path copying). */ function privatizeCommitted(e) {
|
|
521
|
-
if (
|
|
522
|
-
const t =
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
e.v = t;
|
|
653
|
+
if (isOwned(e.v)) return;
|
|
654
|
+
const t = e.v;
|
|
655
|
+
const n = cloneRaw(t, e);
|
|
656
|
+
e.v = n;
|
|
526
657
|
e.ch = false;
|
|
527
658
|
if (e.u) {
|
|
528
659
|
privatizeCommitted(e.u);
|
|
529
660
|
devAssertNeverUserMutation(e.u.v);
|
|
530
|
-
|
|
661
|
+
// CAS, same as drainFolds' path copy: re-point the parent slot only
|
|
662
|
+
// while it still holds the raw we cloned. A parent that folded EARLIER
|
|
663
|
+
// in this drain may have replaced or deleted this slot (the same batch
|
|
664
|
+
// wrote the child and then `parent.row = fresh` / `parent.length = 0`)
|
|
665
|
+
// — an unconditional write resurrected the dropped child over it.
|
|
666
|
+
const n = e.u.v;
|
|
667
|
+
const r = parentSlotKey(e, t);
|
|
668
|
+
if (n[r] === t) n[r] = e.v;
|
|
531
669
|
}
|
|
532
670
|
}
|
|
533
671
|
|
|
672
|
+
/** Resolve the slot this child currently occupies in its parent's committed
|
|
673
|
+
* backing (#3282). `pk` is stamped at wrap time and arrays MOVE: a reverse/
|
|
674
|
+
* unshift/splice relocates the raw, and a fold that re-points the wrap-time
|
|
675
|
+
* slot writes the clone over whichever row lives there now. Objects never
|
|
676
|
+
* move keys, so the stamp is authoritative; for arrays, verify and re-locate
|
|
677
|
+
* by identity when stale (fold-time only — never on a read path). */ function parentSlotKey(e, t) {
|
|
678
|
+
const n = e.pk;
|
|
679
|
+
const r = e.u.v;
|
|
680
|
+
if (r[n] === t || !Array.isArray(r)) return n;
|
|
681
|
+
const i = r.indexOf(t);
|
|
682
|
+
if (i === -1) return n;
|
|
683
|
+
e.pk = i;
|
|
684
|
+
return i;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/** Overlay commit (#3044): apply the batch's writes onto an OWNED committed
|
|
688
|
+
* backing in place — O(written), not O(container). Unowned backings
|
|
689
|
+
* privatize first (clone once, parents re-slotted) — the never-mutate-user-
|
|
690
|
+
* data contract holds. Shared by the deferred fold (drainFolds) and the
|
|
691
|
+
* projection landing's immediate commit (notifyWrites). */ function flattenOverlay(e, t) {
|
|
692
|
+
privatizeCommitted(e);
|
|
693
|
+
const n = e.v;
|
|
694
|
+
// Plain-data grade (sc 2): every own key on the overlay is an enumerable
|
|
695
|
+
// writable data slot (set-trap writes; a non-plain defineProperty
|
|
696
|
+
// downgrades the grade) — a bare assignment lands it without the
|
|
697
|
+
// descriptor round trip.
|
|
698
|
+
for (const r of Reflect.ownKeys(t)) e.sc === 2 ? n[r] = t[r] : copyOwn(n, t, r);
|
|
699
|
+
if (e.del !== null) {
|
|
700
|
+
for (const t of e.del) delete n[t];
|
|
701
|
+
e.del = null;
|
|
702
|
+
}
|
|
703
|
+
e.pb = null;
|
|
704
|
+
e.ovl = false;
|
|
705
|
+
e.wk = null;
|
|
706
|
+
// written-keys window closes with the commit
|
|
707
|
+
}
|
|
708
|
+
|
|
534
709
|
function drainFolds() {
|
|
535
710
|
if (foldOlds.size === 0) return;
|
|
536
711
|
const e = [ ...foldOlds ];
|
|
@@ -540,18 +715,13 @@ function drainFolds() {
|
|
|
540
715
|
// is committing the batch the pull ran ahead of. Transition holds stay —
|
|
541
716
|
// they clear when their transition is done (heldMaskView).
|
|
542
717
|
if (t.ht === PLAIN_HOLD) t.ht = t.hv = null;
|
|
543
|
-
// Eager (write-override) family folds swap pb -> v at notifyWrites'
|
|
544
|
-
// tail: by the time this drain runs they carry no pb, and their
|
|
545
|
-
// structural ops must emit at the fold-commit site below (the clone
|
|
546
|
-
// branch never sees them). Re-audit blocker 4.
|
|
547
|
-
const e = t.pb === null;
|
|
548
718
|
if (t.pb !== null) {
|
|
549
719
|
// #3089: a fold written under a still-running transition defers to
|
|
550
720
|
// that transition's settle (the write-time stamp covers unobserved
|
|
551
721
|
// keys; observed keys also hit the pending-node held check below).
|
|
552
722
|
const e = foldBatches.get(t);
|
|
553
723
|
if (e !== undefined) {
|
|
554
|
-
if (currentTransition(e).
|
|
724
|
+
if (currentTransition(e).ft === false) {
|
|
555
725
|
foldOlds.set(t, n);
|
|
556
726
|
continue;
|
|
557
727
|
}
|
|
@@ -568,14 +738,14 @@ function drainFolds() {
|
|
|
568
738
|
// Only written keys can hold (their nodes took the setSignal); the
|
|
569
739
|
// wk bound keeps this O(written) — see notifyWrites. Same fallback
|
|
570
740
|
// rules as the notify (WK_ALL / accessors / non-plain prototypes).
|
|
571
|
-
const e = t.
|
|
741
|
+
const e = t.wk;
|
|
572
742
|
const n = e === null || e === WK_ALL || t.a === true ||
|
|
573
743
|
// Overlay pbs chain to the COMMITTED object (#3044) — plainness is
|
|
574
744
|
// the committed container's prototype, not the overlay's.
|
|
575
745
|
!plainProto(t.ovl ? t.v : i) ? Reflect.ownKeys(o) : e;
|
|
576
746
|
for (const e of n) {
|
|
577
747
|
const t = o[e];
|
|
578
|
-
if (t !== undefined && t.
|
|
748
|
+
if (t !== undefined && t.Ge !== NOT_PENDING) {
|
|
579
749
|
r = true;
|
|
580
750
|
break;
|
|
581
751
|
}
|
|
@@ -587,87 +757,80 @@ function drainFolds() {
|
|
|
587
757
|
continue;
|
|
588
758
|
}
|
|
589
759
|
if (t.ovl) {
|
|
590
|
-
// Overlay flatten (#3044):
|
|
591
|
-
//
|
|
592
|
-
//
|
|
593
|
-
//
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
//
|
|
760
|
+
// Overlay flatten (#3044): the backing keeps its identity, so the
|
|
761
|
+
// `t.v === old` gate below skips path copying (the parent slot
|
|
762
|
+
// already points here) and the adopted-notify (setter notifications
|
|
763
|
+
// happened at write time).
|
|
764
|
+
flattenOverlay(t, i);
|
|
765
|
+
} else if (t.v !== n) {
|
|
766
|
+
// Privatized mid-batch (#3271): an earlier fold in this drain
|
|
767
|
+
// path-copied THROUGH this target — privatizeCommitted cloned the
|
|
768
|
+
// committed backing, re-pointed the parent slot at the clone, and
|
|
769
|
+
// stitched the descendant's fold into it. The draft's pb predates
|
|
770
|
+
// that: swapping it in would clobber the descendant's fold, and the
|
|
771
|
+
// parent CAS below (still comparing against `old`) would fail and
|
|
772
|
+
// orphan this fold entirely — a projection draft writing descendant-
|
|
773
|
+
// then-ancestor silently lost the ancestor write. Merge the batch's
|
|
774
|
+
// writes onto the current container instead (the parent slot already
|
|
775
|
+
// points at it). privatize first: an adopt-then-write batch can land
|
|
776
|
+
// here with an unowned adoptee as t.v.
|
|
597
777
|
privatizeCommitted(t);
|
|
598
778
|
const e = t.v;
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
779
|
+
const r = t.wk;
|
|
780
|
+
if (r !== null && r !== WK_ALL) {
|
|
781
|
+
// The trap records every write/delete key — apply exactly those.
|
|
782
|
+
for (const t of r) {
|
|
783
|
+
if (hasOwn.call(i, t)) copyOwn(e, i, t); else delete e[t];
|
|
784
|
+
}
|
|
785
|
+
} else {
|
|
786
|
+
// Array length write poisoned the bound (WK_ALL) — value-diff
|
|
787
|
+
// against the pre-batch old. Slots the draft never touched hold
|
|
788
|
+
// the same raw reference in both, so descendant folds stay put.
|
|
789
|
+
// Not copyOwn: the plain-value write is GATED on "the draft changed
|
|
790
|
+
// this slot" — an untouched slot in pb holds the pre-batch reference,
|
|
791
|
+
// and writing it back would clobber a descendant fold stitched into v.
|
|
792
|
+
for (const t of Reflect.ownKeys(i)) {
|
|
793
|
+
const r = Object.getOwnPropertyDescriptor(i, t);
|
|
794
|
+
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;
|
|
795
|
+
}
|
|
796
|
+
for (const t of Reflect.ownKeys(n)) {
|
|
797
|
+
if (!hasOwn.call(i, t)) delete e[t];
|
|
798
|
+
}
|
|
606
799
|
}
|
|
607
|
-
(t.fam?.map ?? storeNextLookup).delete(i);
|
|
608
800
|
t.pb = null;
|
|
609
|
-
t.
|
|
610
|
-
if (t.pc !== null) t.pc.wk = null;
|
|
801
|
+
t.wk = null;
|
|
611
802
|
// written-keys window closes with the fold commit
|
|
612
803
|
} else {
|
|
613
|
-
// Setter-channel structural ops: a fold that changes an array's shape
|
|
614
|
-
// (push/splice/permutation through the setter — the reconcile walk
|
|
615
|
-
// never queues here) is a structural visibility transition for any
|
|
616
|
-
// registered list driver. Identity-keyed; aligned folds emit nothing.
|
|
617
|
-
// Family targets defer to their own adoption emission (fam reconcile).
|
|
618
|
-
// Arrays always fold on this clone branch (overlay is non-array only).
|
|
619
|
-
// Family setter drafts (writable projection push/splice through the
|
|
620
|
-
// masked setter) fold on this branch too and the fold IS their
|
|
621
|
-
// visibility moment — emit unless the structure already rode another
|
|
622
|
-
// channel: adoption folds (reconcile walk emitted ops) and
|
|
623
|
-
// optimistic families (lane-timed override channel). Re-audit
|
|
624
|
-
// blocker 4.
|
|
625
|
-
if (t.pc !== null && t.pc.ro !== null && !t.adopted && t.fam?.opt !== true && Array.isArray(i) && Array.isArray(t.v)) rowHooks.emitSetterRowOps(t, t.v, i);
|
|
626
804
|
t.v = i;
|
|
627
805
|
t.ch = false;
|
|
628
806
|
// pb is always a plain clone
|
|
629
807
|
t.pb = null;
|
|
630
|
-
|
|
808
|
+
t.wk = null;
|
|
631
809
|
// written-keys window closes with the fold commit
|
|
632
810
|
}
|
|
633
811
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
//
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
// eager-folded family SETTER drafts (write-override swaps pb -> v at
|
|
650
|
-
// notifyWrites' tail — the clone branch never sees them; adoption
|
|
651
|
-
// folds re-emitting would double the walk's ops) and PLAIN fold
|
|
652
|
-
// adoptions (no walk at all). Optimistic families ride the override
|
|
653
|
-
// channel (lane-timed ops + revert RESYNC) — never re-emit here.
|
|
654
|
-
if (t.pc.ro !== null && t.fam?.opt !== true && (t.fam !== null ? e && !t.adopted : t.adopted) && Array.isArray(t.v) && Array.isArray(n)) rowHooks.emitSetterRowOps(t, n, t.v);
|
|
655
|
-
if (t.pc.p !== null) {
|
|
656
|
-
// Accessor demotion at the fold-commit seam is DEV-ONLY (see the
|
|
657
|
-
// reconcile seam note: prod never pays per-adoption scans).
|
|
658
|
-
patchHooks.emitPatchLocal(t, t.v, n);
|
|
812
|
+
const e = t.ab;
|
|
813
|
+
t.ab = null;
|
|
814
|
+
if (t.v !== n) {
|
|
815
|
+
// Path copying (CAS: see the eager-fold twin above). Slot resolved by
|
|
816
|
+
// identity (#3282): an array move relocated the raw, so the wrap-time
|
|
817
|
+
// pk may point at a sibling — a raw-slot CAS there both failed to
|
|
818
|
+
// re-point AND (via privatizeCommitted's unguarded write) clobbered
|
|
819
|
+
// the sibling.
|
|
820
|
+
if (t.u) {
|
|
821
|
+
const e = parentSlotKey(t, n);
|
|
822
|
+
if (t.u.v[e] === n) {
|
|
823
|
+
privatizeCommitted(t.u);
|
|
824
|
+
devAssertNeverUserMutation(t.u.v);
|
|
825
|
+
t.u.v[e] = t.v;
|
|
826
|
+
}
|
|
659
827
|
}
|
|
660
828
|
}
|
|
661
|
-
//
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
667
|
-
if (t.adopted) {
|
|
668
|
-
t.adopted = false;
|
|
669
|
-
notifyFold(t, n, t.v);
|
|
670
|
-
}
|
|
829
|
+
// Adoption notify against the base the nodes were last told (#3296). A
|
|
830
|
+
// no-op adoption (A -> B -> A before flush, no draft) has base === v and
|
|
831
|
+
// nothing to say; a draft superseded by an adoption back to the SAME raw
|
|
832
|
+
// still has (base = pending backing) !== v and must notify.
|
|
833
|
+
if (e !== null && e !== t.v) notifyFold(t, e, t.v);
|
|
671
834
|
}
|
|
672
835
|
}
|
|
673
836
|
|
|
@@ -717,7 +880,7 @@ function drainFolds() {
|
|
|
717
880
|
// implicit index deletes), accessors on the record (t.a — a getter node's
|
|
718
881
|
// value can change when ANY key is written), or a non-plain prototype
|
|
719
882
|
// (class instances: prototype getters derive from arbitrary fields).
|
|
720
|
-
const i = e.
|
|
883
|
+
const i = e.wk;
|
|
721
884
|
// Overlay pbs chain to the COMMITTED object (#3044): a prototype-overlay
|
|
722
885
|
// draft is plain data on its own layer, but its getPrototypeOf is the
|
|
723
886
|
// committed container — judge plainness by the COMMITTED prototype or the
|
|
@@ -765,6 +928,7 @@ function drainFolds() {
|
|
|
765
928
|
// keys with no node. O(written/pb keys) equality only when a witness exists.
|
|
766
929
|
if (e.dk !== null) {
|
|
767
930
|
if (e.del !== null && e.del.size !== 0) bumpDeep(e); else for (const r of o ?? Reflect.ownKeys(t)) {
|
|
931
|
+
if (r === $OWNER) continue;
|
|
768
932
|
const i = t[r];
|
|
769
933
|
const o = n[r];
|
|
770
934
|
if (i !== null && typeof i === "object" ? !targetsEqual(o, i) : !isEqual(o, i)) {
|
|
@@ -791,12 +955,6 @@ function drainFolds() {
|
|
|
791
955
|
}
|
|
792
956
|
if (r) setSignal(e.k, e => e + 1);
|
|
793
957
|
}
|
|
794
|
-
// Patch channel (setter site): a committed write transitions this record —
|
|
795
|
-
// queue its patches and bubble to ancestors (targeted nested writes must
|
|
796
|
-
// reach the row patch, §4b). One number compare when no patches exist.
|
|
797
|
-
// Family targets skip this site: their visibility moment is the FOLD
|
|
798
|
-
// commit (drainFolds emits), not the recompute/draft write.
|
|
799
|
-
if (e.fam === null && patchHooks !== null && patchHooks.hasPatches()) patchHooks.emitPatch(e, t, n);
|
|
800
958
|
// Projection backing folds split by channel (two pinned contracts):
|
|
801
959
|
// - sync-derive drafts (recompute body): NEVER eager — a downstream async
|
|
802
960
|
// hold can form LATER in the same flush and the leaf must stay at stale
|
|
@@ -817,14 +975,22 @@ function drainFolds() {
|
|
|
817
975
|
// Landed truth (post-await write-override): immediately visible to every
|
|
818
976
|
// reader — any staged held view is superseded.
|
|
819
977
|
if (e.ht !== null) e.ht = e.hv = null;
|
|
978
|
+
// Overlay landing (#3352): flatten in place — identity-stable, and
|
|
979
|
+
// privatizeCommitted re-slots the parent itself when it has to clone an
|
|
980
|
+
// unowned seed, so no path copy is needed.
|
|
981
|
+
if (e.ovl) return flattenOverlay(e, t);
|
|
820
982
|
const n = e.v;
|
|
821
983
|
e.pb = null;
|
|
822
984
|
e.v = t;
|
|
823
985
|
e.ch = false;
|
|
824
|
-
if (e.u
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
e.u.v[
|
|
986
|
+
if (e.u) {
|
|
987
|
+
// Identity-resolved slot (#3282) — see drainFolds' path-copy twin.
|
|
988
|
+
const r = parentSlotKey(e, n);
|
|
989
|
+
if (e.u.v[r] === n) {
|
|
990
|
+
privatizeCommitted(e.u);
|
|
991
|
+
devAssertNeverUserMutation(e.u.v);
|
|
992
|
+
e.u.v[r] = t;
|
|
993
|
+
}
|
|
828
994
|
}
|
|
829
995
|
}
|
|
830
996
|
}
|
|
@@ -833,9 +999,9 @@ const FORCE = Symbol();
|
|
|
833
999
|
|
|
834
1000
|
/** Same logical slot: both values resolve to one (re-pointed) child target —
|
|
835
1001
|
* adoption preserved identity, so the slot did not change (R9). */ function targetsEqual(e, t) {
|
|
836
|
-
if (e === null || typeof e !== "object") return false;
|
|
837
|
-
const n =
|
|
838
|
-
return n !== undefined && n ===
|
|
1002
|
+
if (e === null || typeof e !== "object" || t === null || typeof t !== "object") return false;
|
|
1003
|
+
const n = lookupTarget(e, null);
|
|
1004
|
+
return n !== undefined && n === lookupTarget(t, null);
|
|
839
1005
|
}
|
|
840
1006
|
|
|
841
1007
|
function arrayStructureChanged(e, t) {
|
|
@@ -850,8 +1016,9 @@ function arrayStructureChanged(e, t) {
|
|
|
850
1016
|
|
|
851
1017
|
function membershipChanged(e, t) {
|
|
852
1018
|
const n = Reflect.ownKeys(t);
|
|
853
|
-
|
|
854
|
-
|
|
1019
|
+
// The $OWNER stamp is not membership: an owned side counts one key more.
|
|
1020
|
+
if (Reflect.ownKeys(e).length - +isOwned(e) !== n.length - +isOwned(t)) return true;
|
|
1021
|
+
for (const t of n) if (t !== $OWNER && !(t in e)) return true;
|
|
855
1022
|
return false;
|
|
856
1023
|
}
|
|
857
1024
|
|
|
@@ -999,6 +1166,34 @@ function inDraft(e) {
|
|
|
999
1166
|
|
|
1000
1167
|
/** Targets written during the current (outermost) setter — notified at exit. */ const pendingNotify = new Set;
|
|
1001
1168
|
|
|
1169
|
+
/** Targets adopted under a live transaction this setter (adoptPB set a
|
|
1170
|
+
* transaction hold) — their nodes are notified at the outermost exit. */ const heldAdoptions = new Set;
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* Write-time notification for a transaction-held adoption (#3330 store twin;
|
|
1174
|
+
* the setter path's notifyWrites twin for adoptions). A projection's fold
|
|
1175
|
+
* normally notifies its nodes at the drain — and the drain of a batch parked
|
|
1176
|
+
* in a live transaction is the transaction's COMMIT, so the nodes took the
|
|
1177
|
+
* adopted values as fresh writes at commit time: every subscriber was
|
|
1178
|
+
* re-marked and re-ran against a frame the lane had already published (a
|
|
1179
|
+
* third `v=1 d=2`), where a signal's write had staged at write time and
|
|
1180
|
+
* promoted silently. Staging here, inside the transaction's batch, makes the
|
|
1181
|
+
* two paths one: the nodes carry transition-stamped `_pendingValue`s, their
|
|
1182
|
+
* subscribers recompute in this flush and park with the transaction, and the
|
|
1183
|
+
* commit promotes without re-notifying. `ab` moves to the adopted backing —
|
|
1184
|
+
* the view the nodes were last told (#3296) — so the drain has nothing left
|
|
1185
|
+
* to say and only path-copies.
|
|
1186
|
+
*/ function stageHeldAdoptions() {
|
|
1187
|
+
const e = [ ...heldAdoptions ];
|
|
1188
|
+
heldAdoptions.clear();
|
|
1189
|
+
for (const t of e) {
|
|
1190
|
+
const e = t.ab;
|
|
1191
|
+
if (e === null || e === t.v) continue;
|
|
1192
|
+
notifyFold(t, e, t.v);
|
|
1193
|
+
t.ab = t.v;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1002
1197
|
const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
1003
1198
|
|
|
1004
1199
|
/** Mirror of core read()'s context rule: the OWNER context (not the tracking
|
|
@@ -1008,7 +1203,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
|
1008
1203
|
* / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */ function inOwnerContext() {
|
|
1009
1204
|
const e = getOwner();
|
|
1010
1205
|
if (e === null) return false;
|
|
1011
|
-
const t = e.
|
|
1206
|
+
const t = e.gt ? e.bt : e;
|
|
1012
1207
|
return t != null && !(t.T & CONFIG_CHILDREN_FORBIDDEN);
|
|
1013
1208
|
}
|
|
1014
1209
|
|
|
@@ -1019,7 +1214,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
|
1019
1214
|
* committed to them regardless of staged writes. */ function inForbiddenScope() {
|
|
1020
1215
|
const e = getOwner();
|
|
1021
1216
|
if (e === null) return false;
|
|
1022
|
-
const t = e.
|
|
1217
|
+
const t = e.gt ? e.bt : e;
|
|
1023
1218
|
return t != null && !!(t.T & CONFIG_CHILDREN_FORBIDDEN);
|
|
1024
1219
|
}
|
|
1025
1220
|
|
|
@@ -1030,7 +1225,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
|
1030
1225
|
if (t === null) return false;
|
|
1031
1226
|
for (const e of Reflect.ownKeys(t)) {
|
|
1032
1227
|
const n = t[e];
|
|
1033
|
-
if (n.
|
|
1228
|
+
if (n.Ge !== NOT_PENDING && n.ge != null && n.ge.ft !== true) return true;
|
|
1034
1229
|
}
|
|
1035
1230
|
return false;
|
|
1036
1231
|
}
|
|
@@ -1102,6 +1297,8 @@ const lookupGetter = Object.prototype.__lookupGetter__;
|
|
|
1102
1297
|
|
|
1103
1298
|
const lookupSetter = Object.prototype.__lookupSetter__;
|
|
1104
1299
|
|
|
1300
|
+
const propertyIsEnumerable = Object.prototype.propertyIsEnumerable;
|
|
1301
|
+
|
|
1105
1302
|
function isOwnAccessor(e, t) {
|
|
1106
1303
|
return hasOwn.call(e, t) && (lookupGetter.call(e, t) !== undefined || lookupSetter.call(e, t) !== undefined);
|
|
1107
1304
|
}
|
|
@@ -1121,7 +1318,7 @@ function isOwnAccessor(e, t) {
|
|
|
1121
1318
|
|
|
1122
1319
|
/** Active optimistic override on an armed node (armed slot idles at
|
|
1123
1320
|
* NOT_PENDING; undefined = unarmed plain node). */ function hasActiveOverride(e) {
|
|
1124
|
-
return e.o?.
|
|
1321
|
+
return e.o?.be !== undefined && e.o?.be !== NOT_PENDING;
|
|
1125
1322
|
}
|
|
1126
1323
|
|
|
1127
1324
|
/** The reading computation is until()'s authoritative-view predicate — same
|
|
@@ -1156,30 +1353,53 @@ function isOwnAccessor(e, t) {
|
|
|
1156
1353
|
// Authoritative-view reads (until()'s predicate) skip the override arm
|
|
1157
1354
|
// only: staged pending values are authoritative, overrides are the
|
|
1158
1355
|
// caller's optimism.
|
|
1159
|
-
const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.
|
|
1356
|
+
const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.be) : e.Ge !== NOT_PENDING && (latestReadActive ||
|
|
1160
1357
|
// Owner-context pending visibility — except HELD truth (#3164,
|
|
1161
1358
|
// see CONFIG_HELD_TRUTH: fold-staged or entangle-stolen
|
|
1162
1359
|
// confirming truth), which only authoritative/latest readers
|
|
1163
1360
|
// see (core read()'s A17-for-held-truth twin; ordinary readers
|
|
1164
1361
|
// keep committed until the transaction's reveal — latest() is
|
|
1165
1362
|
// exempted by the leading arm above).
|
|
1166
|
-
(inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.
|
|
1363
|
+
(inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Ge : t;
|
|
1167
1364
|
return n === FORCE ? t : n;
|
|
1168
1365
|
}
|
|
1169
1366
|
|
|
1367
|
+
/** §7b: a chained target's child found as a RAW — from its pending backing
|
|
1368
|
+
* (a cloneRaw of the inner proxy, whose descriptors yield the inner store's
|
|
1369
|
+
* raws) or from the deep() walk's descriptor read through the chain — must
|
|
1370
|
+
* resolve to the inner family's proxy for that object before this family
|
|
1371
|
+
* wraps it, so the overlay serves the same chained targets as the settled
|
|
1372
|
+
* state. Otherwise every row re-wrapped as a fresh non-chained target keyed
|
|
1373
|
+
* by the raw: identities churned for the life of an optimistic action and
|
|
1374
|
+
* snapped back at settle, and writes landed on those orphans while deep()'s
|
|
1375
|
+
* witnesses sat on the chained targets (#3323). The inner family owns the
|
|
1376
|
+
* raw iff it has served it or its backing holds that exact object at `key`;
|
|
1377
|
+
* anything else is a draft's own replacement object — view-owned, correctly
|
|
1378
|
+
* non-chained — and is returned as is. Recurses down further chains. */ function resolveChainedRaw(e, t, n) {
|
|
1379
|
+
const r = e.v[$TARGET];
|
|
1380
|
+
if (r.ch) {
|
|
1381
|
+
const e = resolveChainedRaw(r, t, n);
|
|
1382
|
+
return e === n ? n : wrapNext(e, r, t);
|
|
1383
|
+
}
|
|
1384
|
+
const i = lookupTarget(n, r.fam);
|
|
1385
|
+
if (i !== undefined) return i.px;
|
|
1386
|
+
if ((r.v[t] === n || r.pb?.[t] === n) && isWrappable(n)) return wrapNext(n, r, t);
|
|
1387
|
+
return n;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1170
1390
|
/** Serve an own data key: node-first when a node exists (pending visibility,
|
|
1171
1391
|
* holds, lanes ride the node); backing otherwise. Chained backings (§7b: the
|
|
1172
1392
|
* backing IS another store's proxy) serve the read-through value — the outer
|
|
1173
1393
|
* node is linked only for adoption-swap notification, its value never
|
|
1174
|
-
* shadows the live chain. */ function serveDataKey(e, t, n, r, i) {
|
|
1175
|
-
const
|
|
1176
|
-
let
|
|
1394
|
+
* shadows the live chain. */ function serveDataKey(e, t, n, r, i, o = -1) {
|
|
1395
|
+
const l = e.ch && r === e.v;
|
|
1396
|
+
let f = n;
|
|
1177
1397
|
// §6: on optimistic arrays LENGTH IS A VIEW, not a node value — one home
|
|
1178
1398
|
// (backing ± presence overrides) for both length and indices makes torn
|
|
1179
1399
|
// iteration impossible (a length node's value rides different visibility
|
|
1180
1400
|
// rails than index overrides mid-settle). The node still carries
|
|
1181
1401
|
// subscriptions; its value is never served here.
|
|
1182
|
-
if (t === "length" && e.fam?.opt === true && !
|
|
1402
|
+
if (t === "length" && e.fam?.opt === true && !l && Array.isArray(r)) {
|
|
1183
1403
|
if (!inDraft(e)) {
|
|
1184
1404
|
const r = e.n?.length;
|
|
1185
1405
|
if (r !== undefined) {
|
|
@@ -1199,9 +1419,9 @@ function isOwnAccessor(e, t) {
|
|
|
1199
1419
|
// #2951). Once ensurePB runs, the seeded clone carries the view.
|
|
1200
1420
|
// AUTHORITATIVE drafts (projection derive) never overlay — ensurePB's
|
|
1201
1421
|
// seeding rule, applied to the read side (#3108).
|
|
1202
|
-
if (e.fam?.opt && e
|
|
1422
|
+
if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
|
|
1203
1423
|
const n = e.n?.[t];
|
|
1204
|
-
if (n !== undefined && hasActiveOverride(n))
|
|
1424
|
+
if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.be);
|
|
1205
1425
|
}
|
|
1206
1426
|
} else {
|
|
1207
1427
|
if (i !== undefined) {
|
|
@@ -1213,28 +1433,31 @@ function isOwnAccessor(e, t) {
|
|
|
1213
1433
|
// parity): READ_SLOW = a global read window or non-plain node.
|
|
1214
1434
|
let e = readNodeFast(i);
|
|
1215
1435
|
if (e === READ_SLOW) e = read(i);
|
|
1216
|
-
if (!
|
|
1217
|
-
} else if (!
|
|
1218
|
-
|
|
1436
|
+
if (!l || hasActiveOverride(i)) f = e === FORCE ? n : e;
|
|
1437
|
+
} else if (!l || hasActiveOverride(i)) {
|
|
1438
|
+
f = nodeValue(i, n);
|
|
1219
1439
|
}
|
|
1220
1440
|
} else if (getObserver() !== null) {
|
|
1221
|
-
read
|
|
1441
|
+
// First tracked read: create + link, and let the wrap-cache branch
|
|
1442
|
+
// below populate px/pxv so read #2 skips wrapNext (slice 2).
|
|
1443
|
+
read(i = getNode(e, t, n, o));
|
|
1222
1444
|
}
|
|
1223
1445
|
}
|
|
1224
1446
|
// Shallow stores serve data raw; store-proxy slots get boundary wrappers.
|
|
1225
|
-
if (e.s) return serveShallow(e, t,
|
|
1447
|
+
if (e.s) return serveShallow(e, t, f);
|
|
1448
|
+
if (e.ch && !l && f !== null && typeof f === "object" && f[$TARGET] === undefined) f = resolveChainedRaw(e, t, f);
|
|
1226
1449
|
if (i !== undefined) {
|
|
1227
1450
|
// Wrap cache (see getNode): only wrappables are ever cached, so a hit
|
|
1228
1451
|
// skips isWrappable too — pointer-compare replaces both checks.
|
|
1229
|
-
if (i.pxv ===
|
|
1230
|
-
if (!isWrappable(
|
|
1231
|
-
const n = wrapNext(
|
|
1452
|
+
if (i.pxv === f && f !== undefined) return draftServe(e, i.px);
|
|
1453
|
+
if (!isWrappable(f)) return f;
|
|
1454
|
+
const n = wrapNext(f, e, t);
|
|
1232
1455
|
i.px = n;
|
|
1233
|
-
i.pxv =
|
|
1456
|
+
i.pxv = f;
|
|
1234
1457
|
return draftServe(e, n);
|
|
1235
1458
|
}
|
|
1236
|
-
if (!isWrappable(
|
|
1237
|
-
return draftServe(e, wrapNext(
|
|
1459
|
+
if (!isWrappable(f)) return f;
|
|
1460
|
+
return draftServe(e, wrapNext(f, e, t));
|
|
1238
1461
|
}
|
|
1239
1462
|
|
|
1240
1463
|
/** §6c store-wide status gate for reads that DON'T flow through a node:
|
|
@@ -1285,6 +1508,8 @@ const traps = {
|
|
|
1285
1508
|
if (typeof t !== "string") {
|
|
1286
1509
|
if (t === $TARGET) return e;
|
|
1287
1510
|
if (t === $PROXY) return n;
|
|
1511
|
+
if (t === $OWNER) return undefined;
|
|
1512
|
+
// ownership stamp: never a user key
|
|
1288
1513
|
// refresh()/isPending resolve the projection computed through $REFRESH.
|
|
1289
1514
|
if (t === $REFRESH) return e.fam?.node ?? undefined;
|
|
1290
1515
|
if (t === $TRACK) {
|
|
@@ -1320,8 +1545,11 @@ const traps = {
|
|
|
1320
1545
|
// tracked read of a present data key — the dbmon/uibench effect re-read
|
|
1321
1546
|
// shape. Skips serveDataKey's frame, the FORCE compare (only accessor
|
|
1322
1547
|
// keys ever hold the sentinel), and isWrappable for primitives.
|
|
1323
|
-
|
|
1324
|
-
|
|
1548
|
+
// ONE node-map lookup serves this block and the accessor probe below
|
|
1549
|
+
// (nothing between them creates nodes).
|
|
1550
|
+
const i = e.n?.[t];
|
|
1551
|
+
if (e.ch === false && writeScopes === null) {
|
|
1552
|
+
const n = i;
|
|
1325
1553
|
if (n !== undefined && n.acc !== true && getObserver() !== null) {
|
|
1326
1554
|
let r = readNodeFast(n);
|
|
1327
1555
|
if (r === READ_SLOW) r = read(n);
|
|
@@ -1349,14 +1577,22 @@ const traps = {
|
|
|
1349
1577
|
// absent-key/accessor subscriptions for every store read during any
|
|
1350
1578
|
// derive, leaving nested projections permanently dependency-less when
|
|
1351
1579
|
// their sources hadn't materialized yet (#3037).
|
|
1352
|
-
|
|
1580
|
+
// First-read dedupe (create-floor slice 2): remember the probe verdict —
|
|
1581
|
+
// node creation downstream reuses it instead of re-scanning the
|
|
1582
|
+
// descriptor, but only when the probed object IS the one getNode would
|
|
1583
|
+
// scan (pb ?? v).
|
|
1584
|
+
let o = -1;
|
|
1353
1585
|
{
|
|
1354
|
-
|
|
1355
|
-
if (
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1586
|
+
let l;
|
|
1587
|
+
if (i !== undefined) l = i.acc === true; else if (!inDraft(e) && getObserver() !== null) {
|
|
1588
|
+
l = isOwnAccessor(r, t);
|
|
1589
|
+
if (r === (e.pb ?? e.v)) o = l ? 1 : 0;
|
|
1590
|
+
} else l = false;
|
|
1591
|
+
if (l) {
|
|
1592
|
+
if (!inDraft(e) && getObserver() !== null) read(i ?? getNode(e, t, undefined, o));
|
|
1593
|
+
const l = Reflect.get(r, t, n);
|
|
1594
|
+
if (e.s) return serveShallow(e, t, l);
|
|
1595
|
+
return isWrappable(l) ? draftServe(e, wrapNext(l, e, t)) : l;
|
|
1360
1596
|
}
|
|
1361
1597
|
}
|
|
1362
1598
|
// Plain-data fast path: no descriptor allocation per read.
|
|
@@ -1366,40 +1602,41 @@ const traps = {
|
|
|
1366
1602
|
// backings chain to the committed backing, so "own in the view" means
|
|
1367
1603
|
// own on either layer (ownInView) — a genuine prototype method is one
|
|
1368
1604
|
// that is own on NEITHER.
|
|
1369
|
-
const
|
|
1370
|
-
if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(
|
|
1371
|
-
let
|
|
1372
|
-
if (
|
|
1605
|
+
const l = e.ovl && r === e.pb;
|
|
1606
|
+
if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(l && hasOwn.call(e.v, t))) return undefined;
|
|
1607
|
+
let f = r[t];
|
|
1608
|
+
if (f === undefined ? !hasOwn.call(r, t) && !(l && hasOwn.call(e.v, t)) : false) {
|
|
1373
1609
|
// Inherited: prototype getters/methods run with the proxy receiver.
|
|
1374
|
-
|
|
1375
|
-
if (typeof
|
|
1610
|
+
f = Reflect.get(r, t, n);
|
|
1611
|
+
if (typeof f === "function") return f;
|
|
1376
1612
|
// proto methods untracked
|
|
1377
1613
|
// Reading a currently-absent own key subscribes to it (R12) — for any
|
|
1378
1614
|
// target OUTSIDE its own draft scope, even mid-setter (#3037, above).
|
|
1379
|
-
if (
|
|
1380
|
-
if (getObserver() !== null) read(getNode(e, t, undefined));
|
|
1615
|
+
if (f === undefined && !inDraft(e)) {
|
|
1616
|
+
if (getObserver() !== null) read(getNode(e, t, undefined, o));
|
|
1381
1617
|
const n = e.n?.[t];
|
|
1382
1618
|
if (n) {
|
|
1383
1619
|
const r = nodeValue(n, undefined);
|
|
1384
1620
|
if (e.s) return serveShallow(e, t, r);
|
|
1385
1621
|
return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
|
|
1386
1622
|
}
|
|
1387
|
-
} else if (
|
|
1623
|
+
} else if (f === undefined && inDraft(e) && e.fam?.opt && draftSeesOverrides(e) &&
|
|
1388
1624
|
// AUTHORITATIVE drafts (landing folds) never seed from overrides —
|
|
1389
1625
|
// the caller's optimism is not truth (has-trap twin below).
|
|
1390
1626
|
!authoritativeServe()) {
|
|
1391
1627
|
const n = e.n?.[t];
|
|
1392
|
-
if (n !== undefined && hasActiveOverride(n))
|
|
1628
|
+
if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.be);
|
|
1393
1629
|
}
|
|
1394
|
-
if (e.s) return serveShallow(e, t,
|
|
1395
|
-
return isWrappable(
|
|
1630
|
+
if (e.s) return serveShallow(e, t, f);
|
|
1631
|
+
return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
|
|
1396
1632
|
}
|
|
1397
|
-
if (typeof
|
|
1633
|
+
if (typeof f === "function" && !hasOwn.call(r, t) && !(l && hasOwn.call(e.v, t))) return f;
|
|
1398
1634
|
// proto method
|
|
1399
|
-
return serveDataKey(e, t,
|
|
1635
|
+
return serveDataKey(e, t, f, r, i, o);
|
|
1400
1636
|
},
|
|
1401
1637
|
has(e, t) {
|
|
1402
1638
|
if (t === $TARGET || t === $PROXY || t === $TRACK) return true;
|
|
1639
|
+
if (t === $OWNER) return false;
|
|
1403
1640
|
if (pendingCheckActive) witnessAffectsMark(e, t);
|
|
1404
1641
|
if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
|
|
1405
1642
|
const n = readSource(e);
|
|
@@ -1415,11 +1652,11 @@ const traps = {
|
|
|
1415
1652
|
if (hasActiveOverride(n)) r = !!i;
|
|
1416
1653
|
} else if (!authoritativeServe()) {
|
|
1417
1654
|
const n = e.h?.[t];
|
|
1418
|
-
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.
|
|
1655
|
+
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.be);
|
|
1419
1656
|
}
|
|
1420
|
-
} else if (e.fam?.opt && e
|
|
1657
|
+
} else if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
|
|
1421
1658
|
const n = e.h?.[t];
|
|
1422
|
-
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.
|
|
1659
|
+
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.be);
|
|
1423
1660
|
}
|
|
1424
1661
|
return r;
|
|
1425
1662
|
},
|
|
@@ -1427,66 +1664,17 @@ const traps = {
|
|
|
1427
1664
|
if (pendingCheckActive) witnessAffectsMark(e);
|
|
1428
1665
|
if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
|
|
1429
1666
|
if (!inDraft(e) && getObserver() !== null) read(getKeySetNode(e));
|
|
1430
|
-
|
|
1431
|
-
let n;
|
|
1432
|
-
if (e.ovl && t === e.pb) {
|
|
1433
|
-
// Overlay merge (#3044): committed keys in their order, then this
|
|
1434
|
-
// batch's NEW keys, minus deletes.
|
|
1435
|
-
n = Reflect.ownKeys(e.v);
|
|
1436
|
-
const r = e.del;
|
|
1437
|
-
if (r !== null && r.size !== 0) n = n.filter(e => !r.has(e));
|
|
1438
|
-
for (const r of Reflect.ownKeys(t)) {
|
|
1439
|
-
if (!hasOwn.call(e.v, r)) n.push(r);
|
|
1440
|
-
}
|
|
1441
|
-
} else n = Reflect.ownKeys(t);
|
|
1442
|
-
// Optimistic membership overlay: presence-node overrides add/remove keys
|
|
1443
|
-
// (per-transaction lifecycle rides the nodes — §6, FINDING-2's fix).
|
|
1444
|
-
// Draft reads before the first write overlay too (pb, once created, is
|
|
1445
|
-
// seeded with the view). Authoritative-view reads (until()'s predicate,
|
|
1446
|
-
// truth-author drafts) skip the overlay.
|
|
1447
|
-
if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || e.pb === null)) {
|
|
1448
|
-
let t = null;
|
|
1449
|
-
for (const r of Reflect.ownKeys(e.h)) {
|
|
1450
|
-
const i = e.h[r];
|
|
1451
|
-
if (!hasActiveOverride(i)) continue;
|
|
1452
|
-
t ??= new Set(n);
|
|
1453
|
-
if (unwrapOverride(i.o?.Pe)) t.add(r); else t.delete(r);
|
|
1454
|
-
}
|
|
1455
|
-
if (t !== null) return [ ...t ];
|
|
1456
|
-
}
|
|
1457
|
-
return n;
|
|
1667
|
+
return visibleKeys(e, readSource(e));
|
|
1458
1668
|
},
|
|
1459
1669
|
getOwnPropertyDescriptor(e, t) {
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
// deleted keys are absent from the pending view.
|
|
1464
|
-
if (e.ovl && n === e.pb) {
|
|
1465
|
-
if (e.del !== null && e.del.has(t)) return undefined;
|
|
1466
|
-
if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, t);
|
|
1467
|
-
}
|
|
1468
|
-
if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
|
|
1469
|
-
const n = e.h?.[t];
|
|
1470
|
-
if (n !== undefined && hasActiveOverride(n)) {
|
|
1471
|
-
if (!unwrapOverride(n.o?.Pe)) return undefined;
|
|
1472
|
-
// opt delete
|
|
1473
|
-
if (r === undefined) {
|
|
1474
|
-
const n = e.n?.[t];
|
|
1475
|
-
return {
|
|
1476
|
-
value: n !== undefined ? nodeValue(n, undefined) : undefined,
|
|
1477
|
-
writable: true,
|
|
1478
|
-
enumerable: true,
|
|
1479
|
-
configurable: true
|
|
1480
|
-
};
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
if (r === undefined) return undefined;
|
|
1670
|
+
if (t === $OWNER) return undefined;
|
|
1671
|
+
const n = visibleDescriptor(e, readSource(e), t);
|
|
1672
|
+
if (n === undefined) return undefined;
|
|
1485
1673
|
// Array targets carry a real non-configurable `length` the proxy
|
|
1486
1674
|
// invariant forces us to report faithfully; everything else reports
|
|
1487
1675
|
// configurable via target indirection (core R51).
|
|
1488
|
-
if (!(t === "length" && Array.isArray(e)))
|
|
1489
|
-
return
|
|
1676
|
+
if (!(t === "length" && Array.isArray(e))) n.configurable = true;
|
|
1677
|
+
return n;
|
|
1490
1678
|
},
|
|
1491
1679
|
set(e, t, n) {
|
|
1492
1680
|
// Writes require the target's draft scope OR the projection write
|
|
@@ -1509,14 +1697,20 @@ const traps = {
|
|
|
1509
1697
|
// Array length writes implicitly delete indices — the written-keys bound
|
|
1510
1698
|
// can't see them, so poison to the full scan for this batch. Index
|
|
1511
1699
|
// writes implicitly GROW length, so arrays always record it alongside.
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
e.add("length");
|
|
1700
|
+
if (Array.isArray(l)) {
|
|
1701
|
+
if (t === "length") e.wk = WK_ALL; else if (e.wk !== WK_ALL) {
|
|
1702
|
+
const n = e.wk ??= new Set;
|
|
1703
|
+
n.add(t);
|
|
1704
|
+
n.add("length");
|
|
1518
1705
|
}
|
|
1519
|
-
} else
|
|
1706
|
+
} else {
|
|
1707
|
+
if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
|
|
1708
|
+
// Live own-key estimate for the overlay/clone choice (#3360): `in`
|
|
1709
|
+
// sees through an overlay to the committed keys, so this counts keys
|
|
1710
|
+
// NEW to the container. Deletes are not un-counted (a stale high
|
|
1711
|
+
// count only picks the overlay a little early).
|
|
1712
|
+
if (!(t in l)) e.kc++;
|
|
1713
|
+
}
|
|
1520
1714
|
// Own data keys literally named "prototype"/"constructor" land as data —
|
|
1521
1715
|
// defineProperty sidesteps a proto-chain setter named the same.
|
|
1522
1716
|
if (UNSAFE_KEYS.has(t)) {
|
|
@@ -1531,8 +1725,10 @@ const traps = {
|
|
|
1531
1725
|
}
|
|
1532
1726
|
// Overlay first-write DEFINES the own key: assignment through the proto
|
|
1533
1727
|
// chain would reject on a non-writable committed property (the clone
|
|
1534
|
-
// path normalized descriptors for exactly this — R51 parity).
|
|
1535
|
-
|
|
1728
|
+
// path normalized descriptors for exactly this — R51 parity). A
|
|
1729
|
+
// plain-data-graded backing (sc 2, owned: every slot writable) takes the
|
|
1730
|
+
// bare assignment — it lands as an own key on the overlay all the same.
|
|
1731
|
+
if (e.ovl && e.sc !== 2 && !hasOwn.call(l, t)) {
|
|
1536
1732
|
Object.defineProperty(l, t, {
|
|
1537
1733
|
value: o,
|
|
1538
1734
|
writable: true,
|
|
@@ -1554,23 +1750,19 @@ const traps = {
|
|
|
1554
1750
|
const i = !r && getWriteOverride();
|
|
1555
1751
|
if (!r && !i) return true;
|
|
1556
1752
|
if (t === "__proto__") return true;
|
|
1557
|
-
if (n.get || n.set)
|
|
1558
|
-
e.a = true;
|
|
1559
|
-
// Accessor demotion (re-audit blocker 3): a record that acquires an
|
|
1560
|
-
// accessor after patch registration stops being patchable — pull its
|
|
1561
|
-
// patches and re-drive them as tracked effect fallbacks. Hooks are
|
|
1562
|
-
// installed whenever pc.p exists (registration installs them).
|
|
1563
|
-
if (e.pc !== null && e.pc.p !== null) patchHooks.demoteToEffects(e);
|
|
1564
|
-
}
|
|
1753
|
+
if (n.get || n.set) e.a = true;
|
|
1565
1754
|
// Unwrap before ensurePB (see the set trap: self-reference materializes).
|
|
1566
1755
|
if ("value" in n) n = {
|
|
1567
1756
|
...n,
|
|
1568
1757
|
value: unwrapValue(n.value)
|
|
1569
1758
|
};
|
|
1570
1759
|
const o = ensurePB(e);
|
|
1760
|
+
// A non-default data descriptor (or an accessor) leaves the plain-data
|
|
1761
|
+
// grade: the key reaches the committed backing as defined, so the spread
|
|
1762
|
+
// clone and bare-assignment paths no longer describe it.
|
|
1763
|
+
if (e.a || !(n.enumerable && n.writable && n.configurable)) e.sc = 1;
|
|
1571
1764
|
pendingNotify.add(e);
|
|
1572
|
-
|
|
1573
|
-
if (l.wk !== WK_ALL) (l.wk ??= new Set).add(t);
|
|
1765
|
+
if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
|
|
1574
1766
|
Object.defineProperty(o, t, n);
|
|
1575
1767
|
if (e.del !== null) e.del.delete(t);
|
|
1576
1768
|
if (i) notifyWrites(e);
|
|
@@ -1582,8 +1774,7 @@ const traps = {
|
|
|
1582
1774
|
if (!n && !r) return true;
|
|
1583
1775
|
const i = ensurePB(e);
|
|
1584
1776
|
pendingNotify.add(e);
|
|
1585
|
-
|
|
1586
|
-
if (o.wk !== WK_ALL) (o.wk ??= new Set).add(t);
|
|
1777
|
+
if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
|
|
1587
1778
|
delete i[t];
|
|
1588
1779
|
// A prototype overlay cannot shadow a delete of a committed key —
|
|
1589
1780
|
// record it aside (#3044); reads/has/ownKeys/commit consult the set.
|
|
@@ -1631,6 +1822,7 @@ const traps = {
|
|
|
1631
1822
|
adoptPB(r, unwrapValue(o));
|
|
1632
1823
|
}
|
|
1633
1824
|
}
|
|
1825
|
+
if (writing === 0 && heldAdoptions.size) stageHeldAdoptions();
|
|
1634
1826
|
}
|
|
1635
1827
|
|
|
1636
1828
|
// Affects integration: the legacy affects machinery reads next targets
|
|
@@ -1647,6 +1839,10 @@ function createStoreNext(e, t = false) {
|
|
|
1647
1839
|
return [ n, setter ];
|
|
1648
1840
|
}
|
|
1649
1841
|
|
|
1842
|
+
// ---------------------------------------------------------------------------
|
|
1843
|
+
// snapshot (next targets): the backing IS the plain raw graph — zero copy.
|
|
1844
|
+
// Sees pending (R27) by reading pb. Chained/owned-copy caching lands with the
|
|
1845
|
+
// utilities increment; this covers the createStore-suite contract.
|
|
1650
1846
|
/** True when `proxy` is a SHALLOW store (children served verbatim, slots
|
|
1651
1847
|
* replaced by reference — #2932). The list driver uses this to choose the
|
|
1652
1848
|
* slot-patch channel (collected row bodies) over per-record registration. */ function storeIsShallow(e) {
|
|
@@ -1678,6 +1874,78 @@ function createStoreNext(e, t = false) {
|
|
|
1678
1874
|
return t !== undefined && t.fam?.opt === true;
|
|
1679
1875
|
}
|
|
1680
1876
|
|
|
1877
|
+
// ---------------------------------------------------------------------------
|
|
1878
|
+
// visibility: what a reader sees on a record — ONE rule for the ownKeys /
|
|
1879
|
+
// getOwnPropertyDescriptor traps and the deep() walk (#3323). The walk used
|
|
1880
|
+
// to re-derive the trap rules over raw backings and missed each new one in
|
|
1881
|
+
// turn (the #3044 overlay merge, then #3323's chain, then optimistic presence
|
|
1882
|
+
// and value overrides); sharing the body makes parity structural.
|
|
1883
|
+
/** Keys visible on `target` served from `src` (= readSource(target)): the
|
|
1884
|
+
* pending-overlay merge (#3044) and, on optimistic families, presence-node
|
|
1885
|
+
* overrides (§6, FINDING-2's fix). Draft reads before the first write overlay
|
|
1886
|
+
* too (pb, once created, is seeded with the view). Authoritative-view reads
|
|
1887
|
+
* (until()'s predicate, truth-author drafts) skip the overlay. */ function visibleKeys(e, t) {
|
|
1888
|
+
let n;
|
|
1889
|
+
if (e.ovl && t === e.pb) {
|
|
1890
|
+
// Overlay merge (#3044): committed keys in their order, then this
|
|
1891
|
+
// batch's NEW keys, minus deletes.
|
|
1892
|
+
n = Reflect.ownKeys(e.v);
|
|
1893
|
+
const r = e.del;
|
|
1894
|
+
if (r !== null && r.size !== 0) n = n.filter(e => !r.has(e));
|
|
1895
|
+
for (const r of Reflect.ownKeys(t)) {
|
|
1896
|
+
if (!hasOwn.call(e.v, r)) n.push(r);
|
|
1897
|
+
}
|
|
1898
|
+
} else n = Reflect.ownKeys(t);
|
|
1899
|
+
// Drop the $OWNER stamp (owned backings carry it as an own enumerable
|
|
1900
|
+
// symbol). Symbols enumerate last, so the scan stops at the first string.
|
|
1901
|
+
for (let e = n.length - 1; e >= 0 && typeof n[e] === "symbol"; e--) {
|
|
1902
|
+
if (n[e] === $OWNER) {
|
|
1903
|
+
n.splice(e, 1);
|
|
1904
|
+
break;
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || draftSeesOverrides(e))) {
|
|
1908
|
+
let t = null;
|
|
1909
|
+
for (const r of Reflect.ownKeys(e.h)) {
|
|
1910
|
+
const i = e.h[r];
|
|
1911
|
+
if (!hasActiveOverride(i)) continue;
|
|
1912
|
+
t ??= new Set(n);
|
|
1913
|
+
if (unwrapOverride(i.o?.be)) t.add(r); else t.delete(r);
|
|
1914
|
+
}
|
|
1915
|
+
if (t !== null) return [ ...t ];
|
|
1916
|
+
}
|
|
1917
|
+
return n;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
/** The data/accessor descriptor visible for `key` on `target` served from
|
|
1921
|
+
* `src`: overlay (#3044 — unwritten keys live on the committed backing,
|
|
1922
|
+
* deleted keys are absent) and optimistic presence overrides (an opt delete
|
|
1923
|
+
* hides the key; an opt add synthesizes a data descriptor from the value
|
|
1924
|
+
* node). `configurable` is the trap's concern (proxy invariant). */ function visibleDescriptor(e, t, n) {
|
|
1925
|
+
let r = Object.getOwnPropertyDescriptor(t, n);
|
|
1926
|
+
if (e.ovl && t === e.pb) {
|
|
1927
|
+
if (e.del !== null && e.del.has(n)) return undefined;
|
|
1928
|
+
if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, n);
|
|
1929
|
+
}
|
|
1930
|
+
if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
|
|
1931
|
+
const t = e.h?.[n];
|
|
1932
|
+
if (t !== undefined && hasActiveOverride(t)) {
|
|
1933
|
+
if (!unwrapOverride(t.o?.be)) return undefined;
|
|
1934
|
+
// opt delete
|
|
1935
|
+
if (r === undefined) {
|
|
1936
|
+
const t = e.n?.[n];
|
|
1937
|
+
return {
|
|
1938
|
+
value: t !== undefined ? nodeValue(t, undefined) : undefined,
|
|
1939
|
+
writable: true,
|
|
1940
|
+
enumerable: true,
|
|
1941
|
+
configurable: true
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
return r;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1681
1949
|
/** Tracking deep snapshot (`deep()` for next targets): subscribes to the
|
|
1682
1950
|
* key-set and deep-witness node at every reachable level, then returns the
|
|
1683
1951
|
* plain view. Shared references and cycles handled via the visited set. */ function deepNext(e) {
|
|
@@ -1689,34 +1957,69 @@ function createStoreNext(e, t = false) {
|
|
|
1689
1957
|
// in per-key nodes, and it walks TARGETS directly — no per-child proxy
|
|
1690
1958
|
// round-trip (wrapNext → proxy → $TARGET trap) on the re-walk every
|
|
1691
1959
|
// effect run performs.
|
|
1692
|
-
|
|
1960
|
+
// Resolve `child` (a raw or a stored proxy found under `t`) to the target
|
|
1961
|
+
// that `t`'s family serves for it — created on first visit, as the get trap
|
|
1962
|
+
// would. Undefined = leaf (unwrappable or raw-marked).
|
|
1963
|
+
const childTarget = (e, t, n) => {
|
|
1964
|
+
let r = lookupTarget(t, e.fam);
|
|
1965
|
+
if (r === undefined) {
|
|
1966
|
+
if (!isWrappable(t)) return undefined;
|
|
1967
|
+
wrapNext(t, e, n);
|
|
1968
|
+
// Stored proxies (chained slots) that this family passes through
|
|
1969
|
+
// resolve to their own target.
|
|
1970
|
+
r = lookupTarget(t, e.fam) ?? t[$TARGET];
|
|
1971
|
+
}
|
|
1972
|
+
return r;
|
|
1973
|
+
};
|
|
1974
|
+
const walkT = e => {
|
|
1693
1975
|
const t = readSource(e);
|
|
1694
1976
|
if (n.has(t)) return;
|
|
1695
1977
|
n.add(t);
|
|
1696
1978
|
read(getKeySetNode(e));
|
|
1697
1979
|
read(getDeepNode(e));
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1980
|
+
// Chained backing (§7b): the committed backing IS another store's proxy.
|
|
1981
|
+
// Writes to the inner store bump the INNER record's witnesses; this
|
|
1982
|
+
// target's k/dk hear only its own family's writes (optimistic overrides).
|
|
1983
|
+
// Read through the whole chain — the $TRACK trap's rule (#2864 / R21)
|
|
1984
|
+
// applied to deep() (#3323). From `t.v`, not `src`: a pending backing is a
|
|
1985
|
+
// raw clone and would hide the chain. Non-chained targets pay one flag.
|
|
1986
|
+
for (let t = e; t.ch; ) {
|
|
1987
|
+
t = t.v[$TARGET];
|
|
1988
|
+
read(getKeySetNode(t));
|
|
1989
|
+
read(getDeepNode(t));
|
|
1990
|
+
}
|
|
1991
|
+
// Keys and children exactly as the traps serve them — the overlay merge
|
|
1992
|
+
// (#3044/#3283: a bare ownKeys over a pending backing mid-flush dropped
|
|
1993
|
+
// every untouched child from the re-subscribing effect's dependencies)
|
|
1994
|
+
// and optimistic presence overrides (a row added under a held action
|
|
1995
|
+
// lives in `h`/`n`, not the committed backing; the walk never reached its
|
|
1996
|
+
// record, so deep() was deaf to every write on it until settle).
|
|
1997
|
+
const r = e.fam?.opt === true;
|
|
1998
|
+
for (const n of visibleKeys(e, t)) {
|
|
1999
|
+
const i = visibleDescriptor(e, t, n);
|
|
1701
2000
|
if (i === undefined) continue;
|
|
1702
2001
|
if (i.get || i.set) {
|
|
1703
2002
|
e.a = true;
|
|
1704
2003
|
continue;
|
|
1705
2004
|
// accessors track through their own reads when invoked
|
|
1706
2005
|
}
|
|
1707
|
-
|
|
2006
|
+
let o = i.value;
|
|
2007
|
+
// An active value override on an optimistic node shadows the backing's
|
|
2008
|
+
// child (an optimistic replacement `d[i] = {...}`) — follow what reads
|
|
2009
|
+
// serve, as nodeValue's leading arm does.
|
|
2010
|
+
if (r) {
|
|
2011
|
+
const t = e.n?.[n];
|
|
2012
|
+
if (t !== undefined && hasActiveOverride(t) && !authoritativeServe()) o = unwrapOverride(t.o.be);
|
|
2013
|
+
}
|
|
1708
2014
|
if (o === null || typeof o !== "object") continue;
|
|
1709
|
-
//
|
|
1710
|
-
//
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
l = r.get(o);
|
|
1716
|
-
if (l === undefined) continue;
|
|
2015
|
+
// Through a chain the descriptor yields the INNERMOST raw: resolve it to
|
|
2016
|
+
// the inner family's proxy so this level wraps the chained `view[i]` the
|
|
2017
|
+
// get trap serves, never a fresh non-chained wrapper of the base raw.
|
|
2018
|
+
if (e.ch && o[$TARGET] === undefined) o = resolveChainedRaw(e, n, o);
|
|
2019
|
+
const l = childTarget(e, o, n);
|
|
2020
|
+
if (l === undefined) continue;
|
|
1717
2021
|
// raw-marked: leaf by contract
|
|
1718
|
-
|
|
1719
|
-
walkT(l);
|
|
2022
|
+
walkT(l);
|
|
1720
2023
|
}
|
|
1721
2024
|
};
|
|
1722
2025
|
walkT(t);
|
|
@@ -1746,24 +2049,35 @@ function snapshotWalk(e, t, n) {
|
|
|
1746
2049
|
// encountered and compose their views over the resolved base, innermost
|
|
1747
2050
|
// outward.
|
|
1748
2051
|
let i = null;
|
|
1749
|
-
for (
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
if (
|
|
1753
|
-
if (
|
|
1754
|
-
if (
|
|
1755
|
-
|
|
2052
|
+
for (let e = true; ;e = false) {
|
|
2053
|
+
const t = r?.[$TARGET]?.v !== undefined;
|
|
2054
|
+
let o = t ? r[$TARGET] : undefined;
|
|
2055
|
+
if (o === undefined && n !== null) o = lookupTarget(r, n);
|
|
2056
|
+
if (o === undefined) o = lookupTarget(r, null);
|
|
2057
|
+
if (o === undefined) break;
|
|
2058
|
+
// Entering a level from a RAW below a chained family: the raw resolves to
|
|
2059
|
+
// the INNER store's target, but this family's wrapper for it — keyed by
|
|
2060
|
+
// the inner proxy, §7b — is where the outer overrides live. Start from
|
|
2061
|
+
// the wrapper so they compose (#3323: a nested optimistic write on a view
|
|
2062
|
+
// row was served by reads but missing from deep()/snapshot()). Entry only:
|
|
2063
|
+
// the descent then runs wrapper → inner → raw and terminates.
|
|
2064
|
+
if (e && !t && n !== null && o.fam !== n) {
|
|
2065
|
+
const e = n.map.get(o.px);
|
|
2066
|
+
if (e !== undefined) o = e;
|
|
2067
|
+
}
|
|
2068
|
+
if (o.fam !== null) n = o.fam;
|
|
2069
|
+
if (o.fam?.opt === true) (i ??= []).push(o);
|
|
1756
2070
|
// The shared visibility decision (#3147): the speculative peek serves
|
|
1757
2071
|
// pending staging, but a HELD landing is masked to committed exactly as
|
|
1758
2072
|
// it is for per-key readers — the two families must answer alike while
|
|
1759
2073
|
// a transaction holds store landings.
|
|
1760
|
-
const
|
|
2074
|
+
const l = pendingBackingVisible(o, true);
|
|
1761
2075
|
// Snapshot runs mid-flush (tracked memos execute before commit), so a
|
|
1762
2076
|
// pending prototype overlay must present as a REAL merged container.
|
|
1763
|
-
if (
|
|
1764
|
-
const
|
|
1765
|
-
if (
|
|
1766
|
-
r =
|
|
2077
|
+
if (l && o.ovl) materializePB(o);
|
|
2078
|
+
const f = l ? o.pb : o.v;
|
|
2079
|
+
if (f === r) break;
|
|
2080
|
+
r = f;
|
|
1767
2081
|
}
|
|
1768
2082
|
if (!isWrappable(r)) return r;
|
|
1769
2083
|
// Optimistic families: compose the visible view; a composed view is a fresh
|
|
@@ -1778,7 +2092,7 @@ function snapshotWalk(e, t, n) {
|
|
|
1778
2092
|
const l = o ? [] : Object.create(Object.getPrototypeOf(e));
|
|
1779
2093
|
t.set(r, l);
|
|
1780
2094
|
for (const r of Reflect.ownKeys(e)) {
|
|
1781
|
-
if (o && r === "length") continue;
|
|
2095
|
+
if (o && r === "length" || r === $OWNER) continue;
|
|
1782
2096
|
const i = e[r];
|
|
1783
2097
|
l[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
|
|
1784
2098
|
}
|
|
@@ -1792,12 +2106,12 @@ function snapshotWalk(e, t, n) {
|
|
|
1792
2106
|
// subtrees "unmodified relative to source"): non-enumerable symbols are
|
|
1793
2107
|
// excluded (recon-snap R29), and the copy registers BEFORE descent so
|
|
1794
2108
|
// cycles keep identity (FINDING-3).
|
|
1795
|
-
if (
|
|
2109
|
+
if (isOwned(r)) {
|
|
1796
2110
|
const e = Array.isArray(r);
|
|
1797
2111
|
const i = e ? [] : Object.create(Object.getPrototypeOf(r));
|
|
1798
2112
|
t.set(r, i);
|
|
1799
2113
|
for (const o of Reflect.ownKeys(r)) {
|
|
1800
|
-
if (e && o === "length") continue;
|
|
2114
|
+
if (e && o === "length" || o === $OWNER) continue;
|
|
1801
2115
|
const l = Object.getOwnPropertyDescriptor(r, o);
|
|
1802
2116
|
if (typeof o === "symbol" && !l.enumerable) continue;
|
|
1803
2117
|
if (l.get || l.set) {
|
|
@@ -1805,10 +2119,10 @@ function snapshotWalk(e, t, n) {
|
|
|
1805
2119
|
continue;
|
|
1806
2120
|
}
|
|
1807
2121
|
const f = l.value;
|
|
1808
|
-
const
|
|
1809
|
-
if (l.enumerable && l.writable && l.configurable) i[o] =
|
|
2122
|
+
const s = f !== null && typeof f === "object" ? snapshotWalk(f, t, n) : f;
|
|
2123
|
+
if (l.enumerable && l.writable && l.configurable) i[o] = s; else Object.defineProperty(i, o, {
|
|
1810
2124
|
...l,
|
|
1811
|
-
value:
|
|
2125
|
+
value: s
|
|
1812
2126
|
});
|
|
1813
2127
|
}
|
|
1814
2128
|
if (e && i.length !== r.length) i.length = r.length;
|
|
@@ -1835,4 +2149,4 @@ function snapshotWalk(e, t, n) {
|
|
|
1835
2149
|
return l ?? r;
|
|
1836
2150
|
}
|
|
1837
2151
|
|
|
1838
|
-
export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue,
|
|
2152
|
+
export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, heldMaskView, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, runAuthoritative, snapshotNext, stagedTruthPB, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetsEqual, unwrapValue, wrapNext };
|