@solidjs/signals 2.0.0-rc.1 → 2.0.0-rc.2
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 +1661 -361
- package/dist/node.cjs +1939 -1331
- package/dist/prod/affects.js +16 -16
- package/dist/prod/boundaries.js +90 -90
- package/dist/prod/core/action.js +3 -3
- package/dist/prod/core/async.js +74 -72
- package/dist/prod/core/constants.js +39 -1
- package/dist/prod/core/core.js +332 -224
- package/dist/prod/core/effect.js +56 -56
- package/dist/prod/core/external.js +4 -4
- package/dist/prod/core/graph.js +65 -54
- package/dist/prod/core/heap.js +52 -44
- package/dist/prod/core/invariants.js +3 -2
- package/dist/prod/core/lanes.js +41 -34
- package/dist/prod/core/optimistic.js +63 -60
- package/dist/prod/core/owner.js +97 -96
- package/dist/prod/core/scheduler.js +243 -194
- package/dist/prod/core/verdict.js +184 -77
- package/dist/prod/map.js +104 -104
- package/dist/prod/signals.js +1 -1
- package/dist/prod/store/next/optimistic.js +31 -23
- package/dist/prod/store/next/projection.js +3 -3
- package/dist/prod/store/next/reconcile.js +78 -74
- package/dist/prod/store/next/store.js +357 -90
- package/dist/prod/store/store.js +7 -7
- package/dist/types/core/attribution-hooks.d.ts +52 -0
- package/dist/types/core/attribution.d.ts +186 -0
- package/dist/types/core/constants.d.ts +26 -0
- package/dist/types/core/core.d.ts +8 -1
- package/dist/types/core/dev.d.ts +20 -3
- package/dist/types/core/graph.d.ts +1 -0
- package/dist/types/core/lanes.d.ts +4 -16
- package/dist/types/core/scheduler.d.ts +8 -0
- package/dist/types/core/types.d.ts +85 -41
- package/dist/types/store/next/projection.d.ts +0 -16
- package/dist/types/store/next/store.d.ts +6 -0
- package/dist/types/store/next/target.d.ts +18 -0
- package/dist/types-cjs/core/attribution-hooks.d.cts +52 -0
- package/dist/types-cjs/core/attribution.d.cts +186 -0
- package/dist/types-cjs/core/constants.d.cts +26 -0
- package/dist/types-cjs/core/core.d.cts +8 -1
- package/dist/types-cjs/core/dev.d.cts +20 -3
- package/dist/types-cjs/core/graph.d.cts +1 -0
- package/dist/types-cjs/core/lanes.d.cts +4 -16
- package/dist/types-cjs/core/scheduler.d.cts +8 -0
- package/dist/types-cjs/core/types.d.cts +85 -41
- package/dist/types-cjs/store/next/projection.d.cts +0 -16
- package/dist/types-cjs/store/next/store.d.cts +6 -0
- package/dist/types-cjs/store/next/target.d.cts +18 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { unwrapOverride, $REFRESH, CONFIG_OWNED_WRITE, NOT_PENDING, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN } from "../../core/constants.js";
|
|
1
|
+
import { unwrapOverride, $REFRESH, CONFIG_OWNED_WRITE, NOT_PENDING, CONFIG_OPTIMISTIC, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN } from "../../core/constants.js";
|
|
2
2
|
|
|
3
|
-
import { setSignal, isEqual, read, pendingCheckActive, readNodeFast, READ_SLOW, signal } from "../../core/core.js";
|
|
3
|
+
import { setSignal, isEqual, read, pendingCheckActive, readNodeFast, READ_SLOW, signal, ext } from "../../core/core.js";
|
|
4
4
|
|
|
5
5
|
import { projectionWriteActive, setProjectionWriteActive, schedule, setStoreCommitHook } from "../../core/scheduler.js";
|
|
6
6
|
|
|
@@ -30,13 +30,48 @@ import { storeNextLookup, optHooks, ownedRaw, devAssertNeverUserMutation } from
|
|
|
30
30
|
*/
|
|
31
31
|
// ---------------------------------------------------------------------------
|
|
32
32
|
// wrap / dedupe
|
|
33
|
+
/** Pre-shaped constructor for OBJECT proxy targets: V8 tips a bare `{}` into
|
|
34
|
+
* dictionary mode once ~19 named properties are assigned onto it (the #3044
|
|
35
|
+
* `ovl`/`del` fields crossed that line — every trap's field read became a
|
|
36
|
+
* hash lookup, a 15% deep-dbmon tick regression). Declaring every field in a
|
|
37
|
+
* constructor pre-allocates in-object slots so the map stays fast, with
|
|
38
|
+
* headroom for future fields. The prototype is reset to `Object.prototype`
|
|
39
|
+
* so proxy-forwarded semantics (getPrototypeOf, constructor) are exactly a
|
|
40
|
+
* plain object's. Array targets keep the bare-`[]` path — they must carry
|
|
41
|
+
* the array exotic class for `Array.isArray(proxy)`, and arrays store named
|
|
42
|
+
* fields off-object where this cliff does not apply. */
|
|
43
|
+
function TargetShape() {
|
|
44
|
+
this.v = undefined;
|
|
45
|
+
this.ch = undefined;
|
|
46
|
+
this.pb = undefined;
|
|
47
|
+
this.n = undefined;
|
|
48
|
+
this.h = undefined;
|
|
49
|
+
this.k = undefined;
|
|
50
|
+
this.dk = undefined;
|
|
51
|
+
this.u = undefined;
|
|
52
|
+
this.pk = undefined;
|
|
53
|
+
this.px = undefined;
|
|
54
|
+
this.d = undefined;
|
|
55
|
+
this.a = undefined;
|
|
56
|
+
this.sc = undefined;
|
|
57
|
+
this.nc = undefined;
|
|
58
|
+
this.adopted = undefined;
|
|
59
|
+
this.fam = undefined;
|
|
60
|
+
this.s = undefined;
|
|
61
|
+
this.ovl = undefined;
|
|
62
|
+
this.del = undefined;
|
|
63
|
+
this.wk = undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
TargetShape.prototype = Object.prototype;
|
|
67
|
+
|
|
33
68
|
function createTarget(e, t, n, r = t?.fam ?? null) {
|
|
34
69
|
// The proxy target carries the array exotic class when the value is an
|
|
35
70
|
// array, so Array.isArray(proxy) is true; the fields live on it directly.
|
|
36
71
|
// Direct field assignment in one fixed order (no Object.assign literal
|
|
37
72
|
// copy): every target shares a hidden-class transition chain — createTarget
|
|
38
73
|
// was the #2 store cost in the uibench creation profile.
|
|
39
|
-
const i = Array.isArray(e) ? [] :
|
|
74
|
+
const i = Array.isArray(e) ? [] : new TargetShape;
|
|
40
75
|
i.v = e;
|
|
41
76
|
// Chained-backing flag (backing IS another store's proxy, §7b) — cached so
|
|
42
77
|
// the hot read path never does a per-read symbol lookup on the backing.
|
|
@@ -56,6 +91,9 @@ function createTarget(e, t, n, r = t?.fam ?? null) {
|
|
|
56
91
|
i.adopted = false;
|
|
57
92
|
i.fam = r;
|
|
58
93
|
i.s = false;
|
|
94
|
+
i.ovl = false;
|
|
95
|
+
i.del = null;
|
|
96
|
+
i.wk = null;
|
|
59
97
|
i.px = new Proxy(i, traps);
|
|
60
98
|
// Legacy interop: shared machinery (affects walks, wrap dedupe) reads the
|
|
61
99
|
// proxy off looked-up targets as a field.
|
|
@@ -83,7 +121,13 @@ function wrapNext(e, t = null, n = null, r = t?.fam ?? null) {
|
|
|
83
121
|
/** Unwrap our own proxies to their current backing; leave everything else. */ function unwrapValue(e) {
|
|
84
122
|
if (e == null || typeof e !== "object") return e;
|
|
85
123
|
const t = e[$TARGET];
|
|
86
|
-
if (t !== undefined && t.px === e && t.v !== undefined)
|
|
124
|
+
if (t !== undefined && t.px === e && t.v !== undefined) {
|
|
125
|
+
// A draft escaping into other storage must be a REAL container that
|
|
126
|
+
// becomes this target's committed backing at fold (the shared-raw
|
|
127
|
+
// contract) — a prototype overlay is neither.
|
|
128
|
+
if (t.ovl) materializePB(t);
|
|
129
|
+
return t.pb ?? t.v;
|
|
130
|
+
}
|
|
87
131
|
return e;
|
|
88
132
|
}
|
|
89
133
|
|
|
@@ -100,7 +144,7 @@ function getNode(e, t, n) {
|
|
|
100
144
|
equals: (t, n) => isEqual(t, n) || sameLogicalSlot(e, t, n),
|
|
101
145
|
unobserved() {
|
|
102
146
|
// A live affects() mark keeps the node addressable (sweep parity).
|
|
103
|
-
if (o.t) return;
|
|
147
|
+
if (o.o?.t) return;
|
|
104
148
|
if (e.n && e.n[t] === o) {
|
|
105
149
|
delete e.n[t];
|
|
106
150
|
e.nc--;
|
|
@@ -126,7 +170,10 @@ function getNode(e, t, n) {
|
|
|
126
170
|
o.pxv = undefined;
|
|
127
171
|
// Optimistic families: arm the override slot — setSignal routes armed
|
|
128
172
|
// nodes through the core engine (lanes, ownership, reverts all native).
|
|
129
|
-
if (e.fam?.opt)
|
|
173
|
+
if (e.fam?.opt) {
|
|
174
|
+
ext(o).De = NOT_PENDING;
|
|
175
|
+
o.T |= CONFIG_OPTIMISTIC;
|
|
176
|
+
}
|
|
130
177
|
// A node born inside a live mark's identity scope inherits the mark
|
|
131
178
|
// (the declaration walk could only cover nodes existing then).
|
|
132
179
|
if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
|
|
@@ -151,12 +198,15 @@ function getHasNode(e, t, n) {
|
|
|
151
198
|
const o = i = signal(n, {
|
|
152
199
|
equals: isEqual,
|
|
153
200
|
unobserved() {
|
|
154
|
-
if (o.t) return;
|
|
201
|
+
if (o.o?.t) return;
|
|
155
202
|
if (e.h && e.h[t] === o) delete e.h[t];
|
|
156
203
|
}
|
|
157
204
|
}, e.fam?.node ?? undefined);
|
|
158
205
|
o.T |= CONFIG_OWNED_WRITE;
|
|
159
|
-
if (e.fam?.opt)
|
|
206
|
+
if (e.fam?.opt) {
|
|
207
|
+
ext(o).De = NOT_PENDING;
|
|
208
|
+
o.T |= CONFIG_OPTIMISTIC;
|
|
209
|
+
}
|
|
160
210
|
if (affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
|
|
161
211
|
r[t] = i;
|
|
162
212
|
markDescendants(e);
|
|
@@ -174,7 +224,10 @@ function getKeySetNode(e) {
|
|
|
174
224
|
}
|
|
175
225
|
}, e.fam?.node ?? undefined);
|
|
176
226
|
n.T |= CONFIG_OWNED_WRITE;
|
|
177
|
-
if (e.fam?.opt)
|
|
227
|
+
if (e.fam?.opt) {
|
|
228
|
+
ext(n).De = NOT_PENDING;
|
|
229
|
+
n.T |= CONFIG_OPTIMISTIC;
|
|
230
|
+
}
|
|
178
231
|
e.k = t;
|
|
179
232
|
markDescendants(e);
|
|
180
233
|
}
|
|
@@ -191,7 +244,10 @@ function getDeepNode(e) {
|
|
|
191
244
|
}
|
|
192
245
|
}, e.fam?.node ?? undefined);
|
|
193
246
|
n.T |= CONFIG_OWNED_WRITE;
|
|
194
|
-
if (e.fam?.opt)
|
|
247
|
+
if (e.fam?.opt) {
|
|
248
|
+
ext(n).De = NOT_PENDING;
|
|
249
|
+
n.T |= CONFIG_OPTIMISTIC;
|
|
250
|
+
}
|
|
195
251
|
if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
|
|
196
252
|
e.dk = t;
|
|
197
253
|
markDescendants(e);
|
|
@@ -235,10 +291,56 @@ function cloneRaw(e, t) {
|
|
|
235
291
|
return Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
|
|
236
292
|
}
|
|
237
293
|
|
|
294
|
+
/** One-time own-accessor scan (Annex-B probes, no descriptor allocation);
|
|
295
|
+
* returns true when the container is plain data (overlay-safe). */ function scanAccessorsOnce(e) {
|
|
296
|
+
const t = e.v;
|
|
297
|
+
for (const n of Reflect.ownKeys(t)) {
|
|
298
|
+
// Own keys shadow prototype accessors, so the lookups are exact here.
|
|
299
|
+
if (lookupGetter.call(t, n) !== undefined || lookupSetter.call(t, n) !== undefined) {
|
|
300
|
+
e.a = true;
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
e.sc = true;
|
|
305
|
+
return !e.a;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Downgrade a prototype-overlay pending backing to the clone path: builds
|
|
309
|
+
* the real container (committed + overlay writes − deletes) that fold will
|
|
310
|
+
* SWAP in as the committed backing, exactly as if the draft had started on
|
|
311
|
+
* the clone path. Consumers that need a complete container (reconcile's
|
|
312
|
+
* diff walks, drafts escaping into other storage) call this. */ function materializePB(e) {
|
|
313
|
+
if (!e.ovl) return;
|
|
314
|
+
const t = e.pb;
|
|
315
|
+
const n = cloneRaw(e.v, e);
|
|
316
|
+
for (const e of Reflect.ownKeys(t)) {
|
|
317
|
+
const r = Object.getOwnPropertyDescriptor(t, e);
|
|
318
|
+
if (r.get || r.set || !r.enumerable || !r.writable || !r.configurable) Object.defineProperty(n, e, r); else n[e] = r.value;
|
|
319
|
+
}
|
|
320
|
+
if (e.del !== null) {
|
|
321
|
+
for (const t of e.del) delete n[t];
|
|
322
|
+
e.del = null;
|
|
323
|
+
}
|
|
324
|
+
const r = e.fam?.map ?? storeNextLookup;
|
|
325
|
+
r.delete(t);
|
|
326
|
+
ownedRaw.add(n);
|
|
327
|
+
r.set(n, e);
|
|
328
|
+
e.pb = n;
|
|
329
|
+
e.ovl = false;
|
|
330
|
+
}
|
|
331
|
+
|
|
238
332
|
function ensurePB(e) {
|
|
239
333
|
let t = e.pb;
|
|
240
334
|
if (t === null) {
|
|
241
|
-
|
|
335
|
+
// Prototype-chain overlay (#3044): plain-data non-array containers
|
|
336
|
+
// outside projection/optimistic families open drafts in O(1) — own keys
|
|
337
|
+
// are the writes, reads fall through to committed. Everything else
|
|
338
|
+
// (arrays: splice/length semantics; families: seeding/revert machinery;
|
|
339
|
+
// accessor containers: live getters) keeps the descriptor clone.
|
|
340
|
+
if (e.fam === null && !Array.isArray(e.v) && (e.sc ? !e.a : scanAccessorsOnce(e))) {
|
|
341
|
+
t = e.pb = Object.create(e.v);
|
|
342
|
+
e.ovl = true;
|
|
343
|
+
} else t = e.pb = cloneRaw(e.v, e);
|
|
242
344
|
// Optimistic families: seed USER drafts from the OPTIMISTIC VIEW
|
|
243
345
|
// (committed + active node overrides), so follow-up writes compose on
|
|
244
346
|
// optimism instead of clobbering from base (#2951's compose half).
|
|
@@ -250,14 +352,14 @@ function ensurePB(e) {
|
|
|
250
352
|
if (n !== null) {
|
|
251
353
|
for (const e of Reflect.ownKeys(n)) {
|
|
252
354
|
const r = n[e];
|
|
253
|
-
if (hasActiveOverride(r)) t[e] = unwrapOverride(r.De);
|
|
355
|
+
if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.De);
|
|
254
356
|
}
|
|
255
357
|
}
|
|
256
358
|
const r = e.h;
|
|
257
359
|
if (r !== null) {
|
|
258
360
|
for (const e of Reflect.ownKeys(r)) {
|
|
259
361
|
const n = r[e];
|
|
260
|
-
if (hasActiveOverride(n) && !unwrapOverride(n.De)) delete t[e];
|
|
362
|
+
if (hasActiveOverride(n) && !unwrapOverride(n.o?.De)) delete t[e];
|
|
261
363
|
}
|
|
262
364
|
}
|
|
263
365
|
}
|
|
@@ -285,6 +387,19 @@ function ensurePB(e) {
|
|
|
285
387
|
e.adopted = true;
|
|
286
388
|
}
|
|
287
389
|
e.pb = null;
|
|
390
|
+
// Overlay and accessor-scan state describe the OUTGOING backing — a
|
|
391
|
+
// swapped container must not inherit them: a stale `ovl` beside a nulled
|
|
392
|
+
// pb crashes materializePB (unwrapValue consults ovl before the
|
|
393
|
+
// null-coalesce), a stale `del` would read the adoptee's keys as deleted
|
|
394
|
+
// in the next draft, and a stale plain-data verdict (`sc`/`a`) could
|
|
395
|
+
// admit an accessor-bearing adoptee to the overlay path. Reset; the next
|
|
396
|
+
// draft rescans once (#3044 audit follow-up).
|
|
397
|
+
e.ovl = false;
|
|
398
|
+
e.del = null;
|
|
399
|
+
e.wk = null;
|
|
400
|
+
// adoption supersedes any staged trap writes
|
|
401
|
+
e.sc = false;
|
|
402
|
+
e.a = false;
|
|
288
403
|
e.v = t;
|
|
289
404
|
e.ch = t[$TARGET] !== undefined;
|
|
290
405
|
(e.fam?.map ?? storeNextLookup).set(t, e);
|
|
@@ -331,9 +446,14 @@ function drainFolds() {
|
|
|
331
446
|
const r = t.pb;
|
|
332
447
|
const i = t.n;
|
|
333
448
|
if (i !== null) {
|
|
334
|
-
|
|
449
|
+
// Only written keys can hold (their nodes took the setSignal); the
|
|
450
|
+
// wk bound keeps this O(written) — see notifyWrites. Same fallback
|
|
451
|
+
// rules as the notify (WK_ALL / accessors / non-plain prototypes).
|
|
452
|
+
const n = t.wk;
|
|
453
|
+
const o = n === null || n === WK_ALL || t.a === true || !plainProto(t.ovl ? t.v : r) ? Reflect.ownKeys(i) : n;
|
|
454
|
+
for (const t of o) {
|
|
335
455
|
const n = i[t];
|
|
336
|
-
if (n.
|
|
456
|
+
if (n !== undefined && n.Pe !== NOT_PENDING) {
|
|
337
457
|
e = true;
|
|
338
458
|
break;
|
|
339
459
|
}
|
|
@@ -344,10 +464,37 @@ function drainFolds() {
|
|
|
344
464
|
// re-queue: commit happens when the hold settles
|
|
345
465
|
continue;
|
|
346
466
|
}
|
|
347
|
-
t.
|
|
348
|
-
|
|
467
|
+
if (t.ovl) {
|
|
468
|
+
// Overlay flatten (#3044): apply this batch's writes onto an OWNED
|
|
469
|
+
// committed backing in place — O(written), not O(container). The
|
|
470
|
+
// backing keeps its identity, so the `t.v === old` gate below skips
|
|
471
|
+
// path copying (the parent slot already points here) and the
|
|
472
|
+
// adopted-notify (setter notifications happened at write time).
|
|
473
|
+
// Unowned backings privatize first (clone once, parents re-slotted)
|
|
474
|
+
// — the never-mutate-user-data contract holds.
|
|
475
|
+
privatizeCommitted(t);
|
|
476
|
+
const e = t.v;
|
|
477
|
+
for (const t of Reflect.ownKeys(r)) {
|
|
478
|
+
const n = Object.getOwnPropertyDescriptor(r, t);
|
|
479
|
+
if (n.get || n.set || !n.enumerable || !n.writable || !n.configurable) Object.defineProperty(e, t, n); else e[t] = n.value;
|
|
480
|
+
}
|
|
481
|
+
if (t.del !== null) {
|
|
482
|
+
for (const n of t.del) delete e[n];
|
|
483
|
+
t.del = null;
|
|
484
|
+
}
|
|
485
|
+
(t.fam?.map ?? storeNextLookup).delete(r);
|
|
486
|
+
t.pb = null;
|
|
487
|
+
t.ovl = false;
|
|
488
|
+
t.wk = null;
|
|
489
|
+
// written-keys window closes with the fold commit
|
|
490
|
+
} else {
|
|
491
|
+
t.v = r;
|
|
492
|
+
t.ch = false;
|
|
349
493
|
// pb is always a plain clone
|
|
350
|
-
|
|
494
|
+
t.pb = null;
|
|
495
|
+
t.wk = null;
|
|
496
|
+
// written-keys window closes with the fold commit
|
|
497
|
+
}
|
|
351
498
|
}
|
|
352
499
|
if (t.v === n) continue;
|
|
353
500
|
// adopted then re-adopted back, or no-op
|
|
@@ -371,8 +518,20 @@ function drainFolds() {
|
|
|
371
518
|
* isPending, affects, and lane machinery ride the core natively (§3's
|
|
372
519
|
* "pending home = the node when a node exists"). Unobserved keys stay in the
|
|
373
520
|
* pending backing and fold directly at commit.
|
|
374
|
-
*/
|
|
375
|
-
|
|
521
|
+
*/
|
|
522
|
+
/** Sentinel for `t.wk`: the written-keys bound is unusable this batch (an
|
|
523
|
+
* array length write implicitly deleted indices) — consumers full-scan. */ const WK_ALL = new Set;
|
|
524
|
+
|
|
525
|
+
/** Plain-prototype check for the written-keys bound: prototype getters on
|
|
526
|
+
* class instances can derive from ANY field, so only plain-data containers
|
|
527
|
+
* may bound the notify to written keys. Overlay pbs chain to the COMMITTED
|
|
528
|
+
* object (#3044), so overlay plainness is judged on the committed proto. */ const plainProto = e => {
|
|
529
|
+
const t = Object.getPrototypeOf(e);
|
|
530
|
+
return t === Object.prototype || t === Array.prototype || t === null;
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
function notifyWrites(e) {
|
|
534
|
+
let t = e.pb;
|
|
376
535
|
if (t === null) return;
|
|
377
536
|
// Optimistic channel: user writes on an optimistic family become node-level
|
|
378
537
|
// engine writes (armed nodes route setSignal through optimisticWrite) — the
|
|
@@ -402,40 +561,51 @@ function drainFolds() {
|
|
|
402
561
|
}
|
|
403
562
|
const n = e.v;
|
|
404
563
|
const r = e.n;
|
|
564
|
+
// Written-keys bound: trap writes record their keys, so the notify visits
|
|
565
|
+
// O(written) nodes instead of every subscription on the record (a selection
|
|
566
|
+
// map with thousands of per-key subscribers pays two visits per select,
|
|
567
|
+
// not a full scan). Falls back to the full node scan when the bound can't
|
|
568
|
+
// hold: no trap granularity (wk null), an array length write (WK_ALL —
|
|
569
|
+
// implicit index deletes), accessors on the record (t.a — a getter node's
|
|
570
|
+
// value can change when ANY key is written), or a non-plain prototype.
|
|
571
|
+
const i = e.wk;
|
|
572
|
+
const o = i === WK_ALL || e.a === true || !plainProto(e.ovl ? e.v : t) ? null : i;
|
|
405
573
|
if (r !== null) {
|
|
406
|
-
|
|
407
|
-
|
|
574
|
+
const i = o ?? Reflect.ownKeys(r);
|
|
575
|
+
for (const o of i) {
|
|
576
|
+
const i = r[o];
|
|
577
|
+
if (i === undefined) continue;
|
|
408
578
|
// Per-key accessor handling: the node's cached flag plus ONE getter
|
|
409
579
|
// probe on the incoming side (getters arriving via merge/adoption).
|
|
410
580
|
// Setter-only props read as data (value undefined) so lookupSetter is
|
|
411
581
|
// not consulted on this hot path; prototype getters never own nodes.
|
|
412
|
-
if (i.acc === true || hasOwn.call(t,
|
|
413
|
-
i.acc = isOwnAccessor(t,
|
|
414
|
-
const
|
|
415
|
-
const
|
|
416
|
-
if (
|
|
417
|
-
if (
|
|
582
|
+
if (i.acc === true || hasOwn.call(t, o) && lookupGetter.call(t, o) !== undefined) {
|
|
583
|
+
i.acc = isOwnAccessor(t, o);
|
|
584
|
+
const e = Object.getOwnPropertyDescriptor(n, o);
|
|
585
|
+
const r = Object.getOwnPropertyDescriptor(t, o);
|
|
586
|
+
if (e && (e.get || e.set) || r && (r.get || r.set)) {
|
|
587
|
+
if (e?.get !== r?.get || e?.set !== r?.set || e?.value !== r?.value) setSignal(i, () => FORCE);
|
|
418
588
|
continue;
|
|
419
589
|
}
|
|
420
|
-
if (!isEqual(
|
|
590
|
+
if (!isEqual(e?.value, r?.value)) setSignal(i, () => r?.value);
|
|
421
591
|
continue;
|
|
422
592
|
}
|
|
423
593
|
// No old-side pre-compare: t.v lags across multi-batch windows (a
|
|
424
594
|
// projection recompute can run before the prior fold commits) — the
|
|
425
595
|
// node's OWN current value is the true old side, and setSignal's
|
|
426
596
|
// internal equality already checks exactly that.
|
|
427
|
-
const
|
|
428
|
-
setSignal(i, () =>
|
|
597
|
+
const f = e.del !== null && e.del.has(o) ? undefined : t[o];
|
|
598
|
+
setSignal(i, () => f);
|
|
429
599
|
}
|
|
430
600
|
}
|
|
431
|
-
const
|
|
432
|
-
if (
|
|
433
|
-
for (const
|
|
601
|
+
const f = e.h;
|
|
602
|
+
if (f !== null) {
|
|
603
|
+
for (const n of Reflect.ownKeys(f)) setSignal(f[n], n in t && !(e.del !== null && e.del.has(n)));
|
|
434
604
|
}
|
|
435
605
|
// Deep-witness (dk): setter writes must notify a deep() subscriber even on
|
|
436
606
|
// keys with no node. O(pb keys) equality only when a witness exists.
|
|
437
607
|
if (e.dk !== null) {
|
|
438
|
-
for (const r of Reflect.ownKeys(t)) {
|
|
608
|
+
if (e.del !== null && e.del.size !== 0) bumpDeep(e); else for (const r of Reflect.ownKeys(t)) {
|
|
439
609
|
const i = t[r];
|
|
440
610
|
const o = n[r];
|
|
441
611
|
if (i !== null && typeof i === "object" ? !targetsEqual(o, i) : !isEqual(o, i)) {
|
|
@@ -445,7 +615,21 @@ function drainFolds() {
|
|
|
445
615
|
}
|
|
446
616
|
}
|
|
447
617
|
if (e.k !== null) {
|
|
448
|
-
|
|
618
|
+
let r;
|
|
619
|
+
if (e.ovl) {
|
|
620
|
+
// Overlay membership: only NEW own keys or deletes can change it.
|
|
621
|
+
r = e.del !== null && e.del.size !== 0;
|
|
622
|
+
if (!r) {
|
|
623
|
+
for (const e of Reflect.ownKeys(t)) {
|
|
624
|
+
if (!hasOwn.call(n, e)) {
|
|
625
|
+
r = true;
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
} else {
|
|
631
|
+
r = Array.isArray(t) && Array.isArray(n) ? arrayStructureChanged(n, t) : membershipChanged(n, t);
|
|
632
|
+
}
|
|
449
633
|
if (r) setSignal(e.k, e => e + 1);
|
|
450
634
|
}
|
|
451
635
|
// Projection backing folds split by channel (two pinned contracts):
|
|
@@ -461,7 +645,9 @@ function drainFolds() {
|
|
|
461
645
|
if (e.fam !== null && e.pb !== null && getWriteOverride()) {
|
|
462
646
|
const n = e.v;
|
|
463
647
|
e.pb = null;
|
|
464
|
-
e.
|
|
648
|
+
e.wk = null;
|
|
649
|
+
// written-keys window closes with the eager fold
|
|
650
|
+
e.v = t;
|
|
465
651
|
e.ch = false;
|
|
466
652
|
if (e.u && e.u.v[e.pk] === n) {
|
|
467
653
|
privatizeCommitted(e.u);
|
|
@@ -650,7 +836,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
|
650
836
|
* / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */ function inOwnerContext() {
|
|
651
837
|
const e = getOwner();
|
|
652
838
|
if (e === null) return false;
|
|
653
|
-
const t = e.
|
|
839
|
+
const t = e.Ct ? e.Ot : e;
|
|
654
840
|
return t != null && !(t.T & CONFIG_CHILDREN_FORBIDDEN);
|
|
655
841
|
}
|
|
656
842
|
|
|
@@ -661,7 +847,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
|
661
847
|
if (t === null) return false;
|
|
662
848
|
for (const e of Reflect.ownKeys(t)) {
|
|
663
849
|
const n = t[e];
|
|
664
|
-
if (n.
|
|
850
|
+
if (n.Pe !== NOT_PENDING && n._e != null && n._e.an !== true) return true;
|
|
665
851
|
}
|
|
666
852
|
return false;
|
|
667
853
|
}
|
|
@@ -710,7 +896,7 @@ function isOwnAccessor(e, t) {
|
|
|
710
896
|
|
|
711
897
|
/** Active optimistic override on an armed node (armed slot idles at
|
|
712
898
|
* NOT_PENDING; undefined = unarmed plain node). */ function hasActiveOverride(e) {
|
|
713
|
-
return e.De !== undefined && e.De !== NOT_PENDING;
|
|
899
|
+
return e.o?.De !== undefined && e.o?.De !== NOT_PENDING;
|
|
714
900
|
}
|
|
715
901
|
|
|
716
902
|
/** Context-aware node view for reads outside tracking: active override >
|
|
@@ -720,7 +906,7 @@ function isOwnAccessor(e, t) {
|
|
|
720
906
|
* reader that forced it (backing commits eagerly; node values fold at flush).
|
|
721
907
|
* FORCE sentinels never surface (they only bump subscribers of accessor
|
|
722
908
|
* keys, which are served by the trap, not the node). */ function nodeValue(e, t) {
|
|
723
|
-
const n = hasActiveOverride(e) ? unwrapOverride(e.De) : e.
|
|
909
|
+
const n = hasActiveOverride(e) ? unwrapOverride(e.o?.De) : e.Pe !== NOT_PENDING && inOwnerContext() ? e.Pe : t;
|
|
724
910
|
return n === FORCE ? t : n;
|
|
725
911
|
}
|
|
726
912
|
|
|
@@ -753,7 +939,7 @@ function isOwnAccessor(e, t) {
|
|
|
753
939
|
// #2951). Once ensurePB runs, the seeded clone carries the view.
|
|
754
940
|
if (e.fam?.opt && e.pb === null) {
|
|
755
941
|
const n = e.n?.[t];
|
|
756
|
-
if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.De);
|
|
942
|
+
if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.De);
|
|
757
943
|
}
|
|
758
944
|
} else {
|
|
759
945
|
if (i !== undefined) {
|
|
@@ -796,6 +982,15 @@ function isOwnAccessor(e, t) {
|
|
|
796
982
|
* read() links the node and throws the firewall's error itself (the node
|
|
797
983
|
* link is what wakes async-memo readers when the landing writes values;
|
|
798
984
|
* the firewall link rides the same read). */ function firewallGate(e) {
|
|
985
|
+
// Own-draft ops are exempt: an async derive's continuation (generator body
|
|
986
|
+
// after an `await`/`yield`) runs OUTSIDE the sync write scope (inDraft is
|
|
987
|
+
// already false), but its draft-proxy traps mark every op with the write
|
|
988
|
+
// override. Those reads are the derive working its own draft (state.push
|
|
989
|
+
// reading .length) — gating them throws NotReadyError back into the derive
|
|
990
|
+
// itself, which the post-await read diagnostic (#2987) then escalates to a
|
|
991
|
+
// reactivity halt. The gate exists for EXTERNAL readers (seed invisibility,
|
|
992
|
+
// proj R23); the derive is the author.
|
|
993
|
+
if (projectionWriteActive || getWriteOverride()) return;
|
|
799
994
|
const t = e.fam?.node;
|
|
800
995
|
if (t != null && t.S & (STATUS_UNINITIALIZED | STATUS_ERROR)) read(t);
|
|
801
996
|
}
|
|
@@ -828,6 +1023,12 @@ const traps = {
|
|
|
828
1023
|
if (pendingCheckActive) witnessAffectsMark(e, t);
|
|
829
1024
|
if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
|
|
830
1025
|
const r = readSource(e);
|
|
1026
|
+
// Overlay delete (#3044): a prototype overlay cannot shadow a delete, so
|
|
1027
|
+
// deleted keys are tracked aside and read as absent in the pending view.
|
|
1028
|
+
if (e.del !== null && r === e.pb && e.del.has(t)) {
|
|
1029
|
+
if (!inDraft(e) && getObserver() !== null) read(getNode(e, t, undefined));
|
|
1030
|
+
return undefined;
|
|
1031
|
+
}
|
|
831
1032
|
// Hot inline case: existing PLAIN node (non-accessor), unchained backing,
|
|
832
1033
|
// tracked read of a present data key — the dbmon/uibench effect re-read
|
|
833
1034
|
// shape. Skips serveDataKey's frame, the FORCE compare (only accessor
|
|
@@ -855,11 +1056,17 @@ const traps = {
|
|
|
855
1056
|
// the node's cached flag; the first TRACKED read (which creates the
|
|
856
1057
|
// node) probes once — untracked node-less reads take the plain path,
|
|
857
1058
|
// where a raw-receiver getter still returns correct committed values.
|
|
1059
|
+
// Tracking suppression is PER-TARGET (inDraft), never global: `writing`
|
|
1060
|
+
// counts every open setter anywhere, and a projection derive runs its
|
|
1061
|
+
// whole body inside one — a global gate silently swallowed EXTERNAL
|
|
1062
|
+
// absent-key/accessor subscriptions for every store read during any
|
|
1063
|
+
// derive, leaving nested projections permanently dependency-less when
|
|
1064
|
+
// their sources hadn't materialized yet (#3037).
|
|
858
1065
|
const i = e.n?.[t];
|
|
859
1066
|
{
|
|
860
|
-
const o = i !== undefined ? i.acc === true : !
|
|
1067
|
+
const o = i !== undefined ? i.acc === true : !inDraft(e) && getObserver() !== null && isOwnAccessor(r, t);
|
|
861
1068
|
if (o) {
|
|
862
|
-
if (!
|
|
1069
|
+
if (!inDraft(e) && getObserver() !== null) read(i ?? getNode(e, t, undefined));
|
|
863
1070
|
const o = Reflect.get(r, t, n);
|
|
864
1071
|
if (e.s) return serveShallow(e, t, o);
|
|
865
1072
|
return isWrappable(o) ? draftServe(e, wrapNext(o, e, t)) : o;
|
|
@@ -868,16 +1075,21 @@ const traps = {
|
|
|
868
1075
|
// Plain-data fast path: no descriptor allocation per read.
|
|
869
1076
|
// Inherited pollution keys are never served (core R30) — checked before
|
|
870
1077
|
// the proto-function branch can leak `constructor`. Interned-string
|
|
871
|
-
// compares beat a Set hash on this per-read path.
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1078
|
+
// compares beat a Set hash on this per-read path. Overlay pending
|
|
1079
|
+
// backings chain to the committed backing, so "own in the view" means
|
|
1080
|
+
// own on either layer (ownInView) — a genuine prototype method is one
|
|
1081
|
+
// that is own on NEITHER.
|
|
1082
|
+
const o = e.ovl && r === e.pb;
|
|
1083
|
+
if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return undefined;
|
|
1084
|
+
let f = r[t];
|
|
1085
|
+
if (f === undefined ? !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t)) : false) {
|
|
875
1086
|
// Inherited: prototype getters/methods run with the proxy receiver.
|
|
876
|
-
|
|
877
|
-
if (typeof
|
|
1087
|
+
f = Reflect.get(r, t, n);
|
|
1088
|
+
if (typeof f === "function") return f;
|
|
878
1089
|
// proto methods untracked
|
|
879
|
-
// Reading a currently-absent own key subscribes to it (R12)
|
|
880
|
-
|
|
1090
|
+
// Reading a currently-absent own key subscribes to it (R12) — for any
|
|
1091
|
+
// target OUTSIDE its own draft scope, even mid-setter (#3037, above).
|
|
1092
|
+
if (f === undefined && !inDraft(e)) {
|
|
881
1093
|
if (getObserver() !== null) read(getNode(e, t, undefined));
|
|
882
1094
|
const n = e.n?.[t];
|
|
883
1095
|
if (n) {
|
|
@@ -885,16 +1097,16 @@ const traps = {
|
|
|
885
1097
|
if (e.s) return serveShallow(e, t, r);
|
|
886
1098
|
return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
|
|
887
1099
|
}
|
|
888
|
-
} else if (
|
|
1100
|
+
} else if (f === undefined && inDraft(e) && e.fam?.opt && e.pb === null) {
|
|
889
1101
|
const n = e.n?.[t];
|
|
890
|
-
if (n !== undefined && hasActiveOverride(n))
|
|
1102
|
+
if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.De);
|
|
891
1103
|
}
|
|
892
|
-
if (e.s) return serveShallow(e, t,
|
|
893
|
-
return isWrappable(
|
|
1104
|
+
if (e.s) return serveShallow(e, t, f);
|
|
1105
|
+
return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
|
|
894
1106
|
}
|
|
895
|
-
if (typeof
|
|
1107
|
+
if (typeof f === "function" && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return f;
|
|
896
1108
|
// proto method
|
|
897
|
-
return serveDataKey(e, t,
|
|
1109
|
+
return serveDataKey(e, t, f, r, i);
|
|
898
1110
|
},
|
|
899
1111
|
has(e, t) {
|
|
900
1112
|
if (t === $TARGET || t === $PROXY || t === $TRACK) return true;
|
|
@@ -902,6 +1114,8 @@ const traps = {
|
|
|
902
1114
|
if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
|
|
903
1115
|
const n = readSource(e);
|
|
904
1116
|
let r = t in n;
|
|
1117
|
+
// Overlay deletes read as absent in the pending view (#3044).
|
|
1118
|
+
if (r && e.del !== null && n === e.pb && e.del.has(t)) r = false;
|
|
905
1119
|
if (!inDraft(e)) {
|
|
906
1120
|
if (getObserver() !== null) {
|
|
907
1121
|
const n = getHasNode(e, t, r);
|
|
@@ -909,11 +1123,11 @@ const traps = {
|
|
|
909
1123
|
if (hasActiveOverride(n)) r = !!i;
|
|
910
1124
|
} else {
|
|
911
1125
|
const n = e.h?.[t];
|
|
912
|
-
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.De);
|
|
1126
|
+
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.De);
|
|
913
1127
|
}
|
|
914
1128
|
} else if (e.fam?.opt && e.pb === null) {
|
|
915
1129
|
const n = e.h?.[t];
|
|
916
|
-
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.De);
|
|
1130
|
+
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.De);
|
|
917
1131
|
}
|
|
918
1132
|
return r;
|
|
919
1133
|
},
|
|
@@ -921,31 +1135,49 @@ const traps = {
|
|
|
921
1135
|
if (pendingCheckActive) witnessAffectsMark(e);
|
|
922
1136
|
if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
|
|
923
1137
|
if (!inDraft(e) && getObserver() !== null) read(getKeySetNode(e));
|
|
924
|
-
const t =
|
|
1138
|
+
const t = readSource(e);
|
|
1139
|
+
let n;
|
|
1140
|
+
if (e.ovl && t === e.pb) {
|
|
1141
|
+
// Overlay merge (#3044): committed keys in their order, then this
|
|
1142
|
+
// batch's NEW keys, minus deletes.
|
|
1143
|
+
n = Reflect.ownKeys(e.v);
|
|
1144
|
+
const r = e.del;
|
|
1145
|
+
if (r !== null && r.size !== 0) n = n.filter(e => !r.has(e));
|
|
1146
|
+
for (const r of Reflect.ownKeys(t)) {
|
|
1147
|
+
if (!hasOwn.call(e.v, r)) n.push(r);
|
|
1148
|
+
}
|
|
1149
|
+
} else n = Reflect.ownKeys(t);
|
|
925
1150
|
// Optimistic membership overlay: presence-node overrides add/remove keys
|
|
926
1151
|
// (per-transaction lifecycle rides the nodes — §6, FINDING-2's fix).
|
|
927
1152
|
// Draft reads before the first write overlay too (pb, once created, is
|
|
928
1153
|
// seeded with the view).
|
|
929
1154
|
if (e.fam?.opt && e.h !== null && (!inDraft(e) || e.pb === null)) {
|
|
930
|
-
let
|
|
1155
|
+
let t = null;
|
|
931
1156
|
for (const r of Reflect.ownKeys(e.h)) {
|
|
932
1157
|
const i = e.h[r];
|
|
933
1158
|
if (!hasActiveOverride(i)) continue;
|
|
934
|
-
|
|
935
|
-
if (unwrapOverride(i.De))
|
|
1159
|
+
t ??= new Set(n);
|
|
1160
|
+
if (unwrapOverride(i.o?.De)) t.add(r); else t.delete(r);
|
|
936
1161
|
}
|
|
937
|
-
if (
|
|
1162
|
+
if (t !== null) return [ ...t ];
|
|
938
1163
|
}
|
|
939
|
-
return
|
|
1164
|
+
return n;
|
|
940
1165
|
},
|
|
941
1166
|
getOwnPropertyDescriptor(e, t) {
|
|
942
|
-
const n =
|
|
1167
|
+
const n = readSource(e);
|
|
1168
|
+
let r = Object.getOwnPropertyDescriptor(n, t);
|
|
1169
|
+
// Overlay (#3044): unwritten keys live on the committed backing;
|
|
1170
|
+
// deleted keys are absent from the pending view.
|
|
1171
|
+
if (e.ovl && n === e.pb) {
|
|
1172
|
+
if (e.del !== null && e.del.has(t)) return undefined;
|
|
1173
|
+
if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, t);
|
|
1174
|
+
}
|
|
943
1175
|
if (e.fam?.opt && !inDraft(e)) {
|
|
944
|
-
const
|
|
945
|
-
if (
|
|
946
|
-
if (!unwrapOverride(
|
|
1176
|
+
const n = e.h?.[t];
|
|
1177
|
+
if (n !== undefined && hasActiveOverride(n)) {
|
|
1178
|
+
if (!unwrapOverride(n.o?.De)) return undefined;
|
|
947
1179
|
// opt delete
|
|
948
|
-
if (
|
|
1180
|
+
if (r === undefined) {
|
|
949
1181
|
const n = e.n?.[t];
|
|
950
1182
|
return {
|
|
951
1183
|
value: n !== undefined ? nodeValue(n, undefined) : undefined,
|
|
@@ -956,12 +1188,12 @@ const traps = {
|
|
|
956
1188
|
}
|
|
957
1189
|
}
|
|
958
1190
|
}
|
|
959
|
-
if (
|
|
1191
|
+
if (r === undefined) return undefined;
|
|
960
1192
|
// Array targets carry a real non-configurable `length` the proxy
|
|
961
1193
|
// invariant forces us to report faithfully; everything else reports
|
|
962
1194
|
// configurable via target indirection (core R51).
|
|
963
|
-
if (!(t === "length" && Array.isArray(e)))
|
|
964
|
-
return
|
|
1195
|
+
if (!(t === "length" && Array.isArray(e))) r.configurable = true;
|
|
1196
|
+
return r;
|
|
965
1197
|
},
|
|
966
1198
|
set(e, t, n) {
|
|
967
1199
|
// Writes require the target's draft scope OR the projection write
|
|
@@ -972,27 +1204,52 @@ const traps = {
|
|
|
972
1204
|
if (!r && !i) return true;
|
|
973
1205
|
if (t === "__proto__") return true;
|
|
974
1206
|
// pollution guard (core R30)
|
|
975
|
-
|
|
1207
|
+
// Unwrap BEFORE ensurePB: unwrapValue materializes a self-referencing
|
|
1208
|
+
// draft's overlay (replacing target.pb), so a pb local captured earlier
|
|
1209
|
+
// would be the abandoned overlay and the write would vanish.
|
|
1210
|
+
// Shallow slots store what was written VERBATIM — another store's proxy
|
|
1211
|
+
// passes through by reference (#2932; markRawOne skips proxies), while
|
|
1212
|
+
// deep stores unwrap to raw backings.
|
|
1213
|
+
const o = e.s ? n : unwrapValue(n);
|
|
1214
|
+
const f = ensurePB(e);
|
|
976
1215
|
pendingNotify.add(e);
|
|
1216
|
+
// Array length writes implicitly delete indices — the written-keys bound
|
|
1217
|
+
// can't see them, so poison to the full scan for this batch. Index
|
|
1218
|
+
// writes implicitly GROW length, so arrays always record it alongside.
|
|
1219
|
+
if (Array.isArray(f)) {
|
|
1220
|
+
if (t === "length") e.wk = WK_ALL; else if (e.wk !== WK_ALL) {
|
|
1221
|
+
const n = e.wk ??= new Set;
|
|
1222
|
+
n.add(t);
|
|
1223
|
+
n.add("length");
|
|
1224
|
+
}
|
|
1225
|
+
} else if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
|
|
977
1226
|
// Own data keys literally named "prototype"/"constructor" land as data —
|
|
978
1227
|
// defineProperty sidesteps a proto-chain setter named the same.
|
|
979
1228
|
if (UNSAFE_KEYS.has(t)) {
|
|
980
|
-
Object.defineProperty(
|
|
981
|
-
value:
|
|
1229
|
+
Object.defineProperty(f, t, {
|
|
1230
|
+
value: o,
|
|
982
1231
|
writable: true,
|
|
983
1232
|
enumerable: true,
|
|
984
1233
|
configurable: true
|
|
985
1234
|
});
|
|
1235
|
+
if (e.del !== null) e.del.delete(t);
|
|
986
1236
|
return true;
|
|
987
1237
|
}
|
|
988
|
-
//
|
|
989
|
-
//
|
|
990
|
-
//
|
|
991
|
-
|
|
992
|
-
|
|
1238
|
+
// Overlay first-write DEFINES the own key: assignment through the proto
|
|
1239
|
+
// chain would reject on a non-writable committed property (the clone
|
|
1240
|
+
// path normalized descriptors for exactly this — R51 parity).
|
|
1241
|
+
if (e.ovl && !hasOwn.call(f, t)) {
|
|
1242
|
+
Object.defineProperty(f, t, {
|
|
1243
|
+
value: o,
|
|
1244
|
+
writable: true,
|
|
1245
|
+
enumerable: true,
|
|
1246
|
+
configurable: true
|
|
1247
|
+
});
|
|
1248
|
+
} else f[t] = o;
|
|
1249
|
+
if (e.del !== null) e.del.delete(t);
|
|
993
1250
|
// Shallow ingest: written records are sticky raw-marked (one entity is
|
|
994
1251
|
// never both deep-wrapped and raw — R41/#2932, shared invariant).
|
|
995
|
-
if (e.s &&
|
|
1252
|
+
if (e.s && o !== null && typeof o === "object") markRawOne(o);
|
|
996
1253
|
// Override-mode (post-await draft) writes have no setter exit — notify
|
|
997
1254
|
// per-op (setSignal equality-gates repeats).
|
|
998
1255
|
if (i) notifyWrites(e);
|
|
@@ -1004,13 +1261,16 @@ const traps = {
|
|
|
1004
1261
|
if (!r && !i) return true;
|
|
1005
1262
|
if (t === "__proto__") return true;
|
|
1006
1263
|
if (n.get || n.set) e.a = true;
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
if ("value" in n) n = {
|
|
1264
|
+
// Unwrap before ensurePB (see the set trap: self-reference materializes).
|
|
1265
|
+
if ("value" in n) n = {
|
|
1010
1266
|
...n,
|
|
1011
1267
|
value: unwrapValue(n.value)
|
|
1012
1268
|
};
|
|
1269
|
+
const o = ensurePB(e);
|
|
1270
|
+
pendingNotify.add(e);
|
|
1271
|
+
if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
|
|
1013
1272
|
Object.defineProperty(o, t, n);
|
|
1273
|
+
if (e.del !== null) e.del.delete(t);
|
|
1014
1274
|
if (i) notifyWrites(e);
|
|
1015
1275
|
return true;
|
|
1016
1276
|
},
|
|
@@ -1020,7 +1280,11 @@ const traps = {
|
|
|
1020
1280
|
if (!n && !r) return true;
|
|
1021
1281
|
const i = ensurePB(e);
|
|
1022
1282
|
pendingNotify.add(e);
|
|
1283
|
+
if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
|
|
1023
1284
|
delete i[t];
|
|
1285
|
+
// A prototype overlay cannot shadow a delete of a committed key —
|
|
1286
|
+
// record it aside (#3044); reads/has/ownKeys/commit consult the set.
|
|
1287
|
+
if (e.ovl && hasOwn.call(e.v, t)) (e.del ??= new Set).add(t);
|
|
1024
1288
|
if (r) notifyWrites(e);
|
|
1025
1289
|
return true;
|
|
1026
1290
|
}
|
|
@@ -1155,6 +1419,9 @@ function snapshotWalk(e, t, n) {
|
|
|
1155
1419
|
if (e === undefined) break;
|
|
1156
1420
|
if (e.fam !== null) n = e.fam;
|
|
1157
1421
|
if (e.fam?.opt === true) (i ??= []).push(e);
|
|
1422
|
+
// Snapshot runs mid-flush (tracked memos execute before commit), so a
|
|
1423
|
+
// pending prototype overlay must present as a REAL merged container.
|
|
1424
|
+
if (e.ovl) materializePB(e);
|
|
1158
1425
|
const t = e.pb ?? e.v;
|
|
1159
1426
|
if (t === r) break;
|
|
1160
1427
|
r = t;
|
|
@@ -1199,10 +1466,10 @@ function snapshotWalk(e, t, n) {
|
|
|
1199
1466
|
continue;
|
|
1200
1467
|
}
|
|
1201
1468
|
const u = f.value;
|
|
1202
|
-
const
|
|
1203
|
-
if (f.enumerable && f.writable && f.configurable) i[o] =
|
|
1469
|
+
const l = u !== null && typeof u === "object" ? snapshotWalk(u, t, n) : u;
|
|
1470
|
+
if (f.enumerable && f.writable && f.configurable) i[o] = l; else Object.defineProperty(i, o, {
|
|
1204
1471
|
...f,
|
|
1205
|
-
value:
|
|
1472
|
+
value: l
|
|
1206
1473
|
});
|
|
1207
1474
|
}
|
|
1208
1475
|
if (e && i.length !== r.length) i.length = r.length;
|
|
@@ -1229,4 +1496,4 @@ function snapshotWalk(e, t, n) {
|
|
|
1229
1496
|
return f ?? r;
|
|
1230
1497
|
}
|
|
1231
1498
|
|
|
1232
|
-
export { adoptPB, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, runAuthoritative, snapshotNext, storeSetterNext, targetsEqual, unwrapValue, wrapNext };
|
|
1499
|
+
export { adoptPB, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, materializePB, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, runAuthoritative, snapshotNext, storeSetterNext, targetsEqual, unwrapValue, wrapNext };
|