@solidjs/signals 2.0.0-rc.6 → 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 +2033 -1291
- package/dist/node.cjs +1821 -2401
- package/dist/node.dev.cjs +13724 -0
- package/dist/prod/boundaries.js +3 -1
- package/dist/prod/core/async.js +6 -1
- 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 +215 -139
- package/dist/prod/core/dev.js +2 -0
- package/dist/prod/core/effect.js +37 -28
- 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 +41 -27
- package/dist/prod/core/optimistic.js +42 -32
- package/dist/prod/core/owner.js +32 -32
- package/dist/prod/core/scheduler.js +140 -154
- package/dist/prod/core/verdict.js +31 -34
- package/dist/prod/index.js +0 -2
- package/dist/prod/map.js +104 -94
- package/dist/prod/signals.js +51 -34
- package/dist/prod/store/next/optimistic.js +153 -172
- package/dist/prod/store/next/reconcile.js +140 -288
- package/dist/prod/store/next/store.js +307 -237
- package/dist/prod/store/store.js +2 -2
- package/dist/types/core/attribution-hooks.d.ts +64 -0
- package/dist/types/core/attribution.d.ts +265 -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 +49 -8
- 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 +9 -4
- package/dist/types/signals.d.ts +10 -0
- 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 +20 -46
- package/dist/types/store/store.d.ts +14 -9
- package/dist/types-cjs/core/attribution-hooks.d.cts +64 -0
- package/dist/types-cjs/core/attribution.d.cts +265 -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 +49 -8
- 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 +9 -4
- package/dist/types-cjs/signals.d.cts +10 -0
- 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 +20 -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
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) {
|
|
@@ -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;
|
|
@@ -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,7 +609,7 @@ 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.
|
|
@@ -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
|
|
@@ -749,15 +798,15 @@ function drainFolds() {
|
|
|
749
798
|
// projection recompute can run before the prior fold commits) — the
|
|
750
799
|
// node's OWN current value is the true old side, and setSignal's
|
|
751
800
|
// internal equality already checks exactly that.
|
|
752
|
-
const
|
|
753
|
-
setSignal(i, () =>
|
|
801
|
+
const f = e.del !== null && e.del.has(o) ? undefined : t[o];
|
|
802
|
+
setSignal(i, () => f);
|
|
754
803
|
}
|
|
755
804
|
}
|
|
756
|
-
const
|
|
757
|
-
if (
|
|
758
|
-
const n = o ?? Reflect.ownKeys(
|
|
805
|
+
const f = e.h;
|
|
806
|
+
if (f !== null) {
|
|
807
|
+
const n = o ?? Reflect.ownKeys(f);
|
|
759
808
|
for (const r of n) {
|
|
760
|
-
const n =
|
|
809
|
+
const n = f[r];
|
|
761
810
|
if (n !== undefined) setSignal(n, r in t && !(e.del !== null && e.del.has(r)));
|
|
762
811
|
}
|
|
763
812
|
}
|
|
@@ -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
|
}
|
|
@@ -881,9 +928,9 @@ i = true) {
|
|
|
881
928
|
if (i?.get !== o?.get || i?.set !== o?.set || i?.value !== o?.value) setSignal(e, () => FORCE);
|
|
882
929
|
return;
|
|
883
930
|
}
|
|
884
|
-
const
|
|
885
|
-
const
|
|
886
|
-
if (!isEqual(
|
|
931
|
+
const f = i?.value;
|
|
932
|
+
const l = o?.value;
|
|
933
|
+
if (!isEqual(f, l) && !targetsEqual(f, l)) setSignal(e, typeof l === "function" ? () => l : l);
|
|
887
934
|
} else {
|
|
888
935
|
const i = n[t];
|
|
889
936
|
const o = r[t];
|
|
@@ -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
|
|
|
@@ -1171,15 +1218,15 @@ function isOwnAccessor(e, t) {
|
|
|
1171
1218
|
* holds, lanes ride the node); backing otherwise. Chained backings (§7b: the
|
|
1172
1219
|
* backing IS another store's proxy) serve the read-through value — the outer
|
|
1173
1220
|
* 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
|
|
1221
|
+
* shadows the live chain. */ function serveDataKey(e, t, n, r, i, o = -1) {
|
|
1222
|
+
const f = e.ch && r === e.v;
|
|
1176
1223
|
let l = n;
|
|
1177
1224
|
// §6: on optimistic arrays LENGTH IS A VIEW, not a node value — one home
|
|
1178
1225
|
// (backing ± presence overrides) for both length and indices makes torn
|
|
1179
1226
|
// iteration impossible (a length node's value rides different visibility
|
|
1180
1227
|
// rails than index overrides mid-settle). The node still carries
|
|
1181
1228
|
// subscriptions; its value is never served here.
|
|
1182
|
-
if (t === "length" && e.fam?.opt === true && !
|
|
1229
|
+
if (t === "length" && e.fam?.opt === true && !f && Array.isArray(r)) {
|
|
1183
1230
|
if (!inDraft(e)) {
|
|
1184
1231
|
const r = e.n?.length;
|
|
1185
1232
|
if (r !== undefined) {
|
|
@@ -1199,7 +1246,7 @@ function isOwnAccessor(e, t) {
|
|
|
1199
1246
|
// #2951). Once ensurePB runs, the seeded clone carries the view.
|
|
1200
1247
|
// AUTHORITATIVE drafts (projection derive) never overlay — ensurePB's
|
|
1201
1248
|
// seeding rule, applied to the read side (#3108).
|
|
1202
|
-
if (e.fam?.opt && e
|
|
1249
|
+
if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
|
|
1203
1250
|
const n = e.n?.[t];
|
|
1204
1251
|
if (n !== undefined && hasActiveOverride(n)) l = unwrapOverride(n.o?.Pe);
|
|
1205
1252
|
}
|
|
@@ -1213,12 +1260,14 @@ function isOwnAccessor(e, t) {
|
|
|
1213
1260
|
// parity): READ_SLOW = a global read window or non-plain node.
|
|
1214
1261
|
let e = readNodeFast(i);
|
|
1215
1262
|
if (e === READ_SLOW) e = read(i);
|
|
1216
|
-
if (!
|
|
1217
|
-
} else if (!
|
|
1263
|
+
if (!f || hasActiveOverride(i)) l = e === FORCE ? n : e;
|
|
1264
|
+
} else if (!f || hasActiveOverride(i)) {
|
|
1218
1265
|
l = nodeValue(i, n);
|
|
1219
1266
|
}
|
|
1220
1267
|
} else if (getObserver() !== null) {
|
|
1221
|
-
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));
|
|
1222
1271
|
}
|
|
1223
1272
|
}
|
|
1224
1273
|
// Shallow stores serve data raw; store-proxy slots get boundary wrappers.
|
|
@@ -1320,8 +1369,11 @@ const traps = {
|
|
|
1320
1369
|
// tracked read of a present data key — the dbmon/uibench effect re-read
|
|
1321
1370
|
// shape. Skips serveDataKey's frame, the FORCE compare (only accessor
|
|
1322
1371
|
// keys ever hold the sentinel), and isWrappable for primitives.
|
|
1323
|
-
|
|
1324
|
-
|
|
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;
|
|
1325
1377
|
if (n !== undefined && n.acc !== true && getObserver() !== null) {
|
|
1326
1378
|
let r = readNodeFast(n);
|
|
1327
1379
|
if (r === READ_SLOW) r = read(n);
|
|
@@ -1349,14 +1401,22 @@ const traps = {
|
|
|
1349
1401
|
// absent-key/accessor subscriptions for every store read during any
|
|
1350
1402
|
// derive, leaving nested projections permanently dependency-less when
|
|
1351
1403
|
// their sources hadn't materialized yet (#3037).
|
|
1352
|
-
|
|
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;
|
|
1353
1409
|
{
|
|
1354
|
-
|
|
1355
|
-
if (
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
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;
|
|
1360
1420
|
}
|
|
1361
1421
|
}
|
|
1362
1422
|
// Plain-data fast path: no descriptor allocation per read.
|
|
@@ -1366,10 +1426,10 @@ const traps = {
|
|
|
1366
1426
|
// backings chain to the committed backing, so "own in the view" means
|
|
1367
1427
|
// own on either layer (ownInView) — a genuine prototype method is one
|
|
1368
1428
|
// that is own on NEITHER.
|
|
1369
|
-
const
|
|
1370
|
-
if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(
|
|
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;
|
|
1371
1431
|
let l = r[t];
|
|
1372
|
-
if (l === undefined ? !hasOwn.call(r, t) && !(
|
|
1432
|
+
if (l === undefined ? !hasOwn.call(r, t) && !(f && hasOwn.call(e.v, t)) : false) {
|
|
1373
1433
|
// Inherited: prototype getters/methods run with the proxy receiver.
|
|
1374
1434
|
l = Reflect.get(r, t, n);
|
|
1375
1435
|
if (typeof l === "function") return l;
|
|
@@ -1377,14 +1437,14 @@ const traps = {
|
|
|
1377
1437
|
// Reading a currently-absent own key subscribes to it (R12) — for any
|
|
1378
1438
|
// target OUTSIDE its own draft scope, even mid-setter (#3037, above).
|
|
1379
1439
|
if (l === undefined && !inDraft(e)) {
|
|
1380
|
-
if (getObserver() !== null) read(getNode(e, t, undefined));
|
|
1440
|
+
if (getObserver() !== null) read(getNode(e, t, undefined, o));
|
|
1381
1441
|
const n = e.n?.[t];
|
|
1382
1442
|
if (n) {
|
|
1383
1443
|
const r = nodeValue(n, undefined);
|
|
1384
1444
|
if (e.s) return serveShallow(e, t, r);
|
|
1385
1445
|
return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
|
|
1386
1446
|
}
|
|
1387
|
-
} else if (l === undefined && inDraft(e) && e.fam?.opt && e
|
|
1447
|
+
} else if (l === undefined && inDraft(e) && e.fam?.opt && draftSeesOverrides(e) &&
|
|
1388
1448
|
// AUTHORITATIVE drafts (landing folds) never seed from overrides —
|
|
1389
1449
|
// the caller's optimism is not truth (has-trap twin below).
|
|
1390
1450
|
!authoritativeServe()) {
|
|
@@ -1394,9 +1454,9 @@ const traps = {
|
|
|
1394
1454
|
if (e.s) return serveShallow(e, t, l);
|
|
1395
1455
|
return isWrappable(l) ? draftServe(e, wrapNext(l, e, t)) : l;
|
|
1396
1456
|
}
|
|
1397
|
-
if (typeof l === "function" && !hasOwn.call(r, t) && !(
|
|
1457
|
+
if (typeof l === "function" && !hasOwn.call(r, t) && !(f && hasOwn.call(e.v, t))) return l;
|
|
1398
1458
|
// proto method
|
|
1399
|
-
return serveDataKey(e, t, l, r, i);
|
|
1459
|
+
return serveDataKey(e, t, l, r, i, o);
|
|
1400
1460
|
},
|
|
1401
1461
|
has(e, t) {
|
|
1402
1462
|
if (t === $TARGET || t === $PROXY || t === $TRACK) return true;
|
|
@@ -1417,7 +1477,7 @@ const traps = {
|
|
|
1417
1477
|
const n = e.h?.[t];
|
|
1418
1478
|
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
|
|
1419
1479
|
}
|
|
1420
|
-
} else if (e.fam?.opt && e
|
|
1480
|
+
} else if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
|
|
1421
1481
|
const n = e.h?.[t];
|
|
1422
1482
|
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
|
|
1423
1483
|
}
|
|
@@ -1444,7 +1504,7 @@ const traps = {
|
|
|
1444
1504
|
// Draft reads before the first write overlay too (pb, once created, is
|
|
1445
1505
|
// seeded with the view). Authoritative-view reads (until()'s predicate,
|
|
1446
1506
|
// truth-author drafts) skip the overlay.
|
|
1447
|
-
if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || e
|
|
1507
|
+
if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || draftSeesOverrides(e))) {
|
|
1448
1508
|
let t = null;
|
|
1449
1509
|
for (const r of Reflect.ownKeys(e.h)) {
|
|
1450
1510
|
const i = e.h[r];
|
|
@@ -1504,23 +1564,22 @@ const traps = {
|
|
|
1504
1564
|
// passes through by reference (#2932; markRawOne skips proxies), while
|
|
1505
1565
|
// deep stores unwrap to raw backings.
|
|
1506
1566
|
const o = e.s ? n : unwrapValue(n);
|
|
1507
|
-
const
|
|
1567
|
+
const f = ensurePB(e);
|
|
1508
1568
|
pendingNotify.add(e);
|
|
1509
1569
|
// Array length writes implicitly delete indices — the written-keys bound
|
|
1510
1570
|
// can't see them, so poison to the full scan for this batch. Index
|
|
1511
1571
|
// writes implicitly GROW length, so arrays always record it alongside.
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
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");
|
|
1518
1577
|
}
|
|
1519
|
-
} else if (
|
|
1578
|
+
} else if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
|
|
1520
1579
|
// Own data keys literally named "prototype"/"constructor" land as data —
|
|
1521
1580
|
// defineProperty sidesteps a proto-chain setter named the same.
|
|
1522
1581
|
if (UNSAFE_KEYS.has(t)) {
|
|
1523
|
-
Object.defineProperty(
|
|
1582
|
+
Object.defineProperty(f, t, {
|
|
1524
1583
|
value: o,
|
|
1525
1584
|
writable: true,
|
|
1526
1585
|
enumerable: true,
|
|
@@ -1532,14 +1591,14 @@ const traps = {
|
|
|
1532
1591
|
// Overlay first-write DEFINES the own key: assignment through the proto
|
|
1533
1592
|
// chain would reject on a non-writable committed property (the clone
|
|
1534
1593
|
// path normalized descriptors for exactly this — R51 parity).
|
|
1535
|
-
if (e.ovl && !hasOwn.call(
|
|
1536
|
-
Object.defineProperty(
|
|
1594
|
+
if (e.ovl && !hasOwn.call(f, t)) {
|
|
1595
|
+
Object.defineProperty(f, t, {
|
|
1537
1596
|
value: o,
|
|
1538
1597
|
writable: true,
|
|
1539
1598
|
enumerable: true,
|
|
1540
1599
|
configurable: true
|
|
1541
1600
|
});
|
|
1542
|
-
} else
|
|
1601
|
+
} else f[t] = o;
|
|
1543
1602
|
if (e.del !== null) e.del.delete(t);
|
|
1544
1603
|
// Shallow ingest: written records are sticky raw-marked (one entity is
|
|
1545
1604
|
// never both deep-wrapped and raw — R41/#2932, shared invariant).
|
|
@@ -1554,14 +1613,7 @@ const traps = {
|
|
|
1554
1613
|
const i = !r && getWriteOverride();
|
|
1555
1614
|
if (!r && !i) return true;
|
|
1556
1615
|
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
|
-
}
|
|
1616
|
+
if (n.get || n.set) e.a = true;
|
|
1565
1617
|
// Unwrap before ensurePB (see the set trap: self-reference materializes).
|
|
1566
1618
|
if ("value" in n) n = {
|
|
1567
1619
|
...n,
|
|
@@ -1569,8 +1621,7 @@ const traps = {
|
|
|
1569
1621
|
};
|
|
1570
1622
|
const o = ensurePB(e);
|
|
1571
1623
|
pendingNotify.add(e);
|
|
1572
|
-
|
|
1573
|
-
if (l.wk !== WK_ALL) (l.wk ??= new Set).add(t);
|
|
1624
|
+
if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
|
|
1574
1625
|
Object.defineProperty(o, t, n);
|
|
1575
1626
|
if (e.del !== null) e.del.delete(t);
|
|
1576
1627
|
if (i) notifyWrites(e);
|
|
@@ -1582,8 +1633,7 @@ const traps = {
|
|
|
1582
1633
|
if (!n && !r) return true;
|
|
1583
1634
|
const i = ensurePB(e);
|
|
1584
1635
|
pendingNotify.add(e);
|
|
1585
|
-
|
|
1586
|
-
if (o.wk !== WK_ALL) (o.wk ??= new Set).add(t);
|
|
1636
|
+
if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
|
|
1587
1637
|
delete i[t];
|
|
1588
1638
|
// A prototype overlay cannot shadow a delete of a committed key —
|
|
1589
1639
|
// record it aside (#3044); reads/has/ownKeys/commit consult the set.
|
|
@@ -1647,6 +1697,10 @@ function createStoreNext(e, t = false) {
|
|
|
1647
1697
|
return [ n, setter ];
|
|
1648
1698
|
}
|
|
1649
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.
|
|
1650
1704
|
/** True when `proxy` is a SHALLOW store (children served verbatim, slots
|
|
1651
1705
|
* replaced by reference — #2932). The list driver uses this to choose the
|
|
1652
1706
|
* slot-patch channel (collected row bodies) over per-record registration. */ function storeIsShallow(e) {
|
|
@@ -1696,8 +1750,24 @@ function createStoreNext(e, t = false) {
|
|
|
1696
1750
|
read(getKeySetNode(e));
|
|
1697
1751
|
read(getDeepNode(e));
|
|
1698
1752
|
const r = e.fam?.map ?? storeNextLookup;
|
|
1699
|
-
|
|
1700
|
-
|
|
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);
|
|
1701
1771
|
if (i === undefined) continue;
|
|
1702
1772
|
if (i.get || i.set) {
|
|
1703
1773
|
e.a = true;
|
|
@@ -1708,15 +1778,15 @@ function createStoreNext(e, t = false) {
|
|
|
1708
1778
|
if (o === null || typeof o !== "object") continue;
|
|
1709
1779
|
// Stored proxies (chained slots) resolve through their own target;
|
|
1710
1780
|
// raw children through the family map, created on first visit.
|
|
1711
|
-
let
|
|
1712
|
-
if (
|
|
1781
|
+
let f = o[$TARGET] ?? r.get(o);
|
|
1782
|
+
if (f === undefined) {
|
|
1713
1783
|
if (!isWrappable(o)) continue;
|
|
1714
1784
|
wrapNext(o, e, n);
|
|
1715
|
-
|
|
1716
|
-
if (
|
|
1785
|
+
f = r.get(o);
|
|
1786
|
+
if (f === undefined) continue;
|
|
1717
1787
|
// raw-marked: leaf by contract
|
|
1718
1788
|
}
|
|
1719
|
-
walkT(
|
|
1789
|
+
walkT(f);
|
|
1720
1790
|
}
|
|
1721
1791
|
};
|
|
1722
1792
|
walkT(t);
|
|
@@ -1775,15 +1845,15 @@ function snapshotWalk(e, t, n) {
|
|
|
1775
1845
|
const i = t.get(r);
|
|
1776
1846
|
if (i !== undefined) return i;
|
|
1777
1847
|
const o = Array.isArray(e);
|
|
1778
|
-
const
|
|
1779
|
-
t.set(r,
|
|
1848
|
+
const f = o ? [] : Object.create(Object.getPrototypeOf(e));
|
|
1849
|
+
t.set(r, f);
|
|
1780
1850
|
for (const r of Reflect.ownKeys(e)) {
|
|
1781
1851
|
if (o && r === "length") continue;
|
|
1782
1852
|
const i = e[r];
|
|
1783
|
-
|
|
1853
|
+
f[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
|
|
1784
1854
|
}
|
|
1785
|
-
if (o)
|
|
1786
|
-
return
|
|
1855
|
+
if (o) f.length = e.length;
|
|
1856
|
+
return f;
|
|
1787
1857
|
}
|
|
1788
1858
|
}
|
|
1789
1859
|
const o = t.get(r);
|
|
@@ -1798,17 +1868,17 @@ function snapshotWalk(e, t, n) {
|
|
|
1798
1868
|
t.set(r, i);
|
|
1799
1869
|
for (const o of Reflect.ownKeys(r)) {
|
|
1800
1870
|
if (e && o === "length") continue;
|
|
1801
|
-
const
|
|
1802
|
-
if (typeof o === "symbol" && !
|
|
1803
|
-
if (
|
|
1804
|
-
Object.defineProperty(i, o,
|
|
1871
|
+
const f = Object.getOwnPropertyDescriptor(r, o);
|
|
1872
|
+
if (typeof o === "symbol" && !f.enumerable) continue;
|
|
1873
|
+
if (f.get || f.set) {
|
|
1874
|
+
Object.defineProperty(i, o, f);
|
|
1805
1875
|
continue;
|
|
1806
1876
|
}
|
|
1807
|
-
const
|
|
1808
|
-
const
|
|
1809
|
-
if (
|
|
1810
|
-
...
|
|
1811
|
-
value:
|
|
1877
|
+
const l = f.value;
|
|
1878
|
+
const s = l !== null && typeof l === "object" ? snapshotWalk(l, t, n) : l;
|
|
1879
|
+
if (f.enumerable && f.writable && f.configurable) i[o] = s; else Object.defineProperty(i, o, {
|
|
1880
|
+
...f,
|
|
1881
|
+
value: s
|
|
1812
1882
|
});
|
|
1813
1883
|
}
|
|
1814
1884
|
if (e && i.length !== r.length) i.length = r.length;
|
|
@@ -1817,22 +1887,22 @@ function snapshotWalk(e, t, n) {
|
|
|
1817
1887
|
// UNOWNED (shared/user) subtrees keep identity unless a descendant
|
|
1818
1888
|
// substituted; copy-on-substitution preserves the documented CoW contract.
|
|
1819
1889
|
t.set(r, r);
|
|
1820
|
-
let
|
|
1890
|
+
let f = null;
|
|
1821
1891
|
for (const e of Reflect.ownKeys(r)) {
|
|
1822
1892
|
const i = Object.getOwnPropertyDescriptor(r, e);
|
|
1823
1893
|
if (!i || i.get || i.set) continue;
|
|
1824
1894
|
const o = i.value;
|
|
1825
1895
|
if (o === null || typeof o !== "object") continue;
|
|
1826
|
-
const
|
|
1827
|
-
if (
|
|
1828
|
-
if (
|
|
1829
|
-
|
|
1830
|
-
t.set(r,
|
|
1896
|
+
const l = snapshotWalk(o, t, n);
|
|
1897
|
+
if (l !== o) {
|
|
1898
|
+
if (f === null) {
|
|
1899
|
+
f = Array.isArray(r) ? [ ...r ] : Object.create(Object.getPrototypeOf(r), Object.getOwnPropertyDescriptors(r));
|
|
1900
|
+
t.set(r, f);
|
|
1831
1901
|
}
|
|
1832
|
-
|
|
1902
|
+
f[e] = l;
|
|
1833
1903
|
}
|
|
1834
1904
|
}
|
|
1835
|
-
return
|
|
1905
|
+
return f ?? r;
|
|
1836
1906
|
}
|
|
1837
1907
|
|
|
1838
|
-
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 };
|