@solidjs/signals 2.0.0-rc.14 → 2.0.0-rc.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev-shared.js +882 -405
- package/dist/dev.attribution.js +172 -66
- package/dist/dev.js +1529 -745
- package/dist/observe/affects-marks.js +86 -0
- package/dist/observe/affects.js +32 -80
- package/dist/observe/boundaries.js +202 -166
- package/dist/observe/core/async.js +192 -141
- package/dist/observe/core/attribution-queries.js +1 -1
- package/dist/observe/core/attribution.js +315 -214
- package/dist/observe/core/constants.js +23 -4
- package/dist/observe/core/context.js +3 -3
- package/dist/observe/core/core.js +532 -360
- package/dist/observe/core/dev.js +63 -35
- package/dist/observe/core/effect.js +47 -47
- package/dist/observe/core/error.js +5 -3
- package/dist/observe/core/external.js +5 -6
- package/dist/observe/core/graph.js +56 -42
- package/dist/observe/core/heap.js +33 -33
- package/dist/observe/core/identities.js +62 -0
- package/dist/observe/core/lanes.js +242 -229
- package/dist/observe/core/owner.js +62 -50
- package/dist/observe/core/scheduler.js +322 -168
- package/dist/observe/core/truth.js +45 -0
- package/dist/observe/core/verdict.js +114 -64
- package/dist/observe/deferred.js +184 -0
- package/dist/observe/index.js +7 -1
- package/dist/observe/map.js +339 -254
- package/dist/observe/reveal.js +77 -77
- package/dist/observe/signals.js +54 -33
- package/dist/observe/store/affects.js +76 -69
- package/dist/observe/store/edges.js +39 -0
- package/dist/observe/store/hooks.js +13 -0
- package/dist/observe/store/index.js +0 -2
- package/dist/observe/store/optimistic.js +82 -44
- package/dist/observe/store/projection.js +83 -64
- package/dist/observe/store/reconcile.js +32 -30
- package/dist/observe/store/store.js +219 -123
- package/dist/prod/affects-marks.js +86 -0
- package/dist/prod/affects.js +33 -79
- package/dist/prod/boundaries.js +185 -149
- package/dist/prod/core/async.js +176 -135
- package/dist/prod/core/constants.js +23 -4
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +521 -353
- package/dist/prod/core/effect.js +66 -66
- package/dist/prod/core/error.js +5 -3
- package/dist/prod/core/external.js +5 -6
- package/dist/prod/core/graph.js +56 -42
- package/dist/prod/core/heap.js +38 -38
- package/dist/prod/core/identities.js +62 -0
- package/dist/prod/core/lanes.js +237 -224
- package/dist/prod/core/owner.js +62 -51
- package/dist/prod/core/scheduler.js +312 -158
- package/dist/prod/core/truth.js +45 -0
- package/dist/prod/core/verdict.js +113 -63
- package/dist/prod/deferred.js +182 -0
- package/dist/prod/index.js +7 -1
- package/dist/prod/map.js +366 -281
- package/dist/prod/reveal.js +77 -77
- package/dist/prod/signals.js +59 -38
- package/dist/prod/store/affects.js +76 -69
- package/dist/prod/store/edges.js +39 -0
- package/dist/prod/store/hooks.js +13 -0
- package/dist/prod/store/index.js +0 -2
- package/dist/prod/store/optimistic.js +121 -84
- package/dist/prod/store/projection.js +46 -34
- package/dist/prod/store/reconcile.js +32 -30
- package/dist/prod/store/store.js +205 -116
- package/dist/types/affects-marks.d.ts +14 -0
- package/dist/types/boundaries.d.ts +5 -1
- package/dist/types/core/async.d.ts +10 -0
- package/dist/types/core/attribution.d.ts +6 -2
- package/dist/types/core/constants.d.ts +18 -1
- package/dist/types/core/core.d.ts +77 -5
- package/dist/types/core/dev.d.ts +43 -13
- package/dist/types/core/graph.d.ts +11 -0
- package/dist/types/core/identities.d.ts +17 -0
- package/dist/types/core/index.d.ts +2 -1
- package/dist/types/core/lanes.d.ts +2 -4
- package/dist/types/core/owner.d.ts +5 -3
- package/dist/types/core/scheduler.d.ts +59 -34
- package/dist/types/core/truth.d.ts +16 -0
- package/dist/types/core/types.d.ts +21 -6
- package/dist/types/core/verdict.d.ts +32 -0
- package/dist/types/deferred.d.ts +28 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/map.d.ts +2 -2
- package/dist/types/signals.d.ts +28 -7
- package/dist/types/store/affects.d.ts +11 -0
- package/dist/types/store/edges.d.ts +9 -0
- package/dist/types/store/hooks.d.ts +48 -0
- package/dist/types/store/index.d.ts +10 -4
- package/dist/types/store/optimistic.d.ts +3 -2
- package/dist/types/store/projection.d.ts +4 -3
- package/dist/types/store/store.d.ts +4 -39
- package/dist/types/store/target.d.ts +16 -8
- package/dist/types/store/types.d.ts +14 -0
- package/package.json +1 -1
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { STATUS_ERROR, STATUS_PENDING, STATUS_UNINITIALIZED, CONFIG_GUESS, CONFIG_HELD, REACTIVE_STAGED_READ, CONFIG_STAGED, CONFIG_STAGED_ERROR, CONFIG_AUTO_DISPOSE, REACTIVE_ZOMBIE, REACTIVE_DIRTY, NOT_PENDING, REACTIVE_DISPOSED, CONFIG_REDERIVE, CONFIG_VERDICT, CONFIG_OVERRIDE } from "./constants.js";
|
|
2
2
|
|
|
3
3
|
import { attrHooks } from "./attribution-hooks.js";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { ext, statusNotifierOf, untrack, context, setSignal } from "./core.js";
|
|
6
6
|
|
|
7
7
|
import "./dev.js";
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { StatusError, NotReadyError } from "./error.js";
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { unobserved, trimStaleDeps } from "./graph.js";
|
|
12
12
|
|
|
13
13
|
import { enqueueSub } from "./heap.js";
|
|
14
14
|
|
|
15
15
|
import { cleanup } from "./owner.js";
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import { passLane, joinFuture, question, MAINLINE_QUESTION, globalQueue, schedule, clock, flush, freshHook, txOf, laneStageHook, queuePendingNode } from "./scheduler.js";
|
|
18
18
|
|
|
19
19
|
// The lazily-created Set is the ONE container for pending sources. Its
|
|
20
20
|
// predecessor — a singular slot promoted to a Set on the second source —
|
|
@@ -22,22 +22,22 @@ import { question, MAINLINE_QUESTION, passLane, joinFuture, clock, schedule, flu
|
|
|
22
22
|
// overlapping source landed beside the Set and removePendingSource refused
|
|
23
23
|
// to clear it, stranding the Set members' pending forever (#2893).
|
|
24
24
|
function addPendingSource(e, t) {
|
|
25
|
-
if (e.
|
|
26
|
-
(ext(e).
|
|
25
|
+
if (e.T?.fe?.has(t)) return false;
|
|
26
|
+
(ext(e).fe ??= new Set).add(t);
|
|
27
27
|
return true;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function removePendingSource(e, t) {
|
|
31
|
-
const n = e.
|
|
31
|
+
const n = e.T?.fe;
|
|
32
32
|
if (!n?.delete(t)) return false;
|
|
33
|
-
if (!n.size) e.
|
|
33
|
+
if (!n.size) e.T.fe = undefined;
|
|
34
34
|
return true;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function clearPendingSources(e) {
|
|
38
38
|
// This set is node-owned and never shared; dropping the sole reference
|
|
39
39
|
// releases the set and every entry without a redundant clear() walk.
|
|
40
|
-
if (e.
|
|
40
|
+
if (e.T !== null) e.T.fe = undefined;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
// A rejection-pending only resolves through the settle sweep over the
|
|
@@ -46,9 +46,9 @@ function clearPendingSources(e) {
|
|
|
46
46
|
// (pending sources propagate the origin node, so this covers any depth).
|
|
47
47
|
// Also guards branch-local recovery: another dependency may still need the source.
|
|
48
48
|
function retryReaches(e, t) {
|
|
49
|
-
for (let n = e.
|
|
50
|
-
const e = n.
|
|
51
|
-
if (e === t || e.
|
|
49
|
+
for (let n = e.C; n; n = n.G) {
|
|
50
|
+
const e = n.S;
|
|
51
|
+
if (e === t || e.T?.fe?.has(t)) return true;
|
|
52
52
|
}
|
|
53
53
|
return false;
|
|
54
54
|
}
|
|
@@ -60,32 +60,32 @@ function retryReaches(e, t) {
|
|
|
60
60
|
* with NO read-visible pending status and no downstream propagation. Commit
|
|
61
61
|
* #0 keeps serving.
|
|
62
62
|
*/ function parkLoadingWindow(e, t) {
|
|
63
|
-
ext(e).
|
|
63
|
+
ext(e).se = true;
|
|
64
64
|
if (t.source) addPendingSource(e, t.source);
|
|
65
65
|
// A settled error is the node's answer ("the error stays the answer until
|
|
66
66
|
// this retry can actually run") — the park must not replace it: reads
|
|
67
67
|
// throw `_error` while STATUS_ERROR is set, and overwriting it here leaks
|
|
68
68
|
// a pending-class NotReadyError from a read-invisible park (#2989).
|
|
69
|
-
if (!(e.
|
|
69
|
+
if (!(e.m & STATUS_ERROR)) setPendingError(e, t.source, t);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function setPendingError(e, t, n) {
|
|
73
73
|
if (!t) {
|
|
74
|
-
if (e.
|
|
74
|
+
if (e.T !== null) e.T.F = null;
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
77
|
if (n instanceof NotReadyError && n.source === t) {
|
|
78
|
-
ext(e).
|
|
78
|
+
ext(e).F = n;
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
|
-
const r = e.
|
|
81
|
+
const r = e.T?.F;
|
|
82
82
|
if (!(r instanceof NotReadyError) || r.source !== t) {
|
|
83
|
-
ext(e).
|
|
83
|
+
ext(e).F = new NotReadyError(t);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
function forEachDependent(e, t) {
|
|
88
|
-
for (let n = e.
|
|
88
|
+
for (let n = e.o; n !== null; n = n.t) t(n.u, n);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
// Queue a node to re-run on the next flush (used both when a pending source
|
|
@@ -99,7 +99,7 @@ function forEachDependent(e, t) {
|
|
|
99
99
|
// callbacks handle their own release (settleAutodispose in handleAsync); this
|
|
100
100
|
// covers derivatively-pending dependents, which have no callbacks of their own.
|
|
101
101
|
function releaseIfSettledUnobserved(e) {
|
|
102
|
-
e.
|
|
102
|
+
e.X && e.l & CONFIG_AUTO_DISPOSE && !e.o && !(e.R & REACTIVE_ZOMBIE) && !(e.m & STATUS_PENDING) && unobserved(e);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// Error-path sweep: notifyStatus(STATUS_ERROR) clears dependents' pending
|
|
@@ -112,7 +112,7 @@ function releaseSettledDependents(e) {
|
|
|
112
112
|
const visit = e => {
|
|
113
113
|
if (n.has(e)) return;
|
|
114
114
|
n.add(e);
|
|
115
|
-
if (!e.
|
|
115
|
+
if (!e.o && e.l & CONFIG_AUTO_DISPOSE) (t ??= []).push(e);
|
|
116
116
|
forEachDependent(e, visit);
|
|
117
117
|
};
|
|
118
118
|
forEachDependent(e, visit);
|
|
@@ -134,13 +134,42 @@ function releaseSettledDependents(e) {
|
|
|
134
134
|
// (releaseSettledDependents shape): identity holders can sit below an
|
|
135
135
|
// intermediate whose own error state has since been scrubbed or replaced
|
|
136
136
|
// (e.g. an error boundary's tree node).
|
|
137
|
+
/** D-41: an error a pass of a held node found is an answer of
|
|
138
|
+
* that frame — it ends the wait like a value and shows when a value would.
|
|
139
|
+
* Staged like one: `_pendingValue` carries it (CONFIG_STAGED_ERROR), readers
|
|
140
|
+
* in the hold derive from it (`serve` throws it), and the commit publishes
|
|
141
|
+
* it through the ordinary error path (`commitPendingNode` → `notifyStatus`),
|
|
142
|
+
* dependents included. Not for a lane pass, nor a node with no shown value
|
|
143
|
+
* (born held — its error has no frame to wait behind): those publish now,
|
|
144
|
+
* as before. Returns whether the error was staged. */ function stageError(e, t, n) {
|
|
145
|
+
// Held, or — for a pass's own throw — derived from a staging of a flush
|
|
146
|
+
// (REACTIVE_STAGED_READ is this pass's; a landing after the pass cannot
|
|
147
|
+
// read it, the flush it read a staging of has committed or parked since).
|
|
148
|
+
// Staged whether or not the flush has joined a hold yet: a pass that runs
|
|
149
|
+
// before its async sibling joins is the seam's to decide, like a value
|
|
150
|
+
// (#4109). A flush that parks nothing publishes it at its commit (S-38).
|
|
151
|
+
if (!(e.l & CONFIG_HELD || n && e.R & REACTIVE_STAGED_READ && globalQueue.q) || passLane) return false;
|
|
152
|
+
// The node answered (an error ends the wait — no longer pending, nor
|
|
153
|
+
// blocked); a shown error stays its display until the commit.
|
|
154
|
+
clearPendingSources(e);
|
|
155
|
+
if (e.T) e.T.se = false;
|
|
156
|
+
if (!((e.m &= STATUS_ERROR | STATUS_UNINITIALIZED) & STATUS_ERROR)) setPendingError(e);
|
|
157
|
+
e.ue = t instanceof StatusError ? t : new StatusError(e, t);
|
|
158
|
+
e.l |= CONFIG_STAGED | CONFIG_STAGED_ERROR;
|
|
159
|
+
// Readers parked on the flight re-run and derive from the staging; the
|
|
160
|
+
// verdict readers the walk wakes need a flush (#4106).
|
|
161
|
+
settlePendingSource(e);
|
|
162
|
+
schedule();
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
|
|
137
166
|
function settleErroredDependents(e, t) {
|
|
138
167
|
let n = false;
|
|
139
168
|
const r = new Set;
|
|
140
169
|
const visit = e => {
|
|
141
170
|
if (r.has(e)) return;
|
|
142
171
|
r.add(e);
|
|
143
|
-
if (e.
|
|
172
|
+
if (e.T?.F === t) {
|
|
144
173
|
enqueueSub(e);
|
|
145
174
|
n = true;
|
|
146
175
|
}
|
|
@@ -175,35 +204,35 @@ function settlePendingSource(e, t = e) {
|
|
|
175
204
|
// when the source went pending, `propagateStatus`), and a landing
|
|
176
205
|
// equal to the committed value notifies nobody — the source settling
|
|
177
206
|
// is its verdict changing: it runs again.
|
|
178
|
-
return f.
|
|
207
|
+
return f.l & CONFIG_VERDICT && enqueueSub(f);
|
|
179
208
|
o.add(f);
|
|
180
|
-
f.
|
|
181
|
-
const
|
|
209
|
+
f.ie = clock;
|
|
210
|
+
const s = f.T?.fe?.values().next().value;
|
|
182
211
|
// STATUS_ERROR + pending sources only coexist via an errored loading
|
|
183
212
|
// window's park (notifyStatus(STATUS_ERROR) clears pending sources
|
|
184
213
|
// otherwise): the settled error stays the answer through the settle —
|
|
185
214
|
// nulling it here would have reads throw `null` until the re-enqueued
|
|
186
215
|
// retry lands, or lose it entirely if that retry parks again (#2989).
|
|
187
|
-
const
|
|
188
|
-
if (
|
|
189
|
-
if (!
|
|
216
|
+
const u = f.m & STATUS_ERROR;
|
|
217
|
+
if (s) {
|
|
218
|
+
if (!u) setPendingError(f, s);
|
|
190
219
|
} else {
|
|
191
|
-
f.
|
|
192
|
-
if (!
|
|
220
|
+
f.m &= ~STATUS_PENDING;
|
|
221
|
+
if (!u) setPendingError(f);
|
|
193
222
|
// L2: a landing resumes the transaction waiting on it (maintainer: "it
|
|
194
223
|
// resumes the sync frame") — a held reader's re-pass over the landed
|
|
195
224
|
// value is that transaction's work, and the flush that carries it
|
|
196
225
|
// parks into it.
|
|
197
|
-
if (f.
|
|
198
|
-
if (f.
|
|
226
|
+
if (f.l & CONFIG_HELD) joinFuture(txOf(f));
|
|
227
|
+
if (f.T?.se) {
|
|
199
228
|
enqueueSub(f);
|
|
200
229
|
n = true;
|
|
201
230
|
}
|
|
202
|
-
if (f.
|
|
231
|
+
if (f.T !== null) f.T.se = false;
|
|
203
232
|
// Fully settled with nobody watching: release candidate (#2934). Checked
|
|
204
233
|
// again at release time — deferred so unobserved() can't unlink subs
|
|
205
234
|
// lists this walk is still iterating.
|
|
206
|
-
if (!f.
|
|
235
|
+
if (!f.o && f.l & CONFIG_AUTO_DISPOSE) (r ??= []).push(f);
|
|
207
236
|
}
|
|
208
237
|
forEachDependent(f, settle);
|
|
209
238
|
};
|
|
@@ -220,9 +249,9 @@ function isThenable(e) {
|
|
|
220
249
|
}
|
|
221
250
|
|
|
222
251
|
/** Fire and clear a node's iterator-flight cancellation hook (#3122). */ function releaseFlightTeardown(e) {
|
|
223
|
-
const t = e.
|
|
252
|
+
const t = e.T?.ae;
|
|
224
253
|
if (t != null) {
|
|
225
|
-
e.
|
|
254
|
+
e.T.ae = null;
|
|
226
255
|
t();
|
|
227
256
|
}
|
|
228
257
|
}
|
|
@@ -242,9 +271,9 @@ r) {
|
|
|
242
271
|
});
|
|
243
272
|
}
|
|
244
273
|
if (!f && !o) {
|
|
245
|
-
if (e.
|
|
274
|
+
if (e.T !== null) e.T.Se = null;
|
|
246
275
|
// A sync landing is the first real answer for a loadingValue node.
|
|
247
|
-
e.
|
|
276
|
+
e.ce = false;
|
|
248
277
|
return t;
|
|
249
278
|
}
|
|
250
279
|
// Flight replacement relies on recompute's supersede release for iterator
|
|
@@ -252,57 +281,60 @@ r) {
|
|
|
252
281
|
// self-registration — runs during a recompute of `el`, which has already
|
|
253
282
|
// fired _flightTeardown. A future non-recompute registration path must
|
|
254
283
|
// release it here before overwriting _inFlight.
|
|
255
|
-
ext(e).
|
|
284
|
+
ext(e).Se = t;
|
|
256
285
|
// The question this flight answers (A18 provenance, lanes.ts): the action
|
|
257
286
|
// whose slice asked it; mainline is always the current question.
|
|
258
|
-
e.
|
|
287
|
+
e.T.Ee = question || MAINLINE_QUESTION;
|
|
259
288
|
// The run that asked this flight read every input without throwing: an
|
|
260
289
|
// input still in flight was masked for it (an active override, A17), so
|
|
261
290
|
// pending state those inputs propagated onto the node earlier does not
|
|
262
291
|
// describe this answer. Drop it — the flight is the node's pending now.
|
|
263
292
|
// The landing retires only the flight's own entry (landStatus, #3373), so
|
|
264
293
|
// an entry that survived here would hold the node past its own answer.
|
|
265
|
-
e.
|
|
294
|
+
e.T.fe = undefined;
|
|
266
295
|
// Attribution hook: a new flight is registered. Fired here (not in the
|
|
267
296
|
// branches below) so every flight shape — plain thenable, iterator, the
|
|
268
297
|
// flattened combinations — is announced exactly once, while the recompute
|
|
269
298
|
// frame that caused it is still on the engine's stack. Not inside a try
|
|
270
299
|
// (#2883 — see attribution-hooks.ts).
|
|
271
300
|
if (attrHooks !== null) attrHooks.flightStart(e, t);
|
|
272
|
-
let
|
|
273
|
-
const handleError =
|
|
274
|
-
if (e.
|
|
301
|
+
let s;
|
|
302
|
+
const handleError = o => {
|
|
303
|
+
if (e.T?.Se !== t) return;
|
|
275
304
|
// NotReadyError from rejected promises should be treated as pending, not error
|
|
276
|
-
let
|
|
277
|
-
if (
|
|
305
|
+
let f = o instanceof NotReadyError;
|
|
306
|
+
if (f && e.ce) {
|
|
278
307
|
// Loading window: the flight died waiting on an unready source. Keep
|
|
279
308
|
// serving commit #0 — same parking as recompute's catch for sync
|
|
280
309
|
// dependency throws. The dead flight is released so the clock-gated
|
|
281
310
|
// error-retry pull (updateIfNecessary) can also re-ask.
|
|
282
|
-
if (e.
|
|
283
|
-
parkLoadingWindow(e,
|
|
284
|
-
e.
|
|
311
|
+
if (e.T !== null) e.T.Se = null;
|
|
312
|
+
parkLoadingWindow(e, o);
|
|
313
|
+
e.ie = clock;
|
|
285
314
|
return;
|
|
286
315
|
}
|
|
287
|
-
|
|
316
|
+
// D-41: a held node's error is staged (`stageError`). A derived store's
|
|
317
|
+
// readers learn its error through the family, not the derive's
|
|
318
|
+
// dependents: it publishes now.
|
|
319
|
+
if (f || n || !stageError(e, o)) notifyStatus(e, f ? STATUS_PENDING : STATUS_ERROR, o);
|
|
288
320
|
// A NotReady rejection is a landing into another pending source. The
|
|
289
321
|
// rejected flight will never settle its self entry, so transfer ownership
|
|
290
322
|
// after notifyStatus has propagated the replacement source.
|
|
291
|
-
if (
|
|
292
|
-
e.
|
|
323
|
+
if (f) settlePendingSource(e);
|
|
324
|
+
e.ie = clock;
|
|
293
325
|
// A real error settles derivatively-pending dependents (notifyStatus
|
|
294
326
|
// cleared their pending sources), so stranded lazy ones release here —
|
|
295
327
|
// the error twin of settlePendingSource's release (#2934).
|
|
296
|
-
if (!
|
|
297
|
-
r?.(
|
|
328
|
+
if (!f) releaseSettledDependents(e);
|
|
329
|
+
r?.(o, f);
|
|
298
330
|
};
|
|
299
331
|
const asyncWrite = (r, o) => {
|
|
300
|
-
if (e.
|
|
332
|
+
if (e.T?.Se !== t) return;
|
|
301
333
|
// If the node was dirtied by a newer write (optimistic override or regular),
|
|
302
334
|
// skip this stale async result — the upcoming flush will recompute the node
|
|
303
335
|
// with the new value, creating a fresh Promise that supersedes this one.
|
|
304
336
|
if (e.R & REACTIVE_DIRTY) return;
|
|
305
|
-
const f =
|
|
337
|
+
const f = e.m & STATUS_UNINITIALIZED;
|
|
306
338
|
landStatus(e);
|
|
307
339
|
// Attribution hook: lets the engine snapshot state before the landing
|
|
308
340
|
// branches, so it can tell whether the plain path's setSignal committed a
|
|
@@ -315,22 +347,27 @@ r) {
|
|
|
315
347
|
handleError(e);
|
|
316
348
|
return;
|
|
317
349
|
}
|
|
350
|
+
// A store derive's landing commits through its setter: unheld, the
|
|
351
|
+
// shown error leaves now; held, at the derive's commit.
|
|
352
|
+
if (!(e.l & CONFIG_HELD)) e.T.de = undefined;
|
|
318
353
|
if (f) landStatus(e, true);
|
|
354
|
+
// Attribution hook: this path landed through the setter (derived
|
|
355
|
+
// stores, projections), which does not go through setSignal, so the
|
|
356
|
+
// plain branch's asyncEnd never ran and the flight stayed open — the
|
|
357
|
+
// next flight counted a committed answer as abandoned (#3947).
|
|
358
|
+
// direct=true: asyncEnd finalizes either way and stamps an async write
|
|
359
|
+
// only if the commit moved `_value`/`_time` or parked `_pendingValue`.
|
|
360
|
+
// A store setter does not move this node's `_value`, so no stamp is
|
|
361
|
+
// recorded and waterfall chaining through derived stores stays
|
|
362
|
+
// unmeasured. Outside the try (#2883 — see attribution-hooks.ts).
|
|
363
|
+
if (attrHooks !== null) attrHooks.asyncEnd(e, undefined, r, true);
|
|
319
364
|
} else {
|
|
320
365
|
// CARVE 2: the override-covered landing (hold + A18 supersession) and the
|
|
321
366
|
// lane-routed landing (derived override, lane effect queue) went with
|
|
322
|
-
// the optimistic engine; every landing is the plain setSignal. A
|
|
323
|
-
//
|
|
324
|
-
|
|
325
|
-
e
|
|
326
|
-
if (f && t && (t = liveTx(t))) holdNode(e, t);
|
|
327
|
-
try {
|
|
328
|
-
setSignal(e, () => r);
|
|
329
|
-
} catch (t) {
|
|
330
|
-
// Same containment as above: setSignal's comparator throw is the only
|
|
331
|
-
// pre-commit failure here, and there is no user callsite to throw to.
|
|
332
|
-
notifyStatus(e, STATUS_ERROR, t);
|
|
333
|
-
}
|
|
367
|
+
// the optimistic engine; every landing is the plain setSignal. A node
|
|
368
|
+
// already held stages its answer into that hold (`setSignal`). Its
|
|
369
|
+
// comparator throw is contained there (`isSame`, A3).
|
|
370
|
+
setSignal(e, () => r);
|
|
334
371
|
// Attribution hook: this path landed through setSignal, whose write
|
|
335
372
|
// hook already saw any committed change — direct=false lets the engine
|
|
336
373
|
// reclassify that write as an async landing iff it actually committed.
|
|
@@ -342,8 +379,8 @@ r) {
|
|
|
342
379
|
// (`_pendingValue` set inside setSignal) is not — commitPendingNode closes
|
|
343
380
|
// the window when the flush commits it, so no one-frame pulse can leak to
|
|
344
381
|
// observers between the landing and its commit (#2990).
|
|
345
|
-
if (e.
|
|
346
|
-
e.
|
|
382
|
+
if (e.ue === NOT_PENDING) {
|
|
383
|
+
e.ce = false;
|
|
347
384
|
// The landing published: the dependency tail the flight's pass left
|
|
348
385
|
// linked goes now (A30, #3410). A staged landing has not replaced the
|
|
349
386
|
// committed frame yet; `commitPendingNode` trims it.
|
|
@@ -362,7 +399,7 @@ r) {
|
|
|
362
399
|
// Returns whether the node released, so the iterator branch can stop
|
|
363
400
|
// pulling values instead of pumping an unobserved stream forever (#2935).
|
|
364
401
|
const settleAutodispose = () => {
|
|
365
|
-
if (e.
|
|
402
|
+
if (e.l & CONFIG_AUTO_DISPOSE && !e.o && !(e.m & STATUS_PENDING)) {
|
|
366
403
|
unobserved(e);
|
|
367
404
|
return true;
|
|
368
405
|
}
|
|
@@ -375,46 +412,52 @@ r) {
|
|
|
375
412
|
// close registration uses the ambient owner; and DEFERRED (the flattening
|
|
376
413
|
// path — a thenable resolved to an iterable in a later microtask), where
|
|
377
414
|
// there is no caller to serve and no ambient owner: sync-settled steps
|
|
378
|
-
// write through asyncWrite, and close registration
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
// meaningful only in the live posture.
|
|
415
|
+
// write through asyncWrite, and close registration targets `owner` (el
|
|
416
|
+
// itself) directly. Returns whether a sync answer landed (first yield or
|
|
417
|
+
// empty completion) — meaningful only in the live posture.
|
|
382
418
|
const consumeIterator = (n, r) => {
|
|
383
419
|
const o = n[Symbol.asyncIterator]();
|
|
384
420
|
let f = false;
|
|
385
|
-
let
|
|
386
|
-
let
|
|
421
|
+
let u = false;
|
|
422
|
+
let i = !r;
|
|
387
423
|
const close = () => {
|
|
388
|
-
if (
|
|
389
|
-
|
|
424
|
+
if (u) return;
|
|
425
|
+
u = true;
|
|
390
426
|
try {
|
|
391
427
|
const e = o.return?.();
|
|
392
428
|
if (isThenable(e)) e.then(undefined, () => {});
|
|
393
429
|
} catch {}
|
|
394
430
|
};
|
|
395
|
-
|
|
431
|
+
cleanup(close, r);
|
|
396
432
|
// Flight-identity cancellation (#3122): the registration above is the
|
|
397
433
|
// owner-death backstop; the teardown slot fires at the _inFlight release
|
|
398
434
|
// sites so supersede stops this stream immediately.
|
|
399
|
-
ext(e).
|
|
435
|
+
ext(e).ae = close;
|
|
400
436
|
// Release check before each next pull: an unobserved lazy node must tear
|
|
401
437
|
// down (its close above runs via disposal, closing the iterator) instead
|
|
402
438
|
// of pumping the stream forever with zero subscribers (#2935).
|
|
439
|
+
// Later pulls run from an async continuation with no compute catch above
|
|
440
|
+
// them: a throw invoking the protocol (next(), the step's then) settles
|
|
441
|
+
// as this flight's error instead of escaping as a rejection (#4112).
|
|
403
442
|
const iterateOrRelease = () => {
|
|
404
|
-
|
|
443
|
+
try {
|
|
444
|
+
if (!settleAutodispose()) iterate();
|
|
445
|
+
} catch (e) {
|
|
446
|
+
handleError(e);
|
|
447
|
+
}
|
|
405
448
|
};
|
|
406
449
|
const iterate = () => {
|
|
407
|
-
let n, r, l = false, a = false,
|
|
450
|
+
let n, r, l = false, a = false, S = true;
|
|
408
451
|
// Protocol tolerance, matching `for await`: `await` unwraps whatever
|
|
409
452
|
// next() returns — a thenable OR a bare IteratorResult. Real producers
|
|
410
453
|
// use the bare form as a promise-free fast path when a value is already
|
|
411
454
|
// buffered (seroval's deserialized streams do), so a bare result is
|
|
412
455
|
// assimilated as an already-settled step instead of crashing on `.then`.
|
|
413
|
-
const
|
|
414
|
-
const
|
|
415
|
-
then: e => void e(
|
|
456
|
+
const c = o.next();
|
|
457
|
+
const E = isThenable(c) ? c : {
|
|
458
|
+
then: e => void e(c)
|
|
416
459
|
};
|
|
417
|
-
|
|
460
|
+
E.then(r => {
|
|
418
461
|
// The sync stash only serves the INITIAL drain (handleAsync's caller
|
|
419
462
|
// consumes syncValue / throws NotReady from it). A sync-settled step
|
|
420
463
|
// after an async gap — seroval buffering values between pulls, a
|
|
@@ -422,17 +465,17 @@ r) {
|
|
|
422
465
|
// stash: it must write through the async path or the value is
|
|
423
466
|
// silently dropped. (The deferred posture never has a caller, so
|
|
424
467
|
// initialRead starts false there and everything writes through.)
|
|
425
|
-
if (
|
|
468
|
+
if (S && i) {
|
|
426
469
|
n = r;
|
|
427
470
|
l = true;
|
|
428
|
-
if (r.done)
|
|
429
|
-
} else if (e.
|
|
471
|
+
if (r.done) u = true;
|
|
472
|
+
} else if (e.T?.Se !== t) {
|
|
430
473
|
return;
|
|
431
474
|
} else if (!r.done) {
|
|
432
475
|
f = true;
|
|
433
476
|
asyncWrite(r.value, iterateOrRelease);
|
|
434
477
|
} else {
|
|
435
|
-
|
|
478
|
+
u = true;
|
|
436
479
|
if (f) {
|
|
437
480
|
schedule();
|
|
438
481
|
flush();
|
|
@@ -443,25 +486,25 @@ r) {
|
|
|
443
486
|
settleAutodispose();
|
|
444
487
|
}
|
|
445
488
|
}, n => {
|
|
446
|
-
if (
|
|
489
|
+
if (S && i) {
|
|
447
490
|
r = n;
|
|
448
491
|
a = true;
|
|
449
|
-
} else if (e.
|
|
450
|
-
|
|
492
|
+
} else if (e.T?.Se === t) {
|
|
493
|
+
u = true;
|
|
451
494
|
handleError(n);
|
|
452
495
|
settleAutodispose();
|
|
453
496
|
}
|
|
454
497
|
});
|
|
455
|
-
|
|
498
|
+
S = false;
|
|
456
499
|
if (a) {
|
|
457
500
|
// Match the promise branch, but only rethrow during the initial read.
|
|
458
|
-
|
|
501
|
+
u = true;
|
|
459
502
|
handleError(r);
|
|
460
|
-
if (
|
|
503
|
+
if (i) throw r;
|
|
461
504
|
return true;
|
|
462
505
|
}
|
|
463
506
|
if (l && !n.done) {
|
|
464
|
-
|
|
507
|
+
s = n.value;
|
|
465
508
|
f = true;
|
|
466
509
|
return iterate();
|
|
467
510
|
}
|
|
@@ -469,13 +512,13 @@ r) {
|
|
|
469
512
|
};
|
|
470
513
|
const l = iterate();
|
|
471
514
|
// Later iterate() calls run from asyncWrite, where rethrowing would be unhandled.
|
|
472
|
-
|
|
515
|
+
i = false;
|
|
473
516
|
return f || l;
|
|
474
517
|
};
|
|
475
518
|
// Landed-synchronously verdict for a LIVE iterator drain; null when no live
|
|
476
519
|
// drain ran (plain promise flight, or a deferred flatten). Drives the
|
|
477
520
|
// shared NotReady/loading tail below.
|
|
478
|
-
let
|
|
521
|
+
let u = null;
|
|
479
522
|
// Flatten one async level: a thenable that RESOLVES to an AsyncIterable —
|
|
480
523
|
// the shape every async stub returning a stream produces — consumes as the
|
|
481
524
|
// stream itself rather than settling on the iterable object. One level
|
|
@@ -489,27 +532,33 @@ r) {
|
|
|
489
532
|
});
|
|
490
533
|
}
|
|
491
534
|
if (!n) return false;
|
|
535
|
+
s = undefined;
|
|
492
536
|
const r = consumeIterator(e, t);
|
|
493
|
-
if (!t)
|
|
537
|
+
if (!t) u = r;
|
|
494
538
|
return true;
|
|
495
539
|
};
|
|
496
540
|
if (f) {
|
|
497
541
|
let n = false, r = false, o, f = true;
|
|
498
|
-
// Close registration for the flattening path. Consumption starts in a
|
|
499
|
-
// microtask where the ambient owner is gone (or worse, someone else's),
|
|
500
|
-
// so `cleanup()` can't be used — the close targets el's disposal list
|
|
501
|
-
// directly, exactly where a live cleanup() during this recompute would
|
|
502
|
-
// have put it. Only a flight that actually flattens becomes
|
|
503
|
-
// disposal-bearing.
|
|
504
|
-
const registerDeferredClose = t => {
|
|
505
|
-
if (!e.Pe) e.Pe = t; else if (Array.isArray(e.Pe)) e.Pe.push(t); else e.Pe = [ e.Pe, t ];
|
|
506
|
-
};
|
|
507
542
|
t.then(r => {
|
|
508
543
|
if (f) {
|
|
509
|
-
|
|
544
|
+
s = r;
|
|
510
545
|
n = true;
|
|
511
|
-
} else
|
|
512
|
-
|
|
546
|
+
} else {
|
|
547
|
+
// Flattening consumes the stream as the value: each yield lands
|
|
548
|
+
// through asyncWrite under this flight's identity; the first one
|
|
549
|
+
// clears pending exactly like a plain promise resolution would.
|
|
550
|
+
// Disposed nodes never start a pump (their disposal list already
|
|
551
|
+
// ran, so nothing could close the iterator). Consumption starts in
|
|
552
|
+
// a microtask where the ambient owner is gone, so the close
|
|
553
|
+
// registers on el's own disposal list. Iterator setup here has no
|
|
554
|
+
// compute catch above it: a protocol throw settles as this
|
|
555
|
+
// flight's error (#4112).
|
|
556
|
+
try {
|
|
557
|
+
if (e.T?.Se === t && !(e.R & REACTIVE_DISPOSED) && flattenIfIterable(r, e)) return;
|
|
558
|
+
asyncWrite(r);
|
|
559
|
+
} catch (e) {
|
|
560
|
+
handleError(e);
|
|
561
|
+
}
|
|
513
562
|
settleAutodispose();
|
|
514
563
|
}
|
|
515
564
|
}, e => {
|
|
@@ -533,35 +582,35 @@ r) {
|
|
|
533
582
|
// work on a loadingValue node is loading-class (invisible to
|
|
534
583
|
// boundaries); the flight itself is already registered in _inFlight and
|
|
535
584
|
// lands through asyncWrite.
|
|
536
|
-
if (e.
|
|
585
|
+
if (e.ce) return e.Ie;
|
|
537
586
|
throw new NotReadyError(context);
|
|
538
|
-
} else if (!flattenIfIterable(
|
|
587
|
+
} else if (!flattenIfIterable(s)) {
|
|
539
588
|
// Synchronously-resolved promise: the first real answer landed.
|
|
540
|
-
e.
|
|
589
|
+
e.ce = false;
|
|
541
590
|
}
|
|
542
591
|
// A sync-resolved promise holding an AsyncIterable flattened LIVE (we
|
|
543
592
|
// are still inside the synchronous read): full initial-drain semantics
|
|
544
593
|
// apply and the shared tail below settles the verdict.
|
|
545
594
|
}
|
|
546
595
|
if (o) flattenIfIterable(t);
|
|
547
|
-
if (
|
|
548
|
-
if (!
|
|
596
|
+
if (u !== null) {
|
|
597
|
+
if (!u) {
|
|
549
598
|
// Loading window: serve commit #0 (see the promise branch above).
|
|
550
|
-
if (e.
|
|
599
|
+
if (e.ce) return e.Ie;
|
|
551
600
|
throw new NotReadyError(context);
|
|
552
601
|
}
|
|
553
602
|
// A sync first yield (or immediate empty completion) is the first real
|
|
554
603
|
// answer; async yields clear inside asyncWrite.
|
|
555
|
-
e.
|
|
604
|
+
e.ce = false;
|
|
556
605
|
}
|
|
557
|
-
return
|
|
606
|
+
return s;
|
|
558
607
|
}
|
|
559
608
|
|
|
560
609
|
function clearStatus(e, t = false) {
|
|
561
|
-
if (e.
|
|
562
|
-
if (e.
|
|
563
|
-
e.
|
|
564
|
-
if (e.
|
|
610
|
+
if (e.T?.fe) clearPendingSources(e);
|
|
611
|
+
if (e.T?.se) if (e.T !== null) e.T.se = false;
|
|
612
|
+
e.m = t ? 0 : e.m & STATUS_UNINITIALIZED;
|
|
613
|
+
if (e.T?.F) setPendingError(e);
|
|
565
614
|
const n = statusNotifierOf(e);
|
|
566
615
|
if (n) n.call(e);
|
|
567
616
|
}
|
|
@@ -591,11 +640,11 @@ function clearStatus(e, t = false) {
|
|
|
591
640
|
* reader thrown NotReady(self) would park on a retired entry. Companions
|
|
592
641
|
* keep their verdict (pending before and after; the write re-syncs them).
|
|
593
642
|
*/ function landStatus(e, t = false) {
|
|
594
|
-
const n = e.
|
|
643
|
+
const n = e.T?.fe;
|
|
595
644
|
// (The full clear below drops the set whether or not self was retired first.)
|
|
596
645
|
if (n && (n.delete(e), n.size)) {
|
|
597
|
-
e.
|
|
598
|
-
if (t) e.
|
|
646
|
+
e.T.se = false;
|
|
647
|
+
if (t) e.m = STATUS_PENDING;
|
|
599
648
|
setPendingError(e, n.values().next().value);
|
|
600
649
|
} else clearStatus(e, t);
|
|
601
650
|
}
|
|
@@ -607,15 +656,17 @@ function notifyStatus(e, t, n, r) {
|
|
|
607
656
|
// (CARVE 2: the lane assignment went with the engine.)
|
|
608
657
|
{
|
|
609
658
|
if (t === STATUS_PENDING && o) {
|
|
659
|
+
// D-41: a re-ask of a shown error keeps it the display (`_shownError`).
|
|
660
|
+
if (e.m & STATUS_ERROR) e.T.de = e.T.F;
|
|
610
661
|
addPendingSource(e, o);
|
|
611
|
-
e.
|
|
662
|
+
e.m = STATUS_PENDING | e.m & STATUS_UNINITIALIZED;
|
|
612
663
|
// Preserve the current source on this propagation so readers park on
|
|
613
664
|
// the distinct pending source.
|
|
614
665
|
setPendingError(e, o, n);
|
|
615
666
|
} else {
|
|
616
667
|
clearPendingSources(e);
|
|
617
|
-
e.
|
|
618
|
-
ext(e).
|
|
668
|
+
e.m = t | (t !== STATUS_ERROR ? e.m & STATUS_UNINITIALIZED : 0);
|
|
669
|
+
ext(e).F = n;
|
|
619
670
|
}
|
|
620
671
|
}
|
|
621
672
|
// Lanes: a written guess is an optimistic boundary (A17 — "the optimistic
|
|
@@ -624,7 +675,7 @@ function notifyStatus(e, t, n, r) {
|
|
|
624
675
|
// transition's to wait on — the frame it was asked in holds (A17: "a
|
|
625
676
|
// transition whose optimistic node is still pending on its own fetch is
|
|
626
677
|
// not complete"), through the node itself (`blocked`).
|
|
627
|
-
if (t === STATUS_PENDING && e.
|
|
678
|
+
if (t === STATUS_PENDING && e.l & CONFIG_GUESS) {
|
|
628
679
|
if (passLane === null) joinFuture(null);
|
|
629
680
|
return;
|
|
630
681
|
}
|
|
@@ -645,7 +696,7 @@ function notifyStatus(e, t, n, r) {
|
|
|
645
696
|
* (store/projection.ts `wakeFamily`), exactly as a memo's would reach its. */ function propagateStatus(e, t, n, r) {
|
|
646
697
|
const o = t === STATUS_PENDING && n instanceof NotReadyError ? n.source : undefined;
|
|
647
698
|
forEachDependent(e, (r, f) => {
|
|
648
|
-
r.
|
|
699
|
+
r.ie = clock;
|
|
649
700
|
// A pending mark on a kept-tail link re-derives the subscriber instead of
|
|
650
701
|
// marking it (A30, #3494 review; fuzzer latest-1 #2141; #3519 review).
|
|
651
702
|
// Past `_depsTail` lie the committed frame's deps, kept by A30 because
|
|
@@ -665,7 +716,7 @@ function notifyStatus(e, t, n, r) {
|
|
|
665
716
|
// Inheriting the error would make a probe's reader errored. A boundary's
|
|
666
717
|
// output (CONFIG_REDERIVE, boundaries.ts) the same: content or fallback
|
|
667
718
|
// is its pass's call.
|
|
668
|
-
if (r.
|
|
719
|
+
if (r.l & (CONFIG_REDERIVE | CONFIG_VERDICT) || t === STATUS_PENDING && f.Te !== r.Ne) {
|
|
669
720
|
enqueueSub(r);
|
|
670
721
|
schedule();
|
|
671
722
|
return;
|
|
@@ -677,25 +728,25 @@ function notifyStatus(e, t, n, r) {
|
|
|
677
728
|
// frame still passes through — its pass is the frame's (#3372, compiled
|
|
678
729
|
// shape: the unmount it stages makes the observer a zombie whose say is
|
|
679
730
|
// moot for this transaction, A15 #3463).
|
|
680
|
-
if (t === STATUS_PENDING && o && r.
|
|
681
|
-
if (passLane === null && (r.
|
|
731
|
+
if (t === STATUS_PENDING && o && r.T?.fe?.has(o)) {
|
|
732
|
+
if (passLane === null && (r.l & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && globalQueue.q && !freshHook?.(r, o.ie === clock)) joinFuture(txOf(r));
|
|
682
733
|
return;
|
|
683
734
|
}
|
|
684
|
-
if (t === STATUS_PENDING && o || t !== STATUS_PENDING && (r.
|
|
735
|
+
if (t === STATUS_PENDING && o || t !== STATUS_PENDING && (r.T?.F !== n || r.T?.fe)) {
|
|
685
736
|
// The marked subscriber is queued for the commit sweep so its status
|
|
686
737
|
// bookkeeping (loading window, uninitialized clear) runs with the flush
|
|
687
738
|
// — with the lane, when the pass propagating is the lane's work and
|
|
688
739
|
// the dependent's pending is the lane's own flight (`laneStage`).
|
|
689
740
|
{
|
|
690
741
|
// (No answer: a pending propagation leaves a written guess a guess.)
|
|
691
|
-
if (passLane !== null)
|
|
742
|
+
if (laneStageHook && passLane !== null) laneStageHook(r, passLane, true, e); else {
|
|
692
743
|
queuePendingNode(r);
|
|
693
744
|
// A15 (#3443): pending propagates onto a held memo without
|
|
694
745
|
// recomputing it, and the propagation itself enters the memo's
|
|
695
746
|
// transaction — the flight flows into a memo that transaction
|
|
696
747
|
// holds, so the write that started it is held with it. A render
|
|
697
748
|
// effect's membership is its pass's (`notify`), never sticky.
|
|
698
|
-
if (t === STATUS_PENDING && (r.
|
|
749
|
+
if (t === STATUS_PENDING && (r.l & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && !r.i) joinFuture(txOf(r));
|
|
699
750
|
}
|
|
700
751
|
}
|
|
701
752
|
notifyStatus(r, t, n);
|
|
@@ -703,4 +754,4 @@ function notifyStatus(e, t, n, r) {
|
|
|
703
754
|
});
|
|
704
755
|
}
|
|
705
756
|
|
|
706
|
-
export { addPendingSource, clearStatus, forEachDependent, handleAsync, isThenable, notifyStatus, parkLoadingWindow, propagateStatus, releaseFlightTeardown, releaseSettledDependents, setPendingError, settleErroredDependents, settlePendingSource };
|
|
757
|
+
export { addPendingSource, clearStatus, forEachDependent, handleAsync, isThenable, notifyStatus, parkLoadingWindow, propagateStatus, releaseFlightTeardown, releaseIfSettledUnobserved, releaseSettledDependents, setPendingError, settleErroredDependents, settlePendingSource, stageError };
|