@solidjs/signals 2.0.0-rc.0 → 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/README.md +1 -1
- package/dist/dev.js +4287 -2803
- package/dist/node.cjs +4563 -3756
- 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 +234 -178
- package/dist/prod/core/constants.js +39 -1
- package/dist/prod/core/core.js +362 -243
- 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 +47 -39
- package/dist/prod/core/invariants.js +3 -2
- package/dist/prod/core/lanes.js +41 -34
- package/dist/prod/core/optimistic.js +78 -58
- package/dist/prod/core/owner.js +101 -100
- package/dist/prod/core/scheduler.js +249 -192
- package/dist/prod/core/verdict.js +185 -78
- package/dist/prod/index.js +7 -7
- package/dist/prod/map.js +108 -106
- package/dist/prod/signals.js +20 -1
- package/dist/prod/store/index.js +36 -0
- package/dist/prod/store/next/optimistic.js +385 -0
- package/dist/prod/store/next/projection.js +172 -0
- package/dist/prod/store/next/reconcile.js +331 -0
- package/dist/prod/store/next/store.js +1499 -0
- package/dist/prod/store/next/target.js +20 -0
- package/dist/prod/store/store.js +126 -882
- package/dist/prod/store/utils.js +59 -186
- 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 +16 -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 +12 -2
- package/dist/types/core/types.d.ts +85 -41
- package/dist/types/signals.d.ts +19 -0
- package/dist/types/store/index.d.ts +15 -5
- package/dist/types/store/next/optimistic.d.ts +20 -0
- package/dist/types/store/next/projection.d.ts +8 -0
- package/dist/types/store/next/reconcile.d.ts +3 -0
- package/dist/types/store/next/store.d.ts +77 -0
- package/dist/types/store/next/target.d.ts +117 -0
- package/dist/types/store/store.d.ts +26 -101
- package/dist/types/store/utils.d.ts +0 -40
- 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 +16 -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 +12 -2
- package/dist/types-cjs/core/types.d.cts +85 -41
- package/dist/types-cjs/signals.d.cts +19 -0
- package/dist/types-cjs/store/index.d.cts +15 -5
- package/dist/types-cjs/store/next/optimistic.d.cts +20 -0
- package/dist/types-cjs/store/next/projection.d.cts +8 -0
- package/dist/types-cjs/store/next/reconcile.d.cts +3 -0
- package/dist/types-cjs/store/next/store.d.cts +77 -0
- package/dist/types-cjs/store/next/target.d.cts +117 -0
- package/dist/types-cjs/store/store.d.cts +26 -101
- package/dist/types-cjs/store/utils.d.cts +0 -40
- package/package.json +2 -1
- package/dist/prod/store/optimistic.js +0 -217
- package/dist/prod/store/projection.js +0 -231
- package/dist/prod/store/reconcile.js +0 -707
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, CONFIG_AUTO_DISPOSE, 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, NO_SNAPSHOT, STORE_SNAPSHOT_PROPS, EFFECT_USER } from "./constants.js";
|
|
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,41 +60,41 @@ 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
|
}
|
|
71
71
|
|
|
72
72
|
function releaseSubtree(e) {
|
|
73
|
-
let t = e.
|
|
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
|
-
|
|
120
|
-
e.
|
|
121
|
-
e.
|
|
119
|
+
const t = ext(e);
|
|
120
|
+
t.We = e.he;
|
|
121
|
+
t.qe = e.ke;
|
|
122
|
+
e.he = null;
|
|
123
|
+
e.ke = null;
|
|
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 o = e.S & STATUS_ERROR ? e._ : undefined;
|
|
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 s = (e.
|
|
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
|
-
let _ = e.
|
|
147
|
-
let f =
|
|
148
|
-
let E =
|
|
146
|
+
e.ie = REACTIVE_RECOMPUTING_DEPS;
|
|
147
|
+
e.Te = clock;
|
|
148
|
+
let c = e.Pe === NOT_PENDING ? e.be : e.Pe;
|
|
149
|
+
let _ = e.Le;
|
|
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
|
|
@@ -153,7 +157,7 @@ function recompute(e, t = false) {
|
|
|
153
157
|
// computed) inside latest(fn) came out permanently dependency-less (#2926).
|
|
154
158
|
// latestRead() already suspends the flag for its pull-recomputes; this
|
|
155
159
|
// covers creation-time computes and flushes that run inside the window.
|
|
156
|
-
const
|
|
160
|
+
const N = latestReadActive;
|
|
157
161
|
latestReadActive = false;
|
|
158
162
|
// Lane posture lives with the engine: OPTIMISTIC_DIRTY is only ever set by
|
|
159
163
|
// engine-driven paths, and _optimisticNodes is only pushed by
|
|
@@ -161,7 +165,11 @@ function recompute(e, t = false) {
|
|
|
161
165
|
if (i) {
|
|
162
166
|
const t = GlobalQueue.je(e, true);
|
|
163
167
|
if (t) currentOptimisticLane = t;
|
|
164
|
-
|
|
168
|
+
// `false` = wake-only lane demotion: recompute plain so a mid-tick
|
|
169
|
+
// latest()/isPending() pull stages instead of direct-committing (#3009).
|
|
170
|
+
// The predicate lives with the engine (recomputeLane).
|
|
171
|
+
else if (t === false) i = false;
|
|
172
|
+
} else if (activeTransition && !t && activeTransition.Ke.length) {
|
|
165
173
|
// Lane adoption: parent-deeper-than-owned-child can run before its OPT-dirty
|
|
166
174
|
// child propagates. Walk deps once and inherit the OPT lane so this node
|
|
167
175
|
// recomputes under the right posture and propagates correctly.
|
|
@@ -171,43 +179,45 @@ function recompute(e, t = false) {
|
|
|
171
179
|
currentOptimisticLane = t;
|
|
172
180
|
}
|
|
173
181
|
}
|
|
174
|
-
const
|
|
182
|
+
const T = n && n !== EFFECT_USER;
|
|
175
183
|
const d = stale;
|
|
176
|
-
if (
|
|
184
|
+
if (T) stale = true;
|
|
177
185
|
try {
|
|
178
186
|
if (!false && e.T & CONFIG_SYNC) {
|
|
179
|
-
c = e.
|
|
180
|
-
e.
|
|
181
|
-
e.
|
|
187
|
+
c = e.Se(c);
|
|
188
|
+
if (e.o !== null) e.o.Ee = null;
|
|
189
|
+
e.Ie = false;
|
|
182
190
|
} else {
|
|
183
191
|
// Snapshot `_inFlight` so we can detect whether `_fn` self-registered an async
|
|
184
192
|
// subscription (e.g. `createProjection` calls `handleAsync` from inside its body
|
|
185
193
|
// with a setter callback). In that case, the outer `handleAsync` call below would
|
|
186
194
|
// clobber the fresh subscription, so we skip it and let the internally-registered
|
|
187
195
|
// iteration drive updates.
|
|
188
|
-
const t = e.
|
|
189
|
-
const n = e.
|
|
196
|
+
const t = e.o?.Ee;
|
|
197
|
+
const n = e.Se(c);
|
|
190
198
|
const i = typeof n === "object" && n !== null;
|
|
191
|
-
const
|
|
192
|
-
c =
|
|
193
|
-
if (!
|
|
194
|
-
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;
|
|
195
203
|
// A sync (non-object) return is the first real answer; async-shaped
|
|
196
204
|
// results clear inside handleAsync at their own landing points, and a
|
|
197
205
|
// self-registered flight (inFlightChanged — projections) clears when
|
|
198
206
|
// its internal handleAsync lands.
|
|
199
|
-
e.
|
|
207
|
+
e.Ie = false;
|
|
200
208
|
}
|
|
201
209
|
}
|
|
202
|
-
// On a status-free node clearStatus is a guaranteed no-op: every
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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);
|
|
208
218
|
} catch (t) {
|
|
209
219
|
const n = t instanceof NotReadyError;
|
|
210
|
-
if (n && e.
|
|
220
|
+
if (n && e.Ie) {
|
|
211
221
|
// Loading window with an unready sync dependency: register for the
|
|
212
222
|
// source's settle (the settlePendingSource walk runs off
|
|
213
223
|
// _pendingSources + _blocked alone) but take NO read-visible pending
|
|
@@ -222,25 +232,31 @@ function recompute(e, t = false) {
|
|
|
222
232
|
if (n && currentOptimisticLane) GlobalQueue.$e(e);
|
|
223
233
|
let i = false;
|
|
224
234
|
if (n) {
|
|
225
|
-
e.ue = true;
|
|
226
|
-
if (GlobalQueue.
|
|
235
|
+
ext(e).ue = true;
|
|
236
|
+
if (GlobalQueue.Je !== null) i = GlobalQueue.Je(e, s);
|
|
227
237
|
}
|
|
228
|
-
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.
|
|
229
|
-
if (i) GlobalQueue.
|
|
238
|
+
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.o?.Be : undefined);
|
|
239
|
+
if (i) GlobalQueue.p(e);
|
|
230
240
|
}
|
|
231
241
|
} finally {
|
|
232
|
-
tracking =
|
|
233
|
-
latestReadActive =
|
|
234
|
-
if (
|
|
235
|
-
|
|
242
|
+
tracking = E;
|
|
243
|
+
latestReadActive = N;
|
|
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);
|
|
236
252
|
context = r;
|
|
237
253
|
}
|
|
238
|
-
if (!e._) {
|
|
254
|
+
if (!e.o?._) {
|
|
239
255
|
trimStaleDeps(e);
|
|
240
|
-
const s =
|
|
256
|
+
const s = u ? unwrapOverride(e.o?.De) : e.Pe === NOT_PENDING ? e.be : e.Pe;
|
|
241
257
|
let r = false;
|
|
242
258
|
try {
|
|
243
|
-
r = !n &&
|
|
259
|
+
r = !n && l || !e.Ue || !e.Ue(s, c);
|
|
244
260
|
} catch (t) {
|
|
245
261
|
// A throwing user comparator is an error of this node's computation.
|
|
246
262
|
// Route it through the same status path as a compute-phase throw so
|
|
@@ -254,91 +270,105 @@ function recompute(e, t = false) {
|
|
|
254
270
|
// gate: the explicit recompute(node, true) inside effect() does not enqueue, so
|
|
255
271
|
// effect() can call its runner synchronously for the first run.
|
|
256
272
|
if (n && r) {
|
|
257
|
-
e.
|
|
273
|
+
e.Xe = !e.o?._;
|
|
258
274
|
// Reuse one bound runner per effect — runEffect no-ops on a stale
|
|
259
275
|
// `_modified`, so re-enqueueing the same function is harmless.
|
|
260
|
-
if (!t) e.C.enqueue(n, e.
|
|
276
|
+
if (!t) e.C.enqueue(n, e.et ??= GlobalQueue.tt.bind(null, e));
|
|
261
277
|
}
|
|
262
|
-
if (e._) ; else if (r) {
|
|
263
|
-
const
|
|
278
|
+
if (e.o?._) ; else if (r) {
|
|
279
|
+
const l = u ? e.o?.De : undefined;
|
|
264
280
|
if (t ||
|
|
265
281
|
// Plain sync flush (no transition on either side) commits effect
|
|
266
282
|
// values directly — the pending round-trip (queuePendingNode +
|
|
267
283
|
// commitPendingNodes) exists to sequence transition reveals, and
|
|
268
284
|
// paying it per effect on the plain path is pure overhead.
|
|
269
|
-
n && (activeTransition !== e.
|
|
270
|
-
e.
|
|
285
|
+
n && (activeTransition !== e._e || activeTransition === null) || i) {
|
|
286
|
+
e.be = c;
|
|
271
287
|
// Lane-propagated correction: upstream data is fresh, correct the
|
|
272
288
|
// override unconditionally. The direct _value commit is the lane's
|
|
273
289
|
// own reveal schedule; drop any superseded older hold so its queued
|
|
274
290
|
// commit can't clobber the fresh value.
|
|
275
|
-
if (
|
|
276
|
-
e.
|
|
277
|
-
e.
|
|
291
|
+
if (u && i) {
|
|
292
|
+
ext(e).De = c === undefined ? OVERRIDE_UNDEFINED : c;
|
|
293
|
+
e.Pe = NOT_PENDING;
|
|
278
294
|
}
|
|
279
295
|
} else {
|
|
280
|
-
e.
|
|
296
|
+
e.Pe = c;
|
|
281
297
|
// A window landing that gets held re-opens the window until the hold
|
|
282
298
|
// commits — the verdict's held-value branch is window-gated (#2990).
|
|
283
|
-
if (a) e.
|
|
299
|
+
if (a) e.Ie = true;
|
|
284
300
|
// Transition-held sync recompute is a write path like setSignal/asyncWrite,
|
|
285
301
|
// so sync derivations of held sources stay visible to isPending()/latest()
|
|
286
302
|
// (#2831). Both companion writes are transition-scoped (optimistic) and
|
|
287
303
|
// auto-revert/re-derive at commit. Skipped for plain flushes where the
|
|
288
304
|
// pending value commits before effects run.
|
|
289
|
-
if ((activeTransition || e.
|
|
305
|
+
if ((activeTransition || e._e) && GlobalQueue.Oe !== null) GlobalQueue.Oe(e, c);
|
|
290
306
|
}
|
|
291
307
|
// insertSubs only walks _subs (no scheduling of its own), so a
|
|
292
308
|
// subscriber-less node has nothing to notify.
|
|
293
|
-
if (e.
|
|
294
|
-
} else if (
|
|
309
|
+
if (e.u !== null && (!u || i || e.o?.De !== l)) insertSubs(e, i || u);
|
|
310
|
+
} else if (u) {
|
|
295
311
|
// Unchanged value (equals the override) recomputed while the override
|
|
296
312
|
// is active: _value may still be stale, so hold the authoritative value
|
|
297
313
|
// for commit on its own transition's schedule — invisibly (A17/A18).
|
|
298
|
-
if (e.
|
|
299
|
-
e.
|
|
300
|
-
if (a) e.
|
|
314
|
+
if (e.Pe === NOT_PENDING) queuePendingNode(e);
|
|
315
|
+
e.Pe = c;
|
|
316
|
+
if (a) e.Ie = true;
|
|
301
317
|
// see the held branch above (#2990)
|
|
302
|
-
} else if (e.
|
|
303
|
-
for (let t = e.
|
|
304
|
-
insertIntoHeapHeight(t.
|
|
318
|
+
} else if (e.Le != _) {
|
|
319
|
+
for (let t = e.u; t !== null; t = t.fe) {
|
|
320
|
+
insertIntoHeapHeight(t.ae, queueFor(t.ae));
|
|
305
321
|
}
|
|
306
322
|
}
|
|
307
323
|
// Silent recovery: errored → unchanged value fires no notification, but
|
|
308
324
|
// dependents still holding the propagated error consumed their dirty flag
|
|
309
325
|
// in an errored run and may sit on stale commits (#2949). Changed-value
|
|
310
326
|
// recoveries ride insertSubs above; a comparator throw re-errored the node
|
|
311
|
-
// (el._error re-set), so this only runs on a genuinely clean recovery.
|
|
312
|
-
if (o !== undefined && !r && !e._) settleErroredDependents(e, o);
|
|
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);
|
|
313
329
|
}
|
|
314
|
-
currentOptimisticLane =
|
|
315
|
-
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;
|
|
316
332
|
// Override-covered holds (hasOverride) always queue: their commit belongs
|
|
317
333
|
// to their own transition's schedule (A18 re-rule) and is unobservable
|
|
318
334
|
// under the override (A17). Revert no longer commits anything, so an
|
|
319
335
|
// unqueued covered hold would leak (INV-7) once the revert clears
|
|
320
336
|
// _transition.
|
|
321
|
-
|
|
322
|
-
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
|
+
}
|
|
323
348
|
}
|
|
324
349
|
|
|
325
350
|
function updateIfNecessary(e) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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;
|
|
329
359
|
const i = n.lt || n;
|
|
330
|
-
if (i.
|
|
360
|
+
if (i.Se) {
|
|
331
361
|
updateIfNecessary(i);
|
|
332
362
|
}
|
|
333
|
-
if (e.
|
|
363
|
+
if (e.ie & REACTIVE_DIRTY) {
|
|
334
364
|
break;
|
|
335
365
|
}
|
|
336
366
|
}
|
|
337
367
|
}
|
|
338
|
-
if (e.
|
|
368
|
+
if (e.ie & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY) || e.o?._ && e.Te < clock && !e.o?.Ee) {
|
|
339
369
|
recompute(e);
|
|
340
370
|
}
|
|
341
|
-
e.
|
|
371
|
+
e.ie = e.ie & (REACTIVE_SNAPSHOT_STALE | REACTIVE_IN_HEAP | REACTIVE_IN_HEAP_HEIGHT);
|
|
342
372
|
}
|
|
343
373
|
|
|
344
374
|
function computed(e, t) {
|
|
@@ -347,44 +377,72 @@ function computed(e, t) {
|
|
|
347
377
|
// `T | undefined` node is a real commit #0. The typeof guard tolerates
|
|
348
378
|
// non-object option values that older call shapes force through `as any`.
|
|
349
379
|
const i = t !== null && typeof t === "object" && "loadingValue" in t;
|
|
350
|
-
const
|
|
380
|
+
const u = {
|
|
351
381
|
id: inheritId(t, n, context),
|
|
352
|
-
T: (n ? CONFIG_TRANSPARENT : 0) | (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (!context || t?.lazy ? CONFIG_AUTO_DISPOSE : 0) | (t?.sync ? CONFIG_SYNC : 0) | (t?.
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
ye: null,
|
|
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),
|
|
383
|
+
Ue: t?.equals != null ? t.equals : isEqual,
|
|
384
|
+
he: null,
|
|
356
385
|
C: context?.C ?? globalQueue,
|
|
357
386
|
we: context?.we ?? defaultContext,
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
u: null,
|
|
387
|
+
Me: 0,
|
|
388
|
+
Se: e,
|
|
389
|
+
be: i ? t.loadingValue : undefined,
|
|
390
|
+
Le: 0,
|
|
363
391
|
ot: undefined,
|
|
364
392
|
st: null,
|
|
365
|
-
|
|
393
|
+
nt: null,
|
|
366
394
|
Ye: null,
|
|
367
395
|
Ze: 0,
|
|
368
|
-
|
|
396
|
+
u: null,
|
|
369
397
|
rt: null,
|
|
370
|
-
|
|
371
|
-
|
|
398
|
+
ve: context,
|
|
399
|
+
Ve: null,
|
|
372
400
|
ct: null,
|
|
373
|
-
|
|
374
|
-
|
|
401
|
+
ke: null,
|
|
402
|
+
ie: t?.lazy ? REACTIVE_LAZY : REACTIVE_NONE,
|
|
375
403
|
// A loadingValue node is born committed: commit #0 is already in _value.
|
|
376
404
|
S: i ? 0 : STATUS_UNINITIALIZED,
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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,
|
|
438
|
+
pe: false,
|
|
439
|
+
l: null,
|
|
440
|
+
ft: undefined,
|
|
441
|
+
Qe: undefined,
|
|
380
442
|
We: null,
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
he: false,
|
|
384
|
-
Ee: i
|
|
443
|
+
qe: null,
|
|
444
|
+
Nt: undefined
|
|
385
445
|
};
|
|
386
|
-
setupComputedNode(l, t);
|
|
387
|
-
return l;
|
|
388
446
|
}
|
|
389
447
|
|
|
390
448
|
/**
|
|
@@ -392,50 +450,48 @@ function computed(e, t) {
|
|
|
392
450
|
* so V8 sees the full hidden class shape at construction time. Effects always run in lazy
|
|
393
451
|
* mode (recompute is called explicitly by `effect()`), so we hardcode the lazy bits and skip
|
|
394
452
|
* the auto-dispose CONFIG bit (effect() previously cleared it post-construction).
|
|
395
|
-
*/ function createEffectNode(e, t, n, i,
|
|
396
|
-
const o =
|
|
453
|
+
*/ function createEffectNode(e, t, n, i, u, l) {
|
|
454
|
+
const o = l?.transparent ?? false;
|
|
397
455
|
const s = {
|
|
398
|
-
id: inheritId(
|
|
399
|
-
T: (o ? CONFIG_TRANSPARENT : 0) | (
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
ye: null,
|
|
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),
|
|
458
|
+
Ue: false,
|
|
459
|
+
he: null,
|
|
403
460
|
C: context?.C ?? globalQueue,
|
|
404
461
|
we: context?.we ?? defaultContext,
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
u: null,
|
|
462
|
+
Me: 0,
|
|
463
|
+
Se: e,
|
|
464
|
+
be: undefined,
|
|
465
|
+
Le: 0,
|
|
410
466
|
ot: undefined,
|
|
411
467
|
st: null,
|
|
412
|
-
|
|
468
|
+
nt: null,
|
|
413
469
|
Ye: null,
|
|
414
470
|
Ze: 0,
|
|
415
|
-
|
|
471
|
+
u: null,
|
|
416
472
|
rt: null,
|
|
417
|
-
|
|
418
|
-
|
|
473
|
+
ve: context,
|
|
474
|
+
Ve: null,
|
|
419
475
|
ct: null,
|
|
420
|
-
|
|
421
|
-
|
|
476
|
+
ke: null,
|
|
477
|
+
ie: REACTIVE_LAZY,
|
|
422
478
|
S: STATUS_UNINITIALIZED,
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
he: false,
|
|
430
|
-
Ee: false,
|
|
431
|
-
Je: false,
|
|
432
|
-
_t: undefined,
|
|
433
|
-
ft: t,
|
|
434
|
-
Et: n,
|
|
479
|
+
Te: clock,
|
|
480
|
+
Pe: NOT_PENDING,
|
|
481
|
+
_e: null,
|
|
482
|
+
_t: -1,
|
|
483
|
+
Ie: false,
|
|
484
|
+
Xe: false,
|
|
435
485
|
Tt: undefined,
|
|
486
|
+
dt: t,
|
|
487
|
+
St: n,
|
|
488
|
+
At: undefined,
|
|
436
489
|
Re: i,
|
|
437
|
-
|
|
490
|
+
o: null
|
|
438
491
|
};
|
|
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;
|
|
439
495
|
setupComputedNode(s, lazyOptions);
|
|
440
496
|
return s;
|
|
441
497
|
}
|
|
@@ -446,23 +502,24 @@ const lazyOptions = {
|
|
|
446
502
|
|
|
447
503
|
function setupComputedNode(e, t) {
|
|
448
504
|
e.st = e;
|
|
449
|
-
const n = context?.
|
|
505
|
+
const n = context?.Ct ? context.Ot : context;
|
|
450
506
|
if (context) {
|
|
451
|
-
const t = context.
|
|
507
|
+
const t = context.ke;
|
|
452
508
|
if (t === null) {
|
|
453
|
-
context.
|
|
509
|
+
context.ke = e;
|
|
454
510
|
} else {
|
|
455
|
-
e.
|
|
511
|
+
e.Ve = t;
|
|
456
512
|
t.ct = e;
|
|
457
|
-
context.
|
|
513
|
+
context.ke = e;
|
|
458
514
|
}
|
|
459
515
|
}
|
|
460
|
-
if (n) e.
|
|
461
|
-
if (GlobalQueue.
|
|
516
|
+
if (n) e.Le = n.Le + 1;
|
|
517
|
+
if (GlobalQueue.Rt !== null) GlobalQueue.Rt(e);
|
|
462
518
|
!t?.lazy && recompute(e, true);
|
|
463
519
|
if (snapshotCaptureActive && !t?.lazy) {
|
|
464
520
|
if (!(e.S & STATUS_PENDING) && !(e.T & CONFIG_NO_SNAPSHOT)) {
|
|
465
|
-
e.
|
|
521
|
+
ext(e).Qe = e.be === undefined ? NO_SNAPSHOT : e.be;
|
|
522
|
+
e.T |= CONFIG_HAS_SNAPSHOT;
|
|
466
523
|
snapshotSources.add(e);
|
|
467
524
|
}
|
|
468
525
|
}
|
|
@@ -470,20 +527,32 @@ function setupComputedNode(e, t) {
|
|
|
470
527
|
|
|
471
528
|
function signal(e, t, n = null) {
|
|
472
529
|
const i = {
|
|
473
|
-
|
|
474
|
-
T: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
o: null,
|
|
530
|
+
Ue: t?.equals != null ? t.equals : isEqual,
|
|
531
|
+
T: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.H ? CONFIG_NO_SNAPSHOT : 0),
|
|
532
|
+
be: e,
|
|
533
|
+
u: null,
|
|
478
534
|
rt: null,
|
|
479
|
-
|
|
535
|
+
Te: clock,
|
|
480
536
|
lt: n,
|
|
481
|
-
|
|
482
|
-
|
|
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
|
|
483
547
|
};
|
|
484
|
-
|
|
548
|
+
if (t?.unobserved) ext(i).ft = t.unobserved;
|
|
549
|
+
if (n) {
|
|
550
|
+
ext(n).l = i;
|
|
551
|
+
n.T |= CONFIG_FW_CHILDREN;
|
|
552
|
+
}
|
|
485
553
|
if (snapshotCaptureActive && !(i.T & CONFIG_NO_SNAPSHOT) && !((n?.S ?? 0) & STATUS_PENDING)) {
|
|
486
|
-
i.
|
|
554
|
+
ext(i).Qe = e === undefined ? NO_SNAPSHOT : e;
|
|
555
|
+
i.T |= CONFIG_HAS_SNAPSHOT;
|
|
487
556
|
snapshotSources.add(i);
|
|
488
557
|
}
|
|
489
558
|
return i;
|
|
@@ -491,13 +560,15 @@ function signal(e, t, n = null) {
|
|
|
491
560
|
|
|
492
561
|
function optimisticSignal(e, t) {
|
|
493
562
|
const n = signal(e, t);
|
|
494
|
-
n.
|
|
563
|
+
ext(n).De = NOT_PENDING;
|
|
564
|
+
n.T |= CONFIG_OPTIMISTIC;
|
|
495
565
|
return n;
|
|
496
566
|
}
|
|
497
567
|
|
|
498
568
|
function optimisticComputed(e, t) {
|
|
499
569
|
const n = computed(e, t);
|
|
500
|
-
n.
|
|
570
|
+
ext(n).De = NOT_PENDING;
|
|
571
|
+
n.T |= CONFIG_OPTIMISTIC;
|
|
501
572
|
return n;
|
|
502
573
|
}
|
|
503
574
|
|
|
@@ -527,11 +598,11 @@ function isEqual(e, t) {
|
|
|
527
598
|
* );
|
|
528
599
|
* ```
|
|
529
600
|
*/ function untrack(e, t) {
|
|
530
|
-
if (GlobalQueue.
|
|
601
|
+
if (GlobalQueue.Gt === null && !tracking && true) return e();
|
|
531
602
|
const n = tracking;
|
|
532
603
|
tracking = false;
|
|
533
604
|
try {
|
|
534
|
-
if (GlobalQueue.
|
|
605
|
+
if (GlobalQueue.Gt !== null) return GlobalQueue.Gt(e);
|
|
535
606
|
return e();
|
|
536
607
|
} finally {
|
|
537
608
|
tracking = n;
|
|
@@ -543,11 +614,17 @@ function isEqual(e, t) {
|
|
|
543
614
|
* an isPending() probe (`refresh`) additionally pulls the node fully up to
|
|
544
615
|
* date so its status flags reflect the current graph.
|
|
545
616
|
*/ function prepareComputed(e, t) {
|
|
546
|
-
if (e.
|
|
547
|
-
e.
|
|
548
|
-
recompute(e, true);
|
|
549
|
-
} else if (e.se & REACTIVE_DISPOSED) {
|
|
617
|
+
if (e.ie & REACTIVE_LAZY) {
|
|
618
|
+
e.ie &= ~REACTIVE_LAZY;
|
|
550
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);
|
|
551
628
|
} else if (t) {
|
|
552
629
|
updateIfNecessary(e);
|
|
553
630
|
}
|
|
@@ -567,11 +644,15 @@ function isEqual(e, t) {
|
|
|
567
644
|
* snapshot / transition / lane / dev-strictRead state all take the full
|
|
568
645
|
* resolution. Anything slow returns READ_SLOW; the caller then calls read().
|
|
569
646
|
*/ function readNodeFast(e) {
|
|
570
|
-
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;
|
|
571
648
|
let t = context;
|
|
572
|
-
if (t?.
|
|
649
|
+
if (t?.Ct) t = t.Ot;
|
|
573
650
|
if (t && tracking) link(e, t);
|
|
574
|
-
|
|
651
|
+
// Children-forbidden readers (createTrackedEffect / onSettled callbacks) get
|
|
652
|
+
// committed visibility: like the effect half of createEffect and event
|
|
653
|
+
// handlers, effect-phase code never observes its own unsettled write — the
|
|
654
|
+
// write lands in the same flush's continuation (#3006).
|
|
655
|
+
return !t || e.Pe === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN ? e.be : e.Pe;
|
|
575
656
|
}
|
|
576
657
|
|
|
577
658
|
function read(e) {
|
|
@@ -579,82 +660,93 @@ function read(e) {
|
|
|
579
660
|
// Checked before isPending so that isPending(() => latest(x)) checks
|
|
580
661
|
// the _pendingSignal of _latestValueComputed (async in flight) rather
|
|
581
662
|
// than the original node (which stays "pending" while held in a transition).
|
|
582
|
-
if (latestReadActive) return GlobalQueue.
|
|
663
|
+
if (latestReadActive) return GlobalQueue.Pt(e);
|
|
583
664
|
let t = context;
|
|
584
|
-
if (t?.
|
|
665
|
+
if (t?.Ct) t = t.Ot;
|
|
585
666
|
const n = e;
|
|
586
667
|
const i = e.lt;
|
|
587
|
-
const
|
|
668
|
+
const u = i || e;
|
|
588
669
|
// Handle isPending() mode: collect pending state while preserving normal read semantics.
|
|
589
670
|
// Probe mode is suspended while preparing the node so nested reads during a
|
|
590
671
|
// recompute don't collect into the probe.
|
|
591
672
|
if (pendingCheckActive) {
|
|
592
|
-
GlobalQueue.
|
|
593
|
-
} else if (typeof n.
|
|
673
|
+
GlobalQueue.Dt(e, t, u, i);
|
|
674
|
+
} else if (typeof n.Se === "function") {
|
|
594
675
|
prepareComputed(e, false);
|
|
595
676
|
}
|
|
596
|
-
if (!n.
|
|
677
|
+
if (!n.Se && u === e && e.o?.De === undefined && e.o?.Qe === undefined && activeTransition === null && currentOptimisticLane === null && !snapshotCaptureActive && true) {
|
|
597
678
|
if (t && tracking) link(e, t);
|
|
598
|
-
|
|
679
|
+
// Committed visibility for children-forbidden readers — see readNodeFast.
|
|
680
|
+
return !t || e.Pe === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN ? e.be : e.Pe;
|
|
599
681
|
}
|
|
600
682
|
if (t && tracking) {
|
|
601
683
|
link(e, t, pendingCheckActive);
|
|
602
|
-
if (
|
|
684
|
+
if (u.Se) {
|
|
603
685
|
const n = queueFor(e);
|
|
604
|
-
if (
|
|
686
|
+
if (u.Le >= n.xe) {
|
|
605
687
|
markNode(t);
|
|
606
688
|
markHeap(n);
|
|
607
|
-
updateIfNecessary(
|
|
689
|
+
updateIfNecessary(u);
|
|
608
690
|
}
|
|
609
|
-
const i =
|
|
691
|
+
const i = u.Le;
|
|
610
692
|
// parent check is shallow, might need to be recursive
|
|
611
|
-
if (i >= t.
|
|
612
|
-
t.
|
|
693
|
+
if (i >= t.Le && e.ve !== t) {
|
|
694
|
+
t.Le = i + 1;
|
|
613
695
|
}
|
|
614
696
|
}
|
|
615
697
|
}
|
|
616
|
-
if (
|
|
617
|
-
if (t && !(stale &&
|
|
698
|
+
if (u.S & STATUS_PENDING) {
|
|
699
|
+
if (t && !(stale && u._e && activeTransition !== u._e)) {
|
|
618
700
|
// Per-lane suspension lives with the engine (a non-null lane implies it
|
|
619
701
|
// is installed): under a lane, only same-lane pending async without an
|
|
620
702
|
// active override throws.
|
|
621
|
-
if (currentOptimisticLane === null || GlobalQueue.
|
|
703
|
+
if (currentOptimisticLane === null || GlobalQueue.ht(u)) {
|
|
622
704
|
if (!tracking && e !== t) link(e, t);
|
|
623
|
-
throw
|
|
705
|
+
throw u.o?._;
|
|
624
706
|
}
|
|
625
|
-
} 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).
|
|
626
718
|
if (!tracking && e !== t) link(e, t);
|
|
627
|
-
throw
|
|
628
|
-
} else if (!t &&
|
|
629
|
-
throw
|
|
719
|
+
throw u.o?._;
|
|
720
|
+
} else if (!t && u.S & STATUS_UNINITIALIZED) {
|
|
721
|
+
throw u.o?._;
|
|
630
722
|
}
|
|
631
723
|
}
|
|
632
724
|
// `owner` is the computed itself, or the firewall behind a store node —
|
|
633
725
|
// firewall-backed reads follow the same rules (memo parity, #2897 ruling):
|
|
634
726
|
// an errored derive throws for every late reader instead of silently
|
|
635
727
|
// serving node values (the seed, or last-good data after a failed refetch).
|
|
636
|
-
if (
|
|
728
|
+
if (u.Se && u.S & STATUS_ERROR) {
|
|
637
729
|
// Only a genuine reactive re-read may retry an errored async source:
|
|
638
730
|
// - tracking: owned/tracked scope only (never events / `untrack` / effect side-effect phase)
|
|
639
731
|
// - !pendingCheckActive: an `isPending` probe observes the error, never refetches
|
|
640
732
|
// - owner._time < clock: only on a later cycle than the one the error was found
|
|
641
|
-
if (tracking && !pendingCheckActive &&
|
|
642
|
-
recompute(
|
|
733
|
+
if (tracking && !pendingCheckActive && u.Te < clock) {
|
|
734
|
+
recompute(u);
|
|
643
735
|
return read(e);
|
|
644
|
-
} else throw
|
|
736
|
+
} else throw u.o?._;
|
|
645
737
|
}
|
|
646
738
|
if (snapshotCaptureActive && t && t.T & CONFIG_IN_SNAPSHOT_SCOPE) {
|
|
647
|
-
const n = e.
|
|
739
|
+
const n = e.o?.Qe;
|
|
648
740
|
if (n !== undefined) {
|
|
649
741
|
const i = n === NO_SNAPSHOT ? undefined : n;
|
|
650
|
-
const
|
|
651
|
-
if (
|
|
742
|
+
const u = e.Pe !== NOT_PENDING ? e.Pe : e.be;
|
|
743
|
+
if (u !== i) t.ie |= REACTIVE_SNAPSHOT_STALE;
|
|
652
744
|
return i;
|
|
653
745
|
}
|
|
654
746
|
}
|
|
655
|
-
if (e.
|
|
747
|
+
if (e.o?.De !== undefined && e.o?.De !== NOT_PENDING) {
|
|
656
748
|
// A17: the override IS the value for every reader.
|
|
657
|
-
return unwrapOverride(e.
|
|
749
|
+
return unwrapOverride(e.o?.De);
|
|
658
750
|
}
|
|
659
751
|
// Entanglement gate: a reader recomputing under an optimistic lane that reads
|
|
660
752
|
// a pending mid-transition write sees the committed value. Projection-store
|
|
@@ -663,43 +755,57 @@ function read(e) {
|
|
|
663
755
|
// _pendingValue for correct fetching. The sub is recorded for replay at commit
|
|
664
756
|
// so it re-runs with the new committed view. (Gate details live with the
|
|
665
757
|
// engine — a non-null lane implies it is installed.)
|
|
666
|
-
if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.
|
|
667
|
-
return e.
|
|
758
|
+
if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.Ft(e, u, t)) {
|
|
759
|
+
return e.be;
|
|
668
760
|
}
|
|
669
761
|
// In optimistic lane context, return _value for optimistic/lane-assigned signals
|
|
670
762
|
// and for regular signals in stale mode (render effects). Non-stale readers (user
|
|
671
763
|
// effects) see _pendingValue so that latest() and direct reads stay consistent.
|
|
672
|
-
// (The lane-context clause lives with the engine.)
|
|
673
|
-
|
|
764
|
+
// (The lane-context clause lives with the engine.) Children-forbidden readers
|
|
765
|
+
// (createTrackedEffect / onSettled callbacks) get committed visibility — see
|
|
766
|
+
// readNodeFast (#3006).
|
|
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;
|
|
674
768
|
// Record that this isPending() probe observed the fresh pending value, so
|
|
675
769
|
// the probe doesn't pair "pending" with the new value (#2831).
|
|
676
|
-
if (pendingCheckActive) GlobalQueue.
|
|
677
|
-
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) {
|
|
678
772
|
unobserved(e);
|
|
679
773
|
}
|
|
680
|
-
return
|
|
774
|
+
return l;
|
|
681
775
|
}
|
|
682
776
|
|
|
683
777
|
function setSignal(e, t) {
|
|
684
|
-
if (e.
|
|
778
|
+
if (e._e && activeTransition !== e._e) globalQueue.initTransition(e._e);
|
|
685
779
|
// The optimistic write path lives with the engine: only optimisticSignal /
|
|
686
780
|
// optimisticComputed callers and optimistic store nodes carry an
|
|
687
|
-
// _overrideValue slot
|
|
688
|
-
//
|
|
689
|
-
|
|
690
|
-
|
|
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;
|
|
691
786
|
if (typeof t === "function") t = t(n);
|
|
692
787
|
// Uninitialized check first: the first commit has no previous value, so the
|
|
693
788
|
// user comparator must not run against `undefined` (matches recompute).
|
|
694
|
-
const i = !!(e.S & STATUS_UNINITIALIZED) || !e.
|
|
789
|
+
const i = !!(e.S & STATUS_UNINITIALIZED) || !e.Ue || !e.Ue(n, t);
|
|
695
790
|
if (!i) return t;
|
|
696
|
-
|
|
697
|
-
|
|
791
|
+
const u = e.Pe !== NOT_PENDING;
|
|
792
|
+
if (!u) queuePendingNode(e);
|
|
793
|
+
e.Pe = t;
|
|
698
794
|
// syncCompanions only pokes _pendingSignal/_latestValueComputed — with
|
|
699
795
|
// neither companion present the call is a guaranteed no-op (companions are
|
|
700
|
-
// only ever created, never removed, and creating one installs the hook
|
|
701
|
-
|
|
702
|
-
|
|
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;
|
|
703
809
|
insertSubs(e);
|
|
704
810
|
schedule();
|
|
705
811
|
return t;
|
|
@@ -713,11 +819,12 @@ function setSignal(e, t) {
|
|
|
713
819
|
* cleanup point.
|
|
714
820
|
*/ function suppressComputedRecompute(e) {
|
|
715
821
|
deleteFromHeap(e, queueFor(e));
|
|
716
|
-
if (!(e.
|
|
822
|
+
if (!(e.ie & REACTIVE_MANUAL_WRITE) && e.Pe === NOT_PENDING) {
|
|
717
823
|
queuePendingNode(e);
|
|
718
824
|
schedule();
|
|
719
825
|
}
|
|
720
|
-
e.
|
|
826
|
+
e.ie = e.ie & -4 | REACTIVE_MANUAL_WRITE;
|
|
827
|
+
e.kt = clock;
|
|
721
828
|
}
|
|
722
829
|
|
|
723
830
|
/**
|
|
@@ -796,7 +903,19 @@ function staleValues(e, t = true) {
|
|
|
796
903
|
if (!t) {
|
|
797
904
|
return;
|
|
798
905
|
}
|
|
799
|
-
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
|
+
}
|
|
800
919
|
// A refresh with no value-change dirt already queued is a re-ask of the
|
|
801
920
|
// same question: mark it so the recompute classifies any resulting
|
|
802
921
|
// pending window as quiet (not pending). If the node is already dirty
|
|
@@ -804,14 +923,14 @@ function staleValues(e, t = true) {
|
|
|
804
923
|
// REACTIVE_IN_HEAP counts as dirt: insertSubs schedules subscribers by
|
|
805
924
|
// heap insertion alone (no DIRTY/CHECK flag), so a same-batch value
|
|
806
925
|
// change followed by refresh() must not be laundered into a quiet re-ask.
|
|
807
|
-
|
|
808
|
-
t.
|
|
926
|
+
else if (!(t.ie & (REACTIVE_DIRTY | REACTIVE_CHECK | REACTIVE_IN_HEAP))) {
|
|
927
|
+
t.ie |= REACTIVE_REASK;
|
|
809
928
|
armReaskClear();
|
|
810
929
|
}
|
|
811
|
-
t.
|
|
930
|
+
t.ie = t.ie & ~REACTIVE_CHECK | REACTIVE_DIRTY;
|
|
812
931
|
insertIntoHeap(t, queueFor(t));
|
|
813
932
|
schedule();
|
|
814
933
|
}
|
|
815
934
|
}
|
|
816
935
|
|
|
817
|
-
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 };
|