@solidjs/signals 2.0.0-rc.5 → 2.0.0-rc.7
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.js +2467 -1322
- package/dist/node.cjs +2037 -2428
- package/dist/node.dev.cjs +13724 -0
- package/dist/prod/boundaries.js +3 -1
- package/dist/prod/core/async.js +47 -20
- package/dist/prod/core/attribution-hooks.js +3 -0
- package/dist/prod/core/constants.js +9 -1
- package/dist/prod/core/context.js +10 -16
- package/dist/prod/core/core.js +260 -188
- package/dist/prod/core/dev.js +2 -0
- package/dist/prod/core/effect.js +41 -32
- package/dist/prod/core/external.js +2 -2
- package/dist/prod/core/graph.js +35 -31
- package/dist/prod/core/heap.js +34 -40
- package/dist/prod/core/lanes.js +44 -30
- package/dist/prod/core/optimistic.js +55 -45
- package/dist/prod/core/owner.js +35 -35
- package/dist/prod/core/scheduler.js +176 -177
- package/dist/prod/core/verdict.js +53 -52
- package/dist/prod/index.js +0 -2
- package/dist/prod/map.js +104 -94
- package/dist/prod/signals.js +119 -35
- package/dist/prod/store/next/optimistic.js +217 -163
- package/dist/prod/store/next/projection.js +2 -2
- package/dist/prod/store/next/reconcile.js +140 -288
- package/dist/prod/store/next/store.js +338 -252
- package/dist/prod/store/store.js +2 -2
- package/dist/types/core/attribution-hooks.d.ts +75 -0
- package/dist/types/core/attribution.d.ts +313 -9
- package/dist/types/core/constants.d.ts +8 -0
- package/dist/types/core/core.d.ts +12 -3
- package/dist/types/core/dev.d.ts +56 -9
- package/dist/types/core/heap.d.ts +5 -3
- package/dist/types/core/invariants.d.ts +1 -1
- package/dist/types/core/lanes.d.ts +10 -0
- package/dist/types/core/scheduler.d.ts +14 -4
- package/dist/types/signals.d.ts +10 -11
- package/dist/types/store/index.d.ts +3 -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 +3 -9
- package/dist/types/store/next/target.d.ts +30 -46
- package/dist/types/store/store.d.ts +14 -9
- package/dist/types-cjs/core/attribution-hooks.d.cts +75 -0
- package/dist/types-cjs/core/attribution.d.cts +313 -9
- package/dist/types-cjs/core/constants.d.cts +8 -0
- package/dist/types-cjs/core/core.d.cts +12 -3
- package/dist/types-cjs/core/dev.d.cts +56 -9
- package/dist/types-cjs/core/heap.d.cts +5 -3
- package/dist/types-cjs/core/invariants.d.cts +1 -1
- package/dist/types-cjs/core/lanes.d.cts +10 -0
- package/dist/types-cjs/core/scheduler.d.cts +14 -4
- package/dist/types-cjs/signals.d.cts +10 -11
- package/dist/types-cjs/store/index.d.cts +3 -6
- package/dist/types-cjs/store/next/optimistic.d.cts +4 -2
- package/dist/types-cjs/store/next/reconcile.d.cts +5 -12
- package/dist/types-cjs/store/next/store.d.cts +3 -9
- package/dist/types-cjs/store/next/target.d.cts +30 -46
- package/dist/types-cjs/store/store.d.cts +14 -9
- package/package.json +3 -2
- 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/store/next/patch-hooks.d.cts +0 -41
- package/dist/types-cjs/store/next/patch.d.cts +0 -91
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { unwrapOverride, $REFRESH,
|
|
1
|
+
import { unwrapOverride, $REFRESH, NOT_PENDING, CONFIG_OWNED_WRITE, CONFIG_OPTIMISTIC, CONFIG_HELD_TRUTH, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
|
|
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, 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
|
|
|
@@ -10,8 +10,6 @@ import { $TARGET, isWrappable, markRawIngest, $PROXY, getWriteOverride, markRawO
|
|
|
10
10
|
|
|
11
11
|
import { storeNextLookup, optHooks, ownedRaw, markDescendants, devAssertNeverUserMutation } from "./target.js";
|
|
12
12
|
|
|
13
|
-
import { patchHooks, rowHooks } from "./patch-hooks.js";
|
|
14
|
-
|
|
15
13
|
/**
|
|
16
14
|
* Store rewrite — increment 2: plain deep stores with pending-backing writes.
|
|
17
15
|
* Contract: INTERNALS-STORE-STATE.md.
|
|
@@ -44,9 +42,9 @@ import { patchHooks, rowHooks } from "./patch-hooks.js";
|
|
|
44
42
|
*
|
|
45
43
|
* ARRAY SHAPE RULE: arrays normalize their named properties to dictionary
|
|
46
44
|
* 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
|
-
*
|
|
45
|
+
* target's named field count is capped at 20 — any future write-side state
|
|
46
|
+
* beyond `wk` must ride an extension object (see target.ts), never new
|
|
47
|
+
* named fields here. */
|
|
50
48
|
function TargetShape() {
|
|
51
49
|
this.v = undefined;
|
|
52
50
|
this.ch = undefined;
|
|
@@ -62,29 +60,18 @@ function TargetShape() {
|
|
|
62
60
|
this.a = undefined;
|
|
63
61
|
this.sc = undefined;
|
|
64
62
|
this.nc = undefined;
|
|
65
|
-
this.
|
|
63
|
+
this.ab = undefined;
|
|
66
64
|
this.fam = undefined;
|
|
67
65
|
this.s = undefined;
|
|
68
66
|
this.ovl = undefined;
|
|
69
67
|
this.del = undefined;
|
|
70
|
-
this.
|
|
68
|
+
this.wk = undefined;
|
|
71
69
|
this.hv = undefined;
|
|
72
70
|
this.ht = undefined;
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
TargetShape.prototype = Object.prototype;
|
|
76
74
|
|
|
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
75
|
function createTarget(e, t, n, r = t?.fam ?? null) {
|
|
89
76
|
// The proxy target carries the array exotic class when the value is an
|
|
90
77
|
// array, so Array.isArray(proxy) is true; the fields live on it directly.
|
|
@@ -101,7 +88,7 @@ function createTarget(e, t, n, r = t?.fam ?? null) {
|
|
|
101
88
|
i.h = null;
|
|
102
89
|
i.k = null;
|
|
103
90
|
i.dk = null;
|
|
104
|
-
i.
|
|
91
|
+
i.wk = null;
|
|
105
92
|
i.u = t;
|
|
106
93
|
i.pk = n;
|
|
107
94
|
i.px = null;
|
|
@@ -109,7 +96,7 @@ function createTarget(e, t, n, r = t?.fam ?? null) {
|
|
|
109
96
|
i.a = false;
|
|
110
97
|
i.sc = false;
|
|
111
98
|
i.nc = 0;
|
|
112
|
-
i.
|
|
99
|
+
i.ab = null;
|
|
113
100
|
i.fam = r;
|
|
114
101
|
i.s = false;
|
|
115
102
|
i.ovl = false;
|
|
@@ -155,55 +142,68 @@ function wrapNext(e, t = null, n = null, r = t?.fam ?? null) {
|
|
|
155
142
|
|
|
156
143
|
// ---------------------------------------------------------------------------
|
|
157
144
|
// 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
|
-
|
|
145
|
+
// Shared slot-node equality (create-floor diet): ONE function for every
|
|
146
|
+
// store node — `this` is the node (method-call convention at every _equals
|
|
147
|
+
// site), `_host` is the baked-in target backref. Logical-slot equality:
|
|
148
|
+
// values resolving to the same child target are the same slot
|
|
149
|
+
// (privatization/adoption swap raw identity without changing the logical
|
|
150
|
+
// value — only changed leaves notify, R9).
|
|
151
|
+
const slotNodeEquals = function(e, t) {
|
|
152
|
+
return isEqual(e, t) || sameLogicalSlot(this.ht, e, t);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Shared slot-node unobserved handler (create-floor diet): registered once;
|
|
156
|
+
// the core sweep dispatches CONFIG_SLOT_NODE nodes here instead of holding a
|
|
157
|
+
// per-node closure in a per-node NodeExtension.
|
|
158
|
+
setSlotUnobserved(e => {
|
|
159
|
+
// A live affects() mark keeps the node addressable (sweep parity).
|
|
160
|
+
if (e.o?.t) return;
|
|
161
|
+
const t = e.ht;
|
|
162
|
+
const n = e.Ht;
|
|
163
|
+
if (t.n && t.n[n] === e) {
|
|
164
|
+
delete t.n[n];
|
|
165
|
+
t.nc--;
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
function getNode(e, t, n,
|
|
170
|
+
// First-read dedupe (create-floor slice 2): the get trap probes
|
|
171
|
+
// accessor-ness right before creating the node — pass the verdict through
|
|
172
|
+
// so creation skips the second descriptor scan. -1 = unknown (other
|
|
173
|
+
// callers), 0/1 = probed.
|
|
174
|
+
r = -1) {
|
|
175
|
+
const i = e.n ??= Object.create(null);
|
|
176
|
+
let o = i[t];
|
|
177
|
+
if (o === undefined) {
|
|
178
|
+
// Create-floor diet: slotSignal bakes the whole node into one literal —
|
|
179
|
+
// no options object, no equals/unobserved closures, no NodeExtension,
|
|
180
|
+
// no post-construction expandos (acc + the wrap cache px/pxv are
|
|
181
|
+
// pre-shaped fields: the proxy last served for this key and the raw it
|
|
182
|
+
// wrapped — one pointer compare replaces the per-read WeakMap lookup in
|
|
183
|
+
// wrapNext). ownedWrite rides the literal's config: the setter carries
|
|
184
|
+
// the owned-scope write guard; node-level setSignals are internal
|
|
185
|
+
// notification machinery. Projection nodes carry the projection
|
|
186
|
+
// computed as their firewall: reads through them link the derive's
|
|
187
|
+
// status/lifecycle (§7b).
|
|
188
|
+
const f = o = slotSignal(n, slotNodeEquals, e, t,
|
|
189
|
+
// Accessor-ness resolved ONCE per node (no per-object descriptor
|
|
190
|
+
// scan on reads): accessor keys serve through Reflect.get with the
|
|
191
|
+
// proxy receiver.
|
|
192
|
+
r === -1 ? isOwnAccessor(e.pb ?? e.v, t) : r === 1, e.fam?.node ?? undefined);
|
|
193
193
|
// Optimistic families: arm the override slot — setSignal routes armed
|
|
194
194
|
// nodes through the core engine (lanes, ownership, reverts all native).
|
|
195
195
|
if (e.fam?.opt) {
|
|
196
|
-
ext(
|
|
197
|
-
|
|
196
|
+
ext(f).Pe = NOT_PENDING;
|
|
197
|
+
f.T |= CONFIG_OPTIMISTIC;
|
|
198
198
|
}
|
|
199
199
|
// A node born inside a live mark's identity scope inherits the mark
|
|
200
200
|
// (the declaration walk could only cover nodes existing then).
|
|
201
|
-
if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(
|
|
202
|
-
|
|
201
|
+
if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(f, e.v, t);
|
|
202
|
+
i[t] = o;
|
|
203
203
|
e.nc++;
|
|
204
204
|
markDescendants(e);
|
|
205
205
|
}
|
|
206
|
-
return
|
|
206
|
+
return o;
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
function sameLogicalSlot(e, t, n) {
|
|
@@ -226,7 +226,7 @@ function getHasNode(e, t, n) {
|
|
|
226
226
|
}, e.fam?.node ?? undefined);
|
|
227
227
|
o.T |= CONFIG_OWNED_WRITE;
|
|
228
228
|
if (e.fam?.opt) {
|
|
229
|
-
ext(o).
|
|
229
|
+
ext(o).Pe = NOT_PENDING;
|
|
230
230
|
o.T |= CONFIG_OPTIMISTIC;
|
|
231
231
|
}
|
|
232
232
|
if (affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
|
|
@@ -247,7 +247,7 @@ function getKeySetNode(e) {
|
|
|
247
247
|
}, e.fam?.node ?? undefined);
|
|
248
248
|
n.T |= CONFIG_OWNED_WRITE;
|
|
249
249
|
if (e.fam?.opt) {
|
|
250
|
-
ext(n).
|
|
250
|
+
ext(n).Pe = NOT_PENDING;
|
|
251
251
|
n.T |= CONFIG_OPTIMISTIC;
|
|
252
252
|
}
|
|
253
253
|
e.k = t;
|
|
@@ -267,7 +267,7 @@ function getDeepNode(e) {
|
|
|
267
267
|
}, e.fam?.node ?? undefined);
|
|
268
268
|
n.T |= CONFIG_OWNED_WRITE;
|
|
269
269
|
if (e.fam?.opt) {
|
|
270
|
-
ext(n).
|
|
270
|
+
ext(n).Pe = NOT_PENDING;
|
|
271
271
|
n.T |= CONFIG_OPTIMISTIC;
|
|
272
272
|
}
|
|
273
273
|
if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
|
|
@@ -305,10 +305,12 @@ function cloneRaw(e, t) {
|
|
|
305
305
|
return Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
/**
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
|
|
308
|
+
/** Copy own `key` from `from` onto `to`. A plain data slot (enumerable,
|
|
309
|
+
* writable, configurable, no accessor) is a bare assignment — the common case
|
|
310
|
+
* and the cheap one; anything else goes through defineProperty so accessors
|
|
311
|
+
* and attribute flags survive the copy. */ function copyOwn(e, t, n) {
|
|
312
|
+
const r = Object.getOwnPropertyDescriptor(t, n);
|
|
313
|
+
if (r.get || r.set || !r.enumerable || !r.writable || !r.configurable) Object.defineProperty(e, n, r); else e[n] = r.value;
|
|
312
314
|
}
|
|
313
315
|
|
|
314
316
|
/** One-time own-accessor scan (Annex-B probes, no descriptor allocation);
|
|
@@ -333,10 +335,7 @@ function cloneRaw(e, t) {
|
|
|
333
335
|
if (!e.ovl) return;
|
|
334
336
|
const t = e.pb;
|
|
335
337
|
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
|
-
}
|
|
338
|
+
for (const e of Reflect.ownKeys(t)) copyOwn(n, t, e);
|
|
340
339
|
if (e.del !== null) {
|
|
341
340
|
for (const t of e.del) delete n[t];
|
|
342
341
|
e.del = null;
|
|
@@ -388,14 +387,14 @@ function ensurePB(e) {
|
|
|
388
387
|
if (n !== null) {
|
|
389
388
|
for (const e of Reflect.ownKeys(n)) {
|
|
390
389
|
const r = n[e];
|
|
391
|
-
if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.
|
|
390
|
+
if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.Pe);
|
|
392
391
|
}
|
|
393
392
|
}
|
|
394
393
|
const r = e.h;
|
|
395
394
|
if (r !== null) {
|
|
396
395
|
for (const e of Reflect.ownKeys(r)) {
|
|
397
396
|
const n = r[e];
|
|
398
|
-
if (hasActiveOverride(n) && !unwrapOverride(n.o?.
|
|
397
|
+
if (hasActiveOverride(n) && !unwrapOverride(n.o?.Pe)) delete t[e];
|
|
399
398
|
}
|
|
400
399
|
}
|
|
401
400
|
}
|
|
@@ -439,7 +438,18 @@ function ensurePB(e) {
|
|
|
439
438
|
if (!n) {
|
|
440
439
|
queueFold(e);
|
|
441
440
|
// records the pre-batch old before we swap
|
|
442
|
-
|
|
441
|
+
// Diff base = the view the nodes were last told (#3296). A draft's
|
|
442
|
+
// setter-exit notifications already moved them to its pending backing,
|
|
443
|
+
// so a later adoption diffs against THAT — against committed, a key the
|
|
444
|
+
// draft changed and the adoption restores would never re-notify. An
|
|
445
|
+
// adoption with no draft leaves nodes where they were: keep an existing
|
|
446
|
+
// base, else the pre-batch committed (foldOlds' entry itself stays the
|
|
447
|
+
// committed identity for the path-copy CAS). Eager callers read t.pb
|
|
448
|
+
// directly; this hand-off exists because pb is gone by drain time.
|
|
449
|
+
if (e.pb !== null) {
|
|
450
|
+
if (e.ovl) materializePB(e);
|
|
451
|
+
e.ab = e.pb;
|
|
452
|
+
} else e.ab ??= foldOlds.get(e);
|
|
443
453
|
// #3074/#3075: a projection recompute deriving from uncommitted inputs
|
|
444
454
|
// swaps the backing SPECULATIVELY — committed-visibility readers must
|
|
445
455
|
// keep the pre-hold view until the hold resolves (a source held by a
|
|
@@ -467,7 +477,7 @@ function ensurePB(e) {
|
|
|
467
477
|
e.del = null;
|
|
468
478
|
e.sc = false;
|
|
469
479
|
e.a = false;
|
|
470
|
-
|
|
480
|
+
e.wk = null;
|
|
471
481
|
// adoption supersedes staged trap writes
|
|
472
482
|
e.v = t;
|
|
473
483
|
e.ch = t[$TARGET] !== undefined;
|
|
@@ -517,20 +527,56 @@ function queueFold(e) {
|
|
|
517
527
|
* transition is ambient). Entries die with their draft — tentative backings
|
|
518
528
|
* are consumed at setter exit. */ const tentativePBs = new WeakSet;
|
|
519
529
|
|
|
530
|
+
/** A draft read composes the live optimistic view until the draft has opened
|
|
531
|
+
* its OWN view-seeded backing (ensurePB seeds that clone from the view and
|
|
532
|
+
* registers it in tentativePBs; from then on reads must see the draft's
|
|
533
|
+
* writes, not the overrides they superseded). A pending backing that exists
|
|
534
|
+
* for any other reason is not that clone — a truth landing staged into a
|
|
535
|
+
* retaining transaction (#3164 fold) is authoritative truth WITHOUT the
|
|
536
|
+
* live overrides. ensurePB parks such a backing on the draft's first WRITE
|
|
537
|
+
* and reseeds from the view, but the reads that precede that write went to
|
|
538
|
+
* the staged truth: `votes++` read base, wrote base+1, and the override it
|
|
539
|
+
* emitted landed on the value already displayed — a second in-flight
|
|
540
|
+
* increment made after a sibling's landing was invisible (#2951's compose
|
|
541
|
+
* half, one landing later). */ function draftSeesOverrides(e) {
|
|
542
|
+
return e.pb === null || !tentativePBs.has(e.pb);
|
|
543
|
+
}
|
|
544
|
+
|
|
520
545
|
/** Committed-time privatization for parent-chain slot updates (path copying). */ function privatizeCommitted(e) {
|
|
521
546
|
if (ownedRaw.has(e.v)) return;
|
|
522
|
-
const t =
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
547
|
+
const t = e.v;
|
|
548
|
+
const n = cloneRaw(t, e);
|
|
549
|
+
ownedRaw.add(n);
|
|
550
|
+
// Register in the target's OWN registration map (#3284): family targets
|
|
551
|
+
// (derived stores, projections, optimistic) resolve children through
|
|
552
|
+
// fam.map — a clone parked only in the global lookup makes the next parent
|
|
553
|
+
// read miss, wrap a fresh target, and orphan every node (subscribers) on
|
|
554
|
+
// this one.
|
|
555
|
+
(e.fam?.map ?? storeNextLookup).set(n, e);
|
|
556
|
+
e.v = n;
|
|
526
557
|
e.ch = false;
|
|
527
558
|
if (e.u) {
|
|
528
559
|
privatizeCommitted(e.u);
|
|
529
560
|
devAssertNeverUserMutation(e.u.v);
|
|
530
|
-
e.u.v[e
|
|
561
|
+
e.u.v[parentSlotKey(e, t)] = e.v;
|
|
531
562
|
}
|
|
532
563
|
}
|
|
533
564
|
|
|
565
|
+
/** Resolve the slot this child currently occupies in its parent's committed
|
|
566
|
+
* backing (#3282). `pk` is stamped at wrap time and arrays MOVE: a reverse/
|
|
567
|
+
* unshift/splice relocates the raw, and a fold that re-points the wrap-time
|
|
568
|
+
* slot writes the clone over whichever row lives there now. Objects never
|
|
569
|
+
* move keys, so the stamp is authoritative; for arrays, verify and re-locate
|
|
570
|
+
* by identity when stale (fold-time only — never on a read path). */ function parentSlotKey(e, t) {
|
|
571
|
+
const n = e.pk;
|
|
572
|
+
const r = e.u.v;
|
|
573
|
+
if (r[n] === t || !Array.isArray(r)) return n;
|
|
574
|
+
const i = r.indexOf(t);
|
|
575
|
+
if (i === -1) return n;
|
|
576
|
+
e.pk = i;
|
|
577
|
+
return i;
|
|
578
|
+
}
|
|
579
|
+
|
|
534
580
|
function drainFolds() {
|
|
535
581
|
if (foldOlds.size === 0) return;
|
|
536
582
|
const e = [ ...foldOlds ];
|
|
@@ -540,11 +586,6 @@ function drainFolds() {
|
|
|
540
586
|
// is committing the batch the pull ran ahead of. Transition holds stay —
|
|
541
587
|
// they clear when their transition is done (heldMaskView).
|
|
542
588
|
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
589
|
if (t.pb !== null) {
|
|
549
590
|
// #3089: a fold written under a still-running transition defers to
|
|
550
591
|
// that transition's settle (the write-time stamp covers unobserved
|
|
@@ -568,14 +609,14 @@ function drainFolds() {
|
|
|
568
609
|
// Only written keys can hold (their nodes took the setSignal); the
|
|
569
610
|
// wk bound keeps this O(written) — see notifyWrites. Same fallback
|
|
570
611
|
// rules as the notify (WK_ALL / accessors / non-plain prototypes).
|
|
571
|
-
const e = t.
|
|
612
|
+
const e = t.wk;
|
|
572
613
|
const n = e === null || e === WK_ALL || t.a === true ||
|
|
573
614
|
// Overlay pbs chain to the COMMITTED object (#3044) — plainness is
|
|
574
615
|
// the committed container's prototype, not the overlay's.
|
|
575
616
|
!plainProto(t.ovl ? t.v : i) ? Reflect.ownKeys(o) : e;
|
|
576
617
|
for (const e of n) {
|
|
577
618
|
const t = o[e];
|
|
578
|
-
if (t !== undefined && t.
|
|
619
|
+
if (t !== undefined && t.Re !== NOT_PENDING) {
|
|
579
620
|
r = true;
|
|
580
621
|
break;
|
|
581
622
|
}
|
|
@@ -596,10 +637,7 @@ function drainFolds() {
|
|
|
596
637
|
// — the never-mutate-user-data contract holds.
|
|
597
638
|
privatizeCommitted(t);
|
|
598
639
|
const e = t.v;
|
|
599
|
-
for (const t of Reflect.ownKeys(i))
|
|
600
|
-
const n = Object.getOwnPropertyDescriptor(i, t);
|
|
601
|
-
if (n.get || n.set || !n.enumerable || !n.writable || !n.configurable) Object.defineProperty(e, t, n); else e[t] = n.value;
|
|
602
|
-
}
|
|
640
|
+
for (const t of Reflect.ownKeys(i)) copyOwn(e, i, t);
|
|
603
641
|
if (t.del !== null) {
|
|
604
642
|
for (const n of t.del) delete e[n];
|
|
605
643
|
t.del = null;
|
|
@@ -607,67 +645,78 @@ function drainFolds() {
|
|
|
607
645
|
(t.fam?.map ?? storeNextLookup).delete(i);
|
|
608
646
|
t.pb = null;
|
|
609
647
|
t.ovl = false;
|
|
610
|
-
|
|
648
|
+
t.wk = null;
|
|
649
|
+
// written-keys window closes with the fold commit
|
|
650
|
+
} else if (t.v !== n) {
|
|
651
|
+
// Privatized mid-batch (#3271): an earlier fold in this drain
|
|
652
|
+
// path-copied THROUGH this target — privatizeCommitted cloned the
|
|
653
|
+
// committed backing, re-pointed the parent slot at the clone, and
|
|
654
|
+
// stitched the descendant's fold into it. The draft's pb predates
|
|
655
|
+
// that: swapping it in would clobber the descendant's fold, and the
|
|
656
|
+
// parent CAS below (still comparing against `old`) would fail and
|
|
657
|
+
// orphan this fold entirely — a projection draft writing descendant-
|
|
658
|
+
// then-ancestor silently lost the ancestor write. Merge the batch's
|
|
659
|
+
// writes onto the current container instead (the parent slot already
|
|
660
|
+
// points at it). privatize first: an adopt-then-write batch can land
|
|
661
|
+
// here with an unowned adoptee as t.v.
|
|
662
|
+
privatizeCommitted(t);
|
|
663
|
+
const e = t.v;
|
|
664
|
+
const r = t.wk;
|
|
665
|
+
if (r !== null && r !== WK_ALL) {
|
|
666
|
+
// The trap records every write/delete key — apply exactly those.
|
|
667
|
+
for (const t of r) {
|
|
668
|
+
if (hasOwn.call(i, t)) copyOwn(e, i, t); else delete e[t];
|
|
669
|
+
}
|
|
670
|
+
} else {
|
|
671
|
+
// Array length write poisoned the bound (WK_ALL) — value-diff
|
|
672
|
+
// against the pre-batch old. Slots the draft never touched hold
|
|
673
|
+
// the same raw reference in both, so descendant folds stay put.
|
|
674
|
+
// Not copyOwn: the plain-value write is GATED on "the draft changed
|
|
675
|
+
// this slot" — an untouched slot in pb holds the pre-batch reference,
|
|
676
|
+
// and writing it back would clobber a descendant fold stitched into v.
|
|
677
|
+
for (const t of Reflect.ownKeys(i)) {
|
|
678
|
+
const r = Object.getOwnPropertyDescriptor(i, t);
|
|
679
|
+
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;
|
|
680
|
+
}
|
|
681
|
+
for (const t of Reflect.ownKeys(n)) {
|
|
682
|
+
if (!hasOwn.call(i, t)) delete e[t];
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
(t.fam?.map ?? storeNextLookup).delete(i);
|
|
686
|
+
t.pb = null;
|
|
687
|
+
t.wk = null;
|
|
611
688
|
// written-keys window closes with the fold commit
|
|
612
689
|
} 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
690
|
t.v = i;
|
|
627
691
|
t.ch = false;
|
|
628
692
|
// pb is always a plain clone
|
|
629
693
|
t.pb = null;
|
|
630
|
-
|
|
694
|
+
t.wk = null;
|
|
631
695
|
// written-keys window closes with the fold commit
|
|
632
696
|
}
|
|
633
697
|
}
|
|
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);
|
|
698
|
+
const e = t.ab;
|
|
699
|
+
t.ab = null;
|
|
700
|
+
if (t.v !== n) {
|
|
701
|
+
// Path copying (CAS: see the eager-fold twin above). Slot resolved by
|
|
702
|
+
// identity (#3282): an array move relocated the raw, so the wrap-time
|
|
703
|
+
// pk may point at a sibling — a raw-slot CAS there both failed to
|
|
704
|
+
// re-point AND (via privatizeCommitted's unguarded write) clobbered
|
|
705
|
+
// the sibling.
|
|
706
|
+
if (t.u) {
|
|
707
|
+
const e = parentSlotKey(t, n);
|
|
708
|
+
if (t.u.v[e] === n) {
|
|
709
|
+
privatizeCommitted(t.u);
|
|
710
|
+
devAssertNeverUserMutation(t.u.v);
|
|
711
|
+
t.u.v[e] = t.v;
|
|
712
|
+
}
|
|
659
713
|
}
|
|
660
714
|
}
|
|
661
|
-
//
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
667
|
-
if (t.adopted) {
|
|
668
|
-
t.adopted = false;
|
|
669
|
-
notifyFold(t, n, t.v);
|
|
670
|
-
}
|
|
715
|
+
// Adoption notify against the base the nodes were last told (#3296). A
|
|
716
|
+
// no-op adoption (A -> B -> A before flush, no draft) has base === v and
|
|
717
|
+
// nothing to say; a draft superseded by an adoption back to the SAME raw
|
|
718
|
+
// still has (base = pending backing) !== v and must notify.
|
|
719
|
+
if (e !== null && e !== t.v) notifyFold(t, e, t.v);
|
|
671
720
|
}
|
|
672
721
|
}
|
|
673
722
|
|
|
@@ -717,7 +766,7 @@ function drainFolds() {
|
|
|
717
766
|
// implicit index deletes), accessors on the record (t.a — a getter node's
|
|
718
767
|
// value can change when ANY key is written), or a non-plain prototype
|
|
719
768
|
// (class instances: prototype getters derive from arbitrary fields).
|
|
720
|
-
const i = e.
|
|
769
|
+
const i = e.wk;
|
|
721
770
|
// Overlay pbs chain to the COMMITTED object (#3044): a prototype-overlay
|
|
722
771
|
// draft is plain data on its own layer, but its getPrototypeOf is the
|
|
723
772
|
// committed container — judge plainness by the COMMITTED prototype or the
|
|
@@ -791,12 +840,6 @@ function drainFolds() {
|
|
|
791
840
|
}
|
|
792
841
|
if (r) setSignal(e.k, e => e + 1);
|
|
793
842
|
}
|
|
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
843
|
// Projection backing folds split by channel (two pinned contracts):
|
|
801
844
|
// - sync-derive drafts (recompute body): NEVER eager — a downstream async
|
|
802
845
|
// hold can form LATER in the same flush and the leaf must stay at stale
|
|
@@ -821,10 +864,14 @@ function drainFolds() {
|
|
|
821
864
|
e.pb = null;
|
|
822
865
|
e.v = t;
|
|
823
866
|
e.ch = false;
|
|
824
|
-
if (e.u
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
e.u.v[
|
|
867
|
+
if (e.u) {
|
|
868
|
+
// Identity-resolved slot (#3282) — see drainFolds' path-copy twin.
|
|
869
|
+
const r = parentSlotKey(e, n);
|
|
870
|
+
if (e.u.v[r] === n) {
|
|
871
|
+
privatizeCommitted(e.u);
|
|
872
|
+
devAssertNeverUserMutation(e.u.v);
|
|
873
|
+
e.u.v[r] = t;
|
|
874
|
+
}
|
|
828
875
|
}
|
|
829
876
|
}
|
|
830
877
|
}
|
|
@@ -1008,7 +1055,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
|
1008
1055
|
* / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */ function inOwnerContext() {
|
|
1009
1056
|
const e = getOwner();
|
|
1010
1057
|
if (e === null) return false;
|
|
1011
|
-
const t = e.
|
|
1058
|
+
const t = e.Dt ? e.Pt : e;
|
|
1012
1059
|
return t != null && !(t.T & CONFIG_CHILDREN_FORBIDDEN);
|
|
1013
1060
|
}
|
|
1014
1061
|
|
|
@@ -1019,7 +1066,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
|
1019
1066
|
* committed to them regardless of staged writes. */ function inForbiddenScope() {
|
|
1020
1067
|
const e = getOwner();
|
|
1021
1068
|
if (e === null) return false;
|
|
1022
|
-
const t = e.
|
|
1069
|
+
const t = e.Dt ? e.Pt : e;
|
|
1023
1070
|
return t != null && !!(t.T & CONFIG_CHILDREN_FORBIDDEN);
|
|
1024
1071
|
}
|
|
1025
1072
|
|
|
@@ -1030,7 +1077,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
|
1030
1077
|
if (t === null) return false;
|
|
1031
1078
|
for (const e of Reflect.ownKeys(t)) {
|
|
1032
1079
|
const n = t[e];
|
|
1033
|
-
if (n.
|
|
1080
|
+
if (n.Re !== NOT_PENDING && n.Ae != null && n.Ae.sn !== true) return true;
|
|
1034
1081
|
}
|
|
1035
1082
|
return false;
|
|
1036
1083
|
}
|
|
@@ -1044,28 +1091,39 @@ function readSource(e) {
|
|
|
1044
1091
|
const t = heldMaskView(e);
|
|
1045
1092
|
if (t !== null) return t;
|
|
1046
1093
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1094
|
+
return pendingBackingVisible(e, false) ? e.pb : e.v;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/** The single pb-vs-committed visibility decision (#3147), shared by per-key
|
|
1098
|
+
* backing reads (readSource) and deep()/snapshot composition (snapshotWalk)
|
|
1099
|
+
* so the two reader families can never disagree about a HELD landing.
|
|
1100
|
+
*
|
|
1101
|
+
* Signal-parity visibility (core read(): owner-context reads serve
|
|
1102
|
+
* _pendingValue, context-free reads serve committed — effects recompute
|
|
1103
|
+
* BEFORE commitPendingNodes in the flush, so the pending view must be
|
|
1104
|
+
* servable). Drafts (setter window OR projection write-override) and
|
|
1105
|
+
* owner-context reads see the pending backing; context-free reads see
|
|
1106
|
+
* committed. Node reads apply the same rule, so all homes agree.
|
|
1107
|
+
*
|
|
1108
|
+
* `speculative` is deep()/snapshot's posture: an untrack/deep PEEK that sees
|
|
1109
|
+
* ordinary pending staging regardless of owner context (the documented
|
|
1110
|
+
* divergence from context-free per-key reads) — but never through a hold:
|
|
1111
|
+
* held truth stays masked exactly as it is for per-key readers. */ function pendingBackingVisible(e, t) {
|
|
1112
|
+
return e.pb !== null && (inDraft(e) || getWriteOverride() ||
|
|
1113
|
+
// Owner-context (and speculative-peek) readers see the pending
|
|
1114
|
+
// backing — EXCEPT held truth on an optimistic family (#3164 fold):
|
|
1115
|
+
// a live pb on an opt family outside the draft/write-override windows
|
|
1116
|
+
// is a staged landing (tentative drafts never outlive their setter),
|
|
1117
|
+
// and only the authoritative postures and latest() see it (the
|
|
1118
|
+
// backing-level twin of core read()'s A17-for-held-truth arm;
|
|
1119
|
+
// ordinary readers keep committed until the transaction's reveal).
|
|
1120
|
+
(t || inOwnerContext()) && !heldTruthMasked(e) ||
|
|
1062
1121
|
// A projection's pending backing is authoritative-elect: serve it to
|
|
1063
1122
|
// context-free readers too UNLESS a transition is holding the node
|
|
1064
1123
|
// commits (downstream async hold — stale committed is the contract)
|
|
1065
1124
|
// or the reader is a CHILDREN_FORBIDDEN scope, which never observes
|
|
1066
1125
|
// its own unsettled write (#3082, signal parity per #3006).
|
|
1067
|
-
e.fam !== null && !heldTruthMasked(e) && !foldHeld(e) && !inForbiddenScope())
|
|
1068
|
-
return e.v;
|
|
1126
|
+
e.fam !== null && !heldTruthMasked(e) && !foldHeld(e) && !inForbiddenScope());
|
|
1069
1127
|
}
|
|
1070
1128
|
|
|
1071
1129
|
/** #3164 fold: HELD truth on an optimistic family — a pending backing
|
|
@@ -1110,7 +1168,7 @@ function isOwnAccessor(e, t) {
|
|
|
1110
1168
|
|
|
1111
1169
|
/** Active optimistic override on an armed node (armed slot idles at
|
|
1112
1170
|
* NOT_PENDING; undefined = unarmed plain node). */ function hasActiveOverride(e) {
|
|
1113
|
-
return e.o?.
|
|
1171
|
+
return e.o?.Pe !== undefined && e.o?.Pe !== NOT_PENDING;
|
|
1114
1172
|
}
|
|
1115
1173
|
|
|
1116
1174
|
/** The reading computation is until()'s authoritative-view predicate — same
|
|
@@ -1145,14 +1203,14 @@ function isOwnAccessor(e, t) {
|
|
|
1145
1203
|
// Authoritative-view reads (until()'s predicate) skip the override arm
|
|
1146
1204
|
// only: staged pending values are authoritative, overrides are the
|
|
1147
1205
|
// caller's optimism.
|
|
1148
|
-
const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.
|
|
1206
|
+
const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.Pe) : e.Re !== NOT_PENDING && (latestReadActive ||
|
|
1149
1207
|
// Owner-context pending visibility — except HELD truth (#3164,
|
|
1150
1208
|
// see CONFIG_HELD_TRUTH: fold-staged or entangle-stolen
|
|
1151
1209
|
// confirming truth), which only authoritative/latest readers
|
|
1152
1210
|
// see (core read()'s A17-for-held-truth twin; ordinary readers
|
|
1153
1211
|
// keep committed until the transaction's reveal — latest() is
|
|
1154
1212
|
// exempted by the leading arm above).
|
|
1155
|
-
(inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.
|
|
1213
|
+
(inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Re : t;
|
|
1156
1214
|
return n === FORCE ? t : n;
|
|
1157
1215
|
}
|
|
1158
1216
|
|
|
@@ -1160,15 +1218,15 @@ function isOwnAccessor(e, t) {
|
|
|
1160
1218
|
* holds, lanes ride the node); backing otherwise. Chained backings (§7b: the
|
|
1161
1219
|
* backing IS another store's proxy) serve the read-through value — the outer
|
|
1162
1220
|
* node is linked only for adoption-swap notification, its value never
|
|
1163
|
-
* shadows the live chain. */ function serveDataKey(e, t, n, r, i) {
|
|
1164
|
-
const
|
|
1165
|
-
let
|
|
1221
|
+
* shadows the live chain. */ function serveDataKey(e, t, n, r, i, o = -1) {
|
|
1222
|
+
const f = e.ch && r === e.v;
|
|
1223
|
+
let l = n;
|
|
1166
1224
|
// §6: on optimistic arrays LENGTH IS A VIEW, not a node value — one home
|
|
1167
1225
|
// (backing ± presence overrides) for both length and indices makes torn
|
|
1168
1226
|
// iteration impossible (a length node's value rides different visibility
|
|
1169
1227
|
// rails than index overrides mid-settle). The node still carries
|
|
1170
1228
|
// subscriptions; its value is never served here.
|
|
1171
|
-
if (t === "length" && e.fam?.opt === true && !
|
|
1229
|
+
if (t === "length" && e.fam?.opt === true && !f && Array.isArray(r)) {
|
|
1172
1230
|
if (!inDraft(e)) {
|
|
1173
1231
|
const r = e.n?.length;
|
|
1174
1232
|
if (r !== undefined) {
|
|
@@ -1188,9 +1246,9 @@ function isOwnAccessor(e, t) {
|
|
|
1188
1246
|
// #2951). Once ensurePB runs, the seeded clone carries the view.
|
|
1189
1247
|
// AUTHORITATIVE drafts (projection derive) never overlay — ensurePB's
|
|
1190
1248
|
// seeding rule, applied to the read side (#3108).
|
|
1191
|
-
if (e.fam?.opt && e
|
|
1249
|
+
if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
|
|
1192
1250
|
const n = e.n?.[t];
|
|
1193
|
-
if (n !== undefined && hasActiveOverride(n))
|
|
1251
|
+
if (n !== undefined && hasActiveOverride(n)) l = unwrapOverride(n.o?.Pe);
|
|
1194
1252
|
}
|
|
1195
1253
|
} else {
|
|
1196
1254
|
if (i !== undefined) {
|
|
@@ -1202,28 +1260,30 @@ function isOwnAccessor(e, t) {
|
|
|
1202
1260
|
// parity): READ_SLOW = a global read window or non-plain node.
|
|
1203
1261
|
let e = readNodeFast(i);
|
|
1204
1262
|
if (e === READ_SLOW) e = read(i);
|
|
1205
|
-
if (!
|
|
1206
|
-
} else if (!
|
|
1207
|
-
|
|
1263
|
+
if (!f || hasActiveOverride(i)) l = e === FORCE ? n : e;
|
|
1264
|
+
} else if (!f || hasActiveOverride(i)) {
|
|
1265
|
+
l = nodeValue(i, n);
|
|
1208
1266
|
}
|
|
1209
1267
|
} else if (getObserver() !== null) {
|
|
1210
|
-
read
|
|
1268
|
+
// First tracked read: create + link, and let the wrap-cache branch
|
|
1269
|
+
// below populate px/pxv so read #2 skips wrapNext (slice 2).
|
|
1270
|
+
read(i = getNode(e, t, n, o));
|
|
1211
1271
|
}
|
|
1212
1272
|
}
|
|
1213
1273
|
// Shallow stores serve data raw; store-proxy slots get boundary wrappers.
|
|
1214
|
-
if (e.s) return serveShallow(e, t,
|
|
1274
|
+
if (e.s) return serveShallow(e, t, l);
|
|
1215
1275
|
if (i !== undefined) {
|
|
1216
1276
|
// Wrap cache (see getNode): only wrappables are ever cached, so a hit
|
|
1217
1277
|
// skips isWrappable too — pointer-compare replaces both checks.
|
|
1218
|
-
if (i.pxv ===
|
|
1219
|
-
if (!isWrappable(
|
|
1220
|
-
const n = wrapNext(
|
|
1278
|
+
if (i.pxv === l && l !== undefined) return draftServe(e, i.px);
|
|
1279
|
+
if (!isWrappable(l)) return l;
|
|
1280
|
+
const n = wrapNext(l, e, t);
|
|
1221
1281
|
i.px = n;
|
|
1222
|
-
i.pxv =
|
|
1282
|
+
i.pxv = l;
|
|
1223
1283
|
return draftServe(e, n);
|
|
1224
1284
|
}
|
|
1225
|
-
if (!isWrappable(
|
|
1226
|
-
return draftServe(e, wrapNext(
|
|
1285
|
+
if (!isWrappable(l)) return l;
|
|
1286
|
+
return draftServe(e, wrapNext(l, e, t));
|
|
1227
1287
|
}
|
|
1228
1288
|
|
|
1229
1289
|
/** §6c store-wide status gate for reads that DON'T flow through a node:
|
|
@@ -1309,8 +1369,11 @@ const traps = {
|
|
|
1309
1369
|
// tracked read of a present data key — the dbmon/uibench effect re-read
|
|
1310
1370
|
// shape. Skips serveDataKey's frame, the FORCE compare (only accessor
|
|
1311
1371
|
// keys ever hold the sentinel), and isWrappable for primitives.
|
|
1312
|
-
|
|
1313
|
-
|
|
1372
|
+
// ONE node-map lookup serves this block and the accessor probe below
|
|
1373
|
+
// (nothing between them creates nodes).
|
|
1374
|
+
const i = e.n?.[t];
|
|
1375
|
+
if (e.ch === false && writeScopes === null) {
|
|
1376
|
+
const n = i;
|
|
1314
1377
|
if (n !== undefined && n.acc !== true && getObserver() !== null) {
|
|
1315
1378
|
let r = readNodeFast(n);
|
|
1316
1379
|
if (r === READ_SLOW) r = read(n);
|
|
@@ -1338,14 +1401,22 @@ const traps = {
|
|
|
1338
1401
|
// absent-key/accessor subscriptions for every store read during any
|
|
1339
1402
|
// derive, leaving nested projections permanently dependency-less when
|
|
1340
1403
|
// their sources hadn't materialized yet (#3037).
|
|
1341
|
-
|
|
1404
|
+
// First-read dedupe (create-floor slice 2): remember the probe verdict —
|
|
1405
|
+
// node creation downstream reuses it instead of re-scanning the
|
|
1406
|
+
// descriptor, but only when the probed object IS the one getNode would
|
|
1407
|
+
// scan (pb ?? v).
|
|
1408
|
+
let o = -1;
|
|
1342
1409
|
{
|
|
1343
|
-
|
|
1344
|
-
if (
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1410
|
+
let f;
|
|
1411
|
+
if (i !== undefined) f = i.acc === true; else if (!inDraft(e) && getObserver() !== null) {
|
|
1412
|
+
f = isOwnAccessor(r, t);
|
|
1413
|
+
if (r === (e.pb ?? e.v)) o = f ? 1 : 0;
|
|
1414
|
+
} else f = false;
|
|
1415
|
+
if (f) {
|
|
1416
|
+
if (!inDraft(e) && getObserver() !== null) read(i ?? getNode(e, t, undefined, o));
|
|
1417
|
+
const f = Reflect.get(r, t, n);
|
|
1418
|
+
if (e.s) return serveShallow(e, t, f);
|
|
1419
|
+
return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
|
|
1349
1420
|
}
|
|
1350
1421
|
}
|
|
1351
1422
|
// Plain-data fast path: no descriptor allocation per read.
|
|
@@ -1355,37 +1426,37 @@ const traps = {
|
|
|
1355
1426
|
// backings chain to the committed backing, so "own in the view" means
|
|
1356
1427
|
// own on either layer (ownInView) — a genuine prototype method is one
|
|
1357
1428
|
// that is own on NEITHER.
|
|
1358
|
-
const
|
|
1359
|
-
if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(
|
|
1360
|
-
let
|
|
1361
|
-
if (
|
|
1429
|
+
const f = e.ovl && r === e.pb;
|
|
1430
|
+
if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(f && hasOwn.call(e.v, t))) return undefined;
|
|
1431
|
+
let l = r[t];
|
|
1432
|
+
if (l === undefined ? !hasOwn.call(r, t) && !(f && hasOwn.call(e.v, t)) : false) {
|
|
1362
1433
|
// Inherited: prototype getters/methods run with the proxy receiver.
|
|
1363
|
-
|
|
1364
|
-
if (typeof
|
|
1434
|
+
l = Reflect.get(r, t, n);
|
|
1435
|
+
if (typeof l === "function") return l;
|
|
1365
1436
|
// proto methods untracked
|
|
1366
1437
|
// Reading a currently-absent own key subscribes to it (R12) — for any
|
|
1367
1438
|
// target OUTSIDE its own draft scope, even mid-setter (#3037, above).
|
|
1368
|
-
if (
|
|
1369
|
-
if (getObserver() !== null) read(getNode(e, t, undefined));
|
|
1439
|
+
if (l === undefined && !inDraft(e)) {
|
|
1440
|
+
if (getObserver() !== null) read(getNode(e, t, undefined, o));
|
|
1370
1441
|
const n = e.n?.[t];
|
|
1371
1442
|
if (n) {
|
|
1372
1443
|
const r = nodeValue(n, undefined);
|
|
1373
1444
|
if (e.s) return serveShallow(e, t, r);
|
|
1374
1445
|
return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
|
|
1375
1446
|
}
|
|
1376
|
-
} else if (
|
|
1447
|
+
} else if (l === undefined && inDraft(e) && e.fam?.opt && draftSeesOverrides(e) &&
|
|
1377
1448
|
// AUTHORITATIVE drafts (landing folds) never seed from overrides —
|
|
1378
1449
|
// the caller's optimism is not truth (has-trap twin below).
|
|
1379
1450
|
!authoritativeServe()) {
|
|
1380
1451
|
const n = e.n?.[t];
|
|
1381
|
-
if (n !== undefined && hasActiveOverride(n))
|
|
1452
|
+
if (n !== undefined && hasActiveOverride(n)) l = unwrapOverride(n.o?.Pe);
|
|
1382
1453
|
}
|
|
1383
|
-
if (e.s) return serveShallow(e, t,
|
|
1384
|
-
return isWrappable(
|
|
1454
|
+
if (e.s) return serveShallow(e, t, l);
|
|
1455
|
+
return isWrappable(l) ? draftServe(e, wrapNext(l, e, t)) : l;
|
|
1385
1456
|
}
|
|
1386
|
-
if (typeof
|
|
1457
|
+
if (typeof l === "function" && !hasOwn.call(r, t) && !(f && hasOwn.call(e.v, t))) return l;
|
|
1387
1458
|
// proto method
|
|
1388
|
-
return serveDataKey(e, t,
|
|
1459
|
+
return serveDataKey(e, t, l, r, i, o);
|
|
1389
1460
|
},
|
|
1390
1461
|
has(e, t) {
|
|
1391
1462
|
if (t === $TARGET || t === $PROXY || t === $TRACK) return true;
|
|
@@ -1404,11 +1475,11 @@ const traps = {
|
|
|
1404
1475
|
if (hasActiveOverride(n)) r = !!i;
|
|
1405
1476
|
} else if (!authoritativeServe()) {
|
|
1406
1477
|
const n = e.h?.[t];
|
|
1407
|
-
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.
|
|
1478
|
+
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
|
|
1408
1479
|
}
|
|
1409
|
-
} else if (e.fam?.opt && e
|
|
1480
|
+
} else if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
|
|
1410
1481
|
const n = e.h?.[t];
|
|
1411
|
-
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.
|
|
1482
|
+
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
|
|
1412
1483
|
}
|
|
1413
1484
|
return r;
|
|
1414
1485
|
},
|
|
@@ -1433,13 +1504,13 @@ const traps = {
|
|
|
1433
1504
|
// Draft reads before the first write overlay too (pb, once created, is
|
|
1434
1505
|
// seeded with the view). Authoritative-view reads (until()'s predicate,
|
|
1435
1506
|
// truth-author drafts) skip the overlay.
|
|
1436
|
-
if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || e
|
|
1507
|
+
if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || draftSeesOverrides(e))) {
|
|
1437
1508
|
let t = null;
|
|
1438
1509
|
for (const r of Reflect.ownKeys(e.h)) {
|
|
1439
1510
|
const i = e.h[r];
|
|
1440
1511
|
if (!hasActiveOverride(i)) continue;
|
|
1441
1512
|
t ??= new Set(n);
|
|
1442
|
-
if (unwrapOverride(i.o?.
|
|
1513
|
+
if (unwrapOverride(i.o?.Pe)) t.add(r); else t.delete(r);
|
|
1443
1514
|
}
|
|
1444
1515
|
if (t !== null) return [ ...t ];
|
|
1445
1516
|
}
|
|
@@ -1457,7 +1528,7 @@ const traps = {
|
|
|
1457
1528
|
if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
|
|
1458
1529
|
const n = e.h?.[t];
|
|
1459
1530
|
if (n !== undefined && hasActiveOverride(n)) {
|
|
1460
|
-
if (!unwrapOverride(n.o?.
|
|
1531
|
+
if (!unwrapOverride(n.o?.Pe)) return undefined;
|
|
1461
1532
|
// opt delete
|
|
1462
1533
|
if (r === undefined) {
|
|
1463
1534
|
const n = e.n?.[t];
|
|
@@ -1498,14 +1569,13 @@ const traps = {
|
|
|
1498
1569
|
// Array length writes implicitly delete indices — the written-keys bound
|
|
1499
1570
|
// can't see them, so poison to the full scan for this batch. Index
|
|
1500
1571
|
// writes implicitly GROW length, so arrays always record it alongside.
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
e.add("length");
|
|
1572
|
+
if (Array.isArray(f)) {
|
|
1573
|
+
if (t === "length") e.wk = WK_ALL; else if (e.wk !== WK_ALL) {
|
|
1574
|
+
const n = e.wk ??= new Set;
|
|
1575
|
+
n.add(t);
|
|
1576
|
+
n.add("length");
|
|
1507
1577
|
}
|
|
1508
|
-
} else if (
|
|
1578
|
+
} else if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
|
|
1509
1579
|
// Own data keys literally named "prototype"/"constructor" land as data —
|
|
1510
1580
|
// defineProperty sidesteps a proto-chain setter named the same.
|
|
1511
1581
|
if (UNSAFE_KEYS.has(t)) {
|
|
@@ -1543,14 +1613,7 @@ const traps = {
|
|
|
1543
1613
|
const i = !r && getWriteOverride();
|
|
1544
1614
|
if (!r && !i) return true;
|
|
1545
1615
|
if (t === "__proto__") return true;
|
|
1546
|
-
if (n.get || n.set)
|
|
1547
|
-
e.a = true;
|
|
1548
|
-
// Accessor demotion (re-audit blocker 3): a record that acquires an
|
|
1549
|
-
// accessor after patch registration stops being patchable — pull its
|
|
1550
|
-
// patches and re-drive them as tracked effect fallbacks. Hooks are
|
|
1551
|
-
// installed whenever pc.p exists (registration installs them).
|
|
1552
|
-
if (e.pc !== null && e.pc.p !== null) patchHooks.demoteToEffects(e);
|
|
1553
|
-
}
|
|
1616
|
+
if (n.get || n.set) e.a = true;
|
|
1554
1617
|
// Unwrap before ensurePB (see the set trap: self-reference materializes).
|
|
1555
1618
|
if ("value" in n) n = {
|
|
1556
1619
|
...n,
|
|
@@ -1558,8 +1621,7 @@ const traps = {
|
|
|
1558
1621
|
};
|
|
1559
1622
|
const o = ensurePB(e);
|
|
1560
1623
|
pendingNotify.add(e);
|
|
1561
|
-
|
|
1562
|
-
if (f.wk !== WK_ALL) (f.wk ??= new Set).add(t);
|
|
1624
|
+
if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
|
|
1563
1625
|
Object.defineProperty(o, t, n);
|
|
1564
1626
|
if (e.del !== null) e.del.delete(t);
|
|
1565
1627
|
if (i) notifyWrites(e);
|
|
@@ -1571,8 +1633,7 @@ const traps = {
|
|
|
1571
1633
|
if (!n && !r) return true;
|
|
1572
1634
|
const i = ensurePB(e);
|
|
1573
1635
|
pendingNotify.add(e);
|
|
1574
|
-
|
|
1575
|
-
if (o.wk !== WK_ALL) (o.wk ??= new Set).add(t);
|
|
1636
|
+
if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
|
|
1576
1637
|
delete i[t];
|
|
1577
1638
|
// A prototype overlay cannot shadow a delete of a committed key —
|
|
1578
1639
|
// record it aside (#3044); reads/has/ownKeys/commit consult the set.
|
|
@@ -1636,6 +1697,10 @@ function createStoreNext(e, t = false) {
|
|
|
1636
1697
|
return [ n, setter ];
|
|
1637
1698
|
}
|
|
1638
1699
|
|
|
1700
|
+
// ---------------------------------------------------------------------------
|
|
1701
|
+
// snapshot (next targets): the backing IS the plain raw graph — zero copy.
|
|
1702
|
+
// Sees pending (R27) by reading pb. Chained/owned-copy caching lands with the
|
|
1703
|
+
// utilities increment; this covers the createStore-suite contract.
|
|
1639
1704
|
/** True when `proxy` is a SHALLOW store (children served verbatim, slots
|
|
1640
1705
|
* replaced by reference — #2932). The list driver uses this to choose the
|
|
1641
1706
|
* slot-patch channel (collected row bodies) over per-record registration. */ function storeIsShallow(e) {
|
|
@@ -1685,8 +1750,24 @@ function createStoreNext(e, t = false) {
|
|
|
1685
1750
|
read(getKeySetNode(e));
|
|
1686
1751
|
read(getDeepNode(e));
|
|
1687
1752
|
const r = e.fam?.map ?? storeNextLookup;
|
|
1688
|
-
|
|
1689
|
-
|
|
1753
|
+
// Overlay pending backings chain to the committed object (#3044): their
|
|
1754
|
+
// OWN keys are only this batch's writes. A bare ownKeys walk mid-flush
|
|
1755
|
+
// (effects recompute before the fold commits) missed every untouched
|
|
1756
|
+
// child, so the re-subscribing effect dropped those records from its
|
|
1757
|
+
// dependency set — later child edits never notified it (#3283). Merge
|
|
1758
|
+
// committed keys, minus deletes, exactly as the ownKeys trap does.
|
|
1759
|
+
let i = Reflect.ownKeys(t);
|
|
1760
|
+
if (e.ovl && t === e.pb) {
|
|
1761
|
+
const t = Reflect.ownKeys(e.v);
|
|
1762
|
+
const n = e.del;
|
|
1763
|
+
const r = n !== null && n.size !== 0 ? t.filter(e => !n.has(e)) : t;
|
|
1764
|
+
for (const t of i) {
|
|
1765
|
+
if (!hasOwn.call(e.v, t)) r.push(t);
|
|
1766
|
+
}
|
|
1767
|
+
i = r;
|
|
1768
|
+
}
|
|
1769
|
+
for (const n of i) {
|
|
1770
|
+
const i = Object.getOwnPropertyDescriptor(t, n) ?? Object.getOwnPropertyDescriptor(e.v, n);
|
|
1690
1771
|
if (i === undefined) continue;
|
|
1691
1772
|
if (i.get || i.set) {
|
|
1692
1773
|
e.a = true;
|
|
@@ -1742,12 +1823,17 @@ function snapshotWalk(e, t, n) {
|
|
|
1742
1823
|
if (e === undefined) break;
|
|
1743
1824
|
if (e.fam !== null) n = e.fam;
|
|
1744
1825
|
if (e.fam?.opt === true) (i ??= []).push(e);
|
|
1826
|
+
// The shared visibility decision (#3147): the speculative peek serves
|
|
1827
|
+
// pending staging, but a HELD landing is masked to committed exactly as
|
|
1828
|
+
// it is for per-key readers — the two families must answer alike while
|
|
1829
|
+
// a transaction holds store landings.
|
|
1830
|
+
const t = pendingBackingVisible(e, true);
|
|
1745
1831
|
// Snapshot runs mid-flush (tracked memos execute before commit), so a
|
|
1746
1832
|
// pending prototype overlay must present as a REAL merged container.
|
|
1747
|
-
if (e.ovl) materializePB(e);
|
|
1748
|
-
const
|
|
1749
|
-
if (
|
|
1750
|
-
r =
|
|
1833
|
+
if (t && e.ovl) materializePB(e);
|
|
1834
|
+
const o = t ? e.pb : e.v;
|
|
1835
|
+
if (o === r) break;
|
|
1836
|
+
r = o;
|
|
1751
1837
|
}
|
|
1752
1838
|
if (!isWrappable(r)) return r;
|
|
1753
1839
|
// Optimistic families: compose the visible view; a composed view is a fresh
|
|
@@ -1789,10 +1875,10 @@ function snapshotWalk(e, t, n) {
|
|
|
1789
1875
|
continue;
|
|
1790
1876
|
}
|
|
1791
1877
|
const l = f.value;
|
|
1792
|
-
const
|
|
1793
|
-
if (f.enumerable && f.writable && f.configurable) i[o] =
|
|
1878
|
+
const s = l !== null && typeof l === "object" ? snapshotWalk(l, t, n) : l;
|
|
1879
|
+
if (f.enumerable && f.writable && f.configurable) i[o] = s; else Object.defineProperty(i, o, {
|
|
1794
1880
|
...f,
|
|
1795
|
-
value:
|
|
1881
|
+
value: s
|
|
1796
1882
|
});
|
|
1797
1883
|
}
|
|
1798
1884
|
if (e && i.length !== r.length) i.length = r.length;
|
|
@@ -1819,4 +1905,4 @@ function snapshotWalk(e, t, n) {
|
|
|
1819
1905
|
return f ?? r;
|
|
1820
1906
|
}
|
|
1821
1907
|
|
|
1822
|
-
export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue,
|
|
1908
|
+
export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, runAuthoritative, snapshotNext, stagedTruthPB, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetsEqual, unwrapValue, wrapNext };
|