@solidjs/signals 2.0.0-rc.2 → 2.0.0-rc.20
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 +6505 -0
- package/dist/dev.attribution.js +3484 -0
- package/dist/dev.js +10227 -9508
- package/dist/observe/affects-marks.js +86 -0
- package/dist/observe/affects.js +112 -0
- package/dist/observe/attribution.js +7 -0
- package/dist/observe/boundaries.js +696 -0
- package/dist/observe/core/action.js +182 -0
- package/dist/observe/core/async.js +824 -0
- package/dist/observe/core/attribution-costs.js +66 -0
- package/dist/observe/core/attribution-feedback.js +280 -0
- package/dist/observe/core/attribution-hooks.js +83 -0
- package/dist/observe/core/attribution-queries.js +43 -0
- package/dist/observe/core/attribution.js +3079 -0
- package/dist/observe/core/constants.js +269 -0
- package/dist/observe/core/context.js +63 -0
- package/dist/observe/core/copies.js +10 -0
- package/dist/observe/core/core.js +1707 -0
- package/dist/observe/core/dev.js +394 -0
- package/dist/observe/core/effect.js +193 -0
- package/dist/observe/core/error-hooks.js +71 -0
- package/dist/observe/core/error.js +82 -0
- package/dist/observe/core/external.js +97 -0
- package/dist/observe/core/graph.js +162 -0
- package/dist/observe/core/heap.js +124 -0
- package/dist/observe/core/identities.js +62 -0
- package/dist/observe/core/invariants.js +12 -0
- package/dist/observe/core/lanes.js +972 -0
- package/dist/observe/core/owner.js +371 -0
- package/dist/observe/core/scheduler.js +1157 -0
- package/dist/observe/core/truth.js +47 -0
- package/dist/observe/core/verdict.js +470 -0
- package/dist/observe/deferred.js +186 -0
- package/dist/observe/flatten.js +79 -0
- package/dist/observe/from.js +248 -0
- package/dist/observe/index.js +71 -0
- package/dist/observe/map.js +593 -0
- package/dist/observe/reveal.js +212 -0
- package/dist/observe/signals.js +717 -0
- package/dist/observe/store/affects.js +142 -0
- package/dist/observe/store/edges.js +39 -0
- package/dist/observe/store/hooks.js +16 -0
- package/dist/observe/store/index.js +47 -0
- package/dist/observe/store/optimistic.js +713 -0
- package/dist/observe/store/projection.js +345 -0
- package/dist/observe/store/reconcile.js +281 -0
- package/dist/observe/store/store.js +2127 -0
- package/dist/observe/store/storePath.js +103 -0
- package/dist/observe/store/target.js +46 -0
- package/dist/observe/store/types.js +83 -0
- package/dist/observe/store/utils.js +1063 -0
- package/dist/prod/affects-marks.js +86 -0
- package/dist/prod/affects.js +98 -112
- package/dist/prod/attribution.js +45 -0
- package/dist/prod/boundaries.js +578 -468
- package/dist/prod/core/action.js +92 -59
- package/dist/prod/core/async.js +522 -336
- package/dist/prod/core/attribution-hooks.js +3 -0
- package/dist/prod/core/constants.js +168 -44
- package/dist/prod/core/context.js +12 -16
- package/dist/prod/core/copies.js +10 -0
- package/dist/prod/core/core.js +1345 -627
- package/dist/prod/core/dev.js +123 -1
- package/dist/prod/core/effect.js +113 -80
- package/dist/prod/core/error-hooks.js +71 -0
- package/dist/prod/core/error.js +18 -4
- package/dist/prod/core/external.js +6 -7
- package/dist/prod/core/graph.js +119 -72
- package/dist/prod/core/heap.js +85 -109
- package/dist/prod/core/identities.js +62 -0
- package/dist/prod/core/invariants.js +1 -32
- package/dist/prod/core/lanes.js +931 -132
- package/dist/prod/core/owner.js +192 -129
- package/dist/prod/core/scheduler.js +948 -642
- package/dist/prod/core/truth.js +47 -0
- package/dist/prod/core/verdict.js +409 -420
- package/dist/prod/deferred.js +184 -0
- package/dist/prod/flatten.js +77 -0
- package/dist/prod/from.js +248 -0
- package/dist/prod/index.js +40 -16
- package/dist/prod/map.js +481 -211
- package/dist/prod/reveal.js +212 -0
- package/dist/prod/signals.js +401 -84
- package/dist/prod/store/affects.js +142 -0
- package/dist/prod/store/edges.js +39 -0
- package/dist/prod/store/hooks.js +16 -0
- package/dist/prod/store/index.js +27 -18
- package/dist/prod/store/optimistic.js +707 -0
- package/dist/prod/store/projection.js +331 -0
- package/dist/prod/store/reconcile.js +281 -0
- package/dist/prod/store/store.js +1952 -267
- package/dist/prod/store/storePath.js +1 -1
- package/dist/prod/store/target.js +43 -0
- package/dist/prod/store/types.js +83 -0
- package/dist/prod/store/utils.js +997 -138
- package/dist/types/affects-marks.d.ts +14 -0
- package/dist/types/affects.d.ts +12 -30
- package/dist/types/attribution.d.ts +19 -0
- package/dist/types/attribution.prod.d.ts +22 -0
- package/dist/types/boundaries.d.ts +99 -124
- package/dist/types/carved.d.ts +1 -0
- package/dist/types/core/action.d.ts +23 -9
- package/dist/types/core/async.d.ts +34 -6
- package/dist/types/core/attribution-costs.d.ts +35 -0
- package/dist/types/core/attribution-feedback.d.ts +133 -0
- package/dist/types/core/attribution-hooks.d.ts +307 -8
- package/dist/types/core/attribution-queries.d.ts +24 -0
- package/dist/types/core/attribution.d.ts +802 -72
- package/dist/types/core/constants.d.ts +195 -38
- package/dist/types/core/copies.d.ts +54 -0
- package/dist/types/core/core.d.ts +295 -65
- package/dist/types/core/dev.d.ts +509 -22
- package/dist/types/core/effect.d.ts +1 -1
- package/dist/types/core/error-hooks.d.ts +71 -0
- package/dist/types/core/error.d.ts +18 -1
- package/dist/types/core/graph.d.ts +40 -1
- package/dist/types/core/heap.d.ts +5 -5
- package/dist/types/core/identities.d.ts +17 -0
- package/dist/types/core/index.d.ts +11 -7
- package/dist/types/core/invariants.d.ts +1 -26
- package/dist/types/core/lanes.d.ts +74 -43
- package/dist/types/core/owner.d.ts +27 -9
- package/dist/types/core/scheduler.d.ts +353 -102
- package/dist/types/core/truth.d.ts +18 -0
- package/dist/types/core/types.d.ts +97 -71
- package/dist/types/core/verdict.d.ts +35 -0
- package/dist/types/deferred.d.ts +28 -0
- package/dist/types/flatten.d.ts +8 -0
- package/dist/types/from.d.ts +47 -0
- package/dist/types/index.d.ts +33 -7
- package/dist/types/map.d.ts +4 -2
- package/dist/types/reveal.d.ts +19 -0
- package/dist/types/signals.d.ts +196 -45
- package/dist/types/store/affects.d.ts +12 -0
- package/dist/types/store/edges.d.ts +9 -0
- package/dist/types/store/hooks.d.ts +62 -0
- package/dist/types/store/index.d.ts +42 -15
- package/dist/types/store/optimistic.d.ts +24 -45
- package/dist/types/store/projection.d.ts +17 -68
- package/dist/types/store/reconcile.d.ts +3 -46
- package/dist/types/store/store.d.ts +133 -136
- package/dist/types/store/storePath.d.ts +1 -1
- package/dist/types/store/target.d.ts +166 -0
- package/dist/types/store/types.d.ts +105 -0
- package/dist/types/store/utils.d.ts +188 -0
- package/package.json +31 -26
- package/dist/node.cjs +0 -8745
- package/dist/prod/core/optimistic.js +0 -265
- package/dist/prod/store/next/optimistic.js +0 -385
- package/dist/prod/store/next/projection.js +0 -172
- package/dist/prod/store/next/reconcile.js +0 -331
- package/dist/prod/store/next/store.js +0 -1499
- package/dist/prod/store/next/target.js +0 -20
- package/dist/types/core/optimistic.d.ts +0 -6
- package/dist/types/store/next/optimistic.d.ts +0 -20
- package/dist/types/store/next/projection.d.ts +0 -8
- package/dist/types/store/next/reconcile.d.ts +0 -3
- package/dist/types/store/next/store.d.ts +0 -77
- package/dist/types/store/next/target.d.ts +0 -117
- package/dist/types-cjs/affects.d.cts +0 -47
- package/dist/types-cjs/boundaries.d.cts +0 -175
- package/dist/types-cjs/core/action.d.cts +0 -64
- package/dist/types-cjs/core/async.d.cts +0 -21
- package/dist/types-cjs/core/attribution-hooks.d.cts +0 -52
- package/dist/types-cjs/core/attribution.d.cts +0 -186
- package/dist/types-cjs/core/constants.d.cts +0 -97
- package/dist/types-cjs/core/context.d.cts +0 -36
- package/dist/types-cjs/core/core.d.cts +0 -163
- package/dist/types-cjs/core/dev.d.cts +0 -81
- package/dist/types-cjs/core/effect.d.cts +0 -30
- package/dist/types-cjs/core/error.d.cts +0 -47
- package/dist/types-cjs/core/external.d.cts +0 -15
- package/dist/types-cjs/core/graph.d.cts +0 -6
- package/dist/types-cjs/core/heap.d.cts +0 -22
- package/dist/types-cjs/core/index.d.cts +0 -13
- package/dist/types-cjs/core/invariants.d.cts +0 -59
- package/dist/types-cjs/core/lanes.d.cts +0 -44
- package/dist/types-cjs/core/optimistic.d.cts +0 -6
- package/dist/types-cjs/core/owner.d.cts +0 -124
- package/dist/types-cjs/core/scheduler.d.cts +0 -180
- package/dist/types-cjs/core/types.d.cts +0 -192
- package/dist/types-cjs/core/verdict.d.cts +0 -2
- package/dist/types-cjs/index.d.cts +0 -10
- package/dist/types-cjs/map.d.cts +0 -69
- package/dist/types-cjs/package.json +0 -3
- package/dist/types-cjs/signals.d.cts +0 -624
- package/dist/types-cjs/store/index.d.cts +0 -19
- package/dist/types-cjs/store/next/optimistic.d.cts +0 -20
- package/dist/types-cjs/store/next/projection.d.cts +0 -8
- package/dist/types-cjs/store/next/reconcile.d.cts +0 -3
- package/dist/types-cjs/store/next/store.d.cts +0 -77
- package/dist/types-cjs/store/next/target.d.cts +0 -117
- package/dist/types-cjs/store/optimistic.d.cts +0 -45
- package/dist/types-cjs/store/projection.d.cts +0 -70
- package/dist/types-cjs/store/reconcile.d.cts +0 -46
- package/dist/types-cjs/store/store.d.cts +0 -138
- package/dist/types-cjs/store/storePath.d.cts +0 -58
- package/dist/types-cjs/store/utils.d.cts +0 -74
|
@@ -0,0 +1,1707 @@
|
|
|
1
|
+
import { notifyStatus, releaseFlightTeardown, handleAsync, clearStatus, parkLoadingWindow, stageError, settlePendingSource, releaseIfSettledUnobserved, settleErroredDependents } from "./async.js";
|
|
2
|
+
|
|
3
|
+
import { STATUS_UNINITIALIZED, REACTIVE_LAZY, REACTIVE_NONE, defaultContext, CONFIG_TRANSPARENT, CONFIG_OWNED_WRITE, CONFIG_AUTO_DISPOSE, CONFIG_SYNC, CONFIG_NO_SNAPSHOT, CONFIG_PLUMBING, CONFIG_IN_SNAPSHOT_SCOPE, NOT_PENDING, CONFIG_HELD, REACTIVE_MANUAL_WRITE, STATUS_ERROR, REACTIVE_DIRTY, CONFIG_OVERRIDE, NO_SNAPSHOT, CONFIG_HAS_SNAPSHOT, REACTIVE_DISPOSED, REACTIVE_CHECK, REACTIVE_IN_HEAP, REACTIVE_REASK, REACTIVE_SNAPSHOT_STALE, CONFIG_CHILDREN_FORBIDDEN, CONFIG_VERDICT, STATUS_PENDING, CONFIG_STAGED_ERROR, REACTIVE_LANE_DIRTY, CONFIG_GUESS, REACTIVE_RECOMPUTING_DEPS, CONFIG_ASYNC, CONFIG_LAGS, EFFECT_RENDER, CONFIG_HELD_FRAME, EFFECT_TRACKED, CONFIG_STAGED, 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, NewSet, STATUS_NONE } from "./constants.js";
|
|
4
|
+
|
|
5
|
+
import { NotReadyError } from "./error.js";
|
|
6
|
+
|
|
7
|
+
import { link, dormantNodes, clearDeps, trimStaleDeps, deferReleases, deferredReleases } from "./graph.js";
|
|
8
|
+
|
|
9
|
+
import { insertIntoHeap, deleteFromHeap, insertIntoHeapHeight, enqueueSub, markNode, markHeap } from "./heap.js";
|
|
10
|
+
|
|
11
|
+
import { GRAPH_SIZE_WARN_AT, noteFanIn } from "./dev.js";
|
|
12
|
+
|
|
13
|
+
import { attrHooks } from "./attribution-hooks.js";
|
|
14
|
+
|
|
15
|
+
import { inheritId, linkChild, disposeChildren, markDisposal } from "./owner.js";
|
|
16
|
+
|
|
17
|
+
import { schedule, externalUntrackHook, clock, txOf, flushTransaction, laneWriteHook, joinFuture, queuePendingNode, globalQueue, notifyEpoch, insertSubs, dirtyQueue, observeFlightHook, laneStageHook, passLane, laneReadHook, wireExternalSourceHook, bumpNotifyEpoch, setPassLane, joinPassTx, freshHook, guessing, holdWorkHook, GlobalQueue, laneOutcomeHook, passTx, holdNode, laneDirty, heldTrim, staleReader, laneServedHook, latestOpened, stagedReaders, laneSeamsHook, deferZombie } from "./scheduler.js";
|
|
18
|
+
|
|
19
|
+
// The heap's per-node step. A tracked effect's heap visit is its compute
|
|
20
|
+
// phase — empty, like a user effect whose compute reads nothing — and hands
|
|
21
|
+
// the callback to the user queue. Routing the wake through the heap, rather
|
|
22
|
+
// than straight into the queue at notify time, is what orders the run after
|
|
23
|
+
// the commit regardless of which phase the write came from: a write in a
|
|
24
|
+
// render-effect callback stages its value for the next pass, but a wake pushed
|
|
25
|
+
// directly into the user queue ran in the SAME pass, read the old value, and
|
|
26
|
+
// nothing re-notified it when the value landed (#3291).
|
|
27
|
+
GlobalQueue.Ze = e => {
|
|
28
|
+
if (e.i === EFFECT_TRACKED) {
|
|
29
|
+
deleteFromHeap(e, dirtyQueue);
|
|
30
|
+
e.Qe = true;
|
|
31
|
+
globalQueue.enqueue(EFFECT_USER, e.Z);
|
|
32
|
+
}
|
|
33
|
+
// L2: a zombie's fate is the seam's — its owner's commit disposes it, or
|
|
34
|
+
// the park cancels the pass (the write that dirtied it is held). It runs
|
|
35
|
+
// only past a seam that left it alive (a live write to a frame whose
|
|
36
|
+
// owner is held in the future). Its lane's own re-staging is a live write
|
|
37
|
+
// (REACTIVE_LANE_DIRTY — a lane shows ahead of any park): it runs now, as
|
|
38
|
+
// the lane's work, for the reveal this seam (#3463).
|
|
39
|
+
else if (e.R & REACTIVE_ZOMBIE && !(e.R & REACTIVE_LANE_DIRTY)) deferZombie(e);
|
|
40
|
+
// A lane frame's member (parked by a lane pass, #3698) follows mainline
|
|
41
|
+
// writes only: a lane's dirty on it is cancelled — the lane's apply retires
|
|
42
|
+
// it, and the pass that replaced it already decided what that screen shows
|
|
43
|
+
// (#4137: a `<Show>` branch withdrawn by its own lane re-ran on a falsy
|
|
44
|
+
// condition).
|
|
45
|
+
// (Lanes exist only with the lane engine installed: folded away without.)
|
|
46
|
+
else if (laneStageHook && e.R & REACTIVE_ZOMBIE && laneFrameMember(e)) {
|
|
47
|
+
deleteFromHeap(e, dirtyQueue);
|
|
48
|
+
e.R &= -65540;
|
|
49
|
+
} else recompute(e);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** The zombie's frame was parked by lane work: the nearest non-zombie owner
|
|
53
|
+
* (the one whose pass parked it) carries a lane's derived value — or, an
|
|
54
|
+
* effect (no lane value: `laneStage`), is listed in a lane (an `insert()`
|
|
55
|
+
* hole's render effect). */ function laneFrameMember(e) {
|
|
56
|
+
let t = e._parent;
|
|
57
|
+
while (t.R & REACTIVE_ZOMBIE) t = t._parent;
|
|
58
|
+
return (t.l & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE || t.i && t.T?.Je?.V;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
GlobalQueue.et = disposeChildren;
|
|
62
|
+
|
|
63
|
+
let tracking = false;
|
|
64
|
+
|
|
65
|
+
let context = null;
|
|
66
|
+
|
|
67
|
+
let snapshotCaptureActive = false;
|
|
68
|
+
|
|
69
|
+
/** The lane at the root of the running chain of first passes: the lane whose
|
|
70
|
+
* pass created the chain's first node (a mount), inherited by every first
|
|
71
|
+
* pass nested under it, whose seats are mainline (`recompute`, #4166). */ let laneRoot;
|
|
72
|
+
|
|
73
|
+
let snapshotSources = null;
|
|
74
|
+
|
|
75
|
+
function ownerInSnapshotScope(e) {
|
|
76
|
+
while (e) {
|
|
77
|
+
if (e.ot) return true;
|
|
78
|
+
e = e._parent;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function setSnapshotCapture(e) {
|
|
84
|
+
snapshotCaptureActive = e;
|
|
85
|
+
if (e && !snapshotSources) snapshotSources = new NewSet;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Marks `owner` a snapshot scope: while capture is on, the readers created
|
|
90
|
+
* under it serve each source's snapshot (the value it had when hydration
|
|
91
|
+
* began) and replay at `releaseSnapshotScope`.
|
|
92
|
+
*
|
|
93
|
+
* With `served` (hydration), `owner` is instead a hydrated computed and
|
|
94
|
+
* `served` its server answer — what the server rendered every reader of the
|
|
95
|
+
* node with — landing after the node was created pending (so the creation
|
|
96
|
+
* arm skipped it). It becomes the node's snapshot when it has none, whether
|
|
97
|
+
* the node committed the answer or a client write had already taken its
|
|
98
|
+
* compute over. A claim made later reads the value its markup shows, and
|
|
99
|
+
* replays the node's own value at its release, as for a written signal.
|
|
100
|
+
*/ function markSnapshotScope(e, t) {
|
|
101
|
+
if (arguments.length < 2) e.ot = true; else if (snapshotCaptureActive && e.T?.st === undefined) takeSnapshot(e, t);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function releaseSnapshotScope(e) {
|
|
105
|
+
e.ot = false;
|
|
106
|
+
releaseSubtree(e);
|
|
107
|
+
schedule();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function releaseSubtree(e) {
|
|
111
|
+
let t = e.ze;
|
|
112
|
+
while (t) {
|
|
113
|
+
if (t.ot) {
|
|
114
|
+
t = t.Xe;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (t.X) {
|
|
118
|
+
const e = t;
|
|
119
|
+
e.l &= ~CONFIG_IN_SNAPSHOT_SCOPE;
|
|
120
|
+
if (e.R & REACTIVE_SNAPSHOT_STALE) {
|
|
121
|
+
e.R &= ~REACTIVE_SNAPSHOT_STALE;
|
|
122
|
+
e.R |= REACTIVE_DIRTY;
|
|
123
|
+
if (dirtyQueue.Et > e.lt) dirtyQueue.Et = e.lt;
|
|
124
|
+
insertIntoHeap(e, dirtyQueue);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
releaseSubtree(t);
|
|
128
|
+
t = t.Xe;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function clearSnapshots() {
|
|
133
|
+
if (snapshotSources) {
|
|
134
|
+
for (const e of snapshotSources) {
|
|
135
|
+
// The extension is a fixed-shape object with `_snapshotValue`
|
|
136
|
+
// pre-initialized to undefined (see ext()), and every reader tests
|
|
137
|
+
// `!== undefined` — assign, don't `delete`: deleting a field pushes the
|
|
138
|
+
// object to dictionary mode for every later read of every field.
|
|
139
|
+
const t = e.T;
|
|
140
|
+
if (t != null) t.st = undefined;
|
|
141
|
+
}
|
|
142
|
+
snapshotSources = null;
|
|
143
|
+
}
|
|
144
|
+
snapshotCaptureActive = false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function recompute(e, t = false) {
|
|
148
|
+
// §12d: any recompute can clean a marked subscriber — invalidate skips.
|
|
149
|
+
bumpNotifyEpoch();
|
|
150
|
+
const n = e.i;
|
|
151
|
+
let o;
|
|
152
|
+
// Lanes (plan sec. 28): the seat of a pass is its node's. A node carrying a lane's
|
|
153
|
+
// derived value runs as the lane's whoever dirtied it — a sync write, a
|
|
154
|
+
// boundary reset, a frame rerun — so its children and its result are the
|
|
155
|
+
// lane's; so does a member the lane's own re-staging dirtied
|
|
156
|
+
// (REACTIVE_LANE_DIRTY, lanes.ts — a leaf the lane owns). A written guess's
|
|
157
|
+
// own pass is its truth arriving (A18), the frame's. A first pass is its
|
|
158
|
+
// creator's (ruling A: a lane pass's children are the lane's frame), and
|
|
159
|
+
// under a guess's lane it reads as the lane's too — a binding the lane
|
|
160
|
+
// mounts sees the screen, like the pass that mounted it (#3835). Not
|
|
161
|
+
// under a verdict lane, for its reads or its result: a verdict lane holds
|
|
162
|
+
// verdicts, and a mount it makes is a mainline mount (A29's boundary
|
|
163
|
+
// exemption, #3851). A tracked read of a lane's value moves a
|
|
164
|
+
// derivation's pass into the lane (`read`); a leaf's never moves.
|
|
165
|
+
// Restored at the end, after this pass's staging and runs have been
|
|
166
|
+
// routed.
|
|
167
|
+
// Lanes exist only with the lane engine installed (`laneStageHook`): a
|
|
168
|
+
// bundle without it seats every pass on mainline (`passLane` is never set)
|
|
169
|
+
// and folds the seat, and its restores, away.
|
|
170
|
+
const s = passLane;
|
|
171
|
+
// The mount a first pass belongs to: its creator's lane, or the one its
|
|
172
|
+
// creator's own mount came from — a render effect a verdict lane's mount's
|
|
173
|
+
// content creates is that mount's too (#4166). Lanes only, like the seat.
|
|
174
|
+
const E = laneRoot;
|
|
175
|
+
const l = laneStageHook && (laneRoot = t && (s || E));
|
|
176
|
+
if (laneStageHook) setPassLane((e.R & REACTIVE_LANE_DIRTY || (e.l & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE) && e.T?.Je?.V ? e.T.Je : // Loose `!=`: false with no creator lane (undefined == null).
|
|
177
|
+
t && s?._parent.it != s && creatorPass(context)?.R & REACTIVE_RECOMPUTING_DEPS ? s : null);
|
|
178
|
+
// Attribution hook: fired before this run touches the dep list — `_deps`
|
|
179
|
+
// still holds the previous run's links (the subscriptions that could have
|
|
180
|
+
// triggered this run, and the baseline for the engine's subscription diff).
|
|
181
|
+
let i = false;
|
|
182
|
+
{
|
|
183
|
+
e.Ve = 0;
|
|
184
|
+
if (attrHooks !== null) attrHooks.recomputeStart(e, t);
|
|
185
|
+
}
|
|
186
|
+
// CARVE 2: lane posture resolution (OPTIMISTIC_DIRTY, derived-override
|
|
187
|
+
// re-derivation, lane adoption through deps) went with the optimistic engine.
|
|
188
|
+
// An error a pass staged, which this pass replaces (`valueChanged`).
|
|
189
|
+
const r = e.l & CONFIG_STAGED_ERROR;
|
|
190
|
+
if (!t) {
|
|
191
|
+
deleteFromHeap(e, dirtyQueue);
|
|
192
|
+
// A pending flight is the transition's own work (#3884), and so is a
|
|
193
|
+
// held async memo's re-run with no verdict window of its own (D-25,
|
|
194
|
+
// `holdWork`): a pass that takes no verdict role this time. A probe on
|
|
195
|
+
// an earlier pass left CONFIG_VERDICT, and a frame read here would serve
|
|
196
|
+
// the committed screen, settle the flight and drop the hold — a
|
|
197
|
+
// superseding write's verdict would depend on that probe. Cleared for
|
|
198
|
+
// this pass, before the flight it judges by is superseded; a probe in
|
|
199
|
+
// the body re-stamps it after the plain reads. Not an effect: it has no
|
|
200
|
+
// flight of its own (its pending is a source's), and a verdict memo's
|
|
201
|
+
// change may have just made it a verdict reader (`laneDirty`). Only a
|
|
202
|
+
// build with the lane engine (`laneStageHook`; verdict.ts imports it)
|
|
203
|
+
// ever sets CONFIG_VERDICT: without it the line folds away.
|
|
204
|
+
if (laneStageHook && !n && (e.m & STATUS_PENDING || (e.l & (CONFIG_ASYNC | CONFIG_HELD)) === (CONFIG_ASYNC | CONFIG_HELD))) e.l &= ~CONFIG_VERDICT;
|
|
205
|
+
if (e.T !== null) {
|
|
206
|
+
// The seam's re-derive runs with the parked flight still current; any
|
|
207
|
+
// later pass asks its own question (`_parked`; only the lane engine
|
|
208
|
+
// parks one).
|
|
209
|
+
const t = laneStageHook && e.T.rt;
|
|
210
|
+
if (t && t !== e.T.Ee) e.T.rt = undefined;
|
|
211
|
+
e.T.Ee = null;
|
|
212
|
+
// Supersede is where an iterator flight dies (#3122): close it now.
|
|
213
|
+
// Idempotent with the cleanup-channel close.
|
|
214
|
+
releaseFlightTeardown(e);
|
|
215
|
+
}
|
|
216
|
+
// L2: a pass over a held derivation joins its transaction — the
|
|
217
|
+
// propagation that reached this node reached the transaction, and this
|
|
218
|
+
// pass's answer replaces the held one there. A node held with nothing
|
|
219
|
+
// staged — pending when its flush parked, or a member of a held frame —
|
|
220
|
+
// joins all the same. Not a render effect (rule 3): it is the frame.
|
|
221
|
+
// Re-run outside its transaction's flush it publishes mainline — the
|
|
222
|
+
// value it staged there is superseded (`frameRead` remembers it for the
|
|
223
|
+
// landing). A frame born into the transaction (`bornInto`: never run,
|
|
224
|
+
// A29, or mounted by a pass of it still awaiting its commit, #4010) keeps
|
|
225
|
+
// staging: live writes keep it current, it effects at the swap (ruling
|
|
226
|
+
// A).
|
|
227
|
+
// A boundary's structure (CONFIG_FRAME) is the frame like a render
|
|
228
|
+
// effect: it publishes mainline outside its transaction's flush (#4283).
|
|
229
|
+
if (e.l & CONFIG_HELD) {
|
|
230
|
+
const t = txOf(e);
|
|
231
|
+
// Held by a blocked lane: the frame joins nothing — a lane never holds
|
|
232
|
+
// a sync write (#3460). The pass is the lane's if it reads the lane's
|
|
233
|
+
// world (`read`), and has left it otherwise.
|
|
234
|
+
// An answered deferred node's re-pass is a lagging question (D-40):
|
|
235
|
+
// it joins nothing, so the write that reached it commits mainline.
|
|
236
|
+
// `EFFECT_RENDER << 23` is CONFIG_FRAME (a render effect, a boundary's
|
|
237
|
+
// tree and output), spelled from the effect type as at creation.
|
|
238
|
+
if (t.V || e.l & CONFIG_LAGS) ; else if (!(e.l & (CONFIG_VERDICT | EFFECT_RENDER << 23))) joinPassTx(t, freshHook?.(e, e.m & STATUS_UNINITIALIZED)); else if (t !== flushTransaction && !(e.m & STATUS_UNINITIALIZED || e.l & CONFIG_HELD_FRAME)) {
|
|
239
|
+
// Published mainline, it is not held: the frame this pass builds is
|
|
240
|
+
// replaced on the spot (#3404) — this flush's commit retires the
|
|
241
|
+
// committed frame the held pass had parked (`commitPendingNode`),
|
|
242
|
+
// after the new frame's first runs, and the held pass's own
|
|
243
|
+
// children die below (`disposeChildren`). The landing re-derives a
|
|
244
|
+
// fresh held frame for the reveal (`_reruns`).
|
|
245
|
+
// Not held any more, whichever frame reader it is: its readers read
|
|
246
|
+
// the value this pass publishes — those that read the staging it
|
|
247
|
+
// supersedes re-derive, even when it publishes the committed value
|
|
248
|
+
// back (no change to notify them by): their held runs carry the
|
|
249
|
+
// superseded frame, and the landing would apply them (#4378).
|
|
250
|
+
if (e.ue !== NOT_PENDING) insertSubs(e);
|
|
251
|
+
e.ue = NOT_PENDING;
|
|
252
|
+
e.l &= ~CONFIG_HELD;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// The previous pass's frame. If that pass is still awaiting its commit
|
|
256
|
+
// (CONFIG_STAGED) its children were never shown and die on the spot — the
|
|
257
|
+
// frame parked before it stays parked. Otherwise they ARE the frame:
|
|
258
|
+
// park them until this pass commits. Tracked effects run after the seam
|
|
259
|
+
// and own nothing to park.
|
|
260
|
+
if (n === EFFECT_TRACKED || e.l & CONFIG_STAGED) {
|
|
261
|
+
// A lazy node only those children read is released when this pass
|
|
262
|
+
// ends, if the pass doesn't read it again (#4142). A cleanup or
|
|
263
|
+
// `unobserved` hook that throws here neither stops the pass nor skips
|
|
264
|
+
// that drain: its error is rethrown when the flush ends (owner.ts).
|
|
265
|
+
const t = deferredReleases;
|
|
266
|
+
deferReleases(o = []);
|
|
267
|
+
disposeChildren(e);
|
|
268
|
+
deferReleases(t);
|
|
269
|
+
} else if (e.ze !== null || e._t !== null) parkChildren(e);
|
|
270
|
+
// An error a pass staged (D-41) is replaced by this pass's answer: a
|
|
271
|
+
// value, a flight, or an error of its own (#4109, #4145).
|
|
272
|
+
if (e.l & CONFIG_STAGED_ERROR) {
|
|
273
|
+
e.ue = NOT_PENDING;
|
|
274
|
+
e.l ^= CONFIG_STAGED_ERROR;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const a = !!(e.m & STATUS_UNINITIALIZED);
|
|
278
|
+
// Capture both error and pending status before the compute clears them.
|
|
279
|
+
// A conditional can drop its pending source and recover to an unchanged
|
|
280
|
+
// value, leaving blocked dependents outside that source’s settle walk.
|
|
281
|
+
const _ = e.m & STATUS_ERROR && e.T?.U;
|
|
282
|
+
const u = (e.m & STATUS_PENDING) !== 0;
|
|
283
|
+
const I = u && e.T?.se;
|
|
284
|
+
// Pending SOURCE-hood, captured before the compute clears status: a node
|
|
285
|
+
// whose own flight parked dependents self-registers in _pendingSources
|
|
286
|
+
// (notifyStatus, isSource). If this recompute supersedes that flight and
|
|
287
|
+
// settles synchronously, those dependents settle HERE — asyncWrite's
|
|
288
|
+
// settlePendingSource walk never runs for a landing that was preempted
|
|
289
|
+
// (#3181).
|
|
290
|
+
const N = e.T?.se?.has(e);
|
|
291
|
+
// D-41: this pass's error is staged, not published (`stageError`).
|
|
292
|
+
let f = false;
|
|
293
|
+
// A19 exc. 2: a `refresh()` re-asks the question already answered — quiet
|
|
294
|
+
// for the verdict — unless the question in flight is a new one, which the
|
|
295
|
+
// re-ask does not launder (a poll during a quiet confirm stays quiet).
|
|
296
|
+
// Nor a guess's new question whose answer is still held (#4336: an
|
|
297
|
+
// action's refresh after an outside input change has not committed it).
|
|
298
|
+
const T = (e.R & REACTIVE_REASK) !== 0 && !((e.m & STATUS_PENDING || guessing && e.ue !== NOT_PENDING) && e.T !== null && !e.T.ut);
|
|
299
|
+
const S = context;
|
|
300
|
+
context = e;
|
|
301
|
+
e._ = null;
|
|
302
|
+
e.Ne++;
|
|
303
|
+
// REACTIVE_ZOMBIE is position: it says the node sits on its owner's parked
|
|
304
|
+
// frame, and the splice/reawaken paths key off it. A zombie reruns for
|
|
305
|
+
// live writes until the commit that disposes it, so every per-pass wipe
|
|
306
|
+
// — here, the finally below, updateIfNecessary — carries it (#3543).
|
|
307
|
+
e.R = REACTIVE_RECOMPUTING_DEPS | e.R & (REACTIVE_ZOMBIE | REACTIVE_REASK);
|
|
308
|
+
e.ie = clock;
|
|
309
|
+
// The pass's previous value: its staging, else a lane node's lane value
|
|
310
|
+
// when it has one (lanes.ts), else the committed one.
|
|
311
|
+
let c = e.ue !== NOT_PENDING ? e.ue : e.l & CONFIG_OVERRIDE && e.T.V !== NOT_PENDING ? e.T.V : e.le;
|
|
312
|
+
let A = e.lt;
|
|
313
|
+
let R = false;
|
|
314
|
+
// L2: did this pass read the future (REACTIVE_JOINED, set by read) — or,
|
|
315
|
+
// a lane's mount, a staging of this flush (#3851, #4166)?
|
|
316
|
+
let C = false;
|
|
317
|
+
let O = tracking;
|
|
318
|
+
tracking = true;
|
|
319
|
+
try {
|
|
320
|
+
if (!false && e.l & CONFIG_SYNC) {
|
|
321
|
+
c = e.X(c);
|
|
322
|
+
if (e.T !== null) e.T.Ee = null;
|
|
323
|
+
e.ce = false;
|
|
324
|
+
} else {
|
|
325
|
+
// A projection's body self-registers its flight through handleAsync
|
|
326
|
+
// (with the commit as setter): its undefined return is not a sync
|
|
327
|
+
// answer, and the outer handleAsync must not clobber the registration.
|
|
328
|
+
const t = e.T?.Ee;
|
|
329
|
+
const n = e.X(c);
|
|
330
|
+
const o = typeof n === "object" && n !== null;
|
|
331
|
+
const s = e.T?.Ee !== t;
|
|
332
|
+
c = s || !o ? n : handleAsync(e, n);
|
|
333
|
+
if (!s && !o) {
|
|
334
|
+
if (e.T !== null) {
|
|
335
|
+
e.T.Ee = null;
|
|
336
|
+
if (holdWorkHook) e.l &= ~CONFIG_ASYNC;
|
|
337
|
+
}
|
|
338
|
+
// A sync (non-object) return is the first real answer; async-shaped
|
|
339
|
+
// results clear inside handleAsync at their own landing points, and a
|
|
340
|
+
// self-registered flight clears when its own handleAsync lands.
|
|
341
|
+
e.ce = false;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// On a status-free node clearStatus is a guaranteed no-op: every field
|
|
345
|
+
// its body gates on is either _statusFlags or lives in the cold
|
|
346
|
+
// extension — no extension, no status to clear. (_x from an unrelated
|
|
347
|
+
// installer just makes clearStatus a cheap re-verified no-op.)
|
|
348
|
+
if (e.m !== 0 || e.T !== null) clearStatus(e, t);
|
|
349
|
+
} catch (t) {
|
|
350
|
+
const n = t instanceof NotReadyError;
|
|
351
|
+
// An error answers, and so does a loading window's unready read (D-25,
|
|
352
|
+
// verdict.ts `holdWork`): no flight is its own. A pass left pending
|
|
353
|
+
// keeps the last answer's. (Kept by the verdict engine alone.)
|
|
354
|
+
if (holdWorkHook && (!n || e.ce)) e.l &= ~CONFIG_ASYNC;
|
|
355
|
+
if (n && e.ce) {
|
|
356
|
+
// Loading window with an unready sync dependency: register for the
|
|
357
|
+
// source's settle (the settlePendingSource walk runs off
|
|
358
|
+
// _pendingSources + _blocked alone) but take NO read-visible pending
|
|
359
|
+
// status and no downstream propagation — the committed loading value
|
|
360
|
+
// keeps serving. If the
|
|
361
|
+
// node is currently errored the error stays the answer until this
|
|
362
|
+
// retry can actually run.
|
|
363
|
+
parkLoadingWindow(e, t);
|
|
364
|
+
} else {
|
|
365
|
+
// D-41: a pass of a held node, or one that read a staging of this
|
|
366
|
+
// flush, stages its error (`stageError`).
|
|
367
|
+
if (n || !(f = stageError(e, t, true))) {
|
|
368
|
+
if (n) ext(e).fe = true;
|
|
369
|
+
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t);
|
|
370
|
+
// The replacement source is fully propagated now. If no new flight
|
|
371
|
+
// re-owned self, retire the superseded flight and its dependent copies.
|
|
372
|
+
if (n && N && !e.T?.Ee) settlePendingSource(e);
|
|
373
|
+
}
|
|
374
|
+
// A re-park drops what the earlier pass carried (#3456): a source this
|
|
375
|
+
// pass no longer reaches — its branch switched, or a fresh flight
|
|
376
|
+
// replaced the inputs' pending with its own — stays copied onto
|
|
377
|
+
// dependents that reached it only through here, and its landing walk
|
|
378
|
+
// stops at this node (nothing left to retire) before it finds them. A
|
|
379
|
+
// dependent then waits forever on a flight it has no path to. The
|
|
380
|
+
// re-park twin of the unchanged-value recovery sweep below; dependents
|
|
381
|
+
// with another path keep the source (retryReaches). A staged error
|
|
382
|
+
// answered what the pass carried: the same.
|
|
383
|
+
if ((n || f) && I) for (const t of I) if (t !== e && !e.T?.se?.has(t)) settlePendingSource(e, t);
|
|
384
|
+
}
|
|
385
|
+
} finally {
|
|
386
|
+
tracking = O;
|
|
387
|
+
if (laneStageHook) laneRoot = E;
|
|
388
|
+
// Consume the missed-wake latch (#3037, set by insertSubs): a dep write
|
|
389
|
+
// landed beneath this pass on a link it had already validated. The wipe
|
|
390
|
+
// below must not key off DIRTY/CHECK — the read-time pull protocol
|
|
391
|
+
// (markNode(c) in read()) marks the running node as part of ordinary
|
|
392
|
+
// bookkeeping, and those marks are correctly discarded here.
|
|
393
|
+
R = (e.R & REACTIVE_MISSED_WAKE) !== 0;
|
|
394
|
+
// A mount's staged read joins it — except a nested derivation left
|
|
395
|
+
// pending: no render effect reaches it yet, so it holds nothing and
|
|
396
|
+
// reaches no boundary (Reader, #4203), whatever it read — a memo the
|
|
397
|
+
// mount bore held over the staging included, at any depth (#4340). A
|
|
398
|
+
// reader that does is caught.
|
|
399
|
+
C = !!(e.R & (l ? s || n || !(e.m & STATUS_PENDING) ? REACTIVE_JOINED | REACTIVE_STAGED_READ : 0 : REACTIVE_JOINED));
|
|
400
|
+
// A verdict reader is one for as long as it probes, or reads a memo
|
|
401
|
+
// that does (`read`): a pass that did neither (REACTIVE_PROBED, the same
|
|
402
|
+
// bit) is an ordinary derivation again. Its async posture is the
|
|
403
|
+
// verdict engine's to keep (CONFIG_ASYNC, verdict.ts `holdWork`, D-25).
|
|
404
|
+
// (Only the verdict engine probes: without it the line folds away.)
|
|
405
|
+
if (observeFlightHook) e.l = e.l & ~CONFIG_VERDICT | e.R & REACTIVE_PROBED;
|
|
406
|
+
// REACTIVE_DISPOSED survives too (#3621): the pass may have disposed its
|
|
407
|
+
// own owner (a memo calling its root's `dispose()`, a cleanup doing so
|
|
408
|
+
// #3601/#3606), and `disposeChildren` set the flag on this node
|
|
409
|
+
// reentrantly. Dropped, the node read as live — `refresh()` re-ran it
|
|
410
|
+
// and `isDisposed()` lied. REACTIVE_FRAME_READ (A15 stale reader) is
|
|
411
|
+
// this pass's verdict for the landing; the next pass's wipe at the top
|
|
412
|
+
// clears it.
|
|
413
|
+
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);
|
|
414
|
+
context = S;
|
|
415
|
+
if (o) for (const e of o) releaseIfSettledUnobserved(e);
|
|
416
|
+
// A19 exc. 2: a pass that went pending on a `refresh()` re-asks the
|
|
417
|
+
// question already answered — quiet for the verdict; any other pending
|
|
418
|
+
// pass is a new question. Settled, there is nothing to classify.
|
|
419
|
+
if (e.T !== null) e.T.ut = T && (e.m & STATUS_PENDING) !== 0;
|
|
420
|
+
}
|
|
421
|
+
// A node that died during its own pass (#3621) is dead at the end of it,
|
|
422
|
+
// and the pass is void. Its owner's teardown already unlinked its deps,
|
|
423
|
+
// removed it from the heap and ran its cleanups; what remains is what the
|
|
424
|
+
// body did AFTER the `dispose()` call: reads that re-linked the dead node
|
|
425
|
+
// to its sources (unlinked here — the leak that kept it re-running in a
|
|
426
|
+
// torn-down tree), a flight it may have started (retired: the landing
|
|
427
|
+
// checks `_inFlight` identity), and the value it returned. That value is
|
|
428
|
+
// NOT published: a dead node freezes at its last committed value (#3024),
|
|
429
|
+
// so nothing is staged, committed, or propagated to subscribers, and an
|
|
430
|
+
// effect's run is not enqueued (runEffect would refuse it anyway). The
|
|
431
|
+
// attribution frame opened at the top is still closed.
|
|
432
|
+
if (e.R & REACTIVE_DISPOSED) {
|
|
433
|
+
clearDeps(e);
|
|
434
|
+
if (e.T !== null) e.T.Ee = null;
|
|
435
|
+
if (attrHooks !== null) attrHooks.recomputeEnd(e, t, false, false, false, false);
|
|
436
|
+
if (laneStageHook) setPassLane(s);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
// The lane this pass is work of, if any: its seat (the node's, its
|
|
440
|
+
// creator's, or the one a lane read moved it into). A pass in a lane's
|
|
441
|
+
// seat that read none of the lane's world has left it: its result is the
|
|
442
|
+
// frame's (a derivation whose branch no longer reaches the guess). A
|
|
443
|
+
// guess is written, not derived — it never leaves this way.
|
|
444
|
+
let d = !laneStageHook ? null : passLane;
|
|
445
|
+
// Listed before its staging, a pending pass included (the lane's own
|
|
446
|
+
// flight is the lane's); false: the pass left the lane (lanes.ts).
|
|
447
|
+
const D = !!e.T?.U || f;
|
|
448
|
+
if (laneStageHook && d !== null) {
|
|
449
|
+
if (laneStageHook(e, d, t, D)) setPassLane(d); else d = null;
|
|
450
|
+
}
|
|
451
|
+
if (!D) {
|
|
452
|
+
// Observe-tier fan-in (HUGE_FAN_IN): the validated prefix [_deps.._depsTail]
|
|
453
|
+
// IS this pass's distinct sources — count it here rather than per link. A
|
|
454
|
+
// begin/end bracket around the pass plus a per-link increment measured
|
|
455
|
+
// -5.8% on createRenderEffects:create1to1 (CodSpeed, dev tier) and cost
|
|
456
|
+
// several points of the shape wins elsewhere; this walk is a fraction of
|
|
457
|
+
// the reads that built the list and keeps no module state, so nested
|
|
458
|
+
// pulls need no save/restore. (The stale tail is trimmed at the end of
|
|
459
|
+
// this pass, or at its commit — see recompute's tail.)
|
|
460
|
+
{
|
|
461
|
+
let t = 0;
|
|
462
|
+
for (let n = e.C; n !== null; n = n.G) {
|
|
463
|
+
t++;
|
|
464
|
+
if (n === e._) break;
|
|
465
|
+
}
|
|
466
|
+
if (t >= GRAPH_SIZE_WARN_AT) noteFanIn(e, t);
|
|
467
|
+
}
|
|
468
|
+
// INV-11 (#3330): the equality gate compares against the slot this run
|
|
469
|
+
// publishes to — the staged `_pendingValue` when staged; a lane node's
|
|
470
|
+
// lane value (a node entering the lane this pass has none yet — its
|
|
471
|
+
// committed value, `laneStage` having voided a frame staging).
|
|
472
|
+
const o = e.ue !== NOT_PENDING ? e.ue : e.l & CONFIG_OVERRIDE && e.T.V !== NOT_PENDING ? e.T.V : e.le;
|
|
473
|
+
// A throwing comparator is contained inside `isSame` (A3, #2837; #4111).
|
|
474
|
+
// A pass replacing an error this node staged is a new answer, even the
|
|
475
|
+
// committed value (D-41: a recovery equal to the last good value is a
|
|
476
|
+
// change): the slot it publishes to held the error, and the readers that
|
|
477
|
+
// derived from it — `latest()` throws it — re-derive (#4403).
|
|
478
|
+
const s = !n && a || !!e.T?.Te || !!r || !isSame(e, o, c);
|
|
479
|
+
// A committed derived change becomes a cause for this node's subscribers,
|
|
480
|
+
// chaining their attribution through this node to the root write.
|
|
481
|
+
if (attrHooks !== null) {
|
|
482
|
+
i = s && !e.T?.U;
|
|
483
|
+
if (i && !n && !t) attrHooks.derivedChanged(e);
|
|
484
|
+
}
|
|
485
|
+
// Effects use `_equals: false` (no per-effect closure). The side effects that
|
|
486
|
+
// the equals closure used to perform — flagging the effect dirty and enqueueing
|
|
487
|
+
// its runner — happen here instead. `!create` matches the previous `initialized`
|
|
488
|
+
// gate: the explicit recompute(node, true) inside effect() does not enqueue, so
|
|
489
|
+
// effect() can call its runner synchronously for the first run.
|
|
490
|
+
if (n && s &&
|
|
491
|
+
// A stale reader of a lane (REACTIVE_SCREEN_READ) that computed what it
|
|
492
|
+
// last applied owes no run (lanes.ts).
|
|
493
|
+
!(e.R & REACTIVE_SCREEN_READ && !e.Qe && Object.is(e.It, c)) &&
|
|
494
|
+
// A re-staging pass owes no run: the commit replays the effect (#3802).
|
|
495
|
+
e.ue === NOT_PENDING) {
|
|
496
|
+
e.Qe = !e.T?.U;
|
|
497
|
+
// Reuse one bound runner per effect — runEffect no-ops on a stale
|
|
498
|
+
// `_modified`, so re-enqueueing the same function is harmless.
|
|
499
|
+
// Lane work's run is the lane's (released at its reveal) — a first
|
|
500
|
+
// pass under a lane too: `effect()` skips the synchronous first run of
|
|
501
|
+
// a lane's node.
|
|
502
|
+
if (!t || d !== null) globalQueue.enqueue(n, e.W ??= GlobalQueue.q.bind(null, e), d);
|
|
503
|
+
}
|
|
504
|
+
// Lanes. A written guess's own pass from the frame: its source
|
|
505
|
+
// recomputed it — the truth (A18: "the source is whatever recomputes the
|
|
506
|
+
// node"). It stages under the lane's parent whether or not it changed (a
|
|
507
|
+
// confirm is a landing too — the lane's end commits it) and notifies
|
|
508
|
+
// only a correction: an equal truth re-runs nothing (lanes.ts). (As the
|
|
509
|
+
// lane's own work the lane judged it — `laneStage`.)
|
|
510
|
+
// (The hook first: a build without lanes folds the arm away.)
|
|
511
|
+
if (laneOutcomeHook && !t && d === null && e.l & CONFIG_GUESS && laneOutcomeHook(e, c, D)) ; else if (D) ; else if (s) {
|
|
512
|
+
// L2: a first pass is born held — staged, committed with the
|
|
513
|
+
// transaction, its effect's first run the landing's (A29; `effect()`
|
|
514
|
+
// skips the synchronous first run on a staged value) — when the flush
|
|
515
|
+
// has joined a transaction and either the pass read a held node (it
|
|
516
|
+
// derives from that world — the join merged the node's transaction
|
|
517
|
+
// into the flush's), or — a verdict lane's mount, mainline — read a
|
|
518
|
+
// staging of this flush (the same world before the seam parks it; the
|
|
519
|
+
// mount is not shown ahead of it: #3851), or a pass created it (ruling
|
|
520
|
+
// A: a held pass's children are the transaction's). Any other mount
|
|
521
|
+
// that read a staging does not join by being new (2026-10-07): it
|
|
522
|
+
// publishes and re-derives if the flush parks. A node created outside any pass
|
|
523
|
+
// that read only the committed world (root setup, a mount, an effect
|
|
524
|
+
// callback) is nobody's frame and publishes directly, as does a first
|
|
525
|
+
// pass that runs before anything joins: the pass's input, not a verdict
|
|
526
|
+
// after it. An effect still carrying an uncommitted staged value
|
|
527
|
+
// re-stages: the commit applies the latest pass, not the born-held one.
|
|
528
|
+
if (d !== null) {
|
|
529
|
+
// Lane work: the lane's value — into the slot while the lane has not
|
|
530
|
+
// revealed (its reveal shows it), as its staging once it has (the
|
|
531
|
+
// next reveal promotes it; the screen keeps the revealed value). An
|
|
532
|
+
// effect's value slot is private: the run the lane holds is what
|
|
533
|
+
// shows it — unless the effect was born held, whose first run is the
|
|
534
|
+
// commit's (A29): it re-stages, as below.
|
|
535
|
+
if (n && e.ue === NOT_PENDING) e.le = c; else if (n || d.Nt) e.ue = c; else e.T.V = c;
|
|
536
|
+
} else if (t ? !((flushTransaction !== null || passTx !== null) && (C || creatorPass(S)?.R & REACTIVE_JOINED)) : n && e.ue === NOT_PENDING) {
|
|
537
|
+
// A first pass publishes directly. So does an effect: its value slot
|
|
538
|
+
// is private (only its own run reads it), and the staging round-trip
|
|
539
|
+
// (queuePendingNode + commitPendingNodes) paid per effect on the
|
|
540
|
+
// plain path is pure overhead.
|
|
541
|
+
// NOTE (stage-3, 2026-08-21): a quiet-world MEMO direct-commit was
|
|
542
|
+
// attempted here and REVERTED — memo staging is load-bearing beyond
|
|
543
|
+
// transitions: mid-batch pulls (read-triggered recomputes before
|
|
544
|
+
// sources commit) must see the fresh value while PLAIN reads stay
|
|
545
|
+
// committed until flush (#3009 purity). The pending round-trip is
|
|
546
|
+
// that separation; it cannot be skipped on any path a pull can reach.
|
|
547
|
+
e.le = c;
|
|
548
|
+
} else {
|
|
549
|
+
e.ue = c;
|
|
550
|
+
// D-39: a staged recovery keeps the error it replaces the display
|
|
551
|
+
// until it commits (`commitPendingNode`), as a write over one does
|
|
552
|
+
// (`setMemo`).
|
|
553
|
+
if (_) e.T.Te = _;
|
|
554
|
+
// Born held: in the transaction from birth, so a read of it this
|
|
555
|
+
// tick is a read of its world (the mount-during-a-hold case), not
|
|
556
|
+
// only after the seam parks it.
|
|
557
|
+
if (t) {
|
|
558
|
+
holdNode(e, flushTransaction ?? passTx);
|
|
559
|
+
// Born into the future: no committed value until the landing
|
|
560
|
+
// (`commitPendingNode` initializes it) — every reader of it
|
|
561
|
+
// derives from the future (`read`), an untracked one throws
|
|
562
|
+
// (A19 exc. 1).
|
|
563
|
+
e.m |= STATUS_UNINITIALIZED;
|
|
564
|
+
// A boundary that has not shown content catches it: its fallback
|
|
565
|
+
// waits for the commit (#3540). (The pass's own status clear above
|
|
566
|
+
// dropped what the read recorded.) Not when the flush has joined
|
|
567
|
+
// the hold and the pass is its own (not a verdict lane's mount,
|
|
568
|
+
// #3851): the mount is part of the hold, and the boundary appears
|
|
569
|
+
// at its commit, its fallback never seen (D-13, U-3) — a `<Show>`
|
|
570
|
+
// mounting it over a landing included.
|
|
571
|
+
freshHook?.(e, !l && flushTransaction);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// insertSubs only walks _subs (no scheduling of its own), so a
|
|
575
|
+
// subscriber-less node has nothing to notify.
|
|
576
|
+
if (e.o !== null) {
|
|
577
|
+
insertSubs(e);
|
|
578
|
+
// Lane work re-staged: the lane's members re-derive as its work.
|
|
579
|
+
if (d !== null) laneDirty(e, d);
|
|
580
|
+
}
|
|
581
|
+
} else if (e.lt != A) {
|
|
582
|
+
for (let t = e.o; t !== null; t = t.t) insertIntoHeapHeight(t.u, dirtyQueue);
|
|
583
|
+
}
|
|
584
|
+
// Silent recovery: errored → unchanged value fires no notification, but
|
|
585
|
+
// dependents still holding the propagated error consumed their dirty flag
|
|
586
|
+
// in an errored run and may sit on stale commits (#2949). Changed-value
|
|
587
|
+
// recoveries ride insertSubs above; a comparator throw re-errored the node
|
|
588
|
+
// (el._x?._error re-set), so this only runs on a genuinely clean recovery.
|
|
589
|
+
if (!s && !e.T?.U) {
|
|
590
|
+
if (_) settleErroredDependents(e, _);
|
|
591
|
+
// Self-registration (this node's own superseded flight) is the #3181
|
|
592
|
+
// sweep's business below — retiring it here too would walk twice.
|
|
593
|
+
if (I) for (const t of I) if (t !== e) settlePendingSource(e, t);
|
|
594
|
+
}
|
|
595
|
+
// #3181: a synchronous settle supersedes the old landing callback, so
|
|
596
|
+
// recompute owns its pending-source sweep. An uninitialized node without
|
|
597
|
+
// a replacement source still has no truth to reveal and must stay parked.
|
|
598
|
+
if (N && !(e.m & (STATUS_PENDING | STATUS_UNINITIALIZED))) settlePendingSource(e);
|
|
599
|
+
}
|
|
600
|
+
// A first pass that went pending where the born-held arm would have held
|
|
601
|
+
// its value (it read a held node, or a verdict lane's mount read a staging,
|
|
602
|
+
// #3851) is held like it: its answer is the hold's, and the hold waits
|
|
603
|
+
// for it (A29, #3800). A boundary that has not shown content catches it
|
|
604
|
+
// and it is held all the same — its answer reveals no earlier than the
|
|
605
|
+
// hold's commit (D-12) — but the hold does not wait for it: its only
|
|
606
|
+
// readers are behind the fallback (`_hidden`; F-4, #3540). A boundary
|
|
607
|
+
// re-armed by `on` owns what it collects outright (D-14): not held.
|
|
608
|
+
if (t && d === null && C && e.m & STATUS_PENDING) {
|
|
609
|
+
const t = flushTransaction ?? passTx;
|
|
610
|
+
if (t && !t.V && freshHook?.(e) !== CAUGHT_REARMED) holdNode(e, t);
|
|
611
|
+
}
|
|
612
|
+
// Dependencies are the committed frame's until it is replaced (A30, #3410):
|
|
613
|
+
// a pass that staged its value leaves the previous pass's tail linked for
|
|
614
|
+
// `commitPendingNode` to trim, so a write to a dependency the committed
|
|
615
|
+
// value still derives from reaches this node until the commit. A pass that
|
|
616
|
+
// published directly, or changed nothing, trims now. An errored pass (a
|
|
617
|
+
// throw, NotReady included, or a comparator throw above) keeps its full
|
|
618
|
+
// list as before — `_depsTail` marks where it stopped — and the commit
|
|
619
|
+
// skips it by the same `_error`. An effect's frame is the run its value is
|
|
620
|
+
// applied by, not the value slot (#3438): a pass that still owes a run
|
|
621
|
+
// (`_modified`) has not replaced what the last run published, so its tail
|
|
622
|
+
// waits for `runEffect` to trim once the run applies. A pass that changed
|
|
623
|
+
// nothing replaced nothing either (#3469): it cannot know at its tail
|
|
624
|
+
// whether the flush that ran it will park with its inputs held, so the
|
|
625
|
+
// trim waits on the flush's verdict (`heldTrim`: trimmed at the commit,
|
|
626
|
+
// kept at a park — one spurious recompute at most). A creation pass and a
|
|
627
|
+
// tracked effect trim now: their frames are replaceable like a direct
|
|
628
|
+
// commit, and a tracked effect's spurious run would be user-visible.
|
|
629
|
+
if (!e.T?.U && e.ue === NOT_PENDING && !(n && e.Qe)) {
|
|
630
|
+
if (t || n === EFFECT_TRACKED) trimStaleDeps(e); else heldTrim(e);
|
|
631
|
+
}
|
|
632
|
+
// Observe: the posture the run executed under — lane work (optimistic:
|
|
633
|
+
// overlay, never waste), a transaction's pass (held), or plain.
|
|
634
|
+
if (attrHooks !== null) attrHooks.recomputeEnd(e, t, i, d !== null, flushTransaction !== null || passTx !== null || (e.l & CONFIG_HELD) !== 0, e.ue !== NOT_PENDING);
|
|
635
|
+
// A staged value, a parked frame (L2: the commit retires it), or status the
|
|
636
|
+
// commit sweep must settle (a pending or uninitialized pass), queues the
|
|
637
|
+
// node for this flush's commit. A first pass queues only when pending or
|
|
638
|
+
// born held: otherwise its value published directly. A queued pass's
|
|
639
|
+
// children are uncommitted (CONFIG_STAGED) until that commit.
|
|
640
|
+
if (e.ue !== NOT_PENDING || e.T !== null && (e.T.ft !== null || e.T.Tt !== null) || (e.m & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0 && (!t || (e.m & STATUS_PENDING) !== 0)) {
|
|
641
|
+
e.l |= CONFIG_STAGED;
|
|
642
|
+
// Lane work is the lane's to reveal (its seam), not this flush's commit.
|
|
643
|
+
if (d === null) queuePendingNode(e);
|
|
644
|
+
}
|
|
645
|
+
if (laneStageHook) setPassLane(s);
|
|
646
|
+
// Missed-wake reschedule (see the finally above): values this pass read
|
|
647
|
+
// before the nested commit are stale, so run again now that the heap will
|
|
648
|
+
// accept the node. Equality gates stop same-value landings from cascading,
|
|
649
|
+
// and a re-run only latches again if another nested commit changes a dep
|
|
650
|
+
// beneath it — convergent unless deps genuinely keep changing.
|
|
651
|
+
if (R) {
|
|
652
|
+
enqueueSub(e);
|
|
653
|
+
schedule();
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function updateIfNecessary(e) {
|
|
658
|
+
// Never re-enter a node that is currently computing: its dep bookkeeping
|
|
659
|
+
// (_depsTail/_depGen) is live, and a nested recompute would corrupt it.
|
|
660
|
+
// A mid-pass mark stays latched for recompute's own tail to reschedule
|
|
661
|
+
// (#3037); readers meanwhile serve the values the pass has so far.
|
|
662
|
+
// Never recompute a DISPOSED node either: recompute rewrites _flags and
|
|
663
|
+
// would resurrect it (#2983) — readers serve its last value.
|
|
664
|
+
if (e.R & (REACTIVE_RECOMPUTING_DEPS | REACTIVE_DISPOSED)) return;
|
|
665
|
+
if (e.R & REACTIVE_CHECK) {
|
|
666
|
+
for (let t = e.C; t; t = t.G) {
|
|
667
|
+
const n = t.S;
|
|
668
|
+
if (n.X) {
|
|
669
|
+
updateIfNecessary(n);
|
|
670
|
+
}
|
|
671
|
+
if (e.R & REACTIVE_DIRTY) {
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
if (e.R & REACTIVE_DIRTY || e.T?.U && e.ie < clock && !e.T?.Ee) {
|
|
677
|
+
recompute(e);
|
|
678
|
+
}
|
|
679
|
+
// The guard above refused an already-disposed node; the recompute it just
|
|
680
|
+
// ran may have disposed it (#3621) — carry the flag, or it comes back alive.
|
|
681
|
+
// The manual-write mark is state, not scheduling (#3612, A34 rule B): it
|
|
682
|
+
// says the node's staging is a PROPOSAL, lifted by a pass that re-derives
|
|
683
|
+
// or by the commit — a pull that recomputed nothing must not. Nor may it
|
|
684
|
+
// erase the last pass's verdicts for the landing (a stale reader's
|
|
685
|
+
// REACTIVE_FRAME_READ — a verdict reader pulled by a sibling before the
|
|
686
|
+
// landing lost its re-derivation; the lane reads likewise).
|
|
687
|
+
e.R = e.R & (REACTIVE_SNAPSHOT_STALE | REACTIVE_IN_HEAP | REACTIVE_IN_HEAP_HEIGHT | REACTIVE_ZOMBIE | REACTIVE_DISPOSED | REACTIVE_MANUAL_WRITE | REACTIVE_FRAME_READ | REACTIVE_STAGED_READ | REACTIVE_LANE_READ | REACTIVE_SCREEN_READ | REACTIVE_PROBE_UNANSWERED);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function computed(e, t) {
|
|
691
|
+
const n = t?.transparent ?? false;
|
|
692
|
+
// `in` (not `!== undefined`): an explicit `loadingValue: undefined` on a
|
|
693
|
+
// `T | undefined` node is a real commit #0. The typeof guard tolerates
|
|
694
|
+
// non-object option values that older call shapes force through `as any`.
|
|
695
|
+
const o = t !== null && typeof t === "object" && "loadingValue" in t;
|
|
696
|
+
// Two literals, one per tier, selected at build time (the observe flag is
|
|
697
|
+
// a literal after replacement; the untaken branch is dead code). The observe
|
|
698
|
+
// literal is the prod literal plus its `_name` and `_loc` slots — slots in
|
|
699
|
+
// the boilerplate, because a post-construction `self._name = …` forces a
|
|
700
|
+
// hidden-class transition and an out-of-object property store on EVERY node
|
|
701
|
+
// (measured: the whole of the observe tier's creation overhead). Keep the
|
|
702
|
+
// two in sync — the dist artifact test pins observe's key set to prod's
|
|
703
|
+
// plus the slots.
|
|
704
|
+
const s = {
|
|
705
|
+
id: inheritId(t, n, context),
|
|
706
|
+
l: (n ? CONFIG_TRANSPARENT : 0) | (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (!context || t?.lazy ? CONFIG_AUTO_DISPOSE : 0) | (t?.sync ? CONFIG_SYNC : 0) | (t?.ae ? CONFIG_NO_SNAPSHOT : 0) | (
|
|
707
|
+
// Plumbing is an observe-tier notion (a name and records to
|
|
708
|
+
// withhold); the prod literal never carries the bit.
|
|
709
|
+
t?.St ? CONFIG_PLUMBING : 0) | (t?.ct ?? 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
710
|
+
je: t?.equals ?? isEqual,
|
|
711
|
+
_t: null,
|
|
712
|
+
A: context?.A ?? defaultContext,
|
|
713
|
+
At: 0,
|
|
714
|
+
X: e,
|
|
715
|
+
le: o ? t.loadingValue : undefined,
|
|
716
|
+
lt: 0,
|
|
717
|
+
Rt: undefined,
|
|
718
|
+
Ct: null,
|
|
719
|
+
C: null,
|
|
720
|
+
_: null,
|
|
721
|
+
Ne: 0,
|
|
722
|
+
o: null,
|
|
723
|
+
Ot: null,
|
|
724
|
+
_parent: context,
|
|
725
|
+
Xe: null,
|
|
726
|
+
dt: null,
|
|
727
|
+
ze: null,
|
|
728
|
+
R: t?.lazy ? REACTIVE_LAZY : REACTIVE_NONE,
|
|
729
|
+
m: o ? 0 : STATUS_UNINITIALIZED,
|
|
730
|
+
ie: clock,
|
|
731
|
+
ue: NOT_PENDING,
|
|
732
|
+
Dt: -1,
|
|
733
|
+
ce: o,
|
|
734
|
+
T: null,
|
|
735
|
+
// The slot is always present (hidden class); plumbing leaves it
|
|
736
|
+
// unset, which `ownerPath` skips.
|
|
737
|
+
_name: t?.St ? undefined : t?.name ?? "computed",
|
|
738
|
+
_loc: t?.loc,
|
|
739
|
+
// Observe: the verdict windows this pass entered (verdict.ts) — a
|
|
740
|
+
// slot, so the stamp is never a write after construction.
|
|
741
|
+
Ve: 0
|
|
742
|
+
};
|
|
743
|
+
if (t?.unobserved) ext(s).Gt = t.unobserved;
|
|
744
|
+
setupComputedNode(s, t);
|
|
745
|
+
return s;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/** Lazily allocate a node's cold extension (ONE shape for signals and
|
|
749
|
+
* computeds — `_x` access stays monomorphic). Installers write through
|
|
750
|
+
* this; hot paths read `el._x?._field` gated by the _config presence bits.
|
|
751
|
+
* Never call ext() just to store a field's default. */ function ext(e) {
|
|
752
|
+
return e.T ??= {
|
|
753
|
+
Ee: null,
|
|
754
|
+
Se: null,
|
|
755
|
+
U: undefined,
|
|
756
|
+
fe: undefined,
|
|
757
|
+
se: undefined,
|
|
758
|
+
Gt: undefined,
|
|
759
|
+
st: undefined,
|
|
760
|
+
ft: null,
|
|
761
|
+
Tt: null,
|
|
762
|
+
Je: null,
|
|
763
|
+
ut: false,
|
|
764
|
+
Ft: NOT_PENDING,
|
|
765
|
+
ht: -1,
|
|
766
|
+
Ie: 0,
|
|
767
|
+
V: NOT_PENDING,
|
|
768
|
+
I: 0,
|
|
769
|
+
Te: null
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/** A tracked read of a held node (the reader derives from its transaction's
|
|
774
|
+
* world): the reading pass is marked as having read it (born-held decision
|
|
775
|
+
* in `recompute`), and the flush joins the node's transaction — unless the
|
|
776
|
+
* reader is a render effect. A render effect is the frame, not a derivation
|
|
777
|
+
* (rule 3): in that transaction's own flush, or born into it (uninitialized,
|
|
778
|
+
* A29), it reads the staged value and holds nothing of its own; otherwise it
|
|
779
|
+
* reads the committed value instead (`frameRead`). A node born into the
|
|
780
|
+
* future has no committed value: every reader joins it. A first pass is
|
|
781
|
+
* something not ready under a loading boundary that has not shown content:
|
|
782
|
+
* the boundary collects it and shows its fallback, and the pass is the
|
|
783
|
+
* boundary's, not the tick's (A29's boundary exemption, #3540). */ function joinPass(e, t) {
|
|
784
|
+
e.R |= REACTIVE_JOINED;
|
|
785
|
+
// A new memo's first pass is the hold's work (born held) but not the
|
|
786
|
+
// tick's: a render effect that reads it decides — under a boundary that
|
|
787
|
+
// catches it, the boundary's; otherwise the tick joins the hold (#3540:
|
|
788
|
+
// the reader's position, not where the memo was created). Its catch, if
|
|
789
|
+
// any, is at its staging.
|
|
790
|
+
if (t.m & STATUS_UNINITIALIZED || e.i !== EFFECT_RENDER) joinPassTx(txOf(t), !e.i && e.m & STATUS_UNINITIALIZED || freshHook?.(e));
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/** `joinPass` into the hold `t` (a folded guess's: store/optimistic.ts
|
|
794
|
+
* `foldedRead`, D-34) — a reader is never a render effect there. */ function joinPassAt(e, t) {
|
|
795
|
+
e.R |= REACTIVE_JOINED;
|
|
796
|
+
joinPassTx(t, !e.i && e.m & STATUS_UNINITIALIZED || freshHook?.(e));
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/** A15's stale reader (shared-hole and reveal corollaries): a render effect
|
|
800
|
+
* reading a node held by a transaction that is not the flush's. It is served
|
|
801
|
+
* the committed value — a pending one's too — publishes mainline, and is
|
|
802
|
+
* re-derived after that transaction's landing (`_reruns`,
|
|
803
|
+
* REACTIVE_FRAME_READ) so the frame catches up with it once it is the
|
|
804
|
+
* committed world. Not a frame born into that transaction (held and never
|
|
805
|
+
* committed — created by one of its held passes, A29; `bornInto`): that
|
|
806
|
+
* one reads its transaction's values and keeps staging. Not of a flight
|
|
807
|
+
* whose inputs are already visible (CONFIG_INPUTS_PUBLISHED, #3305): its
|
|
808
|
+
* committed value would tear against them, so the reveal observes the
|
|
809
|
+
* flight instead. Once per pass.
|
|
810
|
+
*
|
|
811
|
+
* Lane work is a stale reader too (maintainer, 2026-10-01: a lane sees the
|
|
812
|
+
* screen plus its own guesses). A held write is held because showing it
|
|
813
|
+
* would tear against the rest of its frame; a lane revealing a derivation
|
|
814
|
+
* of it would show it anyway, beside inputs still committed elsewhere on
|
|
815
|
+
* the page. The guess is the one value the user opted into predicting. So
|
|
816
|
+
* a lane pass reading a transaction's held node — its parent's or any
|
|
817
|
+
* other's — derives from the committed value and is re-derived at that
|
|
818
|
+
* landing. The case read-through was built for — a corrected guess whose
|
|
819
|
+
* downstream async must refetch with the truth at once, while another of
|
|
820
|
+
* the parent's flights is still up — needs none of this: the correction
|
|
821
|
+
* dissolves the lane, and the refetching pass is the parent's own, reading
|
|
822
|
+
* the truth as a member (`joinPass`). */ function frameRead(e, t) {
|
|
823
|
+
// Frame readers: a render effect, a verdict reader (CONFIG_VERDICT — it
|
|
824
|
+
// reads the screen like one: `[isPending(x), x()]` never pairs the fresh
|
|
825
|
+
// value with pending, A10, because the reader never sees the fresh value),
|
|
826
|
+
// and lane work (a lane sees the screen plus its own guesses). Not a
|
|
827
|
+
// mount's memo (2026-10-02, considered and reverted): a memo is not a
|
|
828
|
+
// leaf — the transaction's own later passes read it — so a mainline mount's
|
|
829
|
+
// derivations carry the future (A29, born held); only its direct bindings
|
|
830
|
+
// read the screen. Lane work with no committed value yet reads a flight
|
|
831
|
+
// as a mount's memo does: it enters, and the boundary it mounts catches
|
|
832
|
+
// the pending (#3540).
|
|
833
|
+
// A boundary's tree and output (CONFIG_FRAME) read as a render effect
|
|
834
|
+
// does: structure, not a derivation of what it shows (#4283). A mount
|
|
835
|
+
// under a hold still enters it: the content memo it reads is born held,
|
|
836
|
+
// with no committed value to serve.
|
|
837
|
+
// (Verdict readers and lanes exist only with their engines installed.)
|
|
838
|
+
const n = observeFlightHook && e.l & CONFIG_VERDICT;
|
|
839
|
+
const o = laneStageHook && passLane !== null;
|
|
840
|
+
if ((!o || e.m & STATUS_UNINITIALIZED && t.m & STATUS_PENDING) && !n && (
|
|
841
|
+
// CONFIG_FRAME, spelled as at the effect's creation.
|
|
842
|
+
!(e.l & EFFECT_RENDER << 23) || t.l & CONFIG_INPUTS_PUBLISHED)) return false;
|
|
843
|
+
const s = txOf(t);
|
|
844
|
+
// Lane work and verdict readers are never the transaction's pass, whichever
|
|
845
|
+
// flush runs it: a node T holds re-enters T when it re-runs (`recompute`'s
|
|
846
|
+
// head), and the pass may then read a guess and become the lane's.
|
|
847
|
+
if (s === flushTransaction && !o && !n || bornInto(e, s)) return false;
|
|
848
|
+
staleReader(e, s);
|
|
849
|
+
// Lane work or a verdict reader no longer waits on `el`'s flight (lanes.ts
|
|
850
|
+
// `laneServed`, A15).
|
|
851
|
+
if (o || n) laneServedHook(e, t);
|
|
852
|
+
return true;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/** A node born into transaction `t`: held by it and never committed (A29,
|
|
856
|
+
* ruling A) — uninitialized (born held), or a member of the frame one of its
|
|
857
|
+
* held passes built (CONFIG_HELD_FRAME). Its branch exists only in `t`: it
|
|
858
|
+
* has no screen to read, so it reads `t`'s values, whichever flush runs it,
|
|
859
|
+
* and keeps staging (#4010). */ function bornInto(e, t) {
|
|
860
|
+
return (e.m & STATUS_UNINITIALIZED || e.l & CONFIG_HELD_FRAME) && e.l & CONFIG_HELD && txOf(e) === t;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/** The computed whose pass a creation under `owner` belongs to (a root's is
|
|
864
|
+
* its nearest computed ancestor's), or null outside every pass. */ function creatorPass(e) {
|
|
865
|
+
return e !== null && e.Pt ? e.Ht : e;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/** What `born` and the passes still running around `p` read — its
|
|
869
|
+
* creator's, theirs — of the future or a staging (REACTIVE_JOINED |
|
|
870
|
+
* REACTIVE_STAGED_READ): a pass created by one that did has no screen; its
|
|
871
|
+
* branch exists only in that world (#4341). */ function creatorReads(e, t) {
|
|
872
|
+
return (e = creatorPass(e._parent))?.R & REACTIVE_RECOMPUTING_DEPS ? creatorReads(e, t | e.R) : t & (REACTIVE_JOINED | REACTIVE_STAGED_READ);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/** L2 — park the previous pass's frame for this node's commit: the children
|
|
876
|
+
* (already allocated — nothing new) and the `_disposal` list move to the
|
|
877
|
+
* extension, keep reacting from the heap, and die when the commit publishes
|
|
878
|
+
* the frame that replaces them (`commitPendingNode`) or when the owner does. */ function parkChildren(e) {
|
|
879
|
+
markDisposal(e);
|
|
880
|
+
const t = ext(e);
|
|
881
|
+
t.Tt = e._t;
|
|
882
|
+
t.ft = e.ze;
|
|
883
|
+
e._t = null;
|
|
884
|
+
e.ze = null;
|
|
885
|
+
e.At = 0;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Build an Effect node with all effect-specific fields baked into a single object literal,
|
|
890
|
+
* so V8 sees the full hidden class shape at construction time. Effects always run in lazy
|
|
891
|
+
* mode (recompute is called explicitly by `effect()`), so we hardcode the lazy bits and skip
|
|
892
|
+
* the auto-dispose CONFIG bit (effect() previously cleared it post-construction).
|
|
893
|
+
*/ function createEffectNode(e, t, n, o, s) {
|
|
894
|
+
const E = s?.transparent ?? false;
|
|
895
|
+
// Prod and observe boilerplates — see computed() for why the observe tier
|
|
896
|
+
// gets its `_name` as a literal slot rather than a write after the fact.
|
|
897
|
+
// The default label is the node kind (tracked effects relabel their computed
|
|
898
|
+
// in trackedEffect); the wrappers in signals.ts no longer spread a name into
|
|
899
|
+
// the options to get it.
|
|
900
|
+
const l = {
|
|
901
|
+
id: inheritId(s, E, context),
|
|
902
|
+
l:
|
|
903
|
+
// CONFIG_FRAME for a render effect (EFFECT_RENDER = 1; a user
|
|
904
|
+
// effect's 1 << 24 is unused).
|
|
905
|
+
o << 23 | (E ? CONFIG_TRANSPARENT : 0) | (s?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (s?.sync ? CONFIG_SYNC : 0) | (s?.ct ?? 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
906
|
+
je: false,
|
|
907
|
+
_t: null,
|
|
908
|
+
A: context?.A ?? defaultContext,
|
|
909
|
+
At: 0,
|
|
910
|
+
X: e,
|
|
911
|
+
le: undefined,
|
|
912
|
+
lt: 0,
|
|
913
|
+
Rt: undefined,
|
|
914
|
+
Ct: null,
|
|
915
|
+
C: null,
|
|
916
|
+
_: null,
|
|
917
|
+
Ne: 0,
|
|
918
|
+
o: null,
|
|
919
|
+
Ot: null,
|
|
920
|
+
_parent: context,
|
|
921
|
+
Xe: null,
|
|
922
|
+
dt: null,
|
|
923
|
+
ze: null,
|
|
924
|
+
R: REACTIVE_LAZY,
|
|
925
|
+
m: STATUS_UNINITIALIZED,
|
|
926
|
+
ie: clock,
|
|
927
|
+
ue: NOT_PENDING,
|
|
928
|
+
Dt: -1,
|
|
929
|
+
ce: false,
|
|
930
|
+
Qe: false,
|
|
931
|
+
It: undefined,
|
|
932
|
+
Ge: t,
|
|
933
|
+
kt: n,
|
|
934
|
+
Vt: undefined,
|
|
935
|
+
i: o,
|
|
936
|
+
T: null,
|
|
937
|
+
_name: s?.name ?? "effect",
|
|
938
|
+
_loc: s?.loc,
|
|
939
|
+
Ve: 0
|
|
940
|
+
};
|
|
941
|
+
// Effects dispatch status through the SHARED notifier (statusNotifierOf,
|
|
942
|
+
// keyed off _type) — storing it per node forced a full NodeExtension
|
|
943
|
+
// allocation on EVERY effect at creation (an alloc + 19 field stores,
|
|
944
|
+
// +23% effect creation, caught by the creation benches). Only genuinely
|
|
945
|
+
// per-node channels (boundaries) live on _x.
|
|
946
|
+
if (s?.unobserved) ext(l).Gt = s.unobserved;
|
|
947
|
+
setupComputedNode(l, lazyOptions);
|
|
948
|
+
return l;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* The shared status notifier for effect nodes, installed once by effect.ts
|
|
953
|
+
* at module evaluation (`this`-dispatched — one function serves every
|
|
954
|
+
* effect, so nodes never store it). CARVE 4: the boundaries' per-node
|
|
955
|
+
* channel (`_x._notifyStatus`) went with them; effects are the only
|
|
956
|
+
* display consumers.
|
|
957
|
+
*/ let effectStatusNotify = null;
|
|
958
|
+
|
|
959
|
+
function setEffectStatusNotify(e) {
|
|
960
|
+
effectStatusNotify = e;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/** Resolve a node's status notifier: effect nodes (`_type` — EFFECT_PURE is
|
|
964
|
+
* 0, and only effect literals carry the field) get the shared notifier.
|
|
965
|
+
* Presence doubles as the "display consumer" membership test in the status
|
|
966
|
+
* walks, exactly as the per-node field did when every effect carried one. */ function statusNotifierOf(e) {
|
|
967
|
+
return e.i ? effectStatusNotify : null;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const lazyOptions = {
|
|
971
|
+
lazy: true
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
function setupComputedNode(e, t) {
|
|
975
|
+
e.Ct = e;
|
|
976
|
+
const n = context?.Pt ? context.Ht : context;
|
|
977
|
+
if (context) linkChild(context, e);
|
|
978
|
+
if (n) e.lt = n.lt + 1;
|
|
979
|
+
wireExternalSourceHook?.(e);
|
|
980
|
+
!t?.lazy && recompute(e, true);
|
|
981
|
+
if (snapshotCaptureActive && !t?.lazy) {
|
|
982
|
+
if (!(e.m & STATUS_PENDING) && !(e.l & CONFIG_NO_SNAPSHOT)) takeSnapshot(e, e.le);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function signal(e, t) {
|
|
987
|
+
// Prod and observe boilerplates — see computed(). The observe literal adds
|
|
988
|
+
// `_name`, `_loc` and `_owner` (the creating owner, stamped by registerGraph for
|
|
989
|
+
// createSignal nodes so ownerPath can locate signal subjects; null here,
|
|
990
|
+
// and staying null on internal signals — one shape either way).
|
|
991
|
+
const n = {
|
|
992
|
+
je: t?.equals ?? isEqual,
|
|
993
|
+
l: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.ae ? CONFIG_NO_SNAPSHOT : 0),
|
|
994
|
+
le: e,
|
|
995
|
+
o: null,
|
|
996
|
+
Ot: null,
|
|
997
|
+
ie: clock,
|
|
998
|
+
ue: NOT_PENDING,
|
|
999
|
+
Dt: -1,
|
|
1000
|
+
T: null,
|
|
1001
|
+
_name: t?.name ?? "signal",
|
|
1002
|
+
_loc: t?.loc,
|
|
1003
|
+
H: null
|
|
1004
|
+
};
|
|
1005
|
+
if (t?.unobserved) ext(n).Gt = t.unobserved;
|
|
1006
|
+
if (snapshotCaptureActive && !(n.l & CONFIG_NO_SNAPSHOT)) takeSnapshot(n, e);
|
|
1007
|
+
return n;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function isEqual(e, t) {
|
|
1011
|
+
return e === t;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
/** A store slot node (CONFIG_SLOT_NODE): the whole node in ONE literal — the
|
|
1015
|
+
* target and key as back-refs in place of closures (`equals` is a method
|
|
1016
|
+
* call, `this` the node; the unobserved sweep dispatches to one shared hook,
|
|
1017
|
+
* graph.ts), the store's wrap cache (`px`/`pxv`: the proxy last served for
|
|
1018
|
+
* this key and the raw it wrapped — one pointer compare replaces a WeakMap
|
|
1019
|
+
* lookup per read) and the accessor verdict (`acc`) pre-shaped. No options
|
|
1020
|
+
* object, no NodeExtension, no post-construction expandos. `ownedWrite` is
|
|
1021
|
+
* baked in: the setter carries the owned-scope guard; node-level writes are
|
|
1022
|
+
* the store's notification machinery. The observe literal adds the label
|
|
1023
|
+
* and location slots (the store relabels `store.<key>` and stamps the
|
|
1024
|
+
* store's `loc` when the engine is installed). */ function slotSignal(e, t, n, o, s) {
|
|
1025
|
+
const E = {
|
|
1026
|
+
je: t,
|
|
1027
|
+
l: CONFIG_OWNED_WRITE | CONFIG_SLOT_NODE,
|
|
1028
|
+
le: e,
|
|
1029
|
+
o: null,
|
|
1030
|
+
Ot: null,
|
|
1031
|
+
ie: clock,
|
|
1032
|
+
ue: NOT_PENDING,
|
|
1033
|
+
Dt: -1,
|
|
1034
|
+
T: null,
|
|
1035
|
+
O: n,
|
|
1036
|
+
gt: o,
|
|
1037
|
+
acc: s,
|
|
1038
|
+
px: undefined,
|
|
1039
|
+
pxv: undefined,
|
|
1040
|
+
_name: "signal",
|
|
1041
|
+
_loc: undefined,
|
|
1042
|
+
H: null
|
|
1043
|
+
};
|
|
1044
|
+
return E;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* When set to a component name string, any reactive read that is not inside a nested tracking
|
|
1049
|
+
* scope will log a dev-mode warning. Managed automatically by `untrack(fn, strictReadLabel)`.
|
|
1050
|
+
*/
|
|
1051
|
+
/** The verdict windows' read (verdict.ts), installed while a `latest` or
|
|
1052
|
+
* `isPending` window is open and null otherwise: `read` dispatches to it
|
|
1053
|
+
* then, and a program that never imports them never has one. */ let verdict = null;
|
|
1054
|
+
|
|
1055
|
+
function setVerdict(e) {
|
|
1056
|
+
verdict = e;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* Runs `fn` outside of any reactive tracking — reads inside `fn` will not
|
|
1061
|
+
* subscribe the current scope. Returns whatever `fn` returns.
|
|
1062
|
+
*
|
|
1063
|
+
* Use `untrack` inside a memo or effect when you need to read a signal once
|
|
1064
|
+
* without making the surrounding computation depend on its future changes.
|
|
1065
|
+
*
|
|
1066
|
+
* `untrack` stops dependency tracking, not async settlement. Reading an
|
|
1067
|
+
* uninitialized async source still suspends the owning computation; it runs
|
|
1068
|
+
* again when that source first resolves. Afterward, the untracked read is a
|
|
1069
|
+
* snapshot and does not subscribe the computation to later changes.
|
|
1070
|
+
*
|
|
1071
|
+
* If the first read must return immediately, give the async source a
|
|
1072
|
+
* `loadingValue` before reading it with `untrack`. `isPending` and `latest`
|
|
1073
|
+
* inspect updates to an existing value; neither supplies a missing initial
|
|
1074
|
+
* value.
|
|
1075
|
+
*
|
|
1076
|
+
* Pass a `strictReadLabel` string to enable a dev-mode warning: any reactive
|
|
1077
|
+
* read inside `fn` that isn't inside a nested tracking scope will log a
|
|
1078
|
+
* warning naming the label.
|
|
1079
|
+
*
|
|
1080
|
+
* @example
|
|
1081
|
+
* ```ts
|
|
1082
|
+
* createEffect(
|
|
1083
|
+
* () => trigger(), // tracks `trigger` only
|
|
1084
|
+
* () => {
|
|
1085
|
+
* const snapshot = untrack(() => state); // read once, untracked
|
|
1086
|
+
* log(snapshot);
|
|
1087
|
+
* }
|
|
1088
|
+
* );
|
|
1089
|
+
*
|
|
1090
|
+
* const color = createMemo<string>(() => fetchColor(), { loadingValue: "gray" });
|
|
1091
|
+
* const initialColor = untrack(color); // "gray" while the fetch is unresolved
|
|
1092
|
+
* ```
|
|
1093
|
+
*/ function untrack(e, t) {
|
|
1094
|
+
const n = tracking;
|
|
1095
|
+
tracking = false;
|
|
1096
|
+
try {
|
|
1097
|
+
if (externalUntrackHook) return externalUntrackHook(e);
|
|
1098
|
+
return e();
|
|
1099
|
+
} finally {
|
|
1100
|
+
tracking = n;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* Set while runtime bookkeeping reads a node inside another node's pass (a
|
|
1106
|
+
* loading boundary priming its tree at creation, from whatever pass is
|
|
1107
|
+
* mounting it). `context` is that node, but the read is nobody's: the value
|
|
1108
|
+
* is probed, never derived from, so nothing the read would normally record
|
|
1109
|
+
* on `context` may be recorded — not the untracked-pending re-run link
|
|
1110
|
+
* (`read`, `!tracking`; #3528: a boundary's `on` key read this way from
|
|
1111
|
+
* `notify` linked the key's source into an unrelated async memo, a cycle that
|
|
1112
|
+
* never converged), and not a transaction entry (`enterStagedRead`; #3540: a
|
|
1113
|
+
* born-held tree would otherwise pull the mounting pass into the hold).
|
|
1114
|
+
*/ let spectating = false;
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Bring a computed to a readable state: lazy/disposed nodes are (re)computed;
|
|
1118
|
+
* `refresh` additionally pulls the node fully up to date so its status flags
|
|
1119
|
+
* reflect the current graph.
|
|
1120
|
+
*/ function prepareComputed(e, t) {
|
|
1121
|
+
if (e.R & REACTIVE_LAZY) {
|
|
1122
|
+
e.R &= ~REACTIVE_LAZY;
|
|
1123
|
+
recompute(e, true);
|
|
1124
|
+
} else if (e.R & REACTIVE_DISPOSED) {
|
|
1125
|
+
// Two disposal lifecycles share the flag (#3024). Observation-lifecycle
|
|
1126
|
+
// nodes (CONFIG_AUTO_DISPOSE) are dormant — torn down by unobserved()
|
|
1127
|
+
// when the last subscriber left — and reads reawaken them; that is the
|
|
1128
|
+
// pay-for-use contract. Owner-lifecycle nodes are dead: recomputing would
|
|
1129
|
+
// re-run user code in a torn-down tree (and discard manual writes on
|
|
1130
|
+
// derived-writable signals), so reads return the last committed value.
|
|
1131
|
+
if (e.l & CONFIG_AUTO_DISPOSE) {
|
|
1132
|
+
const t = e._parent;
|
|
1133
|
+
if (t !== null) {
|
|
1134
|
+
// A dormant node was off the chain when its owner died, so the strip
|
|
1135
|
+
// in disposeChildren missed it: freeze here instead (#3024).
|
|
1136
|
+
if (t.R & REACTIVE_DISPOSED) {
|
|
1137
|
+
e.l &= ~CONFIG_AUTO_DISPOSE;
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
// A zombie never left its chain (the parked frame is drained whole,
|
|
1141
|
+
// and its members skip the splice): relinking would put it on the
|
|
1142
|
+
// live chain too.
|
|
1143
|
+
if (!(e.R & REACTIVE_ZOMBIE)) linkChild(t, e);
|
|
1144
|
+
}
|
|
1145
|
+
recompute(e, true);
|
|
1146
|
+
}
|
|
1147
|
+
} else ;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// CARVE 3: until()'s authoritative-view wakeup (notifyAuthoritativeObservers /
|
|
1151
|
+
// installAuthoritativeRead), the stale-of-foreign clause (heldFromStale,
|
|
1152
|
+
// recordStaleReplay, ownsHold) and the transaction entry on a staged read
|
|
1153
|
+
// (enterStagedRead, stagedEntry / born held, underFreshLoadingBoundary) went
|
|
1154
|
+
// with the transactions.
|
|
1155
|
+
/**
|
|
1156
|
+
* Rule 1 (value selection): does this reader see a STAGED node's COMMITTED
|
|
1157
|
+
* value? One implementation of the rule the fast paths (read's fast block)
|
|
1158
|
+
* carry as their trivial ternary. In order:
|
|
1159
|
+
* - no reader at all (an untracked read) — the committed frame;
|
|
1160
|
+
* - nothing staged;
|
|
1161
|
+
* - a children-forbidden reader (createTrackedEffect / onSettled: the frame,
|
|
1162
|
+
* never the graph — A32).
|
|
1163
|
+
* False means the reader derives from the staged value.
|
|
1164
|
+
*/ function readerSeesCommitted(e, t) {
|
|
1165
|
+
return !t || e.ue === NOT_PENDING || t.l & CONFIG_CHILDREN_FORBIDDEN;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/** A28 — set when a node is staged (queuePendingNode) OUTSIDE a flush;
|
|
1169
|
+
* cleared when the next flush begins. The read sites test this one module
|
|
1170
|
+
* boolean instead of `globalQueue._running`: inside a flush it is false and
|
|
1171
|
+
* the A28 arm costs nothing; outside, only a tick with unflushed writes pays
|
|
1172
|
+
* the staged-node check. */ let unflushedStaged = false;
|
|
1173
|
+
|
|
1174
|
+
function markUnflushedStaged() {
|
|
1175
|
+
unflushedStaged = true;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
/** A28 — a write becomes visible at flush. Outside a flush, a node holding a
|
|
1179
|
+
* staged value was written since the last flush: staging commits at flush
|
|
1180
|
+
* end, so nothing else leaves a node in this state. Inside a flush the rule
|
|
1181
|
+
* does not apply (A28 (4): promoted within the round). The value an
|
|
1182
|
+
* unflushed node serves is its committed value, or NOT_PENDING when nothing
|
|
1183
|
+
* is unflushed. Exempt: promoted writes (A28 (4): a write issued inside a
|
|
1184
|
+
* recompute is promoted at that recompute's end — boundary and loading
|
|
1185
|
+
* machinery, signals declared for in-computation writes). */ function unflushedValue(e) {
|
|
1186
|
+
if (globalQueue.Y || e.ue === NOT_PENDING || e.l & CONFIG_PROMOTED) return NOT_PENDING;
|
|
1187
|
+
// A held node (L2) carries a staged value between flushes too, but it was
|
|
1188
|
+
// flushed — and parked. Not an unflushed write — unless rewritten since:
|
|
1189
|
+
// then the flushed staging is what the world sees until the next flush.
|
|
1190
|
+
if (e.l & CONFIG_HELD) return e.T.ht === clock ? e.T.Ft : NOT_PENDING;
|
|
1191
|
+
return e.le;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/** A28: a held node rewritten outside a flush keeps the staging the last
|
|
1195
|
+
* flush left (`_flushed`) until the flush that carries the rewrite — the
|
|
1196
|
+
* stash is stamped with the clock, which that flush advances. */ function stashFlushed(e) {
|
|
1197
|
+
const t = e.T;
|
|
1198
|
+
if (t.ht !== clock) {
|
|
1199
|
+
t.Ft = e.ue;
|
|
1200
|
+
t.ht = clock;
|
|
1201
|
+
unflushedStaged = true;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/** Nodes written inside a creation-time recompute (CONFIG_PROMOTED). */ const promotedWrites = [];
|
|
1206
|
+
|
|
1207
|
+
/** A derivation served the committed value because of an unflushed write
|
|
1208
|
+
* (A28) must run again in the flush that carries it — the late-linker case
|
|
1209
|
+
* (#3337's reason to defer the walk): it linked after the write walked. */ function markLateLinker(e) {
|
|
1210
|
+
// The pass's own tail re-enqueues on this latch (recompute's finally) —
|
|
1211
|
+
// a direct enqueue here would be wiped by the pass's flag reset.
|
|
1212
|
+
e.R |= REACTIVE_MISSED_WAKE;
|
|
1213
|
+
return true;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
function resyncUnflushedCompanions() {
|
|
1217
|
+
unflushedStaged = false;
|
|
1218
|
+
// Length-guarded: the common flush has nothing here and allocates nothing.
|
|
1219
|
+
if (promotedWrites.length !== 0) {
|
|
1220
|
+
for (const e of promotedWrites) e.l &= ~CONFIG_PROMOTED;
|
|
1221
|
+
promotedWrites.length = 0;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/** A tracked read's pull of a computed: bring it current when the flush has
|
|
1226
|
+
* reached its height (or the reader is a fresh-pull waiter), and sit the
|
|
1227
|
+
* reader above it. */ function pullComputed(e, t) {
|
|
1228
|
+
if (e.lt >= dirtyQueue.Et) {
|
|
1229
|
+
markNode(t);
|
|
1230
|
+
markHeap(dirtyQueue);
|
|
1231
|
+
updateIfNecessary(e);
|
|
1232
|
+
}
|
|
1233
|
+
// Fresh-pull readers (awaitable refresh's waiter) recompute a dirty
|
|
1234
|
+
// source inline even when the height gate defers to the flush: the
|
|
1235
|
+
// waiter must park on the re-ask's window (or serve its sync answer),
|
|
1236
|
+
// never read the PRE-re-ask value as settled. Self-guarded: a clean
|
|
1237
|
+
// node no-ops and updateIfNecessary refuses disposed nodes (#2983) —
|
|
1238
|
+
// a dead target serves its last value, which is already quiescent.
|
|
1239
|
+
else if (t.l & CONFIG_FRESH_READ) updateIfNecessary(e);
|
|
1240
|
+
const n = e.lt;
|
|
1241
|
+
// parent check is shallow, might need to be recursive
|
|
1242
|
+
if (n >= t.lt && e._parent !== t) t.lt = n + 1;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
function read(e) {
|
|
1246
|
+
let t = context;
|
|
1247
|
+
if (t?.Pt) t = t.Ht;
|
|
1248
|
+
const n = e;
|
|
1249
|
+
// CARVE 1: `owner` was `el._firewall || el` — a store leaf's status,
|
|
1250
|
+
// height and error were read off its projection computed. (Typed loosely
|
|
1251
|
+
// as the `||` expression was: a signal's `_statusFlags`/`_height` read as
|
|
1252
|
+
// undefined and mask to 0 on every site below.)
|
|
1253
|
+
const o = e;
|
|
1254
|
+
// The verdict windows (`latest`, `isPending`; verdict.ts): one cold path,
|
|
1255
|
+
// off this one, present only while a window is open. It wakes a lazy or
|
|
1256
|
+
// dormant memo itself, outside the window (#4366).
|
|
1257
|
+
if (verdict !== null) return verdict(e, t);
|
|
1258
|
+
if (typeof n.X === "function") prepareComputed(e);
|
|
1259
|
+
if (!n.X && e.T?.st === undefined && !snapshotCaptureActive && (!unflushedStaged || e.ue === NOT_PENDING) && // A28, see readNodeFast
|
|
1260
|
+
!(e.l & CONFIG_OVERRIDE) && // lanes: the slow path's one arm
|
|
1261
|
+
true) {
|
|
1262
|
+
if (t) {
|
|
1263
|
+
if (tracking) link(e, t);
|
|
1264
|
+
// L2: a pass that reads something already in the future joins it —
|
|
1265
|
+
// the one mechanism behind "a write that reaches the future joins
|
|
1266
|
+
// it". Tracked or not (`untrack` is about dependencies, not about
|
|
1267
|
+
// which world a pass derives from — posture-store-parity S4/S5): a
|
|
1268
|
+
// derivation of a held write published mainline would tear. Frame
|
|
1269
|
+
// readers (children-forbidden, A32) see committed below and stay
|
|
1270
|
+
// out; a render effect outside a parking flush sees committed and
|
|
1271
|
+
// stays out too (rule 3, `frameRead`). The pass remembers it read the
|
|
1272
|
+
// future (REACTIVE_JOINED): a first pass that did is born held (A29),
|
|
1273
|
+
// wherever it was created. Lane work sees the screen (`frameRead`).
|
|
1274
|
+
if (e.l & CONFIG_HELD && !(t.l & CONFIG_CHILDREN_FORBIDDEN)) {
|
|
1275
|
+
if (frameRead(t, e)) return e.le;
|
|
1276
|
+
joinPass(t, e);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
// Committed visibility for untracked and children-forbidden readers.
|
|
1280
|
+
if (!t || e.ue === NOT_PENDING || t.l & CONFIG_CHILDREN_FORBIDDEN) return e.le;
|
|
1281
|
+
// (A plain signal is never uninitialized — the carve-out is `serve`'s.)
|
|
1282
|
+
// Lane work over a folded lane (D-34), once the frame has a
|
|
1283
|
+
// transaction, reads the screen too.
|
|
1284
|
+
if ((observeFlightHook && t.l & CONFIG_VERDICT || laneStageHook && passLane?.folded) && stagedScreen(t)) return e.le;
|
|
1285
|
+
stagedRead(t);
|
|
1286
|
+
return e.ue;
|
|
1287
|
+
}
|
|
1288
|
+
// Rule 3: this read serves the committed value to a render effect outside a
|
|
1289
|
+
// parking flush (decided after the pull below — a pull can join).
|
|
1290
|
+
let s = false;
|
|
1291
|
+
if (t) {
|
|
1292
|
+
if (tracking) {
|
|
1293
|
+
link(e, t);
|
|
1294
|
+
if (o.X) {
|
|
1295
|
+
pullComputed(o, t);
|
|
1296
|
+
// F-6 (#4050): a memo over a verdict reader's value derives from a
|
|
1297
|
+
// display-ahead read — a verdict reader from this pass's end, as if
|
|
1298
|
+
// it probed inline: it reads held values as the screen and shows the
|
|
1299
|
+
// verdict now. Not an effect: a render effect is a frame reader
|
|
1300
|
+
// already, and a user effect's posture is its own reads'.
|
|
1301
|
+
// (A verdict memo exists only with the verdict engine installed.)
|
|
1302
|
+
if (observeFlightHook && !t.i) t.R |= o.l & CONFIG_VERDICT;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
// L2, as in the fast block above (tracked or not). A node born into the
|
|
1306
|
+
// future (uninitialized) has no committed value: every reader derives
|
|
1307
|
+
// from the future there, a render effect included (A29: a stale reader
|
|
1308
|
+
// of it cannot fall back to the committed frame and enters instead).
|
|
1309
|
+
if (e.l & CONFIG_HELD && !(e.l & CONFIG_OVERRIDE) && !(t.l & CONFIG_CHILDREN_FORBIDDEN)) {
|
|
1310
|
+
if (o.m & STATUS_UNINITIALIZED || !frameRead(t, e)) joinPass(t, e); else s = true;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
// Lanes: a lane's node (after the pull — the node is current). NOT_PENDING
|
|
1314
|
+
// falls through: lane work's read of a pending member throws like any.
|
|
1315
|
+
// (CONFIG_OVERRIDE is the lane engine's: no engine, no lane's node.)
|
|
1316
|
+
if (laneReadHook && e.l & CONFIG_OVERRIDE) {
|
|
1317
|
+
const n = laneReadHook(t, e);
|
|
1318
|
+
if (n !== NOT_PENDING) return n;
|
|
1319
|
+
}
|
|
1320
|
+
// A reader in a snapshot scope (a hydration claim) is served the value the
|
|
1321
|
+
// server rendered with: a client write's recompute still in flight does
|
|
1322
|
+
// not pull the claim off the markup it adopts — the reader replays at the
|
|
1323
|
+
// scope's release. An errored node throws below as for any reader.
|
|
1324
|
+
if (snapshotCaptureActive && t && t.l & CONFIG_IN_SNAPSHOT_SCOPE) {
|
|
1325
|
+
const n = e.T?.st;
|
|
1326
|
+
if (n !== undefined && !(o.m & STATUS_ERROR)) {
|
|
1327
|
+
const o = n === NO_SNAPSHOT ? undefined : n;
|
|
1328
|
+
const s = e.ue !== NOT_PENDING ? e.ue : e.le;
|
|
1329
|
+
if (s !== o) t.R |= REACTIVE_SNAPSHOT_STALE;
|
|
1330
|
+
return o;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
// A stale reader of a held flight (rule 3, A15's reveal corollary) is served
|
|
1334
|
+
// the committed value — coherent with the frame, whose inputs are the
|
|
1335
|
+
// committed ones too — and does not go pending on it. So is lane work
|
|
1336
|
+
// reading a flight that is not the lane's own (the parent's, or
|
|
1337
|
+
// mainline's): a lane breaks out of its parent's hold and waits only on
|
|
1338
|
+
// what derives from the guess (A31: "under a lane, a pending node on no
|
|
1339
|
+
// lane serves its committed value").
|
|
1340
|
+
// A verdict reader likewise (CONFIG_VERDICT): a frame reader sees the
|
|
1341
|
+
// screen, and for a flight the screen is the committed value — a render
|
|
1342
|
+
// effect keeps its DOM by throwing, a memo has no DOM and is handed the
|
|
1343
|
+
// value. `[isPending(x), x()]` reads `[true, stale]` in either order (A10)
|
|
1344
|
+
// — unless the flight committed beneath inputs already on screen
|
|
1345
|
+
// (`observeFlight`), or the reader was born into the flight's transaction
|
|
1346
|
+
// (#4010): it has no screen, and waits on the flight with its frame.
|
|
1347
|
+
// (A lane, and a verdict reader, exist only with their engines installed —
|
|
1348
|
+
// the verdict engine imports the lane engine: without it the test folds.)
|
|
1349
|
+
if (laneStageHook && o.m & STATUS_PENDING && !s && !(e.l & CONFIG_OVERRIDE) && !(o.m & STATUS_UNINITIALIZED)) {
|
|
1350
|
+
// A reader with no committed value yet sees the flight pending: a
|
|
1351
|
+
// boundary a lane mounts shows its fallback (A29's boundary exemption).
|
|
1352
|
+
if (passLane !== null && !(t?.m & STATUS_UNINITIALIZED)) s = true; else if (t !== null && t.l & CONFIG_VERDICT) s = observeFlightHook(t, o);
|
|
1353
|
+
}
|
|
1354
|
+
if (o.m & STATUS_PENDING && !s) {
|
|
1355
|
+
// A reader landing on a pending node throws; an untracked read of an
|
|
1356
|
+
// UNINITIALIZED node has no committed value to serve and throws too. A
|
|
1357
|
+
// children-forbidden reader (createTrackedEffect / onSettled) sees the
|
|
1358
|
+
// frame, not the graph (A32): the committed value, and the NotReady only
|
|
1359
|
+
// where there is none (uninitialized).
|
|
1360
|
+
if (t) {
|
|
1361
|
+
if (!(t.l & CONFIG_CHILDREN_FORBIDDEN) || o.m & STATUS_UNINITIALIZED) {
|
|
1362
|
+
// An untracked read of a pending node still re-runs its reader when
|
|
1363
|
+
// the node settles — unless nobody is reading (`spectating`, #3528)
|
|
1364
|
+
// or the reader is the node itself.
|
|
1365
|
+
if (!tracking && !spectating && e !== t) link(e, t);
|
|
1366
|
+
throw o.T?.U;
|
|
1367
|
+
}
|
|
1368
|
+
} else if (o.m & STATUS_UNINITIALIZED) {
|
|
1369
|
+
throw o.T?.U;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
// An errored derive throws for every late reader instead of silently
|
|
1373
|
+
// serving node values (memo parity, #2897 ruling).
|
|
1374
|
+
if (o.X && o.m & STATUS_ERROR) {
|
|
1375
|
+
// Only a genuine reactive re-read may retry an errored async source:
|
|
1376
|
+
// - tracking: owned/tracked scope only (never events / `untrack` / effect side-effect phase)
|
|
1377
|
+
// - owner._time < clock: only on a later cycle than the one the error was found
|
|
1378
|
+
if (tracking && o.ie < clock) {
|
|
1379
|
+
recompute(o);
|
|
1380
|
+
return read(e);
|
|
1381
|
+
}
|
|
1382
|
+
// D-41: a staged error replacing the shown one is this flush's answer
|
|
1383
|
+
// (`stageError`): a reader served the staging (`serve`) throws it, as it
|
|
1384
|
+
// would read a staged value, and stages its own error with it; one
|
|
1385
|
+
// served the screen throws the shown error. Thrown the shown error, a
|
|
1386
|
+
// reader born this flush published it, and its readers' stale error
|
|
1387
|
+
// committed after the new one (#4404).
|
|
1388
|
+
if (!s && e.l & CONFIG_STAGED_ERROR) serve(e, t);
|
|
1389
|
+
throw o.T?.U;
|
|
1390
|
+
}
|
|
1391
|
+
// D-41: an untracked read reads the screen, and so does a frame reader
|
|
1392
|
+
// served the committed value — `serve` hands it that value as it does an
|
|
1393
|
+
// untracked read — a shown error included (`throwShown`).
|
|
1394
|
+
if (s || !t) throwShown(e);
|
|
1395
|
+
const E = serve(e, s ? null : t);
|
|
1396
|
+
if (!t && typeof n.X === "function" && e.l & CONFIG_AUTO_DISPOSE && !(o.m & STATUS_PENDING) && !e.o) {
|
|
1397
|
+
// Deferred, not inline (#3078): an inline unobserved() here made untracked
|
|
1398
|
+
// reads destructive — dispose on this read, full revival recompute on the
|
|
1399
|
+
// next — so consecutive reads could answer differently with no write in
|
|
1400
|
+
// between.
|
|
1401
|
+
// The sweep at flush finalization re-validates and reclaims; schedule()
|
|
1402
|
+
// guarantees that flush happens even if nothing else is queued.
|
|
1403
|
+
dormantNodes.add(e);
|
|
1404
|
+
schedule();
|
|
1405
|
+
}
|
|
1406
|
+
return E;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
/** D-41: a reader served the screen — an untracked read, a frame reader
|
|
1410
|
+
* served the committed value, a verdict reader (`serve`) — reads a shown
|
|
1411
|
+
* error there until the answer that replaces it commits. */ function throwShown(e) {
|
|
1412
|
+
if (e.T?.Te) throw e.T.Te;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* Rule 1, the one slow implementation (DESIGN-CONSOLIDATION move 3b, step
|
|
1417
|
+
* 6c): the value a reader `c` (null = untracked, no pass) is served from
|
|
1418
|
+
* `el`. Called by read()'s slow tail; the fast path (read's fast block) keeps
|
|
1419
|
+
* its trivial ternary by design (perf, see the doc). Arms, in order:
|
|
1420
|
+
* - a node born staged has nothing for an untracked reader (A19 exception 1);
|
|
1421
|
+
* - an unflushed write serves committed and re-runs the reader in the
|
|
1422
|
+
* carrying flush (A28);
|
|
1423
|
+
* - readerSeesCommitted, else the staged value.
|
|
1424
|
+
*/ function serve(e, t) {
|
|
1425
|
+
// A node born staged (recompute) has a staged value and no committed one:
|
|
1426
|
+
// an untracked reader has nothing to serve and holds (A19 exception 1) — a
|
|
1427
|
+
// bookkeeping read (`spectate`) likewise, and a children-forbidden reader
|
|
1428
|
+
// (A32: the frame, which has nothing here).
|
|
1429
|
+
if (e.ue !== NOT_PENDING && e.m & STATUS_UNINITIALIZED && (!t || spectating || t.l & CONFIG_CHILDREN_FORBIDDEN)) throw new NotReadyError(null);
|
|
1430
|
+
const n = t && unflushedStaged ? unflushedValue(e) : NOT_PENDING;
|
|
1431
|
+
if (n !== NOT_PENDING) {
|
|
1432
|
+
markLateLinker(t);
|
|
1433
|
+
return n;
|
|
1434
|
+
}
|
|
1435
|
+
if (readerSeesCommitted(e, t) || observeFlightHook && t.l & CONFIG_VERDICT && !(e.m & STATUS_UNINITIALIZED) && stagedScreen(t) && !throwShown(e)) return e.le;
|
|
1436
|
+
stagedRead(t, e);
|
|
1437
|
+
// D-41: the staged answer is an error (`stageError`).
|
|
1438
|
+
if (e.l & CONFIG_STAGED_ERROR) throw e.ue;
|
|
1439
|
+
return e.ue;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
/** A pass read a staging of this flush: it derives from what the flush may
|
|
1443
|
+
* yet hold (REACTIVE_STAGED_READ — the seam decides). Lane work too (plan sec. 28, a
|
|
1444
|
+
* lane sees the screen plus its own guesses) — a folded lane's only before
|
|
1445
|
+
* the frame has a transaction (after, it reads the screen like a verdict
|
|
1446
|
+
* reader, `stagedScreen` — a list pass never pairs a held length with the
|
|
1447
|
+
* guess's rows, D-34): the staging is the screen if
|
|
1448
|
+
* the frame commits — one pass, the common case — and a held write if it
|
|
1449
|
+
* parks, which the seam repairs (`stagedReaders`): the pass re-derives on
|
|
1450
|
+
* the committed world and its lane's runs wait that round, so the held
|
|
1451
|
+
* write never shows through the lane. A verdict lane's work likewise: the
|
|
1452
|
+
* lane holds verdicts, not the frame's other stagings (#3851) — except a
|
|
1453
|
+
* verdict reader, which answered for itself (the lane seam, lanes.ts). Not
|
|
1454
|
+
* a read of a node born staged at all: it has no committed value to
|
|
1455
|
+
* re-derive on, and a re-run would read the same staging and re-queue every
|
|
1456
|
+
* round (#3969: nor does it make a later lane read a re-derive, `enterLane`). */ function stagedRead(e, t) {
|
|
1457
|
+
if (!(t?.m & STATUS_UNINITIALIZED)) {
|
|
1458
|
+
e.R |= REACTIVE_STAGED_READ;
|
|
1459
|
+
// A verdict reader's too when it is an effect, before the frame has a
|
|
1460
|
+
// transaction (after, `stagedScreen` serves it the screen): the staging
|
|
1461
|
+
// is the frame's read, and the pass re-derives once as a verdict reader
|
|
1462
|
+
// if the flush parks (D-22; D-25: the probe decides the posture in either
|
|
1463
|
+
// read order). A memo's is the frame's derivation either way: a memo is
|
|
1464
|
+
// not a leaf (`frameRead`).
|
|
1465
|
+
if (laneSeamsHook && (passLane !== null || e.i && e.l & CONFIG_VERDICT)) stagedReaders.push(e);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
/** A10 for a staged node: a verdict reader (the pass entered a window) that
|
|
1470
|
+
* also reads a frame's proposal plainly sees the screen — `[latest(q), q()]`
|
|
1471
|
+
* is `[b, a]` — once the frame has a transaction, and re-derives when it
|
|
1472
|
+
* lands: at this very seam if nothing holds it (the stale run is void,
|
|
1473
|
+
* #3322 — one run, on the committed world), or with the hold. Before any
|
|
1474
|
+
* transaction the answer is the seam's and the reader watches it
|
|
1475
|
+
* (verdict.ts). A node born into the future has no screen: its staging is
|
|
1476
|
+
* its only value (A29). Nor has a reader born into the flush's transaction
|
|
1477
|
+
* (`bornInto`, #4010): its branch exists only there. */ function stagedScreen(e) {
|
|
1478
|
+
if (flushTransaction === null || bornInto(e, flushTransaction)) return false;
|
|
1479
|
+
// A transaction only a `latest()` read opened holds nothing: the staging
|
|
1480
|
+
// is the frame's read for a pass in the frame's seat, as it is before the
|
|
1481
|
+
// frame has one, and the seam re-derives the pass once only if the flush
|
|
1482
|
+
// parks (D-22, F-10). Lane work sees the screen (D-37). Not a pass with no
|
|
1483
|
+
// value yet: its first answer is nobody's hold (D-6) and would commit the
|
|
1484
|
+
// staging whether or not the frame parks — it reads the screen, and runs
|
|
1485
|
+
// again at the landing.
|
|
1486
|
+
if (observeFlightHook && flushTransaction === latestOpened && passLane === null && e.l & CONFIG_VERDICT && !(e.m & STATUS_UNINITIALIZED)) return !stagedReaders.push(e);
|
|
1487
|
+
staleReader(e, flushTransaction);
|
|
1488
|
+
return true;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
/** A28 (4) — written inside a recompute that runs OUTSIDE a flush (a
|
|
1492
|
+
* creation-time compute): promoted at that pass's end, visible to the rest of
|
|
1493
|
+
* the block. Cold: only contextual writes reach here. */ function notePromotedWrite(e) {
|
|
1494
|
+
if (globalQueue.Y || e.l & CONFIG_PROMOTED) return;
|
|
1495
|
+
e.l |= CONFIG_PROMOTED;
|
|
1496
|
+
promotedWrites.push(e);
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
/** Cold half of setSignal's snapshot arm (see there), shared with a store
|
|
1500
|
+
* container's structural notify: first write during capture to a plain
|
|
1501
|
+
* signal without a live snapshot records the pre-write value. Only plain
|
|
1502
|
+
* user signals qualify. Computeds reach setSignal from an
|
|
1503
|
+
* async landing (asyncWrite), and a value arriving from async during the pass
|
|
1504
|
+
* REVEALS — the creation-time arm skips pending computeds for the same
|
|
1505
|
+
* reason. Firewall leaves belong to a projection whose compute is the tree's
|
|
1506
|
+
* own work, captured (or deliberately not) at creation. */ function captureWriteSnapshot(e, t) {
|
|
1507
|
+
if (e.l & CONFIG_NO_SNAPSHOT || e.X !== undefined || e.T?.st !== undefined) return;
|
|
1508
|
+
takeSnapshot(e, t);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
function takeSnapshot(e, t) {
|
|
1512
|
+
ext(e).st = t === undefined ? NO_SNAPSHOT : t;
|
|
1513
|
+
e.l |= CONFIG_HAS_SNAPSHOT;
|
|
1514
|
+
snapshotSources.add(e);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
/**
|
|
1518
|
+
* A callback run before every signal or store write lands, while set, with
|
|
1519
|
+
* the written signal or the store proxy whose setter runs. The
|
|
1520
|
+
* server-components client sets it while slot occurrences are left
|
|
1521
|
+
* unmounted after hydration (deferred hydration): a write may be one an
|
|
1522
|
+
* unmounted component would have observed, so the client mounts what is
|
|
1523
|
+
* left before the write applies, and clears the hook. `null` otherwise.
|
|
1524
|
+
*/ let writeHook = null;
|
|
1525
|
+
|
|
1526
|
+
/** @internal */ function setWriteHook(e) {
|
|
1527
|
+
writeHook = e;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
/**
|
|
1531
|
+
* `el`'s comparator verdict (`_equals`), the one guarded call for recompute,
|
|
1532
|
+
* setSignal (user setters and async landings) and the seam's
|
|
1533
|
+
* staged-equals-committed check.
|
|
1534
|
+
* - A comparator that read an unresolved async source has not shown the
|
|
1535
|
+
* values equal (#4111): falsy, a change, whose readers suspend in their own
|
|
1536
|
+
* tracked context.
|
|
1537
|
+
* - Any other throw is an error of `el`'s computation (A3, #2837): routed
|
|
1538
|
+
* through the status path so error boundaries contain it, instead of
|
|
1539
|
+
* unwinding a flush or a setter; answered "same" so nothing commits.
|
|
1540
|
+
* @internal
|
|
1541
|
+
*/ function isSame(e, t, n) {
|
|
1542
|
+
if (e.je === isEqual) return t === n;
|
|
1543
|
+
try {
|
|
1544
|
+
return e.je && e.je(t, n);
|
|
1545
|
+
} catch (t) {
|
|
1546
|
+
if (!(t instanceof NotReadyError)) return notifyStatus(e, STATUS_ERROR, t), true;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
function setSignal(e, t) {
|
|
1551
|
+
if (writeHook !== null) writeHook(e);
|
|
1552
|
+
const n = e.ue === NOT_PENDING ? e.le : e.ue;
|
|
1553
|
+
if (typeof t === "function") t = t(n);
|
|
1554
|
+
// Lanes: a write landing on a guess is its truth (A18 — an async guess's
|
|
1555
|
+
// own landing), judged against the guess; one landing on a lane's
|
|
1556
|
+
// derivation (its flight) is the lane's staging (lanes.ts).
|
|
1557
|
+
if (laneWriteHook && e.l & CONFIG_OVERRIDE) return laneWriteHook(e, t);
|
|
1558
|
+
// L2 (T4) / A34 (1): a write to a node whose staged state is held is a
|
|
1559
|
+
// second proposal for the slot — the same value again or another — and
|
|
1560
|
+
// the two cannot finish at different times: the writer's tick joins the
|
|
1561
|
+
// hold, before the equality gate.
|
|
1562
|
+
if (e.l & CONFIG_HELD) joinFuture(txOf(e));
|
|
1563
|
+
// Uninitialized check first: the first commit has no previous value, so the
|
|
1564
|
+
// user comparator must not run against `undefined` (matches recompute).
|
|
1565
|
+
// D-41: a write over a shown error is a change whatever the last good
|
|
1566
|
+
// value was — the error leaves at its commit (`commitPendingNode`).
|
|
1567
|
+
const o = !!(e.m & STATUS_UNINITIALIZED) || !!e.T?.Te || !isSame(e, n, t);
|
|
1568
|
+
if (!o) return t;
|
|
1569
|
+
// Attribution hook: this committed write is where a re-run chain begins.
|
|
1570
|
+
if (attrHooks !== null) attrHooks.write(e, n, t);
|
|
1571
|
+
// A write during hydration's snapshot capture to a source that has no
|
|
1572
|
+
// snapshot — created BEFORE capture began (module-level state: an identity
|
|
1573
|
+
// minted from onSettled in the pass, a preference read from storage) —
|
|
1574
|
+
// captures the pre-write value now, so the write is held like any other:
|
|
1575
|
+
// in-scope readers keep serving what the server rendered with and replay
|
|
1576
|
+
// at release. Left uncaptured, the write cascades live through a claim
|
|
1577
|
+
// pass whose DOM writes are skipped, and a component rendered later in the
|
|
1578
|
+
// pass reads a value the server never had. Store leaves written here are
|
|
1579
|
+
// plain signals and qualify the same way.
|
|
1580
|
+
if (snapshotCaptureActive) captureWriteSnapshot(e, n);
|
|
1581
|
+
const s = e.ue !== NOT_PENDING;
|
|
1582
|
+
if (!s) queuePendingNode(e);
|
|
1583
|
+
// A28: a held node rewritten outside a flush keeps its flushed staging
|
|
1584
|
+
// for every reader until the flush that carries the rewrite.
|
|
1585
|
+
else if (e.l & CONFIG_HELD && !globalQueue.Y) stashFlushed(e);
|
|
1586
|
+
e.ue = t;
|
|
1587
|
+
if (context !== null) notePromotedWrite(e);
|
|
1588
|
+
// _time is a computed-only slot (§12e): writing it on a signal would fork
|
|
1589
|
+
// the lean shape. Every read site is computed-typed.
|
|
1590
|
+
if (e.X !== undefined) e.ie = clock;
|
|
1591
|
+
// Staged-rewrite fast path (§12d): a re-write to a node whose subscribers
|
|
1592
|
+
// were already walked — and where nothing has recomputed or linked since
|
|
1593
|
+
// (epoch) — re-stages the value and stops. The walk is idempotent (subs
|
|
1594
|
+
// marked, heap entries flag-guarded, effects queued once).
|
|
1595
|
+
if (s && e.Dt === notifyEpoch) return t;
|
|
1596
|
+
insertSubs(e);
|
|
1597
|
+
schedule();
|
|
1598
|
+
return t;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
/**
|
|
1602
|
+
* User-facing setter for the memo form of `createSignal(fn)`. A write applies
|
|
1603
|
+
* first, then derivations re-run (A34 as amended 2026-10-01, rule B; core
|
|
1604
|
+
* R31): the write stages like any other, and a source change — in this flush
|
|
1605
|
+
* or a later one, under a hold or on mainline — re-derives with the staged
|
|
1606
|
+
* write as `prev`. The write never refuses the re-run (the frame-scoped mask
|
|
1607
|
+
* of #3733/#3740 and #2692's "write wins the tick" before it are gone; call
|
|
1608
|
+
* order inside a flush was never observable) and on its own never causes one.
|
|
1609
|
+
* REACTIVE_MANUAL_WRITE only marks the staging as a user proposal — the A34
|
|
1610
|
+
* (1)/(3) discriminator — until a pass re-derives it or the commit lands it.
|
|
1611
|
+
*/ function setMemo(e, t) {
|
|
1612
|
+
// A34 (3), #3612: is `el`'s staging a derivation another transaction holds
|
|
1613
|
+
// — held, not a manual proposal, and the writer not that transaction
|
|
1614
|
+
// (mainline, or a flush joined elsewhere)? A held pass result is nobody's
|
|
1615
|
+
// proposal: a setter reaching it composes on the committed frame it was
|
|
1616
|
+
// written against and becomes `prev` for the hold's re-derivation, instead
|
|
1617
|
+
// of replacing it. A write made under the hold carries the mark and keeps
|
|
1618
|
+
// (1)'s last-write-wins. Only the user setter asks; an async landing
|
|
1619
|
+
// writing a held node is the transaction's own work.
|
|
1620
|
+
const n = e.l & CONFIG_HELD && !(e.R & REACTIVE_MANUAL_WRITE) && txOf(e) !== flushTransaction;
|
|
1621
|
+
// The writer read the committed frame; its updater composes on it.
|
|
1622
|
+
if (n && typeof t === "function") {
|
|
1623
|
+
const n = t(e.le);
|
|
1624
|
+
t = () => n;
|
|
1625
|
+
}
|
|
1626
|
+
// #4110: the write replaces a failed derivation's outcome. It lands without
|
|
1627
|
+
// re-running `_fn`, and nothing else clears STATUS_ERROR, so without this
|
|
1628
|
+
// every later read keeps throwing the error the derivation left behind.
|
|
1629
|
+
// The error stays the display until the write commits (D-41).
|
|
1630
|
+
if (e.m & STATUS_ERROR) {
|
|
1631
|
+
e.T.Te = e.T.U;
|
|
1632
|
+
notifyStatus(e, STATUS_NONE);
|
|
1633
|
+
}
|
|
1634
|
+
t = setSignal(e, t);
|
|
1635
|
+
// A write replaces an error a pass staged (D-41): it is a value again.
|
|
1636
|
+
e.l &= ~CONFIG_STAGED_ERROR;
|
|
1637
|
+
if (n) {
|
|
1638
|
+
// The one write that re-runs: the staging it replaced was derived from
|
|
1639
|
+
// inputs the writer never saw, so the hold re-derives over the write.
|
|
1640
|
+
e.R = e.R & ~REACTIVE_CHECK | REACTIVE_DIRTY;
|
|
1641
|
+
insertIntoHeap(e, dirtyQueue);
|
|
1642
|
+
schedule();
|
|
1643
|
+
} else if (e.ue !== NOT_PENDING) e.R |= REACTIVE_MANUAL_WRITE;
|
|
1644
|
+
return t;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
/**
|
|
1648
|
+
* Executes `fn` with the given `owner` set as the current owner. Any reactive
|
|
1649
|
+
* primitives (`createSignal`, `createMemo`, `createEffect`, `onCleanup`,
|
|
1650
|
+
* `cleanup`, etc.) created inside `fn` are attached to that owner, so they
|
|
1651
|
+
* are disposed when the owner is disposed.
|
|
1652
|
+
*
|
|
1653
|
+
* The classic pattern: capture the current owner with `getOwner()` inside a
|
|
1654
|
+
* component, then re-enter it from a callback (event handler, async resolve,
|
|
1655
|
+
* setTimeout) so disposables created in the callback get cleaned up with the
|
|
1656
|
+
* component.
|
|
1657
|
+
*
|
|
1658
|
+
* @example
|
|
1659
|
+
* ```ts
|
|
1660
|
+
* function delayed<T>(ms: number, fn: () => T) {
|
|
1661
|
+
* const owner = getOwner();
|
|
1662
|
+
* setTimeout(() => runWithOwner(owner, fn), ms);
|
|
1663
|
+
* }
|
|
1664
|
+
* ```
|
|
1665
|
+
*/ function runWithOwner(e, t) {
|
|
1666
|
+
const n = context;
|
|
1667
|
+
const o = tracking;
|
|
1668
|
+
context = e;
|
|
1669
|
+
tracking = false;
|
|
1670
|
+
try {
|
|
1671
|
+
return t();
|
|
1672
|
+
} finally {
|
|
1673
|
+
context = n;
|
|
1674
|
+
tracking = o;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
function staleValues(e, t = true) {
|
|
1679
|
+
try {
|
|
1680
|
+
return e();
|
|
1681
|
+
} finally {}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
/**
|
|
1685
|
+
* Core marking half of `refresh()` (the public wrapper lives in signals.ts —
|
|
1686
|
+
* it validates the target, marks through here, then builds the quiescence
|
|
1687
|
+
* promise on the resolve()/until() effect machinery). Flags the node's next
|
|
1688
|
+
* recompute as a quiet re-ask and schedules it; no-ops for non-derived or
|
|
1689
|
+
* disposed targets.
|
|
1690
|
+
*/ function markRefresh(e) {
|
|
1691
|
+
if (typeof e.X === "function" && !(e.R & REACTIVE_DISPOSED)) {
|
|
1692
|
+
// A manual write never refuses the re-run (A34 rule B): a refresh in the
|
|
1693
|
+
// write's own frame re-asks with the write as `prev`, like any source
|
|
1694
|
+
// change (#3026 generalized).
|
|
1695
|
+
// The re-ask of an answered question (A19 exc. 2): the next pass, going
|
|
1696
|
+
// pending, classifies its flight quiet for the verdict. Not when an
|
|
1697
|
+
// input already changed this tick — that pass asks a new question.
|
|
1698
|
+
e.R = e.R & ~REACTIVE_CHECK | REACTIVE_DIRTY | (e.R & (REACTIVE_DIRTY | REACTIVE_CHECK | REACTIVE_IN_HEAP) ? 0 : REACTIVE_REASK);
|
|
1699
|
+
// A refresh() self-invalidation is a root cause too — the target's next
|
|
1700
|
+
// run has no changed dep to point at, so it points here instead.
|
|
1701
|
+
if (attrHooks !== null) attrHooks.refreshed(e);
|
|
1702
|
+
insertIntoHeap(e, dirtyQueue);
|
|
1703
|
+
schedule();
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
export { bornInto, captureWriteSnapshot, clearSnapshots, computed, context, createEffectNode, creatorPass, creatorReads, effectStatusNotify, ext, isEqual, isSame, joinPass, joinPassAt, 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 };
|