@solidjs/signals 2.0.0-rc.1 → 2.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev.js +1661 -361
- package/dist/node.cjs +1939 -1331
- package/dist/prod/affects.js +16 -16
- package/dist/prod/boundaries.js +90 -90
- package/dist/prod/core/action.js +3 -3
- package/dist/prod/core/async.js +74 -72
- package/dist/prod/core/constants.js +39 -1
- package/dist/prod/core/core.js +332 -224
- package/dist/prod/core/effect.js +56 -56
- package/dist/prod/core/external.js +4 -4
- package/dist/prod/core/graph.js +65 -54
- package/dist/prod/core/heap.js +52 -44
- package/dist/prod/core/invariants.js +3 -2
- package/dist/prod/core/lanes.js +41 -34
- package/dist/prod/core/optimistic.js +63 -60
- package/dist/prod/core/owner.js +97 -96
- package/dist/prod/core/scheduler.js +243 -194
- package/dist/prod/core/verdict.js +184 -77
- package/dist/prod/map.js +104 -104
- package/dist/prod/signals.js +1 -1
- package/dist/prod/store/next/optimistic.js +31 -23
- package/dist/prod/store/next/projection.js +3 -3
- package/dist/prod/store/next/reconcile.js +78 -74
- package/dist/prod/store/next/store.js +357 -90
- package/dist/prod/store/store.js +7 -7
- package/dist/types/core/attribution-hooks.d.ts +52 -0
- package/dist/types/core/attribution.d.ts +186 -0
- package/dist/types/core/constants.d.ts +26 -0
- package/dist/types/core/core.d.ts +8 -1
- package/dist/types/core/dev.d.ts +20 -3
- package/dist/types/core/graph.d.ts +1 -0
- package/dist/types/core/lanes.d.ts +4 -16
- package/dist/types/core/scheduler.d.ts +8 -0
- package/dist/types/core/types.d.ts +85 -41
- package/dist/types/store/next/projection.d.ts +0 -16
- package/dist/types/store/next/store.d.ts +6 -0
- package/dist/types/store/next/target.d.ts +18 -0
- package/dist/types-cjs/core/attribution-hooks.d.cts +52 -0
- package/dist/types-cjs/core/attribution.d.cts +186 -0
- package/dist/types-cjs/core/constants.d.cts +26 -0
- package/dist/types-cjs/core/core.d.cts +8 -1
- package/dist/types-cjs/core/dev.d.cts +20 -3
- package/dist/types-cjs/core/graph.d.cts +1 -0
- package/dist/types-cjs/core/lanes.d.cts +4 -16
- package/dist/types-cjs/core/scheduler.d.cts +8 -0
- package/dist/types-cjs/core/types.d.cts +85 -41
- package/dist/types-cjs/store/next/projection.d.cts +0 -16
- package/dist/types-cjs/store/next/store.d.cts +6 -0
- package/dist/types-cjs/store/next/target.d.cts +18 -0
- package/package.json +1 -1
package/dist/prod/core/core.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { handleAsync, clearStatus, parkLoadingWindow, notifyStatus, settleErroredDependents } from "./async.js";
|
|
2
2
|
|
|
3
|
-
import { EFFECT_TRACKED, REACTIVE_OPTIMISTIC_DIRTY, NOT_PENDING, STATUS_UNINITIALIZED, STATUS_ERROR, REACTIVE_REASK, REACTIVE_RECOMPUTING_DEPS, CONFIG_SYNC, STATUS_PENDING, REACTIVE_NONE, REACTIVE_SNAPSHOT_STALE, unwrapOverride, OVERRIDE_UNDEFINED, REACTIVE_LAZY, REACTIVE_DISPOSED,
|
|
3
|
+
import { EFFECT_TRACKED, REACTIVE_OPTIMISTIC_DIRTY, CONFIG_OPTIMISTIC, NOT_PENDING, STATUS_UNINITIALIZED, STATUS_ERROR, REACTIVE_REASK, REACTIVE_RECOMPUTING_DEPS, CONFIG_SYNC, CONFIG_HAS_LANE, STATUS_PENDING, REACTIVE_MISSED_WAKE, REACTIVE_NONE, REACTIVE_SNAPSHOT_STALE, unwrapOverride, OVERRIDE_UNDEFINED, CONFIG_HAS_COMPANIONS, REACTIVE_LAZY, REACTIVE_DISPOSED, CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, REACTIVE_CHECK, REACTIVE_DIRTY, REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, defaultContext, CONFIG_IN_SNAPSHOT_SCOPE, CONFIG_TRANSPARENT, CONFIG_OWNED_WRITE, CONFIG_NO_SNAPSHOT, $REFRESH, REACTIVE_MANUAL_WRITE, CONFIG_FW_CHILDREN, NO_SNAPSHOT, CONFIG_HAS_SNAPSHOT, STORE_SNAPSHOT_PROPS, EFFECT_USER } from "./constants.js";
|
|
4
4
|
|
|
5
5
|
import { NotReadyError } from "./error.js";
|
|
6
6
|
|
|
7
7
|
import { trimStaleDeps, link, unobserved } from "./graph.js";
|
|
8
8
|
|
|
9
|
-
import { deleteFromHeap, queueFor, insertIntoHeapHeight, markNode, markHeap, insertIntoHeap } from "./heap.js";
|
|
9
|
+
import { deleteFromHeap, queueFor, insertIntoHeapHeight, enqueueSub, markNode, markHeap, insertIntoHeap } from "./heap.js";
|
|
10
10
|
|
|
11
|
-
import { GlobalQueue, activeTransition, globalQueue, clock, insertSubs, queuePendingNode, runInTransition, schedule, dirtyQueue, projectionWriteActive, armReaskClear } from "./scheduler.js";
|
|
11
|
+
import { GlobalQueue, activeTransition, globalQueue, clock, insertSubs, queuePendingNode, runInTransition, schedule, notifyEpoch, dirtyQueue, bumpNotifyEpoch, projectionWriteActive, reaskArmed, armReaskClear } from "./scheduler.js";
|
|
12
12
|
|
|
13
13
|
import "./invariants.js";
|
|
14
14
|
|
|
@@ -16,7 +16,7 @@ import { disposeChildren, markDisposal, inheritId } from "./owner.js";
|
|
|
16
16
|
|
|
17
17
|
GlobalQueue.Ce = recompute;
|
|
18
18
|
|
|
19
|
-
GlobalQueue.
|
|
19
|
+
GlobalQueue.Fe = disposeChildren;
|
|
20
20
|
|
|
21
21
|
let tracking = false;
|
|
22
22
|
|
|
@@ -48,8 +48,8 @@ let snapshotSources = null;
|
|
|
48
48
|
|
|
49
49
|
function ownerInSnapshotScope(e) {
|
|
50
50
|
while (e) {
|
|
51
|
-
if (e.
|
|
52
|
-
e = e.
|
|
51
|
+
if (e.He) return true;
|
|
52
|
+
e = e.ve;
|
|
53
53
|
}
|
|
54
54
|
return false;
|
|
55
55
|
}
|
|
@@ -60,11 +60,11 @@ function setSnapshotCapture(e) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
function markSnapshotScope(e) {
|
|
63
|
-
e.
|
|
63
|
+
e.He = true;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
function releaseSnapshotScope(e) {
|
|
67
|
-
e.
|
|
67
|
+
e.He = false;
|
|
68
68
|
releaseSubtree(e);
|
|
69
69
|
schedule();
|
|
70
70
|
}
|
|
@@ -72,29 +72,29 @@ function releaseSnapshotScope(e) {
|
|
|
72
72
|
function releaseSubtree(e) {
|
|
73
73
|
let t = e.ke;
|
|
74
74
|
while (t) {
|
|
75
|
-
if (t.
|
|
76
|
-
t = t.
|
|
75
|
+
if (t.He) {
|
|
76
|
+
t = t.Ve;
|
|
77
77
|
continue;
|
|
78
78
|
}
|
|
79
|
-
if (t.
|
|
79
|
+
if (t.Se) {
|
|
80
80
|
const e = t;
|
|
81
81
|
e.T &= ~CONFIG_IN_SNAPSHOT_SCOPE;
|
|
82
|
-
if (e.
|
|
83
|
-
e.
|
|
84
|
-
e.
|
|
85
|
-
if (dirtyQueue.
|
|
82
|
+
if (e.ie & REACTIVE_SNAPSHOT_STALE) {
|
|
83
|
+
e.ie &= ~REACTIVE_SNAPSHOT_STALE;
|
|
84
|
+
e.ie |= REACTIVE_DIRTY;
|
|
85
|
+
if (dirtyQueue.xe > e.Le) dirtyQueue.xe = e.Le;
|
|
86
86
|
insertIntoHeap(e, dirtyQueue);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
releaseSubtree(t);
|
|
90
|
-
t = t.
|
|
90
|
+
t = t.Ve;
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
function clearSnapshots() {
|
|
95
95
|
if (snapshotSources) {
|
|
96
96
|
for (const e of snapshotSources) {
|
|
97
|
-
delete e.
|
|
97
|
+
delete e.o?.Qe;
|
|
98
98
|
// StoreNode targets share one pre-initialized hidden class (see
|
|
99
99
|
// createStoreProxy) — assign undefined instead of deleting, and only
|
|
100
100
|
// when present so signal-node sources don't grow the field.
|
|
@@ -106,46 +106,50 @@ function clearSnapshots() {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
function recompute(e, t = false) {
|
|
109
|
+
// §12d: any recompute can clean a marked subscriber — invalidate skips.
|
|
110
|
+
bumpNotifyEpoch();
|
|
109
111
|
const n = e.Re;
|
|
110
112
|
if (!t) {
|
|
111
|
-
if (e.
|
|
113
|
+
if (e._e && (!n || activeTransition) && activeTransition !== e._e) globalQueue.initTransition(e._e);
|
|
112
114
|
deleteFromHeap(e, queueFor(e));
|
|
113
|
-
e.
|
|
115
|
+
if (e.o !== null) e.o.Ee = null;
|
|
114
116
|
// Tracked effects run after finalizePureQueue, so dispose immediately instead of deferring
|
|
115
|
-
if (e.
|
|
117
|
+
if (e._e || n === EFFECT_TRACKED) disposeChildren(e); else if (e.ke !== null || e.he !== null) {
|
|
116
118
|
markDisposal(e);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
const t = ext(e);
|
|
120
|
+
t.We = e.he;
|
|
121
|
+
t.qe = e.ke;
|
|
119
122
|
e.he = null;
|
|
120
123
|
e.ke = null;
|
|
121
|
-
e.
|
|
124
|
+
e.Me = 0;
|
|
122
125
|
} else ;
|
|
123
126
|
}
|
|
124
|
-
let i = !!(e.
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
+
let i = !!(e.ie & REACTIVE_OPTIMISTIC_DIRTY);
|
|
128
|
+
const u = (e.T & CONFIG_OPTIMISTIC) !== 0 && e.o?.De !== NOT_PENDING && e.o?.De !== undefined;
|
|
129
|
+
const l = !!(e.S & STATUS_UNINITIALIZED);
|
|
127
130
|
// Outgoing error, captured before the compute clears status: if this run
|
|
128
131
|
// recovers to an unchanged value, dependents still holding this object must
|
|
129
132
|
// be swept (settleErroredDependents, #2949).
|
|
130
|
-
const
|
|
133
|
+
const o = e.S & STATUS_ERROR ? e.o?._ : undefined;
|
|
131
134
|
// Re-ask classification lives in the verdict module; capture the flag before
|
|
132
135
|
// the recompute wipes _flags below.
|
|
133
|
-
const
|
|
136
|
+
const s = (e.ie & REACTIVE_REASK) !== 0;
|
|
134
137
|
// Captured before the compute clears it on a sync landing: if that landing
|
|
135
138
|
// is transition-held below, the window must stay open until the hold
|
|
136
139
|
// commits (commitPendingNode) — a closed window plus a held value reads as
|
|
137
140
|
// a pending frame to live observers of the verdict (#2990).
|
|
138
|
-
const a = e.
|
|
141
|
+
const a = e.Ie;
|
|
139
142
|
const r = context;
|
|
140
143
|
context = e;
|
|
141
144
|
e.Ye = null;
|
|
142
145
|
e.Ze++;
|
|
143
|
-
e.
|
|
144
|
-
e.
|
|
145
|
-
let c = e.
|
|
146
|
+
e.ie = REACTIVE_RECOMPUTING_DEPS;
|
|
147
|
+
e.Te = clock;
|
|
148
|
+
let c = e.Pe === NOT_PENDING ? e.be : e.Pe;
|
|
146
149
|
let _ = e.Le;
|
|
147
|
-
let f =
|
|
148
|
-
let E =
|
|
150
|
+
let f = false;
|
|
151
|
+
let E = tracking;
|
|
152
|
+
let I = currentOptimisticLane;
|
|
149
153
|
tracking = true;
|
|
150
154
|
// A computed's fn establishes its OWN dependencies, so it must never run
|
|
151
155
|
// inside a latest() read window: read() short-circuits through the
|
|
@@ -165,7 +169,7 @@ function recompute(e, t = false) {
|
|
|
165
169
|
// latest()/isPending() pull stages instead of direct-committing (#3009).
|
|
166
170
|
// The predicate lives with the engine (recomputeLane).
|
|
167
171
|
else if (t === false) i = false;
|
|
168
|
-
} else if (activeTransition && !t && activeTransition.
|
|
172
|
+
} else if (activeTransition && !t && activeTransition.Ke.length) {
|
|
169
173
|
// Lane adoption: parent-deeper-than-owned-child can run before its OPT-dirty
|
|
170
174
|
// child propagates. Walk deps once and inherit the OPT lane so this node
|
|
171
175
|
// recomputes under the right posture and propagates correctly.
|
|
@@ -176,42 +180,44 @@ function recompute(e, t = false) {
|
|
|
176
180
|
}
|
|
177
181
|
}
|
|
178
182
|
const T = n && n !== EFFECT_USER;
|
|
179
|
-
const
|
|
183
|
+
const d = stale;
|
|
180
184
|
if (T) stale = true;
|
|
181
185
|
try {
|
|
182
186
|
if (!false && e.T & CONFIG_SYNC) {
|
|
183
|
-
c = e.
|
|
184
|
-
e.
|
|
185
|
-
e.
|
|
187
|
+
c = e.Se(c);
|
|
188
|
+
if (e.o !== null) e.o.Ee = null;
|
|
189
|
+
e.Ie = false;
|
|
186
190
|
} else {
|
|
187
191
|
// Snapshot `_inFlight` so we can detect whether `_fn` self-registered an async
|
|
188
192
|
// subscription (e.g. `createProjection` calls `handleAsync` from inside its body
|
|
189
193
|
// with a setter callback). In that case, the outer `handleAsync` call below would
|
|
190
194
|
// clobber the fresh subscription, so we skip it and let the internally-registered
|
|
191
195
|
// iteration drive updates.
|
|
192
|
-
const t = e.
|
|
193
|
-
const n = e.
|
|
196
|
+
const t = e.o?.Ee;
|
|
197
|
+
const n = e.Se(c);
|
|
194
198
|
const i = typeof n === "object" && n !== null;
|
|
195
|
-
const
|
|
196
|
-
c =
|
|
197
|
-
if (!
|
|
198
|
-
e.
|
|
199
|
+
const u = e.o?.Ee !== t;
|
|
200
|
+
c = u || !i ? n : handleAsync(e, n);
|
|
201
|
+
if (!u && !i) {
|
|
202
|
+
if (e.o !== null) e.o.Ee = null;
|
|
199
203
|
// A sync (non-object) return is the first real answer; async-shaped
|
|
200
204
|
// results clear inside handleAsync at their own landing points, and a
|
|
201
205
|
// self-registered flight (inFlightChanged — projections) clears when
|
|
202
206
|
// its internal handleAsync lands.
|
|
203
|
-
e.
|
|
207
|
+
e.Ie = false;
|
|
204
208
|
}
|
|
205
209
|
}
|
|
206
|
-
// On a status-free node clearStatus is a guaranteed no-op: every
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
// On a status-free node clearStatus is a guaranteed no-op: every field
|
|
211
|
+
// its body gates on is either _statusFlags or lives in the cold
|
|
212
|
+
// extension — no extension, no status to clear. (_x from an unrelated
|
|
213
|
+
// installer just makes clearStatus a cheap re-verified no-op.)
|
|
214
|
+
if (e.S !== 0 || e.o !== null) clearStatus(e, t);
|
|
215
|
+
// _optimisticLane is only ever assigned by engine paths (CONFIG_HAS_LANE
|
|
216
|
+
// is their sticky presence mark).
|
|
217
|
+
if (e.T & CONFIG_HAS_LANE && e.o?.Be) GlobalQueue.ze(e);
|
|
212
218
|
} catch (t) {
|
|
213
219
|
const n = t instanceof NotReadyError;
|
|
214
|
-
if (n && e.
|
|
220
|
+
if (n && e.Ie) {
|
|
215
221
|
// Loading window with an unready sync dependency: register for the
|
|
216
222
|
// source's settle (the settlePendingSource walk runs off
|
|
217
223
|
// _pendingSources + _blocked alone) but take NO read-visible pending
|
|
@@ -223,28 +229,34 @@ function recompute(e, t = false) {
|
|
|
223
229
|
} else {
|
|
224
230
|
// Track pending async in the lane (not the lane's source — it creates the lane
|
|
225
231
|
// but doesn't belong to it). Set lane BEFORE notifyStatus for downstream propagation.
|
|
226
|
-
if (n && currentOptimisticLane) GlobalQueue
|
|
232
|
+
if (n && currentOptimisticLane) GlobalQueue.$e(e);
|
|
227
233
|
let i = false;
|
|
228
234
|
if (n) {
|
|
229
|
-
e.
|
|
230
|
-
if (GlobalQueue
|
|
235
|
+
ext(e).ue = true;
|
|
236
|
+
if (GlobalQueue.Je !== null) i = GlobalQueue.Je(e, s);
|
|
231
237
|
}
|
|
232
|
-
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.
|
|
233
|
-
if (i) GlobalQueue.
|
|
238
|
+
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.o?.Be : undefined);
|
|
239
|
+
if (i) GlobalQueue.p(e);
|
|
234
240
|
}
|
|
235
241
|
} finally {
|
|
236
|
-
tracking =
|
|
242
|
+
tracking = E;
|
|
237
243
|
latestReadActive = N;
|
|
238
|
-
if (T) stale =
|
|
239
|
-
|
|
244
|
+
if (T) stale = d;
|
|
245
|
+
// Consume the missed-wake latch (#3037, set by insertSubs): a dep write
|
|
246
|
+
// landed beneath this pass on a link it had already validated. The wipe
|
|
247
|
+
// below must not key off DIRTY/CHECK — the read-time pull protocol
|
|
248
|
+
// (markNode(c) in read()) marks the running node as part of ordinary
|
|
249
|
+
// bookkeeping, and those marks are correctly discarded here.
|
|
250
|
+
f = (e.ie & REACTIVE_MISSED_WAKE) !== 0;
|
|
251
|
+
e.ie = REACTIVE_NONE | (t ? e.ie & REACTIVE_SNAPSHOT_STALE : 0);
|
|
240
252
|
context = r;
|
|
241
253
|
}
|
|
242
|
-
if (!e._) {
|
|
254
|
+
if (!e.o?._) {
|
|
243
255
|
trimStaleDeps(e);
|
|
244
|
-
const
|
|
256
|
+
const s = u ? unwrapOverride(e.o?.De) : e.Pe === NOT_PENDING ? e.be : e.Pe;
|
|
245
257
|
let r = false;
|
|
246
258
|
try {
|
|
247
|
-
r = !n &&
|
|
259
|
+
r = !n && l || !e.Ue || !e.Ue(s, c);
|
|
248
260
|
} catch (t) {
|
|
249
261
|
// A throwing user comparator is an error of this node's computation.
|
|
250
262
|
// Route it through the same status path as a compute-phase throw so
|
|
@@ -258,91 +270,105 @@ function recompute(e, t = false) {
|
|
|
258
270
|
// gate: the explicit recompute(node, true) inside effect() does not enqueue, so
|
|
259
271
|
// effect() can call its runner synchronously for the first run.
|
|
260
272
|
if (n && r) {
|
|
261
|
-
e.
|
|
273
|
+
e.Xe = !e.o?._;
|
|
262
274
|
// Reuse one bound runner per effect — runEffect no-ops on a stale
|
|
263
275
|
// `_modified`, so re-enqueueing the same function is harmless.
|
|
264
|
-
if (!t) e.C.enqueue(n, e.
|
|
276
|
+
if (!t) e.C.enqueue(n, e.et ??= GlobalQueue.tt.bind(null, e));
|
|
265
277
|
}
|
|
266
|
-
if (e._) ; else if (r) {
|
|
267
|
-
const
|
|
278
|
+
if (e.o?._) ; else if (r) {
|
|
279
|
+
const l = u ? e.o?.De : undefined;
|
|
268
280
|
if (t ||
|
|
269
281
|
// Plain sync flush (no transition on either side) commits effect
|
|
270
282
|
// values directly — the pending round-trip (queuePendingNode +
|
|
271
283
|
// commitPendingNodes) exists to sequence transition reveals, and
|
|
272
284
|
// paying it per effect on the plain path is pure overhead.
|
|
273
|
-
n && (activeTransition !== e.
|
|
285
|
+
n && (activeTransition !== e._e || activeTransition === null) || i) {
|
|
274
286
|
e.be = c;
|
|
275
287
|
// Lane-propagated correction: upstream data is fresh, correct the
|
|
276
288
|
// override unconditionally. The direct _value commit is the lane's
|
|
277
289
|
// own reveal schedule; drop any superseded older hold so its queued
|
|
278
290
|
// commit can't clobber the fresh value.
|
|
279
|
-
if (
|
|
280
|
-
e.De = c === undefined ? OVERRIDE_UNDEFINED : c;
|
|
281
|
-
e.
|
|
291
|
+
if (u && i) {
|
|
292
|
+
ext(e).De = c === undefined ? OVERRIDE_UNDEFINED : c;
|
|
293
|
+
e.Pe = NOT_PENDING;
|
|
282
294
|
}
|
|
283
295
|
} else {
|
|
284
|
-
e.
|
|
296
|
+
e.Pe = c;
|
|
285
297
|
// A window landing that gets held re-opens the window until the hold
|
|
286
298
|
// commits — the verdict's held-value branch is window-gated (#2990).
|
|
287
|
-
if (a) e.
|
|
299
|
+
if (a) e.Ie = true;
|
|
288
300
|
// Transition-held sync recompute is a write path like setSignal/asyncWrite,
|
|
289
301
|
// so sync derivations of held sources stay visible to isPending()/latest()
|
|
290
302
|
// (#2831). Both companion writes are transition-scoped (optimistic) and
|
|
291
303
|
// auto-revert/re-derive at commit. Skipped for plain flushes where the
|
|
292
304
|
// pending value commits before effects run.
|
|
293
|
-
if ((activeTransition || e.
|
|
305
|
+
if ((activeTransition || e._e) && GlobalQueue.Oe !== null) GlobalQueue.Oe(e, c);
|
|
294
306
|
}
|
|
295
307
|
// insertSubs only walks _subs (no scheduling of its own), so a
|
|
296
308
|
// subscriber-less node has nothing to notify.
|
|
297
|
-
if (e.
|
|
298
|
-
} else if (
|
|
309
|
+
if (e.u !== null && (!u || i || e.o?.De !== l)) insertSubs(e, i || u);
|
|
310
|
+
} else if (u) {
|
|
299
311
|
// Unchanged value (equals the override) recomputed while the override
|
|
300
312
|
// is active: _value may still be stale, so hold the authoritative value
|
|
301
313
|
// for commit on its own transition's schedule — invisibly (A17/A18).
|
|
302
|
-
if (e.
|
|
303
|
-
e.
|
|
304
|
-
if (a) e.
|
|
314
|
+
if (e.Pe === NOT_PENDING) queuePendingNode(e);
|
|
315
|
+
e.Pe = c;
|
|
316
|
+
if (a) e.Ie = true;
|
|
305
317
|
// see the held branch above (#2990)
|
|
306
318
|
} else if (e.Le != _) {
|
|
307
|
-
for (let t = e.
|
|
308
|
-
insertIntoHeapHeight(t.
|
|
319
|
+
for (let t = e.u; t !== null; t = t.fe) {
|
|
320
|
+
insertIntoHeapHeight(t.ae, queueFor(t.ae));
|
|
309
321
|
}
|
|
310
322
|
}
|
|
311
323
|
// Silent recovery: errored → unchanged value fires no notification, but
|
|
312
324
|
// dependents still holding the propagated error consumed their dirty flag
|
|
313
325
|
// in an errored run and may sit on stale commits (#2949). Changed-value
|
|
314
326
|
// recoveries ride insertSubs above; a comparator throw re-errored the node
|
|
315
|
-
// (el._error re-set), so this only runs on a genuinely clean recovery.
|
|
316
|
-
if (
|
|
327
|
+
// (el._x?._error re-set), so this only runs on a genuinely clean recovery.
|
|
328
|
+
if (o !== undefined && !r && !e.o?._) settleErroredDependents(e, o);
|
|
317
329
|
}
|
|
318
|
-
currentOptimisticLane =
|
|
319
|
-
const
|
|
330
|
+
currentOptimisticLane = I;
|
|
331
|
+
const S = e.Pe !== NOT_PENDING || e.o !== null && (e.o.qe !== null || e.o.We !== null) || (e.S & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0;
|
|
320
332
|
// Override-covered holds (hasOverride) always queue: their commit belongs
|
|
321
333
|
// to their own transition's schedule (A18 re-rule) and is unobservable
|
|
322
334
|
// under the override (A17). Revert no longer commits anything, so an
|
|
323
335
|
// unqueued covered hold would leak (INV-7) once the revert clears
|
|
324
336
|
// _transition.
|
|
325
|
-
|
|
326
|
-
e.
|
|
337
|
+
S && (!t || e.S & STATUS_PENDING) && (!e._e || u) && queuePendingNode(e);
|
|
338
|
+
e._e && n && activeTransition !== e._e && runInTransition(e._e, () => recompute(e));
|
|
339
|
+
// Missed-wake reschedule (see the finally above): values this pass read
|
|
340
|
+
// before the nested commit are stale, so run again now that the heap will
|
|
341
|
+
// accept the node. Equality gates stop same-value landings from cascading,
|
|
342
|
+
// and a re-run only latches again if another nested commit changes a dep
|
|
343
|
+
// beneath it — convergent unless deps genuinely keep changing.
|
|
344
|
+
if (f) {
|
|
345
|
+
enqueueSub(e);
|
|
346
|
+
schedule();
|
|
347
|
+
}
|
|
327
348
|
}
|
|
328
349
|
|
|
329
350
|
function updateIfNecessary(e) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
351
|
+
// Never re-enter a node that is currently computing: its dep bookkeeping
|
|
352
|
+
// (_depsTail/_depGen) is live, and a nested recompute would corrupt it.
|
|
353
|
+
// A mid-pass mark stays latched for recompute's own tail to reschedule
|
|
354
|
+
// (#3037); readers meanwhile serve the values the pass has so far.
|
|
355
|
+
if (e.ie & REACTIVE_RECOMPUTING_DEPS) return;
|
|
356
|
+
if (e.ie & REACTIVE_CHECK) {
|
|
357
|
+
for (let t = e.nt; t; t = t.it) {
|
|
358
|
+
const n = t.ut;
|
|
333
359
|
const i = n.lt || n;
|
|
334
|
-
if (i.
|
|
360
|
+
if (i.Se) {
|
|
335
361
|
updateIfNecessary(i);
|
|
336
362
|
}
|
|
337
|
-
if (e.
|
|
363
|
+
if (e.ie & REACTIVE_DIRTY) {
|
|
338
364
|
break;
|
|
339
365
|
}
|
|
340
366
|
}
|
|
341
367
|
}
|
|
342
|
-
if (e.
|
|
368
|
+
if (e.ie & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY) || e.o?._ && e.Te < clock && !e.o?.Ee) {
|
|
343
369
|
recompute(e);
|
|
344
370
|
}
|
|
345
|
-
e.
|
|
371
|
+
e.ie = e.ie & (REACTIVE_SNAPSHOT_STALE | REACTIVE_IN_HEAP | REACTIVE_IN_HEAP_HEIGHT);
|
|
346
372
|
}
|
|
347
373
|
|
|
348
374
|
function computed(e, t) {
|
|
@@ -351,44 +377,72 @@ function computed(e, t) {
|
|
|
351
377
|
// `T | undefined` node is a real commit #0. The typeof guard tolerates
|
|
352
378
|
// non-object option values that older call shapes force through `as any`.
|
|
353
379
|
const i = t !== null && typeof t === "object" && "loadingValue" in t;
|
|
354
|
-
const
|
|
380
|
+
const u = {
|
|
355
381
|
id: inheritId(t, n, context),
|
|
356
|
-
T: (n ? CONFIG_TRANSPARENT : 0) | (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (!context || t?.lazy ? CONFIG_AUTO_DISPOSE : 0) | (t?.sync ? CONFIG_SYNC : 0) | (t?.
|
|
382
|
+
T: (n ? CONFIG_TRANSPARENT : 0) | (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (!context || t?.lazy ? CONFIG_AUTO_DISPOSE : 0) | (t?.sync ? CONFIG_SYNC : 0) | (t?.H ? CONFIG_NO_SNAPSHOT : 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
357
383
|
Ue: t?.equals != null ? t.equals : isEqual,
|
|
358
|
-
ut: t?.unobserved,
|
|
359
384
|
he: null,
|
|
360
385
|
C: context?.C ?? globalQueue,
|
|
361
386
|
we: context?.we ?? defaultContext,
|
|
362
|
-
|
|
363
|
-
|
|
387
|
+
Me: 0,
|
|
388
|
+
Se: e,
|
|
364
389
|
be: i ? t.loadingValue : undefined,
|
|
365
390
|
Le: 0,
|
|
366
|
-
|
|
367
|
-
st:
|
|
368
|
-
|
|
369
|
-
tt: null,
|
|
391
|
+
ot: undefined,
|
|
392
|
+
st: null,
|
|
393
|
+
nt: null,
|
|
370
394
|
Ye: null,
|
|
371
395
|
Ze: 0,
|
|
372
|
-
|
|
396
|
+
u: null,
|
|
373
397
|
rt: null,
|
|
374
|
-
|
|
375
|
-
|
|
398
|
+
ve: context,
|
|
399
|
+
Ve: null,
|
|
376
400
|
ct: null,
|
|
377
401
|
ke: null,
|
|
378
|
-
|
|
402
|
+
ie: t?.lazy ? REACTIVE_LAZY : REACTIVE_NONE,
|
|
379
403
|
// A loadingValue node is born committed: commit #0 is already in _value.
|
|
380
404
|
S: i ? 0 : STATUS_UNINITIALIZED,
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
405
|
+
Te: clock,
|
|
406
|
+
Pe: NOT_PENDING,
|
|
407
|
+
_e: null,
|
|
408
|
+
_t: -1,
|
|
409
|
+
Ie: i,
|
|
410
|
+
// Cold machinery (async/transition/optimistic/verdict slots) lives one
|
|
411
|
+
// hop away in the lazily-allocated extension — the core literal MUST
|
|
412
|
+
// stay under V8's in-object boundary (§12: past ~39 fields every
|
|
413
|
+
// allocation spills to a backing store and creation cost ~4x's).
|
|
414
|
+
o: null
|
|
415
|
+
};
|
|
416
|
+
if (t?.unobserved) ext(u).ft = t.unobserved;
|
|
417
|
+
setupComputedNode(u, t);
|
|
418
|
+
return u;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** Lazily allocate a node's cold extension (ONE shape for signals and
|
|
422
|
+
* computeds — `_x` access stays monomorphic). Installers write through
|
|
423
|
+
* this; hot paths read `el._x?._field` gated by the _config presence bits.
|
|
424
|
+
* Never call ext() just to store a field's default. */ function ext(e) {
|
|
425
|
+
return e.o ??= {
|
|
426
|
+
De: undefined,
|
|
427
|
+
Et: undefined,
|
|
428
|
+
Be: undefined,
|
|
429
|
+
Ge: undefined,
|
|
430
|
+
ge: undefined,
|
|
431
|
+
It: undefined,
|
|
432
|
+
t: 0,
|
|
433
|
+
Ee: null,
|
|
434
|
+
_: undefined,
|
|
435
|
+
ue: undefined,
|
|
436
|
+
le: undefined,
|
|
437
|
+
A: undefined,
|
|
387
438
|
pe: false,
|
|
388
|
-
|
|
439
|
+
l: null,
|
|
440
|
+
ft: undefined,
|
|
441
|
+
Qe: undefined,
|
|
442
|
+
We: null,
|
|
443
|
+
qe: null,
|
|
444
|
+
Nt: undefined
|
|
389
445
|
};
|
|
390
|
-
setupComputedNode(l, t);
|
|
391
|
-
return l;
|
|
392
446
|
}
|
|
393
447
|
|
|
394
448
|
/**
|
|
@@ -396,52 +450,50 @@ function computed(e, t) {
|
|
|
396
450
|
* so V8 sees the full hidden class shape at construction time. Effects always run in lazy
|
|
397
451
|
* mode (recompute is called explicitly by `effect()`), so we hardcode the lazy bits and skip
|
|
398
452
|
* the auto-dispose CONFIG bit (effect() previously cleared it post-construction).
|
|
399
|
-
*/ function createEffectNode(e, t, n, i,
|
|
400
|
-
const
|
|
401
|
-
const
|
|
402
|
-
id: inheritId(
|
|
403
|
-
T: (
|
|
453
|
+
*/ function createEffectNode(e, t, n, i, u, l) {
|
|
454
|
+
const o = l?.transparent ?? false;
|
|
455
|
+
const s = {
|
|
456
|
+
id: inheritId(l, o, context),
|
|
457
|
+
T: (o ? CONFIG_TRANSPARENT : 0) | (l?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (l?.sync ? CONFIG_SYNC : 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
404
458
|
Ue: false,
|
|
405
|
-
ut: u?.unobserved,
|
|
406
459
|
he: null,
|
|
407
460
|
C: context?.C ?? globalQueue,
|
|
408
461
|
we: context?.we ?? defaultContext,
|
|
409
|
-
|
|
410
|
-
|
|
462
|
+
Me: 0,
|
|
463
|
+
Se: e,
|
|
411
464
|
be: undefined,
|
|
412
465
|
Le: 0,
|
|
413
|
-
|
|
414
|
-
st:
|
|
415
|
-
|
|
416
|
-
tt: null,
|
|
466
|
+
ot: undefined,
|
|
467
|
+
st: null,
|
|
468
|
+
nt: null,
|
|
417
469
|
Ye: null,
|
|
418
470
|
Ze: 0,
|
|
419
|
-
|
|
471
|
+
u: null,
|
|
420
472
|
rt: null,
|
|
421
|
-
|
|
422
|
-
|
|
473
|
+
ve: context,
|
|
474
|
+
Ve: null,
|
|
423
475
|
ct: null,
|
|
424
476
|
ke: null,
|
|
425
|
-
|
|
477
|
+
ie: REACTIVE_LAZY,
|
|
426
478
|
S: STATUS_UNINITIALIZED,
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
ft: t,
|
|
438
|
-
Et: n,
|
|
439
|
-
Nt: undefined,
|
|
479
|
+
Te: clock,
|
|
480
|
+
Pe: NOT_PENDING,
|
|
481
|
+
_e: null,
|
|
482
|
+
_t: -1,
|
|
483
|
+
Ie: false,
|
|
484
|
+
Xe: false,
|
|
485
|
+
Tt: undefined,
|
|
486
|
+
dt: t,
|
|
487
|
+
St: n,
|
|
488
|
+
At: undefined,
|
|
440
489
|
Re: i,
|
|
441
|
-
|
|
490
|
+
o: null
|
|
442
491
|
};
|
|
443
|
-
|
|
444
|
-
|
|
492
|
+
// Boundary effects carry a status channel; most effects never touch _x.
|
|
493
|
+
if (u !== undefined) ext(s).A = u;
|
|
494
|
+
if (l?.unobserved) ext(s).ft = l.unobserved;
|
|
495
|
+
setupComputedNode(s, lazyOptions);
|
|
496
|
+
return s;
|
|
445
497
|
}
|
|
446
498
|
|
|
447
499
|
const lazyOptions = {
|
|
@@ -449,24 +501,25 @@ const lazyOptions = {
|
|
|
449
501
|
};
|
|
450
502
|
|
|
451
503
|
function setupComputedNode(e, t) {
|
|
452
|
-
e.
|
|
453
|
-
const n = context?.
|
|
504
|
+
e.st = e;
|
|
505
|
+
const n = context?.Ct ? context.Ot : context;
|
|
454
506
|
if (context) {
|
|
455
507
|
const t = context.ke;
|
|
456
508
|
if (t === null) {
|
|
457
509
|
context.ke = e;
|
|
458
510
|
} else {
|
|
459
|
-
e.
|
|
511
|
+
e.Ve = t;
|
|
460
512
|
t.ct = e;
|
|
461
513
|
context.ke = e;
|
|
462
514
|
}
|
|
463
515
|
}
|
|
464
516
|
if (n) e.Le = n.Le + 1;
|
|
465
|
-
if (GlobalQueue.
|
|
517
|
+
if (GlobalQueue.Rt !== null) GlobalQueue.Rt(e);
|
|
466
518
|
!t?.lazy && recompute(e, true);
|
|
467
519
|
if (snapshotCaptureActive && !t?.lazy) {
|
|
468
520
|
if (!(e.S & STATUS_PENDING) && !(e.T & CONFIG_NO_SNAPSHOT)) {
|
|
469
|
-
e.
|
|
521
|
+
ext(e).Qe = e.be === undefined ? NO_SNAPSHOT : e.be;
|
|
522
|
+
e.T |= CONFIG_HAS_SNAPSHOT;
|
|
470
523
|
snapshotSources.add(e);
|
|
471
524
|
}
|
|
472
525
|
}
|
|
@@ -475,19 +528,31 @@ function setupComputedNode(e, t) {
|
|
|
475
528
|
function signal(e, t, n = null) {
|
|
476
529
|
const i = {
|
|
477
530
|
Ue: t?.equals != null ? t.equals : isEqual,
|
|
478
|
-
T: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.
|
|
479
|
-
ut: t?.unobserved,
|
|
531
|
+
T: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.H ? CONFIG_NO_SNAPSHOT : 0),
|
|
480
532
|
be: e,
|
|
481
|
-
|
|
533
|
+
u: null,
|
|
482
534
|
rt: null,
|
|
483
|
-
|
|
535
|
+
Te: clock,
|
|
484
536
|
lt: n,
|
|
485
|
-
|
|
486
|
-
|
|
537
|
+
ce: n?.o?.l || null,
|
|
538
|
+
Pe: NOT_PENDING,
|
|
539
|
+
// Signal-literal diet (§12e): NO _time/_fn/_statusFlags slots. Stores
|
|
540
|
+
// materialize one signal per touched leaf, so signal bytes are store
|
|
541
|
+
// bytes. _time is write-only on signals (every read site is computed-
|
|
542
|
+
// typed error-retry gating); _fn/_statusFlags read falsy-identically as
|
|
543
|
+
// missing properties on the shared paths (undefined masks to 0).
|
|
544
|
+
_e: null,
|
|
545
|
+
_t: -1,
|
|
546
|
+
o: null
|
|
487
547
|
};
|
|
488
|
-
|
|
548
|
+
if (t?.unobserved) ext(i).ft = t.unobserved;
|
|
549
|
+
if (n) {
|
|
550
|
+
ext(n).l = i;
|
|
551
|
+
n.T |= CONFIG_FW_CHILDREN;
|
|
552
|
+
}
|
|
489
553
|
if (snapshotCaptureActive && !(i.T & CONFIG_NO_SNAPSHOT) && !((n?.S ?? 0) & STATUS_PENDING)) {
|
|
490
|
-
i.
|
|
554
|
+
ext(i).Qe = e === undefined ? NO_SNAPSHOT : e;
|
|
555
|
+
i.T |= CONFIG_HAS_SNAPSHOT;
|
|
491
556
|
snapshotSources.add(i);
|
|
492
557
|
}
|
|
493
558
|
return i;
|
|
@@ -495,13 +560,15 @@ function signal(e, t, n = null) {
|
|
|
495
560
|
|
|
496
561
|
function optimisticSignal(e, t) {
|
|
497
562
|
const n = signal(e, t);
|
|
498
|
-
n.De = NOT_PENDING;
|
|
563
|
+
ext(n).De = NOT_PENDING;
|
|
564
|
+
n.T |= CONFIG_OPTIMISTIC;
|
|
499
565
|
return n;
|
|
500
566
|
}
|
|
501
567
|
|
|
502
568
|
function optimisticComputed(e, t) {
|
|
503
569
|
const n = computed(e, t);
|
|
504
|
-
n.De = NOT_PENDING;
|
|
570
|
+
ext(n).De = NOT_PENDING;
|
|
571
|
+
n.T |= CONFIG_OPTIMISTIC;
|
|
505
572
|
return n;
|
|
506
573
|
}
|
|
507
574
|
|
|
@@ -531,11 +598,11 @@ function isEqual(e, t) {
|
|
|
531
598
|
* );
|
|
532
599
|
* ```
|
|
533
600
|
*/ function untrack(e, t) {
|
|
534
|
-
if (GlobalQueue.
|
|
601
|
+
if (GlobalQueue.Gt === null && !tracking && true) return e();
|
|
535
602
|
const n = tracking;
|
|
536
603
|
tracking = false;
|
|
537
604
|
try {
|
|
538
|
-
if (GlobalQueue.
|
|
605
|
+
if (GlobalQueue.Gt !== null) return GlobalQueue.Gt(e);
|
|
539
606
|
return e();
|
|
540
607
|
} finally {
|
|
541
608
|
tracking = n;
|
|
@@ -547,11 +614,17 @@ function isEqual(e, t) {
|
|
|
547
614
|
* an isPending() probe (`refresh`) additionally pulls the node fully up to
|
|
548
615
|
* date so its status flags reflect the current graph.
|
|
549
616
|
*/ function prepareComputed(e, t) {
|
|
550
|
-
if (e.
|
|
551
|
-
e.
|
|
552
|
-
recompute(e, true);
|
|
553
|
-
} else if (e.se & REACTIVE_DISPOSED) {
|
|
617
|
+
if (e.ie & REACTIVE_LAZY) {
|
|
618
|
+
e.ie &= ~REACTIVE_LAZY;
|
|
554
619
|
recompute(e, true);
|
|
620
|
+
} else if (e.ie & REACTIVE_DISPOSED) {
|
|
621
|
+
// Two disposal lifecycles share the flag (#3024). Observation-lifecycle
|
|
622
|
+
// nodes (CONFIG_AUTO_DISPOSE) are dormant — torn down by unobserved()
|
|
623
|
+
// when the last subscriber left — and reads reawaken them; that is the
|
|
624
|
+
// pay-for-use contract. Owner-lifecycle nodes are dead: recomputing would
|
|
625
|
+
// re-run user code in a torn-down tree (and discard manual writes on
|
|
626
|
+
// derived-writable signals), so reads return the last committed value.
|
|
627
|
+
if (e.T & CONFIG_AUTO_DISPOSE) recompute(e, true);
|
|
555
628
|
} else if (t) {
|
|
556
629
|
updateIfNecessary(e);
|
|
557
630
|
}
|
|
@@ -571,15 +644,15 @@ function isEqual(e, t) {
|
|
|
571
644
|
* snapshot / transition / lane / dev-strictRead state all take the full
|
|
572
645
|
* resolution. Anything slow returns READ_SLOW; the caller then calls read().
|
|
573
646
|
*/ function readNodeFast(e) {
|
|
574
|
-
if (latestReadActive || pendingCheckActive || e.
|
|
647
|
+
if (latestReadActive || pendingCheckActive || e.Se || e.lt || e.o?.De !== undefined || e.o?.Qe !== undefined || activeTransition !== null || currentOptimisticLane !== null || snapshotCaptureActive || false) return READ_SLOW;
|
|
575
648
|
let t = context;
|
|
576
|
-
if (t?.
|
|
649
|
+
if (t?.Ct) t = t.Ot;
|
|
577
650
|
if (t && tracking) link(e, t);
|
|
578
651
|
// Children-forbidden readers (createTrackedEffect / onSettled callbacks) get
|
|
579
652
|
// committed visibility: like the effect half of createEffect and event
|
|
580
653
|
// handlers, effect-phase code never observes its own unsettled write — the
|
|
581
654
|
// write lands in the same flush's continuation (#3006).
|
|
582
|
-
return !t || e.
|
|
655
|
+
return !t || e.Pe === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN ? e.be : e.Pe;
|
|
583
656
|
}
|
|
584
657
|
|
|
585
658
|
function read(e) {
|
|
@@ -587,83 +660,93 @@ function read(e) {
|
|
|
587
660
|
// Checked before isPending so that isPending(() => latest(x)) checks
|
|
588
661
|
// the _pendingSignal of _latestValueComputed (async in flight) rather
|
|
589
662
|
// than the original node (which stays "pending" while held in a transition).
|
|
590
|
-
if (latestReadActive) return GlobalQueue.
|
|
663
|
+
if (latestReadActive) return GlobalQueue.Pt(e);
|
|
591
664
|
let t = context;
|
|
592
|
-
if (t?.
|
|
665
|
+
if (t?.Ct) t = t.Ot;
|
|
593
666
|
const n = e;
|
|
594
667
|
const i = e.lt;
|
|
595
|
-
const
|
|
668
|
+
const u = i || e;
|
|
596
669
|
// Handle isPending() mode: collect pending state while preserving normal read semantics.
|
|
597
670
|
// Probe mode is suspended while preparing the node so nested reads during a
|
|
598
671
|
// recompute don't collect into the probe.
|
|
599
672
|
if (pendingCheckActive) {
|
|
600
|
-
GlobalQueue.
|
|
601
|
-
} else if (typeof n.
|
|
673
|
+
GlobalQueue.Dt(e, t, u, i);
|
|
674
|
+
} else if (typeof n.Se === "function") {
|
|
602
675
|
prepareComputed(e, false);
|
|
603
676
|
}
|
|
604
|
-
if (!n.
|
|
677
|
+
if (!n.Se && u === e && e.o?.De === undefined && e.o?.Qe === undefined && activeTransition === null && currentOptimisticLane === null && !snapshotCaptureActive && true) {
|
|
605
678
|
if (t && tracking) link(e, t);
|
|
606
679
|
// Committed visibility for children-forbidden readers — see readNodeFast.
|
|
607
|
-
return !t || e.
|
|
680
|
+
return !t || e.Pe === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN ? e.be : e.Pe;
|
|
608
681
|
}
|
|
609
682
|
if (t && tracking) {
|
|
610
683
|
link(e, t, pendingCheckActive);
|
|
611
|
-
if (
|
|
684
|
+
if (u.Se) {
|
|
612
685
|
const n = queueFor(e);
|
|
613
|
-
if (
|
|
686
|
+
if (u.Le >= n.xe) {
|
|
614
687
|
markNode(t);
|
|
615
688
|
markHeap(n);
|
|
616
|
-
updateIfNecessary(
|
|
689
|
+
updateIfNecessary(u);
|
|
617
690
|
}
|
|
618
|
-
const i =
|
|
691
|
+
const i = u.Le;
|
|
619
692
|
// parent check is shallow, might need to be recursive
|
|
620
|
-
if (i >= t.Le && e.
|
|
693
|
+
if (i >= t.Le && e.ve !== t) {
|
|
621
694
|
t.Le = i + 1;
|
|
622
695
|
}
|
|
623
696
|
}
|
|
624
697
|
}
|
|
625
|
-
if (
|
|
626
|
-
if (t && !(stale &&
|
|
698
|
+
if (u.S & STATUS_PENDING) {
|
|
699
|
+
if (t && !(stale && u._e && activeTransition !== u._e)) {
|
|
627
700
|
// Per-lane suspension lives with the engine (a non-null lane implies it
|
|
628
701
|
// is installed): under a lane, only same-lane pending async without an
|
|
629
702
|
// active override throws.
|
|
630
|
-
if (currentOptimisticLane === null || GlobalQueue.
|
|
703
|
+
if (currentOptimisticLane === null || GlobalQueue.ht(u)) {
|
|
631
704
|
if (!tracking && e !== t) link(e, t);
|
|
632
|
-
throw
|
|
705
|
+
throw u.o?._;
|
|
633
706
|
}
|
|
634
|
-
} else if (t &&
|
|
707
|
+
} else if (t && u.S & STATUS_UNINITIALIZED) {
|
|
708
|
+
// A stale (render) reader of a node held pending in ANOTHER transition
|
|
709
|
+
// normally keeps showing the node's committed value instead of
|
|
710
|
+
// entangling the two transactions — but an uninitialized node has no
|
|
711
|
+
// committed value to show. Suspend on it (firewall-backed store reads
|
|
712
|
+
// always took this branch; plain memos now do too): the reader
|
|
713
|
+
// registers as a reporter of that source, and its pending-node stamp
|
|
714
|
+
// ties it to the active transaction, so the two transactions merge
|
|
715
|
+
// when the source settles. Falling through served `undefined` as if
|
|
716
|
+
// settled and stranded the reader outside both transactions, so it
|
|
717
|
+
// never re-ran when either landed (#3043 port).
|
|
635
718
|
if (!tracking && e !== t) link(e, t);
|
|
636
|
-
throw
|
|
637
|
-
} else if (!t &&
|
|
638
|
-
throw
|
|
719
|
+
throw u.o?._;
|
|
720
|
+
} else if (!t && u.S & STATUS_UNINITIALIZED) {
|
|
721
|
+
throw u.o?._;
|
|
639
722
|
}
|
|
640
723
|
}
|
|
641
724
|
// `owner` is the computed itself, or the firewall behind a store node —
|
|
642
725
|
// firewall-backed reads follow the same rules (memo parity, #2897 ruling):
|
|
643
726
|
// an errored derive throws for every late reader instead of silently
|
|
644
727
|
// serving node values (the seed, or last-good data after a failed refetch).
|
|
645
|
-
if (
|
|
728
|
+
if (u.Se && u.S & STATUS_ERROR) {
|
|
646
729
|
// Only a genuine reactive re-read may retry an errored async source:
|
|
647
730
|
// - tracking: owned/tracked scope only (never events / `untrack` / effect side-effect phase)
|
|
648
731
|
// - !pendingCheckActive: an `isPending` probe observes the error, never refetches
|
|
649
732
|
// - owner._time < clock: only on a later cycle than the one the error was found
|
|
650
|
-
if (tracking && !pendingCheckActive &&
|
|
651
|
-
recompute(
|
|
733
|
+
if (tracking && !pendingCheckActive && u.Te < clock) {
|
|
734
|
+
recompute(u);
|
|
652
735
|
return read(e);
|
|
653
|
-
} else throw
|
|
736
|
+
} else throw u.o?._;
|
|
654
737
|
}
|
|
655
738
|
if (snapshotCaptureActive && t && t.T & CONFIG_IN_SNAPSHOT_SCOPE) {
|
|
656
|
-
const n = e.
|
|
739
|
+
const n = e.o?.Qe;
|
|
657
740
|
if (n !== undefined) {
|
|
658
741
|
const i = n === NO_SNAPSHOT ? undefined : n;
|
|
659
|
-
const
|
|
660
|
-
if (
|
|
742
|
+
const u = e.Pe !== NOT_PENDING ? e.Pe : e.be;
|
|
743
|
+
if (u !== i) t.ie |= REACTIVE_SNAPSHOT_STALE;
|
|
661
744
|
return i;
|
|
662
745
|
}
|
|
663
746
|
}
|
|
664
|
-
if (e.De !== undefined && e.De !== NOT_PENDING) {
|
|
747
|
+
if (e.o?.De !== undefined && e.o?.De !== NOT_PENDING) {
|
|
665
748
|
// A17: the override IS the value for every reader.
|
|
666
|
-
return unwrapOverride(e.De);
|
|
749
|
+
return unwrapOverride(e.o?.De);
|
|
667
750
|
}
|
|
668
751
|
// Entanglement gate: a reader recomputing under an optimistic lane that reads
|
|
669
752
|
// a pending mid-transition write sees the committed value. Projection-store
|
|
@@ -672,7 +755,7 @@ function read(e) {
|
|
|
672
755
|
// _pendingValue for correct fetching. The sub is recorded for replay at commit
|
|
673
756
|
// so it re-runs with the new committed view. (Gate details live with the
|
|
674
757
|
// engine — a non-null lane implies it is installed.)
|
|
675
|
-
if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.
|
|
758
|
+
if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.Ft(e, u, t)) {
|
|
676
759
|
return e.be;
|
|
677
760
|
}
|
|
678
761
|
// In optimistic lane context, return _value for optimistic/lane-assigned signals
|
|
@@ -681,36 +764,48 @@ function read(e) {
|
|
|
681
764
|
// (The lane-context clause lives with the engine.) Children-forbidden readers
|
|
682
765
|
// (createTrackedEffect / onSettled callbacks) get committed visibility — see
|
|
683
766
|
// readNodeFast (#3006).
|
|
684
|
-
const
|
|
767
|
+
const l = !t || currentOptimisticLane !== null && GlobalQueue.gt(e, u, t) || e.Pe === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN || stale && e._e && activeTransition !== e._e ? e.be : e.Pe;
|
|
685
768
|
// Record that this isPending() probe observed the fresh pending value, so
|
|
686
769
|
// the probe doesn't pair "pending" with the new value (#2831).
|
|
687
|
-
if (pendingCheckActive) GlobalQueue.
|
|
688
|
-
if (!t &&
|
|
770
|
+
if (pendingCheckActive) GlobalQueue.Ht(e, l);
|
|
771
|
+
if (!t && u === e && typeof n.Se === "function" && e.T & CONFIG_AUTO_DISPOSE && !(u.S & STATUS_PENDING) && !e.u) {
|
|
689
772
|
unobserved(e);
|
|
690
773
|
}
|
|
691
|
-
return
|
|
774
|
+
return l;
|
|
692
775
|
}
|
|
693
776
|
|
|
694
777
|
function setSignal(e, t) {
|
|
695
|
-
if (e.
|
|
778
|
+
if (e._e && activeTransition !== e._e) globalQueue.initTransition(e._e);
|
|
696
779
|
// The optimistic write path lives with the engine: only optimisticSignal /
|
|
697
780
|
// optimisticComputed callers and optimistic store nodes carry an
|
|
698
|
-
// _overrideValue slot
|
|
699
|
-
//
|
|
700
|
-
|
|
701
|
-
|
|
781
|
+
// _overrideValue slot (flagged by CONFIG_OPTIMISTIC — a masked read of the
|
|
782
|
+
// always-present config instead of a missing-property probe), and every
|
|
783
|
+
// module that installs one installs the engine first.
|
|
784
|
+
if (e.T & CONFIG_OPTIMISTIC && !projectionWriteActive) return GlobalQueue.vt(e, t);
|
|
785
|
+
const n = e.Pe === NOT_PENDING ? e.be : e.Pe;
|
|
702
786
|
if (typeof t === "function") t = t(n);
|
|
703
787
|
// Uninitialized check first: the first commit has no previous value, so the
|
|
704
788
|
// user comparator must not run against `undefined` (matches recompute).
|
|
705
789
|
const i = !!(e.S & STATUS_UNINITIALIZED) || !e.Ue || !e.Ue(n, t);
|
|
706
790
|
if (!i) return t;
|
|
707
|
-
|
|
708
|
-
|
|
791
|
+
const u = e.Pe !== NOT_PENDING;
|
|
792
|
+
if (!u) queuePendingNode(e);
|
|
793
|
+
e.Pe = t;
|
|
709
794
|
// syncCompanions only pokes _pendingSignal/_latestValueComputed — with
|
|
710
795
|
// neither companion present the call is a guaranteed no-op (companions are
|
|
711
|
-
// only ever created, never removed, and creating one installs the hook
|
|
712
|
-
|
|
713
|
-
|
|
796
|
+
// only ever created, never removed, and creating one installs the hook and
|
|
797
|
+
// sets CONFIG_HAS_COMPANIONS — one masked read replaces two optional-field
|
|
798
|
+
// probes on every write).
|
|
799
|
+
e.T & CONFIG_HAS_COMPANIONS && GlobalQueue.Oe !== null && GlobalQueue.Oe(e, t);
|
|
800
|
+
// _time is a computed-only slot (§12e): writing it on a signal would fork
|
|
801
|
+
// the lean shape. Every read site is computed-typed.
|
|
802
|
+
if (e.Se !== undefined) e.Te = clock;
|
|
803
|
+
// Staged-rewrite fast path (§12d): a re-write to a node whose subscribers
|
|
804
|
+
// were already walked — and where nothing has recomputed or linked since
|
|
805
|
+
// (epoch) — re-stages the value and stops. The walk is idempotent (subs
|
|
806
|
+
// marked, heap entries flag-guarded, effects queued once); lane and reask
|
|
807
|
+
// contexts change what a walk MEANS, so they always walk.
|
|
808
|
+
if (u && e._t === notifyEpoch && currentOptimisticLane === null && !reaskArmed) return t;
|
|
714
809
|
insertSubs(e);
|
|
715
810
|
schedule();
|
|
716
811
|
return t;
|
|
@@ -724,11 +819,12 @@ function setSignal(e, t) {
|
|
|
724
819
|
* cleanup point.
|
|
725
820
|
*/ function suppressComputedRecompute(e) {
|
|
726
821
|
deleteFromHeap(e, queueFor(e));
|
|
727
|
-
if (!(e.
|
|
822
|
+
if (!(e.ie & REACTIVE_MANUAL_WRITE) && e.Pe === NOT_PENDING) {
|
|
728
823
|
queuePendingNode(e);
|
|
729
824
|
schedule();
|
|
730
825
|
}
|
|
731
|
-
e.
|
|
826
|
+
e.ie = e.ie & -4 | REACTIVE_MANUAL_WRITE;
|
|
827
|
+
e.kt = clock;
|
|
732
828
|
}
|
|
733
829
|
|
|
734
830
|
/**
|
|
@@ -807,7 +903,19 @@ function staleValues(e, t = true) {
|
|
|
807
903
|
if (!t) {
|
|
808
904
|
return;
|
|
809
905
|
}
|
|
810
|
-
if (typeof t.
|
|
906
|
+
if (typeof t.Se === "function" && !(t.ie & REACTIVE_DISPOSED)) {
|
|
907
|
+
if (t.ie & REACTIVE_MANUAL_WRITE) {
|
|
908
|
+
// A manual write in the CURRENT tick wins over the refresh (#2692).
|
|
909
|
+
// A mask stamped in an earlier tick only survives because a
|
|
910
|
+
// transaction (action) is holding the pending drain open; there the
|
|
911
|
+
// refresh is a later, explicit re-ask and lifts the mask — otherwise
|
|
912
|
+
// any setStore early in an action silently swallows every refresh()
|
|
913
|
+
// for the rest of the transaction (#3026).
|
|
914
|
+
if (t.kt === clock) return;
|
|
915
|
+
t.ie &= ~REACTIVE_MANUAL_WRITE;
|
|
916
|
+
// No REASK below: the batch carries a manual value change, so the
|
|
917
|
+
// recompute is not a quiet re-ask of an unchanged question.
|
|
918
|
+
}
|
|
811
919
|
// A refresh with no value-change dirt already queued is a re-ask of the
|
|
812
920
|
// same question: mark it so the recompute classifies any resulting
|
|
813
921
|
// pending window as quiet (not pending). If the node is already dirty
|
|
@@ -815,14 +923,14 @@ function staleValues(e, t = true) {
|
|
|
815
923
|
// REACTIVE_IN_HEAP counts as dirt: insertSubs schedules subscribers by
|
|
816
924
|
// heap insertion alone (no DIRTY/CHECK flag), so a same-batch value
|
|
817
925
|
// change followed by refresh() must not be laundered into a quiet re-ask.
|
|
818
|
-
|
|
819
|
-
t.
|
|
926
|
+
else if (!(t.ie & (REACTIVE_DIRTY | REACTIVE_CHECK | REACTIVE_IN_HEAP))) {
|
|
927
|
+
t.ie |= REACTIVE_REASK;
|
|
820
928
|
armReaskClear();
|
|
821
929
|
}
|
|
822
|
-
t.
|
|
930
|
+
t.ie = t.ie & ~REACTIVE_CHECK | REACTIVE_DIRTY;
|
|
823
931
|
insertIntoHeap(t, queueFor(t));
|
|
824
932
|
schedule();
|
|
825
933
|
}
|
|
826
934
|
}
|
|
827
935
|
|
|
828
|
-
export { READ_SLOW, clearSnapshots, computed, context, createEffectNode, currentOptimisticLane, isEqual, latestReadActive, markSnapshotScope, optimisticComputed, optimisticSignal, pendingCheckActive, prepareComputed, read, readNodeFast, recompute, refresh, releaseSnapshotScope, runWithOwner, setContextInternal, setLatestReadActive, setMemo, setPendingCheckActive, setSignal, setSnapshotCapture, signal, snapshotCaptureActive, snapshotSources, stale, staleValues, suppressComputedRecompute, tracking, untrack };
|
|
936
|
+
export { READ_SLOW, clearSnapshots, computed, context, createEffectNode, currentOptimisticLane, ext, isEqual, latestReadActive, markSnapshotScope, optimisticComputed, optimisticSignal, pendingCheckActive, prepareComputed, read, readNodeFast, recompute, refresh, releaseSnapshotScope, runWithOwner, setContextInternal, setLatestReadActive, setMemo, setPendingCheckActive, setSignal, setSnapshotCapture, signal, snapshotCaptureActive, snapshotSources, stale, staleValues, suppressComputedRecompute, tracking, untrack };
|