@solidjs/signals 2.0.0-rc.8 → 2.0.0-rc.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev-shared.js +1399 -285
- package/dist/dev.attribution.js +459 -307
- package/dist/dev.js +1864 -435
- package/dist/observe/affects.js +3 -1
- package/dist/observe/attribution.js +7 -1
- package/dist/observe/boundaries.js +209 -154
- package/dist/observe/core/action.js +18 -8
- package/dist/observe/core/async.js +220 -110
- package/dist/observe/core/attribution-costs.js +66 -0
- package/dist/observe/core/attribution-feedback.js +282 -0
- package/dist/observe/core/attribution-hooks.js +23 -1
- package/dist/observe/core/attribution-queries.js +28 -0
- package/dist/observe/core/attribution.js +262 -485
- package/dist/observe/core/constants.js +34 -1
- package/dist/observe/core/context.js +3 -3
- package/dist/observe/core/core.js +867 -367
- package/dist/observe/core/dev.js +78 -17
- package/dist/observe/core/effect.js +67 -51
- package/dist/observe/core/error-hooks.js +71 -0
- package/dist/observe/core/external.js +4 -4
- package/dist/observe/core/graph.js +37 -37
- package/dist/observe/core/heap.js +45 -45
- package/dist/observe/core/invariants.js +2 -0
- package/dist/observe/core/lanes.js +86 -49
- package/dist/observe/core/optimistic.js +242 -95
- package/dist/observe/core/owner.js +98 -84
- package/dist/observe/core/scheduler.js +543 -305
- package/dist/observe/core/verdict.js +247 -129
- package/dist/observe/index.js +7 -3
- package/dist/observe/map.js +126 -124
- package/dist/observe/signals.js +33 -17
- package/dist/observe/store/index.js +2 -0
- package/dist/observe/store/next/optimistic.js +141 -132
- package/dist/observe/store/next/projection.js +34 -21
- package/dist/observe/store/next/reconcile.js +58 -56
- package/dist/observe/store/next/store.js +260 -146
- package/dist/observe/store/store.js +5 -3
- package/dist/observe/store/utils.js +948 -135
- package/dist/prod/attribution.js +26 -17
- package/dist/prod/boundaries.js +128 -76
- package/dist/prod/core/action.js +16 -8
- package/dist/prod/core/async.js +251 -143
- package/dist/prod/core/constants.js +34 -1
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +843 -347
- package/dist/prod/core/dev.js +17 -1
- package/dist/prod/core/effect.js +48 -34
- package/dist/prod/core/error-hooks.js +71 -0
- package/dist/prod/core/external.js +4 -4
- package/dist/prod/core/graph.js +37 -37
- package/dist/prod/core/heap.js +45 -45
- package/dist/prod/core/lanes.js +90 -53
- package/dist/prod/core/optimistic.js +247 -100
- package/dist/prod/core/owner.js +57 -45
- package/dist/prod/core/scheduler.js +543 -305
- package/dist/prod/core/verdict.js +245 -127
- package/dist/prod/index.js +7 -3
- package/dist/prod/map.js +112 -112
- package/dist/prod/signals.js +28 -12
- package/dist/prod/store/next/optimistic.js +135 -128
- package/dist/prod/store/next/projection.js +31 -20
- package/dist/prod/store/next/store.js +325 -252
- package/dist/prod/store/store.js +2 -2
- package/dist/prod/store/utils.js +946 -135
- package/dist/types/attribution.d.ts +7 -2
- package/dist/types/attribution.prod.d.ts +10 -1
- package/dist/types/boundaries.d.ts +10 -1
- package/dist/types/core/action.d.ts +12 -5
- package/dist/types/core/attribution-costs.d.ts +35 -0
- package/dist/types/core/attribution-feedback.d.ts +133 -0
- package/dist/types/core/attribution-hooks.d.ts +28 -3
- package/dist/types/core/attribution-queries.d.ts +10 -0
- package/dist/types/core/attribution.d.ts +51 -172
- package/dist/types/core/constants.d.ts +33 -0
- package/dist/types/core/core.d.ts +186 -5
- package/dist/types/core/dev.d.ts +129 -9
- package/dist/types/core/error-hooks.d.ts +71 -0
- package/dist/types/core/index.d.ts +3 -1
- package/dist/types/core/invariants.d.ts +4 -0
- package/dist/types/core/lanes.d.ts +31 -4
- package/dist/types/core/scheduler.d.ts +95 -2
- package/dist/types/core/types.d.ts +3 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/signals.d.ts +8 -0
- package/dist/types/store/index.d.ts +2 -1
- package/dist/types/store/next/optimistic.d.ts +1 -1
- package/dist/types/store/next/store.d.ts +6 -5
- package/dist/types/store/next/target.d.ts +1 -1
- package/dist/types/store/utils.d.ts +177 -6
- package/package.json +1 -1
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
import { STATUS_UNINITIALIZED, CONFIG_CHILD_COMPANIONS, STATUS_ERROR, STATUS_PENDING, CONFIG_INPUTS_PUBLISHED, REACTIVE_DIRTY, REACTIVE_OPTIMISTIC_DIRTY, NOT_PENDING, CONFIG_AUTO_DISPOSE, REACTIVE_ZOMBIE, REACTIVE_DISPOSED } from "./constants.js";
|
|
1
|
+
import { STATUS_UNINITIALIZED, CONFIG_CHILD_COMPANIONS, STATUS_ERROR, STATUS_PENDING, CONFIG_DERIVED_OVERRIDE, CONFIG_INPUTS_PUBLISHED, REACTIVE_DIRTY, REACTIVE_OPTIMISTIC_DIRTY, NOT_PENDING, unwrapOverride, CONFIG_AUTO_DISPOSE, REACTIVE_ZOMBIE, REACTIVE_DISPOSED } from "./constants.js";
|
|
2
2
|
|
|
3
3
|
import { attrHooks } from "./attribution-hooks.js";
|
|
4
4
|
|
|
5
|
-
import { untrack, ext, statusNotifierOf, setSignal, context } from "./core.js";
|
|
5
|
+
import { untrack, ext, statusNotifierOf, hasActiveOverride, setSignal, context } from "./core.js";
|
|
6
6
|
|
|
7
7
|
import "./invariants.js";
|
|
8
8
|
|
|
9
|
+
import "./dev.js";
|
|
10
|
+
|
|
9
11
|
import { StatusError, NotReadyError } from "./error.js";
|
|
10
12
|
|
|
11
13
|
import { trimStaleDeps, unobserved } from "./graph.js";
|
|
12
14
|
|
|
13
15
|
import { enqueueSub } from "./heap.js";
|
|
14
16
|
|
|
15
|
-
import {
|
|
17
|
+
import { assignOrMergeLane, resolveLane, resolveTransition } from "./lanes.js";
|
|
16
18
|
|
|
17
19
|
import { cleanup } from "./owner.js";
|
|
18
20
|
|
|
19
|
-
import { GlobalQueue, schedule, flush, queuePendingNode, insertSubs, clock, waitingTransition, currentTransition, globalQueue, setOrigin, origin } from "./scheduler.js";
|
|
21
|
+
import { GlobalQueue, schedule, flush, queuePendingNode, insertSubs, clock, waitingTransition, currentTransition, globalQueue, enterWaiting, setOrigin, origin } from "./scheduler.js";
|
|
20
22
|
|
|
21
23
|
// The lazily-created Set is the ONE container for pending sources. Its
|
|
22
24
|
// predecessor — a singular slot promoted to a Set on the second source —
|
|
@@ -24,22 +26,22 @@ import { GlobalQueue, schedule, flush, queuePendingNode, insertSubs, clock, wait
|
|
|
24
26
|
// overlapping source landed beside the Set and removePendingSource refused
|
|
25
27
|
// to clear it, stranding the Set members' pending forever (#2893).
|
|
26
28
|
function addPendingSource(e, t) {
|
|
27
|
-
if (e.o?.
|
|
28
|
-
(ext(e).
|
|
29
|
+
if (e.o?.ut?.has(t)) return false;
|
|
30
|
+
(ext(e).ut ??= new Set).add(t);
|
|
29
31
|
return true;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
function removePendingSource(e, t) {
|
|
33
|
-
const n = e.o?.
|
|
35
|
+
const n = e.o?.ut;
|
|
34
36
|
if (!n?.delete(t)) return false;
|
|
35
|
-
if (!n.size) e.o.
|
|
37
|
+
if (!n.size) e.o.ut = undefined;
|
|
36
38
|
return true;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
function clearPendingSources(e) {
|
|
40
42
|
// This set is node-owned and never shared; dropping the sole reference
|
|
41
43
|
// releases the set and every entry without a redundant clear() walk.
|
|
42
|
-
if (e.o !== null) e.o.
|
|
44
|
+
if (e.o !== null) e.o.ut = undefined;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
// A rejection-pending only resolves through the settle sweep over the
|
|
@@ -48,9 +50,9 @@ function clearPendingSources(e) {
|
|
|
48
50
|
// (pending sources propagate the origin node, so this covers any depth).
|
|
49
51
|
// Also guards branch-local recovery: another dependency may still need the source.
|
|
50
52
|
function retryReaches(e, t) {
|
|
51
|
-
for (let n = e.
|
|
52
|
-
const e = n.
|
|
53
|
-
if (e === t || e.o?.
|
|
53
|
+
for (let n = e.ee; n; n = n.te) {
|
|
54
|
+
const e = n.re.ne || n.re;
|
|
55
|
+
if (e === t || e.o?.ut?.has(t)) return true;
|
|
54
56
|
}
|
|
55
57
|
return false;
|
|
56
58
|
}
|
|
@@ -62,7 +64,7 @@ function retryReaches(e, t) {
|
|
|
62
64
|
* with NO read-visible pending status, no downstream propagation, no
|
|
63
65
|
* transition, no lane registration. Commit #0 keeps serving.
|
|
64
66
|
*/ function parkLoadingWindow(e, t) {
|
|
65
|
-
ext(e).
|
|
67
|
+
ext(e).oe = true;
|
|
66
68
|
if (t.source) addPendingSource(e, t.source);
|
|
67
69
|
// A settled error is the node's answer ("the error stays the answer until
|
|
68
70
|
// this retry can actually run") — the park must not replace it: reads
|
|
@@ -87,10 +89,10 @@ function setPendingError(e, t, n) {
|
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
function forEachDependent(e, t) {
|
|
90
|
-
for (let n = e.u; n !== null; n = n.
|
|
92
|
+
for (let n = e.u; n !== null; n = n.ie) t(n.se, n);
|
|
91
93
|
// `?? null`: affects() marks route plain signals (no `_child` slot) through here.
|
|
92
94
|
for (let n = e.o?.i ?? null; n !== null; n = n.ue) {
|
|
93
|
-
for (let e = n.u; e !== null; e = e.
|
|
95
|
+
for (let e = n.u; e !== null; e = e.ie) t(e.se, e);
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -105,7 +107,7 @@ function forEachDependent(e, t) {
|
|
|
105
107
|
// callbacks handle their own release (settleAutodispose in handleAsync); this
|
|
106
108
|
// covers derivatively-pending dependents, which have no callbacks of their own.
|
|
107
109
|
function releaseIfSettledUnobserved(e) {
|
|
108
|
-
e.
|
|
110
|
+
e.ht && e.C & CONFIG_AUTO_DISPOSE && !e.u && !(e.ft & REACTIVE_ZOMBIE) && !(e.S & STATUS_PENDING) && unobserved(e);
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
// Error-path sweep: notifyStatus(STATUS_ERROR) clears dependents' pending
|
|
@@ -118,7 +120,7 @@ function releaseSettledDependents(e) {
|
|
|
118
120
|
const visit = e => {
|
|
119
121
|
if (n.has(e)) return;
|
|
120
122
|
n.add(e);
|
|
121
|
-
if (!e.u && e.
|
|
123
|
+
if (!e.u && e.C & CONFIG_AUTO_DISPOSE) (t ??= []).push(e);
|
|
122
124
|
forEachDependent(e, visit);
|
|
123
125
|
};
|
|
124
126
|
forEachDependent(e, visit);
|
|
@@ -169,7 +171,7 @@ function settlePendingSource(e, t = e) {
|
|
|
169
171
|
let r;
|
|
170
172
|
const o = new Set;
|
|
171
173
|
// Companion updates no-op without the verdict layer (null hook).
|
|
172
|
-
const i = GlobalQueue.
|
|
174
|
+
const i = GlobalQueue.le;
|
|
173
175
|
const settle = s => {
|
|
174
176
|
if (o.has(s)) return;
|
|
175
177
|
// A conditional dropped this source, but another dependency can still
|
|
@@ -181,30 +183,30 @@ function settlePendingSource(e, t = e) {
|
|
|
181
183
|
if (t !== e && retryReaches(s, t)) return;
|
|
182
184
|
if (!removePendingSource(s, t)) return;
|
|
183
185
|
o.add(s);
|
|
184
|
-
s.
|
|
185
|
-
const
|
|
186
|
+
s.fe = clock;
|
|
187
|
+
const u = s.o?.ut?.values().next().value;
|
|
186
188
|
// STATUS_ERROR + pending sources only coexist via an errored loading
|
|
187
189
|
// window's park (notifyStatus(STATUS_ERROR) clears pending sources
|
|
188
190
|
// otherwise): the settled error stays the answer through the settle —
|
|
189
191
|
// nulling it here would have reads throw `null` until the re-enqueued
|
|
190
192
|
// retry lands, or lose it entirely if that retry parks again (#2989).
|
|
191
|
-
const
|
|
192
|
-
if (
|
|
193
|
-
if (!
|
|
193
|
+
const l = s.S & STATUS_ERROR;
|
|
194
|
+
if (u) {
|
|
195
|
+
if (!l) setPendingError(s, u);
|
|
194
196
|
i?.(s);
|
|
195
197
|
} else {
|
|
196
198
|
s.S &= ~STATUS_PENDING;
|
|
197
|
-
if (!
|
|
199
|
+
if (!l) setPendingError(s);
|
|
198
200
|
i?.(s);
|
|
199
|
-
if (s.o?.
|
|
201
|
+
if (s.o?.oe) {
|
|
200
202
|
enqueueSub(s);
|
|
201
203
|
n = true;
|
|
202
204
|
}
|
|
203
|
-
if (s.o !== null) s.o.
|
|
205
|
+
if (s.o !== null) s.o.oe = false;
|
|
204
206
|
// Fully settled with nobody watching: release candidate (#2934). Checked
|
|
205
207
|
// again at release time — deferred so unobserved() can't unlink subs
|
|
206
208
|
// lists this walk is still iterating.
|
|
207
|
-
if (!s.u && s.
|
|
209
|
+
if (!s.u && s.C & CONFIG_AUTO_DISPOSE) (r ??= []).push(s);
|
|
208
210
|
}
|
|
209
211
|
forEachDependent(s, settle);
|
|
210
212
|
};
|
|
@@ -221,9 +223,9 @@ function isThenable(e) {
|
|
|
221
223
|
}
|
|
222
224
|
|
|
223
225
|
/** Fire and clear a node's iterator-flight cancellation hook (#3122). */ function releaseFlightTeardown(e) {
|
|
224
|
-
const t = e.o?.
|
|
226
|
+
const t = e.o?.ae;
|
|
225
227
|
if (t != null) {
|
|
226
|
-
e.o.
|
|
228
|
+
e.o.ae = null;
|
|
227
229
|
t();
|
|
228
230
|
}
|
|
229
231
|
}
|
|
@@ -238,9 +240,9 @@ function handleAsync(e, t, n) {
|
|
|
238
240
|
});
|
|
239
241
|
}
|
|
240
242
|
if (!o && !r) {
|
|
241
|
-
if (e.o !== null) e.o.
|
|
243
|
+
if (e.o !== null) e.o.ce = null;
|
|
242
244
|
// A sync landing is the first real answer for a loadingValue node.
|
|
243
|
-
e.
|
|
245
|
+
e.Se = false;
|
|
244
246
|
return t;
|
|
245
247
|
}
|
|
246
248
|
// Flight replacement relies on recompute's supersede release for iterator
|
|
@@ -248,7 +250,14 @@ function handleAsync(e, t, n) {
|
|
|
248
250
|
// self-registration — runs during a recompute of `el`, which has already
|
|
249
251
|
// fired _flightTeardown. A future non-recompute registration path must
|
|
250
252
|
// release it here before overwriting _inFlight.
|
|
251
|
-
ext(e).
|
|
253
|
+
ext(e).ce = t;
|
|
254
|
+
// The run that asked this flight read every input without throwing: an
|
|
255
|
+
// input still in flight was masked for it (an active override, A17), so
|
|
256
|
+
// pending state those inputs propagated onto the node earlier does not
|
|
257
|
+
// describe this answer. Drop it — the flight is the node's pending now.
|
|
258
|
+
// The landing retires only the flight's own entry (landStatus, #3373), so
|
|
259
|
+
// an entry that survived here would hold the node past its own answer.
|
|
260
|
+
e.o.ut = undefined;
|
|
252
261
|
// Provenance of the question this flight asks (#3331): the action whose
|
|
253
262
|
// window is registering it, or the flight whose landing is. Its landings
|
|
254
263
|
// propagate under it (asyncWrite) so an override downstream can tell a
|
|
@@ -279,28 +288,34 @@ function handleAsync(e, t, n) {
|
|
|
279
288
|
// waiting on this flight into it at the landing — a reveal that
|
|
280
289
|
// discovered the flight (#3305) would then wait on the owner's action
|
|
281
290
|
// instead of on the flight (#3334). Enter the waiter: the transaction
|
|
282
|
-
// whose blocker this landing clears.
|
|
283
|
-
|
|
284
|
-
|
|
291
|
+
// whose blocker this landing clears. Then every transaction waiting on
|
|
292
|
+
// the flight folds in (enterWaiting): a reveal that discovered it
|
|
293
|
+
// completes at its landing (A15) — a stampless node's fresh batch
|
|
294
|
+
// included (its flight started under a batch that committed beneath it,
|
|
295
|
+
// #3305). The fold used to happen as each stamped reader recomputed,
|
|
296
|
+
// which effects no longer do (#3407).
|
|
297
|
+
if (e.o?.de) t = waitingTransition(e) ?? t;
|
|
298
|
+
if (t && e.S & STATUS_UNINITIALIZED && !currentTransition(t).ot.has(e)) {
|
|
285
299
|
// Drop the stale stamp too: the plain settle write (setSignal) and the
|
|
286
300
|
// stash-path restamp both re-enter the transaction through it.
|
|
287
|
-
e.
|
|
301
|
+
e.Te = null;
|
|
288
302
|
return;
|
|
289
303
|
}
|
|
290
304
|
globalQueue.initTransition(t);
|
|
305
|
+
enterWaiting(e);
|
|
291
306
|
};
|
|
292
307
|
const handleError = n => {
|
|
293
|
-
if (e.o?.
|
|
308
|
+
if (e.o?.ce !== t) return;
|
|
294
309
|
// NotReadyError from rejected promises should be treated as pending, not error
|
|
295
310
|
let r = n instanceof NotReadyError;
|
|
296
|
-
if (r && e.
|
|
311
|
+
if (r && e.Se) {
|
|
297
312
|
// Loading window: the flight died waiting on an unready source. Keep
|
|
298
313
|
// serving commit #0 — same parking as recompute's catch for sync
|
|
299
314
|
// dependency throws. The dead flight is released so the clock-gated
|
|
300
315
|
// error-retry pull (updateIfNecessary) can also re-ask.
|
|
301
|
-
if (e.o !== null) e.o.
|
|
316
|
+
if (e.o !== null) e.o.ce = null;
|
|
302
317
|
parkLoadingWindow(e, n);
|
|
303
|
-
e.
|
|
318
|
+
e.fe = clock;
|
|
304
319
|
return;
|
|
305
320
|
}
|
|
306
321
|
settleTransition();
|
|
@@ -309,18 +324,18 @@ function handleAsync(e, t, n) {
|
|
|
309
324
|
// rejected flight will never settle its self entry, so transfer ownership
|
|
310
325
|
// after notifyStatus has propagated the replacement source.
|
|
311
326
|
if (r) settlePendingSource(e);
|
|
312
|
-
e.
|
|
327
|
+
e.fe = clock;
|
|
313
328
|
// A real error settles derivatively-pending dependents (notifyStatus
|
|
314
329
|
// cleared their pending sources), so stranded lazy ones release here —
|
|
315
330
|
// the error twin of settlePendingSource's release (#2934).
|
|
316
331
|
if (!r) releaseSettledDependents(e);
|
|
317
332
|
};
|
|
318
333
|
const asyncWrite = (r, o) => {
|
|
319
|
-
if (e.o?.
|
|
334
|
+
if (e.o?.ce !== t) return;
|
|
320
335
|
// If the node was dirtied by a newer write (optimistic override or regular),
|
|
321
336
|
// skip this stale async result — the upcoming flush will recompute the node
|
|
322
337
|
// with the new value, creating a fresh Promise that supersedes this one.
|
|
323
|
-
if (e.
|
|
338
|
+
if (e.ft & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY)) return;
|
|
324
339
|
// The landing propagates under the flight's provenance (#3331) — through
|
|
325
340
|
// the flush below, which clears it.
|
|
326
341
|
setOrigin(i);
|
|
@@ -332,12 +347,11 @@ function handleAsync(e, t, n) {
|
|
|
332
347
|
// that reveal or companion synchronization briefly classifies the held
|
|
333
348
|
// old value as pending, a one-frame pulse to direct observers (#3178).
|
|
334
349
|
// A truthy capture implies `_x` exists, so the restore writes it directly.
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (u) u._e.delete(e);
|
|
350
|
+
const u = e.o?.Ee;
|
|
351
|
+
landStatus(e);
|
|
352
|
+
if (u) e.o.Ee = true;
|
|
353
|
+
const l = resolveLane(e);
|
|
354
|
+
if (l) l.Ie.delete(e);
|
|
341
355
|
// Attribution hook: lets the engine snapshot state before the landing
|
|
342
356
|
// branches, so it can tell whether the plain path's setSignal committed a
|
|
343
357
|
// change (and only then classify it as an async landing).
|
|
@@ -349,8 +363,12 @@ function handleAsync(e, t, n) {
|
|
|
349
363
|
handleError(e);
|
|
350
364
|
return;
|
|
351
365
|
}
|
|
352
|
-
if (s)
|
|
353
|
-
} else if (e.o?.
|
|
366
|
+
if (s) landStatus(e, true);
|
|
367
|
+
} else if (e.o?.Ne !== undefined && !(l && e.C & CONFIG_DERIVED_OVERRIDE)) {
|
|
368
|
+
// A derived override's landing UNDER its lane is the lane's own work
|
|
369
|
+
// (the branch below); demoted — its source superseded (A18) — the
|
|
370
|
+
// landing is the truth the correction asked for, and holds and
|
|
371
|
+
// supersedes here like the sync twin (recompute). Otherwise:
|
|
354
372
|
// Optimistic node — resting OR covered by an active override — holds
|
|
355
373
|
// through the shared pending-node path, exactly like a plain async memo,
|
|
356
374
|
// so the commit clears STATUS_UNINITIALIZED (#2806) and elevation to
|
|
@@ -360,8 +378,8 @@ function handleAsync(e, t, n) {
|
|
|
360
378
|
// (A17 — every reader sees the override); the revert reveals whatever
|
|
361
379
|
// has committed by then, so corrections reveal atomically with their
|
|
362
380
|
// transition rather than escaping it.
|
|
363
|
-
if (e.
|
|
364
|
-
e.
|
|
381
|
+
if (e._e === NOT_PENDING) queuePendingNode(e);
|
|
382
|
+
e._e = r;
|
|
365
383
|
// The hold is a companion-visible write like any other (A13/A19): the
|
|
366
384
|
// clearStatus() above computed its verdict before the hold existed, so
|
|
367
385
|
// isPending must re-derive (the value is not final until commit — V1)
|
|
@@ -379,25 +397,28 @@ function handleAsync(e, t, n) {
|
|
|
379
397
|
// action on an until() never re-notified). The hook is installed with
|
|
380
398
|
// the engine, which an active override implies. The propagation runs
|
|
381
399
|
// under this flight's provenance (setOrigin above).
|
|
382
|
-
GlobalQueue.
|
|
400
|
+
GlobalQueue.De?.(e, r);
|
|
383
401
|
if (!hasActiveOverride(e)) {
|
|
384
402
|
if (attrHooks !== null) attrHooks.asyncEnd(e, undefined, r, true);
|
|
385
403
|
insertSubs(e);
|
|
386
404
|
} else GlobalQueue.Oe(e, r);
|
|
387
|
-
e.
|
|
388
|
-
} else if (
|
|
405
|
+
e.fe = clock;
|
|
406
|
+
} else if (l) {
|
|
389
407
|
// Route through lane's effect queue for independent flushing
|
|
390
|
-
const t = e.
|
|
391
|
-
const n = e.
|
|
392
|
-
const o = e.
|
|
408
|
+
const t = e.Pe;
|
|
409
|
+
const n = hasActiveOverride(e) ? unwrapOverride(e.o.Ne) : e.Ae;
|
|
410
|
+
const o = e.Re;
|
|
393
411
|
try {
|
|
394
412
|
if (!t && s || !o || !o(r, n)) {
|
|
395
|
-
|
|
396
|
-
|
|
413
|
+
// Lanes stage (#3479): a memo's landing under its lane is a derived
|
|
414
|
+
// override, as its sync pass's result is (recompute) — `_value`
|
|
415
|
+
// stays the committed truth for readers off the lane.
|
|
416
|
+
if (t) e.Ae = r; else GlobalQueue.ge(e, r, l);
|
|
417
|
+
e.fe = clock;
|
|
397
418
|
// The latest() shadow write gives latest() effects independent lanes; the
|
|
398
419
|
// _pendingSignal update is a no-op repeat of the clearStatus() call above
|
|
399
420
|
// (computePendingState doesn't read _value).
|
|
400
|
-
GlobalQueue.
|
|
421
|
+
GlobalQueue.De?.(e, r);
|
|
401
422
|
insertSubs(e, true);
|
|
402
423
|
}
|
|
403
424
|
} catch (t) {
|
|
@@ -435,9 +456,18 @@ function handleAsync(e, t, n) {
|
|
|
435
456
|
// window when the hold commits, so no one-frame isPending pulse can leak
|
|
436
457
|
// to live observers between the landing and its commit (#2990). The
|
|
437
458
|
// quiet re-ask classification follows the same schedule (#3178).
|
|
438
|
-
if (e.
|
|
439
|
-
e.
|
|
440
|
-
if (
|
|
459
|
+
if (e._e === NOT_PENDING) {
|
|
460
|
+
e.Se = false;
|
|
461
|
+
if (u) e.o.Ee = false;
|
|
462
|
+
// The landing published: the dependency tail the flight's pass left
|
|
463
|
+
// linked goes now (A30, #3410). A transition-held landing has not
|
|
464
|
+
// replaced the committed frame — the committed value still derives
|
|
465
|
+
// from the previous pass's inputs, and a mainline write to one of them
|
|
466
|
+
// must reach this node and join its hold (its stamp) instead of
|
|
467
|
+
// publishing beside the stale derivation (#3461: `b() ? b() : a()`
|
|
468
|
+
// held on `b` dropped `a` at its landing, and `A: 1` then committed
|
|
469
|
+
// beside `Selected: 0`). `commitPendingNode` trims a held landing.
|
|
470
|
+
trimStaleDeps(e);
|
|
441
471
|
}
|
|
442
472
|
settlePendingSource(e);
|
|
443
473
|
schedule();
|
|
@@ -452,7 +482,7 @@ function handleAsync(e, t, n) {
|
|
|
452
482
|
// Returns whether the node released, so the iterator branch can stop
|
|
453
483
|
// pulling values instead of pumping an unobserved stream forever (#2935).
|
|
454
484
|
const settleAutodispose = () => {
|
|
455
|
-
if (e.
|
|
485
|
+
if (e.C & CONFIG_AUTO_DISPOSE && !e.u && !(e.S & STATUS_PENDING)) {
|
|
456
486
|
unobserved(e);
|
|
457
487
|
return true;
|
|
458
488
|
}
|
|
@@ -472,11 +502,11 @@ function handleAsync(e, t, n) {
|
|
|
472
502
|
const consumeIterator = (n, r) => {
|
|
473
503
|
const o = n[Symbol.asyncIterator]();
|
|
474
504
|
let i = false;
|
|
475
|
-
let
|
|
476
|
-
let
|
|
505
|
+
let u = false;
|
|
506
|
+
let l = !r;
|
|
477
507
|
const close = () => {
|
|
478
|
-
if (
|
|
479
|
-
|
|
508
|
+
if (u) return;
|
|
509
|
+
u = true;
|
|
480
510
|
try {
|
|
481
511
|
const e = o.return?.();
|
|
482
512
|
if (isThenable(e)) e.then(undefined, () => {});
|
|
@@ -487,7 +517,7 @@ function handleAsync(e, t, n) {
|
|
|
487
517
|
// owner-death backstop, but its disposal list can be zombie-deferred
|
|
488
518
|
// until the SUPERSEDING flight settles. The teardown slot fires at the
|
|
489
519
|
// _inFlight release sites so supersede stops this stream immediately.
|
|
490
|
-
ext(e).
|
|
520
|
+
ext(e).ae = close;
|
|
491
521
|
// Release check before each next pull: an unobserved lazy node must tear
|
|
492
522
|
// down (its close above runs via disposal, closing the iterator) instead
|
|
493
523
|
// of pumping the stream forever with zero subscribers (#2935).
|
|
@@ -513,17 +543,17 @@ function handleAsync(e, t, n) {
|
|
|
513
543
|
// stash: it must write through the async path or the value is
|
|
514
544
|
// silently dropped. (The deferred posture never has a caller, so
|
|
515
545
|
// initialRead starts false there and everything writes through.)
|
|
516
|
-
if (c &&
|
|
546
|
+
if (c && l) {
|
|
517
547
|
n = r;
|
|
518
548
|
f = true;
|
|
519
|
-
if (r.done)
|
|
520
|
-
} else if (e.o?.
|
|
549
|
+
if (r.done) u = true;
|
|
550
|
+
} else if (e.o?.ce !== t) {
|
|
521
551
|
return;
|
|
522
552
|
} else if (!r.done) {
|
|
523
553
|
i = true;
|
|
524
554
|
asyncWrite(r.value, iterateOrRelease);
|
|
525
555
|
} else {
|
|
526
|
-
|
|
556
|
+
u = true;
|
|
527
557
|
if (i) {
|
|
528
558
|
schedule();
|
|
529
559
|
flush();
|
|
@@ -534,11 +564,11 @@ function handleAsync(e, t, n) {
|
|
|
534
564
|
settleAutodispose();
|
|
535
565
|
}
|
|
536
566
|
}, n => {
|
|
537
|
-
if (c &&
|
|
567
|
+
if (c && l) {
|
|
538
568
|
r = n;
|
|
539
569
|
a = true;
|
|
540
|
-
} else if (e.o?.
|
|
541
|
-
|
|
570
|
+
} else if (e.o?.ce === t) {
|
|
571
|
+
u = true;
|
|
542
572
|
handleError(n);
|
|
543
573
|
settleAutodispose();
|
|
544
574
|
}
|
|
@@ -546,9 +576,9 @@ function handleAsync(e, t, n) {
|
|
|
546
576
|
c = false;
|
|
547
577
|
if (a) {
|
|
548
578
|
// Match the promise branch, but only rethrow during the initial read.
|
|
549
|
-
|
|
579
|
+
u = true;
|
|
550
580
|
handleError(r);
|
|
551
|
-
if (
|
|
581
|
+
if (l) throw r;
|
|
552
582
|
return true;
|
|
553
583
|
}
|
|
554
584
|
if (f && !n.done) {
|
|
@@ -560,13 +590,13 @@ function handleAsync(e, t, n) {
|
|
|
560
590
|
};
|
|
561
591
|
const f = iterate();
|
|
562
592
|
// Later iterate() calls run from asyncWrite, where rethrowing would be unhandled.
|
|
563
|
-
|
|
593
|
+
l = false;
|
|
564
594
|
return i || f;
|
|
565
595
|
};
|
|
566
596
|
// Landed-synchronously verdict for a LIVE iterator drain; null when no live
|
|
567
597
|
// drain ran (plain promise flight, or a deferred flatten). Drives the
|
|
568
598
|
// shared NotReady/loading tail below.
|
|
569
|
-
let
|
|
599
|
+
let u = null;
|
|
570
600
|
// Flatten one async level: a thenable that RESOLVES to an AsyncIterable —
|
|
571
601
|
// the shape every async stub returning a stream produces — consumes as the
|
|
572
602
|
// stream itself rather than settling on the iterable object. One level
|
|
@@ -581,7 +611,7 @@ function handleAsync(e, t, n) {
|
|
|
581
611
|
}
|
|
582
612
|
if (!n) return false;
|
|
583
613
|
const r = consumeIterator(e, t);
|
|
584
|
-
if (!t)
|
|
614
|
+
if (!t) u = r;
|
|
585
615
|
return true;
|
|
586
616
|
};
|
|
587
617
|
if (o) {
|
|
@@ -597,13 +627,13 @@ function handleAsync(e, t, n) {
|
|
|
597
627
|
// becomes disposal-bearing, which is exactly the class a directly
|
|
598
628
|
// returned iterable already occupies.
|
|
599
629
|
const registerDeferredClose = t => {
|
|
600
|
-
if (!e.
|
|
630
|
+
if (!e.Ue) e.Ue = t; else if (Array.isArray(e.Ue)) e.Ue.push(t); else e.Ue = [ e.Ue, t ];
|
|
601
631
|
};
|
|
602
632
|
t.then(r => {
|
|
603
633
|
if (i) {
|
|
604
634
|
s = r;
|
|
605
635
|
n = true;
|
|
606
|
-
} else if (e.o?.
|
|
636
|
+
} else if (e.o?.ce === t && !(e.ft & REACTIVE_DISPOSED) && flattenIfIterable(r, registerDeferredClose)) ; else {
|
|
607
637
|
asyncWrite(r);
|
|
608
638
|
settleAutodispose();
|
|
609
639
|
}
|
|
@@ -628,63 +658,109 @@ function handleAsync(e, t, n) {
|
|
|
628
658
|
// is opened — first-flight work on a loadingValue node is loading-class
|
|
629
659
|
// (invisible to boundaries and transitions); the flight itself is
|
|
630
660
|
// already registered in _inFlight and lands through asyncWrite.
|
|
631
|
-
if (e.
|
|
661
|
+
if (e.Se) return e.Ae;
|
|
632
662
|
globalQueue.initTransition(resolveTransition(e));
|
|
633
663
|
throw new NotReadyError(context);
|
|
634
664
|
} else if (!flattenIfIterable(s)) {
|
|
635
665
|
// Synchronously-resolved promise: the first real answer landed.
|
|
636
|
-
e.
|
|
666
|
+
e.Se = false;
|
|
637
667
|
}
|
|
638
668
|
// A sync-resolved promise holding an AsyncIterable flattened LIVE (we
|
|
639
669
|
// are still inside the synchronous read): full initial-drain semantics
|
|
640
670
|
// apply and the shared tail below settles the verdict.
|
|
641
671
|
}
|
|
642
672
|
if (r) flattenIfIterable(t);
|
|
643
|
-
if (
|
|
644
|
-
if (!
|
|
673
|
+
if (u !== null) {
|
|
674
|
+
if (!u) {
|
|
645
675
|
// Loading window: serve commit #0 (see the promise branch above).
|
|
646
|
-
if (e.
|
|
676
|
+
if (e.Se) return e.Ae;
|
|
647
677
|
globalQueue.initTransition(resolveTransition(e));
|
|
648
678
|
throw new NotReadyError(context);
|
|
649
679
|
}
|
|
650
680
|
// A sync first yield (or immediate empty completion) is the first real
|
|
651
681
|
// answer; async yields clear inside asyncWrite.
|
|
652
|
-
e.
|
|
682
|
+
e.Se = false;
|
|
653
683
|
}
|
|
654
684
|
return s;
|
|
655
685
|
}
|
|
656
686
|
|
|
657
687
|
function clearStatus(e, t = false) {
|
|
658
|
-
if (e.o?.
|
|
659
|
-
if (e.o?.
|
|
688
|
+
if (e.o?.ut) clearPendingSources(e);
|
|
689
|
+
if (e.o?.oe) if (e.o !== null) e.o.oe = false;
|
|
660
690
|
// The pending window is over; its quiet classification dies with it.
|
|
661
691
|
// (Unconditional: _reask is baked into the node literals, so this is a
|
|
662
692
|
// plain store to an existing slot — no shape change.)
|
|
663
|
-
if (e.o !== null) e.o.
|
|
693
|
+
if (e.o !== null) e.o.Ee = false;
|
|
664
694
|
e.S = t ? 0 : e.S & STATUS_UNINITIALIZED;
|
|
665
695
|
if (e.o?._) setPendingError(e);
|
|
666
696
|
// Update pending signal for isPending() reactivity (companions only exist
|
|
667
697
|
// once the verdict layer created them, which installs the hooks).
|
|
668
|
-
if (e.o?.
|
|
669
|
-
if (e.o?.i && e.
|
|
698
|
+
if (e.o?.he || e.o?.Ge) GlobalQueue.le(e);
|
|
699
|
+
if (e.o?.i && e.C & CONFIG_CHILD_COMPANIONS && GlobalQueue.be !== null) GlobalQueue.be(e);
|
|
670
700
|
const n = statusNotifierOf(e);
|
|
671
701
|
if (n) n.call(e);
|
|
672
702
|
}
|
|
673
703
|
|
|
704
|
+
/**
|
|
705
|
+
* Status clear for a flight LANDING (asyncWrite). A landing answers the
|
|
706
|
+
* node's OWN question — it retires the node's self entry, not the pending
|
|
707
|
+
* state its sources propagated onto it. An input re-asked while this flight
|
|
708
|
+
* was up (a second write to the signal feeding `a` while `b`'s first flight
|
|
709
|
+
* is in the air, #3373) marks `b` pending on `a` by propagation, with `b`'s
|
|
710
|
+
* flight still current: nothing superseded it (the re-ask only changed `a`'s
|
|
711
|
+
* status, not yet its value), so the landing arrives, and a full clear made
|
|
712
|
+
* `b` answer with the stale value — the transaction's reporter for `a` found
|
|
713
|
+
* nothing pending below it and committed the newer signal beside the older
|
|
714
|
+
* derived value (`2 / 1`); `isPending(b)` read false for the gap (#3376).
|
|
715
|
+
* With another source still pending the node stays derivatively pending on
|
|
716
|
+
* it; the landed value is written below (the staged answer is still the
|
|
717
|
+
* answer for the inputs it was asked with) and the input's own settle
|
|
718
|
+
* releases it, or its value change recomputes the node into a fresh flight.
|
|
719
|
+
* `_blocked` clears like a full clear: a landing that passed the `_inFlight`
|
|
720
|
+
* guard was not superseded by a re-run (recompute nulls `_inFlight` first),
|
|
721
|
+
* so the flag is the flight's own registration throw — the input settling
|
|
722
|
+
* unchanged must not re-run the node (an extra flight for the same inputs).
|
|
723
|
+
* The node is already STATUS_PENDING in that branch (only notifyStatus fills
|
|
724
|
+
* the set, with status; a loading-window park cannot coexist with a live
|
|
725
|
+
* flight since registration drops the set), so the flags only change when
|
|
726
|
+
* the first landing retires UNINITIALIZED. `_error` must move off self: a
|
|
727
|
+
* reader thrown NotReady(self) would park on a retired entry. Companions
|
|
728
|
+
* keep their verdict (pending before and after; the write re-syncs them).
|
|
729
|
+
*/ function landStatus(e, t = false) {
|
|
730
|
+
const n = e.o?.ut;
|
|
731
|
+
// (The full clear below drops the set whether or not self was retired first.)
|
|
732
|
+
if (n && (n.delete(e), n.size)) {
|
|
733
|
+
e.o.oe = false;
|
|
734
|
+
if (t) e.S = STATUS_PENDING;
|
|
735
|
+
setPendingError(e, n.values().next().value);
|
|
736
|
+
} else clearStatus(e, t);
|
|
737
|
+
}
|
|
738
|
+
|
|
674
739
|
function notifyStatus(e, t, n, r, o) {
|
|
675
740
|
// Wrap regular errors to track source node
|
|
676
741
|
if (t === STATUS_ERROR && !(n instanceof StatusError) && !(n instanceof NotReadyError)) n = new StatusError(e, n);
|
|
677
742
|
const i = t === STATUS_PENDING && n instanceof NotReadyError ? n.source : undefined;
|
|
678
743
|
const s = i === e;
|
|
679
|
-
|
|
680
|
-
|
|
744
|
+
// An optimistic node (a WRITTEN override slot) pending derivatively is a
|
|
745
|
+
// boundary: its override is the answer, pending stops here (A17). A
|
|
746
|
+
// derived override (#3479) is a previous speculative answer on a plain
|
|
747
|
+
// member — pending flows through it as through any memo.
|
|
748
|
+
const u = t === STATUS_PENDING && e.o?.Ne !== undefined && !(e.C & CONFIG_DERIVED_OVERRIDE) && !s;
|
|
749
|
+
const l = u && hasActiveOverride(e);
|
|
681
750
|
if (!r) {
|
|
751
|
+
// Lane before companions: the companion pokes below may create the
|
|
752
|
+
// node's pending-signal lane, whose parent is read from the node's lane
|
|
753
|
+
// at creation. Assigned after them (as it was), a node made pending by
|
|
754
|
+
// propagation before it rode the lane got a parentless companion lane,
|
|
755
|
+
// and the isPending reader that also depends on the node merged it into
|
|
756
|
+
// the held lane — the verdict then waited on the async it reports (#3379).
|
|
757
|
+
if (o) assignOrMergeLane(e, o);
|
|
682
758
|
if (t === STATUS_PENDING && i) {
|
|
683
759
|
addPendingSource(e, i);
|
|
684
760
|
// A fresh flight from a settled state starts with its inputs unpublished
|
|
685
761
|
// (a replacement flight while still pending keeps the mark: the first
|
|
686
762
|
// flight's committed inputs are still the frame).
|
|
687
|
-
if (!(e.S & STATUS_PENDING)) e.
|
|
763
|
+
if (!(e.S & STATUS_PENDING)) e.C &= ~CONFIG_INPUTS_PUBLISHED;
|
|
688
764
|
e.S = STATUS_PENDING | e.S & STATUS_UNINITIALIZED;
|
|
689
765
|
// Preserve the current source on this propagation so render-effect notification
|
|
690
766
|
// can register every distinct pending source with the transition.
|
|
@@ -694,14 +770,11 @@ function notifyStatus(e, t, n, r, o) {
|
|
|
694
770
|
e.S = t | (t !== STATUS_ERROR ? e.S & STATUS_UNINITIALIZED : 0);
|
|
695
771
|
ext(e)._ = n;
|
|
696
772
|
}
|
|
697
|
-
GlobalQueue.
|
|
698
|
-
if (e.o?.i && e.
|
|
773
|
+
GlobalQueue.le?.(e);
|
|
774
|
+
if (e.o?.i && e.C & CONFIG_CHILD_COMPANIONS && GlobalQueue.be !== null) GlobalQueue.be(e);
|
|
699
775
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
}
|
|
703
|
-
const f = r || u;
|
|
704
|
-
const a = r || l ? undefined : o;
|
|
776
|
+
const f = r || l;
|
|
777
|
+
const a = r || u ? undefined : o;
|
|
705
778
|
const c = statusNotifierOf(e);
|
|
706
779
|
if (c) {
|
|
707
780
|
if (r && t === STATUS_PENDING) {
|
|
@@ -715,19 +788,56 @@ function notifyStatus(e, t, n, r, o) {
|
|
|
715
788
|
return;
|
|
716
789
|
}
|
|
717
790
|
forEachDependent(e, (e, r) => {
|
|
718
|
-
e.
|
|
719
|
-
|
|
791
|
+
e.fe = clock;
|
|
792
|
+
// A pending mark on a kept-tail link re-derives the subscriber instead of
|
|
793
|
+
// marking it (A30, #3494 review; fuzzer latest-1 #2141; #3519 review).
|
|
794
|
+
// Past `_depsTail` lie the committed frame's deps, kept by A30 because
|
|
795
|
+
// that frame still derives from them while the pass that dropped them is
|
|
796
|
+
// held (staged, or unchanged and parked). A source going pending there is
|
|
797
|
+
// a question for the node's NEXT pass, not a fact about its current one:
|
|
798
|
+
// marked, the node was registered as the flight's reporter and its holder
|
|
799
|
+
// entangled with the flight (the A15 arm below) on a dep the held frame
|
|
800
|
+
// never reads — an orphaned fetch held the truth (a hide joined to a
|
|
801
|
+
// parked action, A34), and a manual flight nobody awaited held a gated
|
|
802
|
+
// reader hidden forever (fuzzer branches-1 #1105). Skipped, the committed
|
|
803
|
+
// frame published stale beside its new inputs (`query=1` beside a
|
|
804
|
+
// `selected` derived from `remote(0)`). Re-derived, the pass decides: it
|
|
805
|
+
// reads the dep and registers through its own read, or reads a held input
|
|
806
|
+
// and enters that transaction (A29), or reads neither and is done. Clears
|
|
807
|
+
// and errors still ride every link. A link inside the prefix carries the
|
|
808
|
+
// pass's generation (`link()`), so the test is O(1); mid-pass the prefix
|
|
809
|
+
// is what the pass has read so far, and the heap refuses a recomputing
|
|
810
|
+
// node — a dep it has yet to reach registers through its own read.
|
|
811
|
+
if (t === STATUS_PENDING && r.pe !== e.ye) {
|
|
812
|
+
enqueueSub(e);
|
|
813
|
+
schedule();
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
if (t === STATUS_PENDING && i && !e.o?.ut?.has(i) || t !== STATUS_PENDING && (e.o?._ !== n || e.o?.ut)) {
|
|
720
817
|
// A pending-observer link is the subscription an `isPending` read created.
|
|
721
818
|
// It exists so the observer re-runs when the source settles, but it must
|
|
722
819
|
// not carry a real (non-NotReadyError) error — the synchronous `isPending`
|
|
723
820
|
// read swallows those, and the async path must match. Re-run the observer
|
|
724
821
|
// so `isPending` re-evaluates (to not-pending) instead of forwarding.
|
|
725
|
-
if (r.
|
|
822
|
+
if (r.Ce && t !== STATUS_PENDING && !(n instanceof NotReadyError)) {
|
|
726
823
|
enqueueSub(e);
|
|
727
824
|
schedule();
|
|
728
825
|
return;
|
|
729
826
|
}
|
|
730
|
-
|
|
827
|
+
// A memo another live transaction HOLDS — pending on its work, or
|
|
828
|
+
// staged by it — made pending by THIS flight cannot reveal before the
|
|
829
|
+
// flight lands: the two settle as one unit (A15, a shared derivation of
|
|
830
|
+
// both — #3443). Propagation marks the held memo without recomputing it
|
|
831
|
+
// (its inputs' values are unchanged), so this is the one moment the
|
|
832
|
+
// entanglement is known; the memo's stamped re-entry at its next pass
|
|
833
|
+
// came too late — the holder's own flight landed first and revealed the
|
|
834
|
+
// inputs beside the stale sum. The stamp alone decides nothing (#3334):
|
|
835
|
+
// a node the transaction once queued but holds nothing of — a switch's
|
|
836
|
+
// shared output whose first flight the second write superseded — must
|
|
837
|
+
// not drag the older flight into the newer reveal. Effects entangle
|
|
838
|
+
// nothing (A15 shared-hole corollary): their reader registers with the
|
|
839
|
+
// flight's transaction at queue notification.
|
|
840
|
+
if (!f) e.Te ? i && !e.Pe && (e.S & STATUS_PENDING || e._e !== NOT_PENDING) && globalQueue.initTransition(e.Te) : queuePendingNode(e);
|
|
731
841
|
notifyStatus(e, t, n, f, a);
|
|
732
842
|
}
|
|
733
843
|
});
|