@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/async.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { STATUS_UNINITIALIZED, STATUS_ERROR, STATUS_PENDING, REACTIVE_DIRTY, REACTIVE_OPTIMISTIC_DIRTY, NOT_PENDING, CONFIG_AUTO_DISPOSE, REACTIVE_ZOMBIE } from "./constants.js";
|
|
1
|
+
import { STATUS_UNINITIALIZED, CONFIG_CHILD_COMPANIONS, STATUS_ERROR, STATUS_PENDING, REACTIVE_DIRTY, REACTIVE_OPTIMISTIC_DIRTY, NOT_PENDING, CONFIG_AUTO_DISPOSE, REACTIVE_ZOMBIE, REACTIVE_DISPOSED } from "./constants.js";
|
|
2
2
|
|
|
3
|
-
import { untrack,
|
|
3
|
+
import { untrack, ext, setSignal, context } from "./core.js";
|
|
4
4
|
|
|
5
5
|
import "./invariants.js";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { StatusError, NotReadyError } from "./error.js";
|
|
8
8
|
|
|
9
9
|
import { trimStaleDeps, unobserved } from "./graph.js";
|
|
10
10
|
|
|
11
11
|
import { enqueueSub } from "./heap.js";
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { hasActiveOverride, assignOrMergeLane, resolveLane, resolveTransition } from "./lanes.js";
|
|
14
14
|
|
|
15
15
|
import { cleanup } from "./owner.js";
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import { GlobalQueue, schedule, flush, queuePendingNode, insertSubs, clock, currentTransition, globalQueue } 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,20 +22,20 @@ import { globalQueue, GlobalQueue, schedule, flush, queuePendingNode, insertSubs
|
|
|
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, n) {
|
|
25
|
-
if (e.
|
|
26
|
-
(e.
|
|
25
|
+
if (e.o?.le?.has(n)) return false;
|
|
26
|
+
(ext(e).le ??= new Set).add(n);
|
|
27
27
|
return true;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function removePendingSource(e, n) {
|
|
31
|
-
if (!e.
|
|
32
|
-
if (e.
|
|
31
|
+
if (!e.o?.le?.delete(n)) return false;
|
|
32
|
+
if (e.o?.le.size === 0) if (e.o !== null) e.o.le = undefined;
|
|
33
33
|
return true;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function clearPendingSources(e) {
|
|
37
|
-
e.
|
|
38
|
-
e.
|
|
37
|
+
e.o?.le?.clear();
|
|
38
|
+
if (e.o !== null) e.o.le = undefined;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
@@ -45,7 +45,7 @@ function clearPendingSources(e) {
|
|
|
45
45
|
* with NO read-visible pending status, no downstream propagation, no
|
|
46
46
|
* transition, no lane registration. Commit #0 keeps serving.
|
|
47
47
|
*/ function parkLoadingWindow(e, n) {
|
|
48
|
-
e.ue = true;
|
|
48
|
+
ext(e).ue = true;
|
|
49
49
|
if (n.source) addPendingSource(e, n.source);
|
|
50
50
|
// A settled error is the node's answer ("the error stays the answer until
|
|
51
51
|
// this retry can actually run") — the park must not replace it: reads
|
|
@@ -56,24 +56,24 @@ function clearPendingSources(e) {
|
|
|
56
56
|
|
|
57
57
|
function setPendingError(e, n, t) {
|
|
58
58
|
if (!n) {
|
|
59
|
-
e._ = null;
|
|
59
|
+
if (e.o !== null) e.o._ = null;
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
if (t instanceof NotReadyError && t.source === n) {
|
|
63
|
-
e._ = t;
|
|
63
|
+
ext(e)._ = t;
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
|
-
const r = e._;
|
|
66
|
+
const r = e.o?._;
|
|
67
67
|
if (!(r instanceof NotReadyError) || r.source !== n) {
|
|
68
|
-
e._ = new NotReadyError(n);
|
|
68
|
+
ext(e)._ = new NotReadyError(n);
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function forEachDependent(e, n) {
|
|
73
|
-
for (let t = e.
|
|
73
|
+
for (let t = e.u; t !== null; t = t.fe) n(t.ae, t);
|
|
74
74
|
// `?? null`: affects() marks route plain signals (no `_child` slot) through here.
|
|
75
|
-
for (let t = e.
|
|
76
|
-
for (let e = t.
|
|
75
|
+
for (let t = e.o?.l ?? null; t !== null; t = t.ce) {
|
|
76
|
+
for (let e = t.u; e !== null; e = e.fe) n(e.ae, e);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -88,7 +88,7 @@ function forEachDependent(e, n) {
|
|
|
88
88
|
// callbacks handle their own release (settleAutodispose in handleAsync); this
|
|
89
89
|
// covers derivatively-pending dependents, which have no callbacks of their own.
|
|
90
90
|
function releaseIfSettledUnobserved(e) {
|
|
91
|
-
e.
|
|
91
|
+
e.Se && e.T & CONFIG_AUTO_DISPOSE && !e.u && !(e.ie & REACTIVE_ZOMBIE) && !(e.S & STATUS_PENDING) && unobserved(e);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
// Error-path sweep: notifyStatus(STATUS_ERROR) clears dependents' pending
|
|
@@ -101,7 +101,7 @@ function releaseSettledDependents(e) {
|
|
|
101
101
|
const visit = e => {
|
|
102
102
|
if (t.has(e)) return;
|
|
103
103
|
t.add(e);
|
|
104
|
-
if (!e.
|
|
104
|
+
if (!e.u && e.T & CONFIG_AUTO_DISPOSE) (n ??= []).push(e);
|
|
105
105
|
forEachDependent(e, visit);
|
|
106
106
|
};
|
|
107
107
|
forEachDependent(e, visit);
|
|
@@ -129,7 +129,7 @@ function settleErroredDependents(e, n) {
|
|
|
129
129
|
const visit = e => {
|
|
130
130
|
if (r.has(e)) return;
|
|
131
131
|
r.add(e);
|
|
132
|
-
if (e._ === n) {
|
|
132
|
+
if (e.o?._ === n) {
|
|
133
133
|
enqueueSub(e);
|
|
134
134
|
t = true;
|
|
135
135
|
}
|
|
@@ -144,36 +144,36 @@ function settlePendingSource(e) {
|
|
|
144
144
|
let t;
|
|
145
145
|
const r = new Set;
|
|
146
146
|
// Companion updates no-op without the verdict layer (null hook).
|
|
147
|
-
const o = GlobalQueue.
|
|
148
|
-
const settle =
|
|
149
|
-
if (r.has(
|
|
150
|
-
r.add(
|
|
151
|
-
|
|
152
|
-
const
|
|
147
|
+
const o = GlobalQueue.de;
|
|
148
|
+
const settle = l => {
|
|
149
|
+
if (r.has(l) || !removePendingSource(l, e)) return;
|
|
150
|
+
r.add(l);
|
|
151
|
+
l.Te = clock;
|
|
152
|
+
const u = l.o?.le?.values().next().value;
|
|
153
153
|
// STATUS_ERROR + pending sources only coexist via an errored loading
|
|
154
154
|
// window's park (notifyStatus(STATUS_ERROR) clears pending sources
|
|
155
155
|
// otherwise): the settled error stays the answer through the settle —
|
|
156
156
|
// nulling it here would have reads throw `null` until the re-enqueued
|
|
157
157
|
// retry lands, or lose it entirely if that retry parks again (#2989).
|
|
158
|
-
const
|
|
159
|
-
if (
|
|
160
|
-
if (!
|
|
161
|
-
o !== null && o(
|
|
158
|
+
const i = l.S & STATUS_ERROR;
|
|
159
|
+
if (u) {
|
|
160
|
+
if (!i) setPendingError(l, u);
|
|
161
|
+
o !== null && o(l);
|
|
162
162
|
} else {
|
|
163
|
-
|
|
164
|
-
if (!
|
|
165
|
-
o !== null && o(
|
|
166
|
-
if (
|
|
167
|
-
enqueueSub(
|
|
163
|
+
l.S &= ~STATUS_PENDING;
|
|
164
|
+
if (!i) setPendingError(l);
|
|
165
|
+
o !== null && o(l);
|
|
166
|
+
if (l.o?.ue) {
|
|
167
|
+
enqueueSub(l);
|
|
168
168
|
n = true;
|
|
169
169
|
}
|
|
170
|
-
|
|
170
|
+
if (l.o !== null) l.o.ue = false;
|
|
171
171
|
// Fully settled with nobody watching: release candidate (#2934). Checked
|
|
172
172
|
// again at release time — deferred so unobserved() can't unlink subs
|
|
173
173
|
// lists this walk is still iterating.
|
|
174
|
-
if (!u
|
|
174
|
+
if (!l.u && l.T & CONFIG_AUTO_DISPOSE) (t ??= []).push(l);
|
|
175
175
|
}
|
|
176
|
-
forEachDependent(
|
|
176
|
+
forEachDependent(l, settle);
|
|
177
177
|
};
|
|
178
178
|
forEachDependent(e, settle);
|
|
179
179
|
// Release before the flush schedule below: unobserved() pulls the node back
|
|
@@ -197,13 +197,13 @@ function handleAsync(e, n, t) {
|
|
|
197
197
|
});
|
|
198
198
|
}
|
|
199
199
|
if (!o && !r) {
|
|
200
|
-
e.
|
|
200
|
+
if (e.o !== null) e.o.Ee = null;
|
|
201
201
|
// A sync landing is the first real answer for a loadingValue node.
|
|
202
|
-
e.
|
|
202
|
+
e.Ie = false;
|
|
203
203
|
return n;
|
|
204
204
|
}
|
|
205
|
-
e.
|
|
206
|
-
let
|
|
205
|
+
ext(e).Ee = n;
|
|
206
|
+
let l;
|
|
207
207
|
// Settle-time transition re-entry. The loading rail is invisible to
|
|
208
208
|
// transactions (#2933): a boundary-caught first load never registers as an
|
|
209
209
|
// async reporter, so its settle — the boundary's fallback -> content
|
|
@@ -216,52 +216,52 @@ function handleAsync(e, n, t) {
|
|
|
216
216
|
// are the transaction's reveal machinery and always re-enter.
|
|
217
217
|
const settleTransition = () => {
|
|
218
218
|
const n = resolveTransition(e);
|
|
219
|
-
if (n && e.S & STATUS_UNINITIALIZED && !currentTransition(n).
|
|
219
|
+
if (n && e.S & STATUS_UNINITIALIZED && !currentTransition(n).Ne.has(e)) {
|
|
220
220
|
// Drop the stale stamp too: the plain settle write (setSignal) and the
|
|
221
221
|
// stash-path restamp both re-enter the transaction through it.
|
|
222
|
-
e.
|
|
222
|
+
e._e = null;
|
|
223
223
|
return;
|
|
224
224
|
}
|
|
225
225
|
globalQueue.initTransition(n);
|
|
226
226
|
};
|
|
227
227
|
const handleError = t => {
|
|
228
|
-
if (e.
|
|
228
|
+
if (e.o?.Ee !== n) return;
|
|
229
229
|
// NotReadyError from rejected promises should be treated as pending, not error
|
|
230
230
|
let r = t instanceof NotReadyError;
|
|
231
|
-
if (r && e.
|
|
231
|
+
if (r && e.Ie) {
|
|
232
232
|
// Loading window: the flight died waiting on an unready source. Keep
|
|
233
233
|
// serving commit #0 — same parking as recompute's catch for sync
|
|
234
234
|
// dependency throws. The dead flight is released so the clock-gated
|
|
235
235
|
// error-retry pull (updateIfNecessary) can also re-ask.
|
|
236
|
-
e.
|
|
236
|
+
if (e.o !== null) e.o.Ee = null;
|
|
237
237
|
parkLoadingWindow(e, t);
|
|
238
|
-
e.
|
|
238
|
+
e.Te = clock;
|
|
239
239
|
return;
|
|
240
240
|
}
|
|
241
241
|
settleTransition();
|
|
242
242
|
notifyStatus(e, r ? STATUS_PENDING : STATUS_ERROR, t);
|
|
243
|
-
e.
|
|
243
|
+
e.Te = clock;
|
|
244
244
|
// A real error settles derivatively-pending dependents (notifyStatus
|
|
245
245
|
// cleared their pending sources), so stranded lazy ones release here —
|
|
246
246
|
// the error twin of settlePendingSource's release (#2934).
|
|
247
247
|
if (!r) releaseSettledDependents(e);
|
|
248
248
|
};
|
|
249
249
|
const asyncWrite = (r, o) => {
|
|
250
|
-
if (e.
|
|
250
|
+
if (e.o?.Ee !== n) return;
|
|
251
251
|
// If the node was dirtied by a newer write (optimistic override or regular),
|
|
252
252
|
// skip this stale async result — the upcoming flush will recompute the node
|
|
253
253
|
// with the new value, creating a fresh Promise that supersedes this one.
|
|
254
|
-
if (e.
|
|
254
|
+
if (e.ie & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY)) return;
|
|
255
255
|
settleTransition();
|
|
256
|
-
const
|
|
256
|
+
const l = !!(e.S & STATUS_UNINITIALIZED);
|
|
257
257
|
trimStaleDeps(e);
|
|
258
258
|
clearStatus(e);
|
|
259
|
-
const
|
|
260
|
-
if (
|
|
259
|
+
const u = resolveLane(e);
|
|
260
|
+
if (u) u.Ae.delete(e);
|
|
261
261
|
if (t) {
|
|
262
262
|
t(r);
|
|
263
|
-
if (
|
|
264
|
-
} else if (e.
|
|
263
|
+
if (l) clearStatus(e, true);
|
|
264
|
+
} else if (e.o?.De !== undefined) {
|
|
265
265
|
// Optimistic node — resting OR covered by an active override — holds
|
|
266
266
|
// through the shared pending-node path, exactly like a plain async memo,
|
|
267
267
|
// so the commit clears STATUS_UNINITIALIZED (#2806) and elevation to
|
|
@@ -271,8 +271,8 @@ function handleAsync(e, n, t) {
|
|
|
271
271
|
// (A17 — every reader sees the override); the revert reveals whatever
|
|
272
272
|
// has committed by then, so corrections reveal atomically with their
|
|
273
273
|
// transition rather than escaping it.
|
|
274
|
-
if (e.
|
|
275
|
-
e.
|
|
274
|
+
if (e.Pe === NOT_PENDING) queuePendingNode(e);
|
|
275
|
+
e.Pe = r;
|
|
276
276
|
// The hold is a companion-visible write like any other (A13/A19): the
|
|
277
277
|
// clearStatus() above computed its verdict before the hold existed, so
|
|
278
278
|
// isPending must re-derive (the value is not final until commit — V1)
|
|
@@ -280,22 +280,24 @@ function handleAsync(e, n, t) {
|
|
|
280
280
|
// only notified when the hold is visible to them: under an active
|
|
281
281
|
// override every reader sees the override (A17), so waking subs would
|
|
282
282
|
// re-show an unchanged view — the revert is the notification point.
|
|
283
|
-
GlobalQueue.
|
|
284
|
-
if (!hasActiveOverride(e))
|
|
285
|
-
|
|
286
|
-
|
|
283
|
+
GlobalQueue.Oe !== null && GlobalQueue.Oe(e, r);
|
|
284
|
+
if (!hasActiveOverride(e)) {
|
|
285
|
+
insertSubs(e);
|
|
286
|
+
}
|
|
287
|
+
e.Te = clock;
|
|
288
|
+
} else if (u) {
|
|
287
289
|
// Route through lane's effect queue for independent flushing
|
|
288
290
|
const n = e.Re;
|
|
289
|
-
const t = e.
|
|
290
|
-
const o = e.
|
|
291
|
+
const t = e.be;
|
|
292
|
+
const o = e.Ue;
|
|
291
293
|
try {
|
|
292
|
-
if (!n &&
|
|
293
|
-
e.
|
|
294
|
-
e.
|
|
294
|
+
if (!n && l || !o || !o(r, t)) {
|
|
295
|
+
e.be = r;
|
|
296
|
+
e.Te = clock;
|
|
295
297
|
// The latest() shadow write gives latest() effects independent lanes; the
|
|
296
298
|
// _pendingSignal update is a no-op repeat of the clearStatus() call above
|
|
297
299
|
// (computePendingState doesn't read _value).
|
|
298
|
-
GlobalQueue.
|
|
300
|
+
GlobalQueue.Oe !== null && GlobalQueue.Oe(e, r);
|
|
299
301
|
insertSubs(e, true);
|
|
300
302
|
}
|
|
301
303
|
} catch (n) {
|
|
@@ -320,7 +322,7 @@ function handleAsync(e, n, t) {
|
|
|
320
322
|
// held-value branch is window-gated, and commitPendingNode closes the
|
|
321
323
|
// window when the hold commits, so no one-frame isPending pulse can leak
|
|
322
324
|
// to live observers between the landing and its commit (#2990).
|
|
323
|
-
if (e.
|
|
325
|
+
if (e.Pe === NOT_PENDING) e.Ie = false;
|
|
324
326
|
settlePendingSource(e);
|
|
325
327
|
schedule();
|
|
326
328
|
flush();
|
|
@@ -334,100 +336,74 @@ function handleAsync(e, n, t) {
|
|
|
334
336
|
// Returns whether the node released, so the iterator branch can stop
|
|
335
337
|
// pulling values instead of pumping an unobserved stream forever (#2935).
|
|
336
338
|
const settleAutodispose = () => {
|
|
337
|
-
if (e.T & CONFIG_AUTO_DISPOSE && !e.
|
|
339
|
+
if (e.T & CONFIG_AUTO_DISPOSE && !e.u && !(e.S & STATUS_PENDING)) {
|
|
338
340
|
unobserved(e);
|
|
339
341
|
return true;
|
|
340
342
|
}
|
|
341
343
|
return false;
|
|
342
344
|
};
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
i = false;
|
|
363
|
-
if (r) {
|
|
364
|
-
// Settle through the same status path an async rejection uses, then
|
|
365
|
-
// unwind the in-progress synchronous read so the errored node isn't
|
|
366
|
-
// momentarily read as `undefined`.
|
|
367
|
-
handleError(o);
|
|
368
|
-
throw o;
|
|
369
|
-
} else if (!t) {
|
|
370
|
-
// Loading window: serve commit #0 instead of suspending. No transition
|
|
371
|
-
// is opened — first-flight work on a loadingValue node is loading-class
|
|
372
|
-
// (invisible to boundaries and transitions); the flight itself is
|
|
373
|
-
// already registered in _inFlight and lands through asyncWrite.
|
|
374
|
-
if (e.Ee) return e.Ue;
|
|
375
|
-
globalQueue.initTransition(resolveTransition(e));
|
|
376
|
-
throw new NotReadyError(context);
|
|
377
|
-
} else {
|
|
378
|
-
// Synchronously-resolved promise: the first real answer landed.
|
|
379
|
-
e.Ee = false;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
if (r) {
|
|
383
|
-
const t = n[Symbol.asyncIterator]();
|
|
384
|
-
let r = false;
|
|
385
|
-
let o = false;
|
|
386
|
-
let i = true;
|
|
387
|
-
cleanup(() => {
|
|
388
|
-
if (o) return;
|
|
389
|
-
o = true;
|
|
345
|
+
// Consumes an AsyncIterable as this flight's value stream. Two postures:
|
|
346
|
+
// LIVE (called synchronously from this read — the compute returned an
|
|
347
|
+
// iterable directly, or a sync-settled thenable held one), where the
|
|
348
|
+
// initial drain may stash a sync first yield for the caller to return and
|
|
349
|
+
// close registration uses the ambient owner; and DEFERRED (the flattening
|
|
350
|
+
// path — a thenable resolved to an iterable in a later microtask), where
|
|
351
|
+
// there is no caller to serve and no ambient owner: sync-settled steps
|
|
352
|
+
// write through asyncWrite, and close registration goes through the slot
|
|
353
|
+
// the thenable branch pre-registered while it still owned the context.
|
|
354
|
+
// Returns whether a sync answer landed (first yield or empty completion) —
|
|
355
|
+
// meaningful only in the live posture.
|
|
356
|
+
const consumeIterator = (t, r) => {
|
|
357
|
+
const o = t[Symbol.asyncIterator]();
|
|
358
|
+
let u = false;
|
|
359
|
+
let i = false;
|
|
360
|
+
let s = !r;
|
|
361
|
+
const close = () => {
|
|
362
|
+
if (i) return;
|
|
363
|
+
i = true;
|
|
390
364
|
try {
|
|
391
|
-
const e =
|
|
365
|
+
const e = o.return?.();
|
|
392
366
|
if (isThenable(e)) e.then(undefined, () => {});
|
|
393
367
|
} catch {}
|
|
394
|
-
}
|
|
368
|
+
};
|
|
369
|
+
r ? r(close) : cleanup(close);
|
|
395
370
|
// Release check before each next pull: an unobserved lazy node must tear
|
|
396
|
-
// down (its
|
|
397
|
-
// stream forever with zero subscribers (#2935).
|
|
371
|
+
// down (its close above runs via disposal, closing the iterator) instead
|
|
372
|
+
// of pumping the stream forever with zero subscribers (#2935).
|
|
398
373
|
const iterateOrRelease = () => {
|
|
399
374
|
if (!settleAutodispose()) iterate();
|
|
400
375
|
};
|
|
401
376
|
const iterate = () => {
|
|
402
|
-
let
|
|
377
|
+
let t, r, f = false, a = false, c = true;
|
|
403
378
|
// Protocol tolerance, matching `for await`: `await` unwraps whatever
|
|
404
379
|
// next() returns — a thenable OR a bare IteratorResult. Real producers
|
|
405
380
|
// use the bare form as a promise-free fast path when a value is already
|
|
406
381
|
// buffered (seroval's deserialized streams do), so a bare result is
|
|
407
382
|
// assimilated as an already-settled step instead of crashing on `.then`.
|
|
408
|
-
const S =
|
|
383
|
+
const S = o.next();
|
|
409
384
|
const d = isThenable(S) ? S : {
|
|
410
385
|
then: e => void e(S)
|
|
411
386
|
};
|
|
412
|
-
d.then(
|
|
387
|
+
d.then(r => {
|
|
413
388
|
// The sync stash only serves the INITIAL drain (handleAsync's caller
|
|
414
389
|
// consumes syncValue / throws NotReady from it). A sync-settled step
|
|
415
390
|
// after an async gap — seroval buffering values between pulls, a
|
|
416
391
|
// sync-thenable producer mid-stream — has no caller reading the
|
|
417
392
|
// stash: it must write through the async path or the value is
|
|
418
|
-
// silently dropped.
|
|
419
|
-
|
|
420
|
-
|
|
393
|
+
// silently dropped. (The deferred posture never has a caller, so
|
|
394
|
+
// initialRead starts false there and everything writes through.)
|
|
395
|
+
if (c && s) {
|
|
396
|
+
t = r;
|
|
421
397
|
f = true;
|
|
422
|
-
if (
|
|
423
|
-
} else if (e.
|
|
398
|
+
if (r.done) i = true;
|
|
399
|
+
} else if (e.o?.Ee !== n) {
|
|
424
400
|
return;
|
|
425
|
-
} else if (!
|
|
426
|
-
|
|
427
|
-
asyncWrite(
|
|
401
|
+
} else if (!r.done) {
|
|
402
|
+
u = true;
|
|
403
|
+
asyncWrite(r.value, iterateOrRelease);
|
|
428
404
|
} else {
|
|
429
|
-
|
|
430
|
-
if (
|
|
405
|
+
i = true;
|
|
406
|
+
if (u) {
|
|
431
407
|
schedule();
|
|
432
408
|
flush();
|
|
433
409
|
} else {
|
|
@@ -437,11 +413,11 @@ function handleAsync(e, n, t) {
|
|
|
437
413
|
settleAutodispose();
|
|
438
414
|
}
|
|
439
415
|
}, t => {
|
|
440
|
-
if (c) {
|
|
441
|
-
|
|
416
|
+
if (c && s) {
|
|
417
|
+
r = t;
|
|
442
418
|
a = true;
|
|
443
|
-
} else if (e.
|
|
444
|
-
|
|
419
|
+
} else if (e.o?.Ee === n) {
|
|
420
|
+
i = true;
|
|
445
421
|
handleError(t);
|
|
446
422
|
settleAutodispose();
|
|
447
423
|
}
|
|
@@ -449,102 +425,182 @@ function handleAsync(e, n, t) {
|
|
|
449
425
|
c = false;
|
|
450
426
|
if (a) {
|
|
451
427
|
// Match the promise branch, but only rethrow during the initial read.
|
|
452
|
-
|
|
453
|
-
handleError(
|
|
454
|
-
if (
|
|
428
|
+
i = true;
|
|
429
|
+
handleError(r);
|
|
430
|
+
if (s) throw r;
|
|
455
431
|
return true;
|
|
456
432
|
}
|
|
457
|
-
if (f && !
|
|
458
|
-
|
|
459
|
-
|
|
433
|
+
if (f && !t.done) {
|
|
434
|
+
l = t.value;
|
|
435
|
+
u = true;
|
|
460
436
|
return iterate();
|
|
461
437
|
}
|
|
462
|
-
return f &&
|
|
438
|
+
return f && t.done;
|
|
463
439
|
};
|
|
464
|
-
const
|
|
440
|
+
const f = iterate();
|
|
465
441
|
// Later iterate() calls run from asyncWrite, where rethrowing would be unhandled.
|
|
466
|
-
|
|
467
|
-
|
|
442
|
+
s = false;
|
|
443
|
+
return u || f;
|
|
444
|
+
};
|
|
445
|
+
// Landed-synchronously verdict for a LIVE iterator drain; null when no live
|
|
446
|
+
// drain ran (plain promise flight, or a deferred flatten). Drives the
|
|
447
|
+
// shared NotReady/loading tail below.
|
|
448
|
+
let u = null;
|
|
449
|
+
// Flatten one async level: a thenable that RESOLVES to an AsyncIterable —
|
|
450
|
+
// the shape every async stub returning a stream produces — consumes as the
|
|
451
|
+
// stream itself rather than settling on the iterable object. One level
|
|
452
|
+
// only: A+ `then` already collapses nested thenables, so the resolved
|
|
453
|
+
// value is never itself a thenable.
|
|
454
|
+
const flattenIfIterable = (e, n) => {
|
|
455
|
+
let t = false;
|
|
456
|
+
if (typeof e === "object" && e !== null) {
|
|
457
|
+
untrack(() => {
|
|
458
|
+
t = e[Symbol.asyncIterator];
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
if (!t) return false;
|
|
462
|
+
const r = consumeIterator(e, n);
|
|
463
|
+
if (!n) u = r;
|
|
464
|
+
return true;
|
|
465
|
+
};
|
|
466
|
+
if (o) {
|
|
467
|
+
let t = false, r = false, o, u = true;
|
|
468
|
+
// Close registration for the flattening path. Consumption starts in a
|
|
469
|
+
// microtask where the ambient owner is gone (or worse, someone else's),
|
|
470
|
+
// so `cleanup()` can't be used — the close targets el's disposal list
|
|
471
|
+
// directly, exactly where a live cleanup() during this recompute would
|
|
472
|
+
// have put it. Deliberately NOT pre-registered at flight start: a
|
|
473
|
+
// non-null `_disposal` reclassifies the node into recompute's deferred
|
|
474
|
+
// (zombie) disposal path, and plain promise flights — the overwhelming
|
|
475
|
+
// majority — must not pay that. Only a flight that actually flattens
|
|
476
|
+
// becomes disposal-bearing, which is exactly the class a directly
|
|
477
|
+
// returned iterable already occupies.
|
|
478
|
+
const registerDeferredClose = n => {
|
|
479
|
+
if (!e.he) e.he = n; else if (Array.isArray(e.he)) e.he.push(n); else e.he = [ e.he, n ];
|
|
480
|
+
};
|
|
481
|
+
n.then(r => {
|
|
482
|
+
if (u) {
|
|
483
|
+
l = r;
|
|
484
|
+
t = true;
|
|
485
|
+
} else if (e.o?.Ee === n && !(e.ie & REACTIVE_DISPOSED) && flattenIfIterable(r, registerDeferredClose)) ; else {
|
|
486
|
+
asyncWrite(r);
|
|
487
|
+
settleAutodispose();
|
|
488
|
+
}
|
|
489
|
+
}, e => {
|
|
490
|
+
if (u) {
|
|
491
|
+
o = e;
|
|
492
|
+
r = true;
|
|
493
|
+
} else {
|
|
494
|
+
handleError(e);
|
|
495
|
+
settleAutodispose();
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
u = false;
|
|
499
|
+
if (r) {
|
|
500
|
+
// Settle through the same status path an async rejection uses, then
|
|
501
|
+
// unwind the in-progress synchronous read so the errored node isn't
|
|
502
|
+
// momentarily read as `undefined`.
|
|
503
|
+
handleError(o);
|
|
504
|
+
throw o;
|
|
505
|
+
} else if (!t) {
|
|
506
|
+
// Loading window: serve commit #0 instead of suspending. No transition
|
|
507
|
+
// is opened — first-flight work on a loadingValue node is loading-class
|
|
508
|
+
// (invisible to boundaries and transitions); the flight itself is
|
|
509
|
+
// already registered in _inFlight and lands through asyncWrite.
|
|
510
|
+
if (e.Ie) return e.be;
|
|
511
|
+
globalQueue.initTransition(resolveTransition(e));
|
|
512
|
+
throw new NotReadyError(context);
|
|
513
|
+
} else if (!flattenIfIterable(l)) {
|
|
514
|
+
// Synchronously-resolved promise: the first real answer landed.
|
|
515
|
+
e.Ie = false;
|
|
516
|
+
}
|
|
517
|
+
// A sync-resolved promise holding an AsyncIterable flattened LIVE (we
|
|
518
|
+
// are still inside the synchronous read): full initial-drain semantics
|
|
519
|
+
// apply and the shared tail below settles the verdict.
|
|
520
|
+
}
|
|
521
|
+
if (r) flattenIfIterable(n);
|
|
522
|
+
if (u !== null) {
|
|
523
|
+
if (!u) {
|
|
468
524
|
// Loading window: serve commit #0 (see the promise branch above).
|
|
469
|
-
if (e.
|
|
525
|
+
if (e.Ie) return e.be;
|
|
470
526
|
globalQueue.initTransition(resolveTransition(e));
|
|
471
527
|
throw new NotReadyError(context);
|
|
472
528
|
}
|
|
473
529
|
// A sync first yield (or immediate empty completion) is the first real
|
|
474
530
|
// answer; async yields clear inside asyncWrite.
|
|
475
|
-
e.
|
|
531
|
+
e.Ie = false;
|
|
476
532
|
}
|
|
477
|
-
return
|
|
533
|
+
return l;
|
|
478
534
|
}
|
|
479
535
|
|
|
480
536
|
function clearStatus(e, n = false) {
|
|
481
|
-
if (e.
|
|
482
|
-
if (e.ue) e.ue = false;
|
|
537
|
+
if (e.o?.le) clearPendingSources(e);
|
|
538
|
+
if (e.o?.ue) if (e.o !== null) e.o.ue = false;
|
|
483
539
|
// The pending window is over; its quiet classification dies with it.
|
|
484
540
|
// (Unconditional: _reask is baked into the node literals, so this is a
|
|
485
541
|
// plain store to an existing slot — no shape change.)
|
|
486
|
-
e.
|
|
542
|
+
if (e.o !== null) e.o.pe = false;
|
|
487
543
|
e.S = n ? 0 : e.S & STATUS_UNINITIALIZED;
|
|
488
|
-
if (e._) setPendingError(e);
|
|
544
|
+
if (e.o?._) setPendingError(e);
|
|
489
545
|
// Update pending signal for isPending() reactivity (companions only exist
|
|
490
546
|
// once the verdict layer created them, which installs the hooks).
|
|
491
|
-
if (e.
|
|
492
|
-
if (e.
|
|
493
|
-
if (e.
|
|
547
|
+
if (e.o?.Ge || e.o?.ge) GlobalQueue.de(e);
|
|
548
|
+
if (e.o?.l && e.T & CONFIG_CHILD_COMPANIONS && GlobalQueue.ye !== null) GlobalQueue.ye(e);
|
|
549
|
+
if (e.o?.A) e.o.A.call(e);
|
|
494
550
|
}
|
|
495
551
|
|
|
496
552
|
function notifyStatus(e, n, t, r, o) {
|
|
497
553
|
// Wrap regular errors to track source node
|
|
498
554
|
if (n === STATUS_ERROR && !(t instanceof StatusError) && !(t instanceof NotReadyError)) t = new StatusError(e, t);
|
|
499
|
-
const
|
|
500
|
-
const
|
|
501
|
-
const
|
|
502
|
-
const s =
|
|
555
|
+
const l = n === STATUS_PENDING && t instanceof NotReadyError ? t.source : undefined;
|
|
556
|
+
const u = l === e;
|
|
557
|
+
const i = n === STATUS_PENDING && e.o?.De !== undefined && !u;
|
|
558
|
+
const s = i && hasActiveOverride(e);
|
|
503
559
|
if (!r) {
|
|
504
|
-
if (n === STATUS_PENDING &&
|
|
505
|
-
addPendingSource(e,
|
|
560
|
+
if (n === STATUS_PENDING && l) {
|
|
561
|
+
addPendingSource(e, l);
|
|
506
562
|
e.S = STATUS_PENDING | e.S & STATUS_UNINITIALIZED;
|
|
507
563
|
// Preserve the current source on this propagation so render-effect notification
|
|
508
564
|
// can register every distinct pending source with the transition.
|
|
509
|
-
setPendingError(e,
|
|
565
|
+
setPendingError(e, l, t);
|
|
510
566
|
} else {
|
|
511
567
|
clearPendingSources(e);
|
|
512
568
|
e.S = n | (n !== STATUS_ERROR ? e.S & STATUS_UNINITIALIZED : 0);
|
|
513
|
-
e._ = t;
|
|
569
|
+
ext(e)._ = t;
|
|
514
570
|
}
|
|
515
|
-
GlobalQueue.
|
|
516
|
-
if (e.
|
|
571
|
+
GlobalQueue.de !== null && GlobalQueue.de(e);
|
|
572
|
+
if (e.o?.l && e.T & CONFIG_CHILD_COMPANIONS && GlobalQueue.ye !== null) GlobalQueue.ye(e);
|
|
517
573
|
}
|
|
518
574
|
if (o && !r) {
|
|
519
575
|
assignOrMergeLane(e, o);
|
|
520
576
|
}
|
|
521
577
|
const f = r || s;
|
|
522
|
-
const a = r ||
|
|
523
|
-
if (e.
|
|
578
|
+
const a = r || i ? undefined : o;
|
|
579
|
+
if (e.o?.A) {
|
|
524
580
|
if (r && n === STATUS_PENDING) {
|
|
525
581
|
return;
|
|
526
582
|
}
|
|
527
583
|
if (f) {
|
|
528
|
-
e.
|
|
584
|
+
e.o.A.call(e, n, t);
|
|
529
585
|
} else {
|
|
530
|
-
e.
|
|
586
|
+
e.o.A.call(e);
|
|
531
587
|
}
|
|
532
588
|
return;
|
|
533
589
|
}
|
|
534
590
|
forEachDependent(e, (e, r) => {
|
|
535
|
-
e.
|
|
536
|
-
if (n === STATUS_PENDING &&
|
|
591
|
+
e.Te = clock;
|
|
592
|
+
if (n === STATUS_PENDING && l && !e.o?.le?.has(l) || n !== STATUS_PENDING && (e.o?._ !== t || e.o?.le)) {
|
|
537
593
|
// A pending-observer link is the subscription an `isPending` read created.
|
|
538
594
|
// It exists so the observer re-runs when the source settles, but it must
|
|
539
595
|
// not carry a real (non-NotReadyError) error — the synchronous `isPending`
|
|
540
596
|
// read swallows those, and the async path must match. Re-run the observer
|
|
541
597
|
// so `isPending` re-evaluates (to not-pending) instead of forwarding.
|
|
542
|
-
if (r.
|
|
598
|
+
if (r.me && n !== STATUS_PENDING && !(t instanceof NotReadyError)) {
|
|
543
599
|
enqueueSub(e);
|
|
544
600
|
schedule();
|
|
545
601
|
return;
|
|
546
602
|
}
|
|
547
|
-
if (!f && !e.
|
|
603
|
+
if (!f && !e._e) queuePendingNode(e);
|
|
548
604
|
notifyStatus(e, n, t, f, a);
|
|
549
605
|
}
|
|
550
606
|
});
|