@solidjs/signals 2.0.0-rc.14 → 2.0.0-rc.16
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 +882 -405
- package/dist/dev.attribution.js +172 -66
- package/dist/dev.js +1529 -745
- package/dist/observe/affects-marks.js +86 -0
- package/dist/observe/affects.js +32 -80
- package/dist/observe/boundaries.js +202 -166
- package/dist/observe/core/async.js +192 -141
- package/dist/observe/core/attribution-queries.js +1 -1
- package/dist/observe/core/attribution.js +315 -214
- package/dist/observe/core/constants.js +23 -4
- package/dist/observe/core/context.js +3 -3
- package/dist/observe/core/core.js +532 -360
- package/dist/observe/core/dev.js +63 -35
- package/dist/observe/core/effect.js +47 -47
- package/dist/observe/core/error.js +5 -3
- package/dist/observe/core/external.js +5 -6
- package/dist/observe/core/graph.js +56 -42
- package/dist/observe/core/heap.js +33 -33
- package/dist/observe/core/identities.js +62 -0
- package/dist/observe/core/lanes.js +242 -229
- package/dist/observe/core/owner.js +62 -50
- package/dist/observe/core/scheduler.js +322 -168
- package/dist/observe/core/truth.js +45 -0
- package/dist/observe/core/verdict.js +114 -64
- package/dist/observe/deferred.js +184 -0
- package/dist/observe/index.js +7 -1
- package/dist/observe/map.js +339 -254
- package/dist/observe/reveal.js +77 -77
- package/dist/observe/signals.js +54 -33
- package/dist/observe/store/affects.js +76 -69
- package/dist/observe/store/edges.js +39 -0
- package/dist/observe/store/hooks.js +13 -0
- package/dist/observe/store/index.js +0 -2
- package/dist/observe/store/optimistic.js +82 -44
- package/dist/observe/store/projection.js +83 -64
- package/dist/observe/store/reconcile.js +32 -30
- package/dist/observe/store/store.js +219 -123
- package/dist/prod/affects-marks.js +86 -0
- package/dist/prod/affects.js +33 -79
- package/dist/prod/boundaries.js +185 -149
- package/dist/prod/core/async.js +176 -135
- package/dist/prod/core/constants.js +23 -4
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +521 -353
- package/dist/prod/core/effect.js +66 -66
- package/dist/prod/core/error.js +5 -3
- package/dist/prod/core/external.js +5 -6
- package/dist/prod/core/graph.js +56 -42
- package/dist/prod/core/heap.js +38 -38
- package/dist/prod/core/identities.js +62 -0
- package/dist/prod/core/lanes.js +237 -224
- package/dist/prod/core/owner.js +62 -51
- package/dist/prod/core/scheduler.js +312 -158
- package/dist/prod/core/truth.js +45 -0
- package/dist/prod/core/verdict.js +113 -63
- package/dist/prod/deferred.js +182 -0
- package/dist/prod/index.js +7 -1
- package/dist/prod/map.js +366 -281
- package/dist/prod/reveal.js +77 -77
- package/dist/prod/signals.js +59 -38
- package/dist/prod/store/affects.js +76 -69
- package/dist/prod/store/edges.js +39 -0
- package/dist/prod/store/hooks.js +13 -0
- package/dist/prod/store/index.js +0 -2
- package/dist/prod/store/optimistic.js +121 -84
- package/dist/prod/store/projection.js +46 -34
- package/dist/prod/store/reconcile.js +32 -30
- package/dist/prod/store/store.js +205 -116
- package/dist/types/affects-marks.d.ts +14 -0
- package/dist/types/boundaries.d.ts +5 -1
- package/dist/types/core/async.d.ts +10 -0
- package/dist/types/core/attribution.d.ts +6 -2
- package/dist/types/core/constants.d.ts +18 -1
- package/dist/types/core/core.d.ts +77 -5
- package/dist/types/core/dev.d.ts +43 -13
- package/dist/types/core/graph.d.ts +11 -0
- package/dist/types/core/identities.d.ts +17 -0
- package/dist/types/core/index.d.ts +2 -1
- package/dist/types/core/lanes.d.ts +2 -4
- package/dist/types/core/owner.d.ts +5 -3
- package/dist/types/core/scheduler.d.ts +59 -34
- package/dist/types/core/truth.d.ts +16 -0
- package/dist/types/core/types.d.ts +21 -6
- package/dist/types/core/verdict.d.ts +32 -0
- package/dist/types/deferred.d.ts +28 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/map.d.ts +2 -2
- package/dist/types/signals.d.ts +28 -7
- package/dist/types/store/affects.d.ts +11 -0
- package/dist/types/store/edges.d.ts +9 -0
- package/dist/types/store/hooks.d.ts +48 -0
- package/dist/types/store/index.d.ts +10 -4
- package/dist/types/store/optimistic.d.ts +3 -2
- package/dist/types/store/projection.d.ts +4 -3
- package/dist/types/store/store.d.ts +4 -39
- package/dist/types/store/target.d.ts +16 -8
- package/dist/types/store/types.d.ts +14 -0
- package/package.json +1 -1
package/dist/prod/core/core.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { releaseFlightTeardown, handleAsync, clearStatus, parkLoadingWindow,
|
|
1
|
+
import { notifyStatus, releaseFlightTeardown, handleAsync, clearStatus, parkLoadingWindow, stageError, settlePendingSource, releaseIfSettledUnobserved, settleErroredDependents } from "./async.js";
|
|
2
2
|
|
|
3
|
-
import { STATUS_UNINITIALIZED, REACTIVE_LAZY, REACTIVE_NONE, defaultContext, CONFIG_TRANSPARENT, CONFIG_OWNED_WRITE, CONFIG_AUTO_DISPOSE, CONFIG_SYNC, CONFIG_NO_SNAPSHOT, CONFIG_IN_SNAPSHOT_SCOPE, NOT_PENDING, NO_SNAPSHOT, CONFIG_HAS_SNAPSHOT, CONFIG_HELD, REACTIVE_MANUAL_WRITE, REACTIVE_DIRTY, CONFIG_OVERRIDE, REACTIVE_DISPOSED, REACTIVE_CHECK, REACTIVE_IN_HEAP, REACTIVE_REASK, REACTIVE_SNAPSHOT_STALE, REACTIVE_LANE_DIRTY, CONFIG_GUESS, REACTIVE_RECOMPUTING_DEPS, EFFECT_RENDER,
|
|
3
|
+
import { STATUS_UNINITIALIZED, REACTIVE_LAZY, REACTIVE_NONE, defaultContext, CONFIG_TRANSPARENT, CONFIG_OWNED_WRITE, CONFIG_AUTO_DISPOSE, CONFIG_SYNC, CONFIG_NO_SNAPSHOT, CONFIG_IN_SNAPSHOT_SCOPE, NOT_PENDING, NO_SNAPSHOT, CONFIG_HAS_SNAPSHOT, CONFIG_HELD, REACTIVE_MANUAL_WRITE, STATUS_ERROR, REACTIVE_DIRTY, CONFIG_OVERRIDE, REACTIVE_DISPOSED, REACTIVE_CHECK, REACTIVE_IN_HEAP, REACTIVE_REASK, REACTIVE_SNAPSHOT_STALE, CONFIG_CHILDREN_FORBIDDEN, CONFIG_VERDICT, STATUS_PENDING, REACTIVE_LANE_DIRTY, CONFIG_GUESS, REACTIVE_RECOMPUTING_DEPS, CONFIG_LAGS, EFFECT_RENDER, CONFIG_HELD_FRAME, EFFECT_TRACKED, CONFIG_STAGED, CONFIG_STAGED_ERROR, REACTIVE_ZOMBIE, REACTIVE_MISSED_WAKE, REACTIVE_JOINED, REACTIVE_STAGED_READ, REACTIVE_PROBED, REACTIVE_FRAME_READ, REACTIVE_LANE_READ, REACTIVE_SCREEN_READ, REACTIVE_PROBE_UNANSWERED, CAUGHT_REARMED, CONFIG_PROMOTED, CONFIG_INPUTS_PUBLISHED, CONFIG_FRESH_READ, REACTIVE_IN_HEAP_HEIGHT, CONFIG_SLOT_NODE, EFFECT_USER, STATUS_NONE } from "./constants.js";
|
|
4
4
|
|
|
5
5
|
import { NotReadyError } from "./error.js";
|
|
6
6
|
|
|
7
|
-
import { clearDeps, trimStaleDeps,
|
|
7
|
+
import { link, dormantNodes, clearDeps, trimStaleDeps, deferReleases, deferredReleases } from "./graph.js";
|
|
8
8
|
|
|
9
9
|
import { insertIntoHeap, deleteFromHeap, insertIntoHeapHeight, enqueueSub, markNode, markHeap } from "./heap.js";
|
|
10
10
|
|
|
11
|
-
import { inheritId,
|
|
11
|
+
import { inheritId, linkChild, disposeChildren, markDisposal } from "./owner.js";
|
|
12
12
|
|
|
13
|
-
import { schedule,
|
|
13
|
+
import { schedule, externalUntrackHook, clock, txOf, flushTransaction, laneWriteHook, joinFuture, queuePendingNode, globalQueue, notifyEpoch, insertSubs, dirtyQueue, observeFlightHook, laneReadHook, laneStageHook, passLane, wireExternalSourceHook, bumpNotifyEpoch, setPassLane, joinPassTx, freshHook, GlobalQueue, laneOutcomeHook, passTx, holdNode, laneDirty, heldTrim, staleReader, laneSeamsHook, stagedReaders, deferZombie } from "./scheduler.js";
|
|
14
14
|
|
|
15
15
|
// The heap's per-node step. A tracked effect's heap visit is its compute
|
|
16
16
|
// phase — empty, like a user effect whose compute reads nothing — and hands
|
|
@@ -20,11 +20,11 @@ import { schedule, GlobalQueue, clock, txOf, flushTransaction, joinFuture, queue
|
|
|
20
20
|
// render-effect callback stages its value for the next pass, but a wake pushed
|
|
21
21
|
// directly into the user queue ran in the SAME pass, read the old value, and
|
|
22
22
|
// nothing re-notified it when the value landed (#3291).
|
|
23
|
-
GlobalQueue.
|
|
23
|
+
GlobalQueue._e = e => {
|
|
24
24
|
if (e.i === EFFECT_TRACKED) {
|
|
25
25
|
deleteFromHeap(e, dirtyQueue);
|
|
26
|
-
e.
|
|
27
|
-
globalQueue.enqueue(EFFECT_USER, e.
|
|
26
|
+
e.Ae = true;
|
|
27
|
+
globalQueue.enqueue(EFFECT_USER, e.M);
|
|
28
28
|
}
|
|
29
29
|
// L2: a zombie's fate is the seam's — its owner's commit disposes it, or
|
|
30
30
|
// the park cancels the pass (the write that dirtied it is held). It runs
|
|
@@ -32,10 +32,29 @@ GlobalQueue.Ce = e => {
|
|
|
32
32
|
// owner is held in the future). Its lane's own re-staging is a live write
|
|
33
33
|
// (REACTIVE_LANE_DIRTY — a lane shows ahead of any park): it runs now, as
|
|
34
34
|
// the lane's work, for the reveal this seam (#3463).
|
|
35
|
-
else if (e.R & REACTIVE_ZOMBIE && !(e.R & REACTIVE_LANE_DIRTY)) deferZombie(e);
|
|
35
|
+
else if (e.R & REACTIVE_ZOMBIE && !(e.R & REACTIVE_LANE_DIRTY)) deferZombie(e);
|
|
36
|
+
// A lane frame's member (parked by a lane pass, #3698) follows mainline
|
|
37
|
+
// writes only: a lane's dirty on it is cancelled — the lane's apply retires
|
|
38
|
+
// it, and the pass that replaced it already decided what that screen shows
|
|
39
|
+
// (#4137: a `<Show>` branch withdrawn by its own lane re-ran on a falsy
|
|
40
|
+
// condition).
|
|
41
|
+
// (Lanes exist only with the lane engine installed: folded away without.)
|
|
42
|
+
else if (laneStageHook && e.R & REACTIVE_ZOMBIE && laneFrameMember(e)) {
|
|
43
|
+
deleteFromHeap(e, dirtyQueue);
|
|
44
|
+
e.R &= -65540;
|
|
45
|
+
} else recompute(e);
|
|
36
46
|
};
|
|
37
47
|
|
|
38
|
-
|
|
48
|
+
/** The zombie's frame was parked by lane work: the nearest non-zombie owner
|
|
49
|
+
* (the one whose pass parked it) carries a lane's derived value — or, an
|
|
50
|
+
* effect (no lane value: `laneStage`), is listed in a lane (an `insert()`
|
|
51
|
+
* hole's render effect). */ function laneFrameMember(e) {
|
|
52
|
+
let t = e._parent;
|
|
53
|
+
while (t.R & REACTIVE_ZOMBIE) t = t._parent;
|
|
54
|
+
return (t.l & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE || t.i && t.T?.Re?.v;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
GlobalQueue.Ce = disposeChildren;
|
|
39
58
|
|
|
40
59
|
let tracking = false;
|
|
41
60
|
|
|
@@ -43,11 +62,15 @@ let context = null;
|
|
|
43
62
|
|
|
44
63
|
let snapshotCaptureActive = false;
|
|
45
64
|
|
|
65
|
+
/** The lane at the root of the running chain of first passes: the lane whose
|
|
66
|
+
* pass created the chain's first node (a mount), inherited by every first
|
|
67
|
+
* pass nested under it, whose seats are mainline (`recompute`, #4166). */ let laneRoot;
|
|
68
|
+
|
|
46
69
|
let snapshotSources = null;
|
|
47
70
|
|
|
48
71
|
function ownerInSnapshotScope(e) {
|
|
49
72
|
while (e) {
|
|
50
|
-
if (e.
|
|
73
|
+
if (e.Oe) return true;
|
|
51
74
|
e = e._parent;
|
|
52
75
|
}
|
|
53
76
|
return false;
|
|
@@ -59,34 +82,34 @@ function setSnapshotCapture(e) {
|
|
|
59
82
|
}
|
|
60
83
|
|
|
61
84
|
function markSnapshotScope(e) {
|
|
62
|
-
e.
|
|
85
|
+
e.Oe = true;
|
|
63
86
|
}
|
|
64
87
|
|
|
65
88
|
function releaseSnapshotScope(e) {
|
|
66
|
-
e.
|
|
89
|
+
e.Oe = false;
|
|
67
90
|
releaseSubtree(e);
|
|
68
91
|
schedule();
|
|
69
92
|
}
|
|
70
93
|
|
|
71
94
|
function releaseSubtree(e) {
|
|
72
|
-
let t = e.
|
|
95
|
+
let t = e.De;
|
|
73
96
|
while (t) {
|
|
74
|
-
if (t.
|
|
75
|
-
t = t.
|
|
97
|
+
if (t.Oe) {
|
|
98
|
+
t = t.pe;
|
|
76
99
|
continue;
|
|
77
100
|
}
|
|
78
|
-
if (t.
|
|
101
|
+
if (t.X) {
|
|
79
102
|
const e = t;
|
|
80
|
-
e.
|
|
103
|
+
e.l &= ~CONFIG_IN_SNAPSHOT_SCOPE;
|
|
81
104
|
if (e.R & REACTIVE_SNAPSHOT_STALE) {
|
|
82
105
|
e.R &= ~REACTIVE_SNAPSHOT_STALE;
|
|
83
106
|
e.R |= REACTIVE_DIRTY;
|
|
84
|
-
if (dirtyQueue.
|
|
107
|
+
if (dirtyQueue.Ge > e.Fe) dirtyQueue.Ge = e.Fe;
|
|
85
108
|
insertIntoHeap(e, dirtyQueue);
|
|
86
109
|
}
|
|
87
110
|
}
|
|
88
111
|
releaseSubtree(t);
|
|
89
|
-
t = t.
|
|
112
|
+
t = t.pe;
|
|
90
113
|
}
|
|
91
114
|
}
|
|
92
115
|
|
|
@@ -97,8 +120,8 @@ function clearSnapshots() {
|
|
|
97
120
|
// pre-initialized to undefined (see ext()), and every reader tests
|
|
98
121
|
// `!== undefined` — assign, don't `delete`: deleting a field pushes the
|
|
99
122
|
// object to dictionary mode for every later read of every field.
|
|
100
|
-
const t = e.
|
|
101
|
-
if (t != null) t.
|
|
123
|
+
const t = e.T;
|
|
124
|
+
if (t != null) t.Pe = undefined;
|
|
102
125
|
}
|
|
103
126
|
snapshotSources = null;
|
|
104
127
|
}
|
|
@@ -109,6 +132,7 @@ function recompute(e, t = false) {
|
|
|
109
132
|
// §12d: any recompute can clean a marked subscriber — invalidate skips.
|
|
110
133
|
bumpNotifyEpoch();
|
|
111
134
|
const n = e.i;
|
|
135
|
+
let o;
|
|
112
136
|
// Lanes (plan sec. 28): the seat of a pass is its node's. A node carrying a lane's
|
|
113
137
|
// derived value runs as the lane's whoever dirtied it — a sync write, a
|
|
114
138
|
// boundary reset, a frame rerun — so its children and its result are the
|
|
@@ -124,15 +148,22 @@ function recompute(e, t = false) {
|
|
|
124
148
|
// derivation's pass into the lane (`read`); a leaf's never moves.
|
|
125
149
|
// Restored at the end, after this pass's staging and runs have been
|
|
126
150
|
// routed.
|
|
151
|
+
// Lanes exist only with the lane engine installed (`laneStageHook`): a
|
|
152
|
+
// bundle without it seats every pass on mainline, and folds the seat away.
|
|
127
153
|
const E = passLane;
|
|
128
|
-
|
|
129
|
-
|
|
154
|
+
// The mount a first pass belongs to: its creator's lane, or the one its
|
|
155
|
+
// creator's own mount came from — a render effect a verdict lane's mount's
|
|
156
|
+
// content creates is that mount's too (#4166). Lanes only, like the seat.
|
|
157
|
+
const s = laneRoot;
|
|
158
|
+
const i = laneStageHook && (laneRoot = t && (E || s));
|
|
159
|
+
setPassLane(!laneStageHook ? null : (e.R & REACTIVE_LANE_DIRTY || (e.l & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE) && e.T?.Re?.v ? e.T.Re : // Loose `!=`: false with no creator lane (undefined == null).
|
|
160
|
+
t && E?._parent.he != E && creatorPass(context)?.R & REACTIVE_RECOMPUTING_DEPS ? E : null);
|
|
130
161
|
// CARVE 2: lane posture resolution (OPTIMISTIC_DIRTY, derived-override
|
|
131
162
|
// re-derivation, lane adoption through deps) went with the optimistic engine.
|
|
132
163
|
if (!t) {
|
|
133
164
|
deleteFromHeap(e, dirtyQueue);
|
|
134
|
-
if (e.
|
|
135
|
-
e.
|
|
165
|
+
if (e.T !== null) {
|
|
166
|
+
e.T.Se = null;
|
|
136
167
|
// Supersede is where an iterator flight dies (#3122): close it now.
|
|
137
168
|
// Idempotent with the cleanup-channel close.
|
|
138
169
|
releaseFlightTeardown(e);
|
|
@@ -144,23 +175,32 @@ function recompute(e, t = false) {
|
|
|
144
175
|
// joins all the same. Not a render effect (rule 3): it is the frame.
|
|
145
176
|
// Re-run outside its transaction's flush it publishes mainline — the
|
|
146
177
|
// value it staged there is superseded (`frameRead` remembers it for the
|
|
147
|
-
// landing). A frame born into the transaction (
|
|
148
|
-
// A29
|
|
149
|
-
// swap (ruling
|
|
150
|
-
|
|
178
|
+
// landing). A frame born into the transaction (`bornInto`: never run,
|
|
179
|
+
// A29, or mounted by a pass of it still awaiting its commit, #4010) keeps
|
|
180
|
+
// staging: live writes keep it current, it effects at the swap (ruling
|
|
181
|
+
// A).
|
|
182
|
+
// A pending flight is the transition's own work (#3884). A probe on an
|
|
183
|
+
// earlier pass left CONFIG_VERDICT, and a frame read here would serve the
|
|
184
|
+
// committed screen, settle the flight and drop the hold — a superseding
|
|
185
|
+
// write's verdict would depend on that probe. Cleared for this pass; a
|
|
186
|
+
// probe in the body re-stamps it after the plain reads.
|
|
187
|
+
if (e.m & STATUS_PENDING) e.l &= ~CONFIG_VERDICT;
|
|
188
|
+
if (e.l & CONFIG_HELD) {
|
|
151
189
|
const t = txOf(e);
|
|
152
190
|
// Held by a blocked lane: the frame joins nothing — a lane never holds
|
|
153
191
|
// a sync write (#3460). The pass is the lane's if it reads the lane's
|
|
154
192
|
// world (`read`), and has left it otherwise.
|
|
155
|
-
|
|
193
|
+
// An answered deferred node's re-pass is a lagging question (D-40):
|
|
194
|
+
// it joins nothing, so the write that reached it commits mainline.
|
|
195
|
+
if (t.v || e.l & CONFIG_LAGS) ; else if (n !== EFFECT_RENDER && !(e.l & CONFIG_VERDICT)) joinPassTx(t, freshHook?.(e, e.m & STATUS_UNINITIALIZED)); else if (t !== flushTransaction && !(e.m & STATUS_UNINITIALIZED || e.l & CONFIG_HELD_FRAME)) {
|
|
156
196
|
// Published mainline, it is not held: the frame this pass builds is
|
|
157
197
|
// replaced on the spot (#3404) — this flush's commit retires the
|
|
158
198
|
// committed frame the held pass had parked (`commitPendingNode`),
|
|
159
199
|
// after the new frame's first runs, and the held pass's own
|
|
160
200
|
// children die below (`disposeChildren`). The landing re-derives a
|
|
161
201
|
// fresh held frame for the reveal (`_reruns`).
|
|
162
|
-
e.
|
|
163
|
-
if (n === EFFECT_RENDER) e.
|
|
202
|
+
e.se = NOT_PENDING;
|
|
203
|
+
if (n === EFFECT_RENDER) e.l &= ~CONFIG_HELD;
|
|
164
204
|
}
|
|
165
205
|
}
|
|
166
206
|
// The previous pass's frame. If that pass is still awaiting its commit
|
|
@@ -168,75 +208,91 @@ function recompute(e, t = false) {
|
|
|
168
208
|
// frame parked before it stays parked. Otherwise they ARE the frame:
|
|
169
209
|
// park them until this pass commits. Tracked effects run after the seam
|
|
170
210
|
// and own nothing to park.
|
|
171
|
-
if (n === EFFECT_TRACKED || e.
|
|
211
|
+
if (n === EFFECT_TRACKED || e.l & CONFIG_STAGED) {
|
|
212
|
+
// A lazy node only those children read is released when this pass
|
|
213
|
+
// ends, if the pass doesn't read it again (#4142).
|
|
214
|
+
const t = deferredReleases;
|
|
215
|
+
deferReleases(o = []);
|
|
216
|
+
disposeChildren(e);
|
|
217
|
+
deferReleases(t);
|
|
218
|
+
} else if (e.De !== null || e.He !== null) parkChildren(e);
|
|
219
|
+
// An error a pass staged (D-41) is replaced by this pass's answer: a
|
|
220
|
+
// value, a flight, or an error of its own (#4109, #4145).
|
|
221
|
+
if (e.l & CONFIG_STAGED_ERROR) {
|
|
222
|
+
e.se = NOT_PENDING;
|
|
223
|
+
e.l ^= CONFIG_STAGED_ERROR;
|
|
224
|
+
}
|
|
172
225
|
}
|
|
173
|
-
const
|
|
226
|
+
const l = !!(e.m & STATUS_UNINITIALIZED);
|
|
174
227
|
// Capture both error and pending status before the compute clears them.
|
|
175
228
|
// A conditional can drop its pending source and recover to an unchanged
|
|
176
229
|
// value, leaving blocked dependents outside that source’s settle walk.
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
const
|
|
230
|
+
const r = e.m & STATUS_ERROR ? e.T?.F : undefined;
|
|
231
|
+
const u = (e.m & STATUS_PENDING) !== 0;
|
|
232
|
+
const _ = u ? e.T?.fe : undefined;
|
|
180
233
|
// Pending SOURCE-hood, captured before the compute clears status: a node
|
|
181
234
|
// whose own flight parked dependents self-registers in _pendingSources
|
|
182
235
|
// (notifyStatus, isSource). If this recompute supersedes that flight and
|
|
183
236
|
// settles synchronously, those dependents settle HERE — asyncWrite's
|
|
184
237
|
// settlePendingSource walk never runs for a landing that was preempted
|
|
185
238
|
// (#3181).
|
|
186
|
-
const
|
|
239
|
+
const a = e.T?.fe?.has(e);
|
|
240
|
+
// D-41: this pass's error is staged, not published (`stageError`).
|
|
241
|
+
let I = false;
|
|
187
242
|
// A19 exc. 2: a `refresh()` re-asks the question already answered — quiet
|
|
188
243
|
// for the verdict — unless the question in flight is a new one, which the
|
|
189
244
|
// re-ask does not launder (a poll during a quiet confirm stays quiet).
|
|
190
|
-
const
|
|
191
|
-
const
|
|
245
|
+
const N = (e.R & REACTIVE_REASK) !== 0 && !(e.m & STATUS_PENDING && e.T !== null && !e.T.Ve);
|
|
246
|
+
const f = context;
|
|
192
247
|
context = e;
|
|
193
|
-
e.
|
|
194
|
-
e.
|
|
248
|
+
e._ = null;
|
|
249
|
+
e.Ne++;
|
|
195
250
|
// REACTIVE_ZOMBIE is position: it says the node sits on its owner's parked
|
|
196
251
|
// frame, and the splice/reawaken paths key off it. A zombie reruns for
|
|
197
252
|
// live writes until the commit that disposes it, so every per-pass wipe
|
|
198
253
|
// — here, the finally below, updateIfNecessary — carries it (#3543).
|
|
199
|
-
e.R = REACTIVE_RECOMPUTING_DEPS | e.R & REACTIVE_ZOMBIE;
|
|
200
|
-
e.
|
|
201
|
-
// The pass's previous value: its staging, else
|
|
202
|
-
//
|
|
203
|
-
let
|
|
204
|
-
let
|
|
205
|
-
let
|
|
206
|
-
// L2: did this pass read the future (REACTIVE_JOINED, set by read)
|
|
207
|
-
|
|
208
|
-
|
|
254
|
+
e.R = REACTIVE_RECOMPUTING_DEPS | e.R & (REACTIVE_ZOMBIE | REACTIVE_REASK);
|
|
255
|
+
e.ie = clock;
|
|
256
|
+
// The pass's previous value: its staging, else a lane node's lane value
|
|
257
|
+
// when it has one (lanes.ts), else the committed one.
|
|
258
|
+
let T = e.se !== NOT_PENDING ? e.se : e.l & CONFIG_OVERRIDE && e.T.v !== NOT_PENDING ? e.T.v : e.Ie;
|
|
259
|
+
let S = e.Fe;
|
|
260
|
+
let c = false;
|
|
261
|
+
// L2: did this pass read the future (REACTIVE_JOINED, set by read) — or,
|
|
262
|
+
// a lane's mount, a staging of this flush (#3851, #4166)?
|
|
263
|
+
let A = false;
|
|
264
|
+
let R = tracking;
|
|
209
265
|
tracking = true;
|
|
210
266
|
try {
|
|
211
|
-
if (!false && e.
|
|
212
|
-
|
|
213
|
-
if (e.
|
|
214
|
-
e.
|
|
267
|
+
if (!false && e.l & CONFIG_SYNC) {
|
|
268
|
+
T = e.X(T);
|
|
269
|
+
if (e.T !== null) e.T.Se = null;
|
|
270
|
+
e.ce = false;
|
|
215
271
|
} else {
|
|
216
272
|
// A projection's body self-registers its flight through handleAsync
|
|
217
273
|
// (with the commit as setter): its undefined return is not a sync
|
|
218
274
|
// answer, and the outer handleAsync must not clobber the registration.
|
|
219
|
-
const t = e.
|
|
220
|
-
const n = e.
|
|
221
|
-
const
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
if (!
|
|
225
|
-
if (e.
|
|
275
|
+
const t = e.T?.Se;
|
|
276
|
+
const n = e.X(T);
|
|
277
|
+
const o = typeof n === "object" && n !== null;
|
|
278
|
+
const E = e.T?.Se !== t;
|
|
279
|
+
T = E || !o ? n : handleAsync(e, n);
|
|
280
|
+
if (!E && !o) {
|
|
281
|
+
if (e.T !== null) e.T.Se = null;
|
|
226
282
|
// A sync (non-object) return is the first real answer; async-shaped
|
|
227
283
|
// results clear inside handleAsync at their own landing points, and a
|
|
228
284
|
// self-registered flight clears when its own handleAsync lands.
|
|
229
|
-
e.
|
|
285
|
+
e.ce = false;
|
|
230
286
|
}
|
|
231
287
|
}
|
|
232
288
|
// On a status-free node clearStatus is a guaranteed no-op: every field
|
|
233
289
|
// its body gates on is either _statusFlags or lives in the cold
|
|
234
290
|
// extension — no extension, no status to clear. (_x from an unrelated
|
|
235
291
|
// installer just makes clearStatus a cheap re-verified no-op.)
|
|
236
|
-
if (e.
|
|
292
|
+
if (e.m !== 0 || e.T !== null) clearStatus(e, t);
|
|
237
293
|
} catch (t) {
|
|
238
294
|
const n = t instanceof NotReadyError;
|
|
239
|
-
if (n && e.
|
|
295
|
+
if (n && e.ce) {
|
|
240
296
|
// Loading window with an unready sync dependency: register for the
|
|
241
297
|
// source's settle (the settlePendingSource walk runs off
|
|
242
298
|
// _pendingSources + _blocked alone) but take NO read-visible pending
|
|
@@ -246,11 +302,15 @@ function recompute(e, t = false) {
|
|
|
246
302
|
// retry can actually run.
|
|
247
303
|
parkLoadingWindow(e, t);
|
|
248
304
|
} else {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
305
|
+
// D-41: a pass of a held node, or one that read a staging of this
|
|
306
|
+
// flush, stages its error (`stageError`).
|
|
307
|
+
if (n || !(I = stageError(e, t, true))) {
|
|
308
|
+
if (n) ext(e).ue = true;
|
|
309
|
+
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t);
|
|
310
|
+
// The replacement source is fully propagated now. If no new flight
|
|
311
|
+
// re-owned self, retire the superseded flight and its dependent copies.
|
|
312
|
+
if (n && a && !e.T?.Se) settlePendingSource(e);
|
|
313
|
+
}
|
|
254
314
|
// A re-park drops what the earlier pass carried (#3456): a source this
|
|
255
315
|
// pass no longer reaches — its branch switched, or a fresh flight
|
|
256
316
|
// replaced the inputs' pending with its own — stays copied onto
|
|
@@ -258,21 +318,24 @@ function recompute(e, t = false) {
|
|
|
258
318
|
// stops at this node (nothing left to retire) before it finds them. A
|
|
259
319
|
// dependent then waits forever on a flight it has no path to. The
|
|
260
320
|
// re-park twin of the unchanged-value recovery sweep below; dependents
|
|
261
|
-
// with another path keep the source (retryReaches).
|
|
262
|
-
|
|
321
|
+
// with another path keep the source (retryReaches). A staged error
|
|
322
|
+
// answered what the pass carried: the same.
|
|
323
|
+
if ((n || I) && _) for (const t of _) if (t !== e && !e.T?.fe?.has(t)) settlePendingSource(e, t);
|
|
263
324
|
}
|
|
264
325
|
} finally {
|
|
265
|
-
tracking =
|
|
326
|
+
tracking = R;
|
|
327
|
+
if (laneStageHook) laneRoot = s;
|
|
266
328
|
// Consume the missed-wake latch (#3037, set by insertSubs): a dep write
|
|
267
329
|
// landed beneath this pass on a link it had already validated. The wipe
|
|
268
330
|
// below must not key off DIRTY/CHECK — the read-time pull protocol
|
|
269
331
|
// (markNode(c) in read()) marks the running node as part of ordinary
|
|
270
332
|
// bookkeeping, and those marks are correctly discarded here.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
// A verdict reader is one for as long as it probes
|
|
274
|
-
//
|
|
275
|
-
|
|
333
|
+
c = (e.R & REACTIVE_MISSED_WAKE) !== 0;
|
|
334
|
+
A = !!(e.R & (i ? REACTIVE_JOINED | REACTIVE_STAGED_READ : REACTIVE_JOINED));
|
|
335
|
+
// A verdict reader is one for as long as it probes, or reads a memo
|
|
336
|
+
// that does (`read`): a pass that did neither (REACTIVE_PROBED, the same
|
|
337
|
+
// bit) is an ordinary derivation again.
|
|
338
|
+
e.l = e.l & ~CONFIG_VERDICT | e.R & REACTIVE_PROBED;
|
|
276
339
|
// REACTIVE_DISPOSED survives too (#3621): the pass may have disposed its
|
|
277
340
|
// own owner (a memo calling its root's `dispose()`, a cleanup doing so
|
|
278
341
|
// #3601/#3606), and `disposeChildren` set the flag on this node
|
|
@@ -281,11 +344,12 @@ function recompute(e, t = false) {
|
|
|
281
344
|
// this pass's verdict for the landing; the next pass's wipe at the top
|
|
282
345
|
// clears it.
|
|
283
346
|
e.R = e.R & (REACTIVE_ZOMBIE | REACTIVE_DISPOSED | REACTIVE_FRAME_READ | REACTIVE_STAGED_READ | REACTIVE_LANE_READ | REACTIVE_SCREEN_READ | REACTIVE_PROBE_UNANSWERED) | (t ? e.R & REACTIVE_SNAPSHOT_STALE : 0);
|
|
284
|
-
context =
|
|
347
|
+
context = f;
|
|
348
|
+
if (o) for (const e of o) releaseIfSettledUnobserved(e);
|
|
285
349
|
// A19 exc. 2: a pass that went pending on a `refresh()` re-asks the
|
|
286
350
|
// question already answered — quiet for the verdict; any other pending
|
|
287
351
|
// pass is a new question. Settled, there is nothing to classify.
|
|
288
|
-
if (e.
|
|
352
|
+
if (e.T !== null) e.T.Ve = N && (e.m & STATUS_PENDING) !== 0;
|
|
289
353
|
}
|
|
290
354
|
// A node that died during its own pass (#3621) is dead at the end of it,
|
|
291
355
|
// and the pass is void. Its owner's teardown already unlinked its deps,
|
|
@@ -300,7 +364,7 @@ function recompute(e, t = false) {
|
|
|
300
364
|
// attribution frame opened at the top is still closed.
|
|
301
365
|
if (e.R & REACTIVE_DISPOSED) {
|
|
302
366
|
clearDeps(e);
|
|
303
|
-
if (e.
|
|
367
|
+
if (e.T !== null) e.T.Se = null;
|
|
304
368
|
setPassLane(E);
|
|
305
369
|
return;
|
|
306
370
|
}
|
|
@@ -309,47 +373,39 @@ function recompute(e, t = false) {
|
|
|
309
373
|
// seat that read none of the lane's world has left it: its result is the
|
|
310
374
|
// frame's (a derivation whose branch no longer reaches the guess). A
|
|
311
375
|
// guess is written, not derived — it never leaves this way.
|
|
312
|
-
let
|
|
376
|
+
let C = !laneStageHook ? null : passLane;
|
|
313
377
|
// Listed before its staging, a pending pass included (the lane's own
|
|
314
378
|
// flight is the lane's); false: the pass left the lane (lanes.ts).
|
|
315
|
-
const
|
|
316
|
-
if (
|
|
317
|
-
if (
|
|
379
|
+
const d = !!e.T?.F || I;
|
|
380
|
+
if (laneStageHook && C !== null) {
|
|
381
|
+
if (laneStageHook(e, C, t, d)) setPassLane(C); else C = null;
|
|
318
382
|
}
|
|
319
|
-
if (!
|
|
383
|
+
if (!d) {
|
|
320
384
|
// INV-11 (#3330): the equality gate compares against the slot this run
|
|
321
385
|
// publishes to — the staged `_pendingValue` when staged; a lane node's
|
|
322
386
|
// lane value (a node entering the lane this pass has none yet — its
|
|
323
387
|
// committed value, `laneStage` having voided a frame staging).
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
o = !n && u || !e.We || !e.We(i, I);
|
|
328
|
-
} catch (t) {
|
|
329
|
-
// A throwing user comparator is an error of this node's computation.
|
|
330
|
-
// Route it through the same status path as a compute-phase throw so
|
|
331
|
-
// error boundaries contain it; otherwise it unwinds the scheduler
|
|
332
|
-
// flush, bypassing every boundary and wedging the queue (#2837).
|
|
333
|
-
notifyStatus(e, STATUS_ERROR, t);
|
|
334
|
-
}
|
|
388
|
+
const o = e.se !== NOT_PENDING ? e.se : e.l & CONFIG_OVERRIDE && e.T.v !== NOT_PENDING ? e.T.v : e.Ie;
|
|
389
|
+
// A throwing comparator is contained inside `isSame` (A3, #2837; #4111).
|
|
390
|
+
const E = !n && l || !!e.T?.de || !isSame(e, o, T);
|
|
335
391
|
// Effects use `_equals: false` (no per-effect closure). The side effects that
|
|
336
392
|
// the equals closure used to perform — flagging the effect dirty and enqueueing
|
|
337
393
|
// its runner — happen here instead. `!create` matches the previous `initialized`
|
|
338
394
|
// gate: the explicit recompute(node, true) inside effect() does not enqueue, so
|
|
339
395
|
// effect() can call its runner synchronously for the first run.
|
|
340
|
-
if (n &&
|
|
396
|
+
if (n && E &&
|
|
341
397
|
// A stale reader of a lane (REACTIVE_SCREEN_READ) that computed what it
|
|
342
398
|
// last applied owes no run (lanes.ts).
|
|
343
|
-
!(e.R & REACTIVE_SCREEN_READ && !e.
|
|
399
|
+
!(e.R & REACTIVE_SCREEN_READ && !e.Ae && Object.is(e.ge, T)) &&
|
|
344
400
|
// A re-staging pass owes no run: the commit replays the effect (#3802).
|
|
345
|
-
e.
|
|
346
|
-
e.
|
|
401
|
+
e.se === NOT_PENDING) {
|
|
402
|
+
e.Ae = !e.T?.F;
|
|
347
403
|
// Reuse one bound runner per effect — runEffect no-ops on a stale
|
|
348
404
|
// `_modified`, so re-enqueueing the same function is harmless.
|
|
349
405
|
// Lane work's run is the lane's (released at its reveal) — a first
|
|
350
406
|
// pass under a lane too: `effect()` skips the synchronous first run of
|
|
351
407
|
// a lane's node.
|
|
352
|
-
if (!t ||
|
|
408
|
+
if (!t || C !== null) globalQueue.enqueue(n, e.W ??= GlobalQueue.K.bind(null, e), C);
|
|
353
409
|
}
|
|
354
410
|
// Lanes. A written guess's own pass from the frame: its source
|
|
355
411
|
// recomputed it — the truth (A18: "the source is whatever recomputes the
|
|
@@ -357,7 +413,7 @@ function recompute(e, t = false) {
|
|
|
357
413
|
// confirm is a landing too — the lane's end commits it) and notifies
|
|
358
414
|
// only a correction: an equal truth re-runs nothing (lanes.ts). (As the
|
|
359
415
|
// lane's own work the lane judged it — `laneStage`.)
|
|
360
|
-
if (!t &&
|
|
416
|
+
if (!t && C === null && e.l & CONFIG_GUESS && laneOutcomeHook(e, T, d)) ; else if (d) ; else if (E) {
|
|
361
417
|
// L2: a first pass is born held — staged, committed with the
|
|
362
418
|
// transaction, its effect's first run the landing's (A29; `effect()`
|
|
363
419
|
// skips the synchronous first run on a staged value) — when the flush
|
|
@@ -374,15 +430,15 @@ function recompute(e, t = false) {
|
|
|
374
430
|
// pass that runs before anything joins: the pass's input, not a verdict
|
|
375
431
|
// after it. An effect still carrying an uncommitted staged value
|
|
376
432
|
// re-stages: the commit applies the latest pass, not the born-held one.
|
|
377
|
-
if (
|
|
433
|
+
if (C !== null) {
|
|
378
434
|
// Lane work: the lane's value — into the slot while the lane has not
|
|
379
435
|
// revealed (its reveal shows it), as its staging once it has (the
|
|
380
436
|
// next reveal promotes it; the screen keeps the revealed value). An
|
|
381
437
|
// effect's value slot is private: the run the lane holds is what
|
|
382
438
|
// shows it — unless the effect was born held, whose first run is the
|
|
383
439
|
// commit's (A29): it re-stages, as below.
|
|
384
|
-
if (n && e.
|
|
385
|
-
} else if (t ? !((flushTransaction !== null || passTx !== null) && (
|
|
440
|
+
if (n && e.se === NOT_PENDING) e.Ie = T; else if (n || C.ke) e.se = T; else e.T.v = T;
|
|
441
|
+
} else if (t ? !((flushTransaction !== null || passTx !== null) && (A || creatorPass(f)?.R & REACTIVE_JOINED)) : n && e.se === NOT_PENDING) {
|
|
386
442
|
// A first pass publishes directly. So does an effect: its value slot
|
|
387
443
|
// is private (only its own run reads it), and the staging round-trip
|
|
388
444
|
// (queuePendingNode + commitPendingNodes) paid per effect on the
|
|
@@ -393,9 +449,9 @@ function recompute(e, t = false) {
|
|
|
393
449
|
// sources commit) must see the fresh value while PLAIN reads stay
|
|
394
450
|
// committed until flush (#3009 purity). The pending round-trip is
|
|
395
451
|
// that separation; it cannot be skipped on any path a pull can reach.
|
|
396
|
-
e.
|
|
452
|
+
e.Ie = T;
|
|
397
453
|
} else {
|
|
398
|
-
e.
|
|
454
|
+
e.se = T;
|
|
399
455
|
// Born held: in the transaction from birth, so a read of it this
|
|
400
456
|
// tick is a read of its world (the mount-during-a-hold case), not
|
|
401
457
|
// only after the seam parks it.
|
|
@@ -405,41 +461,55 @@ function recompute(e, t = false) {
|
|
|
405
461
|
// (`commitPendingNode` initializes it) — every reader of it
|
|
406
462
|
// derives from the future (`read`), an untracked one throws
|
|
407
463
|
// (A19 exc. 1).
|
|
408
|
-
e.
|
|
464
|
+
e.m |= STATUS_UNINITIALIZED;
|
|
465
|
+
// A boundary that has not shown content catches it: its fallback
|
|
466
|
+
// waits for the commit (#3540). (The pass's own status clear above
|
|
467
|
+
// dropped what the read recorded.) Not when the flush has joined
|
|
468
|
+
// the hold and the pass is its own (not a verdict lane's mount,
|
|
469
|
+
// #3851): the mount is part of the hold, and the boundary appears
|
|
470
|
+
// at its commit, its fallback never seen (D-13, U-3) — a `<Show>`
|
|
471
|
+
// mounting it over a landing included.
|
|
472
|
+
freshHook?.(e, !i && flushTransaction);
|
|
409
473
|
}
|
|
410
474
|
}
|
|
411
475
|
// insertSubs only walks _subs (no scheduling of its own), so a
|
|
412
476
|
// subscriber-less node has nothing to notify.
|
|
413
|
-
if (e.
|
|
477
|
+
if (e.o !== null) {
|
|
414
478
|
insertSubs(e);
|
|
415
479
|
// Lane work re-staged: the lane's members re-derive as its work.
|
|
416
|
-
if (
|
|
480
|
+
if (C !== null) laneDirty(e, C);
|
|
417
481
|
}
|
|
418
|
-
} else if (e.
|
|
419
|
-
for (let t = e.
|
|
482
|
+
} else if (e.Fe != S) {
|
|
483
|
+
for (let t = e.o; t !== null; t = t.t) insertIntoHeapHeight(t.u, dirtyQueue);
|
|
420
484
|
}
|
|
421
485
|
// Silent recovery: errored → unchanged value fires no notification, but
|
|
422
486
|
// dependents still holding the propagated error consumed their dirty flag
|
|
423
487
|
// in an errored run and may sit on stale commits (#2949). Changed-value
|
|
424
488
|
// recoveries ride insertSubs above; a comparator throw re-errored the node
|
|
425
489
|
// (el._x?._error re-set), so this only runs on a genuinely clean recovery.
|
|
426
|
-
if (!
|
|
427
|
-
if (
|
|
490
|
+
if (!E && !e.T?.F) {
|
|
491
|
+
if (r !== undefined) settleErroredDependents(e, r);
|
|
428
492
|
// Self-registration (this node's own superseded flight) is the #3181
|
|
429
493
|
// sweep's business below — retiring it here too would walk twice.
|
|
430
|
-
if (
|
|
494
|
+
if (_) for (const t of _) if (t !== e) settlePendingSource(e, t);
|
|
431
495
|
}
|
|
432
496
|
// #3181: a synchronous settle supersedes the old landing callback, so
|
|
433
497
|
// recompute owns its pending-source sweep. An uninitialized node without
|
|
434
498
|
// a replacement source still has no truth to reveal and must stay parked.
|
|
435
|
-
if (
|
|
499
|
+
if (a && !(e.m & (STATUS_PENDING | STATUS_UNINITIALIZED))) settlePendingSource(e);
|
|
436
500
|
}
|
|
437
501
|
// A first pass that went pending where the born-held arm would have held
|
|
438
502
|
// its value (it read a held node, or a verdict lane's mount read a staging,
|
|
439
|
-
// #3851) is
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
|
|
503
|
+
// #3851) is held like it: its answer is the hold's, and the hold waits
|
|
504
|
+
// for it (A29, #3800). A boundary that has not shown content catches it
|
|
505
|
+
// and it is held all the same — its answer reveals no earlier than the
|
|
506
|
+
// hold's commit (D-12) — but the hold does not wait for it: its only
|
|
507
|
+
// readers are behind the fallback (`_hidden`; F-4, #3540). A boundary
|
|
508
|
+
// re-armed by `on` owns what it collects outright (D-14): not held.
|
|
509
|
+
if (t && C === null && A && e.m & STATUS_PENDING) {
|
|
510
|
+
const t = flushTransaction ?? passTx;
|
|
511
|
+
if (t && !t.v && freshHook?.(e) !== CAUGHT_REARMED) holdNode(e, t);
|
|
512
|
+
}
|
|
443
513
|
// Dependencies are the committed frame's until it is replaced (A30, #3410):
|
|
444
514
|
// a pass that staged its value leaves the previous pass's tail linked for
|
|
445
515
|
// `commitPendingNode` to trim, so a write to a dependency the committed
|
|
@@ -457,7 +527,7 @@ function recompute(e, t = false) {
|
|
|
457
527
|
// kept at a park — one spurious recompute at most). A creation pass and a
|
|
458
528
|
// tracked effect trim now: their frames are replaceable like a direct
|
|
459
529
|
// commit, and a tracked effect's spurious run would be user-visible.
|
|
460
|
-
if (!e.
|
|
530
|
+
if (!e.T?.F && e.se === NOT_PENDING && !(n && e.Ae)) {
|
|
461
531
|
if (t || n === EFFECT_TRACKED) trimStaleDeps(e); else heldTrim(e);
|
|
462
532
|
}
|
|
463
533
|
// A staged value, a parked frame (L2: the commit retires it), or status the
|
|
@@ -465,10 +535,10 @@ function recompute(e, t = false) {
|
|
|
465
535
|
// node for this flush's commit. A first pass queues only when pending or
|
|
466
536
|
// born held: otherwise its value published directly. A queued pass's
|
|
467
537
|
// children are uncommitted (CONFIG_STAGED) until that commit.
|
|
468
|
-
if (e.
|
|
469
|
-
e.
|
|
538
|
+
if (e.se !== NOT_PENDING || e.T !== null && (e.T.Ue !== null || e.T.me !== null) || (e.m & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0 && (!t || (e.m & STATUS_PENDING) !== 0)) {
|
|
539
|
+
e.l |= CONFIG_STAGED;
|
|
470
540
|
// Lane work is the lane's to reveal (its seam), not this flush's commit.
|
|
471
|
-
if (
|
|
541
|
+
if (C === null) queuePendingNode(e);
|
|
472
542
|
}
|
|
473
543
|
setPassLane(E);
|
|
474
544
|
// Missed-wake reschedule (see the finally above): values this pass read
|
|
@@ -476,7 +546,7 @@ function recompute(e, t = false) {
|
|
|
476
546
|
// accept the node. Equality gates stop same-value landings from cascading,
|
|
477
547
|
// and a re-run only latches again if another nested commit changes a dep
|
|
478
548
|
// beneath it — convergent unless deps genuinely keep changing.
|
|
479
|
-
if (
|
|
549
|
+
if (c) {
|
|
480
550
|
enqueueSub(e);
|
|
481
551
|
schedule();
|
|
482
552
|
}
|
|
@@ -491,9 +561,9 @@ function updateIfNecessary(e) {
|
|
|
491
561
|
// would resurrect it (#2983) — readers serve its last value.
|
|
492
562
|
if (e.R & (REACTIVE_RECOMPUTING_DEPS | REACTIVE_DISPOSED)) return;
|
|
493
563
|
if (e.R & REACTIVE_CHECK) {
|
|
494
|
-
for (let t = e.
|
|
495
|
-
const n = t.
|
|
496
|
-
if (n.
|
|
564
|
+
for (let t = e.C; t; t = t.G) {
|
|
565
|
+
const n = t.S;
|
|
566
|
+
if (n.X) {
|
|
497
567
|
updateIfNecessary(n);
|
|
498
568
|
}
|
|
499
569
|
if (e.R & REACTIVE_DIRTY) {
|
|
@@ -501,7 +571,7 @@ function updateIfNecessary(e) {
|
|
|
501
571
|
}
|
|
502
572
|
}
|
|
503
573
|
}
|
|
504
|
-
if (e.R & REACTIVE_DIRTY || e.
|
|
574
|
+
if (e.R & REACTIVE_DIRTY || e.T?.F && e.ie < clock && !e.T?.Se) {
|
|
505
575
|
recompute(e);
|
|
506
576
|
}
|
|
507
577
|
// The guard above refused an already-disposed node; the recompute it just
|
|
@@ -520,75 +590,75 @@ function computed(e, t) {
|
|
|
520
590
|
// `in` (not `!== undefined`): an explicit `loadingValue: undefined` on a
|
|
521
591
|
// `T | undefined` node is a real commit #0. The typeof guard tolerates
|
|
522
592
|
// non-object option values that older call shapes force through `as any`.
|
|
523
|
-
const
|
|
593
|
+
const o = t !== null && typeof t === "object" && "loadingValue" in t;
|
|
524
594
|
// Two literals, one per tier, selected at build time (the observe flag is
|
|
525
595
|
// a literal after replacement; the untaken branch is dead code). The observe
|
|
526
|
-
// literal is the prod literal plus its `_name`
|
|
527
|
-
// boilerplate, because a post-construction `self._name = …` forces a
|
|
596
|
+
// literal is the prod literal plus its `_name` and `_loc` slots — slots in
|
|
597
|
+
// the boilerplate, because a post-construction `self._name = …` forces a
|
|
528
598
|
// hidden-class transition and an out-of-object property store on EVERY node
|
|
529
599
|
// (measured: the whole of the observe tier's creation overhead). Keep the
|
|
530
600
|
// two in sync — the dist artifact test pins observe's key set to prod's
|
|
531
|
-
// plus
|
|
532
|
-
const
|
|
601
|
+
// plus the slots.
|
|
602
|
+
const E = {
|
|
533
603
|
id: inheritId(t, n, context),
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
604
|
+
l: (n ? CONFIG_TRANSPARENT : 0) | (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (!context || t?.lazy ? CONFIG_AUTO_DISPOSE : 0) | (t?.sync ? CONFIG_SYNC : 0) | (t?.le ? CONFIG_NO_SNAPSHOT : 0) | (t?.Le ?? 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
605
|
+
xe: t?.equals ?? isEqual,
|
|
606
|
+
He: null,
|
|
607
|
+
A: context?.A ?? defaultContext,
|
|
608
|
+
ye: 0,
|
|
609
|
+
X: e,
|
|
610
|
+
Ie: o ? t.loadingValue : undefined,
|
|
611
|
+
Fe: 0,
|
|
612
|
+
be: undefined,
|
|
613
|
+
We: null,
|
|
614
|
+
C: null,
|
|
544
615
|
_: null,
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
qe: null,
|
|
616
|
+
Ne: 0,
|
|
617
|
+
o: null,
|
|
618
|
+
ve: null,
|
|
549
619
|
_parent: context,
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
620
|
+
pe: null,
|
|
621
|
+
Ze: null,
|
|
622
|
+
De: null,
|
|
553
623
|
R: t?.lazy ? REACTIVE_LAZY : REACTIVE_NONE,
|
|
554
624
|
// A loadingValue node is born committed: commit #0 is already in _value.
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
625
|
+
m: o ? 0 : STATUS_UNINITIALIZED,
|
|
626
|
+
ie: clock,
|
|
627
|
+
se: NOT_PENDING,
|
|
628
|
+
Me: -1,
|
|
629
|
+
ce: o,
|
|
560
630
|
// Cold machinery (async slots) lives one hop away in the lazily-allocated extension — the core literal MUST
|
|
561
631
|
// stay under V8's in-object boundary (§12: past ~39 fields every
|
|
562
632
|
// allocation spills to a backing store and creation cost ~4x's).
|
|
563
|
-
|
|
633
|
+
T: null
|
|
564
634
|
};
|
|
565
|
-
if (t?.unobserved) ext(
|
|
566
|
-
setupComputedNode(
|
|
567
|
-
return
|
|
635
|
+
if (t?.unobserved) ext(E).we = t.unobserved;
|
|
636
|
+
setupComputedNode(E, t);
|
|
637
|
+
return E;
|
|
568
638
|
}
|
|
569
639
|
|
|
570
640
|
/** Lazily allocate a node's cold extension (ONE shape for signals and
|
|
571
641
|
* computeds — `_x` access stays monomorphic). Installers write through
|
|
572
642
|
* this; hot paths read `el._x?._field` gated by the _config presence bits.
|
|
573
643
|
* Never call ext() just to store a field's default. */ function ext(e) {
|
|
574
|
-
return e.
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
ke: null,
|
|
644
|
+
return e.T ??= {
|
|
645
|
+
Se: null,
|
|
646
|
+
ae: null,
|
|
647
|
+
F: undefined,
|
|
648
|
+
ue: undefined,
|
|
649
|
+
fe: undefined,
|
|
650
|
+
we: undefined,
|
|
651
|
+
Pe: undefined,
|
|
652
|
+
Ue: null,
|
|
653
|
+
me: null,
|
|
585
654
|
Re: null,
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
655
|
+
Ve: false,
|
|
656
|
+
Be: NOT_PENDING,
|
|
657
|
+
Qe: -1,
|
|
658
|
+
Ee: 0,
|
|
659
|
+
v: NOT_PENDING,
|
|
660
|
+
I: 0,
|
|
661
|
+
de: undefined
|
|
592
662
|
};
|
|
593
663
|
}
|
|
594
664
|
|
|
@@ -604,7 +674,12 @@ function computed(e, t) {
|
|
|
604
674
|
* the boundary collects it and shows its fallback, and the pass is the
|
|
605
675
|
* boundary's, not the tick's (A29's boundary exemption, #3540). */ function joinPass(e, t) {
|
|
606
676
|
e.R |= REACTIVE_JOINED;
|
|
607
|
-
|
|
677
|
+
// A new memo's first pass is the hold's work (born held) but not the
|
|
678
|
+
// tick's: a render effect that reads it decides — under a boundary that
|
|
679
|
+
// catches it, the boundary's; otherwise the tick joins the hold (#3540:
|
|
680
|
+
// the reader's position, not where the memo was created). Its catch, if
|
|
681
|
+
// any, is at its staging.
|
|
682
|
+
if (t.m & STATUS_UNINITIALIZED || e.i !== EFFECT_RENDER) joinPassTx(txOf(t), !e.i && e.m & STATUS_UNINITIALIZED || freshHook?.(e));
|
|
608
683
|
}
|
|
609
684
|
|
|
610
685
|
/** A15's stale reader (shared-hole and reveal corollaries): a render effect
|
|
@@ -613,11 +688,11 @@ function computed(e, t) {
|
|
|
613
688
|
* re-derived after that transaction's landing (`_reruns`,
|
|
614
689
|
* REACTIVE_FRAME_READ) so the frame catches up with it once it is the
|
|
615
690
|
* committed world. Not a frame born into that transaction (held and never
|
|
616
|
-
* committed — created by one of its held passes, A29): that
|
|
617
|
-
* transaction's values and keeps staging. Not of a flight
|
|
618
|
-
* already visible (CONFIG_INPUTS_PUBLISHED, #3305): its
|
|
619
|
-
* would tear against them, so the reveal observes the
|
|
620
|
-
* per pass.
|
|
691
|
+
* committed — created by one of its held passes, A29; `bornInto`): that
|
|
692
|
+
* one reads its transaction's values and keeps staging. Not of a flight
|
|
693
|
+
* whose inputs are already visible (CONFIG_INPUTS_PUBLISHED, #3305): its
|
|
694
|
+
* committed value would tear against them, so the reveal observes the
|
|
695
|
+
* flight instead. Once per pass.
|
|
621
696
|
*
|
|
622
697
|
* Lane work is a stale reader too (maintainer, 2026-10-01: a lane sees the
|
|
623
698
|
* screen plus its own guesses). A held write is held because showing it
|
|
@@ -641,20 +716,30 @@ function computed(e, t) {
|
|
|
641
716
|
// read the screen. Lane work with no committed value yet reads a flight
|
|
642
717
|
// as a mount's memo does: it enters, and the boundary it mounts catches
|
|
643
718
|
// the pending (#3540).
|
|
644
|
-
|
|
645
|
-
|
|
719
|
+
// (Verdict readers and lanes exist only with their engines installed.)
|
|
720
|
+
const n = observeFlightHook && e.l & CONFIG_VERDICT;
|
|
721
|
+
const o = laneStageHook && passLane !== null;
|
|
722
|
+
if ((!o || e.m & STATUS_UNINITIALIZED && t.m & STATUS_PENDING) && !n && (e.i !== EFFECT_RENDER || t.l & CONFIG_INPUTS_PUBLISHED)) return false;
|
|
646
723
|
const E = txOf(t);
|
|
647
724
|
// Lane work and verdict readers are never the transaction's pass, whichever
|
|
648
725
|
// flush runs it: a node T holds re-enters T when it re-runs (`recompute`'s
|
|
649
726
|
// head), and the pass may then read a guess and become the lane's.
|
|
650
|
-
if (E === flushTransaction &&
|
|
727
|
+
if (E === flushTransaction && !o && !n || bornInto(e, E)) return false;
|
|
651
728
|
staleReader(e, E);
|
|
652
729
|
return true;
|
|
653
730
|
}
|
|
654
731
|
|
|
732
|
+
/** A node born into transaction `t`: held by it and never committed (A29,
|
|
733
|
+
* ruling A) — uninitialized (born held), or a member of the frame one of its
|
|
734
|
+
* held passes built (CONFIG_HELD_FRAME). Its branch exists only in `t`: it
|
|
735
|
+
* has no screen to read, so it reads `t`'s values, whichever flush runs it,
|
|
736
|
+
* and keeps staging (#4010). */ function bornInto(e, t) {
|
|
737
|
+
return (e.m & STATUS_UNINITIALIZED || e.l & CONFIG_HELD_FRAME) && e.l & CONFIG_HELD && txOf(e) === t;
|
|
738
|
+
}
|
|
739
|
+
|
|
655
740
|
/** The computed whose pass a creation under `owner` belongs to (a root's is
|
|
656
741
|
* its nearest computed ancestor's), or null outside every pass. */ function creatorPass(e) {
|
|
657
|
-
return e !== null && e.
|
|
742
|
+
return e !== null && e.je ? e.Ye : e;
|
|
658
743
|
}
|
|
659
744
|
|
|
660
745
|
/** L2 — park the previous pass's frame for this node's commit: the children
|
|
@@ -663,11 +748,11 @@ function computed(e, t) {
|
|
|
663
748
|
* the frame that replaces them (`commitPendingNode`) or when the owner does. */ function parkChildren(e) {
|
|
664
749
|
markDisposal(e);
|
|
665
750
|
const t = ext(e);
|
|
666
|
-
t.
|
|
667
|
-
t.
|
|
668
|
-
e.
|
|
669
|
-
e.
|
|
670
|
-
e.
|
|
751
|
+
t.me = e.He;
|
|
752
|
+
t.Ue = e.De;
|
|
753
|
+
e.He = null;
|
|
754
|
+
e.De = null;
|
|
755
|
+
e.ye = 0;
|
|
671
756
|
}
|
|
672
757
|
|
|
673
758
|
/**
|
|
@@ -675,54 +760,54 @@ function computed(e, t) {
|
|
|
675
760
|
* so V8 sees the full hidden class shape at construction time. Effects always run in lazy
|
|
676
761
|
* mode (recompute is called explicitly by `effect()`), so we hardcode the lazy bits and skip
|
|
677
762
|
* the auto-dispose CONFIG bit (effect() previously cleared it post-construction).
|
|
678
|
-
*/ function createEffectNode(e, t, n,
|
|
679
|
-
const s =
|
|
763
|
+
*/ function createEffectNode(e, t, n, o, E) {
|
|
764
|
+
const s = E?.transparent ?? false;
|
|
680
765
|
// Prod and observe boilerplates — see computed() for why the observe tier
|
|
681
766
|
// gets its `_name` as a literal slot rather than a write after the fact.
|
|
682
767
|
// The default label is the node kind (tracked effects relabel their computed
|
|
683
768
|
// in trackedEffect); the wrappers in signals.ts no longer spread a name into
|
|
684
769
|
// the options to get it.
|
|
685
770
|
const i = {
|
|
686
|
-
id: inheritId(
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
771
|
+
id: inheritId(E, s, context),
|
|
772
|
+
l: (s ? CONFIG_TRANSPARENT : 0) | (E?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (E?.sync ? CONFIG_SYNC : 0) | (E?.Le ?? 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
773
|
+
xe: false,
|
|
774
|
+
He: null,
|
|
775
|
+
A: context?.A ?? defaultContext,
|
|
776
|
+
ye: 0,
|
|
777
|
+
X: e,
|
|
778
|
+
Ie: undefined,
|
|
779
|
+
Fe: 0,
|
|
780
|
+
be: undefined,
|
|
781
|
+
We: null,
|
|
782
|
+
C: null,
|
|
697
783
|
_: null,
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
qe: null,
|
|
784
|
+
Ne: 0,
|
|
785
|
+
o: null,
|
|
786
|
+
ve: null,
|
|
702
787
|
_parent: context,
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
788
|
+
pe: null,
|
|
789
|
+
Ze: null,
|
|
790
|
+
De: null,
|
|
706
791
|
R: REACTIVE_LAZY,
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
i:
|
|
718
|
-
|
|
792
|
+
m: STATUS_UNINITIALIZED,
|
|
793
|
+
ie: clock,
|
|
794
|
+
se: NOT_PENDING,
|
|
795
|
+
Me: -1,
|
|
796
|
+
ce: false,
|
|
797
|
+
Ae: false,
|
|
798
|
+
ge: undefined,
|
|
799
|
+
Ke: t,
|
|
800
|
+
qe: n,
|
|
801
|
+
Je: undefined,
|
|
802
|
+
i: o,
|
|
803
|
+
T: null
|
|
719
804
|
};
|
|
720
805
|
// Effects dispatch status through the SHARED notifier (statusNotifierOf,
|
|
721
806
|
// keyed off _type) — storing it per node forced a full NodeExtension
|
|
722
807
|
// allocation on EVERY effect at creation (an alloc + 19 field stores,
|
|
723
808
|
// +23% effect creation, caught by the creation benches). Only genuinely
|
|
724
809
|
// per-node channels (boundaries) live on _x.
|
|
725
|
-
if (
|
|
810
|
+
if (E?.unobserved) ext(i).we = E.unobserved;
|
|
726
811
|
setupComputedNode(i, lazyOptions);
|
|
727
812
|
return i;
|
|
728
813
|
}
|
|
@@ -751,16 +836,16 @@ const lazyOptions = {
|
|
|
751
836
|
};
|
|
752
837
|
|
|
753
838
|
function setupComputedNode(e, t) {
|
|
754
|
-
e.
|
|
755
|
-
const n = context?.
|
|
839
|
+
e.We = e;
|
|
840
|
+
const n = context?.je ? context.Ye : context;
|
|
756
841
|
if (context) linkChild(context, e);
|
|
757
|
-
if (n) e.
|
|
758
|
-
|
|
842
|
+
if (n) e.Fe = n.Fe + 1;
|
|
843
|
+
wireExternalSourceHook?.(e);
|
|
759
844
|
!t?.lazy && recompute(e, true);
|
|
760
845
|
if (snapshotCaptureActive && !t?.lazy) {
|
|
761
|
-
if (!(e.
|
|
762
|
-
ext(e).
|
|
763
|
-
e.
|
|
846
|
+
if (!(e.m & STATUS_PENDING) && !(e.l & CONFIG_NO_SNAPSHOT)) {
|
|
847
|
+
ext(e).Pe = e.Ie === undefined ? NO_SNAPSHOT : e.Ie;
|
|
848
|
+
e.l |= CONFIG_HAS_SNAPSHOT;
|
|
764
849
|
snapshotSources.add(e);
|
|
765
850
|
}
|
|
766
851
|
}
|
|
@@ -768,29 +853,29 @@ function setupComputedNode(e, t) {
|
|
|
768
853
|
|
|
769
854
|
function signal(e, t) {
|
|
770
855
|
// Prod and observe boilerplates — see computed(). The observe literal adds
|
|
771
|
-
// `_name` and `_owner` (the creating owner, stamped by registerGraph for
|
|
856
|
+
// `_name`, `_loc` and `_owner` (the creating owner, stamped by registerGraph for
|
|
772
857
|
// createSignal nodes so ownerPath can locate signal subjects; null here,
|
|
773
858
|
// and staying null on internal signals — one shape either way).
|
|
774
859
|
const n = {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
860
|
+
xe: t?.equals ?? isEqual,
|
|
861
|
+
l: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.le ? CONFIG_NO_SNAPSHOT : 0),
|
|
862
|
+
Ie: e,
|
|
863
|
+
o: null,
|
|
864
|
+
ve: null,
|
|
865
|
+
ie: clock,
|
|
866
|
+
se: NOT_PENDING,
|
|
782
867
|
// Signal-literal diet (§12e): NO _time/_fn/_statusFlags slots. Stores
|
|
783
868
|
// materialize one signal per touched leaf, so signal bytes are store
|
|
784
869
|
// bytes. _time is write-only on signals (every read site is computed-
|
|
785
870
|
// typed error-retry gating); _fn/_statusFlags read falsy-identically as
|
|
786
871
|
// missing properties on the shared paths (undefined masks to 0).
|
|
787
|
-
|
|
788
|
-
|
|
872
|
+
Me: -1,
|
|
873
|
+
T: null
|
|
789
874
|
};
|
|
790
|
-
if (t?.unobserved) ext(n).
|
|
791
|
-
if (snapshotCaptureActive && !(n.
|
|
792
|
-
ext(n).
|
|
793
|
-
n.
|
|
875
|
+
if (t?.unobserved) ext(n).we = t.unobserved;
|
|
876
|
+
if (snapshotCaptureActive && !(n.l & CONFIG_NO_SNAPSHOT)) {
|
|
877
|
+
ext(n).Pe = e === undefined ? NO_SNAPSHOT : e;
|
|
878
|
+
n.l |= CONFIG_HAS_SNAPSHOT;
|
|
794
879
|
snapshotSources.add(n);
|
|
795
880
|
}
|
|
796
881
|
return n;
|
|
@@ -809,20 +894,21 @@ function isEqual(e, t) {
|
|
|
809
894
|
* object, no NodeExtension, no post-construction expandos. `ownedWrite` is
|
|
810
895
|
* baked in: the setter carries the owned-scope guard; node-level writes are
|
|
811
896
|
* the store's notification machinery. The observe literal adds the label
|
|
812
|
-
*
|
|
897
|
+
* and location slots (the store relabels `store.<key>` and stamps the
|
|
898
|
+
* store's `loc` when the engine is installed). */ function slotSignal(e, t, n, o, E) {
|
|
813
899
|
const s = {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
acc:
|
|
900
|
+
xe: t,
|
|
901
|
+
l: CONFIG_OWNED_WRITE | CONFIG_SLOT_NODE,
|
|
902
|
+
Ie: e,
|
|
903
|
+
o: null,
|
|
904
|
+
ve: null,
|
|
905
|
+
ie: clock,
|
|
906
|
+
se: NOT_PENDING,
|
|
907
|
+
Me: -1,
|
|
908
|
+
T: null,
|
|
909
|
+
O: n,
|
|
910
|
+
ze: o,
|
|
911
|
+
acc: E,
|
|
826
912
|
px: undefined,
|
|
827
913
|
pxv: undefined
|
|
828
914
|
};
|
|
@@ -848,6 +934,16 @@ function setVerdict(e) {
|
|
|
848
934
|
* Use `untrack` inside a memo or effect when you need to read a signal once
|
|
849
935
|
* without making the surrounding computation depend on its future changes.
|
|
850
936
|
*
|
|
937
|
+
* `untrack` stops dependency tracking, not async settlement. Reading an
|
|
938
|
+
* uninitialized async source still suspends the owning computation; it runs
|
|
939
|
+
* again when that source first resolves. Afterward, the untracked read is a
|
|
940
|
+
* snapshot and does not subscribe the computation to later changes.
|
|
941
|
+
*
|
|
942
|
+
* If the first read must return immediately, give the async source a
|
|
943
|
+
* `loadingValue` before reading it with `untrack`. `isPending` and `latest`
|
|
944
|
+
* inspect updates to an existing value; neither supplies a missing initial
|
|
945
|
+
* value.
|
|
946
|
+
*
|
|
851
947
|
* Pass a `strictReadLabel` string to enable a dev-mode warning: any reactive
|
|
852
948
|
* read inside `fn` that isn't inside a nested tracking scope will log a
|
|
853
949
|
* warning naming the label.
|
|
@@ -861,13 +957,15 @@ function setVerdict(e) {
|
|
|
861
957
|
* log(snapshot);
|
|
862
958
|
* }
|
|
863
959
|
* );
|
|
960
|
+
*
|
|
961
|
+
* const color = createMemo<string>(() => fetchColor(), { loadingValue: "gray" });
|
|
962
|
+
* const initialColor = untrack(color); // "gray" while the fetch is unresolved
|
|
864
963
|
* ```
|
|
865
964
|
*/ function untrack(e, t) {
|
|
866
|
-
if (GlobalQueue.It === null && !tracking && true) return e();
|
|
867
965
|
const n = tracking;
|
|
868
966
|
tracking = false;
|
|
869
967
|
try {
|
|
870
|
-
if (
|
|
968
|
+
if (externalUntrackHook) return externalUntrackHook(e);
|
|
871
969
|
return e();
|
|
872
970
|
} finally {
|
|
873
971
|
tracking = n;
|
|
@@ -901,13 +999,13 @@ function setVerdict(e) {
|
|
|
901
999
|
// pay-for-use contract. Owner-lifecycle nodes are dead: recomputing would
|
|
902
1000
|
// re-run user code in a torn-down tree (and discard manual writes on
|
|
903
1001
|
// derived-writable signals), so reads return the last committed value.
|
|
904
|
-
if (e.
|
|
1002
|
+
if (e.l & CONFIG_AUTO_DISPOSE) {
|
|
905
1003
|
const t = e._parent;
|
|
906
1004
|
if (t !== null) {
|
|
907
1005
|
// A dormant node was off the chain when its owner died, so the strip
|
|
908
1006
|
// in disposeChildren missed it: freeze here instead (#3024).
|
|
909
1007
|
if (t.R & REACTIVE_DISPOSED) {
|
|
910
|
-
e.
|
|
1008
|
+
e.l &= ~CONFIG_AUTO_DISPOSE;
|
|
911
1009
|
return;
|
|
912
1010
|
}
|
|
913
1011
|
// A zombie never left its chain (the parked frame is drained whole,
|
|
@@ -935,7 +1033,7 @@ function setVerdict(e) {
|
|
|
935
1033
|
* never the graph — A32).
|
|
936
1034
|
* False means the reader derives from the staged value.
|
|
937
1035
|
*/ function readerSeesCommitted(e, t) {
|
|
938
|
-
return
|
|
1036
|
+
return !t || e.se === NOT_PENDING || t.l & CONFIG_CHILDREN_FORBIDDEN;
|
|
939
1037
|
}
|
|
940
1038
|
|
|
941
1039
|
/** A28 — set when a node is staged (queuePendingNode) OUTSIDE a flush;
|
|
@@ -956,21 +1054,21 @@ function markUnflushedStaged() {
|
|
|
956
1054
|
* is unflushed. Exempt: promoted writes (A28 (4): a write issued inside a
|
|
957
1055
|
* recompute is promoted at that recompute's end — boundary and loading
|
|
958
1056
|
* machinery, signals declared for in-computation writes). */ function unflushedValue(e) {
|
|
959
|
-
if (globalQueue.
|
|
1057
|
+
if (globalQueue.q || e.se === NOT_PENDING || e.l & CONFIG_PROMOTED) return NOT_PENDING;
|
|
960
1058
|
// A held node (L2) carries a staged value between flushes too, but it was
|
|
961
1059
|
// flushed — and parked. Not an unflushed write — unless rewritten since:
|
|
962
1060
|
// then the flushed staging is what the world sees until the next flush.
|
|
963
|
-
if (e.
|
|
964
|
-
return e.
|
|
1061
|
+
if (e.l & CONFIG_HELD) return e.T.Qe === clock ? e.T.Be : NOT_PENDING;
|
|
1062
|
+
return e.Ie;
|
|
965
1063
|
}
|
|
966
1064
|
|
|
967
1065
|
/** A28: a held node rewritten outside a flush keeps the staging the last
|
|
968
1066
|
* flush left (`_flushed`) until the flush that carries the rewrite — the
|
|
969
1067
|
* stash is stamped with the clock, which that flush advances. */ function stashFlushed(e) {
|
|
970
|
-
const t = e.
|
|
971
|
-
if (t.
|
|
972
|
-
t
|
|
973
|
-
t.
|
|
1068
|
+
const t = e.T;
|
|
1069
|
+
if (t.Qe !== clock) {
|
|
1070
|
+
t.Be = e.se;
|
|
1071
|
+
t.Qe = clock;
|
|
974
1072
|
unflushedStaged = true;
|
|
975
1073
|
}
|
|
976
1074
|
}
|
|
@@ -990,7 +1088,7 @@ function resyncUnflushedCompanions() {
|
|
|
990
1088
|
unflushedStaged = false;
|
|
991
1089
|
// Length-guarded: the common flush has nothing here and allocates nothing.
|
|
992
1090
|
if (promotedWrites.length !== 0) {
|
|
993
|
-
for (const e of promotedWrites) e.
|
|
1091
|
+
for (const e of promotedWrites) e.l &= ~CONFIG_PROMOTED;
|
|
994
1092
|
promotedWrites.length = 0;
|
|
995
1093
|
}
|
|
996
1094
|
}
|
|
@@ -998,7 +1096,7 @@ function resyncUnflushedCompanions() {
|
|
|
998
1096
|
/** A tracked read's pull of a computed: bring it current when the flush has
|
|
999
1097
|
* reached its height (or the reader is a fresh-pull waiter), and sit the
|
|
1000
1098
|
* reader above it. */ function pullComputed(e, t) {
|
|
1001
|
-
if (e.
|
|
1099
|
+
if (e.Fe >= dirtyQueue.Ge) {
|
|
1002
1100
|
markNode(t);
|
|
1003
1101
|
markHeap(dirtyQueue);
|
|
1004
1102
|
updateIfNecessary(e);
|
|
@@ -1009,27 +1107,27 @@ function resyncUnflushedCompanions() {
|
|
|
1009
1107
|
// never read the PRE-re-ask value as settled. Self-guarded: a clean
|
|
1010
1108
|
// node no-ops and updateIfNecessary refuses disposed nodes (#2983) —
|
|
1011
1109
|
// a dead target serves its last value, which is already quiescent.
|
|
1012
|
-
else if (t.
|
|
1013
|
-
const n = e.
|
|
1110
|
+
else if (t.l & CONFIG_FRESH_READ) updateIfNecessary(e);
|
|
1111
|
+
const n = e.Fe;
|
|
1014
1112
|
// parent check is shallow, might need to be recursive
|
|
1015
|
-
if (n >= t.
|
|
1113
|
+
if (n >= t.Fe && e._parent !== t) t.Fe = n + 1;
|
|
1016
1114
|
}
|
|
1017
1115
|
|
|
1018
1116
|
function read(e) {
|
|
1019
1117
|
let t = context;
|
|
1020
|
-
if (t?.
|
|
1118
|
+
if (t?.je) t = t.Ye;
|
|
1021
1119
|
const n = e;
|
|
1022
1120
|
// CARVE 1: `owner` was `el._firewall || el` — a store leaf's status,
|
|
1023
1121
|
// height and error were read off its projection computed. (Typed loosely
|
|
1024
1122
|
// as the `||` expression was: a signal's `_statusFlags`/`_height` read as
|
|
1025
1123
|
// undefined and mask to 0 on every site below.)
|
|
1026
|
-
const
|
|
1027
|
-
if (typeof n.
|
|
1124
|
+
const o = e;
|
|
1125
|
+
if (typeof n.X === "function") prepareComputed(e);
|
|
1028
1126
|
// The verdict windows (`latest`, `isPending`; verdict.ts): one cold path,
|
|
1029
1127
|
// off this one, present only while a window is open.
|
|
1030
1128
|
if (verdict !== null) return verdict(e, t);
|
|
1031
|
-
if (!n.
|
|
1032
|
-
!(e.
|
|
1129
|
+
if (!n.X && e.T?.Pe === undefined && !snapshotCaptureActive && (!unflushedStaged || e.se === NOT_PENDING) && // A28, see readNodeFast
|
|
1130
|
+
!(e.l & CONFIG_OVERRIDE) && // lanes: the slow path's one arm
|
|
1033
1131
|
true) {
|
|
1034
1132
|
if (t) {
|
|
1035
1133
|
if (tracking) link(e, t);
|
|
@@ -1043,38 +1141,47 @@ function read(e) {
|
|
|
1043
1141
|
// stays out too (rule 3, `frameRead`). The pass remembers it read the
|
|
1044
1142
|
// future (REACTIVE_JOINED): a first pass that did is born held (A29),
|
|
1045
1143
|
// wherever it was created. Lane work sees the screen (`frameRead`).
|
|
1046
|
-
if (e.
|
|
1047
|
-
if (frameRead(t, e)) return e.
|
|
1144
|
+
if (e.l & CONFIG_HELD && !(t.l & CONFIG_CHILDREN_FORBIDDEN)) {
|
|
1145
|
+
if (frameRead(t, e)) return e.Ie;
|
|
1048
1146
|
joinPass(t, e);
|
|
1049
1147
|
}
|
|
1050
1148
|
}
|
|
1051
1149
|
// Committed visibility for untracked and children-forbidden readers.
|
|
1052
|
-
if (!t || e.
|
|
1150
|
+
if (!t || e.se === NOT_PENDING || t.l & CONFIG_CHILDREN_FORBIDDEN) return e.Ie;
|
|
1053
1151
|
// (A plain signal is never uninitialized — the carve-out is `serve`'s.)
|
|
1054
|
-
if (t.
|
|
1152
|
+
if (observeFlightHook && t.l & CONFIG_VERDICT && stagedScreen(t)) return e.Ie;
|
|
1055
1153
|
stagedRead(t);
|
|
1056
|
-
return e.
|
|
1154
|
+
return e.se;
|
|
1057
1155
|
}
|
|
1058
1156
|
// Rule 3: this read serves the committed value to a render effect outside a
|
|
1059
1157
|
// parking flush (decided after the pull below — a pull can join).
|
|
1060
|
-
let
|
|
1158
|
+
let E = false;
|
|
1061
1159
|
if (t) {
|
|
1062
1160
|
if (tracking) {
|
|
1063
1161
|
link(e, t);
|
|
1064
|
-
if (
|
|
1162
|
+
if (o.X) {
|
|
1163
|
+
pullComputed(o, t);
|
|
1164
|
+
// F-6 (#4050): a memo over a verdict reader's value derives from a
|
|
1165
|
+
// display-ahead read — a verdict reader from this pass's end, as if
|
|
1166
|
+
// it probed inline: it reads held values as the screen and shows the
|
|
1167
|
+
// verdict now. Not an effect: a render effect is a frame reader
|
|
1168
|
+
// already, and a user effect's posture is its own reads'.
|
|
1169
|
+
if (!t.i) t.R |= o.l & CONFIG_VERDICT;
|
|
1170
|
+
}
|
|
1065
1171
|
}
|
|
1066
1172
|
// L2, as in the fast block above (tracked or not). A node born into the
|
|
1067
1173
|
// future (uninitialized) has no committed value: every reader derives
|
|
1068
1174
|
// from the future there, a render effect included (A29: a stale reader
|
|
1069
1175
|
// of it cannot fall back to the committed frame and enters instead).
|
|
1070
|
-
if (e.
|
|
1071
|
-
if (
|
|
1176
|
+
if (e.l & CONFIG_HELD && !(e.l & CONFIG_OVERRIDE) && !(t.l & CONFIG_CHILDREN_FORBIDDEN)) {
|
|
1177
|
+
if (o.m & STATUS_UNINITIALIZED || !frameRead(t, e)) joinPass(t, e); else E = true;
|
|
1072
1178
|
}
|
|
1073
1179
|
}
|
|
1074
1180
|
// Lanes: a lane's node (after the pull — the node is current). NOT_PENDING
|
|
1075
1181
|
// falls through: lane work's read of a pending member throws like any.
|
|
1076
|
-
|
|
1077
|
-
|
|
1182
|
+
// (CONFIG_OVERRIDE is the lane engine's: no engine, no lane's node.)
|
|
1183
|
+
if (laneReadHook && e.l & CONFIG_OVERRIDE) {
|
|
1184
|
+
const n = laneReadHook(t, e);
|
|
1078
1185
|
if (n !== NOT_PENDING) return n;
|
|
1079
1186
|
}
|
|
1080
1187
|
// A stale reader of a held flight (rule 3, A15's reveal corollary) is served
|
|
@@ -1089,53 +1196,58 @@ function read(e) {
|
|
|
1089
1196
|
// effect keeps its DOM by throwing, a memo has no DOM and is handed the
|
|
1090
1197
|
// value. `[isPending(x), x()]` reads `[true, stale]` in either order (A10)
|
|
1091
1198
|
// — unless the flight committed beneath inputs already on screen
|
|
1092
|
-
// (`observeFlight`)
|
|
1093
|
-
|
|
1199
|
+
// (`observeFlight`), or the reader was born into the flight's transaction
|
|
1200
|
+
// (#4010): it has no screen, and waits on the flight with its frame.
|
|
1201
|
+
if (o.m & STATUS_PENDING && !E && !(e.l & CONFIG_OVERRIDE) && !(o.m & STATUS_UNINITIALIZED)) {
|
|
1094
1202
|
// A reader with no committed value yet sees the flight pending: a
|
|
1095
1203
|
// boundary a lane mounts shows its fallback (A29's boundary exemption).
|
|
1096
|
-
|
|
1204
|
+
// (A lane, and a verdict reader, exist only with their engines installed.)
|
|
1205
|
+
if (laneStageHook && passLane !== null && !(t?.m & STATUS_UNINITIALIZED)) E = true; else if (observeFlightHook && t !== null && t.l & CONFIG_VERDICT) E = observeFlightHook(t, o);
|
|
1097
1206
|
}
|
|
1098
|
-
if (
|
|
1207
|
+
if (o.m & STATUS_PENDING && !E) {
|
|
1099
1208
|
// A reader landing on a pending node throws; an untracked read of an
|
|
1100
1209
|
// UNINITIALIZED node has no committed value to serve and throws too. A
|
|
1101
1210
|
// children-forbidden reader (createTrackedEffect / onSettled) sees the
|
|
1102
1211
|
// frame, not the graph (A32): the committed value, and the NotReady only
|
|
1103
1212
|
// where there is none (uninitialized).
|
|
1104
1213
|
if (t) {
|
|
1105
|
-
if (!(t.
|
|
1214
|
+
if (!(t.l & CONFIG_CHILDREN_FORBIDDEN) || o.m & STATUS_UNINITIALIZED) {
|
|
1106
1215
|
// An untracked read of a pending node still re-runs its reader when
|
|
1107
1216
|
// the node settles — unless nobody is reading (`spectating`, #3528)
|
|
1108
1217
|
// or the reader is the node itself.
|
|
1109
1218
|
if (!tracking && !spectating && e !== t) link(e, t);
|
|
1110
|
-
throw
|
|
1219
|
+
throw o.T?.F;
|
|
1111
1220
|
}
|
|
1112
|
-
} else if (
|
|
1113
|
-
throw
|
|
1221
|
+
} else if (o.m & STATUS_UNINITIALIZED) {
|
|
1222
|
+
throw o.T?.F;
|
|
1114
1223
|
}
|
|
1115
1224
|
}
|
|
1116
1225
|
// An errored derive throws for every late reader instead of silently
|
|
1117
1226
|
// serving node values (memo parity, #2897 ruling).
|
|
1118
|
-
if (
|
|
1227
|
+
if (o.X && o.m & STATUS_ERROR) {
|
|
1119
1228
|
// Only a genuine reactive re-read may retry an errored async source:
|
|
1120
1229
|
// - tracking: owned/tracked scope only (never events / `untrack` / effect side-effect phase)
|
|
1121
1230
|
// - owner._time < clock: only on a later cycle than the one the error was found
|
|
1122
|
-
if (tracking &&
|
|
1123
|
-
recompute(
|
|
1231
|
+
if (tracking && o.ie < clock) {
|
|
1232
|
+
recompute(o);
|
|
1124
1233
|
return read(e);
|
|
1125
|
-
} else throw
|
|
1234
|
+
} else throw o.T?.F;
|
|
1126
1235
|
}
|
|
1127
|
-
if (snapshotCaptureActive && t && t.
|
|
1128
|
-
const n = e.
|
|
1236
|
+
if (snapshotCaptureActive && t && t.l & CONFIG_IN_SNAPSHOT_SCOPE) {
|
|
1237
|
+
const n = e.T?.Pe;
|
|
1129
1238
|
if (n !== undefined) {
|
|
1130
|
-
const
|
|
1131
|
-
const
|
|
1132
|
-
if (
|
|
1133
|
-
return
|
|
1239
|
+
const o = n === NO_SNAPSHOT ? undefined : n;
|
|
1240
|
+
const E = e.se !== NOT_PENDING ? e.se : e.Ie;
|
|
1241
|
+
if (E !== o) t.R |= REACTIVE_SNAPSHOT_STALE;
|
|
1242
|
+
return o;
|
|
1134
1243
|
}
|
|
1135
1244
|
}
|
|
1136
|
-
if (
|
|
1245
|
+
if (E) return e.Ie;
|
|
1246
|
+
// D-41: an untracked read reads the screen, and a shown error is on it
|
|
1247
|
+
// until the answer that replaces it commits.
|
|
1248
|
+
if (!t && e.T?.de) throw e.T.de;
|
|
1137
1249
|
const s = serve(e, t);
|
|
1138
|
-
if (!t && typeof n.
|
|
1250
|
+
if (!t && typeof n.X === "function" && e.l & CONFIG_AUTO_DISPOSE && !(o.m & STATUS_PENDING) && !e.o) {
|
|
1139
1251
|
// Deferred, not inline (#3078): an inline unobserved() here made untracked
|
|
1140
1252
|
// reads destructive — dispose on this read, full revival recompute on the
|
|
1141
1253
|
// next — so consecutive reads could answer differently with no write in
|
|
@@ -1162,16 +1274,18 @@ function read(e) {
|
|
|
1162
1274
|
// an untracked reader has nothing to serve and holds (A19 exception 1) — a
|
|
1163
1275
|
// bookkeeping read (`spectate`) likewise, and a children-forbidden reader
|
|
1164
1276
|
// (A32: the frame, which has nothing here).
|
|
1165
|
-
if (e.
|
|
1277
|
+
if (e.se !== NOT_PENDING && e.m & STATUS_UNINITIALIZED && (!t || spectating || t.l & CONFIG_CHILDREN_FORBIDDEN)) throw new NotReadyError(null);
|
|
1166
1278
|
const n = t && unflushedStaged ? unflushedValue(e) : NOT_PENDING;
|
|
1167
1279
|
if (n !== NOT_PENDING) {
|
|
1168
1280
|
markLateLinker(t);
|
|
1169
1281
|
return n;
|
|
1170
1282
|
}
|
|
1171
|
-
if (readerSeesCommitted(e, t)) return e.
|
|
1172
|
-
if (t.
|
|
1283
|
+
if (readerSeesCommitted(e, t)) return e.Ie;
|
|
1284
|
+
if (observeFlightHook && t.l & CONFIG_VERDICT && !(e.m & STATUS_UNINITIALIZED) && stagedScreen(t)) return e.Ie;
|
|
1173
1285
|
stagedRead(t, e);
|
|
1174
|
-
|
|
1286
|
+
// D-41: the staged answer is an error (`stageError`).
|
|
1287
|
+
if (e.l & CONFIG_STAGED_ERROR) throw e.se;
|
|
1288
|
+
return e.se;
|
|
1175
1289
|
}
|
|
1176
1290
|
|
|
1177
1291
|
/** A pass read a staging of this flush: it derives from what the flush may
|
|
@@ -1183,10 +1297,13 @@ function read(e) {
|
|
|
1183
1297
|
* write never shows through the lane. A verdict lane's work likewise: the
|
|
1184
1298
|
* lane holds verdicts, not the frame's other stagings (#3851) — except a
|
|
1185
1299
|
* verdict reader, which answered for itself (the lane seam, lanes.ts). Not
|
|
1186
|
-
* a read of a node born staged: it has no committed value to
|
|
1187
|
-
* and a re-run would read the same staging and re-queue every
|
|
1188
|
-
|
|
1189
|
-
if (
|
|
1300
|
+
* a read of a node born staged at all: it has no committed value to
|
|
1301
|
+
* re-derive on, and a re-run would read the same staging and re-queue every
|
|
1302
|
+
* round (#3969: nor does it make a later lane read a re-derive, `enterLane`). */ function stagedRead(e, t) {
|
|
1303
|
+
if (!(t?.m & STATUS_UNINITIALIZED)) {
|
|
1304
|
+
e.R |= REACTIVE_STAGED_READ;
|
|
1305
|
+
if (laneSeamsHook && passLane !== null) stagedReaders.push(e);
|
|
1306
|
+
}
|
|
1190
1307
|
}
|
|
1191
1308
|
|
|
1192
1309
|
/** A10 for a staged node: a verdict reader (the pass entered a window) that
|
|
@@ -1196,8 +1313,9 @@ function read(e) {
|
|
|
1196
1313
|
* #3322 — one run, on the committed world), or with the hold. Before any
|
|
1197
1314
|
* transaction the answer is the seam's and the reader watches it
|
|
1198
1315
|
* (verdict.ts). A node born into the future has no screen: its staging is
|
|
1199
|
-
* its only value (A29).
|
|
1200
|
-
|
|
1316
|
+
* its only value (A29). Nor has a reader born into the flush's transaction
|
|
1317
|
+
* (`bornInto`, #4010): its branch exists only there. */ function stagedScreen(e) {
|
|
1318
|
+
if (flushTransaction === null || bornInto(e, flushTransaction)) return false;
|
|
1201
1319
|
staleReader(e, flushTransaction);
|
|
1202
1320
|
return true;
|
|
1203
1321
|
}
|
|
@@ -1205,40 +1323,77 @@ function read(e) {
|
|
|
1205
1323
|
/** A28 (4) — written inside a recompute that runs OUTSIDE a flush (a
|
|
1206
1324
|
* creation-time compute): promoted at that pass's end, visible to the rest of
|
|
1207
1325
|
* the block. Cold: only contextual writes reach here. */ function notePromotedWrite(e) {
|
|
1208
|
-
if (globalQueue.
|
|
1209
|
-
e.
|
|
1326
|
+
if (globalQueue.q || e.l & CONFIG_PROMOTED) return;
|
|
1327
|
+
e.l |= CONFIG_PROMOTED;
|
|
1210
1328
|
promotedWrites.push(e);
|
|
1211
1329
|
}
|
|
1212
1330
|
|
|
1213
|
-
/** Cold half of setSignal's snapshot arm (see there)
|
|
1214
|
-
*
|
|
1215
|
-
*
|
|
1331
|
+
/** Cold half of setSignal's snapshot arm (see there), shared with a store
|
|
1332
|
+
* container's structural notify: first write during capture to a plain
|
|
1333
|
+
* signal without a live snapshot records the pre-write value. Only plain
|
|
1334
|
+
* user signals qualify. Computeds reach setSignal from an
|
|
1216
1335
|
* async landing (asyncWrite), and a value arriving from async during the pass
|
|
1217
1336
|
* REVEALS — the creation-time arm skips pending computeds for the same
|
|
1218
1337
|
* reason. Firewall leaves belong to a projection whose compute is the tree's
|
|
1219
1338
|
* own work, captured (or deliberately not) at creation. */ function captureWriteSnapshot(e, t) {
|
|
1220
|
-
if (e.
|
|
1221
|
-
ext(e).
|
|
1222
|
-
e.
|
|
1339
|
+
if (e.l & CONFIG_NO_SNAPSHOT || e.X !== undefined || e.T?.Pe !== undefined) return;
|
|
1340
|
+
ext(e).Pe = t === undefined ? NO_SNAPSHOT : t;
|
|
1341
|
+
e.l |= CONFIG_HAS_SNAPSHOT;
|
|
1223
1342
|
snapshotSources.add(e);
|
|
1224
1343
|
}
|
|
1225
1344
|
|
|
1345
|
+
/**
|
|
1346
|
+
* A callback run before every signal or store write lands, while set, with
|
|
1347
|
+
* the written signal or the store proxy whose setter runs. The
|
|
1348
|
+
* server-components client sets it while slot occurrences are left
|
|
1349
|
+
* unmounted after hydration (deferred hydration): a write may be one an
|
|
1350
|
+
* unmounted component would have observed, so the client mounts what is
|
|
1351
|
+
* left before the write applies, and clears the hook. `null` otherwise.
|
|
1352
|
+
*/ let writeHook = null;
|
|
1353
|
+
|
|
1354
|
+
/** @internal */ function setWriteHook(e) {
|
|
1355
|
+
writeHook = e;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
/**
|
|
1359
|
+
* `el`'s comparator verdict (`_equals`), the one guarded call for recompute,
|
|
1360
|
+
* setSignal (user setters and async landings) and the seam's
|
|
1361
|
+
* staged-equals-committed check.
|
|
1362
|
+
* - A comparator that read an unresolved async source has not shown the
|
|
1363
|
+
* values equal (#4111): falsy, a change, whose readers suspend in their own
|
|
1364
|
+
* tracked context.
|
|
1365
|
+
* - Any other throw is an error of `el`'s computation (A3, #2837): routed
|
|
1366
|
+
* through the status path so error boundaries contain it, instead of
|
|
1367
|
+
* unwinding a flush or a setter; answered "same" so nothing commits.
|
|
1368
|
+
* @internal
|
|
1369
|
+
*/ function isSame(e, t, n) {
|
|
1370
|
+
if (e.xe === isEqual) return t === n;
|
|
1371
|
+
try {
|
|
1372
|
+
return e.xe && e.xe(t, n);
|
|
1373
|
+
} catch (t) {
|
|
1374
|
+
if (!(t instanceof NotReadyError)) return notifyStatus(e, STATUS_ERROR, t), true;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1226
1378
|
function setSignal(e, t) {
|
|
1227
|
-
|
|
1379
|
+
if (writeHook !== null) writeHook(e);
|
|
1380
|
+
const n = e.se === NOT_PENDING ? e.Ie : e.se;
|
|
1228
1381
|
if (typeof t === "function") t = t(n);
|
|
1229
1382
|
// Lanes: a write landing on a guess is its truth (A18 — an async guess's
|
|
1230
1383
|
// own landing), judged against the guess; one landing on a lane's
|
|
1231
1384
|
// derivation (its flight) is the lane's staging (lanes.ts).
|
|
1232
|
-
if (e.
|
|
1385
|
+
if (laneWriteHook && e.l & CONFIG_OVERRIDE) return laneWriteHook(e, t);
|
|
1233
1386
|
// L2 (T4) / A34 (1): a write to a node whose staged state is held is a
|
|
1234
1387
|
// second proposal for the slot — the same value again or another — and
|
|
1235
1388
|
// the two cannot finish at different times: the writer's tick joins the
|
|
1236
1389
|
// hold, before the equality gate.
|
|
1237
|
-
if (e.
|
|
1390
|
+
if (e.l & CONFIG_HELD) joinFuture(txOf(e));
|
|
1238
1391
|
// Uninitialized check first: the first commit has no previous value, so the
|
|
1239
1392
|
// user comparator must not run against `undefined` (matches recompute).
|
|
1240
|
-
|
|
1241
|
-
|
|
1393
|
+
// D-41: a write over a shown error is a change whatever the last good
|
|
1394
|
+
// value was — the error leaves at its commit (`commitPendingNode`).
|
|
1395
|
+
const o = !!(e.m & STATUS_UNINITIALIZED) || !!e.T?.de || !isSame(e, n, t);
|
|
1396
|
+
if (!o) return t;
|
|
1242
1397
|
// A write during hydration's snapshot capture to a source that has no
|
|
1243
1398
|
// snapshot — created BEFORE capture began (module-level state: an identity
|
|
1244
1399
|
// minted from onSettled in the pass, a preference read from storage) —
|
|
@@ -1249,21 +1404,21 @@ function setSignal(e, t) {
|
|
|
1249
1404
|
// pass reads a value the server never had. Store leaves written here are
|
|
1250
1405
|
// plain signals and qualify the same way.
|
|
1251
1406
|
if (snapshotCaptureActive) captureWriteSnapshot(e, n);
|
|
1252
|
-
const
|
|
1253
|
-
if (!
|
|
1407
|
+
const E = e.se !== NOT_PENDING;
|
|
1408
|
+
if (!E) queuePendingNode(e);
|
|
1254
1409
|
// A28: a held node rewritten outside a flush keeps its flushed staging
|
|
1255
1410
|
// for every reader until the flush that carries the rewrite.
|
|
1256
|
-
else if (e.
|
|
1257
|
-
e.
|
|
1411
|
+
else if (e.l & CONFIG_HELD && !globalQueue.q) stashFlushed(e);
|
|
1412
|
+
e.se = t;
|
|
1258
1413
|
if (context !== null) notePromotedWrite(e);
|
|
1259
1414
|
// _time is a computed-only slot (§12e): writing it on a signal would fork
|
|
1260
1415
|
// the lean shape. Every read site is computed-typed.
|
|
1261
|
-
if (e.
|
|
1416
|
+
if (e.X !== undefined) e.ie = clock;
|
|
1262
1417
|
// Staged-rewrite fast path (§12d): a re-write to a node whose subscribers
|
|
1263
1418
|
// were already walked — and where nothing has recomputed or linked since
|
|
1264
1419
|
// (epoch) — re-stages the value and stops. The walk is idempotent (subs
|
|
1265
1420
|
// marked, heap entries flag-guarded, effects queued once).
|
|
1266
|
-
if (
|
|
1421
|
+
if (E && e.Me === notifyEpoch) return t;
|
|
1267
1422
|
insertSubs(e);
|
|
1268
1423
|
schedule();
|
|
1269
1424
|
return t;
|
|
@@ -1288,18 +1443,31 @@ function setSignal(e, t) {
|
|
|
1288
1443
|
// of replacing it. A write made under the hold carries the mark and keeps
|
|
1289
1444
|
// (1)'s last-write-wins. Only the user setter asks; an async landing
|
|
1290
1445
|
// writing a held node is the transaction's own work.
|
|
1291
|
-
const n = e.
|
|
1446
|
+
const n = e.l & CONFIG_HELD && !(e.R & REACTIVE_MANUAL_WRITE) && txOf(e) !== flushTransaction;
|
|
1292
1447
|
// The writer read the committed frame; its updater composes on it.
|
|
1293
|
-
if (n && typeof t === "function")
|
|
1294
|
-
|
|
1448
|
+
if (n && typeof t === "function") {
|
|
1449
|
+
const n = t(e.Ie);
|
|
1450
|
+
t = () => n;
|
|
1451
|
+
}
|
|
1452
|
+
// #4110: the write replaces a failed derivation's outcome. It lands without
|
|
1453
|
+
// re-running `_fn`, and nothing else clears STATUS_ERROR, so without this
|
|
1454
|
+
// every later read keeps throwing the error the derivation left behind.
|
|
1455
|
+
// The error stays the display until the write commits (D-41).
|
|
1456
|
+
if (e.m & STATUS_ERROR) {
|
|
1457
|
+
e.T.de = e.T.F;
|
|
1458
|
+
notifyStatus(e, STATUS_NONE, undefined);
|
|
1459
|
+
}
|
|
1460
|
+
t = setSignal(e, t);
|
|
1461
|
+
// A write replaces an error a pass staged (D-41): it is a value again.
|
|
1462
|
+
e.l &= ~CONFIG_STAGED_ERROR;
|
|
1295
1463
|
if (n) {
|
|
1296
1464
|
// The one write that re-runs: the staging it replaced was derived from
|
|
1297
1465
|
// inputs the writer never saw, so the hold re-derives over the write.
|
|
1298
1466
|
e.R = e.R & ~REACTIVE_CHECK | REACTIVE_DIRTY;
|
|
1299
1467
|
insertIntoHeap(e, dirtyQueue);
|
|
1300
1468
|
schedule();
|
|
1301
|
-
} else if (e.
|
|
1302
|
-
return
|
|
1469
|
+
} else if (e.se !== NOT_PENDING) e.R |= REACTIVE_MANUAL_WRITE;
|
|
1470
|
+
return t;
|
|
1303
1471
|
}
|
|
1304
1472
|
|
|
1305
1473
|
/**
|
|
@@ -1322,14 +1490,14 @@ function setSignal(e, t) {
|
|
|
1322
1490
|
* ```
|
|
1323
1491
|
*/ function runWithOwner(e, t) {
|
|
1324
1492
|
const n = context;
|
|
1325
|
-
const
|
|
1493
|
+
const o = tracking;
|
|
1326
1494
|
context = e;
|
|
1327
1495
|
tracking = false;
|
|
1328
1496
|
try {
|
|
1329
1497
|
return t();
|
|
1330
1498
|
} finally {
|
|
1331
1499
|
context = n;
|
|
1332
|
-
tracking =
|
|
1500
|
+
tracking = o;
|
|
1333
1501
|
}
|
|
1334
1502
|
}
|
|
1335
1503
|
|
|
@@ -1346,7 +1514,7 @@ function staleValues(e, t = true) {
|
|
|
1346
1514
|
* recompute as a quiet re-ask and schedules it; no-ops for non-derived or
|
|
1347
1515
|
* disposed targets.
|
|
1348
1516
|
*/ function markRefresh(e) {
|
|
1349
|
-
if (typeof e.
|
|
1517
|
+
if (typeof e.X === "function" && !(e.R & REACTIVE_DISPOSED)) {
|
|
1350
1518
|
// A manual write never refuses the re-run (A34 rule B): a refresh in the
|
|
1351
1519
|
// write's own frame re-asks with the write as `prev`, like any source
|
|
1352
1520
|
// change (#3026 generalized).
|
|
@@ -1359,4 +1527,4 @@ function staleValues(e, t = true) {
|
|
|
1359
1527
|
}
|
|
1360
1528
|
}
|
|
1361
1529
|
|
|
1362
|
-
export { clearSnapshots, computed, context, createEffectNode, effectStatusNotify, ext, isEqual, markLateLinker, markRefresh, markSnapshotScope, markUnflushedStaged, notePromotedWrite, prepareComputed, pullComputed, read, readerSeesCommitted, recompute, releaseSnapshotScope, resyncUnflushedCompanions, runWithOwner, serve, setEffectStatusNotify, setMemo, setSignal, setSnapshotCapture, setVerdict, signal, slotSignal, snapshotCaptureActive, snapshotSources, spectating, stagedRead, stagedScreen, staleValues, stashFlushed, statusNotifierOf, tracking, unflushedStaged, unflushedValue, untrack, verdict };
|
|
1530
|
+
export { bornInto, captureWriteSnapshot, clearSnapshots, computed, context, createEffectNode, effectStatusNotify, ext, isEqual, isSame, joinPass, markLateLinker, markRefresh, markSnapshotScope, markUnflushedStaged, notePromotedWrite, prepareComputed, pullComputed, read, readerSeesCommitted, recompute, releaseSnapshotScope, resyncUnflushedCompanions, runWithOwner, serve, setEffectStatusNotify, setMemo, setSignal, setSnapshotCapture, setVerdict, setWriteHook, signal, slotSignal, snapshotCaptureActive, snapshotSources, spectating, stagedRead, stagedScreen, staleValues, stashFlushed, statusNotifierOf, tracking, unflushedStaged, unflushedValue, untrack, verdict, writeHook };
|