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

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