@solidjs/signals 2.0.0-rc.15 → 2.0.0-rc.17

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