@solidjs/signals 2.0.0-rc.3 → 2.0.0-rc.5
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 +2494 -271
- package/dist/node.cjs +3691 -1595
- package/dist/prod/affects.js +13 -12
- package/dist/prod/boundaries.js +43 -35
- package/dist/prod/core/action.js +3 -3
- package/dist/prod/core/async.js +137 -106
- package/dist/prod/core/constants.js +55 -1
- package/dist/prod/core/core.js +354 -247
- package/dist/prod/core/effect.js +47 -50
- package/dist/prod/core/error.js +13 -1
- package/dist/prod/core/external.js +4 -4
- package/dist/prod/core/graph.js +88 -52
- package/dist/prod/core/heap.js +38 -38
- package/dist/prod/core/lanes.js +34 -34
- package/dist/prod/core/optimistic.js +61 -58
- package/dist/prod/core/owner.js +38 -38
- package/dist/prod/core/scheduler.js +401 -174
- package/dist/prod/core/verdict.js +132 -65
- package/dist/prod/index.js +7 -3
- package/dist/prod/map.js +106 -106
- package/dist/prod/signals.js +253 -25
- package/dist/prod/store/index.js +2 -0
- package/dist/prod/store/next/optimistic.js +314 -121
- package/dist/prod/store/next/patch-hooks.js +13 -0
- package/dist/prod/store/next/patch.js +614 -0
- package/dist/prod/store/next/projection.js +23 -19
- package/dist/prod/store/next/reconcile.js +307 -120
- package/dist/prod/store/next/store.js +440 -117
- package/dist/prod/store/next/target.js +13 -4
- package/dist/prod/store/store.js +5 -5
- package/dist/types/core/async.d.ts +2 -0
- package/dist/types/core/attribution.d.ts +9 -4
- package/dist/types/core/constants.d.ts +54 -0
- package/dist/types/core/core.d.ts +34 -21
- package/dist/types/core/dev.d.ts +8 -0
- package/dist/types/core/error.d.ts +9 -0
- package/dist/types/core/graph.d.ts +22 -0
- package/dist/types/core/index.d.ts +2 -2
- package/dist/types/core/scheduler.d.ts +46 -0
- package/dist/types/core/types.d.ts +12 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/signals.d.ts +108 -0
- package/dist/types/store/index.d.ts +2 -0
- package/dist/types/store/next/optimistic.d.ts +13 -10
- package/dist/types/store/next/patch-hooks.d.ts +41 -0
- package/dist/types/store/next/patch.d.ts +91 -0
- package/dist/types/store/next/projection.d.ts +1 -1
- package/dist/types/store/next/reconcile.d.ts +14 -0
- package/dist/types/store/next/store.d.ts +52 -2
- package/dist/types/store/next/target.d.ts +73 -8
- package/dist/types-cjs/core/async.d.cts +2 -0
- package/dist/types-cjs/core/attribution.d.cts +9 -4
- package/dist/types-cjs/core/constants.d.cts +54 -0
- package/dist/types-cjs/core/core.d.cts +34 -21
- package/dist/types-cjs/core/dev.d.cts +8 -0
- package/dist/types-cjs/core/error.d.cts +9 -0
- package/dist/types-cjs/core/graph.d.cts +22 -0
- package/dist/types-cjs/core/index.d.cts +2 -2
- package/dist/types-cjs/core/scheduler.d.cts +46 -0
- package/dist/types-cjs/core/types.d.cts +12 -0
- package/dist/types-cjs/index.d.cts +3 -3
- package/dist/types-cjs/signals.d.cts +108 -0
- package/dist/types-cjs/store/index.d.cts +2 -0
- package/dist/types-cjs/store/next/optimistic.d.cts +13 -10
- package/dist/types-cjs/store/next/patch-hooks.d.cts +41 -0
- package/dist/types-cjs/store/next/patch.d.cts +91 -0
- package/dist/types-cjs/store/next/projection.d.cts +1 -1
- package/dist/types-cjs/store/next/reconcile.d.cts +14 -0
- package/dist/types-cjs/store/next/store.d.cts +52 -2
- package/dist/types-cjs/store/next/target.d.cts +73 -8
- package/package.json +2 -2
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { unwrapOverride, $REFRESH, CONFIG_OWNED_WRITE, NOT_PENDING, CONFIG_OPTIMISTIC, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN } from "../../core/constants.js";
|
|
1
|
+
import { unwrapOverride, $REFRESH, CONFIG_OWNED_WRITE, NOT_PENDING, 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, readNodeFast, READ_SLOW, signal, ext } from "../../core/core.js";
|
|
3
|
+
import { setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, ext, setLatestReadActive, prepareComputed, context } from "../../core/core.js";
|
|
4
4
|
|
|
5
|
-
import { projectionWriteActive, setProjectionWriteActive, schedule, setStoreCommitHook } from "../../core/scheduler.js";
|
|
5
|
+
import { projectionWriteActive, setProjectionWriteActive, activeTransition, schedule, currentTransition, setStoreCommitHook } from "../../core/scheduler.js";
|
|
6
6
|
|
|
7
7
|
import { getObserver, getOwner } from "../../core/owner.js";
|
|
8
8
|
|
|
9
9
|
import { $TARGET, isWrappable, markRawIngest, $PROXY, getWriteOverride, markRawOne, witnessAffectsMark, $TRACK, affectsScopesLive, inheritAffectsMarks, $AFFECTS, rawValuesUsed, isRawValue, setNextAffectsNodeResolver } from "../store.js";
|
|
10
10
|
|
|
11
|
-
import { storeNextLookup, optHooks, ownedRaw, devAssertNeverUserMutation } from "./target.js";
|
|
11
|
+
import { storeNextLookup, optHooks, ownedRaw, markDescendants, devAssertNeverUserMutation } from "./target.js";
|
|
12
|
+
|
|
13
|
+
import { patchHooks, rowHooks } from "./patch-hooks.js";
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Store rewrite — increment 2: plain deep stores with pending-backing writes.
|
|
@@ -38,8 +40,13 @@ import { storeNextLookup, optHooks, ownedRaw, devAssertNeverUserMutation } from
|
|
|
38
40
|
* headroom for future fields. The prototype is reset to `Object.prototype`
|
|
39
41
|
* so proxy-forwarded semantics (getPrototypeOf, constructor) are exactly a
|
|
40
42
|
* plain object's. Array targets keep the bare-`[]` path — they must carry
|
|
41
|
-
* the array exotic class for `Array.isArray(proxy)
|
|
42
|
-
*
|
|
43
|
+
* the array exotic class for `Array.isArray(proxy)`.
|
|
44
|
+
*
|
|
45
|
+
* ARRAY SHAPE RULE: arrays normalize their named properties to dictionary
|
|
46
|
+
* mode as the count grows (V8 13.x: counts ≡ 0 mod 3 from 18 up), so the
|
|
47
|
+
* target's named field count is capped at 20 — write-side patch-channel
|
|
48
|
+
* state lives inside the single `pc` extension (see target.ts), never as
|
|
49
|
+
* new named fields here. */
|
|
43
50
|
function TargetShape() {
|
|
44
51
|
this.v = undefined;
|
|
45
52
|
this.ch = undefined;
|
|
@@ -60,11 +67,24 @@ function TargetShape() {
|
|
|
60
67
|
this.s = undefined;
|
|
61
68
|
this.ovl = undefined;
|
|
62
69
|
this.del = undefined;
|
|
63
|
-
this.
|
|
70
|
+
this.pc = undefined;
|
|
71
|
+
this.hv = undefined;
|
|
72
|
+
this.ht = undefined;
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
TargetShape.prototype = Object.prototype;
|
|
67
76
|
|
|
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
|
+
|
|
68
88
|
function createTarget(e, t, n, r = t?.fam ?? null) {
|
|
69
89
|
// The proxy target carries the array exotic class when the value is an
|
|
70
90
|
// array, so Array.isArray(proxy) is true; the fields live on it directly.
|
|
@@ -81,6 +101,7 @@ function createTarget(e, t, n, r = t?.fam ?? null) {
|
|
|
81
101
|
i.h = null;
|
|
82
102
|
i.k = null;
|
|
83
103
|
i.dk = null;
|
|
104
|
+
i.pc = null;
|
|
84
105
|
i.u = t;
|
|
85
106
|
i.pk = n;
|
|
86
107
|
i.px = null;
|
|
@@ -93,7 +114,8 @@ function createTarget(e, t, n, r = t?.fam ?? null) {
|
|
|
93
114
|
i.s = false;
|
|
94
115
|
i.ovl = false;
|
|
95
116
|
i.del = null;
|
|
96
|
-
i.
|
|
117
|
+
i.hv = null;
|
|
118
|
+
i.ht = null;
|
|
97
119
|
i.px = new Proxy(i, traps);
|
|
98
120
|
// Legacy interop: shared machinery (affects walks, wrap dedupe) reads the
|
|
99
121
|
// proxy off looked-up targets as a field.
|
|
@@ -171,7 +193,7 @@ function getNode(e, t, n) {
|
|
|
171
193
|
// Optimistic families: arm the override slot — setSignal routes armed
|
|
172
194
|
// nodes through the core engine (lanes, ownership, reverts all native).
|
|
173
195
|
if (e.fam?.opt) {
|
|
174
|
-
ext(o).
|
|
196
|
+
ext(o).Oe = NOT_PENDING;
|
|
175
197
|
o.T |= CONFIG_OPTIMISTIC;
|
|
176
198
|
}
|
|
177
199
|
// A node born inside a live mark's identity scope inherits the mark
|
|
@@ -204,7 +226,7 @@ function getHasNode(e, t, n) {
|
|
|
204
226
|
}, e.fam?.node ?? undefined);
|
|
205
227
|
o.T |= CONFIG_OWNED_WRITE;
|
|
206
228
|
if (e.fam?.opt) {
|
|
207
|
-
ext(o).
|
|
229
|
+
ext(o).Oe = NOT_PENDING;
|
|
208
230
|
o.T |= CONFIG_OPTIMISTIC;
|
|
209
231
|
}
|
|
210
232
|
if (affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
|
|
@@ -225,7 +247,7 @@ function getKeySetNode(e) {
|
|
|
225
247
|
}, e.fam?.node ?? undefined);
|
|
226
248
|
n.T |= CONFIG_OWNED_WRITE;
|
|
227
249
|
if (e.fam?.opt) {
|
|
228
|
-
ext(n).
|
|
250
|
+
ext(n).Oe = NOT_PENDING;
|
|
229
251
|
n.T |= CONFIG_OPTIMISTIC;
|
|
230
252
|
}
|
|
231
253
|
e.k = t;
|
|
@@ -245,7 +267,7 @@ function getDeepNode(e) {
|
|
|
245
267
|
}, e.fam?.node ?? undefined);
|
|
246
268
|
n.T |= CONFIG_OWNED_WRITE;
|
|
247
269
|
if (e.fam?.opt) {
|
|
248
|
-
ext(n).
|
|
270
|
+
ext(n).Oe = NOT_PENDING;
|
|
249
271
|
n.T |= CONFIG_OPTIMISTIC;
|
|
250
272
|
}
|
|
251
273
|
if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
|
|
@@ -260,14 +282,6 @@ function getDeepNode(e) {
|
|
|
260
282
|
if (e.dk !== null) setSignal(e.dk, 1);
|
|
261
283
|
}
|
|
262
284
|
|
|
263
|
-
function markDescendants(e) {
|
|
264
|
-
let t = e;
|
|
265
|
-
while (t && !t.d) {
|
|
266
|
-
t.d = true;
|
|
267
|
-
t = t.u;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
285
|
// ---------------------------------------------------------------------------
|
|
272
286
|
// pending backing + fold (the single mutation point)
|
|
273
287
|
/** target → committed backing at batch start (the fold diff's old side). */ const foldOlds = new Map;
|
|
@@ -291,6 +305,12 @@ function cloneRaw(e, t) {
|
|
|
291
305
|
return Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
|
|
292
306
|
}
|
|
293
307
|
|
|
308
|
+
/** Scanned plainness for patch admission (patchableRaw): runs the one-time
|
|
309
|
+
* accessor scan if it hasn't happened yet — the sticky `a` flag alone is not
|
|
310
|
+
* trustworthy before a scan (it starts false and is discovered lazily). */ function targetIsPlain(e) {
|
|
311
|
+
return e.sc ? !e.a : scanAccessorsOnce(e);
|
|
312
|
+
}
|
|
313
|
+
|
|
294
314
|
/** One-time own-accessor scan (Annex-B probes, no descriptor allocation);
|
|
295
315
|
* returns true when the container is plain data (overlay-safe). */ function scanAccessorsOnce(e) {
|
|
296
316
|
const t = e.v;
|
|
@@ -331,6 +351,21 @@ function cloneRaw(e, t) {
|
|
|
331
351
|
|
|
332
352
|
function ensurePB(e) {
|
|
333
353
|
let t = e.pb;
|
|
354
|
+
// Truth-staged backing hand-off (#3164 fold): a TENTATIVE draft opening on
|
|
355
|
+
// a target whose pending backing is truth-staged (a landing folded into a
|
|
356
|
+
// retaining transaction — it carries a foldBatches stamp) must not share
|
|
357
|
+
// the container. Tentative writes would pollute staged truth, and the
|
|
358
|
+
// tentative discard (notifyOptimisticWrites nulls pb) would destroy the
|
|
359
|
+
// landing. Park the staged backing and open a fresh draft seeded from the
|
|
360
|
+
// optimistic view below; the tentative discard restores it. The
|
|
361
|
+
// tentativePBs guard scopes this to draft OPEN: the draft's own backing
|
|
362
|
+
// (foldBatches-stamped by its first write when an action's transition is
|
|
363
|
+
// ambient) must not be parked by its own later writes.
|
|
364
|
+
if (t !== null && !tentativePBs.has(t) && e.fam?.opt === true && !projectionWriteActive && !getWriteOverride() && foldBatches.has(e)) {
|
|
365
|
+
stagedTruthPB.set(e, t);
|
|
366
|
+
t = e.pb = null;
|
|
367
|
+
}
|
|
368
|
+
if (activeTransition !== null) foldBatches.set(e, activeTransition);
|
|
334
369
|
if (t === null) {
|
|
335
370
|
// Prototype-chain overlay (#3044): plain-data non-array containers
|
|
336
371
|
// outside projection/optimistic families open drafts in O(1) — own keys
|
|
@@ -348,18 +383,19 @@ function ensurePB(e) {
|
|
|
348
383
|
// seed from committed truth — seeding overrides there would fold a lane
|
|
349
384
|
// value into the committed home ("authority wins at reveal" would break).
|
|
350
385
|
if (e.fam?.opt && !projectionWriteActive && !getWriteOverride()) {
|
|
386
|
+
tentativePBs.add(t);
|
|
351
387
|
const n = e.n;
|
|
352
388
|
if (n !== null) {
|
|
353
389
|
for (const e of Reflect.ownKeys(n)) {
|
|
354
390
|
const r = n[e];
|
|
355
|
-
if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.
|
|
391
|
+
if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.Oe);
|
|
356
392
|
}
|
|
357
393
|
}
|
|
358
394
|
const r = e.h;
|
|
359
395
|
if (r !== null) {
|
|
360
396
|
for (const e of Reflect.ownKeys(r)) {
|
|
361
397
|
const n = r[e];
|
|
362
|
-
if (hasActiveOverride(n) && !unwrapOverride(n.o?.
|
|
398
|
+
if (hasActiveOverride(n) && !unwrapOverride(n.o?.Oe)) delete t[e];
|
|
363
399
|
}
|
|
364
400
|
}
|
|
365
401
|
}
|
|
@@ -370,6 +406,25 @@ function ensurePB(e) {
|
|
|
370
406
|
return t;
|
|
371
407
|
}
|
|
372
408
|
|
|
409
|
+
/** Sentinel holder for `t.ht`: a latest()-pull staged this adoption outside
|
|
410
|
+
* any transition — the hold lasts until the fold commit (drainFolds). */ const PLAIN_HOLD = Symbol("plainHold");
|
|
411
|
+
|
|
412
|
+
/** True while a latest() read is pulling the projection computed up to date
|
|
413
|
+
* (see the get trap): adoptions landing during the pull are speculative
|
|
414
|
+
* against the un-flushed batch and stage a held view. (Not injectable — the
|
|
415
|
+
* derived createStore overload retains projection machinery in every store
|
|
416
|
+
* bundle, see treeshake.test.ts.) */ let latestPullActive = false;
|
|
417
|
+
|
|
418
|
+
/** Resolve the held committed view (#3074): answers the masked old backing
|
|
419
|
+
* while the hold is live, and lazily clears a hold whose transition has
|
|
420
|
+
* committed (transitions merge — resolve through currentTransition, same as
|
|
421
|
+
* foldHeld's node stamps). */ function heldMaskView(e) {
|
|
422
|
+
const t = e.ht;
|
|
423
|
+
if (t === null) return null;
|
|
424
|
+
if (t !== PLAIN_HOLD && currentTransition(t)?.sn === true) return e.ht = e.hv = null;
|
|
425
|
+
return e.hv;
|
|
426
|
+
}
|
|
427
|
+
|
|
373
428
|
/**
|
|
374
429
|
* Adoption (2026-08-16c): the incoming object becomes the committed backing
|
|
375
430
|
* IMMEDIATELY — reconcile is eagerly visible to every reader (shipped
|
|
@@ -385,6 +440,20 @@ function ensurePB(e) {
|
|
|
385
440
|
queueFold(e);
|
|
386
441
|
// records the pre-batch old before we swap
|
|
387
442
|
e.adopted = true;
|
|
443
|
+
// #3074/#3075: a projection recompute deriving from uncommitted inputs
|
|
444
|
+
// swaps the backing SPECULATIVELY — committed-visibility readers must
|
|
445
|
+
// keep the pre-hold view until the hold resolves (a source held by a
|
|
446
|
+
// live transition, or a latest()-pull ahead of the flush). Post-await
|
|
447
|
+
// landings (write-override) stay immediately visible — landed truth —
|
|
448
|
+
// and clear any hold; optimistic families ride the lane machinery.
|
|
449
|
+
if (e.fam?.opt !== true) {
|
|
450
|
+
if (getWriteOverride()) {
|
|
451
|
+
e.ht = e.hv = null;
|
|
452
|
+
} else if (activeTransition !== null || latestPullActive) {
|
|
453
|
+
if (heldMaskView(e) === null) e.hv = e.v;
|
|
454
|
+
e.ht = activeTransition ?? PLAIN_HOLD;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
388
457
|
}
|
|
389
458
|
e.pb = null;
|
|
390
459
|
// Overlay and accessor-scan state describe the OUTGOING backing — a
|
|
@@ -396,28 +465,58 @@ function ensurePB(e) {
|
|
|
396
465
|
// draft rescans once (#3044 audit follow-up).
|
|
397
466
|
e.ovl = false;
|
|
398
467
|
e.del = null;
|
|
399
|
-
e.
|
|
400
|
-
// adoption supersedes any staged trap writes
|
|
401
|
-
e.sc = false;
|
|
468
|
+
e.sc = false;
|
|
402
469
|
e.a = false;
|
|
403
|
-
e.
|
|
470
|
+
if (e.pc !== null) e.pc.wk = null;
|
|
471
|
+
// adoption supersedes staged trap writes
|
|
472
|
+
e.v = t;
|
|
404
473
|
e.ch = t[$TARGET] !== undefined;
|
|
405
474
|
(e.fam?.map ?? storeNextLookup).set(t, e);
|
|
406
475
|
}
|
|
407
476
|
|
|
477
|
+
/** Sentinel for `t.wk`: the written-keys bound is unusable this batch (an
|
|
478
|
+
* array length write implicitly deleted indices) — consumers full-scan. */ const WK_ALL = new Set;
|
|
479
|
+
|
|
480
|
+
const plainProto = e => {
|
|
481
|
+
const t = Object.getPrototypeOf(e);
|
|
482
|
+
return t === Object.prototype || t === Array.prototype || t === null;
|
|
483
|
+
};
|
|
484
|
+
|
|
408
485
|
function queueFold(e) {
|
|
409
486
|
if (foldOlds.has(e)) return;
|
|
410
|
-
if (
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
487
|
+
if (!hookInstalled) {
|
|
488
|
+
hookInstalled = true;
|
|
489
|
+
setStoreCommitHook(drainFolds);
|
|
490
|
+
}
|
|
491
|
+
// Always arm — "map non-empty ⇒ drain scheduled" is NOT an invariant: a
|
|
492
|
+
// held re-queue, or an incomplete-transition flush (which skips
|
|
493
|
+
// commitPendingNodes entirely), leaves entries behind after `scheduled`
|
|
494
|
+
// was consumed. A size-gated arm then strands every LATER fold — queued
|
|
495
|
+
// silently, never drained, committed base frozen at stale state while its
|
|
496
|
+
// nodes commit (#3089). schedule() early-returns when already armed.
|
|
415
497
|
schedule();
|
|
416
|
-
// once per batch — drain clears the map
|
|
417
|
-
}
|
|
418
498
|
foldOlds.set(e, e.v);
|
|
419
499
|
}
|
|
420
500
|
|
|
501
|
+
/** Fold write-attribution (#3089): a draft written while a transition is
|
|
502
|
+
* active belongs to that transition — its fold must not commit before the
|
|
503
|
+
* transition settles. Observed keys already defer through the held check in
|
|
504
|
+
* drainFolds (their nodes carry _pendingValue); this write-time stamp is the
|
|
505
|
+
* equivalent hold for UNOBSERVED keys, which have no node to consult.
|
|
506
|
+
* Refreshed on every write; resolved through currentTransition at drain
|
|
507
|
+
* (transitions merge — same rule as heldMaskView). */ const foldBatches = new WeakMap;
|
|
508
|
+
|
|
509
|
+
/** Parked truth-staged pending backings (#3164 fold): a tentative draft that
|
|
510
|
+
* opens while a folded landing's backing is live moves the staged container
|
|
511
|
+
* here (see ensurePB); the tentative discard in notifyOptimisticWrites
|
|
512
|
+
* restores it in place of the usual null. */ const stagedTruthPB = new WeakMap;
|
|
513
|
+
|
|
514
|
+
/** Backings opened by TENTATIVE drafts (optimistic user setters): ensurePB's
|
|
515
|
+
* truth-park must not fire against the draft's own container on its second
|
|
516
|
+
* and later writes (the first write stamps foldBatches whenever an action's
|
|
517
|
+
* transition is ambient). Entries die with their draft — tentative backings
|
|
518
|
+
* are consumed at setter exit. */ const tentativePBs = new WeakSet;
|
|
519
|
+
|
|
421
520
|
/** Committed-time privatization for parent-chain slot updates (path copying). */ function privatizeCommitted(e) {
|
|
422
521
|
if (ownedRaw.has(e.v)) return;
|
|
423
522
|
const t = cloneRaw(e.v, e);
|
|
@@ -437,29 +536,52 @@ function drainFolds() {
|
|
|
437
536
|
const e = [ ...foldOlds ];
|
|
438
537
|
foldOlds.clear();
|
|
439
538
|
for (const [t, n] of e) {
|
|
539
|
+
// A latest()-pull staging holds only until the fold commit: this flush
|
|
540
|
+
// is committing the batch the pull ran ahead of. Transition holds stay —
|
|
541
|
+
// they clear when their transition is done (heldMaskView).
|
|
542
|
+
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;
|
|
440
548
|
if (t.pb !== null) {
|
|
549
|
+
// #3089: a fold written under a still-running transition defers to
|
|
550
|
+
// that transition's settle (the write-time stamp covers unobserved
|
|
551
|
+
// keys; observed keys also hit the pending-node held check below).
|
|
552
|
+
const e = foldBatches.get(t);
|
|
553
|
+
if (e !== undefined) {
|
|
554
|
+
if (currentTransition(e).sn === false) {
|
|
555
|
+
foldOlds.set(t, n);
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
foldBatches.delete(t);
|
|
559
|
+
}
|
|
441
560
|
// Setter path: nodes were setSignal'd at setter exit (write-time
|
|
442
561
|
// notification — transitions/holds ride core machinery). Commit the
|
|
443
562
|
// backing only for keys whose nodes have committed; a still-pending
|
|
444
563
|
// node (transition-held) re-queues the target for the settling flush.
|
|
445
|
-
|
|
446
|
-
const
|
|
447
|
-
const
|
|
448
|
-
if (
|
|
564
|
+
let r = false;
|
|
565
|
+
const i = t.pb;
|
|
566
|
+
const o = t.n;
|
|
567
|
+
if (o !== null) {
|
|
449
568
|
// Only written keys can hold (their nodes took the setSignal); the
|
|
450
569
|
// wk bound keeps this O(written) — see notifyWrites. Same fallback
|
|
451
570
|
// rules as the notify (WK_ALL / accessors / non-plain prototypes).
|
|
452
|
-
const
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
571
|
+
const e = t.pc !== null ? t.pc.wk : null;
|
|
572
|
+
const n = e === null || e === WK_ALL || t.a === true ||
|
|
573
|
+
// Overlay pbs chain to the COMMITTED object (#3044) — plainness is
|
|
574
|
+
// the committed container's prototype, not the overlay's.
|
|
575
|
+
!plainProto(t.ovl ? t.v : i) ? Reflect.ownKeys(o) : e;
|
|
576
|
+
for (const e of n) {
|
|
577
|
+
const t = o[e];
|
|
578
|
+
if (t !== undefined && t.Pe !== NOT_PENDING) {
|
|
579
|
+
r = true;
|
|
458
580
|
break;
|
|
459
581
|
}
|
|
460
582
|
}
|
|
461
583
|
}
|
|
462
|
-
if (
|
|
584
|
+
if (r) {
|
|
463
585
|
foldOlds.set(t, n);
|
|
464
586
|
// re-queue: commit happens when the hold settles
|
|
465
587
|
continue;
|
|
@@ -474,30 +596,68 @@ function drainFolds() {
|
|
|
474
596
|
// — the never-mutate-user-data contract holds.
|
|
475
597
|
privatizeCommitted(t);
|
|
476
598
|
const e = t.v;
|
|
477
|
-
for (const t of Reflect.ownKeys(
|
|
478
|
-
const n = Object.getOwnPropertyDescriptor(
|
|
599
|
+
for (const t of Reflect.ownKeys(i)) {
|
|
600
|
+
const n = Object.getOwnPropertyDescriptor(i, t);
|
|
479
601
|
if (n.get || n.set || !n.enumerable || !n.writable || !n.configurable) Object.defineProperty(e, t, n); else e[t] = n.value;
|
|
480
602
|
}
|
|
481
603
|
if (t.del !== null) {
|
|
482
604
|
for (const n of t.del) delete e[n];
|
|
483
605
|
t.del = null;
|
|
484
606
|
}
|
|
485
|
-
(t.fam?.map ?? storeNextLookup).delete(
|
|
607
|
+
(t.fam?.map ?? storeNextLookup).delete(i);
|
|
486
608
|
t.pb = null;
|
|
487
609
|
t.ovl = false;
|
|
488
|
-
t.wk = null;
|
|
610
|
+
if (t.pc !== null) t.pc.wk = null;
|
|
489
611
|
// written-keys window closes with the fold commit
|
|
490
612
|
} else {
|
|
491
|
-
|
|
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
|
+
t.v = i;
|
|
492
627
|
t.ch = false;
|
|
493
628
|
// pb is always a plain clone
|
|
494
629
|
t.pb = null;
|
|
495
|
-
t.wk = null;
|
|
630
|
+
if (t.pc !== null) t.pc.wk = null;
|
|
496
631
|
// written-keys window closes with the fold commit
|
|
497
632
|
}
|
|
498
633
|
}
|
|
499
|
-
if (t.v === n)
|
|
500
|
-
|
|
634
|
+
if (t.v === n) {
|
|
635
|
+
// A no-op adoption (A -> B -> A before flush) still consumed its walk:
|
|
636
|
+
// clear the flag or every later setter row-op gate (!t.adopted) stays
|
|
637
|
+
// failed and a driven family list freezes (re-audit 5, P1-1).
|
|
638
|
+
t.adopted = false;
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
// Patch channel (fold-commit site): family targets emit HERE — the fold
|
|
642
|
+
// IS their visibility moment (held folds re-queued above emit when they
|
|
643
|
+
// actually commit) — and so do PLAIN fold-adopted targets (setter-
|
|
644
|
+
// returned root replacements, chained-store swaps: adoptions WITHOUT a
|
|
645
|
+
// reconcile walk, so no walk-site emission ever happened — re-audit 2,
|
|
646
|
+
// P1-2). Plain eager targets emitted at their walk/setter sites already.
|
|
647
|
+
if (t.pc !== null && (t.fam !== null || t.adopted)) {
|
|
648
|
+
// Structural ops for folds whose structure rode no other channel:
|
|
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);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
501
661
|
// Path copying (CAS: see the eager-fold twin above).
|
|
502
662
|
if (t.u && t.u.v[t.pk] === n) {
|
|
503
663
|
privatizeCommitted(t.u);
|
|
@@ -518,19 +678,7 @@ function drainFolds() {
|
|
|
518
678
|
* isPending, affects, and lane machinery ride the core natively (§3's
|
|
519
679
|
* "pending home = the node when a node exists"). Unobserved keys stay in the
|
|
520
680
|
* pending backing and fold directly at commit.
|
|
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) {
|
|
681
|
+
*/ function notifyWrites(e) {
|
|
534
682
|
let t = e.pb;
|
|
535
683
|
if (t === null) return;
|
|
536
684
|
// Optimistic channel: user writes on an optimistic family become node-level
|
|
@@ -567,9 +715,16 @@ function notifyWrites(e) {
|
|
|
567
715
|
// not a full scan). Falls back to the full node scan when the bound can't
|
|
568
716
|
// hold: no trap granularity (wk null), an array length write (WK_ALL —
|
|
569
717
|
// 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
|
-
|
|
572
|
-
|
|
718
|
+
// value can change when ANY key is written), or a non-plain prototype
|
|
719
|
+
// (class instances: prototype getters derive from arbitrary fields).
|
|
720
|
+
const i = e.pc !== null ? e.pc.wk : null;
|
|
721
|
+
// Overlay pbs chain to the COMMITTED object (#3044): a prototype-overlay
|
|
722
|
+
// draft is plain data on its own layer, but its getPrototypeOf is the
|
|
723
|
+
// committed container — judge plainness by the COMMITTED prototype or the
|
|
724
|
+
// bound never engages for overlay writes (every plain-object setter batch
|
|
725
|
+
// would full-scan: the exact selection-map workload wk exists for; jf
|
|
726
|
+
// `select` regressed 2x on this).
|
|
727
|
+
const o = i === WK_ALL || e.a === true || !plainProto(e.ovl ? e.v : t) ? null : i;
|
|
573
728
|
if (r !== null) {
|
|
574
729
|
const i = o ?? Reflect.ownKeys(r);
|
|
575
730
|
for (const o of i) {
|
|
@@ -600,12 +755,16 @@ function notifyWrites(e) {
|
|
|
600
755
|
}
|
|
601
756
|
const f = e.h;
|
|
602
757
|
if (f !== null) {
|
|
603
|
-
|
|
758
|
+
const n = o ?? Reflect.ownKeys(f);
|
|
759
|
+
for (const r of n) {
|
|
760
|
+
const n = f[r];
|
|
761
|
+
if (n !== undefined) setSignal(n, r in t && !(e.del !== null && e.del.has(r)));
|
|
762
|
+
}
|
|
604
763
|
}
|
|
605
764
|
// Deep-witness (dk): setter writes must notify a deep() subscriber even on
|
|
606
|
-
// keys with no node. O(pb keys) equality only when a witness exists.
|
|
765
|
+
// keys with no node. O(written/pb keys) equality only when a witness exists.
|
|
607
766
|
if (e.dk !== null) {
|
|
608
|
-
if (e.del !== null && e.del.size !== 0) bumpDeep(e); else for (const r of Reflect.ownKeys(t)) {
|
|
767
|
+
if (e.del !== null && e.del.size !== 0) bumpDeep(e); else for (const r of o ?? Reflect.ownKeys(t)) {
|
|
609
768
|
const i = t[r];
|
|
610
769
|
const o = n[r];
|
|
611
770
|
if (i !== null && typeof i === "object" ? !targetsEqual(o, i) : !isEqual(o, i)) {
|
|
@@ -632,6 +791,12 @@ function notifyWrites(e) {
|
|
|
632
791
|
}
|
|
633
792
|
if (r) setSignal(e.k, e => e + 1);
|
|
634
793
|
}
|
|
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);
|
|
635
800
|
// Projection backing folds split by channel (two pinned contracts):
|
|
636
801
|
// - sync-derive drafts (recompute body): NEVER eager — a downstream async
|
|
637
802
|
// hold can form LATER in the same flush and the leaf must stay at stale
|
|
@@ -642,12 +807,19 @@ function notifyWrites(e) {
|
|
|
642
807
|
// IMMEDIATE — landed truth shows to untracked readers even while a
|
|
643
808
|
// downstream consumer's own async still holds the effect-level reveal
|
|
644
809
|
// (spec-async "verdicts never inherit consumers' in-flight state").
|
|
645
|
-
|
|
810
|
+
// EXCEPT under an active transaction (#3164 fold): a landing riding a
|
|
811
|
+
// retaining transaction (the optimistic module's aroundWrite binds it)
|
|
812
|
+
// stages instead — ensurePB stamped foldBatches, so the backing commits
|
|
813
|
+
// with the transaction and the reveal is atomic at settle. The pinned
|
|
814
|
+
// immediate-commit contract is stated over the no-transaction microtask
|
|
815
|
+
// posture, which `activeTransition === null` is exactly.
|
|
816
|
+
if (e.fam !== null && e.pb !== null && getWriteOverride() && activeTransition === null) {
|
|
817
|
+
// Landed truth (post-await write-override): immediately visible to every
|
|
818
|
+
// reader — any staged held view is superseded.
|
|
819
|
+
if (e.ht !== null) e.ht = e.hv = null;
|
|
646
820
|
const n = e.v;
|
|
647
821
|
e.pb = null;
|
|
648
|
-
e.
|
|
649
|
-
// written-keys window closes with the eager fold
|
|
650
|
-
e.v = t;
|
|
822
|
+
e.v = t;
|
|
651
823
|
e.ch = false;
|
|
652
824
|
if (e.u && e.u.v[e.pk] === n) {
|
|
653
825
|
privatizeCommitted(e.u);
|
|
@@ -710,8 +882,8 @@ i = true) {
|
|
|
710
882
|
return;
|
|
711
883
|
}
|
|
712
884
|
const f = i?.value;
|
|
713
|
-
const
|
|
714
|
-
if (!isEqual(f,
|
|
885
|
+
const l = o?.value;
|
|
886
|
+
if (!isEqual(f, l) && !targetsEqual(f, l)) setSignal(e, typeof l === "function" ? () => l : l);
|
|
715
887
|
} else {
|
|
716
888
|
const i = n[t];
|
|
717
889
|
const o = r[t];
|
|
@@ -836,10 +1008,21 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
|
836
1008
|
* / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */ function inOwnerContext() {
|
|
837
1009
|
const e = getOwner();
|
|
838
1010
|
if (e === null) return false;
|
|
839
|
-
const t = e.
|
|
1011
|
+
const t = e.Gt ? e.Dt : e;
|
|
840
1012
|
return t != null && !(t.T & CONFIG_CHILDREN_FORBIDDEN);
|
|
841
1013
|
}
|
|
842
1014
|
|
|
1015
|
+
/** CHILDREN_FORBIDDEN execution scope (createTrackedEffect / onSettled
|
|
1016
|
+
* callbacks). Distinct from context-free: these scopes get committed
|
|
1017
|
+
* visibility even against a projection's authoritative-elect pending
|
|
1018
|
+
* backing (#3082) — parity with signals, where core read() serves
|
|
1019
|
+
* committed to them regardless of staged writes. */ function inForbiddenScope() {
|
|
1020
|
+
const e = getOwner();
|
|
1021
|
+
if (e === null) return false;
|
|
1022
|
+
const t = e.Gt ? e.Dt : e;
|
|
1023
|
+
return t != null && !!(t.T & CONFIG_CHILDREN_FORBIDDEN);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
843
1026
|
/** A pending fold is transition-held when any written node's parked value is
|
|
844
1027
|
* stamped by a live transition (a plain batch parking — the lazy-recompute
|
|
845
1028
|
* read case — has no transition stamp and serves fresh). */ function foldHeld(e) {
|
|
@@ -847,26 +1030,57 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
|
|
|
847
1030
|
if (t === null) return false;
|
|
848
1031
|
for (const e of Reflect.ownKeys(t)) {
|
|
849
1032
|
const n = t[e];
|
|
850
|
-
if (n.Pe !== NOT_PENDING && n.
|
|
1033
|
+
if (n.Pe !== NOT_PENDING && n.Ae != null && n.Ae.sn !== true) return true;
|
|
851
1034
|
}
|
|
852
1035
|
return false;
|
|
853
1036
|
}
|
|
854
1037
|
|
|
855
1038
|
function readSource(e) {
|
|
1039
|
+
// Held view first (#3074): an adoption staged under a live hold serves the
|
|
1040
|
+
// pre-hold committed backing to committed-visibility readers. Speculative
|
|
1041
|
+
// readers — drafts, write-override, owner-context computeds recomputing
|
|
1042
|
+
// inside the transaction, and latest() reads — see the adopted backing.
|
|
1043
|
+
if (e.ht !== null && !latestReadActive && !inDraft(e) && !getWriteOverride() && !inOwnerContext()) {
|
|
1044
|
+
const t = heldMaskView(e);
|
|
1045
|
+
if (t !== null) return t;
|
|
1046
|
+
}
|
|
856
1047
|
// Signal-parity visibility (core read(): owner-context reads serve
|
|
857
1048
|
// _pendingValue, context-free reads serve committed — effects recompute
|
|
858
1049
|
// BEFORE commitPendingNodes in the flush, so the pending view must be
|
|
859
1050
|
// servable). Drafts (setter window OR projection write-override) and
|
|
860
1051
|
// owner-context reads see the pending backing; context-free reads see
|
|
861
1052
|
// committed. Node reads apply the same rule, so both homes agree.
|
|
862
|
-
|
|
1053
|
+
if (e.pb !== null && (inDraft(e) || getWriteOverride() ||
|
|
1054
|
+
// Owner-context readers see the pending backing — EXCEPT held truth
|
|
1055
|
+
// on an optimistic family (#3164 fold): a live pb on an opt family
|
|
1056
|
+
// outside the draft/write-override windows is a staged landing
|
|
1057
|
+
// (tentative drafts never outlive their setter), and only the
|
|
1058
|
+
// authoritative postures and latest() see it (the backing-level twin
|
|
1059
|
+
// of core read()'s A17-for-held-truth arm; ordinary readers keep
|
|
1060
|
+
// committed until the transaction's reveal).
|
|
1061
|
+
inOwnerContext() && !heldTruthMasked(e) ||
|
|
863
1062
|
// A projection's pending backing is authoritative-elect: serve it to
|
|
864
1063
|
// context-free readers too UNLESS a transition is holding the node
|
|
865
|
-
// commits (downstream async hold — stale committed is the contract)
|
|
866
|
-
|
|
1064
|
+
// commits (downstream async hold — stale committed is the contract)
|
|
1065
|
+
// or the reader is a CHILDREN_FORBIDDEN scope, which never observes
|
|
1066
|
+
// its own unsettled write (#3082, signal parity per #3006).
|
|
1067
|
+
e.fam !== null && !heldTruthMasked(e) && !foldHeld(e) && !inForbiddenScope())) return e.pb;
|
|
867
1068
|
return e.v;
|
|
868
1069
|
}
|
|
869
1070
|
|
|
1071
|
+
/** #3164 fold: HELD truth on an optimistic family — a pending backing
|
|
1072
|
+
* stamped by a live transition that retains optimism — is masked from
|
|
1073
|
+
* ordinary readers (they keep committed until the transaction's reveal);
|
|
1074
|
+
* the authoritative postures and latest() tunnel through. Un-stamped
|
|
1075
|
+
* backings and optimism-free transitions keep ordinary mid-batch/
|
|
1076
|
+
* speculation visibility. */ function heldTruthMasked(e) {
|
|
1077
|
+
if (e.fam?.opt !== true || latestReadActive || authoritativeServe()) return false;
|
|
1078
|
+
const t = foldBatches.get(e);
|
|
1079
|
+
// opt families are only created by createOptimisticStore, whose module
|
|
1080
|
+
// install populates optHooks — the assertion holds by construction.
|
|
1081
|
+
return t !== undefined && optHooks.retainsOptimism(t);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
870
1084
|
const hasOwn = Object.prototype.hasOwnProperty;
|
|
871
1085
|
|
|
872
1086
|
// Allocation-free own-accessor probe (replaces eager descriptor scans — the
|
|
@@ -896,7 +1110,27 @@ function isOwnAccessor(e, t) {
|
|
|
896
1110
|
|
|
897
1111
|
/** Active optimistic override on an armed node (armed slot idles at
|
|
898
1112
|
* NOT_PENDING; undefined = unarmed plain node). */ function hasActiveOverride(e) {
|
|
899
|
-
return e.o?.
|
|
1113
|
+
return e.o?.Oe !== undefined && e.o?.Oe !== NOT_PENDING;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/** The reading computation is until()'s authoritative-view predicate — same
|
|
1117
|
+
* source of truth as core read()'s A17 carve-out (`context`, which persists
|
|
1118
|
+
* under untrack). optimisticView()'s composition gate consults exactly this:
|
|
1119
|
+
* write-side machinery (patch emission, tentative re-application) must keep
|
|
1120
|
+
* composing even when it runs inside an authoritative-write bracket. */ function authoritativeRead() {
|
|
1121
|
+
const e = context;
|
|
1122
|
+
return e !== null && (e.T & CONFIG_AUTHORITATIVE_READ) !== 0;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/** Serve-side authoritative gate: until()'s predicate PLUS truth authors —
|
|
1126
|
+
* the projection derive's draft (wrapDraft trap brackets, runAuthoritative;
|
|
1127
|
+
* the same posture pair ensurePB classifies drafts by). A source computing
|
|
1128
|
+
* the next truth must never read its callers' tentative overlays: a derive
|
|
1129
|
+
* continuation's `store.push` computing its index from an action's
|
|
1130
|
+
* optimistic row landed truth in the wrong slot and corrupted committed
|
|
1131
|
+
* state (#3108). Trap-level overlay serves gate on this so values, length,
|
|
1132
|
+
* membership, and keys leave the authoritative view together. */ function authoritativeServe() {
|
|
1133
|
+
return projectionWriteActive || getWriteOverride() || authoritativeRead();
|
|
900
1134
|
}
|
|
901
1135
|
|
|
902
1136
|
/** Context-aware node view for reads outside tracking: active override >
|
|
@@ -906,7 +1140,19 @@ function isOwnAccessor(e, t) {
|
|
|
906
1140
|
* reader that forced it (backing commits eagerly; node values fold at flush).
|
|
907
1141
|
* FORCE sentinels never surface (they only bump subscribers of accessor
|
|
908
1142
|
* keys, which are served by the trap, not the node). */ function nodeValue(e, t) {
|
|
909
|
-
|
|
1143
|
+
// latest() sees the in-flight parked value like an owner-context reader
|
|
1144
|
+
// does (#3075) — signal/memo parity for store-node-backed keys.
|
|
1145
|
+
// Authoritative-view reads (until()'s predicate) skip the override arm
|
|
1146
|
+
// only: staged pending values are authoritative, overrides are the
|
|
1147
|
+
// caller's optimism.
|
|
1148
|
+
const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.Oe) : e.Pe !== NOT_PENDING && (latestReadActive ||
|
|
1149
|
+
// Owner-context pending visibility — except HELD truth (#3164,
|
|
1150
|
+
// see CONFIG_HELD_TRUTH: fold-staged or entangle-stolen
|
|
1151
|
+
// confirming truth), which only authoritative/latest readers
|
|
1152
|
+
// see (core read()'s A17-for-held-truth twin; ordinary readers
|
|
1153
|
+
// keep committed until the transaction's reveal — latest() is
|
|
1154
|
+
// exempted by the leading arm above).
|
|
1155
|
+
(inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Pe : t;
|
|
910
1156
|
return n === FORCE ? t : n;
|
|
911
1157
|
}
|
|
912
1158
|
|
|
@@ -931,15 +1177,20 @@ function isOwnAccessor(e, t) {
|
|
|
931
1177
|
read(getNode(e, t, n));
|
|
932
1178
|
}
|
|
933
1179
|
}
|
|
934
|
-
|
|
1180
|
+
// Truth authors read the backing's own length — an optimistic row from
|
|
1181
|
+
// the caller's transaction must not shift where the author's next write
|
|
1182
|
+
// lands (#3108).
|
|
1183
|
+
return (authoritativeServe() ? r : optHooks.optimisticView(e, r)).length;
|
|
935
1184
|
}
|
|
936
1185
|
if (inDraft(e)) {
|
|
937
1186
|
// Optimistic drafts before their first write have no pending backing yet;
|
|
938
1187
|
// reads must still see the live optimistic view (compose, not clobber —
|
|
939
1188
|
// #2951). Once ensurePB runs, the seeded clone carries the view.
|
|
940
|
-
|
|
1189
|
+
// AUTHORITATIVE drafts (projection derive) never overlay — ensurePB's
|
|
1190
|
+
// seeding rule, applied to the read side (#3108).
|
|
1191
|
+
if (e.fam?.opt && e.pb === null && !authoritativeServe()) {
|
|
941
1192
|
const n = e.n?.[t];
|
|
942
|
-
if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.
|
|
1193
|
+
if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.Oe);
|
|
943
1194
|
}
|
|
944
1195
|
} else {
|
|
945
1196
|
if (i !== undefined) {
|
|
@@ -995,6 +1246,27 @@ function isOwnAccessor(e, t) {
|
|
|
995
1246
|
if (t != null && t.S & (STATUS_UNINITIALIZED | STATUS_ERROR)) read(t);
|
|
996
1247
|
}
|
|
997
1248
|
|
|
1249
|
+
/** latest() pull (#3075): bring the projection computed up to date so the
|
|
1250
|
+
* read serves the IN-FLIGHT derivation — signal/memo parity, where core
|
|
1251
|
+
* read() routes latest() through a companion that recomputes speculatively.
|
|
1252
|
+
* The latest flag is suspended for the recompute (the derive's own reads
|
|
1253
|
+
* are normal reads), and latestPullActive marks any adoption it commits as
|
|
1254
|
+
* staged (see adoptPB) — the speculative swap must not leak to
|
|
1255
|
+
* committed-visibility readers before the flush. */ function pullProjectionForLatest(e) {
|
|
1256
|
+
const t = e.fam.node;
|
|
1257
|
+
if (t == null) return;
|
|
1258
|
+
const n = latestReadActive;
|
|
1259
|
+
setLatestReadActive(false);
|
|
1260
|
+
const r = latestPullActive;
|
|
1261
|
+
latestPullActive = true;
|
|
1262
|
+
try {
|
|
1263
|
+
prepareComputed(t, true);
|
|
1264
|
+
} finally {
|
|
1265
|
+
latestPullActive = r;
|
|
1266
|
+
setLatestReadActive(n);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
|
|
998
1270
|
const traps = {
|
|
999
1271
|
get(e, t, n) {
|
|
1000
1272
|
// One typeof gates every brand-symbol compare off the hot string path
|
|
@@ -1022,6 +1294,10 @@ const traps = {
|
|
|
1022
1294
|
}
|
|
1023
1295
|
if (pendingCheckActive) witnessAffectsMark(e, t);
|
|
1024
1296
|
if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
|
|
1297
|
+
// latest() pull (#3075): store traps never reach core read() without an
|
|
1298
|
+
// observer, so bring the projection computed up to date here — signal/
|
|
1299
|
+
// memo parity for latest() reads through a projection.
|
|
1300
|
+
if (e.fam !== null && latestReadActive && !inDraft(e) && !getWriteOverride()) pullProjectionForLatest(e);
|
|
1025
1301
|
const r = readSource(e);
|
|
1026
1302
|
// Overlay delete (#3044): a prototype overlay cannot shadow a delete, so
|
|
1027
1303
|
// deleted keys are tracked aside and read as absent in the pending view.
|
|
@@ -1097,9 +1373,12 @@ const traps = {
|
|
|
1097
1373
|
if (e.s) return serveShallow(e, t, r);
|
|
1098
1374
|
return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
|
|
1099
1375
|
}
|
|
1100
|
-
} else if (f === undefined && inDraft(e) && e.fam?.opt && e.pb === null
|
|
1376
|
+
} else if (f === undefined && inDraft(e) && e.fam?.opt && e.pb === null &&
|
|
1377
|
+
// AUTHORITATIVE drafts (landing folds) never seed from overrides —
|
|
1378
|
+
// the caller's optimism is not truth (has-trap twin below).
|
|
1379
|
+
!authoritativeServe()) {
|
|
1101
1380
|
const n = e.n?.[t];
|
|
1102
|
-
if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.
|
|
1381
|
+
if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.Oe);
|
|
1103
1382
|
}
|
|
1104
1383
|
if (e.s) return serveShallow(e, t, f);
|
|
1105
1384
|
return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
|
|
@@ -1119,15 +1398,17 @@ const traps = {
|
|
|
1119
1398
|
if (!inDraft(e)) {
|
|
1120
1399
|
if (getObserver() !== null) {
|
|
1121
1400
|
const n = getHasNode(e, t, r);
|
|
1122
|
-
|
|
1401
|
+
// Authoritative-view readers get the right answer for free: core read()
|
|
1402
|
+
// skips the override arm for them, so nv is authoritative presence.
|
|
1403
|
+
const i = read(n);
|
|
1123
1404
|
if (hasActiveOverride(n)) r = !!i;
|
|
1124
|
-
} else {
|
|
1405
|
+
} else if (!authoritativeServe()) {
|
|
1125
1406
|
const n = e.h?.[t];
|
|
1126
|
-
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.
|
|
1407
|
+
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Oe);
|
|
1127
1408
|
}
|
|
1128
|
-
} else if (e.fam?.opt && e.pb === null) {
|
|
1409
|
+
} else if (e.fam?.opt && e.pb === null && !authoritativeServe()) {
|
|
1129
1410
|
const n = e.h?.[t];
|
|
1130
|
-
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.
|
|
1411
|
+
if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Oe);
|
|
1131
1412
|
}
|
|
1132
1413
|
return r;
|
|
1133
1414
|
},
|
|
@@ -1150,14 +1431,15 @@ const traps = {
|
|
|
1150
1431
|
// Optimistic membership overlay: presence-node overrides add/remove keys
|
|
1151
1432
|
// (per-transaction lifecycle rides the nodes — §6, FINDING-2's fix).
|
|
1152
1433
|
// Draft reads before the first write overlay too (pb, once created, is
|
|
1153
|
-
// seeded with the view).
|
|
1154
|
-
|
|
1434
|
+
// seeded with the view). Authoritative-view reads (until()'s predicate,
|
|
1435
|
+
// truth-author drafts) skip the overlay.
|
|
1436
|
+
if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || e.pb === null)) {
|
|
1155
1437
|
let t = null;
|
|
1156
1438
|
for (const r of Reflect.ownKeys(e.h)) {
|
|
1157
1439
|
const i = e.h[r];
|
|
1158
1440
|
if (!hasActiveOverride(i)) continue;
|
|
1159
1441
|
t ??= new Set(n);
|
|
1160
|
-
if (unwrapOverride(i.o?.
|
|
1442
|
+
if (unwrapOverride(i.o?.Oe)) t.add(r); else t.delete(r);
|
|
1161
1443
|
}
|
|
1162
1444
|
if (t !== null) return [ ...t ];
|
|
1163
1445
|
}
|
|
@@ -1172,10 +1454,10 @@ const traps = {
|
|
|
1172
1454
|
if (e.del !== null && e.del.has(t)) return undefined;
|
|
1173
1455
|
if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, t);
|
|
1174
1456
|
}
|
|
1175
|
-
if (e.fam?.opt && !inDraft(e)) {
|
|
1457
|
+
if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
|
|
1176
1458
|
const n = e.h?.[t];
|
|
1177
1459
|
if (n !== undefined && hasActiveOverride(n)) {
|
|
1178
|
-
if (!unwrapOverride(n.o?.
|
|
1460
|
+
if (!unwrapOverride(n.o?.Oe)) return undefined;
|
|
1179
1461
|
// opt delete
|
|
1180
1462
|
if (r === undefined) {
|
|
1181
1463
|
const n = e.n?.[t];
|
|
@@ -1216,13 +1498,14 @@ const traps = {
|
|
|
1216
1498
|
// Array length writes implicitly delete indices — the written-keys bound
|
|
1217
1499
|
// can't see them, so poison to the full scan for this batch. Index
|
|
1218
1500
|
// writes implicitly GROW length, so arrays always record it alongside.
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1501
|
+
const l = pcOf(e);
|
|
1502
|
+
if (Array.isArray(f)) {
|
|
1503
|
+
if (t === "length") l.wk = WK_ALL; else if (l.wk !== WK_ALL) {
|
|
1504
|
+
const e = l.wk ??= new Set;
|
|
1505
|
+
e.add(t);
|
|
1506
|
+
e.add("length");
|
|
1224
1507
|
}
|
|
1225
|
-
} else if (
|
|
1508
|
+
} else if (l.wk !== WK_ALL) (l.wk ??= new Set).add(t);
|
|
1226
1509
|
// Own data keys literally named "prototype"/"constructor" land as data —
|
|
1227
1510
|
// defineProperty sidesteps a proto-chain setter named the same.
|
|
1228
1511
|
if (UNSAFE_KEYS.has(t)) {
|
|
@@ -1260,7 +1543,14 @@ const traps = {
|
|
|
1260
1543
|
const i = !r && getWriteOverride();
|
|
1261
1544
|
if (!r && !i) return true;
|
|
1262
1545
|
if (t === "__proto__") return true;
|
|
1263
|
-
if (n.get || n.set)
|
|
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
|
+
}
|
|
1264
1554
|
// Unwrap before ensurePB (see the set trap: self-reference materializes).
|
|
1265
1555
|
if ("value" in n) n = {
|
|
1266
1556
|
...n,
|
|
@@ -1268,7 +1558,8 @@ const traps = {
|
|
|
1268
1558
|
};
|
|
1269
1559
|
const o = ensurePB(e);
|
|
1270
1560
|
pendingNotify.add(e);
|
|
1271
|
-
|
|
1561
|
+
const f = pcOf(e);
|
|
1562
|
+
if (f.wk !== WK_ALL) (f.wk ??= new Set).add(t);
|
|
1272
1563
|
Object.defineProperty(o, t, n);
|
|
1273
1564
|
if (e.del !== null) e.del.delete(t);
|
|
1274
1565
|
if (i) notifyWrites(e);
|
|
@@ -1280,7 +1571,8 @@ const traps = {
|
|
|
1280
1571
|
if (!n && !r) return true;
|
|
1281
1572
|
const i = ensurePB(e);
|
|
1282
1573
|
pendingNotify.add(e);
|
|
1283
|
-
|
|
1574
|
+
const o = pcOf(e);
|
|
1575
|
+
if (o.wk !== WK_ALL) (o.wk ??= new Set).add(t);
|
|
1284
1576
|
delete i[t];
|
|
1285
1577
|
// A prototype overlay cannot shadow a delete of a committed key —
|
|
1286
1578
|
// record it aside (#3044); reads/has/ownKeys/commit consult the set.
|
|
@@ -1344,8 +1636,39 @@ function createStoreNext(e, t = false) {
|
|
|
1344
1636
|
return [ n, setter ];
|
|
1345
1637
|
}
|
|
1346
1638
|
|
|
1639
|
+
/** True when `proxy` is a SHALLOW store (children served verbatim, slots
|
|
1640
|
+
* replaced by reference — #2932). The list driver uses this to choose the
|
|
1641
|
+
* slot-patch channel (collected row bodies) over per-record registration. */ function storeIsShallow(e) {
|
|
1642
|
+
const t = e?.[$TARGET];
|
|
1643
|
+
return t !== undefined && t.s === true;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
/** True when `proxy` belongs to a projection/optimistic FAMILY. The list
|
|
1647
|
+
* driver must DECLINE family arrays (external audit finding): family
|
|
1648
|
+
* structural changes never emit row/slot ops (the setter channel is
|
|
1649
|
+
* fam-gated; optimistic writes ride node overrides), and the proxy identity
|
|
1650
|
+
* is stable so the each-watch cannot catch the change either — an engaged
|
|
1651
|
+
* list would freeze on optimistic/projection structural updates. Record-
|
|
1652
|
+
* level family patches are unaffected (they have their own emission). */ function storeHasFamily(e) {
|
|
1653
|
+
const t = e?.[$TARGET];
|
|
1654
|
+
return t !== undefined && t.fam !== null;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
/** True when `proxy` belongs to an OPTIMISTIC family specifically. The list
|
|
1658
|
+
* driver declines these (audit finding, narrowed): optimistic user writes
|
|
1659
|
+
* ride node-level overrides — they never enter the reconcile walk, so no
|
|
1660
|
+
* row/slot ops are emitted and an engaged list would freeze on optimistic
|
|
1661
|
+
* structural changes. PROJECTION (non-optimistic) families are drivable:
|
|
1662
|
+
* their recomputes go through the reconcile walk, whose emissions are
|
|
1663
|
+
* transition-stamped in the apply queue like any other (equivalence-matrix
|
|
1664
|
+
* gated). Re-admitting optimistic families requires a lane-timed structural
|
|
1665
|
+
* emission mirroring emitPatchOptimistic, plus revert resync. */ function storeHasOptimisticFamily(e) {
|
|
1666
|
+
const t = e?.[$TARGET];
|
|
1667
|
+
return t !== undefined && t.fam?.opt === true;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1347
1670
|
/** Tracking deep snapshot (`deep()` for next targets): subscribes to the
|
|
1348
|
-
* key-set and
|
|
1671
|
+
* key-set and deep-witness node at every reachable level, then returns the
|
|
1349
1672
|
* plain view. Shared references and cycles handled via the visited set. */ function deepNext(e) {
|
|
1350
1673
|
const t = e?.[$TARGET];
|
|
1351
1674
|
if (t === undefined || t.px !== e) return e;
|
|
@@ -1465,11 +1788,11 @@ function snapshotWalk(e, t, n) {
|
|
|
1465
1788
|
Object.defineProperty(i, o, f);
|
|
1466
1789
|
continue;
|
|
1467
1790
|
}
|
|
1468
|
-
const
|
|
1469
|
-
const
|
|
1470
|
-
if (f.enumerable && f.writable && f.configurable) i[o] =
|
|
1791
|
+
const l = f.value;
|
|
1792
|
+
const u = l !== null && typeof l === "object" ? snapshotWalk(l, t, n) : l;
|
|
1793
|
+
if (f.enumerable && f.writable && f.configurable) i[o] = u; else Object.defineProperty(i, o, {
|
|
1471
1794
|
...f,
|
|
1472
|
-
value:
|
|
1795
|
+
value: u
|
|
1473
1796
|
});
|
|
1474
1797
|
}
|
|
1475
1798
|
if (e && i.length !== r.length) i.length = r.length;
|
|
@@ -1484,16 +1807,16 @@ function snapshotWalk(e, t, n) {
|
|
|
1484
1807
|
if (!i || i.get || i.set) continue;
|
|
1485
1808
|
const o = i.value;
|
|
1486
1809
|
if (o === null || typeof o !== "object") continue;
|
|
1487
|
-
const
|
|
1488
|
-
if (
|
|
1810
|
+
const l = snapshotWalk(o, t, n);
|
|
1811
|
+
if (l !== o) {
|
|
1489
1812
|
if (f === null) {
|
|
1490
1813
|
f = Array.isArray(r) ? [ ...r ] : Object.create(Object.getPrototypeOf(r), Object.getOwnPropertyDescriptors(r));
|
|
1491
1814
|
t.set(r, f);
|
|
1492
1815
|
}
|
|
1493
|
-
f[e] =
|
|
1816
|
+
f[e] = l;
|
|
1494
1817
|
}
|
|
1495
1818
|
}
|
|
1496
1819
|
return f ?? r;
|
|
1497
1820
|
}
|
|
1498
1821
|
|
|
1499
|
-
export { adoptPB, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, materializePB, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, runAuthoritative, snapshotNext, storeSetterNext, targetsEqual, unwrapValue, wrapNext };
|
|
1822
|
+
export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, pcOf, runAuthoritative, snapshotNext, stagedTruthPB, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetIsPlain, targetsEqual, unwrapValue, wrapNext };
|