@solidjs/signals 2.0.0-rc.8 → 2.0.0-rc.9
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 +1399 -285
- package/dist/dev.attribution.js +459 -307
- package/dist/dev.js +1864 -435
- package/dist/observe/affects.js +3 -1
- package/dist/observe/attribution.js +7 -1
- package/dist/observe/boundaries.js +209 -154
- package/dist/observe/core/action.js +18 -8
- package/dist/observe/core/async.js +220 -110
- package/dist/observe/core/attribution-costs.js +66 -0
- package/dist/observe/core/attribution-feedback.js +282 -0
- package/dist/observe/core/attribution-hooks.js +23 -1
- package/dist/observe/core/attribution-queries.js +28 -0
- package/dist/observe/core/attribution.js +262 -485
- package/dist/observe/core/constants.js +34 -1
- package/dist/observe/core/context.js +3 -3
- package/dist/observe/core/core.js +867 -367
- package/dist/observe/core/dev.js +78 -17
- package/dist/observe/core/effect.js +67 -51
- package/dist/observe/core/error-hooks.js +71 -0
- package/dist/observe/core/external.js +4 -4
- package/dist/observe/core/graph.js +37 -37
- package/dist/observe/core/heap.js +45 -45
- package/dist/observe/core/invariants.js +2 -0
- package/dist/observe/core/lanes.js +86 -49
- package/dist/observe/core/optimistic.js +242 -95
- package/dist/observe/core/owner.js +98 -84
- package/dist/observe/core/scheduler.js +543 -305
- package/dist/observe/core/verdict.js +247 -129
- package/dist/observe/index.js +7 -3
- package/dist/observe/map.js +126 -124
- package/dist/observe/signals.js +33 -17
- package/dist/observe/store/index.js +2 -0
- package/dist/observe/store/next/optimistic.js +141 -132
- package/dist/observe/store/next/projection.js +34 -21
- package/dist/observe/store/next/reconcile.js +58 -56
- package/dist/observe/store/next/store.js +260 -146
- package/dist/observe/store/store.js +5 -3
- package/dist/observe/store/utils.js +948 -135
- package/dist/prod/attribution.js +26 -17
- package/dist/prod/boundaries.js +128 -76
- package/dist/prod/core/action.js +16 -8
- package/dist/prod/core/async.js +251 -143
- package/dist/prod/core/constants.js +34 -1
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +843 -347
- package/dist/prod/core/dev.js +17 -1
- package/dist/prod/core/effect.js +48 -34
- package/dist/prod/core/error-hooks.js +71 -0
- package/dist/prod/core/external.js +4 -4
- package/dist/prod/core/graph.js +37 -37
- package/dist/prod/core/heap.js +45 -45
- package/dist/prod/core/lanes.js +90 -53
- package/dist/prod/core/optimistic.js +247 -100
- package/dist/prod/core/owner.js +57 -45
- package/dist/prod/core/scheduler.js +543 -305
- package/dist/prod/core/verdict.js +245 -127
- package/dist/prod/index.js +7 -3
- package/dist/prod/map.js +112 -112
- package/dist/prod/signals.js +28 -12
- package/dist/prod/store/next/optimistic.js +135 -128
- package/dist/prod/store/next/projection.js +31 -20
- package/dist/prod/store/next/store.js +325 -252
- package/dist/prod/store/store.js +2 -2
- package/dist/prod/store/utils.js +946 -135
- package/dist/types/attribution.d.ts +7 -2
- package/dist/types/attribution.prod.d.ts +10 -1
- package/dist/types/boundaries.d.ts +10 -1
- package/dist/types/core/action.d.ts +12 -5
- 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 +28 -3
- package/dist/types/core/attribution-queries.d.ts +10 -0
- package/dist/types/core/attribution.d.ts +51 -172
- package/dist/types/core/constants.d.ts +33 -0
- package/dist/types/core/core.d.ts +186 -5
- package/dist/types/core/dev.d.ts +129 -9
- package/dist/types/core/error-hooks.d.ts +71 -0
- package/dist/types/core/index.d.ts +3 -1
- package/dist/types/core/invariants.d.ts +4 -0
- package/dist/types/core/lanes.d.ts +31 -4
- package/dist/types/core/scheduler.d.ts +95 -2
- package/dist/types/core/types.d.ts +3 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/signals.d.ts +8 -0
- package/dist/types/store/index.d.ts +2 -1
- package/dist/types/store/next/optimistic.d.ts +1 -1
- package/dist/types/store/next/store.d.ts +6 -5
- package/dist/types/store/next/target.d.ts +1 -1
- package/dist/types/store/utils.d.ts +177 -6
- package/package.json +1 -1
package/dist/prod/core/core.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { releaseFlightTeardown, handleAsync, clearStatus, parkLoadingWindow, notifyStatus, settlePendingSource, settleErroredDependents } from "./async.js";
|
|
2
2
|
|
|
3
|
-
import { EFFECT_TRACKED, EFFECT_USER, REACTIVE_OPTIMISTIC_DIRTY, CONFIG_OPTIMISTIC,
|
|
3
|
+
import { NOT_PENDING, EFFECT_TRACKED, EFFECT_USER, CONFIG_HELD_CHILDREN, REACTIVE_OPTIMISTIC_DIRTY, CONFIG_OPTIMISTIC, CONFIG_DERIVED_OVERRIDE, STATUS_UNINITIALIZED, STATUS_ERROR, STATUS_PENDING, REACTIVE_REASK, REACTIVE_RECOMPUTING_DEPS, CONFIG_SYNC, CONFIG_HAS_LANE, REACTIVE_MISSED_WAKE, REACTIVE_NONE, REACTIVE_SNAPSHOT_STALE, unwrapOverride, CONFIG_DIRECT_COMMIT, CONFIG_HAS_COMPANIONS, CONFIG_PROMOTED, CONFIG_ADOPTED_UNFLUSHED, CONFIG_CHILDREN_FORBIDDEN, CONFIG_FRESH_READ, CONFIG_INPUTS_PUBLISHED, CONFIG_AUTO_DISPOSE, REACTIVE_LAZY, REACTIVE_DISPOSED, REACTIVE_CHECK, REACTIVE_DIRTY, REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, CONFIG_AUTHORITATIVE_READ, CONFIG_OVERRIDE_SUPERSEDED, CONFIG_AUTHORITATIVE_OBSERVED, defaultContext, CONFIG_HELD_TRUTH, CONFIG_TRANSPARENT, CONFIG_OWNED_WRITE, CONFIG_NO_SNAPSHOT, CONFIG_IN_SNAPSHOT_SCOPE, NO_SNAPSHOT, CONFIG_HAS_SNAPSHOT, REACTIVE_MANUAL_WRITE, CONFIG_FW_CHILDREN, CONFIG_SLOT_NODE, STORE_SNAPSHOT_PROPS } from "./constants.js";
|
|
4
4
|
|
|
5
5
|
import { NotReadyError } from "./error.js";
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ import { trimStaleDeps, link, dormantNodes } from "./graph.js";
|
|
|
8
8
|
|
|
9
9
|
import { deleteFromHeap, queueFor, insertIntoHeapHeight, enqueueSub, markNode, markHeap, insertIntoHeap } from "./heap.js";
|
|
10
10
|
|
|
11
|
-
import { GlobalQueue, bumpNotifyEpoch, activeTransition, globalQueue, clock, currentTransition, insertSubs, queuePendingNode, runInTransition, schedule, notifyEpoch, dirtyQueue, projectionWriteActive, reaskArmed, armReaskClear } from "./scheduler.js";
|
|
11
|
+
import { GlobalQueue, bumpNotifyEpoch, activeTransition, globalQueue, clock, currentTransition, insertSubs, queuePendingNode, wakeParked, heldTrims, runInTransition, schedule, batchJoins, notifyEpoch, dirtyQueue, projectionWriteActive, reaskArmed, armReaskClear } from "./scheduler.js";
|
|
12
12
|
|
|
13
13
|
import "./invariants.js";
|
|
14
14
|
|
|
@@ -22,15 +22,15 @@ import { disposeChildren, markDisposal, inheritId } from "./owner.js";
|
|
|
22
22
|
// render-effect callback stages its value for the next pass, but a wake pushed
|
|
23
23
|
// directly into the user queue ran in the SAME pass, read the old value, and
|
|
24
24
|
// nothing re-notified it when the value landed (#3291).
|
|
25
|
-
GlobalQueue.
|
|
26
|
-
if (e.
|
|
25
|
+
GlobalQueue.We = e => {
|
|
26
|
+
if (e.Le === EFFECT_TRACKED) {
|
|
27
27
|
deleteFromHeap(e, queueFor(e));
|
|
28
|
-
e.
|
|
29
|
-
e.C.enqueue(EFFECT_USER, e.
|
|
28
|
+
e.Ye = true;
|
|
29
|
+
e.C.enqueue(EFFECT_USER, e.Ke);
|
|
30
30
|
} else recompute(e);
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
GlobalQueue.
|
|
33
|
+
GlobalQueue.Be = disposeChildren;
|
|
34
34
|
|
|
35
35
|
let tracking = false;
|
|
36
36
|
|
|
@@ -62,8 +62,8 @@ let snapshotSources = null;
|
|
|
62
62
|
|
|
63
63
|
function ownerInSnapshotScope(e) {
|
|
64
64
|
while (e) {
|
|
65
|
-
if (e.
|
|
66
|
-
e = e.
|
|
65
|
+
if (e.Je) return true;
|
|
66
|
+
e = e._parent;
|
|
67
67
|
}
|
|
68
68
|
return false;
|
|
69
69
|
}
|
|
@@ -74,44 +74,49 @@ function setSnapshotCapture(e) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
function markSnapshotScope(e) {
|
|
77
|
-
e.
|
|
77
|
+
e.Je = true;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
function releaseSnapshotScope(e) {
|
|
81
|
-
e.
|
|
81
|
+
e.Je = false;
|
|
82
82
|
releaseSubtree(e);
|
|
83
83
|
schedule();
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
function releaseSubtree(e) {
|
|
87
|
-
let t = e.
|
|
87
|
+
let t = e.Xe;
|
|
88
88
|
while (t) {
|
|
89
|
-
if (t.
|
|
90
|
-
t = t
|
|
89
|
+
if (t.Je) {
|
|
90
|
+
t = t.$e;
|
|
91
91
|
continue;
|
|
92
92
|
}
|
|
93
|
-
if (t.
|
|
93
|
+
if (t.ce) {
|
|
94
94
|
const e = t;
|
|
95
95
|
e.T &= ~CONFIG_IN_SNAPSHOT_SCOPE;
|
|
96
|
-
if (e.
|
|
97
|
-
e.
|
|
98
|
-
e.
|
|
99
|
-
if (dirtyQueue.
|
|
96
|
+
if (e.ue & REACTIVE_SNAPSHOT_STALE) {
|
|
97
|
+
e.ue &= ~REACTIVE_SNAPSHOT_STALE;
|
|
98
|
+
e.ue |= REACTIVE_DIRTY;
|
|
99
|
+
if (dirtyQueue.et > e.tt) dirtyQueue.et = e.tt;
|
|
100
100
|
insertIntoHeap(e, dirtyQueue);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
releaseSubtree(t);
|
|
104
|
-
t = t
|
|
104
|
+
t = t.$e;
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
function clearSnapshots() {
|
|
109
109
|
if (snapshotSources) {
|
|
110
110
|
for (const e of snapshotSources) {
|
|
111
|
-
|
|
111
|
+
// The extension is a fixed-shape object with `_snapshotValue`
|
|
112
|
+
// pre-initialized to undefined (see ext()), and every reader tests
|
|
113
|
+
// `!== undefined` — assign, don't `delete`: deleting a field pushes the
|
|
114
|
+
// object to dictionary mode for every later read of every field.
|
|
115
|
+
const t = e.o;
|
|
116
|
+
if (t != null) t.nt = undefined;
|
|
112
117
|
// StoreNode targets share one pre-initialized hidden class (see
|
|
113
|
-
// createStoreProxy) —
|
|
114
|
-
//
|
|
118
|
+
// createStoreProxy) — same rule, and only when present so signal-node
|
|
119
|
+
// sources don't grow the field.
|
|
115
120
|
if (e[STORE_SNAPSHOT_PROPS] !== undefined) e[STORE_SNAPSHOT_PROPS] = undefined;
|
|
116
121
|
}
|
|
117
122
|
snapshotSources = null;
|
|
@@ -122,12 +127,16 @@ function clearSnapshots() {
|
|
|
122
127
|
function recompute(e, t = false) {
|
|
123
128
|
// §12d: any recompute can clean a marked subscriber — invalidate skips.
|
|
124
129
|
bumpNotifyEpoch();
|
|
125
|
-
const n = e.
|
|
130
|
+
const n = e.Le;
|
|
126
131
|
if (!t) {
|
|
127
|
-
|
|
132
|
+
// A stamped memo re-enters its hold: its value is that transaction's work.
|
|
133
|
+
// An effect's pass belongs to whatever dirtied it (A15 corollary: effects
|
|
134
|
+
// don't entangle parallel transactions); it joins its stamp only when the
|
|
135
|
+
// pass observes the held flight — queue notification (#3407).
|
|
136
|
+
if (e.Ge && !n && activeTransition !== e.Ge) globalQueue.initTransition(e.Ge);
|
|
128
137
|
deleteFromHeap(e, queueFor(e));
|
|
129
138
|
if (e.o !== null) {
|
|
130
|
-
e.o.
|
|
139
|
+
e.o.Re = null;
|
|
131
140
|
// Supersede is where an iterator flight dies (#3122): close it now.
|
|
132
141
|
// Its cleanup(close) registration may sit in a zombie-deferred
|
|
133
142
|
// disposal list that a held transition only drains when the
|
|
@@ -135,51 +144,66 @@ function recompute(e, t = false) {
|
|
|
135
144
|
// work that replaced it. Idempotent with the cleanup-channel close.
|
|
136
145
|
releaseFlightTeardown(e);
|
|
137
146
|
}
|
|
138
|
-
// Tracked effects run after finalizePureQueue, so dispose immediately
|
|
139
|
-
|
|
147
|
+
// Tracked effects run after finalizePureQueue, so dispose immediately
|
|
148
|
+
// instead of deferring. Children built by an uncommitted recompute
|
|
149
|
+
// (CONFIG_HELD_CHILDREN) die immediately too: no frame ever showed them.
|
|
150
|
+
// Everything else is the committed frame's and is deferred as zombies
|
|
151
|
+
// until this node's commit — a transaction-owned node included (#3404):
|
|
152
|
+
// a parked node's children predate the hold, and tearing them down when
|
|
153
|
+
// the source lands ran cleanups before the transaction's atomic reveal.
|
|
154
|
+
if (n === EFFECT_TRACKED || e.T & CONFIG_HELD_CHILDREN) disposeChildren(e); else if (e.Xe !== null || e.ke !== null) {
|
|
140
155
|
markDisposal(e);
|
|
141
156
|
const t = ext(e);
|
|
142
|
-
t.
|
|
143
|
-
t.
|
|
144
|
-
e.
|
|
145
|
-
e.
|
|
146
|
-
e
|
|
157
|
+
t.it = e.ke;
|
|
158
|
+
t.lt = e.Xe;
|
|
159
|
+
e.ke = null;
|
|
160
|
+
e.Xe = null;
|
|
161
|
+
e.ut = 0;
|
|
147
162
|
} else ;
|
|
148
163
|
}
|
|
149
|
-
let i = !!(e.
|
|
150
|
-
|
|
164
|
+
let i = !!(e.ue & REACTIVE_OPTIMISTIC_DIRTY);
|
|
165
|
+
// A derived override (lanes stage, #3479) is override-covered like a written
|
|
166
|
+
// one: a plain pass over it — its source superseded (A18) — stages the truth
|
|
167
|
+
// and supersedes the override through the sync twin below.
|
|
168
|
+
const l = (e.T & (CONFIG_OPTIMISTIC | CONFIG_DERIVED_OVERRIDE)) !== 0 && e.o?.Ce !== NOT_PENDING && e.o?.Ce !== undefined;
|
|
151
169
|
const u = !!(e.S & STATUS_UNINITIALIZED);
|
|
152
170
|
// Capture both error and pending status before the compute clears them.
|
|
153
171
|
// A conditional can drop its pending source and recover to an unchanged
|
|
154
172
|
// value, leaving blocked dependents outside that source’s settle walk.
|
|
155
173
|
const o = e.S & STATUS_ERROR ? e.o?._ : undefined;
|
|
156
|
-
const s = e.S & STATUS_PENDING
|
|
174
|
+
const s = (e.S & STATUS_PENDING) !== 0;
|
|
175
|
+
const r = s ? e.o?.ae : undefined;
|
|
157
176
|
// Pending SOURCE-hood, captured before the compute clears status: a node
|
|
158
177
|
// whose own flight parked dependents self-registers in _pendingSources
|
|
159
178
|
// (notifyStatus, isSource). If this recompute supersedes that flight and
|
|
160
179
|
// settles synchronously, those dependents settle HERE — asyncWrite's
|
|
161
180
|
// settlePendingSource walk never runs for a landing that was preempted
|
|
162
181
|
// (#3181).
|
|
163
|
-
const a = e.o?.
|
|
182
|
+
const a = e.o?.ae?.has(e);
|
|
164
183
|
// Re-ask classification lives in the verdict module; capture the flag before
|
|
165
184
|
// the recompute wipes _flags below.
|
|
166
|
-
const
|
|
185
|
+
const c = (e.ue & REACTIVE_REASK) !== 0;
|
|
167
186
|
// Captured before the compute clears it on a sync landing: if that landing
|
|
168
187
|
// is transition-held below, the window must stay open until the hold
|
|
169
188
|
// commits (commitPendingNode) — a closed window plus a held value reads as
|
|
170
189
|
// a pending frame to live observers of the verdict (#2990).
|
|
171
|
-
const
|
|
172
|
-
|
|
190
|
+
const f = e.ge;
|
|
191
|
+
// Creation-time A29 (see enterStagedRead): a pass outside a flush that is
|
|
192
|
+
// served a live transaction's staged value records the transaction here
|
|
193
|
+
// and is staged INTO it below — "born held" — instead of committing.
|
|
194
|
+
const _ = stagedEntry;
|
|
195
|
+
stagedEntry = null;
|
|
196
|
+
const N = context;
|
|
173
197
|
context = e;
|
|
174
|
-
e.
|
|
175
|
-
e.
|
|
176
|
-
e.
|
|
177
|
-
e.
|
|
178
|
-
let
|
|
179
|
-
let E = e.
|
|
198
|
+
e.ot = null;
|
|
199
|
+
e.Ze++;
|
|
200
|
+
e.ue = REACTIVE_RECOMPUTING_DEPS;
|
|
201
|
+
e.Pe = clock;
|
|
202
|
+
let d = e.ve === NOT_PENDING ? e.Qe : e.ve;
|
|
203
|
+
let E = e.tt;
|
|
180
204
|
let I = false;
|
|
181
|
-
let
|
|
182
|
-
let
|
|
205
|
+
let T = tracking;
|
|
206
|
+
let S = currentOptimisticLane;
|
|
183
207
|
tracking = true;
|
|
184
208
|
// A computed's fn establishes its OWN dependencies, so it must never run
|
|
185
209
|
// inside a latest() read window: read() short-circuits through the
|
|
@@ -187,74 +211,102 @@ function recompute(e, t = false) {
|
|
|
187
211
|
// computed) inside latest(fn) came out permanently dependency-less (#2926).
|
|
188
212
|
// latestRead() already suspends the flag for its pull-recomputes; this
|
|
189
213
|
// covers creation-time computes and flushes that run inside the window.
|
|
190
|
-
const
|
|
214
|
+
const O = latestReadActive;
|
|
191
215
|
latestReadActive = false;
|
|
216
|
+
// A memo computes under its OWN lane posture, never the puller's (A31,
|
|
217
|
+
// #3442): its value is one shared slot every reader sees, so a pull from a
|
|
218
|
+
// lane-carrying reader (a probe effect on its companion lane pulling a sync
|
|
219
|
+
// memo) must not run it with that lane's read carve-outs — under a lane, a
|
|
220
|
+
// pending node on no lane serves its committed value instead of throwing,
|
|
221
|
+
// and the memo then published a stale "settled" value, dropped its
|
|
222
|
+
// pending status, and stopped holding its transaction. The branches below
|
|
223
|
+
// re-establish the posture the memo itself owns (OPT-dirty, or adopted
|
|
224
|
+
// through its deps). Effects keep the ambient lane: their runs are the
|
|
225
|
+
// lane's own view.
|
|
226
|
+
if (!n) currentOptimisticLane = null;
|
|
192
227
|
// Lane posture lives with the engine: OPTIMISTIC_DIRTY is only ever set by
|
|
193
228
|
// engine-driven paths, and _optimisticNodes is only pushed by
|
|
194
229
|
// _optimisticWrite, so the hook is installed whenever either gate holds.
|
|
195
230
|
if (i) {
|
|
196
|
-
const t = GlobalQueue.
|
|
231
|
+
const t = GlobalQueue.st(e, true);
|
|
197
232
|
if (t) currentOptimisticLane = t;
|
|
198
233
|
// `false` = wake-only lane demotion: recompute plain so a mid-tick
|
|
199
234
|
// latest()/isPending() pull stages instead of direct-committing (#3009).
|
|
200
235
|
// The predicate lives with the engine (recomputeLane).
|
|
201
236
|
else if (t === false) i = false;
|
|
202
|
-
} else if (
|
|
237
|
+
} else if (e.T & CONFIG_DERIVED_OVERRIDE) {
|
|
238
|
+
// Lanes stage (#3479): a pass over a live lane member carrying a derived
|
|
239
|
+
// override is the lane's pass whatever channel dirtied it (a boundary
|
|
240
|
+
// reset, an unrelated sync write) — its inputs serve the lane's view, so
|
|
241
|
+
// its result is the lane's and belongs in the override slot. Run plain,
|
|
242
|
+
// A18's sync twin below read that re-derived lane view as a differing
|
|
243
|
+
// truth (a fresh array), superseded the override and demoted the lane;
|
|
244
|
+
// the lane's next pass then dropped the staged "truth" and left the node
|
|
245
|
+
// flagged superseded with nothing to serve (fuzzer latest-1 #2481). A
|
|
246
|
+
// demoted node resolves no lane and stays plain: its pass IS the truth.
|
|
247
|
+
const t = GlobalQueue.st(e, true);
|
|
248
|
+
if (t) {
|
|
249
|
+
i = true;
|
|
250
|
+
currentOptimisticLane = t;
|
|
251
|
+
}
|
|
252
|
+
} else if (activeTransition && !t && activeTransition.rt.length) {
|
|
203
253
|
// Lane adoption: parent-deeper-than-owned-child can run before its OPT-dirty
|
|
204
254
|
// child propagates. Walk deps once and inherit the OPT lane so this node
|
|
205
255
|
// recomputes under the right posture and propagates correctly.
|
|
206
|
-
const t = GlobalQueue.
|
|
256
|
+
const t = GlobalQueue.st(e, false);
|
|
207
257
|
if (t) {
|
|
208
258
|
i = true;
|
|
209
259
|
currentOptimisticLane = t;
|
|
210
260
|
}
|
|
211
261
|
}
|
|
212
|
-
const
|
|
262
|
+
const C = n && n !== EFFECT_USER;
|
|
213
263
|
const A = stale;
|
|
214
|
-
if (
|
|
264
|
+
if (C) stale = true;
|
|
215
265
|
// An effect recorded for this transaction's commit replay (it once read a
|
|
216
266
|
// node the transaction held and showed the committed value) and now
|
|
217
267
|
// recomputing UNDER the transaction sees its staged view: the value this
|
|
218
268
|
// run produces is applied by the commit itself, and the stale recording
|
|
219
269
|
// would publish the frame a second time. Drop it; the reads below re-record
|
|
220
270
|
// if they are served the committed view again (a lane's committed read).
|
|
221
|
-
if (n && activeTransition !== null && activeTransition.
|
|
271
|
+
if (n && activeTransition !== null && activeTransition.ct.size) activeTransition.ct.delete(e);
|
|
222
272
|
try {
|
|
223
273
|
if (!false && e.T & CONFIG_SYNC) {
|
|
224
|
-
|
|
225
|
-
if (e.o !== null) e.o.
|
|
226
|
-
e.
|
|
274
|
+
d = e.ce(d);
|
|
275
|
+
if (e.o !== null) e.o.Re = null;
|
|
276
|
+
e.ge = false;
|
|
227
277
|
} else {
|
|
228
278
|
// Snapshot `_inFlight` so we can detect whether `_fn` self-registered an async
|
|
229
279
|
// subscription (e.g. `createProjection` calls `handleAsync` from inside its body
|
|
230
280
|
// with a setter callback). In that case, the outer `handleAsync` call below would
|
|
231
281
|
// clobber the fresh subscription, so we skip it and let the internally-registered
|
|
232
282
|
// iteration drive updates.
|
|
233
|
-
const t = e.o?.
|
|
234
|
-
const n = e.
|
|
283
|
+
const t = e.o?.Re;
|
|
284
|
+
const n = e.ce(d);
|
|
235
285
|
const i = typeof n === "object" && n !== null;
|
|
236
|
-
const l = e.o?.
|
|
237
|
-
|
|
286
|
+
const l = e.o?.Re !== t;
|
|
287
|
+
d = l || !i ? n : handleAsync(e, n);
|
|
238
288
|
if (!l && !i) {
|
|
239
|
-
if (e.o !== null) e.o.
|
|
289
|
+
if (e.o !== null) e.o.Re = null;
|
|
240
290
|
// A sync (non-object) return is the first real answer; async-shaped
|
|
241
291
|
// results clear inside handleAsync at their own landing points, and a
|
|
242
292
|
// self-registered flight (inFlightChanged — projections) clears when
|
|
243
293
|
// its internal handleAsync lands.
|
|
244
|
-
e.
|
|
294
|
+
e.ge = false;
|
|
245
295
|
}
|
|
246
296
|
}
|
|
247
297
|
// On a status-free node clearStatus is a guaranteed no-op: every field
|
|
248
298
|
// its body gates on is either _statusFlags or lives in the cold
|
|
249
299
|
// extension — no extension, no status to clear. (_x from an unrelated
|
|
250
300
|
// installer just makes clearStatus a cheap re-verified no-op.)
|
|
251
|
-
|
|
301
|
+
// A node born held keeps STATUS_UNINITIALIZED until its transaction
|
|
302
|
+
// commits: it has no committed value, and read() holds its readers.
|
|
303
|
+
if (e.S !== 0 || e.o !== null) clearStatus(e, t && stagedEntry === null);
|
|
252
304
|
// _optimisticLane is only ever assigned by engine paths (CONFIG_HAS_LANE
|
|
253
305
|
// is their sticky presence mark).
|
|
254
|
-
if (e.T & CONFIG_HAS_LANE && e.o?.
|
|
306
|
+
if (e.T & CONFIG_HAS_LANE && e.o?.Ue) GlobalQueue.ft(e);
|
|
255
307
|
} catch (t) {
|
|
256
308
|
const n = t instanceof NotReadyError;
|
|
257
|
-
if (n && e.
|
|
309
|
+
if (n && e.ge) {
|
|
258
310
|
// Loading window with an unready sync dependency: register for the
|
|
259
311
|
// source's settle (the settlePendingSource walk runs off
|
|
260
312
|
// _pendingSources + _blocked alone) but take NO read-visible pending
|
|
@@ -266,43 +318,56 @@ function recompute(e, t = false) {
|
|
|
266
318
|
} else {
|
|
267
319
|
// Track pending async in the lane (not the lane's source — it creates the lane
|
|
268
320
|
// but doesn't belong to it). Set lane BEFORE notifyStatus for downstream propagation.
|
|
269
|
-
if (n && currentOptimisticLane) GlobalQueue.
|
|
321
|
+
if (n && currentOptimisticLane) GlobalQueue._t(e);
|
|
270
322
|
let i = false;
|
|
271
323
|
if (n) {
|
|
272
|
-
ext(e).
|
|
273
|
-
if (GlobalQueue.
|
|
324
|
+
ext(e).Ie = true;
|
|
325
|
+
if (GlobalQueue.Nt !== null) i = GlobalQueue.Nt(e, c);
|
|
274
326
|
}
|
|
275
|
-
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.o?.
|
|
327
|
+
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.o?.Ue : undefined);
|
|
276
328
|
// The replacement source is fully propagated now. If no new flight
|
|
277
329
|
// re-owned self, retire the superseded flight and its dependent copies.
|
|
278
|
-
if (n && a && !e.o?.
|
|
330
|
+
if (n && a && !e.o?.Re) settlePendingSource(e);
|
|
331
|
+
// A re-park drops what the earlier pass carried (#3456): a source this
|
|
332
|
+
// pass no longer reaches — its branch switched, or a fresh flight
|
|
333
|
+
// replaced the inputs' pending with its own — stays copied onto
|
|
334
|
+
// dependents that reached it only through here, and its landing walk
|
|
335
|
+
// stops at this node (nothing left to retire) before it finds them. A
|
|
336
|
+
// dependent then waits forever on a flight it has no path to. The
|
|
337
|
+
// re-park twin of the unchanged-value recovery sweep below; dependents
|
|
338
|
+
// with another path keep the source (retryReaches).
|
|
339
|
+
if (n && r) for (const t of r) if (t !== e && !e.o?.ae?.has(t)) settlePendingSource(e, t);
|
|
279
340
|
if (i) GlobalQueue.k(e);
|
|
280
341
|
}
|
|
281
342
|
} finally {
|
|
282
|
-
tracking =
|
|
283
|
-
latestReadActive =
|
|
284
|
-
if (
|
|
343
|
+
tracking = T;
|
|
344
|
+
latestReadActive = O;
|
|
345
|
+
if (C) stale = A;
|
|
285
346
|
// Consume the missed-wake latch (#3037, set by insertSubs): a dep write
|
|
286
347
|
// landed beneath this pass on a link it had already validated. The wipe
|
|
287
348
|
// below must not key off DIRTY/CHECK — the read-time pull protocol
|
|
288
349
|
// (markNode(c) in read()) marks the running node as part of ordinary
|
|
289
350
|
// bookkeeping, and those marks are correctly discarded here.
|
|
290
|
-
I = (e.
|
|
291
|
-
e.
|
|
292
|
-
context =
|
|
351
|
+
I = (e.ue & REACTIVE_MISSED_WAKE) !== 0;
|
|
352
|
+
e.ue = REACTIVE_NONE | (t ? e.ue & REACTIVE_SNAPSHOT_STALE : 0);
|
|
353
|
+
context = N;
|
|
293
354
|
}
|
|
355
|
+
// The cast re-widens: TS narrowed `stagedEntry` to `null` at the reset
|
|
356
|
+
// above and does not invalidate that across the compute call that
|
|
357
|
+
// `enterStagedRead` runs under. No emitted code.
|
|
358
|
+
const p = stagedEntry;
|
|
359
|
+
stagedEntry = _;
|
|
294
360
|
if (!e.o?._) {
|
|
295
|
-
trimStaleDeps(e);
|
|
296
361
|
// INV-11 (#3330): the equality gate compares against the slot this run
|
|
297
362
|
// publishes to. An override-covered node publishes the override; a lane
|
|
298
363
|
// recompute (OPT-dirty) direct-commits `_value` — the lane's own reveal
|
|
299
364
|
// schedule — so a transaction-held `_pendingValue` that already equals
|
|
300
365
|
// the new result is not "unchanged": the screen still shows `_value`.
|
|
301
366
|
// Only a transaction-staged run compares against `_pendingValue`.
|
|
302
|
-
|
|
303
|
-
let
|
|
367
|
+
const s = l ? unwrapOverride(e.o?.Ce) : i || e.ve === NOT_PENDING ? e.Qe : e.ve;
|
|
368
|
+
let c = false;
|
|
304
369
|
try {
|
|
305
|
-
|
|
370
|
+
c = !n && u || !e.Fe || !e.Fe(s, d);
|
|
306
371
|
} catch (t) {
|
|
307
372
|
// A throwing user comparator is an error of this node's computation.
|
|
308
373
|
// Route it through the same status path as a compute-phase throw so
|
|
@@ -315,12 +380,12 @@ function recompute(e, t = false) {
|
|
|
315
380
|
// its runner — happen here instead. `!create` matches the previous `initialized`
|
|
316
381
|
// gate: the explicit recompute(node, true) inside effect() does not enqueue, so
|
|
317
382
|
// effect() can call its runner synchronously for the first run.
|
|
318
|
-
if (n &&
|
|
319
|
-
e.
|
|
383
|
+
if (n && c) {
|
|
384
|
+
e.Ye = !e.o?._;
|
|
320
385
|
// Reuse one bound runner per effect — runEffect no-ops on a stale
|
|
321
386
|
// `_modified`, so re-enqueueing the same function is harmless.
|
|
322
387
|
if (!t) {
|
|
323
|
-
e.C.enqueue(n, e.
|
|
388
|
+
e.C.enqueue(n, e.dt ??= GlobalQueue.Et.bind(null, e));
|
|
324
389
|
// Contested effect (#3322). Effects don't entangle transactions (a
|
|
325
390
|
// shared effect is not shared state), yet they have one value slot:
|
|
326
391
|
// when this write commits under a different transaction
|
|
@@ -334,19 +399,19 @@ function recompute(e, t = false) {
|
|
|
334
399
|
// too, for the same reason, unless it is mainline — mainline publishes
|
|
335
400
|
// what it computes. A previous owner that was mainline, or already
|
|
336
401
|
// committed, left nothing to protect.
|
|
337
|
-
let t = e.
|
|
402
|
+
let t = e.It;
|
|
338
403
|
if (t !== activeTransition) {
|
|
339
|
-
e.
|
|
340
|
-
if (t !== null && (t = currentTransition(t)) !== activeTransition && !t.
|
|
341
|
-
(t.
|
|
342
|
-
if (activeTransition !== null) (activeTransition.
|
|
404
|
+
e.It = activeTransition;
|
|
405
|
+
if (t !== null && (t = currentTransition(t)) !== activeTransition && !t.Tt) {
|
|
406
|
+
(t.St ??= []).push(e);
|
|
407
|
+
if (activeTransition !== null) (activeTransition.St ??= []).push(e);
|
|
343
408
|
}
|
|
344
409
|
}
|
|
345
410
|
}
|
|
346
411
|
}
|
|
347
|
-
if (e.o?._) ; else if (
|
|
348
|
-
const u = l ? e.o?.
|
|
349
|
-
if (t ||
|
|
412
|
+
if (e.o?._) ; else if (c) {
|
|
413
|
+
const u = l ? e.o?.Ce : undefined;
|
|
414
|
+
if (t && p === null ||
|
|
350
415
|
// Plain sync flush (no transition on either side) commits effect
|
|
351
416
|
// values directly — the pending round-trip (queuePendingNode +
|
|
352
417
|
// commitPendingNodes) exists to sequence transition reveals, and
|
|
@@ -355,31 +420,53 @@ function recompute(e, t = false) {
|
|
|
355
420
|
// their own held transition: their applies deliver on a microtask,
|
|
356
421
|
// not the stashed queues, so a staged value would hand the immediate
|
|
357
422
|
// apply stale state — see CONFIG_DIRECT_COMMIT.
|
|
358
|
-
n && (activeTransition !== e.
|
|
359
|
-
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
423
|
+
n && p === null && (activeTransition !== e.Ge || activeTransition === null || e.T & CONFIG_DIRECT_COMMIT) || i) {
|
|
424
|
+
// Lanes stage (#3479): a lane pass on a memo publishes its speculative
|
|
425
|
+
// result as an OVERRIDE — `_value` stays the committed truth, so a
|
|
426
|
+
// reader off the lane (A17's committed view, #3460) sees a whole
|
|
427
|
+
// committed frame: the source's shadow and its derivations together,
|
|
428
|
+
// never a committed shadow beside a speculative memo. The lane's own
|
|
429
|
+
// readers and untracked reads see the override (A17); the revert
|
|
430
|
+
// drops it and re-derives (resolveOptimisticNodes). Effects keep the
|
|
431
|
+
// direct commit — their `_value` is a run result the lane's queues
|
|
432
|
+
// already sequence. Either way the lane pass drops any superseded
|
|
433
|
+
// older hold so its queued commit can't clobber the fresh frame: a
|
|
434
|
+
// hold staged on an earlier, lane-free pass of the SAME transaction
|
|
435
|
+
// is superseded — left in place, the commit published the older
|
|
436
|
+
// frame over the fresh one (#3377). A reversion pass (OPT-dirty with
|
|
437
|
+
// no lane — the override dropped) commits directly: it IS the truth.
|
|
438
|
+
if (i && !n && currentOptimisticLane !== null) GlobalQueue.Ve(e, d, currentOptimisticLane); else e.Qe = d;
|
|
439
|
+
if (i) e.ve = NOT_PENDING;
|
|
368
440
|
} else {
|
|
369
|
-
e.
|
|
441
|
+
e.ve = d;
|
|
442
|
+
if (p !== null) {
|
|
443
|
+
// Born held: the pass ran from mainline and derived from this
|
|
444
|
+
// transaction's staged world (enterStagedRead). Its value is the
|
|
445
|
+
// transaction's — staged into it directly, stamped, and committed
|
|
446
|
+
// with it; mainline's batch never sees it. A born-held effect
|
|
447
|
+
// skips its synchronous first run (effect()) and is replayed by
|
|
448
|
+
// the commit like a stale reader that showed the committed frame.
|
|
449
|
+
e.Ge = p;
|
|
450
|
+
p.Ot.push(e);
|
|
451
|
+
if (n) p.ct.add(e);
|
|
452
|
+
}
|
|
370
453
|
// A window landing that gets held re-opens the window until the hold
|
|
371
454
|
// commits — the verdict's held-value branch is window-gated (#2990).
|
|
372
|
-
if (
|
|
373
|
-
//
|
|
455
|
+
if (f) e.ge = true;
|
|
456
|
+
// A staged sync recompute is a write path like setSignal/asyncWrite,
|
|
374
457
|
// so sync derivations of held sources stay visible to isPending()/latest()
|
|
375
458
|
// (#2831). Both companion writes are transition-scoped (optimistic) and
|
|
376
|
-
// auto-revert/re-derive at commit.
|
|
377
|
-
//
|
|
378
|
-
|
|
459
|
+
// auto-revert/re-derive at commit. Not gated on an active transition:
|
|
460
|
+
// a plain flush can still become a hold after this recompute — an
|
|
461
|
+
// async memo downstream pends and the batch is adopted into a
|
|
462
|
+
// transaction (scheduler.enterTransition) — and nothing re-derives
|
|
463
|
+
// the companion at adoption, so a memo held that way read
|
|
464
|
+
// isPending() false while its held source read true (#3413).
|
|
465
|
+
if (e.T & CONFIG_HAS_COMPANIONS && GlobalQueue.me !== null) GlobalQueue.me(e, d);
|
|
379
466
|
}
|
|
380
467
|
// insertSubs only walks _subs (no scheduling of its own), so a
|
|
381
468
|
// subscriber-less node has nothing to notify.
|
|
382
|
-
if (e.u !== null && (!l || i || e.o?.
|
|
469
|
+
if (e.u !== null && (!l || i || e.o?.Ce !== u)) insertSubs(e, i || l);
|
|
383
470
|
// A18 supersession, sync twin of asyncWrite's override branch (#3331):
|
|
384
471
|
// this pass published truth that differs from the override (the gate
|
|
385
472
|
// compared against it) into the transaction-held slot. Whether the
|
|
@@ -391,14 +478,14 @@ function recompute(e, t = false) {
|
|
|
391
478
|
// in this same tick (optimisticWrite stamps `_overrideTime`) is the
|
|
392
479
|
// newer intent over whatever this pass derives from the batch's staged
|
|
393
480
|
// inputs, and is not superseded by it.
|
|
394
|
-
else if (l && !i && e.o.
|
|
481
|
+
else if (l && !i && e.o.Ct !== clock) GlobalQueue.we(e, d);
|
|
395
482
|
} else if (l) {
|
|
396
483
|
// Unchanged value (equals the override) recomputed while the override
|
|
397
484
|
// is active: _value may still be stale, so hold the authoritative value
|
|
398
485
|
// for commit on its own transition's schedule — invisibly (A17/A18).
|
|
399
|
-
if (e.
|
|
400
|
-
e.
|
|
401
|
-
if (
|
|
486
|
+
if (e.ve === NOT_PENDING) queuePendingNode(e);
|
|
487
|
+
e.ve = d;
|
|
488
|
+
if (f) e.ge = true;
|
|
402
489
|
// see the held branch above (#2990)
|
|
403
490
|
// A confirmation after a supersession restores the override as the
|
|
404
491
|
// graph's value and notifies; a plain confirmation wakes only an
|
|
@@ -407,10 +494,10 @@ function recompute(e, t = false) {
|
|
|
407
494
|
// equal to the override" is exactly the acknowledgment it waits for;
|
|
408
495
|
// A17 silence holds for every ordinary subscriber. Both live in the
|
|
409
496
|
// engine's supersedeOverride.
|
|
410
|
-
GlobalQueue.
|
|
411
|
-
} else if (e.
|
|
412
|
-
for (let t = e.u; t !== null; t = t.
|
|
413
|
-
insertIntoHeapHeight(t.
|
|
497
|
+
GlobalQueue.we(e, d);
|
|
498
|
+
} else if (e.tt != E) {
|
|
499
|
+
for (let t = e.u; t !== null; t = t.Ne) {
|
|
500
|
+
insertIntoHeapHeight(t._e, queueFor(t._e));
|
|
414
501
|
}
|
|
415
502
|
}
|
|
416
503
|
// Silent recovery: errored → unchanged value fires no notification, but
|
|
@@ -418,26 +505,92 @@ function recompute(e, t = false) {
|
|
|
418
505
|
// in an errored run and may sit on stale commits (#2949). Changed-value
|
|
419
506
|
// recoveries ride insertSubs above; a comparator throw re-errored the node
|
|
420
507
|
// (el._x?._error re-set), so this only runs on a genuinely clean recovery.
|
|
421
|
-
if (!
|
|
508
|
+
if (!c && !e.o?._) {
|
|
422
509
|
if (o !== undefined) settleErroredDependents(e, o);
|
|
423
510
|
// Self-registration (this node's own superseded flight) is the #3181
|
|
424
511
|
// sweep's business below — retiring it here too would walk twice.
|
|
425
|
-
if (
|
|
512
|
+
if (r) for (const t of r) if (t !== e) settlePendingSource(e, t);
|
|
426
513
|
}
|
|
427
514
|
// #3181: a synchronous settle supersedes the old landing callback, so
|
|
428
515
|
// recompute owns its pending-source sweep. An uninitialized node without
|
|
429
516
|
// a replacement source still has no truth to reveal and must stay parked.
|
|
430
517
|
if (a && !(e.S & (STATUS_PENDING | STATUS_UNINITIALIZED))) settlePendingSource(e);
|
|
431
518
|
}
|
|
432
|
-
|
|
433
|
-
|
|
519
|
+
// A REPORTER whose pass stopped reading a source it reported on (a gate
|
|
520
|
+
// closed) stops counting for the transaction waiting on it (A15 / #3426:
|
|
521
|
+
// the hold lasts while a live reporter observes the flight). Nothing else
|
|
522
|
+
// re-judges a parked transaction (see wokenTransitions; the disposal
|
|
523
|
+
// (#3372) and boundary (#3375) twins of this site), so the writes it held
|
|
524
|
+
// stayed staged for as long as the flight stayed up — forever, for one
|
|
525
|
+
// that never lands (fuzzer #3446 P1, spec O3). The event is "this pass
|
|
526
|
+
// dropped a dep" — deps past `_depsTail` (trimmed below, or kept by A30
|
|
527
|
+
// for a staged pass), or a pass that read nothing — not "recovered from
|
|
528
|
+
// pending": a reporter registered by the stale-reader carve-out
|
|
529
|
+
// (heldFromStale, an INITIALIZED source refetching) displays the committed
|
|
530
|
+
// value and is never pending (fuzzer case 79). Every parked transaction,
|
|
531
|
+
// not the reporter's stamp: the transaction waiting on it registered it
|
|
532
|
+
// without stamping it. One idle pass per parked transaction; done ones
|
|
533
|
+
// return at re-entry. Effects only — reporters register from render-effect
|
|
534
|
+
// notification (INV-3).
|
|
535
|
+
// (`_depsTail` was reset at the top of the pass; TS keeps that narrowing.)
|
|
536
|
+
const R = e.ot;
|
|
537
|
+
if (n && (s && !(e.S & STATUS_PENDING) || (R === null ? e.Se !== null : R.de !== null))) wakeParked();
|
|
538
|
+
// Dependencies are the committed frame's until it is replaced (A30, #3410; the
|
|
539
|
+
// deps twin of the held children above): a pass that staged its value
|
|
540
|
+
// leaves the previous pass's tail linked for `commitPendingNode` to trim,
|
|
541
|
+
// so a write to a dependency the committed value still derives from
|
|
542
|
+
// reaches this node — and joins its hold if the stage is transaction-held
|
|
543
|
+
// by then (a plain flush decides nothing here: the transaction that holds
|
|
544
|
+
// the pass may open later in the same flush). A pass that published
|
|
545
|
+
// directly (a first pass, a lane's own reveal) trims now. An errored pass
|
|
546
|
+
// (a throw, NotReady included, or a comparator throw above) keeps its full
|
|
547
|
+
// list as before — `_depsTail` marks where it stopped — and the commit
|
|
548
|
+
// skips it by the same `_error`. An effect's frame is the run its value is
|
|
549
|
+
// applied by, not the value slot (#3438): a direct-committed pass that
|
|
550
|
+
// still owes a run (`_modified`) has not replaced what the last run
|
|
551
|
+
// published — the same flush may stash that run into a transaction it
|
|
552
|
+
// opens later — so its tail waits for `runEffect` to trim once the run
|
|
553
|
+
// applies. A pass that changed nothing replaced nothing either (#3469): the
|
|
554
|
+
// same flush may park with its inputs held, and the committed frame still
|
|
555
|
+
// derives from the tail — its trim waits on the flush's verdict (heldTrims).
|
|
556
|
+
// A tracked effect's pass IS its run (it bypasses the heap and runs after
|
|
557
|
+
// the commit): the frame is replaced, trim now.
|
|
558
|
+
if (!e.o?._ && e.ve === NOT_PENDING && !(n && e.Ye)) {
|
|
559
|
+
if (t || i || n === EFFECT_TRACKED) trimStaleDeps(e); else if (e.ot?.de ?? e.Se) heldTrims.push(e);
|
|
560
|
+
}
|
|
561
|
+
currentOptimisticLane = S;
|
|
562
|
+
const G = e.ve !== NOT_PENDING || e.o !== null && (e.o.lt !== null || e.o.it !== null) || (e.S & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0;
|
|
434
563
|
// Override-covered holds (hasOverride) always queue: their commit belongs
|
|
435
564
|
// to their own transition's schedule (A18 re-rule) and is unobservable
|
|
436
565
|
// under the override (A17). Revert no longer commits anything, so an
|
|
437
566
|
// unqueued covered hold would leak (INV-7) once the revert clears
|
|
438
567
|
// _transition.
|
|
439
|
-
|
|
440
|
-
|
|
568
|
+
|
|
569
|
+
// While a pass's result waits on a commit, its children (and `_disposal`)
|
|
570
|
+
// wait with it, and a re-run may tear them down immediately
|
|
571
|
+
// (CONFIG_HELD_CHILDREN, #3404). One exception: a transaction-owned effect
|
|
572
|
+
// recomputed mainline (contested, #3322) published its value directly — a
|
|
573
|
+
// `_pendingValue` left from an earlier held pass is that transaction's,
|
|
574
|
+
// not this one's — so this pass's children are the frame's, and any
|
|
575
|
+
// zombies deferred at the top are superseded on that same frame. Its
|
|
576
|
+
// commit rides the transaction, not this flush: release them here rather
|
|
577
|
+
// than let two generations render at once.
|
|
578
|
+
let D = G && (!t || p !== null || (e.S & STATUS_PENDING) !== 0);
|
|
579
|
+
if (D && (!e.Ge || l)) queuePendingNode(e); else if (D && activeTransition === null && !(e.S & (STATUS_PENDING | STATUS_UNINITIALIZED))) {
|
|
580
|
+
D = false;
|
|
581
|
+
disposeChildren(e, false, true);
|
|
582
|
+
}
|
|
583
|
+
if (D) e.T |= CONFIG_HELD_CHILDREN; else e.T &= ~CONFIG_HELD_CHILDREN;
|
|
584
|
+
// (A born-held pass IS the transaction's staged view — nothing to refresh.)
|
|
585
|
+
if (e.Ge && n && activeTransition !== e.Ge && p === null) {
|
|
586
|
+
// The re-run refreshes the transaction's STAGED view (_pendingValue); the
|
|
587
|
+
// value this pass published in _value belongs to the run that just
|
|
588
|
+
// finished. Keep that ownership, or the effect phase parks a
|
|
589
|
+
// mainline-computed value with the transaction (#3412).
|
|
590
|
+
const t = e.It;
|
|
591
|
+
runInTransition(e.Ge, () => recompute(e));
|
|
592
|
+
e.It = t;
|
|
593
|
+
}
|
|
441
594
|
// Missed-wake reschedule (see the finally above): values this pass read
|
|
442
595
|
// before the nested commit are stale, so run again now that the heap will
|
|
443
596
|
// accept the node. Equality gates stop same-value landings from cascading,
|
|
@@ -456,23 +609,23 @@ function updateIfNecessary(e) {
|
|
|
456
609
|
// (#3037); readers meanwhile serve the values the pass has so far.
|
|
457
610
|
// Never recompute a DISPOSED node either: recompute rewrites _flags and
|
|
458
611
|
// would resurrect it (#2983) — readers serve its last value.
|
|
459
|
-
if (e.
|
|
460
|
-
if (e.
|
|
461
|
-
for (let t = e.
|
|
462
|
-
const n = t.
|
|
463
|
-
const i = n.
|
|
464
|
-
if (i.
|
|
612
|
+
if (e.ue & (REACTIVE_RECOMPUTING_DEPS | REACTIVE_DISPOSED)) return;
|
|
613
|
+
if (e.ue & REACTIVE_CHECK) {
|
|
614
|
+
for (let t = e.Se; t; t = t.de) {
|
|
615
|
+
const n = t.Ee;
|
|
616
|
+
const i = n.Te || n;
|
|
617
|
+
if (i.ce) {
|
|
465
618
|
updateIfNecessary(i);
|
|
466
619
|
}
|
|
467
|
-
if (e.
|
|
620
|
+
if (e.ue & REACTIVE_DIRTY) {
|
|
468
621
|
break;
|
|
469
622
|
}
|
|
470
623
|
}
|
|
471
624
|
}
|
|
472
|
-
if (e.
|
|
625
|
+
if (e.ue & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY) || e.o?._ && e.Pe < clock && !e.o?.Re) {
|
|
473
626
|
recompute(e);
|
|
474
627
|
}
|
|
475
|
-
e.
|
|
628
|
+
e.ue = e.ue & (REACTIVE_SNAPSHOT_STALE | REACTIVE_IN_HEAP | REACTIVE_IN_HEAP_HEIGHT);
|
|
476
629
|
}
|
|
477
630
|
|
|
478
631
|
function computed(e, t) {
|
|
@@ -491,41 +644,41 @@ function computed(e, t) {
|
|
|
491
644
|
// plus `_name`.
|
|
492
645
|
const l = {
|
|
493
646
|
id: inheritId(t, n, context),
|
|
494
|
-
T: (n ? CONFIG_TRANSPARENT : 0) | (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (!context || t?.lazy ? CONFIG_AUTO_DISPOSE : 0) | (t?.sync ? CONFIG_SYNC : 0) | (t?.
|
|
495
|
-
|
|
496
|
-
|
|
647
|
+
T: (n ? CONFIG_TRANSPARENT : 0) | (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (!context || t?.lazy ? CONFIG_AUTO_DISPOSE : 0) | (t?.sync ? CONFIG_SYNC : 0) | (t?.Z ? CONFIG_NO_SNAPSHOT : 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
648
|
+
Fe: t?.equals ?? isEqual,
|
|
649
|
+
ke: null,
|
|
497
650
|
C: context?.C ?? globalQueue,
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
Be: 0,
|
|
503
|
-
Nt: undefined,
|
|
504
|
-
Tt: null,
|
|
505
|
-
fe: null,
|
|
506
|
-
et: null,
|
|
651
|
+
ze: context?.ze ?? defaultContext,
|
|
652
|
+
ut: 0,
|
|
653
|
+
ce: e,
|
|
654
|
+
Qe: i ? t.loadingValue : undefined,
|
|
507
655
|
tt: 0,
|
|
656
|
+
At: undefined,
|
|
657
|
+
Rt: null,
|
|
658
|
+
Se: null,
|
|
659
|
+
ot: null,
|
|
660
|
+
Ze: 0,
|
|
508
661
|
u: null,
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
662
|
+
Gt: null,
|
|
663
|
+
_parent: context,
|
|
664
|
+
$e: null,
|
|
665
|
+
Dt: null,
|
|
666
|
+
Xe: null,
|
|
667
|
+
ue: t?.lazy ? REACTIVE_LAZY : REACTIVE_NONE,
|
|
515
668
|
// A loadingValue node is born committed: commit #0 is already in _value.
|
|
516
669
|
S: i ? 0 : STATUS_UNINITIALIZED,
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
670
|
+
Pe: clock,
|
|
671
|
+
ve: NOT_PENDING,
|
|
672
|
+
Ge: null,
|
|
673
|
+
ht: -1,
|
|
674
|
+
ge: i,
|
|
522
675
|
// Cold machinery (async/transition/optimistic/verdict slots) lives one
|
|
523
676
|
// hop away in the lazily-allocated extension — the core literal MUST
|
|
524
677
|
// stay under V8's in-object boundary (§12: past ~39 fields every
|
|
525
678
|
// allocation spills to a backing store and creation cost ~4x's).
|
|
526
679
|
o: null
|
|
527
680
|
};
|
|
528
|
-
if (t?.unobserved) ext(l).
|
|
681
|
+
if (t?.unobserved) ext(l).Pt = t.unobserved;
|
|
529
682
|
setupComputedNode(l, t);
|
|
530
683
|
return l;
|
|
531
684
|
}
|
|
@@ -535,28 +688,29 @@ function computed(e, t) {
|
|
|
535
688
|
* this; hot paths read `el._x?._field` gated by the _config presence bits.
|
|
536
689
|
* Never call ext() just to store a field's default. */ function ext(e) {
|
|
537
690
|
return e.o ??= {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
691
|
+
Ce: undefined,
|
|
692
|
+
Ft: undefined,
|
|
693
|
+
Ct: 0,
|
|
694
|
+
gt: NOT_PENDING,
|
|
695
|
+
vt: 0,
|
|
696
|
+
Ue: undefined,
|
|
697
|
+
je: undefined,
|
|
698
|
+
xe: undefined,
|
|
699
|
+
Ht: undefined,
|
|
546
700
|
t: 0,
|
|
547
|
-
|
|
548
|
-
|
|
701
|
+
Re: null,
|
|
702
|
+
Ae: null,
|
|
549
703
|
_: undefined,
|
|
550
|
-
|
|
551
|
-
|
|
704
|
+
Ie: undefined,
|
|
705
|
+
ae: undefined,
|
|
552
706
|
h: undefined,
|
|
553
|
-
|
|
707
|
+
be: false,
|
|
554
708
|
i: null,
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
709
|
+
Pt: undefined,
|
|
710
|
+
nt: undefined,
|
|
711
|
+
it: null,
|
|
712
|
+
lt: null,
|
|
713
|
+
bt: undefined
|
|
560
714
|
};
|
|
561
715
|
}
|
|
562
716
|
|
|
@@ -574,40 +728,40 @@ function computed(e, t) {
|
|
|
574
728
|
// the options to get it.
|
|
575
729
|
const o = {
|
|
576
730
|
id: inheritId(l, u, context),
|
|
577
|
-
T: (u ? CONFIG_TRANSPARENT : 0) | (l?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (l?.sync ? CONFIG_SYNC : 0) | (l?.
|
|
578
|
-
|
|
579
|
-
|
|
731
|
+
T: (u ? CONFIG_TRANSPARENT : 0) | (l?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (l?.sync ? CONFIG_SYNC : 0) | (l?.kt ?? 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
732
|
+
Fe: false,
|
|
733
|
+
ke: null,
|
|
580
734
|
C: context?.C ?? globalQueue,
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
Be: 0,
|
|
586
|
-
Nt: undefined,
|
|
587
|
-
Tt: null,
|
|
588
|
-
fe: null,
|
|
589
|
-
et: null,
|
|
735
|
+
ze: context?.ze ?? defaultContext,
|
|
736
|
+
ut: 0,
|
|
737
|
+
ce: e,
|
|
738
|
+
Qe: undefined,
|
|
590
739
|
tt: 0,
|
|
740
|
+
At: undefined,
|
|
741
|
+
Rt: null,
|
|
742
|
+
Se: null,
|
|
743
|
+
ot: null,
|
|
744
|
+
Ze: 0,
|
|
591
745
|
u: null,
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
746
|
+
Gt: null,
|
|
747
|
+
_parent: context,
|
|
748
|
+
$e: null,
|
|
749
|
+
Dt: null,
|
|
750
|
+
Xe: null,
|
|
751
|
+
ue: REACTIVE_LAZY,
|
|
598
752
|
S: STATUS_UNINITIALIZED,
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
753
|
+
Pe: clock,
|
|
754
|
+
ve: NOT_PENDING,
|
|
755
|
+
Ge: null,
|
|
756
|
+
ht: -1,
|
|
757
|
+
ge: false,
|
|
758
|
+
Ye: false,
|
|
759
|
+
Ut: undefined,
|
|
760
|
+
Lt: t,
|
|
761
|
+
Vt: n,
|
|
762
|
+
yt: undefined,
|
|
763
|
+
Le: i,
|
|
764
|
+
It: null,
|
|
611
765
|
o: null
|
|
612
766
|
};
|
|
613
767
|
// Effects dispatch status through the SHARED notifier (statusNotifierOf,
|
|
@@ -615,7 +769,7 @@ function computed(e, t) {
|
|
|
615
769
|
// allocation on EVERY effect at creation (an alloc + 19 field stores,
|
|
616
770
|
// +23% effect creation, caught by the creation benches). Only genuinely
|
|
617
771
|
// per-node channels (boundaries) live on _x.
|
|
618
|
-
if (l?.unobserved) ext(o).
|
|
772
|
+
if (l?.unobserved) ext(o).Pt = l.unobserved;
|
|
619
773
|
setupComputedNode(o, lazyOptions);
|
|
620
774
|
return o;
|
|
621
775
|
}
|
|
@@ -638,7 +792,7 @@ function setEffectStatusNotify(e) {
|
|
|
638
792
|
* per-node field did when every effect carried one. */ function statusNotifierOf(e) {
|
|
639
793
|
const t = e.o?.h;
|
|
640
794
|
if (t !== undefined) return t;
|
|
641
|
-
return e.
|
|
795
|
+
return e.Le ? effectStatusNotify ?? undefined : undefined;
|
|
642
796
|
}
|
|
643
797
|
|
|
644
798
|
const lazyOptions = {
|
|
@@ -646,24 +800,24 @@ const lazyOptions = {
|
|
|
646
800
|
};
|
|
647
801
|
|
|
648
802
|
function setupComputedNode(e, t) {
|
|
649
|
-
e.
|
|
650
|
-
const n = context?.
|
|
803
|
+
e.Rt = e;
|
|
804
|
+
const n = context?.xt ? context.Qt : context;
|
|
651
805
|
if (context) {
|
|
652
|
-
const t = context.
|
|
806
|
+
const t = context.Xe;
|
|
653
807
|
if (t === null) {
|
|
654
|
-
context.
|
|
808
|
+
context.Xe = e;
|
|
655
809
|
} else {
|
|
656
|
-
e
|
|
657
|
-
t.
|
|
658
|
-
context.
|
|
810
|
+
e.$e = t;
|
|
811
|
+
t.Dt = e;
|
|
812
|
+
context.Xe = e;
|
|
659
813
|
}
|
|
660
814
|
}
|
|
661
|
-
if (n) e.
|
|
662
|
-
if (GlobalQueue.
|
|
815
|
+
if (n) e.tt = n.tt + 1;
|
|
816
|
+
if (GlobalQueue.wt !== null) GlobalQueue.wt(e);
|
|
663
817
|
!t?.lazy && recompute(e, true);
|
|
664
818
|
if (snapshotCaptureActive && !t?.lazy) {
|
|
665
819
|
if (!(e.S & STATUS_PENDING) && !(e.T & CONFIG_NO_SNAPSHOT)) {
|
|
666
|
-
ext(e).
|
|
820
|
+
ext(e).nt = e.Qe === undefined ? NO_SNAPSHOT : e.Qe;
|
|
667
821
|
e.T |= CONFIG_HAS_SNAPSHOT;
|
|
668
822
|
snapshotSources.add(e);
|
|
669
823
|
}
|
|
@@ -676,29 +830,29 @@ function signal(e, t, n = null) {
|
|
|
676
830
|
// createSignal nodes so ownerPath can locate signal subjects; null here,
|
|
677
831
|
// and staying null on internal signals — one shape either way).
|
|
678
832
|
const i = {
|
|
679
|
-
|
|
680
|
-
T: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.
|
|
681
|
-
|
|
833
|
+
Fe: t?.equals ?? isEqual,
|
|
834
|
+
T: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.Z ? CONFIG_NO_SNAPSHOT : 0),
|
|
835
|
+
Qe: e,
|
|
682
836
|
u: null,
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
837
|
+
Gt: null,
|
|
838
|
+
Pe: clock,
|
|
839
|
+
Te: n,
|
|
840
|
+
De: n?.o?.i || null,
|
|
841
|
+
Mt: null,
|
|
842
|
+
ve: NOT_PENDING,
|
|
689
843
|
// Signal-literal diet (§12e): NO _time/_fn/_statusFlags slots. Stores
|
|
690
844
|
// materialize one signal per touched leaf, so signal bytes are store
|
|
691
845
|
// bytes. _time is write-only on signals (every read site is computed-
|
|
692
846
|
// typed error-retry gating); _fn/_statusFlags read falsy-identically as
|
|
693
847
|
// missing properties on the shared paths (undefined masks to 0).
|
|
694
|
-
|
|
695
|
-
|
|
848
|
+
Ge: null,
|
|
849
|
+
ht: -1,
|
|
696
850
|
o: null
|
|
697
851
|
};
|
|
698
|
-
if (t?.unobserved) ext(i).
|
|
852
|
+
if (t?.unobserved) ext(i).Pt = t.unobserved;
|
|
699
853
|
if (n) linkFirewallChild(n, i);
|
|
700
854
|
if (snapshotCaptureActive && !(i.T & CONFIG_NO_SNAPSHOT) && !((n?.S ?? 0) & STATUS_PENDING)) {
|
|
701
|
-
ext(i).
|
|
855
|
+
ext(i).nt = e === undefined ? NO_SNAPSHOT : e;
|
|
702
856
|
i.T |= CONFIG_HAS_SNAPSHOT;
|
|
703
857
|
snapshotSources.add(i);
|
|
704
858
|
}
|
|
@@ -727,8 +881,8 @@ function signal(e, t, n = null) {
|
|
|
727
881
|
* points `_nextChild` at the old head). Doubly linked so a released leaf
|
|
728
882
|
* unlinks in O(1) — the chain is walked per mark of the projection and
|
|
729
883
|
* would otherwise grow by one node per leaf ever read (#3351). */ function linkFirewallChild(e, t) {
|
|
730
|
-
const n = t.
|
|
731
|
-
if (n !== null) n.
|
|
884
|
+
const n = t.De;
|
|
885
|
+
if (n !== null) n.Mt = t;
|
|
732
886
|
ext(e).i = t;
|
|
733
887
|
e.T |= CONFIG_FW_CHILDREN;
|
|
734
888
|
}
|
|
@@ -736,39 +890,45 @@ function signal(e, t, n = null) {
|
|
|
736
890
|
/** Release a firewall child the store no longer addresses (unobserved sweep
|
|
737
891
|
* dropped it from its target's cache): unlink it from the chain so the
|
|
738
892
|
* projection stops retaining it and its last value. The node keeps its own
|
|
739
|
-
* `_nextChild` so a walk that is mid-chain on it still terminates.
|
|
740
|
-
* `_companionChildren`
|
|
741
|
-
*
|
|
893
|
+
* `_nextChild` so a walk that is mid-chain on it still terminates. It also
|
|
894
|
+
* leaves `_companionChildren` (#3503): the companions themselves are
|
|
895
|
+
* permanent on the node, but the node is unreachable through the store, so
|
|
896
|
+
* the set would only retain it and its last value. */ function unlinkFirewallChild(e) {
|
|
742
897
|
const t = e;
|
|
743
|
-
const n = t.
|
|
898
|
+
const n = t.Te;
|
|
744
899
|
if (!n) return;
|
|
745
|
-
const i = t.
|
|
746
|
-
const l = t.
|
|
747
|
-
if (i !== null) i.
|
|
748
|
-
if (l !== null) l.
|
|
749
|
-
t.
|
|
900
|
+
const i = t.Mt;
|
|
901
|
+
const l = t.De;
|
|
902
|
+
if (i !== null) i.De = l; else if (n.o.i === t) n.o.i = l;
|
|
903
|
+
if (l !== null) l.Mt = i;
|
|
904
|
+
t.Mt = null;
|
|
905
|
+
// #3503: the companion set is the last projection-side reference to a
|
|
906
|
+
// released leaf; leaving it there kept the leaf and its last value alive
|
|
907
|
+
// for the projection's lifetime after every latest()/isPending() reader
|
|
908
|
+
// was disposed.
|
|
909
|
+
n.o.bt?.delete(t);
|
|
750
910
|
}
|
|
751
911
|
|
|
752
912
|
function slotSignal(e, t, n, i, l, u = null) {
|
|
753
913
|
// Prod and observe boilerplates — see computed(). The store relabels the
|
|
754
914
|
// observe slot (`store.<key>`) when the attribution engine is installed.
|
|
755
915
|
const o = {
|
|
756
|
-
|
|
916
|
+
Fe: t,
|
|
757
917
|
T: CONFIG_OWNED_WRITE | CONFIG_SLOT_NODE,
|
|
758
|
-
|
|
918
|
+
Qe: e,
|
|
759
919
|
u: null,
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
920
|
+
Gt: null,
|
|
921
|
+
Pe: clock,
|
|
922
|
+
Te: u,
|
|
923
|
+
De: u?.o?.i || null,
|
|
924
|
+
Mt: null,
|
|
925
|
+
ve: NOT_PENDING,
|
|
926
|
+
Ge: null,
|
|
927
|
+
ht: -1,
|
|
768
928
|
o: null,
|
|
769
929
|
// Slot backrefs: what the equals/unobserved closures used to capture.
|
|
770
|
-
|
|
771
|
-
|
|
930
|
+
Wt: n,
|
|
931
|
+
qt: i,
|
|
772
932
|
// Store read-path caches, pre-shaped (were post-construction expandos).
|
|
773
933
|
acc: l,
|
|
774
934
|
px: undefined,
|
|
@@ -776,7 +936,7 @@ function slotSignal(e, t, n, i, l, u = null) {
|
|
|
776
936
|
};
|
|
777
937
|
if (u) linkFirewallChild(u, o);
|
|
778
938
|
if (snapshotCaptureActive && !((u?.S ?? 0) & STATUS_PENDING)) {
|
|
779
|
-
ext(o).
|
|
939
|
+
ext(o).nt = e === undefined ? NO_SNAPSHOT : e;
|
|
780
940
|
o.T |= CONFIG_HAS_SNAPSHOT;
|
|
781
941
|
snapshotSources.add(o);
|
|
782
942
|
}
|
|
@@ -785,14 +945,14 @@ function slotSignal(e, t, n, i, l, u = null) {
|
|
|
785
945
|
|
|
786
946
|
function optimisticSignal(e, t) {
|
|
787
947
|
const n = signal(e, t);
|
|
788
|
-
ext(n).
|
|
948
|
+
ext(n).Ce = NOT_PENDING;
|
|
789
949
|
n.T |= CONFIG_OPTIMISTIC;
|
|
790
950
|
return n;
|
|
791
951
|
}
|
|
792
952
|
|
|
793
953
|
function optimisticComputed(e, t) {
|
|
794
954
|
const n = computed(e, t);
|
|
795
|
-
ext(n).
|
|
955
|
+
ext(n).Ce = NOT_PENDING;
|
|
796
956
|
n.T |= CONFIG_OPTIMISTIC;
|
|
797
957
|
return n;
|
|
798
958
|
}
|
|
@@ -823,26 +983,51 @@ function isEqual(e, t) {
|
|
|
823
983
|
* );
|
|
824
984
|
* ```
|
|
825
985
|
*/ function untrack(e, t) {
|
|
826
|
-
if (GlobalQueue.
|
|
986
|
+
if (GlobalQueue.Yt === null && !tracking && true) return e();
|
|
827
987
|
const n = tracking;
|
|
828
988
|
tracking = false;
|
|
829
989
|
try {
|
|
830
|
-
if (GlobalQueue.
|
|
990
|
+
if (GlobalQueue.Yt !== null) return GlobalQueue.Yt(e);
|
|
831
991
|
return e();
|
|
832
992
|
} finally {
|
|
833
993
|
tracking = n;
|
|
834
994
|
}
|
|
835
995
|
}
|
|
836
996
|
|
|
997
|
+
/**
|
|
998
|
+
* Set while runtime bookkeeping evaluates a user accessor inside another
|
|
999
|
+
* node's pass (a loading boundary's `on` key, read from `notify` while the
|
|
1000
|
+
* node that went pending is still recomputing). `context` is that node, but
|
|
1001
|
+
* the read is nobody's: the value is compared, never derived from, so the
|
|
1002
|
+
* untracked-pending re-run link (`read`, `!tracking`) must not be recorded on
|
|
1003
|
+
* it — it made the key's source a dependency of an unrelated async memo
|
|
1004
|
+
* (#3528: `isPending(m2)` through a memo in `on()` linked the memo into `m2`,
|
|
1005
|
+
* a cycle that re-derived `m2` on every pending mark and never converged).
|
|
1006
|
+
*/ let spectating = false;
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Evaluates `fn` untracked and without recording a dependency for the
|
|
1010
|
+
* untracked-pending re-run rule on the current `context`. For bookkeeping
|
|
1011
|
+
* reads made mid-propagation on behalf of no node — see `spectating`.
|
|
1012
|
+
*/ function spectate(e) {
|
|
1013
|
+
const t = spectating;
|
|
1014
|
+
spectating = true;
|
|
1015
|
+
try {
|
|
1016
|
+
return untrack(e);
|
|
1017
|
+
} finally {
|
|
1018
|
+
spectating = t;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
837
1022
|
/**
|
|
838
1023
|
* Bring a computed to a readable state: lazy/disposed nodes are (re)computed;
|
|
839
1024
|
* an isPending() probe (`refresh`) additionally pulls the node fully up to
|
|
840
1025
|
* date so its status flags reflect the current graph.
|
|
841
1026
|
*/ function prepareComputed(e, t) {
|
|
842
|
-
if (e.
|
|
843
|
-
e.
|
|
1027
|
+
if (e.ue & REACTIVE_LAZY) {
|
|
1028
|
+
e.ue &= ~REACTIVE_LAZY;
|
|
844
1029
|
recompute(e, true);
|
|
845
|
-
} else if (e.
|
|
1030
|
+
} else if (e.ue & REACTIVE_DISPOSED) {
|
|
846
1031
|
// Two disposal lifecycles share the flag (#3024). Observation-lifecycle
|
|
847
1032
|
// nodes (CONFIG_AUTO_DISPOSE) are dormant — torn down by unobserved()
|
|
848
1033
|
// when the last subscriber left — and reads reawaken them; that is the
|
|
@@ -877,14 +1062,14 @@ function isEqual(e, t) {
|
|
|
877
1062
|
* Pay-for-use: reached through GlobalQueue._notifyAuthoritativeObservers,
|
|
878
1063
|
* installed at first until() call — apps that never use until() shake it.
|
|
879
1064
|
*/ function notifyAuthoritativeObservers(e) {
|
|
880
|
-
for (let t = e.u; t !== null; t = t.
|
|
881
|
-
const e = t.
|
|
1065
|
+
for (let t = e.u; t !== null; t = t.Ne) {
|
|
1066
|
+
const e = t._e;
|
|
882
1067
|
if (!(e.T & CONFIG_AUTHORITATIVE_READ)) continue;
|
|
883
1068
|
// Missed-wake latch (#3037), same contract as insertSubs: the reader may
|
|
884
1069
|
// itself have pulled this recompute (updateIfNecessary from its own
|
|
885
1070
|
// read), and the heap refuses RECOMPUTING nodes — latch so recompute's
|
|
886
1071
|
// tail reschedules it with the staged value visible.
|
|
887
|
-
if (e.
|
|
1072
|
+
if (e.ue & REACTIVE_RECOMPUTING_DEPS && t.qe === e.Ze && t !== e.ot) e.ue |= REACTIVE_MISSED_WAKE;
|
|
888
1073
|
enqueueSub(e);
|
|
889
1074
|
}
|
|
890
1075
|
schedule();
|
|
@@ -895,7 +1080,7 @@ function isEqual(e, t) {
|
|
|
895
1080
|
* before its first read (same late-binding contract as the optimistic engine;
|
|
896
1081
|
* the gating bit is only ever set by such a read, so the `!` call sites are
|
|
897
1082
|
* safe once every setter installs, #3303). */ function installAuthoritativeRead() {
|
|
898
|
-
if (GlobalQueue.
|
|
1083
|
+
if (GlobalQueue.Zt === null) GlobalQueue.Zt = notifyAuthoritativeObservers;
|
|
899
1084
|
}
|
|
900
1085
|
|
|
901
1086
|
/**
|
|
@@ -908,19 +1093,246 @@ function isEqual(e, t) {
|
|
|
908
1093
|
* already use). An effect the transaction itself computed re-derives at its
|
|
909
1094
|
* commit on its own (parked run, or the contested re-derive, #3322) and is
|
|
910
1095
|
* not recorded — replaying it too would publish the frame twice.
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1096
|
+
*
|
|
1097
|
+
* Flight twin (the pending-branch carve-out): the reader is served the
|
|
1098
|
+
* node's committed, pre-flight value and now observes that flight — A15:
|
|
1099
|
+
* async work observed by a reader settles as one unit with the writes that
|
|
1100
|
+
* asked it — so it joins the transaction's reporters for the node. The
|
|
1101
|
+
* reporter the transaction recorded when the flight started may be gone (a
|
|
1102
|
+
* keyed remount disposed it, #3374); a completion check that found no live
|
|
1103
|
+
* reporter committed the writes ahead of the answer, tearing the new
|
|
1104
|
+
* reader's frame (`Count: 1` beside `Details: 0`). Joins an entry the
|
|
1105
|
+
* transaction already holds; a flight nobody had observed yet has none (the
|
|
1106
|
+
* reader is its first observer — a conditional that just revealed it, #3458)
|
|
1107
|
+
* and is notified up the reader's own queue chain under that transaction,
|
|
1108
|
+
* the one sanctioned registration site (INV-3): a collecting boundary above
|
|
1109
|
+
* the reader consumes it as it would any pending, an unboundaried reader
|
|
1110
|
+
* opens the entry — and the transaction, judged complete on its other
|
|
1111
|
+
* flights, revealed the inputs beside the reader's pre-flight value
|
|
1112
|
+
* otherwise (`Count: 1 | A: 1` beside `B: 0`). A staged signal or a settled
|
|
1113
|
+
* node registers nothing. Every reporter dies with its reader
|
|
1114
|
+
* (reporterBlocksSource: the read linked it as a dep). The node's own entry
|
|
1115
|
+
* is the only one that can matter: a chain's intermediate memo is re-pulled
|
|
1116
|
+
* by the read (updateIfNecessary's retry) and enters the transaction, so the
|
|
1117
|
+
* reader holds through the normal path; a node with its own flight that is
|
|
1118
|
+
* also pending on an upstream re-ask blocks through that flight until it
|
|
1119
|
+
* lands, and its landing re-runs the reader into the normal path.
|
|
1120
|
+
*/
|
|
1121
|
+
/** The replay half of the stale-of-foreign clause (A15 / A26): a stale reader
|
|
1122
|
+
* served the committed value because `txn` holds what it read re-runs at
|
|
1123
|
+
* txn's commit, when the value it was denied becomes the frame — unless its
|
|
1124
|
+
* own last value already came from that transaction. One registration for
|
|
1125
|
+
* the node path (heldFromStale) and the store's backing paths, which have
|
|
1126
|
+
* no node to carry the hold (heldFromReader, the adoption hold view). */ function recordStaleReplay(e, t) {
|
|
1127
|
+
const n = t.It;
|
|
1128
|
+
if (n == null || currentTransition(n) !== e) e.ct.add(t);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* The ownership relation (DESIGN-CONSOLIDATION §6, ruled 2026-09-17): is
|
|
1133
|
+
* `hold` part of the running pass's world? A plain reader's world is the
|
|
1134
|
+
* transaction it runs under, through merges. A lane reader's world is its
|
|
1135
|
+
* lane AND the transition that owns the lane — the one asymmetry between a
|
|
1136
|
+
* lane and a separate transaction (a lane sees what lands from its parent as
|
|
1137
|
+
* its own; a separate transaction would wait for the parent to settle) —
|
|
1138
|
+
* see `ownsLane` in lanes.ts, built on this. One relation for the
|
|
1139
|
+
* stale-of-foreign clause (heldFromStale), the lane arm (readsHeldCommitted)
|
|
1140
|
+
* and the store's backing holds (foreignHold); `serve` has no lane arm of
|
|
1141
|
+
* its own, the lane's extra visibility lives here.
|
|
1142
|
+
*/ function ownsHold(e) {
|
|
1143
|
+
return activeTransition !== null && currentTransition(e) === currentTransition(activeTransition);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function heldFromStale(e, t) {
|
|
1147
|
+
const n = e.Ge;
|
|
1148
|
+
if (n === null || ownsHold(n)) return false;
|
|
914
1149
|
const i = currentTransition(n);
|
|
915
|
-
|
|
916
|
-
|
|
1150
|
+
recordStaleReplay(i, t);
|
|
1151
|
+
const l = i.oe.get(e);
|
|
1152
|
+
if (l) l.add(t); else if (e.S & STATUS_PENDING) runInTransition(i, () => t.C.notify(t, STATUS_PENDING, STATUS_PENDING, e.o._));
|
|
1153
|
+
return true;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* A tracked computation about to be served a live transaction's staged value
|
|
1158
|
+
* derives from that transaction's world, so it enters the transaction and
|
|
1159
|
+
* this pass's result is held with it (A29, #3408). The write side (`setSignal`)
|
|
1160
|
+
* and a stamped node's recompute already enter; a reader that only now
|
|
1161
|
+
* starts reading the held node — a conditional memo whose branch flipped —
|
|
1162
|
+
* was the gap: it published a value derived from the staged world into the
|
|
1163
|
+
* mainline frame. Not for a probe (`isPending(() => x())` observes, it does
|
|
1164
|
+
* not derive) and a no-op for the ambient batch (`_transition` null) or the
|
|
1165
|
+
* transaction already active.
|
|
1166
|
+
*/
|
|
1167
|
+
/** The transaction a pass running OUTSIDE a flush was served a staged value
|
|
1168
|
+
* from (A29, creation-time form). Inside a flush the pass enters through
|
|
1169
|
+
* initTransition; outside one — a memo or effect created from mainline code
|
|
1170
|
+
* while a hold is live — entering would leave `activeTransition` and the
|
|
1171
|
+
* batch pointed at the transaction for the rest of the synchronous block,
|
|
1172
|
+
* so an unrelated write made after the mount was held with someone else's
|
|
1173
|
+
* action. The entry is the pass's alone: recompute stages the node into the
|
|
1174
|
+
* transaction (born held) and mainline is never touched. */ let stagedEntry = null;
|
|
1175
|
+
|
|
1176
|
+
function enterStagedRead(e, t = e.Ge) {
|
|
1177
|
+
if (!t || t === activeTransition || pendingCheckActive) return;
|
|
1178
|
+
// A companion (the latest() shadow, the isPending() verdict signal) is the
|
|
1179
|
+
// engine's mirror of the flushed world — reading it, or being it, is an
|
|
1180
|
+
// observation, not a derivation from the hold: latest(x) never enters x's
|
|
1181
|
+
// transaction, and the shadow's own pass never enters either (it would
|
|
1182
|
+
// flip activeTransition under the reader that pulled it). (`el` is null for
|
|
1183
|
+
// a store backing served under a hold — no node, the transaction is the
|
|
1184
|
+
// fold's.)
|
|
1185
|
+
if (e?.o?.Ht || context?.o?.Ht) return;
|
|
1186
|
+
// Verdict machinery (GlobalQueue._verdictPull: companion creation and the
|
|
1187
|
+
// latest()/isPending() pulls — the latest() shadow is created before it is
|
|
1188
|
+
// marked optimistic, so the bit alone cannot tell) and optimistic nodes
|
|
1189
|
+
// (own lane posture, A31) read staged truth by design; they keep the
|
|
1190
|
+
// entering path.
|
|
1191
|
+
// (`context` is non-null here: every caller selected a value for a reader.)
|
|
1192
|
+
const n = context;
|
|
1193
|
+
if (activeTransition === null && !globalQueue.Kt) {
|
|
1194
|
+
// Verdict pulls are observations, not derivations: a latest() /
|
|
1195
|
+
// isPending() call from mainline must never enter a transaction (it
|
|
1196
|
+
// would capture the rest of the caller's synchronous block).
|
|
1197
|
+
if (GlobalQueue.jt) return;
|
|
1198
|
+
if (n.ue & REACTIVE_RECOMPUTING_DEPS && !(n.T & CONFIG_OPTIMISTIC) && (stagedEntry === null || stagedEntry === t)) {
|
|
1199
|
+
stagedEntry = t;
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
globalQueue.initTransition(t);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
/**
|
|
1207
|
+
* Rule 1 (value selection), the full arm: does this reader see a STAGED
|
|
1208
|
+
* node's COMMITTED value? One implementation of the rule the fast paths
|
|
1209
|
+
* (readNodeFast, read's fast block) carry as their trivial ternary and that
|
|
1210
|
+
* every slow site — read's tail, the store's backing selection, the lane and
|
|
1211
|
+
* verdict arms — used to restate by hand (docs/DESIGN-CONSOLIDATION.md, move 3b). In order:
|
|
1212
|
+
* - no reader at all (an untracked read) — the committed frame;
|
|
1213
|
+
* - a reader under an optimistic lane the engine says reads committed
|
|
1214
|
+
* (laneReadsCommitted: another lane's hold, #3460);
|
|
1215
|
+
* - nothing staged;
|
|
1216
|
+
* - a children-forbidden reader (createTrackedEffect / onSettled: the frame,
|
|
1217
|
+
* never the graph — A32);
|
|
1218
|
+
* - a stale reader (render effect) of a FOREIGN transaction's staged write —
|
|
1219
|
+
* committed, no entanglement (heldFromStale registers the replay; a node
|
|
1220
|
+
* born held has no committed frame to fall back to, `noCommitted`);
|
|
1221
|
+
* - A17 for HELD truth (#3164, CONFIG_HELD_TRUTH): staged confirming truth —
|
|
1222
|
+
* fold-staged onto an armed family, or entangle-stolen by an awaited
|
|
1223
|
+
* until() — is masked from ordinary readers until its transaction's
|
|
1224
|
+
* reveal, the retaining transaction's own speculative recomputes included
|
|
1225
|
+
* (partial override coverage would otherwise compose override + staged
|
|
1226
|
+
* truth into a state no timeline contains). Authoritative readers
|
|
1227
|
+
* (until()'s predicate) and latest() see the staged truth — the tunnel that
|
|
1228
|
+
* keeps the hold deadlock-free.
|
|
1229
|
+
* False means the reader derives from the staged value and enters its
|
|
1230
|
+
* transaction (enterStagedRead, A29).
|
|
1231
|
+
*/ function readerSeesCommitted(e, t, n, i) {
|
|
1232
|
+
return !!(!t || currentOptimisticLane !== null && GlobalQueue.Bt(e, n, t) || e.ve === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN || stale && !i && heldFromStale(e, t) || e.T & CONFIG_HELD_TRUTH && !latestReadActive && !(t.T & CONFIG_AUTHORITATIVE_READ));
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/** A28 — set when a node is staged (queuePendingNode) or a held node rewritten
|
|
1236
|
+
* (stashHeldRewrite) OUTSIDE a flush; cleared when the next flush begins. The
|
|
1237
|
+
* read sites test this one module boolean instead of `globalQueue._running`:
|
|
1238
|
+
* inside a flush it is false and the A28 arm costs nothing; outside, only a
|
|
1239
|
+
* tick with unflushed writes pays the staged-node check. */ let unflushedStaged = false;
|
|
1240
|
+
|
|
1241
|
+
function markUnflushedStaged() {
|
|
1242
|
+
unflushedStaged = true;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/** A28 — a write becomes visible at flush. Outside a flush, a node holding an
|
|
1246
|
+
* AMBIENT staged value (no transaction stamp) was written since the last
|
|
1247
|
+
* flush: ambient staging commits at flush end, so nothing else leaves a node
|
|
1248
|
+
* in this state; a stamped value is the flushed held world, which A28 says
|
|
1249
|
+
* latest() serves. Inside a flush the rule does not apply (A28 (4): promoted
|
|
1250
|
+
* within the round). Structural — no marker on the write path. */ function unflushed(e) {
|
|
1251
|
+
return unflushedValue(e) !== NOT_PENDING;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
/** The value an unflushed node serves — the committed value for an ambient
|
|
1255
|
+
* write, the flushed staged value for a rewrite of a held node — or
|
|
1256
|
+
* NOT_PENDING when nothing is unflushed. Exempt: owned-write nodes (A28 (4):
|
|
1257
|
+
* a write issued inside a recompute is promoted at that recompute's end —
|
|
1258
|
+
* boundary and loading machinery, until()'s internals, signals declared for
|
|
1259
|
+
* in-computation writes) and engine companions (the isPending() verdict
|
|
1260
|
+
* signal, the latest() shadow: the system's own writes, made at the source's
|
|
1261
|
+
* write to mirror it, installing eagerly — A28, A8). */ function unflushedValue(e, t = e.Qe) {
|
|
1262
|
+
if (globalQueue.Kt || e.ve === NOT_PENDING || e.T & CONFIG_PROMOTED || e.o?.Ht) return NOT_PENDING;
|
|
1263
|
+
// Ambient, or adopted by a transaction before any flush carried the staging
|
|
1264
|
+
// (CONFIG_ADOPTED_UNFLUSHED): nothing flushed is staged — the committed
|
|
1265
|
+
// value answers (the caller's notion of committed: a store node's backing).
|
|
1266
|
+
// A held node: unflushed only if rewritten since the last flush (stash).
|
|
1267
|
+
if (e.Ge === null || e.T & CONFIG_ADOPTED_UNFLUSHED) return t;
|
|
1268
|
+
return e.o === null ? NOT_PENDING : e.o.gt;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/** Held nodes rewritten since the last flush (setSignal); the flush clears
|
|
1272
|
+
* their stash — from then on latest() answers with the rewrite. */ const unflushedRewrites = [];
|
|
1273
|
+
|
|
1274
|
+
/** Nodes written inside a creation-time recompute (CONFIG_PROMOTED). */ const promotedWrites = [];
|
|
1275
|
+
|
|
1276
|
+
/** A28 (5): an optimistic write becomes the ACTIVE override at the flush that
|
|
1277
|
+
* carries it. `_overrideTime` is stamped with `clock` at the write and `clock`
|
|
1278
|
+
* advances after every flush, so "this tick, outside a flush" is unflushed. */ function unflushedOverride(e) {
|
|
1279
|
+
// Companions are optimistic signals written by the engine (see unflushed).
|
|
1280
|
+
return !globalQueue.Kt && e.o?.Ct === clock && !e.o?.Ht;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/** Active optimistic override on an armed node (an armed slot idles at
|
|
1284
|
+
* NOT_PENDING; undefined = unarmed plain node). The writer's own channels —
|
|
1285
|
+
* the draft, `in`/keys inside the setter — compose on this regardless of
|
|
1286
|
+
* flush state. */ function hasActiveOverride(e) {
|
|
1287
|
+
const t = e.o;
|
|
1288
|
+
return t !== null && t.Ce !== undefined && t.Ce !== NOT_PENDING;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
/** The override a READER sees: installed, and carried by a flush (A28 (5) —
|
|
1292
|
+
* an optimistic write is a write; until its flush no reader sees it). One
|
|
1293
|
+
* implementation for read()'s override arm, the verdict channels
|
|
1294
|
+
* (latestRead, computePendingState) and the store's selection
|
|
1295
|
+
* (docs/DESIGN-CONSOLIDATION.md, move 3b). */ function visibleOverride(e) {
|
|
1296
|
+
return hasActiveOverride(e) && !unflushedOverride(e);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
/** A derivation served the committed value because of an unflushed write
|
|
1300
|
+
* (A28) must run again in the flush that carries it — the late-linker case
|
|
1301
|
+
* (#3337's reason to defer the walk): it linked after the write walked. */ function markLateLinker(e) {
|
|
1302
|
+
// The pass's own tail re-enqueues on this latch (recompute's finally) —
|
|
1303
|
+
// a direct enqueue here would be wiped by the pass's flag reset.
|
|
1304
|
+
e.ue |= REACTIVE_MISSED_WAKE;
|
|
917
1305
|
return true;
|
|
918
1306
|
}
|
|
919
1307
|
|
|
1308
|
+
/** Companion-bearing nodes written outside a flush (setSignal); the flush
|
|
1309
|
+
* that carries their writes re-syncs their companions (A28). */ const unflushedCompanions = [];
|
|
1310
|
+
|
|
1311
|
+
function resyncUnflushedCompanions() {
|
|
1312
|
+
unflushedStaged = false;
|
|
1313
|
+
// Length-guarded: the common flush has nothing here, and must allocate nothing.
|
|
1314
|
+
// Length-guarded: the common flush has nothing here and allocates nothing.
|
|
1315
|
+
if (unflushedRewrites.length !== 0) {
|
|
1316
|
+
for (const e of unflushedRewrites) e.o.gt = NOT_PENDING;
|
|
1317
|
+
unflushedRewrites.length = 0;
|
|
1318
|
+
}
|
|
1319
|
+
if (promotedWrites.length !== 0) {
|
|
1320
|
+
for (const e of promotedWrites) e.T &= ~CONFIG_PROMOTED;
|
|
1321
|
+
promotedWrites.length = 0;
|
|
1322
|
+
}
|
|
1323
|
+
if (unflushedCompanions.length !== 0) {
|
|
1324
|
+
for (const e of unflushedCompanions) GlobalQueue.me(e, e.ve !== NOT_PENDING ? e.ve : e.Qe);
|
|
1325
|
+
unflushedCompanions.length = 0;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
|
|
920
1329
|
function readNodeFast(e) {
|
|
921
|
-
if (latestReadActive || pendingCheckActive || e.
|
|
1330
|
+
if (latestReadActive || pendingCheckActive || e.ce || e.Te || e.o?.Ce !== undefined || e.o?.nt !== undefined || activeTransition !== null || currentOptimisticLane !== null || snapshotCaptureActive ||
|
|
1331
|
+
// A28: a staged node read outside a flush may serve its committed value;
|
|
1332
|
+
// that arm lives on the slow path so this body stays inlinable.
|
|
1333
|
+
unflushedStaged && e.ve !== NOT_PENDING || false) return READ_SLOW;
|
|
922
1334
|
let t = context;
|
|
923
|
-
if (t?.
|
|
1335
|
+
if (t?.xt) t = t.Qt;
|
|
924
1336
|
if (t && tracking) link(e, t);
|
|
925
1337
|
// Children-forbidden readers (createTrackedEffect / onSettled callbacks) get
|
|
926
1338
|
// committed visibility: like the effect half of createEffect and event
|
|
@@ -932,7 +1344,8 @@ function readNodeFast(e) {
|
|
|
932
1344
|
// the same rule the slow path applies (#3322). Without it a zombie
|
|
933
1345
|
// recompute, or a commit-time re-derive of a contested effect, published
|
|
934
1346
|
// another transaction's uncommitted value.
|
|
935
|
-
return !t || e.
|
|
1347
|
+
return !t || e.ve === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN || stale && heldFromStale(e, t) ? e.Qe : (enterStagedRead(e),
|
|
1348
|
+
e.ve);
|
|
936
1349
|
}
|
|
937
1350
|
|
|
938
1351
|
function read(e) {
|
|
@@ -940,31 +1353,33 @@ function read(e) {
|
|
|
940
1353
|
// Checked before isPending so that isPending(() => latest(x)) checks
|
|
941
1354
|
// the _pendingSignal of _latestValueComputed (async in flight) rather
|
|
942
1355
|
// than the original node (which stays "pending" while held in a transition).
|
|
943
|
-
if (latestReadActive) return GlobalQueue.
|
|
1356
|
+
if (latestReadActive) return GlobalQueue.zt(e);
|
|
944
1357
|
let t = context;
|
|
945
|
-
if (t?.
|
|
1358
|
+
if (t?.xt) t = t.Qt;
|
|
946
1359
|
const n = e;
|
|
947
|
-
const i = e.
|
|
1360
|
+
const i = e.Te;
|
|
948
1361
|
const l = i || e;
|
|
949
1362
|
// Handle isPending() mode: collect pending state while preserving normal read semantics.
|
|
950
1363
|
// Probe mode is suspended while preparing the node so nested reads during a
|
|
951
1364
|
// recompute don't collect into the probe.
|
|
952
1365
|
if (pendingCheckActive) {
|
|
953
|
-
GlobalQueue.
|
|
954
|
-
} else if (typeof n.
|
|
1366
|
+
GlobalQueue.Jt(e, t, l, i);
|
|
1367
|
+
} else if (typeof n.ce === "function") {
|
|
955
1368
|
prepareComputed(e, false);
|
|
956
1369
|
}
|
|
957
|
-
if (!n.
|
|
1370
|
+
if (!n.ce && l === e && e.o?.Ce === undefined && e.o?.nt === undefined && activeTransition === null && currentOptimisticLane === null && !snapshotCaptureActive && (!unflushedStaged || e.ve === NOT_PENDING) && // A28, see readNodeFast
|
|
1371
|
+
true) {
|
|
958
1372
|
if (t && tracking) link(e, t);
|
|
959
1373
|
// Committed visibility for children-forbidden readers and for stale
|
|
960
1374
|
// readers of a foreign transaction's staged write — see readNodeFast.
|
|
961
|
-
return !t || e.
|
|
1375
|
+
return !t || e.ve === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN || stale && heldFromStale(e, t) ? e.Qe : (enterStagedRead(e),
|
|
1376
|
+
e.ve);
|
|
962
1377
|
}
|
|
963
1378
|
if (t && tracking) {
|
|
964
1379
|
link(e, t, pendingCheckActive);
|
|
965
|
-
if (l.
|
|
1380
|
+
if (l.ce) {
|
|
966
1381
|
const n = queueFor(e);
|
|
967
|
-
if (l.
|
|
1382
|
+
if (l.tt >= n.et) {
|
|
968
1383
|
markNode(t);
|
|
969
1384
|
markHeap(n);
|
|
970
1385
|
updateIfNecessary(l);
|
|
@@ -976,10 +1391,10 @@ function read(e) {
|
|
|
976
1391
|
// node no-ops and updateIfNecessary refuses disposed nodes (#2983) —
|
|
977
1392
|
// a dead target serves its last value, which is already quiescent.
|
|
978
1393
|
else if (t.T & CONFIG_FRESH_READ) updateIfNecessary(l);
|
|
979
|
-
const i = l.
|
|
1394
|
+
const i = l.tt;
|
|
980
1395
|
// parent check is shallow, might need to be recursive
|
|
981
|
-
if (i >= t.
|
|
982
|
-
t.
|
|
1396
|
+
if (i >= t.tt && e._parent !== t) {
|
|
1397
|
+
t.tt = i + 1;
|
|
983
1398
|
}
|
|
984
1399
|
}
|
|
985
1400
|
}
|
|
@@ -1000,14 +1415,17 @@ function read(e) {
|
|
|
1000
1415
|
// (firewall-backed store reads always did; plain memos since the #3043
|
|
1001
1416
|
// port): falling through served `undefined` as if settled and stranded
|
|
1002
1417
|
// the reader outside both transactions, so it never re-ran.
|
|
1003
|
-
if (t && !(stale && !(l.S & STATUS_UNINITIALIZED) && !(l.T & CONFIG_INPUTS_PUBLISHED) && !(l.T & CONFIG_HAS_LANE && GlobalQueue.
|
|
1418
|
+
if (t && !(stale && !(l.S & STATUS_UNINITIALIZED) && !(l.T & CONFIG_INPUTS_PUBLISHED) && !(l.T & CONFIG_HAS_LANE && GlobalQueue.Xt(l)) && heldFromStale(l, t))) {
|
|
1004
1419
|
// Per-lane suspension lives with the engine (a non-null lane implies it
|
|
1005
1420
|
// is installed): under a lane, only same-lane pending async without an
|
|
1006
1421
|
// active override throws — plus uninitialized sources regardless of
|
|
1007
1422
|
// lane (#3276); that check rides laneSuspends so floor bundles don't
|
|
1008
1423
|
// pay for it.
|
|
1009
|
-
if (currentOptimisticLane === null || GlobalQueue
|
|
1010
|
-
|
|
1424
|
+
if (currentOptimisticLane === null || GlobalQueue.$t(l)) {
|
|
1425
|
+
// An untracked read of a pending node still re-runs its reader when
|
|
1426
|
+
// the node settles — unless nobody is reading (`spectating`, #3528)
|
|
1427
|
+
// or the reader is the node itself.
|
|
1428
|
+
if (!tracking && !spectating && e !== t) link(e, t);
|
|
1011
1429
|
throw l.o?._;
|
|
1012
1430
|
}
|
|
1013
1431
|
} else if (!t && l.S & STATUS_UNINITIALIZED) {
|
|
@@ -1018,26 +1436,60 @@ function read(e) {
|
|
|
1018
1436
|
// firewall-backed reads follow the same rules (memo parity, #2897 ruling):
|
|
1019
1437
|
// an errored derive throws for every late reader instead of silently
|
|
1020
1438
|
// serving node values (the seed, or last-good data after a failed refetch).
|
|
1021
|
-
if (l.
|
|
1439
|
+
if (l.ce && l.S & STATUS_ERROR) {
|
|
1022
1440
|
// Only a genuine reactive re-read may retry an errored async source:
|
|
1023
1441
|
// - tracking: owned/tracked scope only (never events / `untrack` / effect side-effect phase)
|
|
1024
1442
|
// - !pendingCheckActive: an `isPending` probe observes the error, never refetches
|
|
1025
1443
|
// - owner._time < clock: only on a later cycle than the one the error was found
|
|
1026
|
-
if (tracking && !pendingCheckActive && l.
|
|
1444
|
+
if (tracking && !pendingCheckActive && l.Pe < clock) {
|
|
1027
1445
|
recompute(l);
|
|
1028
1446
|
return read(e);
|
|
1029
1447
|
} else throw l.o?._;
|
|
1030
1448
|
}
|
|
1031
1449
|
if (snapshotCaptureActive && t && t.T & CONFIG_IN_SNAPSHOT_SCOPE) {
|
|
1032
|
-
const n = e.o?.
|
|
1450
|
+
const n = e.o?.nt;
|
|
1033
1451
|
if (n !== undefined) {
|
|
1034
1452
|
const i = n === NO_SNAPSHOT ? undefined : n;
|
|
1035
|
-
const l = e.
|
|
1036
|
-
if (l !== i) t.
|
|
1453
|
+
const l = e.ve !== NOT_PENDING ? e.ve : e.Qe;
|
|
1454
|
+
if (l !== i) t.ue |= REACTIVE_SNAPSHOT_STALE;
|
|
1037
1455
|
return i;
|
|
1038
1456
|
}
|
|
1039
1457
|
}
|
|
1040
|
-
|
|
1458
|
+
const u = serve(e, t, l, e.Qe);
|
|
1459
|
+
if (!t && l === e && typeof n.ce === "function" && e.T & CONFIG_AUTO_DISPOSE && !(l.S & STATUS_PENDING) && !e.u &&
|
|
1460
|
+
// An untracked read served a visible override returned before this
|
|
1461
|
+
// sweep registration when the arm was inline; keep that.
|
|
1462
|
+
!visibleOverride(e)) {
|
|
1463
|
+
// Deferred, not inline (#3078): an inline unobserved() here made untracked
|
|
1464
|
+
// reads destructive — dispose on this read, full revival recompute on the
|
|
1465
|
+
// next — so consecutive reads could answer differently with no write in
|
|
1466
|
+
// between (the revival samples the ambient transition/lane context).
|
|
1467
|
+
// The sweep at flush finalization re-validates and reclaims; schedule()
|
|
1468
|
+
// guarantees that flush happens even if nothing else is queued.
|
|
1469
|
+
dormantNodes.add(e);
|
|
1470
|
+
schedule();
|
|
1471
|
+
}
|
|
1472
|
+
return u;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
/**
|
|
1476
|
+
* Rule 1, the one slow implementation (DESIGN-CONSOLIDATION move 3b, step
|
|
1477
|
+
* 6c): the value a reader `c` (null = untracked, no pass) is served from
|
|
1478
|
+
* `el`, whose committed value is `committed` — the node's own `_value` for
|
|
1479
|
+
* a signal or memo, the BACKING for a store property node (single-home rule,
|
|
1480
|
+
* O6: committed truth lives in the backing and a node's `_value` is never
|
|
1481
|
+
* served for one). Called by read()'s slow tail and by the store's untracked
|
|
1482
|
+
* node path (nodeValue); the fast paths (readNodeFast, read's fast block)
|
|
1483
|
+
* keep their trivial ternary by design (perf, see the doc). Arms, in order:
|
|
1484
|
+
* - the override (A17), routed through the engine for a tracked reader under
|
|
1485
|
+
* a lane or a supersession (A18), an authoritative reader marked instead;
|
|
1486
|
+
* - the lane entanglement gate (committed, recorded for replay);
|
|
1487
|
+
* - a node born held has nothing for an untracked reader (A19 exception 1);
|
|
1488
|
+
* - an unflushed write serves committed and re-runs the reader in the
|
|
1489
|
+
* carrying flush (A28);
|
|
1490
|
+
* - readerSeesCommitted, else the staged value and the transaction (A29).
|
|
1491
|
+
*/ function serve(e, t, n, i) {
|
|
1492
|
+
if (hasActiveOverride(e)) {
|
|
1041
1493
|
// A17: the override IS the value for every reader — except an authoritative
|
|
1042
1494
|
// reader (until()'s predicate carries CONFIG_AUTHORITATIVE_READ): it must
|
|
1043
1495
|
// observe independently-arriving truth, and serving it the caller's own
|
|
@@ -1048,14 +1500,18 @@ function read(e) {
|
|
|
1048
1500
|
// authoritative — optimism never lives there); the sticky mark makes the
|
|
1049
1501
|
// A17-silent "landing equals override" paths notify this node's subs so
|
|
1050
1502
|
// the reader re-runs when truth arrives.
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
//
|
|
1056
|
-
//
|
|
1057
|
-
|
|
1058
|
-
|
|
1503
|
+
// A28 (5): an optimistic write written this tick, outside a flush, is not
|
|
1504
|
+
// yet the active override — fall through to the normal selection (the
|
|
1505
|
+
// authoritative mark below still applies: until() must wake on landing).
|
|
1506
|
+
if (!(t && t.T & CONFIG_AUTHORITATIVE_READ) && !unflushedOverride(e)) {
|
|
1507
|
+
// A tracked read of an override is the engine's selection (a lane or a
|
|
1508
|
+
// supersession implies the engine): a render effect OFF the override's
|
|
1509
|
+
// held lane sees the committed value (#3460, lanes mirror transitions);
|
|
1510
|
+
// a node whose own source answered with a DIFFERENT value hands a
|
|
1511
|
+
// tracked reader the staged truth (A18 supersession, #3331). Untracked
|
|
1512
|
+
// reads display the override.
|
|
1513
|
+
if (t && e.T & (CONFIG_HAS_LANE | CONFIG_OVERRIDE_SUPERSEDED)) return GlobalQueue.en(e, t);
|
|
1514
|
+
return unwrapOverride(e.o?.Ce);
|
|
1059
1515
|
}
|
|
1060
1516
|
e.T |= CONFIG_AUTHORITATIVE_OBSERVED;
|
|
1061
1517
|
}
|
|
@@ -1066,8 +1522,8 @@ function read(e) {
|
|
|
1066
1522
|
// _pendingValue for correct fetching. The sub is recorded for replay at commit
|
|
1067
1523
|
// so it re-runs with the new committed view. (Gate details live with the
|
|
1068
1524
|
// engine — a non-null lane implies it is installed.)
|
|
1069
|
-
if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.
|
|
1070
|
-
return
|
|
1525
|
+
if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.tn(e, n, t)) {
|
|
1526
|
+
return i;
|
|
1071
1527
|
}
|
|
1072
1528
|
// In optimistic lane context, return _value for optimistic/lane-assigned signals
|
|
1073
1529
|
// and for regular signals in stale mode (render effects). Non-stale readers (user
|
|
@@ -1075,70 +1531,110 @@ function read(e) {
|
|
|
1075
1531
|
// (The lane-context clause lives with the engine.) Children-forbidden readers
|
|
1076
1532
|
// (createTrackedEffect / onSettled callbacks) get committed visibility — see
|
|
1077
1533
|
// readNodeFast (#3006).
|
|
1078
|
-
|
|
1079
|
-
//
|
|
1080
|
-
//
|
|
1081
|
-
//
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1534
|
+
// A node born held (recompute) has a staged value and no committed one: a
|
|
1535
|
+
// stale reader cannot fall back to the committed frame, so it takes the
|
|
1536
|
+
// staged value and enters like a tracked reader (A29); an untracked reader
|
|
1537
|
+
// has nothing to serve and holds (A19 exception 1).
|
|
1538
|
+
const l = e.ve !== NOT_PENDING && (e.S & STATUS_UNINITIALIZED) !== 0;
|
|
1539
|
+
if (l && !t) throw new NotReadyError(null);
|
|
1540
|
+
const u = t && unflushedStaged ? unflushedValue(e, i) : NOT_PENDING;
|
|
1541
|
+
if (u !== NOT_PENDING) {
|
|
1542
|
+
markLateLinker(t);
|
|
1543
|
+
if (pendingCheckActive) GlobalQueue.nn(e, u);
|
|
1544
|
+
return u;
|
|
1545
|
+
}
|
|
1546
|
+
const o = readerSeesCommitted(e, t, n, l) ? i : (enterStagedRead(e), e.ve);
|
|
1088
1547
|
// Record that this isPending() probe observed the fresh pending value, so
|
|
1089
1548
|
// the probe doesn't pair "pending" with the new value (#2831).
|
|
1090
|
-
if (pendingCheckActive) GlobalQueue.
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1549
|
+
if (pendingCheckActive) GlobalQueue.nn(e, o);
|
|
1550
|
+
return o;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
/** A28 — a rewrite of a HELD node outside a flush keeps the staged value the
|
|
1554
|
+
* last flush left, for latest()/verdicts, until the next flush carries it
|
|
1555
|
+
* (stash, cleared at that flush). Cold: only stamped nodes reach here. */ function stashHeldRewrite(e) {
|
|
1556
|
+
if (globalQueue.Kt) return;
|
|
1557
|
+
const t = ext(e);
|
|
1558
|
+
if (t.gt === NOT_PENDING) {
|
|
1559
|
+
t.gt = e.ve;
|
|
1560
|
+
unflushedRewrites.push(e);
|
|
1561
|
+
unflushedStaged = true;
|
|
1100
1562
|
}
|
|
1101
|
-
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
/** A28 (4) — written inside a recompute that runs OUTSIDE a flush (a
|
|
1566
|
+
* creation-time compute): promoted at that pass's end, visible to the rest of
|
|
1567
|
+
* the block. Cold: only contextual writes reach here. */ function notePromotedWrite(e) {
|
|
1568
|
+
if (globalQueue.Kt || e.T & CONFIG_PROMOTED) return;
|
|
1569
|
+
e.T |= CONFIG_PROMOTED;
|
|
1570
|
+
promotedWrites.push(e);
|
|
1102
1571
|
}
|
|
1103
1572
|
|
|
1104
1573
|
function setSignal(e, t) {
|
|
1105
|
-
|
|
1574
|
+
// A write to a held node is a second proposal on a contested node (A34, #3494):
|
|
1575
|
+
// the writer's tick reveals with the hold, the same value or another. Inside
|
|
1576
|
+
// a flush the round enters now; from mainline the entry waits for the next
|
|
1577
|
+
// flush's start (batchJoins) — entering here left activeTransition set for
|
|
1578
|
+
// the rest of the caller's block, so a memo created after the write became
|
|
1579
|
+
// the transaction's instead of mainline's (A28, A29). Before the equality
|
|
1580
|
+
// gate below: repeating the held value proposes it too — and that repeat
|
|
1581
|
+
// leaves through the gate, so the join schedules its own flush here; left
|
|
1582
|
+
// for the next flush to find, it adopted an unrelated tick (#3519 review).
|
|
1583
|
+
if (e.Ge && activeTransition !== e.Ge) {
|
|
1584
|
+
if (globalQueue.Kt) globalQueue.initTransition(e.Ge); else {
|
|
1585
|
+
batchJoins.push(e.Ge);
|
|
1586
|
+
// dupes: a bare return in initTransition
|
|
1587
|
+
schedule();
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1106
1590
|
// The optimistic write path lives with the engine: only optimisticSignal /
|
|
1107
1591
|
// optimisticComputed callers and optimistic store nodes carry an
|
|
1108
1592
|
// _overrideValue slot (flagged by CONFIG_OPTIMISTIC — a masked read of the
|
|
1109
1593
|
// always-present config instead of a missing-property probe), and every
|
|
1110
1594
|
// module that installs one installs the engine first.
|
|
1111
1595
|
if (e.T & CONFIG_OPTIMISTIC) {
|
|
1112
|
-
if (!projectionWriteActive) return GlobalQueue.
|
|
1596
|
+
if (!projectionWriteActive) return GlobalQueue.ln(e, t);
|
|
1113
1597
|
// An authoritative store landing on an override-covered node: the store
|
|
1114
1598
|
// twin of asyncWrite's override branch, decided by the engine (#3331).
|
|
1115
|
-
const n = e.o?.
|
|
1116
|
-
if (n !== undefined && n !== NOT_PENDING) return GlobalQueue.
|
|
1599
|
+
const n = e.o?.Ce;
|
|
1600
|
+
if (n !== undefined && n !== NOT_PENDING) return GlobalQueue.un(e, t);
|
|
1117
1601
|
}
|
|
1118
|
-
const n = e.
|
|
1602
|
+
const n = e.ve === NOT_PENDING ? e.Qe : e.ve;
|
|
1119
1603
|
if (typeof t === "function") t = t(n);
|
|
1120
1604
|
// Uninitialized check first: the first commit has no previous value, so the
|
|
1121
1605
|
// user comparator must not run against `undefined` (matches recompute).
|
|
1122
|
-
const i = !!(e.S & STATUS_UNINITIALIZED) || !e.
|
|
1606
|
+
const i = !!(e.S & STATUS_UNINITIALIZED) || !e.Fe || !e.Fe(n, t);
|
|
1123
1607
|
if (!i) return t;
|
|
1124
|
-
const l = e.
|
|
1608
|
+
const l = e.ve !== NOT_PENDING;
|
|
1125
1609
|
if (!l) queuePendingNode(e);
|
|
1126
|
-
|
|
1610
|
+
// A28 arms, gated on the loads the write already pays for (a plain ambient
|
|
1611
|
+
// rewrite outside a flush — the write-loop shape — costs `_transition` and
|
|
1612
|
+
// `context` here and nothing else; the arms themselves are cold helpers so
|
|
1613
|
+
// setSignal stays within every setter's inlining budget, ~300 B bytecode).
|
|
1614
|
+
else if (e.Ge !== null) stashHeldRewrite(e);
|
|
1615
|
+
e.ve = t;
|
|
1616
|
+
if (context !== null) notePromotedWrite(e);
|
|
1127
1617
|
// syncCompanions only pokes _pendingSignal/_latestValueComputed — with
|
|
1128
1618
|
// neither companion present the call is a guaranteed no-op (companions are
|
|
1129
1619
|
// only ever created, never removed, and creating one installs the hook and
|
|
1130
1620
|
// sets CONFIG_HAS_COMPANIONS — one masked read replaces two optional-field
|
|
1131
1621
|
// probes on every write).
|
|
1132
|
-
e.T & CONFIG_HAS_COMPANIONS && GlobalQueue.
|
|
1622
|
+
if (e.T & CONFIG_HAS_COMPANIONS && GlobalQueue.me !== null) {
|
|
1623
|
+
GlobalQueue.me(e, t);
|
|
1624
|
+
// A28 (2): the verdict computed here is the PRE-flush one (an unflushed
|
|
1625
|
+
// write is not pending); the flush that carries the write re-syncs so
|
|
1626
|
+
// the companions mirror the flushed world. Only companion nodes pay.
|
|
1627
|
+
if (!globalQueue.Kt) unflushedCompanions.push(e);
|
|
1628
|
+
}
|
|
1133
1629
|
// _time is a computed-only slot (§12e): writing it on a signal would fork
|
|
1134
1630
|
// the lean shape. Every read site is computed-typed.
|
|
1135
|
-
if (e.
|
|
1631
|
+
if (e.ce !== undefined) e.Pe = clock;
|
|
1136
1632
|
// Staged-rewrite fast path (§12d): a re-write to a node whose subscribers
|
|
1137
1633
|
// were already walked — and where nothing has recomputed or linked since
|
|
1138
1634
|
// (epoch) — re-stages the value and stops. The walk is idempotent (subs
|
|
1139
1635
|
// marked, heap entries flag-guarded, effects queued once); lane and reask
|
|
1140
1636
|
// contexts change what a walk MEANS, so they always walk.
|
|
1141
|
-
if (l && e.
|
|
1637
|
+
if (l && e.ht === notifyEpoch && currentOptimisticLane === null && !reaskArmed) return t;
|
|
1142
1638
|
insertSubs(e);
|
|
1143
1639
|
schedule();
|
|
1144
1640
|
return t;
|
|
@@ -1152,12 +1648,12 @@ function setSignal(e, t) {
|
|
|
1152
1648
|
* cleanup point.
|
|
1153
1649
|
*/ function suppressComputedRecompute(e) {
|
|
1154
1650
|
deleteFromHeap(e, queueFor(e));
|
|
1155
|
-
if (!(e.
|
|
1651
|
+
if (!(e.ue & REACTIVE_MANUAL_WRITE) && e.ve === NOT_PENDING) {
|
|
1156
1652
|
queuePendingNode(e);
|
|
1157
1653
|
schedule();
|
|
1158
1654
|
}
|
|
1159
|
-
e.
|
|
1160
|
-
e.
|
|
1655
|
+
e.ue = e.ue & -4 | REACTIVE_MANUAL_WRITE;
|
|
1656
|
+
e.sn = clock;
|
|
1161
1657
|
}
|
|
1162
1658
|
|
|
1163
1659
|
/**
|
|
@@ -1219,16 +1715,16 @@ function staleValues(e, t = true) {
|
|
|
1219
1715
|
* recompute as a quiet re-ask and schedules it; no-ops for non-derived or
|
|
1220
1716
|
* disposed targets and for same-tick manual writes.
|
|
1221
1717
|
*/ function markRefresh(e) {
|
|
1222
|
-
if (typeof e.
|
|
1223
|
-
if (e.
|
|
1718
|
+
if (typeof e.ce === "function" && !(e.ue & REACTIVE_DISPOSED)) {
|
|
1719
|
+
if (e.ue & REACTIVE_MANUAL_WRITE) {
|
|
1224
1720
|
// A manual write in the CURRENT tick wins over the refresh (#2692).
|
|
1225
1721
|
// A mask stamped in an earlier tick only survives because a
|
|
1226
1722
|
// transaction (action) is holding the pending drain open; there the
|
|
1227
1723
|
// refresh is a later, explicit re-ask and lifts the mask — otherwise
|
|
1228
1724
|
// any setStore early in an action silently swallows every refresh()
|
|
1229
1725
|
// for the rest of the transaction (#3026).
|
|
1230
|
-
if (e.
|
|
1231
|
-
e.
|
|
1726
|
+
if (e.sn === clock) return;
|
|
1727
|
+
e.ue &= ~REACTIVE_MANUAL_WRITE;
|
|
1232
1728
|
// The lift falls through to the re-ask classification below. The held
|
|
1233
1729
|
// write's value change already rides the transaction; the refetch it
|
|
1234
1730
|
// asks for is the same question with unchanged inputs. Skipping the
|
|
@@ -1245,14 +1741,14 @@ function staleValues(e, t = true) {
|
|
|
1245
1741
|
// REACTIVE_IN_HEAP counts as dirt: insertSubs schedules subscribers by
|
|
1246
1742
|
// heap insertion alone (no DIRTY/CHECK flag), so a same-batch value
|
|
1247
1743
|
// change followed by refresh() must not be laundered into a quiet re-ask.
|
|
1248
|
-
if (!(e.
|
|
1249
|
-
e.
|
|
1744
|
+
if (!(e.ue & (REACTIVE_DIRTY | REACTIVE_CHECK | REACTIVE_IN_HEAP))) {
|
|
1745
|
+
e.ue |= REACTIVE_REASK;
|
|
1250
1746
|
armReaskClear();
|
|
1251
1747
|
}
|
|
1252
|
-
e.
|
|
1748
|
+
e.ue = e.ue & ~REACTIVE_CHECK | REACTIVE_DIRTY;
|
|
1253
1749
|
insertIntoHeap(e, queueFor(e));
|
|
1254
1750
|
schedule();
|
|
1255
1751
|
}
|
|
1256
1752
|
}
|
|
1257
1753
|
|
|
1258
|
-
export { READ_SLOW, clearSnapshots, computed, context, createEffectNode, currentOptimisticLane, effectStatusNotify, ext, installAuthoritativeRead, isEqual, latestReadActive, markRefresh, markSnapshotScope, notifyAuthoritativeObservers, optimisticComputed, optimisticSignal, pendingCheckActive, prepareComputed, read, readNodeFast, recompute, releaseSnapshotScope, runWithOwner, setContextInternal, setEffectStatusNotify, setLatestReadActive, setMemo, setPendingCheckActive, setSignal, setSlotUnobserved, setSnapshotCapture, signal, slotSignal, slotUnobservedHook, snapshotCaptureActive, snapshotSources, stale, staleValues, statusNotifierOf, suppressComputedRecompute, tracking, unlinkFirewallChild, untrack };
|
|
1754
|
+
export { READ_SLOW, clearSnapshots, computed, context, createEffectNode, currentOptimisticLane, effectStatusNotify, enterStagedRead, ext, hasActiveOverride, installAuthoritativeRead, isEqual, latestReadActive, markLateLinker, markRefresh, markSnapshotScope, markUnflushedStaged, notifyAuthoritativeObservers, optimisticComputed, optimisticSignal, ownsHold, pendingCheckActive, prepareComputed, read, readNodeFast, readerSeesCommitted, recompute, recordStaleReplay, releaseSnapshotScope, resyncUnflushedCompanions, runWithOwner, serve, setContextInternal, setEffectStatusNotify, setLatestReadActive, setMemo, setPendingCheckActive, setSignal, setSlotUnobserved, setSnapshotCapture, signal, slotSignal, slotUnobservedHook, snapshotCaptureActive, snapshotSources, spectate, spectating, stale, staleValues, statusNotifierOf, suppressComputedRecompute, tracking, unflushed, unflushedCompanions, unflushedOverride, unflushedStaged, unflushedValue, unlinkFirewallChild, untrack, visibleOverride };
|