@solidjs/signals 2.0.0-beta.9 → 2.0.0-rc.1

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.
Files changed (106) hide show
  1. package/README.md +14 -10
  2. package/dist/dev.js +6740 -2011
  3. package/dist/node.cjs +7927 -3340
  4. package/dist/prod/affects.js +126 -0
  5. package/dist/prod/boundaries.js +572 -0
  6. package/dist/prod/core/action.js +139 -0
  7. package/dist/prod/core/async.js +607 -0
  8. package/dist/prod/core/constants.js +92 -0
  9. package/dist/prod/core/context.js +67 -0
  10. package/dist/prod/core/core.js +828 -0
  11. package/dist/prod/core/dev.js +3 -0
  12. package/dist/prod/core/effect.js +145 -0
  13. package/dist/prod/core/error.js +68 -0
  14. package/dist/prod/core/external.js +98 -0
  15. package/dist/prod/core/graph.js +104 -0
  16. package/dist/prod/core/heap.js +140 -0
  17. package/dist/prod/core/invariants.js +42 -0
  18. package/dist/prod/core/lanes.js +140 -0
  19. package/dist/prod/core/optimistic.js +262 -0
  20. package/dist/prod/core/owner.js +302 -0
  21. package/dist/prod/core/scheduler.js +757 -0
  22. package/dist/prod/core/verdict.js +369 -0
  23. package/dist/prod/index.js +45 -0
  24. package/dist/prod/map.js +319 -0
  25. package/dist/prod/signals.js +394 -0
  26. package/dist/prod/store/index.js +36 -0
  27. package/dist/prod/store/next/optimistic.js +377 -0
  28. package/dist/prod/store/next/projection.js +172 -0
  29. package/dist/prod/store/next/reconcile.js +327 -0
  30. package/dist/prod/store/next/store.js +1232 -0
  31. package/dist/prod/store/next/target.js +20 -0
  32. package/dist/prod/store/store.js +325 -0
  33. package/dist/prod/store/storePath.js +103 -0
  34. package/dist/prod/store/utils.js +201 -0
  35. package/dist/types/affects.d.ts +47 -0
  36. package/dist/types/boundaries.d.ts +60 -13
  37. package/dist/types/core/action.d.ts +35 -6
  38. package/dist/types/core/async.d.ts +16 -1
  39. package/dist/types/core/constants.d.ts +35 -6
  40. package/dist/types/core/context.d.ts +10 -2
  41. package/dist/types/core/core.d.ts +66 -63
  42. package/dist/types/core/dev.d.ts +17 -2
  43. package/dist/types/core/effect.d.ts +1 -2
  44. package/dist/types/core/error.d.ts +33 -0
  45. package/dist/types/core/external.d.ts +0 -11
  46. package/dist/types/core/graph.d.ts +2 -1
  47. package/dist/types/core/heap.d.ts +8 -0
  48. package/dist/types/core/index.d.ts +3 -2
  49. package/dist/types/core/invariants.d.ts +59 -0
  50. package/dist/types/core/lanes.d.ts +2 -0
  51. package/dist/types/core/optimistic.d.ts +6 -0
  52. package/dist/types/core/owner.d.ts +50 -3
  53. package/dist/types/core/scheduler.d.ts +85 -11
  54. package/dist/types/core/types.d.ts +66 -1
  55. package/dist/types/core/verdict.d.ts +2 -0
  56. package/dist/types/index.d.ts +3 -2
  57. package/dist/types/map.d.ts +21 -5
  58. package/dist/types/signals.d.ts +183 -12
  59. package/dist/types/store/index.d.ts +16 -6
  60. package/dist/types/store/next/optimistic.d.ts +20 -0
  61. package/dist/types/store/next/projection.d.ts +24 -0
  62. package/dist/types/store/next/reconcile.d.ts +3 -0
  63. package/dist/types/store/next/store.d.ts +71 -0
  64. package/dist/types/store/next/target.d.ts +99 -0
  65. package/dist/types/store/optimistic.d.ts +3 -3
  66. package/dist/types/store/projection.d.ts +10 -6
  67. package/dist/types/store/reconcile.d.ts +31 -8
  68. package/dist/types/store/store.d.ts +91 -71
  69. package/dist/types/store/utils.d.ts +0 -40
  70. package/dist/types-cjs/affects.d.cts +47 -0
  71. package/dist/types-cjs/boundaries.d.cts +60 -13
  72. package/dist/types-cjs/core/action.d.cts +35 -6
  73. package/dist/types-cjs/core/async.d.cts +16 -1
  74. package/dist/types-cjs/core/constants.d.cts +35 -6
  75. package/dist/types-cjs/core/context.d.cts +10 -2
  76. package/dist/types-cjs/core/core.d.cts +66 -63
  77. package/dist/types-cjs/core/dev.d.cts +17 -2
  78. package/dist/types-cjs/core/effect.d.cts +1 -2
  79. package/dist/types-cjs/core/error.d.cts +33 -0
  80. package/dist/types-cjs/core/external.d.cts +0 -11
  81. package/dist/types-cjs/core/graph.d.cts +2 -1
  82. package/dist/types-cjs/core/heap.d.cts +8 -0
  83. package/dist/types-cjs/core/index.d.cts +3 -2
  84. package/dist/types-cjs/core/invariants.d.cts +59 -0
  85. package/dist/types-cjs/core/lanes.d.cts +2 -0
  86. package/dist/types-cjs/core/optimistic.d.cts +6 -0
  87. package/dist/types-cjs/core/owner.d.cts +50 -3
  88. package/dist/types-cjs/core/scheduler.d.cts +85 -11
  89. package/dist/types-cjs/core/types.d.cts +66 -1
  90. package/dist/types-cjs/core/verdict.d.cts +2 -0
  91. package/dist/types-cjs/index.d.cts +3 -2
  92. package/dist/types-cjs/map.d.cts +21 -5
  93. package/dist/types-cjs/signals.d.cts +183 -12
  94. package/dist/types-cjs/store/index.d.cts +16 -6
  95. package/dist/types-cjs/store/next/optimistic.d.cts +20 -0
  96. package/dist/types-cjs/store/next/projection.d.cts +24 -0
  97. package/dist/types-cjs/store/next/reconcile.d.cts +3 -0
  98. package/dist/types-cjs/store/next/store.d.cts +71 -0
  99. package/dist/types-cjs/store/next/target.d.cts +99 -0
  100. package/dist/types-cjs/store/optimistic.d.cts +3 -3
  101. package/dist/types-cjs/store/projection.d.cts +10 -6
  102. package/dist/types-cjs/store/reconcile.d.cts +31 -8
  103. package/dist/types-cjs/store/store.d.cts +91 -71
  104. package/dist/types-cjs/store/utils.d.cts +0 -40
  105. package/package.json +12 -9
  106. package/dist/prod.js +0 -3627
@@ -0,0 +1,607 @@
1
+ import { STATUS_UNINITIALIZED, STATUS_ERROR, STATUS_PENDING, REACTIVE_DIRTY, REACTIVE_OPTIMISTIC_DIRTY, NOT_PENDING, CONFIG_AUTO_DISPOSE, REACTIVE_ZOMBIE, REACTIVE_DISPOSED } from "./constants.js";
2
+
3
+ import { untrack, setSignal, context } from "./core.js";
4
+
5
+ import "./invariants.js";
6
+
7
+ import { StatusError, NotReadyError } from "./error.js";
8
+
9
+ import { trimStaleDeps, unobserved } from "./graph.js";
10
+
11
+ import { enqueueSub } from "./heap.js";
12
+
13
+ import { hasActiveOverride, assignOrMergeLane, resolveLane, resolveTransition } from "./lanes.js";
14
+
15
+ import { cleanup } from "./owner.js";
16
+
17
+ import { GlobalQueue, schedule, flush, queuePendingNode, insertSubs, clock, currentTransition, globalQueue } from "./scheduler.js";
18
+
19
+ // The lazily-created Set is the ONE container for pending sources. Its
20
+ // predecessor — a singular slot promoted to a Set on the second source —
21
+ // created dual state whose migration invariant was easy to break: a third
22
+ // overlapping source landed beside the Set and removePendingSource refused
23
+ // to clear it, stranding the Set members' pending forever (#2893).
24
+ function addPendingSource(e, n) {
25
+ if (e.oe?.has(n)) return false;
26
+ (e.oe ??= new Set).add(n);
27
+ return true;
28
+ }
29
+
30
+ function removePendingSource(e, n) {
31
+ if (!e.oe?.delete(n)) return false;
32
+ if (e.oe.size === 0) e.oe = undefined;
33
+ return true;
34
+ }
35
+
36
+ function clearPendingSources(e) {
37
+ e.oe?.clear();
38
+ e.oe = undefined;
39
+ }
40
+
41
+ /**
42
+ * A loading-window node hit an unready source (sync throw in recompute, or a
43
+ * NotReadyError-rejected flight): register for the source's settle — the
44
+ * settlePendingSource walk runs off `_pendingSources` + `_blocked` alone —
45
+ * with NO read-visible pending status, no downstream propagation, no
46
+ * transition, no lane registration. Commit #0 keeps serving.
47
+ */ function parkLoadingWindow(e, n) {
48
+ e.le = true;
49
+ if (n.source) addPendingSource(e, n.source);
50
+ // A settled error is the node's answer ("the error stays the answer until
51
+ // this retry can actually run") — the park must not replace it: reads
52
+ // throw `_error` while STATUS_ERROR is set, and overwriting it here leaks
53
+ // a pending-class NotReadyError from a read-invisible park (#2989).
54
+ if (!(e.S & STATUS_ERROR)) setPendingError(e, n.source, n);
55
+ }
56
+
57
+ function setPendingError(e, n, t) {
58
+ if (!n) {
59
+ e._ = null;
60
+ return;
61
+ }
62
+ if (t instanceof NotReadyError && t.source === n) {
63
+ e._ = t;
64
+ return;
65
+ }
66
+ const r = e._;
67
+ if (!(r instanceof NotReadyError) || r.source !== n) {
68
+ e._ = new NotReadyError(n);
69
+ }
70
+ }
71
+
72
+ function forEachDependent(e, n) {
73
+ for (let t = e.o; t !== null; t = t.ue) n(t.fe, t);
74
+ // `?? null`: affects() marks route plain signals (no `_child` slot) through here.
75
+ for (let t = e.u ?? null; t !== null; t = t.ae) {
76
+ for (let e = t.o; e !== null; e = e.ue) n(e.fe, e);
77
+ }
78
+ }
79
+
80
+ // Queue a node to re-run on the next flush (used both when a pending source
81
+ // settles and when an `isPending` observer must re-evaluate after a real error):
82
+ // shared scheduling helper in heap.ts (tracked effects bypass the heap).
83
+ // Settle-time counterpart of unlinkSubs' last-one-out check. A lazy node that
84
+ // loses its last subscriber while STATUS_PENDING is exempt from autodispose
85
+ // (the in-flight work is an observer), so whatever CLEARS that pending state
86
+ // must run the release — otherwise the node stays linked and recomputes
87
+ // forever with zero subscribers (#2934). The node's own promise/iterator
88
+ // callbacks handle their own release (settleAutodispose in handleAsync); this
89
+ // covers derivatively-pending dependents, which have no callbacks of their own.
90
+ function releaseIfSettledUnobserved(e) {
91
+ e.ce && e.T & CONFIG_AUTO_DISPOSE && !e.o && !(e.se & REACTIVE_ZOMBIE) && !(e.S & STATUS_PENDING) && unobserved(e);
92
+ }
93
+
94
+ // Error-path sweep: notifyStatus(STATUS_ERROR) clears dependents' pending
95
+ // sources through its own recursion (no per-node settle callback), so after
96
+ // the propagation completes, walk the same graph for stranded lazy nodes.
97
+ // Collect-then-release so unobserved() never unlinks under the walk.
98
+ function releaseSettledDependents(e) {
99
+ let n;
100
+ const t = new Set;
101
+ const visit = e => {
102
+ if (t.has(e)) return;
103
+ t.add(e);
104
+ if (!e.o && e.T & CONFIG_AUTO_DISPOSE) (n ??= []).push(e);
105
+ forEachDependent(e, visit);
106
+ };
107
+ forEachDependent(e, visit);
108
+ if (n) for (const e of n) releaseIfSettledUnobserved(e);
109
+ }
110
+
111
+ // Error-dimension twin of settlePendingSource's blocked re-enqueue (#2949):
112
+ // a node in STATUS_ERROR that recovers by recomputing to an UNCHANGED value
113
+ // fires no value notification — the recovery is completely silent. But a
114
+ // dependent that re-ran during the error window consumed its dirty flag and
115
+ // committed nothing (the fresh sibling values it read were absorbed into an
116
+ // errored run), so its committed value is stale. The propagated error is one
117
+ // object identity down the whole dependent tree, and holding it is exactly
118
+ // the "blocked on this error" marker — re-enqueue those holders so they
119
+ // re-run: fresh values commit and flow, and a dependent with another
120
+ // still-broken source simply re-errors. The async dimension needs no twin of
121
+ // its own: recovery there passes through a pending window whose re-runners
122
+ // set _blocked and ride settlePendingSource. Walks the full dependent graph
123
+ // (releaseSettledDependents shape): identity holders can sit below an
124
+ // intermediate whose own error state has since been scrubbed or replaced
125
+ // (e.g. an error boundary's tree node).
126
+ function settleErroredDependents(e, n) {
127
+ let t = false;
128
+ const r = new Set;
129
+ const visit = e => {
130
+ if (r.has(e)) return;
131
+ r.add(e);
132
+ if (e._ === n) {
133
+ enqueueSub(e);
134
+ t = true;
135
+ }
136
+ forEachDependent(e, visit);
137
+ };
138
+ forEachDependent(e, visit);
139
+ if (t) schedule();
140
+ }
141
+
142
+ function settlePendingSource(e) {
143
+ let n = false;
144
+ let t;
145
+ const r = new Set;
146
+ // Companion updates no-op without the verdict layer (null hook).
147
+ const o = GlobalQueue.Se;
148
+ const settle = l => {
149
+ if (r.has(l) || !removePendingSource(l, e)) return;
150
+ r.add(l);
151
+ l.de = clock;
152
+ const u = l.oe?.values().next().value;
153
+ // STATUS_ERROR + pending sources only coexist via an errored loading
154
+ // window's park (notifyStatus(STATUS_ERROR) clears pending sources
155
+ // otherwise): the settled error stays the answer through the settle —
156
+ // nulling it here would have reads throw `null` until the re-enqueued
157
+ // retry lands, or lose it entirely if that retry parks again (#2989).
158
+ const i = l.S & STATUS_ERROR;
159
+ if (u) {
160
+ if (!i) setPendingError(l, u);
161
+ o !== null && o(l);
162
+ } else {
163
+ l.S &= ~STATUS_PENDING;
164
+ if (!i) setPendingError(l);
165
+ o !== null && o(l);
166
+ if (l.le) {
167
+ enqueueSub(l);
168
+ n = true;
169
+ }
170
+ l.le = false;
171
+ // Fully settled with nobody watching: release candidate (#2934). Checked
172
+ // again at release time — deferred so unobserved() can't unlink subs
173
+ // lists this walk is still iterating.
174
+ if (!l.o && l.T & CONFIG_AUTO_DISPOSE) (t ??= []).push(l);
175
+ }
176
+ forEachDependent(l, settle);
177
+ };
178
+ forEachDependent(e, settle);
179
+ // Release before the flush schedule below: unobserved() pulls the node back
180
+ // out of the heap, so the enqueueSub above never recomputes a released node.
181
+ if (t) for (const e of t) releaseIfSettledUnobserved(e);
182
+ if (n) schedule();
183
+ }
184
+
185
+ // Object-thenable detection (Promises/A+ shape).
186
+ function isThenable(e) {
187
+ return e != null && typeof e === "object" && typeof e.then === "function";
188
+ }
189
+
190
+ function handleAsync(e, n, t) {
191
+ let r = false;
192
+ let o = false;
193
+ if (typeof n === "object" && n !== null) {
194
+ untrack(() => {
195
+ r = n[Symbol.asyncIterator];
196
+ o = !r && isThenable(n);
197
+ });
198
+ }
199
+ if (!o && !r) {
200
+ e.Te = null;
201
+ // A sync landing is the first real answer for a loadingValue node.
202
+ e.Ee = false;
203
+ return n;
204
+ }
205
+ e.Te = n;
206
+ let l;
207
+ // Settle-time transition re-entry. The loading rail is invisible to
208
+ // transactions (#2933): a boundary-caught first load never registers as an
209
+ // async reporter, so its settle — the boundary's fallback -> content
210
+ // reveal — must flow ambiently. The node can still carry a `_transition`
211
+ // stamp (pending-node bookkeeping rides through the stamping sites), and
212
+ // blindly re-entering that stamped, still-incomplete transaction stashed
213
+ // the reveal with it — a deadlock when the transaction's completion
214
+ // depended on the reveal (#2937). An ESCAPED first load did register and
215
+ // keeps transition scheduling; initialized (value-holding) pending settles
216
+ // are the transaction's reveal machinery and always re-enter.
217
+ const settleTransition = () => {
218
+ const n = resolveTransition(e);
219
+ if (n && e.S & STATUS_UNINITIALIZED && !currentTransition(n).Ie.has(e)) {
220
+ // Drop the stale stamp too: the plain settle write (setSignal) and the
221
+ // stash-path restamp both re-enter the transaction through it.
222
+ e.Ne = null;
223
+ return;
224
+ }
225
+ globalQueue.initTransition(n);
226
+ };
227
+ const handleError = t => {
228
+ if (e.Te !== n) return;
229
+ // NotReadyError from rejected promises should be treated as pending, not error
230
+ let r = t instanceof NotReadyError;
231
+ if (r && e.Ee) {
232
+ // Loading window: the flight died waiting on an unready source. Keep
233
+ // serving commit #0 — same parking as recompute's catch for sync
234
+ // dependency throws. The dead flight is released so the clock-gated
235
+ // error-retry pull (updateIfNecessary) can also re-ask.
236
+ e.Te = null;
237
+ parkLoadingWindow(e, t);
238
+ e.de = clock;
239
+ return;
240
+ }
241
+ settleTransition();
242
+ notifyStatus(e, r ? STATUS_PENDING : STATUS_ERROR, t);
243
+ e.de = clock;
244
+ // A real error settles derivatively-pending dependents (notifyStatus
245
+ // cleared their pending sources), so stranded lazy ones release here —
246
+ // the error twin of settlePendingSource's release (#2934).
247
+ if (!r) releaseSettledDependents(e);
248
+ };
249
+ const asyncWrite = (r, o) => {
250
+ if (e.Te !== n) return;
251
+ // If the node was dirtied by a newer write (optimistic override or regular),
252
+ // skip this stale async result — the upcoming flush will recompute the node
253
+ // with the new value, creating a fresh Promise that supersedes this one.
254
+ if (e.se & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY)) return;
255
+ settleTransition();
256
+ const l = !!(e.S & STATUS_UNINITIALIZED);
257
+ trimStaleDeps(e);
258
+ clearStatus(e);
259
+ const u = resolveLane(e);
260
+ if (u) u.Ae.delete(e);
261
+ if (t) {
262
+ t(r);
263
+ if (l) clearStatus(e, true);
264
+ } else if (e.De !== undefined) {
265
+ // Optimistic node — resting OR covered by an active override — holds
266
+ // through the shared pending-node path, exactly like a plain async memo,
267
+ // so the commit clears STATUS_UNINITIALIZED (#2806) and elevation to
268
+ // _value happens on this value's OWN transition schedule (A18 as
269
+ // re-ruled 2026-07-07: _value only changes at commit points). With an
270
+ // override active the hold and its eventual commit are unobservable
271
+ // (A17 — every reader sees the override); the revert reveals whatever
272
+ // has committed by then, so corrections reveal atomically with their
273
+ // transition rather than escaping it.
274
+ if (e._e === NOT_PENDING) queuePendingNode(e);
275
+ e._e = r;
276
+ // The hold is a companion-visible write like any other (A13/A19): the
277
+ // clearStatus() above computed its verdict before the hold existed, so
278
+ // isPending must re-derive (the value is not final until commit — V1)
279
+ // and latest() must see the fresh in-flight value (V2). Subscribers are
280
+ // only notified when the hold is visible to them: under an active
281
+ // override every reader sees the override (A17), so waking subs would
282
+ // re-show an unchanged view — the revert is the notification point.
283
+ GlobalQueue.Pe !== null && GlobalQueue.Pe(e, r);
284
+ if (!hasActiveOverride(e)) insertSubs(e);
285
+ e.de = clock;
286
+ } else if (u) {
287
+ // Route through lane's effect queue for independent flushing
288
+ const n = e.Re;
289
+ const t = e.be;
290
+ const o = e.Ue;
291
+ try {
292
+ if (!n && l || !o || !o(r, t)) {
293
+ e.be = r;
294
+ e.de = clock;
295
+ // The latest() shadow write gives latest() effects independent lanes; the
296
+ // _pendingSignal update is a no-op repeat of the clearStatus() call above
297
+ // (computePendingState doesn't read _value).
298
+ GlobalQueue.Pe !== null && GlobalQueue.Pe(e, r);
299
+ insertSubs(e, true);
300
+ }
301
+ } catch (n) {
302
+ // A user comparator throwing during async resolution has no caller to
303
+ // surface to (we're in promise machinery) — route it through the node's
304
+ // error status so boundaries contain it instead of an unhandled
305
+ // rejection (#2837).
306
+ notifyStatus(e, STATUS_ERROR, n);
307
+ }
308
+ } else {
309
+ try {
310
+ setSignal(e, () => r);
311
+ } catch (n) {
312
+ // Same containment as above: setSignal's comparator throw is the only
313
+ // pre-commit failure here, and there is no user callsite to throw to.
314
+ notifyStatus(e, STATUS_ERROR, n);
315
+ }
316
+ }
317
+ // First real answer landing: the window closes when the answer becomes
318
+ // OBSERVABLE. A direct commit is observable now; a transition-held write
319
+ // (`_pendingValue` set above or inside setSignal) is not — the verdict's
320
+ // held-value branch is window-gated, and commitPendingNode closes the
321
+ // window when the hold commits, so no one-frame isPending pulse can leak
322
+ // to live observers between the landing and its commit (#2990).
323
+ if (e._e === NOT_PENDING) e.Ee = false;
324
+ settlePendingSource(e);
325
+ schedule();
326
+ flush();
327
+ o?.();
328
+ };
329
+ // A pending node's in-flight promise is an observer: `unlinkSubs` skips
330
+ // autodispose while STATUS_PENDING so subscriber churn can't orphan the
331
+ // work (a lazy async memo would otherwise tear down and re-execute — one
332
+ // fetch per suspended re-read). Settling is that observer's release, so
333
+ // it runs the same last-one-out check the other release sites run.
334
+ // Returns whether the node released, so the iterator branch can stop
335
+ // pulling values instead of pumping an unobserved stream forever (#2935).
336
+ const settleAutodispose = () => {
337
+ if (e.T & CONFIG_AUTO_DISPOSE && !e.o && !(e.S & STATUS_PENDING)) {
338
+ unobserved(e);
339
+ return true;
340
+ }
341
+ return false;
342
+ };
343
+ // Consumes an AsyncIterable as this flight's value stream. Two postures:
344
+ // LIVE (called synchronously from this read — the compute returned an
345
+ // iterable directly, or a sync-settled thenable held one), where the
346
+ // initial drain may stash a sync first yield for the caller to return and
347
+ // close registration uses the ambient owner; and DEFERRED (the flattening
348
+ // path — a thenable resolved to an iterable in a later microtask), where
349
+ // there is no caller to serve and no ambient owner: sync-settled steps
350
+ // write through asyncWrite, and close registration goes through the slot
351
+ // the thenable branch pre-registered while it still owned the context.
352
+ // Returns whether a sync answer landed (first yield or empty completion) —
353
+ // meaningful only in the live posture.
354
+ const consumeIterator = (t, r) => {
355
+ const o = t[Symbol.asyncIterator]();
356
+ let u = false;
357
+ let i = false;
358
+ let s = !r;
359
+ const close = () => {
360
+ if (i) return;
361
+ i = true;
362
+ try {
363
+ const e = o.return?.();
364
+ if (isThenable(e)) e.then(undefined, () => {});
365
+ } catch {}
366
+ };
367
+ r ? r(close) : cleanup(close);
368
+ // Release check before each next pull: an unobserved lazy node must tear
369
+ // down (its close above runs via disposal, closing the iterator) instead
370
+ // of pumping the stream forever with zero subscribers (#2935).
371
+ const iterateOrRelease = () => {
372
+ if (!settleAutodispose()) iterate();
373
+ };
374
+ const iterate = () => {
375
+ let t, r, f = false, a = false, c = true;
376
+ // Protocol tolerance, matching `for await`: `await` unwraps whatever
377
+ // next() returns — a thenable OR a bare IteratorResult. Real producers
378
+ // use the bare form as a promise-free fast path when a value is already
379
+ // buffered (seroval's deserialized streams do), so a bare result is
380
+ // assimilated as an already-settled step instead of crashing on `.then`.
381
+ const S = o.next();
382
+ const d = isThenable(S) ? S : {
383
+ then: e => void e(S)
384
+ };
385
+ d.then(r => {
386
+ // The sync stash only serves the INITIAL drain (handleAsync's caller
387
+ // consumes syncValue / throws NotReady from it). A sync-settled step
388
+ // after an async gap — seroval buffering values between pulls, a
389
+ // sync-thenable producer mid-stream — has no caller reading the
390
+ // stash: it must write through the async path or the value is
391
+ // silently dropped. (The deferred posture never has a caller, so
392
+ // initialRead starts false there and everything writes through.)
393
+ if (c && s) {
394
+ t = r;
395
+ f = true;
396
+ if (r.done) i = true;
397
+ } else if (e.Te !== n) {
398
+ return;
399
+ } else if (!r.done) {
400
+ u = true;
401
+ asyncWrite(r.value, iterateOrRelease);
402
+ } else {
403
+ i = true;
404
+ if (u) {
405
+ schedule();
406
+ flush();
407
+ } else {
408
+ // Empty completion settles like the immediately-done sync path.
409
+ asyncWrite(undefined);
410
+ }
411
+ settleAutodispose();
412
+ }
413
+ }, t => {
414
+ if (c && s) {
415
+ r = t;
416
+ a = true;
417
+ } else if (e.Te === n) {
418
+ i = true;
419
+ handleError(t);
420
+ settleAutodispose();
421
+ }
422
+ });
423
+ c = false;
424
+ if (a) {
425
+ // Match the promise branch, but only rethrow during the initial read.
426
+ i = true;
427
+ handleError(r);
428
+ if (s) throw r;
429
+ return true;
430
+ }
431
+ if (f && !t.done) {
432
+ l = t.value;
433
+ u = true;
434
+ return iterate();
435
+ }
436
+ return f && t.done;
437
+ };
438
+ const f = iterate();
439
+ // Later iterate() calls run from asyncWrite, where rethrowing would be unhandled.
440
+ s = false;
441
+ return u || f;
442
+ };
443
+ // Landed-synchronously verdict for a LIVE iterator drain; null when no live
444
+ // drain ran (plain promise flight, or a deferred flatten). Drives the
445
+ // shared NotReady/loading tail below.
446
+ let u = null;
447
+ // Flatten one async level: a thenable that RESOLVES to an AsyncIterable —
448
+ // the shape every async stub returning a stream produces — consumes as the
449
+ // stream itself rather than settling on the iterable object. One level
450
+ // only: A+ `then` already collapses nested thenables, so the resolved
451
+ // value is never itself a thenable.
452
+ const flattenIfIterable = (e, n) => {
453
+ let t = false;
454
+ if (typeof e === "object" && e !== null) {
455
+ untrack(() => {
456
+ t = e[Symbol.asyncIterator];
457
+ });
458
+ }
459
+ if (!t) return false;
460
+ const r = consumeIterator(e, n);
461
+ if (!n) u = r;
462
+ return true;
463
+ };
464
+ if (o) {
465
+ let t = false, r = false, o, u = true;
466
+ // Close registration for the flattening path. Consumption starts in a
467
+ // microtask where the ambient owner is gone (or worse, someone else's),
468
+ // so `cleanup()` can't be used — the close targets el's disposal list
469
+ // directly, exactly where a live cleanup() during this recompute would
470
+ // have put it. Deliberately NOT pre-registered at flight start: a
471
+ // non-null `_disposal` reclassifies the node into recompute's deferred
472
+ // (zombie) disposal path, and plain promise flights — the overwhelming
473
+ // majority — must not pay that. Only a flight that actually flattens
474
+ // becomes disposal-bearing, which is exactly the class a directly
475
+ // returned iterable already occupies.
476
+ const registerDeferredClose = n => {
477
+ if (!e.he) e.he = n; else if (Array.isArray(e.he)) e.he.push(n); else e.he = [ e.he, n ];
478
+ };
479
+ n.then(r => {
480
+ if (u) {
481
+ l = r;
482
+ t = true;
483
+ } else if (e.Te === n && !(e.se & REACTIVE_DISPOSED) && flattenIfIterable(r, registerDeferredClose)) ; else {
484
+ asyncWrite(r);
485
+ settleAutodispose();
486
+ }
487
+ }, e => {
488
+ if (u) {
489
+ o = e;
490
+ r = true;
491
+ } else {
492
+ handleError(e);
493
+ settleAutodispose();
494
+ }
495
+ });
496
+ u = false;
497
+ if (r) {
498
+ // Settle through the same status path an async rejection uses, then
499
+ // unwind the in-progress synchronous read so the errored node isn't
500
+ // momentarily read as `undefined`.
501
+ handleError(o);
502
+ throw o;
503
+ } else if (!t) {
504
+ // Loading window: serve commit #0 instead of suspending. No transition
505
+ // is opened — first-flight work on a loadingValue node is loading-class
506
+ // (invisible to boundaries and transitions); the flight itself is
507
+ // already registered in _inFlight and lands through asyncWrite.
508
+ if (e.Ee) return e.be;
509
+ globalQueue.initTransition(resolveTransition(e));
510
+ throw new NotReadyError(context);
511
+ } else if (!flattenIfIterable(l)) {
512
+ // Synchronously-resolved promise: the first real answer landed.
513
+ e.Ee = false;
514
+ }
515
+ // A sync-resolved promise holding an AsyncIterable flattened LIVE (we
516
+ // are still inside the synchronous read): full initial-drain semantics
517
+ // apply and the shared tail below settles the verdict.
518
+ }
519
+ if (r) flattenIfIterable(n);
520
+ if (u !== null) {
521
+ if (!u) {
522
+ // Loading window: serve commit #0 (see the promise branch above).
523
+ if (e.Ee) return e.be;
524
+ globalQueue.initTransition(resolveTransition(e));
525
+ throw new NotReadyError(context);
526
+ }
527
+ // A sync first yield (or immediate empty completion) is the first real
528
+ // answer; async yields clear inside asyncWrite.
529
+ e.Ee = false;
530
+ }
531
+ return l;
532
+ }
533
+
534
+ function clearStatus(e, n = false) {
535
+ if (e.oe) clearPendingSources(e);
536
+ if (e.le) e.le = false;
537
+ // The pending window is over; its quiet classification dies with it.
538
+ // (Unconditional: _reask is baked into the node literals, so this is a
539
+ // plain store to an existing slot — no shape change.)
540
+ e.pe = false;
541
+ e.S = n ? 0 : e.S & STATUS_UNINITIALIZED;
542
+ if (e._) setPendingError(e);
543
+ // Update pending signal for isPending() reactivity (companions only exist
544
+ // once the verdict layer created them, which installs the hooks).
545
+ if (e.Oe || e.ge) GlobalQueue.Se(e);
546
+ if (e.u && GlobalQueue.Ge !== null) GlobalQueue.Ge(e);
547
+ if (e.i) e.i();
548
+ }
549
+
550
+ function notifyStatus(e, n, t, r, o) {
551
+ // Wrap regular errors to track source node
552
+ if (n === STATUS_ERROR && !(t instanceof StatusError) && !(t instanceof NotReadyError)) t = new StatusError(e, t);
553
+ const l = n === STATUS_PENDING && t instanceof NotReadyError ? t.source : undefined;
554
+ const u = l === e;
555
+ const i = n === STATUS_PENDING && e.De !== undefined && !u;
556
+ const s = i && hasActiveOverride(e);
557
+ if (!r) {
558
+ if (n === STATUS_PENDING && l) {
559
+ addPendingSource(e, l);
560
+ e.S = STATUS_PENDING | e.S & STATUS_UNINITIALIZED;
561
+ // Preserve the current source on this propagation so render-effect notification
562
+ // can register every distinct pending source with the transition.
563
+ setPendingError(e, l, t);
564
+ } else {
565
+ clearPendingSources(e);
566
+ e.S = n | (n !== STATUS_ERROR ? e.S & STATUS_UNINITIALIZED : 0);
567
+ e._ = t;
568
+ }
569
+ GlobalQueue.Se !== null && GlobalQueue.Se(e);
570
+ if (e.u && GlobalQueue.Ge !== null) GlobalQueue.Ge(e);
571
+ }
572
+ if (o && !r) {
573
+ assignOrMergeLane(e, o);
574
+ }
575
+ const f = r || s;
576
+ const a = r || i ? undefined : o;
577
+ if (e.i) {
578
+ if (r && n === STATUS_PENDING) {
579
+ return;
580
+ }
581
+ if (f) {
582
+ e.i(n, t);
583
+ } else {
584
+ e.i();
585
+ }
586
+ return;
587
+ }
588
+ forEachDependent(e, (e, r) => {
589
+ e.de = clock;
590
+ if (n === STATUS_PENDING && l && !e.oe?.has(l) || n !== STATUS_PENDING && (e._ !== t || e.oe)) {
591
+ // A pending-observer link is the subscription an `isPending` read created.
592
+ // It exists so the observer re-runs when the source settles, but it must
593
+ // not carry a real (non-NotReadyError) error — the synchronous `isPending`
594
+ // read swallows those, and the async path must match. Re-run the observer
595
+ // so `isPending` re-evaluates (to not-pending) instead of forwarding.
596
+ if (r.ye && n !== STATUS_PENDING && !(t instanceof NotReadyError)) {
597
+ enqueueSub(e);
598
+ schedule();
599
+ return;
600
+ }
601
+ if (!f && !e.Ne) queuePendingNode(e);
602
+ notifyStatus(e, n, t, f, a);
603
+ }
604
+ });
605
+ }
606
+
607
+ export { addPendingSource, clearStatus, forEachDependent, handleAsync, isThenable, notifyStatus, parkLoadingWindow, releaseSettledDependents, setPendingError, settleErroredDependents, settlePendingSource };