@solidjs/signals 2.0.0-rc.14 → 2.0.0-rc.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev-shared.js +248 -189
- package/dist/dev.attribution.js +124 -41
- package/dist/dev.js +553 -322
- package/dist/observe/affects.js +34 -31
- package/dist/observe/boundaries.js +166 -132
- package/dist/observe/core/async.js +90 -83
- package/dist/observe/core/attribution-queries.js +1 -1
- package/dist/observe/core/attribution.js +281 -197
- package/dist/observe/core/constants.js +7 -2
- package/dist/observe/core/context.js +3 -3
- package/dist/observe/core/core.js +277 -245
- package/dist/observe/core/dev.js +3 -3
- package/dist/observe/core/effect.js +21 -21
- package/dist/observe/core/external.js +2 -2
- package/dist/observe/core/graph.js +25 -25
- package/dist/observe/core/heap.js +9 -9
- package/dist/observe/core/lanes.js +206 -160
- package/dist/observe/core/owner.js +27 -27
- package/dist/observe/core/scheduler.js +92 -86
- package/dist/observe/core/verdict.js +66 -54
- package/dist/observe/map.js +35 -35
- package/dist/observe/reveal.js +24 -24
- package/dist/observe/signals.js +38 -28
- package/dist/observe/store/affects.js +75 -68
- package/dist/observe/store/hooks.js +13 -0
- package/dist/observe/store/index.js +0 -2
- package/dist/observe/store/optimistic.js +67 -34
- package/dist/observe/store/projection.js +37 -31
- package/dist/observe/store/reconcile.js +32 -30
- package/dist/observe/store/store.js +128 -85
- package/dist/prod/affects.js +34 -31
- package/dist/prod/boundaries.js +143 -109
- package/dist/prod/core/async.js +64 -67
- package/dist/prod/core/constants.js +7 -2
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +302 -270
- package/dist/prod/core/effect.js +38 -38
- package/dist/prod/core/external.js +4 -4
- package/dist/prod/core/graph.js +27 -27
- package/dist/prod/core/heap.js +30 -30
- package/dist/prod/core/lanes.js +257 -211
- package/dist/prod/core/owner.js +39 -39
- package/dist/prod/core/scheduler.js +125 -119
- package/dist/prod/core/verdict.js +70 -58
- package/dist/prod/map.js +73 -73
- package/dist/prod/reveal.js +38 -38
- package/dist/prod/signals.js +30 -20
- package/dist/prod/store/affects.js +75 -68
- package/dist/prod/store/hooks.js +13 -0
- package/dist/prod/store/index.js +0 -2
- package/dist/prod/store/optimistic.js +114 -81
- package/dist/prod/store/projection.js +30 -24
- package/dist/prod/store/reconcile.js +32 -30
- package/dist/prod/store/store.js +136 -93
- package/dist/types/boundaries.d.ts +4 -0
- package/dist/types/core/attribution.d.ts +6 -2
- package/dist/types/core/constants.d.ts +5 -0
- package/dist/types/core/core.d.ts +33 -3
- package/dist/types/core/lanes.d.ts +10 -0
- package/dist/types/core/scheduler.d.ts +12 -6
- package/dist/types/core/types.d.ts +0 -6
- package/dist/types/store/affects.d.ts +11 -0
- package/dist/types/store/hooks.d.ts +48 -0
- package/dist/types/store/index.d.ts +0 -1
- package/dist/types/store/store.d.ts +3 -38
- package/package.json +1 -1
package/dist/dev.js
CHANGED
|
@@ -10,27 +10,27 @@ import {
|
|
|
10
10
|
b as CONFIG_VERDICT,
|
|
11
11
|
c as REACTIVE_PROBE_UNANSWERED,
|
|
12
12
|
d as blocked,
|
|
13
|
-
e as
|
|
13
|
+
e as REACTIVE_LANE_DIRTY,
|
|
14
|
+
f as enqueueSub,
|
|
14
15
|
r as resolveTx,
|
|
15
|
-
|
|
16
|
+
g as CONFIG_AUTHORITATIVE,
|
|
16
17
|
t as txOf,
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
h as tracking,
|
|
19
|
+
i as CONFIG_CHILDREN_FORBIDDEN,
|
|
19
20
|
p as passLane,
|
|
20
21
|
S as STATUS_PENDING,
|
|
21
22
|
E as EFFECT_RENDER,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
z as REACTIVE_LANE_DIRTY,
|
|
23
|
+
j as sameLane,
|
|
24
|
+
k as CONFIG_HELD,
|
|
25
|
+
l as flushTransaction,
|
|
26
|
+
m as STATUS_UNINITIALIZED,
|
|
27
|
+
o as NotReadyError,
|
|
28
|
+
u as REACTIVE_SCREEN_READ,
|
|
29
|
+
v as REACTIVE_FRAME_READ,
|
|
30
|
+
w as REACTIVE_LANE_READ,
|
|
31
|
+
x as ext,
|
|
32
|
+
y as REACTIVE_RECOMPUTING_DEPS,
|
|
33
|
+
z as queuePendingNode,
|
|
34
34
|
A as list,
|
|
35
35
|
B as clock,
|
|
36
36
|
D as insertSubs,
|
|
@@ -43,119 +43,123 @@ import {
|
|
|
43
43
|
M as ownFlights,
|
|
44
44
|
O as setPassLane,
|
|
45
45
|
P as REACTIVE_JOINED,
|
|
46
|
-
Q as
|
|
47
|
-
T as
|
|
48
|
-
U as
|
|
49
|
-
V as
|
|
50
|
-
W as
|
|
51
|
-
X as
|
|
52
|
-
Y as
|
|
53
|
-
Z as
|
|
54
|
-
_ as
|
|
55
|
-
$ as
|
|
56
|
-
a0 as
|
|
57
|
-
a1 as
|
|
58
|
-
a2 as
|
|
59
|
-
a3 as
|
|
60
|
-
a4 as
|
|
61
|
-
a5 as
|
|
62
|
-
a6 as
|
|
63
|
-
a7 as
|
|
64
|
-
a8 as
|
|
65
|
-
a9 as
|
|
66
|
-
aa as
|
|
67
|
-
ab as
|
|
68
|
-
ac as
|
|
69
|
-
ad as
|
|
70
|
-
ae as
|
|
71
|
-
af as
|
|
72
|
-
ag as
|
|
73
|
-
ah as
|
|
74
|
-
ai as
|
|
75
|
-
aj as
|
|
76
|
-
ak as
|
|
77
|
-
al as
|
|
78
|
-
am as
|
|
79
|
-
an as
|
|
80
|
-
ao as
|
|
81
|
-
ap as
|
|
82
|
-
aq as
|
|
83
|
-
ar as
|
|
84
|
-
as as
|
|
85
|
-
at as
|
|
86
|
-
au as
|
|
87
|
-
av as
|
|
88
|
-
aw as
|
|
89
|
-
ax as
|
|
90
|
-
ay as
|
|
91
|
-
az as
|
|
92
|
-
aA as
|
|
93
|
-
aB as
|
|
94
|
-
aC as
|
|
95
|
-
aD as
|
|
96
|
-
aE as
|
|
97
|
-
aF as
|
|
98
|
-
aG as
|
|
99
|
-
aH as
|
|
100
|
-
aI as
|
|
101
|
-
aJ as
|
|
102
|
-
aK as
|
|
103
|
-
aL as
|
|
104
|
-
aM as
|
|
105
|
-
aN as
|
|
106
|
-
aO as
|
|
107
|
-
aP as
|
|
108
|
-
aQ as
|
|
109
|
-
aR as
|
|
110
|
-
aS as
|
|
111
|
-
aT as
|
|
112
|
-
aU as
|
|
113
|
-
aV as
|
|
114
|
-
aW as
|
|
115
|
-
aX as
|
|
116
|
-
aY as
|
|
117
|
-
aZ as
|
|
118
|
-
a_ as
|
|
119
|
-
a$ as
|
|
120
|
-
b0 as
|
|
121
|
-
b1 as
|
|
122
|
-
b2 as
|
|
123
|
-
b3 as
|
|
124
|
-
b4 as
|
|
125
|
-
b5 as
|
|
126
|
-
b6 as
|
|
127
|
-
b7 as
|
|
128
|
-
b8 as
|
|
129
|
-
b9 as
|
|
130
|
-
ba as
|
|
131
|
-
bb as
|
|
132
|
-
bc as
|
|
133
|
-
bd as
|
|
134
|
-
be as
|
|
135
|
-
bf as
|
|
136
|
-
bg as
|
|
137
|
-
bh as
|
|
138
|
-
bi as
|
|
139
|
-
bj as
|
|
140
|
-
bk as
|
|
141
|
-
bl as
|
|
142
|
-
bm as
|
|
143
|
-
bn as
|
|
144
|
-
bo as
|
|
145
|
-
bp as
|
|
146
|
-
bq as
|
|
147
|
-
br as
|
|
46
|
+
Q as REACTIVE_STAGED_READ,
|
|
47
|
+
T as stagedRead,
|
|
48
|
+
U as CONFIG_SLOT_NODE,
|
|
49
|
+
V as attrHooks,
|
|
50
|
+
W as holdNode,
|
|
51
|
+
X as joinFuture,
|
|
52
|
+
Y as commitPendingNode,
|
|
53
|
+
Z as REACTIVE_IN_HEAP,
|
|
54
|
+
_ as REACTIVE_DIRTY,
|
|
55
|
+
$ as REACTIVE_CHECK,
|
|
56
|
+
a0 as CONFIG_INPUTS_PUBLISHED,
|
|
57
|
+
a1 as setVerdict,
|
|
58
|
+
a2 as context,
|
|
59
|
+
a3 as REACTIVE_PROBED,
|
|
60
|
+
a4 as bornInto,
|
|
61
|
+
a5 as globalQueue,
|
|
62
|
+
a6 as staleReader,
|
|
63
|
+
a7 as verdict,
|
|
64
|
+
a8 as link,
|
|
65
|
+
a9 as pullComputed,
|
|
66
|
+
aa as strictRead,
|
|
67
|
+
ab as warnStrictReadUntracked,
|
|
68
|
+
ac as joinPass,
|
|
69
|
+
ad as markLateLinker,
|
|
70
|
+
ae as spectating,
|
|
71
|
+
af as STATUS_ERROR,
|
|
72
|
+
ag as unflushedValue,
|
|
73
|
+
ah as stagedReaders,
|
|
74
|
+
ai as enterAction,
|
|
75
|
+
aj as setEffectCallback,
|
|
76
|
+
ak as exitAction,
|
|
77
|
+
al as isThenable,
|
|
78
|
+
am as actionDepth,
|
|
79
|
+
an as flush,
|
|
80
|
+
ao as setQuestion,
|
|
81
|
+
ap as emitDiagnostic,
|
|
82
|
+
aq as enterCallback,
|
|
83
|
+
ar as exitCallback,
|
|
84
|
+
as as getOwner,
|
|
85
|
+
at as inEffectCallback,
|
|
86
|
+
au as signal,
|
|
87
|
+
av as setSignal,
|
|
88
|
+
aw as cleanup,
|
|
89
|
+
ax as read,
|
|
90
|
+
ay as NoOwnerError,
|
|
91
|
+
az as ContextNotFoundError,
|
|
92
|
+
aA as createEffectNode,
|
|
93
|
+
aB as recompute,
|
|
94
|
+
aC as EFFECT_USER,
|
|
95
|
+
aD as reportDiagnostic,
|
|
96
|
+
aE as computed,
|
|
97
|
+
aF as staleValues,
|
|
98
|
+
aG as CONFIG_AUTO_DISPOSE,
|
|
99
|
+
aH as EFFECT_TRACKED,
|
|
100
|
+
aI as unwrapStatusError,
|
|
101
|
+
aJ as haltReactivity,
|
|
102
|
+
aK as setStrictRead,
|
|
103
|
+
aL as StatusError,
|
|
104
|
+
aM as trimStaleDeps,
|
|
105
|
+
aN as setTrackedQueueCallback,
|
|
106
|
+
aO as setEffectStatusNotify,
|
|
107
|
+
aP as _hitUnhandledAsync,
|
|
108
|
+
aQ as resetUnhandledAsync,
|
|
109
|
+
aR as ROOT_ERROR_HOOK,
|
|
110
|
+
aS as registerGraph,
|
|
111
|
+
aT as runWithOwner,
|
|
112
|
+
aU as setMemo,
|
|
113
|
+
aV as $REFRESH,
|
|
114
|
+
aW as markRefresh,
|
|
115
|
+
aX as createRoot,
|
|
116
|
+
aY as getObserver,
|
|
117
|
+
aZ as dispose,
|
|
118
|
+
a_ as TimeoutError,
|
|
119
|
+
a$ as untrack,
|
|
120
|
+
b0 as dirtyQueue,
|
|
121
|
+
b1 as CONFIG_REDERIVE,
|
|
122
|
+
b2 as passTx,
|
|
123
|
+
b3 as CONFIG_FRESH_READ,
|
|
124
|
+
b4 as createOwner,
|
|
125
|
+
b5 as REACTIVE_WRITE_IN_OWNED_SCOPE_SIGNAL_MESSAGE,
|
|
126
|
+
b6 as ownedScopeWriteMessage,
|
|
127
|
+
b7 as DEV$1,
|
|
128
|
+
b8 as isEqual,
|
|
129
|
+
b9 as CONFIG_MANUAL_WRITE,
|
|
130
|
+
ba as snapshotCaptureActive,
|
|
131
|
+
bb as captureWriteSnapshot,
|
|
132
|
+
bc as notePromotedWrite,
|
|
133
|
+
bd as slotSignal,
|
|
134
|
+
be as stagedScreen,
|
|
135
|
+
bf as CONFIG_PROMOTED,
|
|
136
|
+
bg as setSlotUnobserved,
|
|
137
|
+
bh as joinPassTx,
|
|
138
|
+
bi as asyncTailFlights,
|
|
139
|
+
bj as untrackDepth,
|
|
140
|
+
bk as checkPostAwaitRead,
|
|
141
|
+
bl as insertIntoHeap,
|
|
142
|
+
bm as isDisposed,
|
|
143
|
+
bn as handleAsync,
|
|
144
|
+
bo as addPendingSource,
|
|
145
|
+
bp as propagateStatus,
|
|
146
|
+
bq as forEachDependent,
|
|
147
|
+
br as settlePendingSource,
|
|
148
|
+
bs as notifyEpoch,
|
|
149
|
+
bt as SUPPORTS_PROXY,
|
|
150
|
+
bu as REACTIVE_ZOMBIE,
|
|
151
|
+
bv as OBSERVE$1
|
|
148
152
|
} from "./dev-shared.js";
|
|
149
153
|
export {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
154
|
+
bw as clearSnapshots,
|
|
155
|
+
bx as enforceLoadingBoundary,
|
|
156
|
+
by as getNextChildId,
|
|
157
|
+
bz as markSnapshotScope,
|
|
158
|
+
bA as peekNextChildId,
|
|
159
|
+
bB as releaseSnapshotScope,
|
|
160
|
+
bC as resetErrorHalt,
|
|
161
|
+
bD as setConsoleFooter,
|
|
162
|
+
bE as setSnapshotCapture
|
|
159
163
|
} from "./dev-shared.js";
|
|
160
164
|
|
|
161
165
|
// Lanes (plan sec. 28 replay, 2026-10-02; maintainer rulings 2026-10-01/02). A lane
|
|
@@ -190,6 +194,8 @@ export {
|
|
|
190
194
|
// carries none of this.
|
|
191
195
|
/** Live lanes (parented). Scanned at every seam while non-empty. */
|
|
192
196
|
const lanes = [];
|
|
197
|
+
/** A pass left a shown slot the equality gate can no longer see (#3892). */
|
|
198
|
+
const LEFT_SHOWN = 1 << 20;
|
|
193
199
|
function newLane(parent) {
|
|
194
200
|
const l = newTransaction(true, parent);
|
|
195
201
|
lanes.push(l);
|
|
@@ -326,8 +332,10 @@ function enterLane(l, c) {
|
|
|
326
332
|
// The pass read a held write before its first lane read (it joined the
|
|
327
333
|
// frame's future as mainline would): lane work sees the screen — the
|
|
328
334
|
// seam re-derives it on the committed world next round (`stagedReaders`,
|
|
329
|
-
// the park's repair), as it does a lane pass that read a staging.
|
|
330
|
-
|
|
335
|
+
// the park's repair), as it does a lane pass that read a staging. Not a
|
|
336
|
+
// pass whose held reads were all of nodes born held (no staged read,
|
|
337
|
+
// `stagedRead`): it has no committed world to re-derive on (#3969).
|
|
338
|
+
if (c._flags & REACTIVE_JOINED && c._flags & REACTIVE_STAGED_READ) stagedRead(c);
|
|
331
339
|
return;
|
|
332
340
|
}
|
|
333
341
|
if (sameLane(p, l)) return setPassLane(l);
|
|
@@ -402,17 +410,22 @@ function verdictLane(t) {
|
|
|
402
410
|
* propagation (async.ts `propagateStatus`), the node gone pending. */
|
|
403
411
|
function laneStage(el, l, create, errored) {
|
|
404
412
|
if (!(create || errored || el._flags & REACTIVE_LANE_READ || el._config & CONFIG_GUESS)) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
413
|
+
const x = el._x;
|
|
414
|
+
if (x !== null) {
|
|
415
|
+
const tx = x._transaction;
|
|
416
|
+
// Cleared before equality, so a return to the committed value notifies
|
|
417
|
+
// nobody. The seam wakes it unless the frame is still held (#3892).
|
|
418
|
+
if (tx?._shown && !blocked(resolveTx(tx._parent))) el._flags |= LEFT_SHOWN;
|
|
419
|
+
if (tx?._lane) x._transaction = null;
|
|
420
|
+
x._lane = NOT_PENDING;
|
|
421
|
+
}
|
|
422
|
+
el._config &= -1050881;
|
|
410
423
|
return false;
|
|
411
424
|
}
|
|
412
425
|
if (!el._type) {
|
|
413
426
|
if (!(el._config & CONFIG_OVERRIDE)) {
|
|
414
427
|
el._pendingValue = NOT_PENDING;
|
|
415
|
-
el._config = (el._config &
|
|
428
|
+
el._config = (el._config & -1048833) | CONFIG_OVERRIDE;
|
|
416
429
|
ext(el);
|
|
417
430
|
}
|
|
418
431
|
// A written guess whose own pass is the lane's work — its source is a
|
|
@@ -464,6 +477,11 @@ function supersede(n, value, changed) {
|
|
|
464
477
|
// lane opened (two actions guessing one slot entangle, A34 (1)) — the
|
|
465
478
|
// truth is held by the transaction that lands, not the merged-away one.
|
|
466
479
|
const parent = resolveTx(l._parent ?? l);
|
|
480
|
+
// A store row answered by a new object the store would reconcile into
|
|
481
|
+
// the guessed one unchanged is an echo, not a correction (#3898): the
|
|
482
|
+
// server's object never matches the guess by reference.
|
|
483
|
+
if (changed && n._config & CONFIG_SLOT_NODE && GlobalQueue._echoes?.(n._x._lane, value))
|
|
484
|
+
changed = false;
|
|
467
485
|
if (changed) {
|
|
468
486
|
// Observe: a displayed guess is being replaced by a differing truth — a
|
|
469
487
|
// landing's (superseded), or the value it covered (reverted: the body
|
|
@@ -476,17 +494,46 @@ function supersede(n, value, changed) {
|
|
|
476
494
|
if (l._shown) n._value = n._x._lane;
|
|
477
495
|
n._config &= -10241;
|
|
478
496
|
n._x._lane = NOT_PENDING;
|
|
479
|
-
n._pendingValue = value;
|
|
480
497
|
holdNode(n, parent);
|
|
481
|
-
joinFuture(parent);
|
|
482
498
|
if (n._subs !== null) insertSubs(n);
|
|
483
499
|
} else {
|
|
484
|
-
n._pendingValue = value;
|
|
485
500
|
n._config |= CONFIG_HELD;
|
|
486
|
-
|
|
487
|
-
|
|
501
|
+
// A confirmation is silent (A17); only truth readers are told.
|
|
502
|
+
GlobalQueue._confirmed?.(l);
|
|
503
|
+
}
|
|
504
|
+
n._pendingValue = value;
|
|
505
|
+
joinFuture(parent);
|
|
506
|
+
}
|
|
507
|
+
/** Truth readers (`until`, `refresh`; CONFIG_AUTHORITATIVE) — pay-for-use:
|
|
508
|
+
* the first one installs these on GlobalQueue (signals.ts `watch`).
|
|
509
|
+
*
|
|
510
|
+
* A truth reader's read of a lane node: the lane's value when it is the
|
|
511
|
+
* truth (#3943), else the truth staged beneath a guess, else the committed
|
|
512
|
+
* value. */
|
|
513
|
+
function truthRead(el) {
|
|
514
|
+
return truth(el)
|
|
515
|
+
? laneValueOf(el)
|
|
516
|
+
: el._config & CONFIG_GUESS && el._pendingValue !== NOT_PENDING
|
|
517
|
+
? el._pendingValue
|
|
518
|
+
: el._value;
|
|
519
|
+
}
|
|
520
|
+
/** A guess confirmed: its lane's truth readers re-read — of the lane's
|
|
521
|
+
* derivations too, which may be the truth now (#3943). */
|
|
522
|
+
function truthConfirmed(l) {
|
|
523
|
+
for (let i = 0; i < l._nodes.length; i++)
|
|
524
|
+
for (let s = l._nodes[i]._subs; s !== null; s = s._nextSub)
|
|
488
525
|
if (s._sub._config & CONFIG_AUTHORITATIVE) enqueueSub(s._sub);
|
|
489
|
-
|
|
526
|
+
}
|
|
527
|
+
/** A lane node's value is the truth when every guess beneath it is
|
|
528
|
+
* confirmed — its source staged a value equal to the guess — and nothing
|
|
529
|
+
* beneath it is held in the parent: same inputs, same value. */
|
|
530
|
+
function truth(n) {
|
|
531
|
+
if (n._config & CONFIG_GUESS)
|
|
532
|
+
return n._pendingValue !== NOT_PENDING && n._equals && n._equals(n._x._lane, n._pendingValue);
|
|
533
|
+
if (n._pendingValue !== NOT_PENDING) return;
|
|
534
|
+
if (n._config & CONFIG_OVERRIDE)
|
|
535
|
+
for (let d = n._deps; d; d = d._nextDep) if (!truth(d._dep)) return;
|
|
536
|
+
return 1;
|
|
490
537
|
}
|
|
491
538
|
/** A lane's end, two ways. `into === null`: the parent landed — the lane's
|
|
492
539
|
* values are the truth now: a guess lands the truth staged beneath it or
|
|
@@ -586,7 +633,7 @@ function dissolveLane(l, into, except) {
|
|
|
586
633
|
} else {
|
|
587
634
|
if (x._pendingFirstChild !== null || x._pendingDisposal !== null)
|
|
588
635
|
GlobalQueue._dispose(n, false, true);
|
|
589
|
-
n._config &= -
|
|
636
|
+
n._config &= -1048961;
|
|
590
637
|
}
|
|
591
638
|
}
|
|
592
639
|
}
|
|
@@ -621,8 +668,7 @@ function dissolveLane(l, into, except) {
|
|
|
621
668
|
* member throws like any. */
|
|
622
669
|
function laneRead(c, el) {
|
|
623
670
|
const guess = el._config & CONFIG_GUESS;
|
|
624
|
-
if (c !== null && c._config & CONFIG_AUTHORITATIVE)
|
|
625
|
-
return guess && el._pendingValue !== NOT_PENDING ? el._pendingValue : el._value;
|
|
671
|
+
if (c !== null && c._config & CONFIG_AUTHORITATIVE) return GlobalQueue._truth(el);
|
|
626
672
|
if (c === null) return laneValueOf(el);
|
|
627
673
|
const l = txOf(el);
|
|
628
674
|
const status = el._statusFlags;
|
|
@@ -734,6 +780,10 @@ function laneSeam(l, leaked) {
|
|
|
734
780
|
reruns(l);
|
|
735
781
|
for (let i = 0; i < l._nodes.length; i++) {
|
|
736
782
|
const n = l._nodes[i];
|
|
783
|
+
if (n._flags & LEFT_SHOWN) {
|
|
784
|
+
n._flags ^= LEFT_SHOWN;
|
|
785
|
+
insertSubs(n);
|
|
786
|
+
}
|
|
737
787
|
const x = n._x;
|
|
738
788
|
if (x._transaction !== l) continue;
|
|
739
789
|
if (!(n._config & CONFIG_GUESS) && !n._type && n._pendingValue !== NOT_PENDING) {
|
|
@@ -886,6 +936,13 @@ GlobalQueue._laneSeams = leaks => {
|
|
|
886
936
|
: !blocked(l._parent)))
|
|
887
937
|
)
|
|
888
938
|
continue;
|
|
939
|
+
// A leaked effect re-derives seated in its lane from its first read: it
|
|
940
|
+
// carries no lane value (`laneStage`), so without this its pass starts
|
|
941
|
+
// as mainline, joins the parked frame on its first held read and reads
|
|
942
|
+
// the staging, then re-enters the lane at its guess read and leaks
|
|
943
|
+
// again — every round (#4014). A computed is already seated by its
|
|
944
|
+
// override, and a written guess's own pass stays the frame's.
|
|
945
|
+
if (l != null && l._lane && r._type) r._flags |= REACTIVE_LANE_DIRTY;
|
|
889
946
|
enqueueSub(r);
|
|
890
947
|
if (l != null && l._lane && leaked.indexOf(l) === -1) leaked.push(l);
|
|
891
948
|
}
|
|
@@ -957,9 +1014,12 @@ function route(c, t) {
|
|
|
957
1014
|
* every flush (fuzzer F12). True when the reader is served the committed
|
|
958
1015
|
* value — not over a flight committed beneath inputs already on screen (A15
|
|
959
1016
|
* reveal corollary, #3305): that value would tear against them, and the
|
|
960
|
-
* reveal observes the flight.
|
|
1017
|
+
* reveal observes the flight. Nor for a reader born into the flight's
|
|
1018
|
+
* transaction (`bornInto`, #4010): it has no screen — it waits on the flight
|
|
1019
|
+
* with the rest of its frame. */
|
|
961
1020
|
function observeFlight(c, el) {
|
|
962
|
-
if (el._config & CONFIG_INPUTS_PUBLISHED
|
|
1021
|
+
if (el._config & CONFIG_INPUTS_PUBLISHED || (el._config & CONFIG_HELD && bornInto(c, txOf(el))))
|
|
1022
|
+
return false;
|
|
963
1023
|
if (globalQueue._running) {
|
|
964
1024
|
joinFuture(null);
|
|
965
1025
|
if (!(el._config & CONFIG_HELD)) queuePendingNode(el);
|
|
@@ -983,9 +1043,10 @@ let probeFound = false;
|
|
|
983
1043
|
* reader re-derives there. `latest` shows the proposal that lands — a
|
|
984
1044
|
* rewrite or a landing reaches its reader through the link it holds — so a
|
|
985
1045
|
* re-run would only repeat the frame. */
|
|
986
|
-
function verdictRead(c, t,
|
|
1046
|
+
function verdictRead(c, t, plain) {
|
|
987
1047
|
route(c, t);
|
|
988
|
-
if (
|
|
1048
|
+
if (plain) staleReader(c, t);
|
|
1049
|
+
else if (probing && t._reruns[t._reruns.length - 1] !== c) t._reruns.push(c);
|
|
989
1050
|
}
|
|
990
1051
|
/** A verdict read of a node staged or in flight but not held yet: whether it
|
|
991
1052
|
* will be is the seam's.
|
|
@@ -1068,11 +1129,14 @@ function pendingVerdict(c, el) {
|
|
|
1068
1129
|
}
|
|
1069
1130
|
}
|
|
1070
1131
|
/** The pass entering a verdict window is a verdict reader (CONFIG_VERDICT):
|
|
1071
|
-
* a dependency going pending re-derives it, the verdict having changed.
|
|
1132
|
+
* a dependency going pending re-derives it, the verdict having changed. Not
|
|
1133
|
+
* an untracked window: that is a snapshot, like any untracked read (#3946 —
|
|
1134
|
+
* a router's link claim probing inside the computation that creates the
|
|
1135
|
+
* anchor). A throw still reaches the pass through `untrack`. */
|
|
1072
1136
|
function markVerdictReader(window) {
|
|
1073
1137
|
let c = context;
|
|
1074
1138
|
if (c?._root) c = c._parentComputed;
|
|
1075
|
-
if (c !== null) {
|
|
1139
|
+
if (c !== null && tracking) {
|
|
1076
1140
|
c._config |= CONFIG_VERDICT;
|
|
1077
1141
|
c._flags |= REACTIVE_PROBED;
|
|
1078
1142
|
// Observe tier: which windows this pass entered (1 `isPending`, 2
|
|
@@ -1154,7 +1218,8 @@ function heldLatest(el, c) {
|
|
|
1154
1218
|
* - an errored derivation throws for every reader (#2897). */
|
|
1155
1219
|
function verdictValue(el, c) {
|
|
1156
1220
|
const owner = el;
|
|
1157
|
-
const
|
|
1221
|
+
const owned = c !== null && !(c._config & CONFIG_CHILDREN_FORBIDDEN);
|
|
1222
|
+
const tracked = owned && tracking;
|
|
1158
1223
|
if (c !== null && tracking) {
|
|
1159
1224
|
link(el, c);
|
|
1160
1225
|
if (owner._fn !== undefined) {
|
|
@@ -1233,9 +1298,14 @@ function verdictValue(el, c) {
|
|
|
1233
1298
|
}
|
|
1234
1299
|
return el._value;
|
|
1235
1300
|
}
|
|
1236
|
-
// Born into the future: its staging is its only value.
|
|
1237
|
-
|
|
1301
|
+
// Born into the future: its staging is its only value. It has no
|
|
1302
|
+
// committed value, so no verdict: `isPending` reads it as its read does —
|
|
1303
|
+
// the owner joins the hold (held with it, or caught by a boundary that
|
|
1304
|
+
// has not shown content) and answers for the commit, where it is final.
|
|
1305
|
+
if (el._pendingValue !== NOT_PENDING && !(owner._statusFlags & STATUS_PENDING)) {
|
|
1306
|
+
if (probing && owned) joinPass(c, el);
|
|
1238
1307
|
return el._pendingValue;
|
|
1308
|
+
}
|
|
1239
1309
|
} else if (el._pendingValue !== NOT_PENDING) {
|
|
1240
1310
|
if (owner._statusFlags & STATUS_PENDING && !uninitialized)
|
|
1241
1311
|
pendingVerdict(tracked ? c : null, owner);
|
|
@@ -2309,6 +2379,10 @@ function createReaction(effectFn, options) {
|
|
|
2309
2379
|
* joined it (L2, born held — A29) and delivers at that commit: the
|
|
2310
2380
|
* committed view, never a foreign hold's unrevealed frame. */
|
|
2311
2381
|
function watch(fn, onValue, onError, config = 0, inPass, own = flushTransaction) {
|
|
2382
|
+
if (config & CONFIG_AUTHORITATIVE) {
|
|
2383
|
+
GlobalQueue._truth = truthRead;
|
|
2384
|
+
GlobalQueue._confirmed = truthConfirmed;
|
|
2385
|
+
}
|
|
2312
2386
|
// `config` is on the node for its first pass too (the pass runs inside
|
|
2313
2387
|
// `computed()`): `until`'s predicate is authoritative from its first read,
|
|
2314
2388
|
// or an already-displayed guess would satisfy it in a later slice. (REDERIVE
|
|
@@ -2446,17 +2520,24 @@ function refresh(target) {
|
|
|
2446
2520
|
const own = flushTransaction;
|
|
2447
2521
|
const promise = new Promise((res, rej) => {
|
|
2448
2522
|
queueMicrotask(() => {
|
|
2449
|
-
//
|
|
2450
|
-
//
|
|
2451
|
-
//
|
|
2452
|
-
// capture runs inside the watcher's
|
|
2453
|
-
// IS the watcher — exactly what
|
|
2523
|
+
// The root is load-bearing: an ownerless computed is auto-disposing,
|
|
2524
|
+
// and the settle walk releases an unobserved one instead of re-running
|
|
2525
|
+
// it, so the waiter would never see the refetch land (#3888). Settle
|
|
2526
|
+
// disposes the waiter directly; the capture runs inside the watcher's
|
|
2527
|
+
// own pass, where the ambient owner IS the watcher — exactly what
|
|
2528
|
+
// dispose() takes.
|
|
2454
2529
|
let waiter = null;
|
|
2455
2530
|
const make = () =>
|
|
2456
2531
|
watch(
|
|
2457
2532
|
() => {
|
|
2458
2533
|
if (waiter === null) waiter = getOwner();
|
|
2459
|
-
|
|
2534
|
+
// Authoritative: the truth beneath a guess (`until`'s view). A
|
|
2535
|
+
// guessed node's own re-ask is not answered by it (#3895): park
|
|
2536
|
+
// on that flight, or reject with its error.
|
|
2537
|
+
const v = read(node);
|
|
2538
|
+
if (node._config & CONFIG_GUESS && node._statusFlags & (STATUS_PENDING | STATUS_ERROR))
|
|
2539
|
+
throw node._x?._error;
|
|
2540
|
+
return v;
|
|
2460
2541
|
},
|
|
2461
2542
|
value => {
|
|
2462
2543
|
res(typeof target === "function" ? value : target);
|
|
@@ -2466,7 +2547,7 @@ function refresh(target) {
|
|
|
2466
2547
|
rej(err);
|
|
2467
2548
|
dispose(waiter);
|
|
2468
2549
|
},
|
|
2469
|
-
CONFIG_FRESH_READ,
|
|
2550
|
+
CONFIG_FRESH_READ | CONFIG_AUTHORITATIVE,
|
|
2470
2551
|
undefined,
|
|
2471
2552
|
own
|
|
2472
2553
|
);
|
|
@@ -3343,6 +3424,15 @@ function compare(key, a, b) {
|
|
|
3343
3424
|
return key ? key(a) === key(b) : true;
|
|
3344
3425
|
}
|
|
3345
3426
|
|
|
3427
|
+
let optHooks = null;
|
|
3428
|
+
function installOptHooks(hooks) {
|
|
3429
|
+
optHooks = hooks;
|
|
3430
|
+
}
|
|
3431
|
+
let affectsHooks = null;
|
|
3432
|
+
function installAffectsHooks(hooks) {
|
|
3433
|
+
affectsHooks = hooks;
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3346
3436
|
/**
|
|
3347
3437
|
* Store — plain stores on L2 (plan sec. 31, S1).
|
|
3348
3438
|
*
|
|
@@ -3373,14 +3463,6 @@ function compare(key, a, b) {
|
|
|
3373
3463
|
* setter (`wk`); overlay drafts for large plain records; no new named
|
|
3374
3464
|
* fields on array targets; zero allocation for adopted-but-unread objects.
|
|
3375
3465
|
*/
|
|
3376
|
-
let optHooks = null;
|
|
3377
|
-
function installOptHooks(hooks) {
|
|
3378
|
-
optHooks = hooks;
|
|
3379
|
-
}
|
|
3380
|
-
let affectsHooks = null;
|
|
3381
|
-
function installAffectsHooks(hooks) {
|
|
3382
|
-
affectsHooks = hooks;
|
|
3383
|
-
}
|
|
3384
3466
|
/** Key of the deep-witness slot node (never a user key). */
|
|
3385
3467
|
const $DEEP = Symbol("STORE_DEEP");
|
|
3386
3468
|
// ---------------------------------------------------------------------------
|
|
@@ -3570,7 +3652,7 @@ function releaseSlot(node) {
|
|
|
3570
3652
|
function noteNode(target, node, key) {
|
|
3571
3653
|
if (target.fam !== null) target.fam.live.add(target);
|
|
3572
3654
|
markDescendants(target);
|
|
3573
|
-
if (affectsHooks !== null) affectsHooks.
|
|
3655
|
+
if (affectsHooks !== null) affectsHooks._born(target, node, key);
|
|
3574
3656
|
}
|
|
3575
3657
|
// Shared slot-node release handler: registered once; the core sweep
|
|
3576
3658
|
// dispatches CONFIG_SLOT_NODE nodes here (graph.ts) instead of holding a
|
|
@@ -3746,6 +3828,7 @@ function notifyContainer(k, pb) {
|
|
|
3746
3828
|
if (k._config & CONFIG_OVERRIDE) setSignal(k, pb);
|
|
3747
3829
|
else {
|
|
3748
3830
|
if (k._config & CONFIG_HELD) joinFuture(txOf(k));
|
|
3831
|
+
if (snapshotCaptureActive) captureWriteSnapshot(k, k._value);
|
|
3749
3832
|
insertSubs(k);
|
|
3750
3833
|
schedule();
|
|
3751
3834
|
}
|
|
@@ -4118,6 +4201,10 @@ GlobalQueue._storeCommit = () => {
|
|
|
4118
4201
|
drainFolds();
|
|
4119
4202
|
optHooks?.sweep();
|
|
4120
4203
|
};
|
|
4204
|
+
/** The store half of `affects()` (store/affects.ts) makes its marks' nodes
|
|
4205
|
+
* and walks records through these: it never imports the engine. */
|
|
4206
|
+
GlobalQueue._storeNode = getNode;
|
|
4207
|
+
GlobalQueue._storeWrappable = isWrappable;
|
|
4121
4208
|
/** A projection's creation run commits directly (a memo's first value is
|
|
4122
4209
|
* its `_value`, not a staging): the draft still writes a clone of the seed,
|
|
4123
4210
|
* nothing is staged on the nodes, and the setter's exit folds the touched
|
|
@@ -4144,7 +4231,10 @@ function foldTarget(t, old) {
|
|
|
4144
4231
|
// the adopted object (shared ownership — never cloned).
|
|
4145
4232
|
t.pb = null;
|
|
4146
4233
|
t.wk = null;
|
|
4147
|
-
} else if (
|
|
4234
|
+
} else if (
|
|
4235
|
+
t.ovl &&
|
|
4236
|
+
(t.v !== old || (!overlayRebuilds(t, pb) && k?._x?._snapshotValue !== old))
|
|
4237
|
+
) {
|
|
4148
4238
|
// Overlay flatten (#3044): the backing keeps its identity, so the
|
|
4149
4239
|
// `t.v === old` gate below skips path copying (the parent slot
|
|
4150
4240
|
// already points here) and the adopted-notify (setter
|
|
@@ -4355,16 +4445,19 @@ function notifyWrites(t) {
|
|
|
4355
4445
|
) {
|
|
4356
4446
|
// Accessor keys: the node is linked for shape-change notification,
|
|
4357
4447
|
// its value is never served (the getter runs with the proxy
|
|
4358
|
-
// receiver on read) — FORCE wakes the readers.
|
|
4448
|
+
// receiver on read) — FORCE wakes the readers. A data descriptor
|
|
4449
|
+
// falls through so the cleared-acc hot read serves it (#3949);
|
|
4450
|
+
// an own getter's descriptor has no `value`, so the compare below
|
|
4451
|
+
// would otherwise keep the getter's result (or FORCE) for `undefined`.
|
|
4359
4452
|
node.acc = isOwnAccessor(pb, key);
|
|
4360
4453
|
const od = Object.getOwnPropertyDescriptor(old, key);
|
|
4361
4454
|
const nd = Object.getOwnPropertyDescriptor(pb, key);
|
|
4362
|
-
if (
|
|
4455
|
+
if (!nd || nd.get || nd.set) {
|
|
4363
4456
|
if (od?.get !== nd?.get || od?.set !== nd?.set || od?.value !== nd?.value)
|
|
4364
4457
|
setSignal(node, () => FORCE);
|
|
4365
4458
|
continue;
|
|
4366
4459
|
}
|
|
4367
|
-
if (!isEqual(od?.value, nd
|
|
4460
|
+
if (od?.get || od?.set || !isEqual(od?.value, nd.value)) setSignal(node, () => nd.value);
|
|
4368
4461
|
continue;
|
|
4369
4462
|
}
|
|
4370
4463
|
const nv = t.del !== null && t.del.has(key) ? undefined : pb[key];
|
|
@@ -4462,14 +4555,15 @@ function notifyKeyDiff(node, key, old, neu, probe = true) {
|
|
|
4462
4555
|
node.acc = isOwnAccessor(neu, key);
|
|
4463
4556
|
const od = Object.getOwnPropertyDescriptor(old, key);
|
|
4464
4557
|
const nd = Object.getOwnPropertyDescriptor(neu, key);
|
|
4465
|
-
|
|
4558
|
+
// Data descriptors fall through, as in notifyWrites (#3949).
|
|
4559
|
+
if (!nd || nd.get || nd.set) {
|
|
4466
4560
|
if (od?.get !== nd?.get || od?.set !== nd?.set || od?.value !== nd?.value)
|
|
4467
4561
|
setSignal(node, () => FORCE);
|
|
4468
4562
|
return;
|
|
4469
4563
|
}
|
|
4470
4564
|
const ov = od?.value;
|
|
4471
4565
|
const nv = nd?.value;
|
|
4472
|
-
if (!isEqual(ov, nv) && !targetsEqual(ov, nv))
|
|
4566
|
+
if (od?.get || od?.set || (!isEqual(ov, nv) && !targetsEqual(ov, nv)))
|
|
4473
4567
|
setSignal(node, typeof nv === "function" ? () => nv : nv);
|
|
4474
4568
|
} else {
|
|
4475
4569
|
const ov = old[key];
|
|
@@ -4710,7 +4804,67 @@ function readSource(target, key, shape = false) {
|
|
|
4710
4804
|
/** Did the HELD staging (the container node's, not a mainline layer above
|
|
4711
4805
|
* it) change `key`? */
|
|
4712
4806
|
function heldKeyChanged(target, key, shape = true) {
|
|
4713
|
-
return
|
|
4807
|
+
return (
|
|
4808
|
+
changedBetween(target, target.k._value, target.k._pendingValue, key, shape) &&
|
|
4809
|
+
!adoptChild(target, key)
|
|
4810
|
+
);
|
|
4811
|
+
}
|
|
4812
|
+
/** The values of a held frame, built once per frame: neither the committed
|
|
4813
|
+
* frame nor the held staging changes while the hold stands (a draft writes a
|
|
4814
|
+
* clone), so the set stays true for every first read of the hold. */
|
|
4815
|
+
const frameRaws = new WeakMap();
|
|
4816
|
+
function rawsOf(frame) {
|
|
4817
|
+
let raws = frameRaws.get(frame);
|
|
4818
|
+
if (raws === undefined) frameRaws.set(frame, (raws = new Set(Object.values(frame))));
|
|
4819
|
+
return raws;
|
|
4820
|
+
}
|
|
4821
|
+
/** The same row at a slot, as the adoption pairs rows (reconcile.ts): two
|
|
4822
|
+
* objects whose keys agree — positionally for an unkeyed family. */
|
|
4823
|
+
function sameRow$1(pv, nv, keyFn) {
|
|
4824
|
+
return (
|
|
4825
|
+
pv !== null &&
|
|
4826
|
+
typeof pv === "object" &&
|
|
4827
|
+
nv !== null &&
|
|
4828
|
+
typeof nv === "object" &&
|
|
4829
|
+
(keyFn === null || sameKey(keyFn(pv), keyFn(nv)))
|
|
4830
|
+
);
|
|
4831
|
+
}
|
|
4832
|
+
/** A key of a held adoption whose old and new values are the same row that
|
|
4833
|
+
* nothing had proxied when the adoption ran, so `descend` never paired
|
|
4834
|
+
* them: paired now, as it would have been (#3712) — one target, the old
|
|
4835
|
+
* raw its committed frame, the new raw its staging, held by the same
|
|
4836
|
+
* transaction. Both raws resolve to it, so the slot reads as unchanged and
|
|
4837
|
+
* the row's own leaves decide: `id` the same in both frames holds no one,
|
|
4838
|
+
* `version` holds. A record's keys pair by name; an array's by the family's
|
|
4839
|
+
* key — without a family the pairing is not known here and the hold stands. */
|
|
4840
|
+
function adoptChild(target, key) {
|
|
4841
|
+
const k = target.k;
|
|
4842
|
+
const old = asBacking(k._value);
|
|
4843
|
+
const ov = old[key];
|
|
4844
|
+
const nv = k._pendingValue[key];
|
|
4845
|
+
const keyFn = Array.isArray(old) ? target.fam?.key : null;
|
|
4846
|
+
if (
|
|
4847
|
+
target.s ||
|
|
4848
|
+
keyFn === undefined ||
|
|
4849
|
+
!sameRow$1(ov, nv, keyFn) ||
|
|
4850
|
+
!isWrappable(ov) ||
|
|
4851
|
+
!isWrappable(nv) ||
|
|
4852
|
+
Array.isArray(ov) !== Array.isArray(nv) ||
|
|
4853
|
+
lookupTarget(ov, target.fam) !== undefined ||
|
|
4854
|
+
lookupTarget(nv, target.fam) !== undefined ||
|
|
4855
|
+
// A raw in both frames (the same objects swapped or reordered) is its
|
|
4856
|
+
// own row: pairing it with another would alias the two after the commit.
|
|
4857
|
+
rawsOf(old).has(nv) ||
|
|
4858
|
+
rawsOf(k._pendingValue).has(ov)
|
|
4859
|
+
)
|
|
4860
|
+
return false;
|
|
4861
|
+
// A raw-marked value wraps to itself, a proxy to its own target: neither
|
|
4862
|
+
// is a fresh target around `ov`.
|
|
4863
|
+
const ct = wrap(ov, target, key)[$TARGET];
|
|
4864
|
+
if (ct === undefined || ct.v !== ov) return false;
|
|
4865
|
+
adoptPB(ct, nv, false);
|
|
4866
|
+
holdNode(getContainerNode(ct), txOf(k));
|
|
4867
|
+
return true;
|
|
4714
4868
|
}
|
|
4715
4869
|
/** A family target's unheld staging, read with no pass (a handler). */
|
|
4716
4870
|
function familyAhead(target) {
|
|
@@ -4839,11 +4993,13 @@ function pullFamily(target) {
|
|
|
4839
4993
|
// One that already observes the flight (linked by the pass that saw it
|
|
4840
4994
|
// go up — the frame's hold, `blocked`) keeps observing it: a re-run for
|
|
4841
4995
|
// another reason (a guess it read, the seam) that dropped the link would
|
|
4842
|
-
// release the hold with the flight still up.
|
|
4996
|
+
// release the hold with the flight still up. An errored derive has no
|
|
4997
|
+
// landing to learn of: the error is what the frame shows.
|
|
4843
4998
|
const c = context;
|
|
4844
4999
|
if (
|
|
4845
5000
|
c !== null &&
|
|
4846
5001
|
c._type === EFFECT_RENDER &&
|
|
5002
|
+
!(fw._statusFlags & STATUS_ERROR) &&
|
|
4847
5003
|
fw._config & CONFIG_HELD &&
|
|
4848
5004
|
flushTransaction !== txOf(fw) &&
|
|
4849
5005
|
!linkedTo(c, fw)
|
|
@@ -4955,7 +5111,7 @@ const traps = {
|
|
|
4955
5111
|
// (The witness before the pull: a mark on an uninitialized derived
|
|
4956
5112
|
// store is witnessed, then the pull throws — loading, declared pending.)
|
|
4957
5113
|
if (verdict !== null && affectsHooks !== null && getObserver() === null)
|
|
4958
|
-
affectsHooks.
|
|
5114
|
+
affectsHooks._witness(target, key);
|
|
4959
5115
|
if (target.fam !== null) pullFamily(target);
|
|
4960
5116
|
// Hot inline case: existing PLAIN node (non-accessor) read outside any
|
|
4961
5117
|
// draft — the dbmon/uibench effect re-read shape. Core's `read()` serves
|
|
@@ -5102,7 +5258,7 @@ const traps = {
|
|
|
5102
5258
|
// (The witness before the pull: a mark on an uninitialized derived
|
|
5103
5259
|
// store is witnessed, then the pull throws — loading, declared pending.)
|
|
5104
5260
|
if (verdict !== null && affectsHooks !== null && getObserver() === null)
|
|
5105
|
-
affectsHooks.
|
|
5261
|
+
affectsHooks._witness(target, key);
|
|
5106
5262
|
if (target.fam !== null) pullFamily(target);
|
|
5107
5263
|
const src = readSource(target, key);
|
|
5108
5264
|
// A tracked reader's presence node answers (born from the two frames;
|
|
@@ -5127,7 +5283,7 @@ const traps = {
|
|
|
5127
5283
|
},
|
|
5128
5284
|
ownKeys(target) {
|
|
5129
5285
|
if (verdict !== null && affectsHooks !== null && getObserver() === null)
|
|
5130
|
-
affectsHooks.
|
|
5286
|
+
affectsHooks._witness(target, undefined);
|
|
5131
5287
|
if (target.fam !== null) pullFamily(target);
|
|
5132
5288
|
return visibleKeys(target, enumerationSource(target));
|
|
5133
5289
|
},
|
|
@@ -5135,7 +5291,7 @@ const traps = {
|
|
|
5135
5291
|
if (key === $OWNER || key === $RECORD) return undefined;
|
|
5136
5292
|
const obs = getObserver();
|
|
5137
5293
|
if (verdict !== null && affectsHooks !== null && obs === null)
|
|
5138
|
-
affectsHooks.
|
|
5294
|
+
affectsHooks._witness(target, key);
|
|
5139
5295
|
if (target.fam !== null) pullFamily(target);
|
|
5140
5296
|
// An enumerator (spread, Object.entries) already holds the container
|
|
5141
5297
|
// node and reads its frame; a descriptor read on its own tracks
|
|
@@ -5662,127 +5818,6 @@ const storePath = /* @__PURE__ */ Object.assign(
|
|
|
5662
5818
|
{ DELETE }
|
|
5663
5819
|
);
|
|
5664
5820
|
|
|
5665
|
-
/**
|
|
5666
|
-
* Store — the store half of `affects()` (plan sec. 29, plan sec. 37; A24 (4), #2882,
|
|
5667
|
-
* #2893, #2904).
|
|
5668
|
-
*
|
|
5669
|
-
* A mark on a store is a mark on nodes (affects.ts: a count on the node, a
|
|
5670
|
-
* dependency walk at probe time): `affects(record, key)` marks the slot's
|
|
5671
|
-
* leaf; `affects(record)` marks a carrier (the record's `$AFFECTS` leaf)
|
|
5672
|
-
* and every live node in the record's subtree — the edges existing readers
|
|
5673
|
-
* subscribed through. Coverage is by RAW IDENTITY, not read path (#2882: a
|
|
5674
|
-
* captured row proxy, a derived store sharing the source's raw, never read
|
|
5675
|
-
* through the declaration's proxy): the identities reachable at the
|
|
5676
|
-
* declaration are the mark's `scope`, so a node born inside the window on
|
|
5677
|
-
* a covered record inherits the mark (released with the carrier's last),
|
|
5678
|
-
* and an untracked probe through a record with no nodes is witnessed into
|
|
5679
|
-
* the verdict. A mark declared on a draft (inside a setter) or over an
|
|
5680
|
-
* optimistic family walks the view the writer sees.
|
|
5681
|
-
*
|
|
5682
|
-
* Installed by `store/index.ts`: a program with stores carries it; one
|
|
5683
|
-
* without pays nothing (affects.ts asks `GlobalQueue._storeMarks`).
|
|
5684
|
-
*/
|
|
5685
|
-
/** The keyless mark's carrier key: a leaf of the record no read serves. */
|
|
5686
|
-
const $AFFECTS = Symbol("STORE_AFFECTS");
|
|
5687
|
-
const scopes = new Map();
|
|
5688
|
-
/** The nodes an `affects(store[, key])` declaration marks (affects.ts
|
|
5689
|
-
* registers each). */
|
|
5690
|
-
function storeMarks(t, key) {
|
|
5691
|
-
if (key === undefined) {
|
|
5692
|
-
const carrier = getNode(t, $AFFECTS);
|
|
5693
|
-
let entry = scopes.get(carrier);
|
|
5694
|
-
if (entry === undefined) scopes.set(carrier, (entry = { scope: new Set(), inherited: [] }));
|
|
5695
|
-
const found = [carrier];
|
|
5696
|
-
walk(t.px, entry, found, t.fam, new Set());
|
|
5697
|
-
return found;
|
|
5698
|
-
}
|
|
5699
|
-
const node = t.n?.[key] ?? getNode(t, key);
|
|
5700
|
-
let entry = scopes.get(node);
|
|
5701
|
-
if (entry === undefined) scopes.set(node, (entry = { scope: new Set(), inherited: [], key }));
|
|
5702
|
-
entry.scope.add(t.v);
|
|
5703
|
-
if (t.pb !== null) entry.scope.add(t.pb);
|
|
5704
|
-
return [node];
|
|
5705
|
-
}
|
|
5706
|
-
/** The identities reachable from `value` into the scope (the draft's and
|
|
5707
|
-
* the committed backing both, when a draft is open; an optimistic family's
|
|
5708
|
-
* view), every live node under each record into `found`. Untracked by
|
|
5709
|
-
* construction: raws, never traps. */
|
|
5710
|
-
function walk(value, entry, found, fam, visited) {
|
|
5711
|
-
if (!isWrappable(value)) return;
|
|
5712
|
-
const t = value[$TARGET] ?? lookupTarget(value, fam);
|
|
5713
|
-
let raw = t !== undefined ? (t.pb ?? t.v) : value;
|
|
5714
|
-
if (visited.has(raw)) return;
|
|
5715
|
-
visited.add(raw);
|
|
5716
|
-
entry.scope.add(raw);
|
|
5717
|
-
if (t !== undefined) {
|
|
5718
|
-
if (t.pb !== null) entry.scope.add(t.v);
|
|
5719
|
-
// The writer's view: the tick's own unflushed guesses are in motion too.
|
|
5720
|
-
if (t.fam?.opt === true) raw = optHooks.view(t, raw, true);
|
|
5721
|
-
const nodes = t.n;
|
|
5722
|
-
if (nodes !== null)
|
|
5723
|
-
for (const k of Reflect.ownKeys(nodes))
|
|
5724
|
-
// Another mark's carrier is its own channel: counting it here would
|
|
5725
|
-
// extend that scope's lifetime to this declaration's.
|
|
5726
|
-
if (k !== $AFFECTS) found.push(nodes[k]);
|
|
5727
|
-
const has = t.h;
|
|
5728
|
-
if (has !== null) for (const k of Reflect.ownKeys(has)) found.push(has[k]);
|
|
5729
|
-
if (t.k !== null) found.push(t.k);
|
|
5730
|
-
if (t.dk !== null) found.push(t.dk);
|
|
5731
|
-
fam = t.fam ?? fam;
|
|
5732
|
-
}
|
|
5733
|
-
for (const k of Reflect.ownKeys(raw)) {
|
|
5734
|
-
if (k === $OWNER) continue;
|
|
5735
|
-
const d = Object.getOwnPropertyDescriptor(raw, k);
|
|
5736
|
-
if (d === undefined || d.get !== undefined) continue;
|
|
5737
|
-
walk(d.value, entry, found, fam, visited);
|
|
5738
|
-
}
|
|
5739
|
-
}
|
|
5740
|
-
/** A live scope covering `t`'s identity (keyed: for `key`). Chained
|
|
5741
|
-
* backings (§7b): a view's backing is another store's proxy — marks cover
|
|
5742
|
-
* the BASE raw, so every identity along the chain is checked. */
|
|
5743
|
-
function covering(t, key, skip) {
|
|
5744
|
-
for (const [carrier, entry] of scopes) {
|
|
5745
|
-
if (carrier === skip || (entry.key !== undefined && entry.key !== key)) continue;
|
|
5746
|
-
for (let r = t.v; ; ) {
|
|
5747
|
-
if (entry.scope.has(r)) return carrier;
|
|
5748
|
-
const inner = r?.[$TARGET];
|
|
5749
|
-
if (inner === undefined) break;
|
|
5750
|
-
const backing = inner.pb ?? inner.v;
|
|
5751
|
-
if (backing === r) break;
|
|
5752
|
-
r = backing;
|
|
5753
|
-
}
|
|
5754
|
-
}
|
|
5755
|
-
return null;
|
|
5756
|
-
}
|
|
5757
|
-
installAffectsHooks({
|
|
5758
|
-
// A node born on a record a live mark covers inherits it (released with
|
|
5759
|
-
// the carrier's last registration).
|
|
5760
|
-
born(t, node, key) {
|
|
5761
|
-
if (key === $AFFECTS) return;
|
|
5762
|
-
const carrier = covering(t, key, node);
|
|
5763
|
-
if (carrier === null) return;
|
|
5764
|
-
GlobalQueue._mark(node);
|
|
5765
|
-
scopes.get(carrier).inherited.push(node);
|
|
5766
|
-
},
|
|
5767
|
-
// An untracked probe (`isPending(() => s.x)` with no observer) reading a
|
|
5768
|
-
// record a live mark covers: no node carries the mark for it — the
|
|
5769
|
-
// verdict is told directly.
|
|
5770
|
-
witness(t, key) {
|
|
5771
|
-
const own = t.n?.[$AFFECTS];
|
|
5772
|
-
if ((own !== undefined && own._x !== null && own._x._marks !== 0) || covering(t, key) !== null)
|
|
5773
|
-
GlobalQueue._witnessMark();
|
|
5774
|
-
}
|
|
5775
|
-
});
|
|
5776
|
-
GlobalQueue._storeMarks = storeMarks;
|
|
5777
|
-
/** The carrier's last mark released (affects.ts): its scope dies, and the
|
|
5778
|
-
* nodes that inherited the mark release theirs. */
|
|
5779
|
-
GlobalQueue._releaseMarkScope = carrier => {
|
|
5780
|
-
const entry = scopes.get(carrier);
|
|
5781
|
-
if (entry === undefined) return;
|
|
5782
|
-
scopes.delete(carrier);
|
|
5783
|
-
GlobalQueue._releaseMarks(entry.inherited);
|
|
5784
|
-
};
|
|
5785
|
-
|
|
5786
5821
|
/**
|
|
5787
5822
|
* Store — reconcile: the immutable-diff adoption channel (INTERNALS §3).
|
|
5788
5823
|
*
|
|
@@ -5874,17 +5909,7 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
|
|
|
5874
5909
|
for (const end = Math.min(plen, nlen); i < end; i++) {
|
|
5875
5910
|
const nv = nextRows[i];
|
|
5876
5911
|
const pvRaw = prevRows[i];
|
|
5877
|
-
if (
|
|
5878
|
-
pvRaw !== nv &&
|
|
5879
|
-
!(
|
|
5880
|
-
pvRaw !== null &&
|
|
5881
|
-
typeof pvRaw === "object" &&
|
|
5882
|
-
nv !== null &&
|
|
5883
|
-
typeof nv === "object" &&
|
|
5884
|
-
sameKey(keyFn(pvRaw), keyFn(nv))
|
|
5885
|
-
)
|
|
5886
|
-
)
|
|
5887
|
-
break; // misaligned: fall to the keyed remainder below
|
|
5912
|
+
if (pvRaw !== nv && !sameRow$1(pvRaw, nv, keyFn)) break; // misaligned: fall to the keyed remainder below
|
|
5888
5913
|
if (
|
|
5889
5914
|
(pvRaw !== nv || (nv !== null && typeof nv === "object" && isOwned(nv))) &&
|
|
5890
5915
|
nv !== null &&
|
|
@@ -6296,7 +6321,9 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
|
|
|
6296
6321
|
// A flight went up (NotReady out of the pass — the derive's own, with
|
|
6297
6322
|
// the derive as its source): the leaves' readers learn it from here —
|
|
6298
6323
|
// they do not subscribe to the derive.
|
|
6299
|
-
|
|
6324
|
+
// A synchronous error the same way (#3956: a rerun that finds the
|
|
6325
|
+
// entry failed throws): every live reader re-derives and meets it.
|
|
6326
|
+
if (!first) e instanceof NotReadyError ? wakeFamily(fam, e) : errorFamily(fam);
|
|
6300
6327
|
throw e;
|
|
6301
6328
|
}
|
|
6302
6329
|
// A pass that returned with a flight up (an iterator's sync first yield):
|
|
@@ -6313,11 +6340,20 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
|
|
|
6313
6340
|
return owner;
|
|
6314
6341
|
}
|
|
6315
6342
|
function createProjectionInternal(fn, seed, options) {
|
|
6343
|
+
const keyOption = options?.key === undefined ? "id" : options.key;
|
|
6316
6344
|
const fam = {
|
|
6317
6345
|
map: new WeakMap(),
|
|
6318
6346
|
node: null,
|
|
6319
6347
|
live: new Set(),
|
|
6320
|
-
shallow: !!options?.shallow
|
|
6348
|
+
shallow: !!options?.shallow,
|
|
6349
|
+
// The row key, for a child paired after the adoption (store.ts
|
|
6350
|
+
// `adoptChild`, #3712) as the adoption pairs them.
|
|
6351
|
+
key:
|
|
6352
|
+
typeof keyOption === "function"
|
|
6353
|
+
? keyOption
|
|
6354
|
+
: keyOption === null
|
|
6355
|
+
? null
|
|
6356
|
+
: row => (isWrappable(row) ? row[keyOption] : undefined)
|
|
6321
6357
|
};
|
|
6322
6358
|
const store = wrap(seed, null, null, fam);
|
|
6323
6359
|
if (fam.shallow) {
|
|
@@ -6332,7 +6368,7 @@ function createProjectionInternal(fn, seed, options) {
|
|
|
6332
6368
|
}
|
|
6333
6369
|
const node = computed(() => {
|
|
6334
6370
|
if (!fam.node) fam.node = getOwner();
|
|
6335
|
-
runProjectionComputed(store, fn,
|
|
6371
|
+
runProjectionComputed(store, fn, keyOption);
|
|
6336
6372
|
}, nodeOptions);
|
|
6337
6373
|
node._config &= ~CONFIG_AUTO_DISPOSE;
|
|
6338
6374
|
fam.node = node;
|
|
@@ -6494,8 +6530,38 @@ function rowSame(a, b, keyFn) {
|
|
|
6494
6530
|
if (keyFn === null || !isWrappable(a) || !isWrappable(b)) return false;
|
|
6495
6531
|
const ka = keyFn(unwrapValue(a));
|
|
6496
6532
|
const kb = keyFn(unwrapValue(b));
|
|
6533
|
+
// Keyless rows are identified by position — reconcile's fallback when the
|
|
6534
|
+
// key is missing. The caller already paired these by index, so two keyless
|
|
6535
|
+
// rows are the same row and the landing confirms the arrangement instead of
|
|
6536
|
+
// dissolving it. Dissolving held an external echo under the action, and a
|
|
6537
|
+
// mainline `until` then timed out on a source that already contained it
|
|
6538
|
+
// (#3898). Keyed rows still match only on the key.
|
|
6539
|
+
if (ka === undefined && kb === undefined) return true;
|
|
6497
6540
|
return ka !== undefined && sameKey(ka, kb);
|
|
6498
6541
|
}
|
|
6542
|
+
/** Whether reconcile would write anything into `a` from `b`: wrappable
|
|
6543
|
+
* pairs descend, leaves compare as its writes do. A key mismatch is a
|
|
6544
|
+
* replacement, which a content difference already says. Plain data — the
|
|
6545
|
+
* server's echo of a guessed row is a new object with the same contents. */
|
|
6546
|
+
function echoes(a, b) {
|
|
6547
|
+
if (a === b) return true;
|
|
6548
|
+
if (!isWrappable(a) || !isWrappable(b)) return false;
|
|
6549
|
+
a = unwrapValue(a);
|
|
6550
|
+
b = unwrapValue(b);
|
|
6551
|
+
const ak = Object.keys(a);
|
|
6552
|
+
if (Array.isArray(a) !== Array.isArray(b) || ak.length !== Object.keys(b).length) return false;
|
|
6553
|
+
for (const k of ak) if (!hasOwn.call(b, k) || !echoes(a[k], b[k])) return false;
|
|
6554
|
+
return true;
|
|
6555
|
+
}
|
|
6556
|
+
// A cycle (a row that references itself) is not plain data: no match, so
|
|
6557
|
+
// the landing stays a correction.
|
|
6558
|
+
GlobalQueue._echoes = (a, b) => {
|
|
6559
|
+
try {
|
|
6560
|
+
return echoes(a, b);
|
|
6561
|
+
} catch {
|
|
6562
|
+
return false;
|
|
6563
|
+
}
|
|
6564
|
+
};
|
|
6499
6565
|
function arrangementEqual(a, b, keyFn) {
|
|
6500
6566
|
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
6501
6567
|
if (Array.isArray(a)) {
|
|
@@ -7934,6 +8000,137 @@ function deep(value) {
|
|
|
7934
8000
|
return deep$1(value);
|
|
7935
8001
|
}
|
|
7936
8002
|
|
|
8003
|
+
/**
|
|
8004
|
+
* Store — the store half of `affects()` (plan sec. 29, plan sec. 37; A24 (4), #2882,
|
|
8005
|
+
* #2893, #2904).
|
|
8006
|
+
*
|
|
8007
|
+
* A mark on a store is a mark on nodes (affects.ts: a count on the node, a
|
|
8008
|
+
* dependency walk at probe time): `affects(record, key)` marks the slot's
|
|
8009
|
+
* leaf; `affects(record)` marks a carrier (the record's `$AFFECTS` leaf)
|
|
8010
|
+
* and every live node in the record's subtree — the edges existing readers
|
|
8011
|
+
* subscribed through. Coverage is by RAW IDENTITY, not read path (#2882: a
|
|
8012
|
+
* captured row proxy, a derived store sharing the source's raw, never read
|
|
8013
|
+
* through the declaration's proxy): the identities reachable at the
|
|
8014
|
+
* declaration are the mark's `scope`, so a node born inside the window on
|
|
8015
|
+
* a covered record inherits the mark (released with the carrier's last),
|
|
8016
|
+
* and an untracked probe through a record with no nodes is witnessed into
|
|
8017
|
+
* the verdict. A mark declared on a draft (inside a setter) or over an
|
|
8018
|
+
* optimistic family walks the view the writer sees.
|
|
8019
|
+
*
|
|
8020
|
+
* Imported by `affects()`, which installs it on its first store target
|
|
8021
|
+
* (#3891, after #3912): a program with stores that never declares a mark
|
|
8022
|
+
* carries none of it. It never imports store.ts — the engine's top level —
|
|
8023
|
+
* so a program declaring marks without stores carries this module alone:
|
|
8024
|
+
* the store's nodes and its wrappability test come through `GlobalQueue`
|
|
8025
|
+
* (`_storeNode`, `_storeWrappable`), and node birth and the untracked traps
|
|
8026
|
+
* reach back through `affectsHooks` (hooks.ts).
|
|
8027
|
+
*/
|
|
8028
|
+
/** The keyless mark's carrier key: a leaf of the record no read serves. */
|
|
8029
|
+
const $AFFECTS = Symbol("STORE_AFFECTS");
|
|
8030
|
+
const scopes = new Map();
|
|
8031
|
+
function scopeOf(carrier, key) {
|
|
8032
|
+
let entry = scopes.get(carrier);
|
|
8033
|
+
if (entry === undefined)
|
|
8034
|
+
scopes.set(carrier, (entry = { _scope: new Set(), _inherited: [], _key: key }));
|
|
8035
|
+
return entry;
|
|
8036
|
+
}
|
|
8037
|
+
/** The nodes an `affects(store[, key])` declaration marks (affects.ts
|
|
8038
|
+
* registers each). */
|
|
8039
|
+
function storeMarks(t, key) {
|
|
8040
|
+
const node = GlobalQueue._storeNode(t, key === undefined ? $AFFECTS : key);
|
|
8041
|
+
const entry = scopeOf(node, key);
|
|
8042
|
+
if (key === undefined) {
|
|
8043
|
+
const found = [node];
|
|
8044
|
+
walk(t.px, entry, found, t.fam, new Set());
|
|
8045
|
+
return found;
|
|
8046
|
+
}
|
|
8047
|
+
entry._scope.add(t.v);
|
|
8048
|
+
if (t.pb !== null) entry._scope.add(t.pb);
|
|
8049
|
+
return [node];
|
|
8050
|
+
}
|
|
8051
|
+
/** The identities reachable from `value` into the scope (the draft's and
|
|
8052
|
+
* the committed backing both, when a draft is open; an optimistic family's
|
|
8053
|
+
* view), every live node under each record into `found`. Untracked by
|
|
8054
|
+
* construction: raws, never traps. */
|
|
8055
|
+
function walk(value, entry, found, fam, visited) {
|
|
8056
|
+
if (!GlobalQueue._storeWrappable(value)) return;
|
|
8057
|
+
const t = value[$TARGET] ?? lookupTarget(value, fam);
|
|
8058
|
+
let raw = t !== undefined ? (t.pb ?? t.v) : value;
|
|
8059
|
+
if (visited.has(raw)) return;
|
|
8060
|
+
visited.add(raw);
|
|
8061
|
+
entry._scope.add(raw);
|
|
8062
|
+
if (t !== undefined) {
|
|
8063
|
+
if (t.pb !== null) entry._scope.add(t.v);
|
|
8064
|
+
// The writer's view: the tick's own unflushed guesses are in motion too.
|
|
8065
|
+
if (t.fam?.opt === true) raw = optHooks.view(t, raw, true);
|
|
8066
|
+
const nodes = t.n;
|
|
8067
|
+
if (nodes !== null)
|
|
8068
|
+
for (const k of Reflect.ownKeys(nodes))
|
|
8069
|
+
// Another mark's carrier is its own channel: counting it here would
|
|
8070
|
+
// extend that scope's lifetime to this declaration's.
|
|
8071
|
+
if (k !== $AFFECTS) found.push(nodes[k]);
|
|
8072
|
+
const has = t.h;
|
|
8073
|
+
if (has !== null) for (const k of Reflect.ownKeys(has)) found.push(has[k]);
|
|
8074
|
+
if (t.k !== null) found.push(t.k);
|
|
8075
|
+
if (t.dk !== null) found.push(t.dk);
|
|
8076
|
+
fam = t.fam ?? fam;
|
|
8077
|
+
}
|
|
8078
|
+
for (const k of Reflect.ownKeys(raw))
|
|
8079
|
+
if (k !== $OWNER) {
|
|
8080
|
+
const d = Object.getOwnPropertyDescriptor(raw, k);
|
|
8081
|
+
if (d.get === undefined) walk(d.value, entry, found, fam, visited);
|
|
8082
|
+
}
|
|
8083
|
+
}
|
|
8084
|
+
/** A live scope covering `t`'s identity (keyed: for `key`). Chained
|
|
8085
|
+
* backings (§7b): a view's backing is another store's proxy — marks cover
|
|
8086
|
+
* the BASE raw, so every identity along the chain is checked. */
|
|
8087
|
+
function covering(t, key, skip) {
|
|
8088
|
+
for (const [carrier, entry] of scopes) {
|
|
8089
|
+
if (carrier === skip || (entry._key !== undefined && entry._key !== key)) continue;
|
|
8090
|
+
for (let r = t.v; ; ) {
|
|
8091
|
+
if (entry._scope.has(r)) return carrier;
|
|
8092
|
+
const inner = r?.[$TARGET];
|
|
8093
|
+
if (inner === undefined) break;
|
|
8094
|
+
const backing = inner.pb ?? inner.v;
|
|
8095
|
+
if (backing === r) break;
|
|
8096
|
+
r = backing;
|
|
8097
|
+
}
|
|
8098
|
+
}
|
|
8099
|
+
return null;
|
|
8100
|
+
}
|
|
8101
|
+
/** The store's side: node birth and the untracked traps call it (store.ts,
|
|
8102
|
+
* through `affectsHooks`) — with no live mark too: an empty scope map is
|
|
8103
|
+
* no work. */
|
|
8104
|
+
const hooks = {
|
|
8105
|
+
// A node born on a record a live mark covers inherits it (released with
|
|
8106
|
+
// the carrier's last registration).
|
|
8107
|
+
_born(t, node, key) {
|
|
8108
|
+
if (scopes.size === 0 || key === $AFFECTS) return;
|
|
8109
|
+
const carrier = covering(t, key, node);
|
|
8110
|
+
if (!carrier) return;
|
|
8111
|
+
GlobalQueue._mark(node);
|
|
8112
|
+
scopes.get(carrier)._inherited.push(node);
|
|
8113
|
+
},
|
|
8114
|
+
// An untracked probe (`isPending(() => s.x)` with no observer) reading a
|
|
8115
|
+
// record a live mark covers: no node carries the mark for it — the
|
|
8116
|
+
// verdict is told directly.
|
|
8117
|
+
_witness(t, key) {
|
|
8118
|
+
if (t.n?.[$AFFECTS]?._x?._marks || covering(t, key)) GlobalQueue._witnessMark();
|
|
8119
|
+
}
|
|
8120
|
+
};
|
|
8121
|
+
/** `affects()` calls it before a store-targeted declaration (idempotent). */
|
|
8122
|
+
function installStoreAffects() {
|
|
8123
|
+
installAffectsHooks(hooks);
|
|
8124
|
+
}
|
|
8125
|
+
/** The carrier's last mark released (affects.ts): its scope dies, and the
|
|
8126
|
+
* nodes that inherited the mark release theirs. */
|
|
8127
|
+
function releaseMarkScope(carrier) {
|
|
8128
|
+
const entry = scopes.get(carrier);
|
|
8129
|
+
if (entry === undefined) return;
|
|
8130
|
+
scopes.delete(carrier);
|
|
8131
|
+
GlobalQueue._releaseMarks(entry._inherited);
|
|
8132
|
+
}
|
|
8133
|
+
|
|
7937
8134
|
// `affects()` — the declaration verb of the pending model (A24 (4), ruled
|
|
7938
8135
|
// 2026-07-13; the #2893 audit corollaries; rebuilt on L2 2026-10-02 as ruled:
|
|
7939
8136
|
// transaction-inert).
|
|
@@ -8001,7 +8198,7 @@ function release$1(nodes) {
|
|
|
8001
8198
|
repoll(n);
|
|
8002
8199
|
// A store mark's carrier: its scope (and the marks the nodes born in
|
|
8003
8200
|
// its window inherited) goes with it.
|
|
8004
|
-
|
|
8201
|
+
releaseMarkScope(n);
|
|
8005
8202
|
}
|
|
8006
8203
|
}
|
|
8007
8204
|
nodes.length = 0;
|
|
@@ -8032,7 +8229,8 @@ function affects(target, key) {
|
|
|
8032
8229
|
// record's carrier and every live node under it.
|
|
8033
8230
|
const t = target?.[$TARGET];
|
|
8034
8231
|
if (t !== undefined) {
|
|
8035
|
-
|
|
8232
|
+
installStoreAffects();
|
|
8233
|
+
const nodes = storeMarks(t, key);
|
|
8036
8234
|
for (let i = 0; i < nodes.length; i++) register(nodes[i]);
|
|
8037
8235
|
schedule();
|
|
8038
8236
|
return;
|
|
@@ -8354,8 +8552,11 @@ function prune(b, pass) {
|
|
|
8354
8552
|
// Landed, held: the content is that transaction's — the output's
|
|
8355
8553
|
// pass enters it and the reveal lands with the reader's run. The
|
|
8356
8554
|
// seam, past its park decision, waits for the commit instead. (A
|
|
8357
|
-
// lane's reader reveals with the lane, this round.)
|
|
8358
|
-
|
|
8555
|
+
// lane's reader reveals with the lane, this round.) A boundary that
|
|
8556
|
+
// has not shown content catches it instead: the fallback stays until
|
|
8557
|
+
// the reader is committed (#3540: what a fresh boundary catches holds
|
|
8558
|
+
// nothing outside it).
|
|
8559
|
+
if (!pass || !b._initialized) continue;
|
|
8359
8560
|
joinFuture(txOf(r));
|
|
8360
8561
|
}
|
|
8361
8562
|
b._readers.delete(r);
|
|
@@ -8541,7 +8742,8 @@ function createBoundary(type, fn, show, onFn) {
|
|
|
8541
8742
|
_gated: false,
|
|
8542
8743
|
_collapsed: false,
|
|
8543
8744
|
_error: type === STATUS_ERROR ? signal(undefined, ERROR_SIGNAL) : null,
|
|
8544
|
-
_show: show
|
|
8745
|
+
_show: show,
|
|
8746
|
+
_fb: null
|
|
8545
8747
|
};
|
|
8546
8748
|
const controller = type === STATUS_PENDING ? owner._context[REVEAL] : null;
|
|
8547
8749
|
if (getOwner())
|
|
@@ -8682,6 +8884,20 @@ function createBoundary(type, fn, show, onFn) {
|
|
|
8682
8884
|
if (b._fallback && b._type & STATUS_PENDING && attrHooks !== null)
|
|
8683
8885
|
attrHooks.boundaryFallback(b, b._tree, false);
|
|
8684
8886
|
b._fallback = false;
|
|
8887
|
+
// The fallback dies when the content that replaces it is on screen —
|
|
8888
|
+
// with this flush's commits when the pass is the frame's, at the
|
|
8889
|
+
// landing when the frame parks (the run is held with it, as the old
|
|
8890
|
+
// children of a held pass are) — not now: a held swap keeps the
|
|
8891
|
+
// fallback on screen, live, until it commits. A pass back to the
|
|
8892
|
+
// fallback before then finds it still built and keeps it.
|
|
8893
|
+
const fb = b._fb;
|
|
8894
|
+
if (fb !== null)
|
|
8895
|
+
globalQueue.enqueue(EFFECT_RENDER, () => {
|
|
8896
|
+
if (!b._fallback && b._fb === fb) {
|
|
8897
|
+
dispose(fb);
|
|
8898
|
+
b._fb = null;
|
|
8899
|
+
}
|
|
8900
|
+
});
|
|
8685
8901
|
release(b);
|
|
8686
8902
|
// Shown: the slot leaves its reveal order (a later re-arm gates nobody
|
|
8687
8903
|
// else; the frontier moves on).
|
|
@@ -8724,7 +8940,22 @@ function fallback(b) {
|
|
|
8724
8940
|
if (!globalQueue._running) schedule();
|
|
8725
8941
|
}
|
|
8726
8942
|
b._fallback = true;
|
|
8727
|
-
|
|
8943
|
+
// Built in its own scope, like `Show`'s function child, once per show
|
|
8944
|
+
// (D-39: the fallback is the display until a new answer replaces the
|
|
8945
|
+
// error): a re-derive of the output while it shows — a second reader
|
|
8946
|
+
// caught, the same error propagated again through a sibling's re-run, a
|
|
8947
|
+
// retry pending after `reset()`, a later error — keeps the built
|
|
8948
|
+
// fallback, its DOM and focus with it (#3991). A later error reaches it
|
|
8949
|
+
// through the error accessor; only a fallback reading the accessor at its
|
|
8950
|
+
// top level is rebuilt by it. Created beside the output — in its owner,
|
|
8951
|
+
// outside the boundary, as the output is: its errors and runs are the
|
|
8952
|
+
// enclosing boundary's — with the output's id, so what it builds gets the
|
|
8953
|
+
// ids a build under the output would; disposed once content is on screen.
|
|
8954
|
+
if (b._fb === null) {
|
|
8955
|
+
const out = getOwner();
|
|
8956
|
+
b._fb = runWithOwner(out._parent, () => computed(() => b._show(b), { id: out.id }));
|
|
8957
|
+
}
|
|
8958
|
+
return read(b._fb);
|
|
8728
8959
|
}
|
|
8729
8960
|
const ERROR_SIGNAL = { ownedWrite: true, _noSnapshot: true };
|
|
8730
8961
|
/**
|