@solidjs/signals 2.0.0-rc.4 → 2.0.0-rc.6

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 (57) hide show
  1. package/dist/dev.js +1618 -232
  2. package/dist/node.cjs +2262 -1199
  3. package/dist/prod/boundaries.js +4 -1
  4. package/dist/prod/core/async.js +152 -101
  5. package/dist/prod/core/constants.js +55 -1
  6. package/dist/prod/core/core.js +305 -234
  7. package/dist/prod/core/effect.js +28 -28
  8. package/dist/prod/core/error.js +13 -1
  9. package/dist/prod/core/external.js +2 -2
  10. package/dist/prod/core/graph.js +27 -27
  11. package/dist/prod/core/heap.js +30 -30
  12. package/dist/prod/core/lanes.js +32 -32
  13. package/dist/prod/core/optimistic.js +57 -57
  14. package/dist/prod/core/owner.js +34 -34
  15. package/dist/prod/core/scheduler.js +346 -152
  16. package/dist/prod/core/verdict.js +122 -65
  17. package/dist/prod/index.js +3 -3
  18. package/dist/prod/map.js +106 -106
  19. package/dist/prod/signals.js +321 -26
  20. package/dist/prod/store/next/optimistic.js +363 -151
  21. package/dist/prod/store/next/patch.js +6 -6
  22. package/dist/prod/store/next/projection.js +25 -21
  23. package/dist/prod/store/next/store.js +223 -113
  24. package/dist/prod/store/store.js +2 -2
  25. package/dist/types/core/async.d.ts +2 -0
  26. package/dist/types/core/attribution-hooks.d.ts +11 -0
  27. package/dist/types/core/attribution.d.ts +57 -4
  28. package/dist/types/core/constants.d.ts +54 -0
  29. package/dist/types/core/core.d.ts +19 -20
  30. package/dist/types/core/dev.d.ts +8 -2
  31. package/dist/types/core/error.d.ts +9 -0
  32. package/dist/types/core/index.d.ts +2 -2
  33. package/dist/types/core/scheduler.d.ts +39 -0
  34. package/dist/types/core/types.d.ts +12 -0
  35. package/dist/types/index.d.ts +3 -3
  36. package/dist/types/signals.d.ts +108 -11
  37. package/dist/types/store/next/optimistic.d.ts +13 -10
  38. package/dist/types/store/next/projection.d.ts +1 -1
  39. package/dist/types/store/next/store.d.ts +22 -0
  40. package/dist/types/store/next/target.d.ts +25 -0
  41. package/dist/types-cjs/core/async.d.cts +2 -0
  42. package/dist/types-cjs/core/attribution-hooks.d.cts +11 -0
  43. package/dist/types-cjs/core/attribution.d.cts +57 -4
  44. package/dist/types-cjs/core/constants.d.cts +54 -0
  45. package/dist/types-cjs/core/core.d.cts +19 -20
  46. package/dist/types-cjs/core/dev.d.cts +8 -2
  47. package/dist/types-cjs/core/error.d.cts +9 -0
  48. package/dist/types-cjs/core/index.d.cts +2 -2
  49. package/dist/types-cjs/core/scheduler.d.cts +39 -0
  50. package/dist/types-cjs/core/types.d.cts +12 -0
  51. package/dist/types-cjs/index.d.cts +3 -3
  52. package/dist/types-cjs/signals.d.cts +108 -11
  53. package/dist/types-cjs/store/next/optimistic.d.cts +13 -10
  54. package/dist/types-cjs/store/next/projection.d.cts +1 -1
  55. package/dist/types-cjs/store/next/store.d.cts +22 -0
  56. package/dist/types-cjs/store/next/target.d.cts +25 -0
  57. package/package.json +1 -1
@@ -561,7 +561,10 @@ function flattenArray(e, t = [], r) {
561
561
  } while (typeof n === "function" && !n.length);
562
562
  }
563
563
  if (Array.isArray(n)) {
564
- s = flattenArray(n, t, r);
564
+ // OR, don't overwrite: an accessor already pushed under doNotUnwrap
565
+ // still needs the resolving wrapper even when a later sibling
566
+ // fragment contains no functions (#3133).
567
+ s = flattenArray(n, t, r) || s;
565
568
  } else if (r?.skipNonRendered && (n == null || n === true || n === false || n === "")) {
566
569
  // skip
567
570
  } else t.push(n);
@@ -1,4 +1,4 @@
1
- import { STATUS_UNINITIALIZED, CONFIG_CHILD_COMPANIONS, STATUS_ERROR, STATUS_PENDING, REACTIVE_DIRTY, REACTIVE_OPTIMISTIC_DIRTY, NOT_PENDING, CONFIG_AUTO_DISPOSE, REACTIVE_ZOMBIE, REACTIVE_DISPOSED } from "./constants.js";
1
+ import { STATUS_UNINITIALIZED, CONFIG_CHILD_COMPANIONS, STATUS_ERROR, STATUS_PENDING, REACTIVE_DIRTY, REACTIVE_OPTIMISTIC_DIRTY, NOT_PENDING, CONFIG_AUTHORITATIVE_OBSERVED, CONFIG_AUTO_DISPOSE, REACTIVE_ZOMBIE, REACTIVE_DISPOSED } from "./constants.js";
2
2
 
3
3
  import { untrack, ext, statusNotifierOf, setSignal, context } from "./core.js";
4
4
 
@@ -28,13 +28,15 @@ function addPendingSource(e, n) {
28
28
  }
29
29
 
30
30
  function removePendingSource(e, n) {
31
- if (!e.o?.le?.delete(n)) return false;
32
- if (e.o?.le.size === 0) if (e.o !== null) e.o.le = undefined;
31
+ const t = e.o?.le;
32
+ if (!t?.delete(n)) return false;
33
+ if (!t.size) e.o.le = undefined;
33
34
  return true;
34
35
  }
35
36
 
36
37
  function clearPendingSources(e) {
37
- e.o?.le?.clear();
38
+ // This set is node-owned and never shared; dropping the sole reference
39
+ // releases the set and every entry without a redundant clear() walk.
38
40
  if (e.o !== null) e.o.le = undefined;
39
41
  }
40
42
 
@@ -140,40 +142,44 @@ function settleErroredDependents(e, n) {
140
142
  }
141
143
 
142
144
  function settlePendingSource(e) {
145
+ // The normal landing path already cleared the source's own set. Superseded
146
+ // re-parks arrive here with an abandoned self entry, which must retire in
147
+ // the same walk as its propagated copies.
148
+ removePendingSource(e, e);
143
149
  let n = false;
144
150
  let t;
145
151
  const r = new Set;
146
152
  // Companion updates no-op without the verdict layer (null hook).
147
153
  const o = GlobalQueue.de;
148
- const settle = l => {
149
- if (r.has(l) || !removePendingSource(l, e)) return;
150
- r.add(l);
151
- l.Te = clock;
152
- const i = l.o?.le?.values().next().value;
154
+ const settle = i => {
155
+ if (r.has(i) || !removePendingSource(i, e)) return;
156
+ r.add(i);
157
+ i.Te = clock;
158
+ const l = i.o?.le?.values().next().value;
153
159
  // STATUS_ERROR + pending sources only coexist via an errored loading
154
160
  // window's park (notifyStatus(STATUS_ERROR) clears pending sources
155
161
  // otherwise): the settled error stays the answer through the settle —
156
162
  // nulling it here would have reads throw `null` until the re-enqueued
157
163
  // retry lands, or lose it entirely if that retry parks again (#2989).
158
- const u = l.S & STATUS_ERROR;
159
- if (i) {
160
- if (!u) setPendingError(l, i);
161
- o !== null && o(l);
164
+ const s = i.S & STATUS_ERROR;
165
+ if (l) {
166
+ if (!s) setPendingError(i, l);
167
+ o?.(i);
162
168
  } else {
163
- l.S &= ~STATUS_PENDING;
164
- if (!u) setPendingError(l);
165
- o !== null && o(l);
166
- if (l.o?.fe) {
167
- enqueueSub(l);
169
+ i.S &= ~STATUS_PENDING;
170
+ if (!s) setPendingError(i);
171
+ o?.(i);
172
+ if (i.o?.fe) {
173
+ enqueueSub(i);
168
174
  n = true;
169
175
  }
170
- if (l.o !== null) l.o.fe = false;
176
+ if (i.o !== null) i.o.fe = false;
171
177
  // Fully settled with nobody watching: release candidate (#2934). Checked
172
178
  // again at release time — deferred so unobserved() can't unlink subs
173
179
  // lists this walk is still iterating.
174
- if (!l.u && l.T & CONFIG_AUTO_DISPOSE) (t ??= []).push(l);
180
+ if (!i.u && i.T & CONFIG_AUTO_DISPOSE) (t ??= []).push(i);
175
181
  }
176
- forEachDependent(l, settle);
182
+ forEachDependent(i, settle);
177
183
  };
178
184
  forEachDependent(e, settle);
179
185
  // Release before the flush schedule below: unobserved() pulls the node back
@@ -187,6 +193,14 @@ function isThenable(e) {
187
193
  return e != null && typeof e === "object" && typeof e.then === "function";
188
194
  }
189
195
 
196
+ /** Fire and clear a node's iterator-flight cancellation hook (#3122). */ function releaseFlightTeardown(e) {
197
+ const n = e.o?.Ee;
198
+ if (n != null) {
199
+ e.o.Ee = null;
200
+ n();
201
+ }
202
+ }
203
+
190
204
  function handleAsync(e, n, t) {
191
205
  let r = false;
192
206
  let o = false;
@@ -197,13 +211,18 @@ function handleAsync(e, n, t) {
197
211
  });
198
212
  }
199
213
  if (!o && !r) {
200
- if (e.o !== null) e.o.Ee = null;
214
+ if (e.o !== null) e.o.Ie = null;
201
215
  // A sync landing is the first real answer for a loadingValue node.
202
- e.Ie = false;
216
+ e.Ne = false;
203
217
  return n;
204
218
  }
205
- ext(e).Ee = n;
206
- let l;
219
+ // Flight replacement relies on recompute's supersede release for iterator
220
+ // teardown (#3122): every handleAsync call — including the projection
221
+ // self-registration — runs during a recompute of `el`, which has already
222
+ // fired _flightTeardown. A future non-recompute registration path must
223
+ // release it here before overwriting _inFlight.
224
+ ext(e).Ie = n;
225
+ let i;
207
226
  // Settle-time transition re-entry. The loading rail is invisible to
208
227
  // transactions (#2933): a boundary-caught first load never registers as an
209
228
  // async reporter, so its settle — the boundary's fallback -> content
@@ -216,30 +235,34 @@ function handleAsync(e, n, t) {
216
235
  // are the transaction's reveal machinery and always re-enter.
217
236
  const settleTransition = () => {
218
237
  const n = resolveTransition(e);
219
- if (n && e.S & STATUS_UNINITIALIZED && !currentTransition(n).Ne.has(e)) {
238
+ if (n && e.S & STATUS_UNINITIALIZED && !currentTransition(n)._e.has(e)) {
220
239
  // Drop the stale stamp too: the plain settle write (setSignal) and the
221
240
  // stash-path restamp both re-enter the transaction through it.
222
- e._e = null;
241
+ e.Ae = null;
223
242
  return;
224
243
  }
225
244
  globalQueue.initTransition(n);
226
245
  };
227
246
  const handleError = t => {
228
- if (e.o?.Ee !== n) return;
247
+ if (e.o?.Ie !== n) return;
229
248
  // NotReadyError from rejected promises should be treated as pending, not error
230
249
  let r = t instanceof NotReadyError;
231
- if (r && e.Ie) {
250
+ if (r && e.Ne) {
232
251
  // Loading window: the flight died waiting on an unready source. Keep
233
252
  // serving commit #0 — same parking as recompute's catch for sync
234
253
  // dependency throws. The dead flight is released so the clock-gated
235
254
  // error-retry pull (updateIfNecessary) can also re-ask.
236
- if (e.o !== null) e.o.Ee = null;
255
+ if (e.o !== null) e.o.Ie = null;
237
256
  parkLoadingWindow(e, t);
238
257
  e.Te = clock;
239
258
  return;
240
259
  }
241
260
  settleTransition();
242
261
  notifyStatus(e, r ? STATUS_PENDING : STATUS_ERROR, t);
262
+ // A NotReady rejection is a landing into another pending source. The
263
+ // rejected flight will never settle its self entry, so transfer ownership
264
+ // after notifyStatus has propagated the replacement source.
265
+ if (r) settlePendingSource(e);
243
266
  e.Te = clock;
244
267
  // A real error settles derivatively-pending dependents (notifyStatus
245
268
  // cleared their pending sources), so stranded lazy ones release here —
@@ -247,21 +270,29 @@ function handleAsync(e, n, t) {
247
270
  if (!r) releaseSettledDependents(e);
248
271
  };
249
272
  const asyncWrite = (r, o) => {
250
- if (e.o?.Ee !== n) return;
273
+ if (e.o?.Ie !== n) return;
251
274
  // If the node was dirtied by a newer write (optimistic override or regular),
252
275
  // skip this stale async result — the upcoming flush will recompute the node
253
276
  // with the new value, creating a fresh Promise that supersedes this one.
254
277
  if (e.ie & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY)) return;
255
278
  settleTransition();
256
- const l = !!(e.S & STATUS_UNINITIALIZED);
279
+ const i = !!(e.S & STATUS_UNINITIALIZED);
280
+ // Captured before clearStatus wipes it: a quiet re-ask's landing may be
281
+ // transition-held below, and the displayed value keeps answering the same
282
+ // question until the hold commits — the classification must survive to
283
+ // that reveal or companion synchronization briefly classifies the held
284
+ // old value as pending, a one-frame pulse to direct observers (#3178).
285
+ // A truthy capture implies `_x` exists, so the restore writes it directly.
286
+ const l = e.o?.De;
257
287
  trimStaleDeps(e);
258
288
  clearStatus(e);
259
- const i = resolveLane(e);
260
- if (i) i.Ae.delete(e);
289
+ if (l) e.o.De = true;
290
+ const s = resolveLane(e);
291
+ if (s) s.Oe.delete(e);
261
292
  if (t) {
262
293
  t(r);
263
- if (l) clearStatus(e, true);
264
- } else if (e.o?.De !== undefined) {
294
+ if (i) clearStatus(e, true);
295
+ } else if (e.o?.Pe !== undefined) {
265
296
  // Optimistic node — resting OR covered by an active override — holds
266
297
  // through the shared pending-node path, exactly like a plain async memo,
267
298
  // so the commit clears STATUS_UNINITIALIZED (#2806) and elevation to
@@ -271,8 +302,8 @@ function handleAsync(e, n, t) {
271
302
  // (A17 — every reader sees the override); the revert reveals whatever
272
303
  // has committed by then, so corrections reveal atomically with their
273
304
  // transition rather than escaping it.
274
- if (e.Pe === NOT_PENDING) queuePendingNode(e);
275
- e.Pe = r;
305
+ if (e.Re === NOT_PENDING) queuePendingNode(e);
306
+ e.Re = r;
276
307
  // The hold is a companion-visible write like any other (A13/A19): the
277
308
  // clearStatus() above computed its verdict before the hold existed, so
278
309
  // isPending must re-derive (the value is not final until commit — V1)
@@ -280,24 +311,35 @@ function handleAsync(e, n, t) {
280
311
  // only notified when the hold is visible to them: under an active
281
312
  // override every reader sees the override (A17), so waking subs would
282
313
  // re-show an unchanged view — the revert is the notification point.
283
- GlobalQueue.Oe !== null && GlobalQueue.Oe(e, r);
314
+ GlobalQueue.Ue?.(e, r);
284
315
  if (!hasActiveOverride(e)) {
285
316
  insertSubs(e);
317
+ } else if (e.T & CONFIG_AUTHORITATIVE_OBSERVED) {
318
+ // A17 silence is stated over ordinary readers; an authoritative-view
319
+ // reader (until()'s predicate) observed this node PAST its override
320
+ // and is waiting for exactly this staged truth. Without the wake the
321
+ // hold deadlocks: the landing waits on the transaction, the
322
+ // transaction on the action, the action on an until() that was never
323
+ // re-notified (#3164). Same selective wake as the equal-landing
324
+ // branch in recompute(). Optional call: the bit implies the
325
+ // optimistic engine WAS consulted, but the hook only installs with
326
+ // it — a bare-core build must not crash here.
327
+ GlobalQueue.he?.(e);
286
328
  }
287
329
  e.Te = clock;
288
- } else if (i) {
330
+ } else if (s) {
289
331
  // Route through lane's effect queue for independent flushing
290
- const n = e.Re;
332
+ const n = e.ge;
291
333
  const t = e.be;
292
- const o = e.Ue;
334
+ const o = e.pe;
293
335
  try {
294
- if (!n && l || !o || !o(r, t)) {
336
+ if (!n && i || !o || !o(r, t)) {
295
337
  e.be = r;
296
338
  e.Te = clock;
297
339
  // The latest() shadow write gives latest() effects independent lanes; the
298
340
  // _pendingSignal update is a no-op repeat of the clearStatus() call above
299
341
  // (computePendingState doesn't read _value).
300
- GlobalQueue.Oe !== null && GlobalQueue.Oe(e, r);
342
+ GlobalQueue.Ue?.(e, r);
301
343
  insertSubs(e, true);
302
344
  }
303
345
  } catch (n) {
@@ -321,8 +363,12 @@ function handleAsync(e, n, t) {
321
363
  // (`_pendingValue` set above or inside setSignal) is not — the verdict's
322
364
  // held-value branch is window-gated, and commitPendingNode closes the
323
365
  // window when the hold commits, so no one-frame isPending pulse can leak
324
- // to live observers between the landing and its commit (#2990).
325
- if (e.Pe === NOT_PENDING) e.Ie = false;
366
+ // to live observers between the landing and its commit (#2990). The
367
+ // quiet re-ask classification follows the same schedule (#3178).
368
+ if (e.Re === NOT_PENDING) {
369
+ e.Ne = false;
370
+ if (l) e.o.De = false;
371
+ }
326
372
  settlePendingSource(e);
327
373
  schedule();
328
374
  flush();
@@ -355,18 +401,23 @@ function handleAsync(e, n, t) {
355
401
  // meaningful only in the live posture.
356
402
  const consumeIterator = (t, r) => {
357
403
  const o = t[Symbol.asyncIterator]();
358
- let i = false;
359
- let u = false;
360
- let s = !r;
404
+ let l = false;
405
+ let s = false;
406
+ let u = !r;
361
407
  const close = () => {
362
- if (u) return;
363
- u = true;
408
+ if (s) return;
409
+ s = true;
364
410
  try {
365
411
  const e = o.return?.();
366
412
  if (isThenable(e)) e.then(undefined, () => {});
367
413
  } catch {}
368
414
  };
369
415
  r ? r(close) : cleanup(close);
416
+ // Flight-identity cancellation (#3122): the registration above is the
417
+ // owner-death backstop, but its disposal list can be zombie-deferred
418
+ // until the SUPERSEDING flight settles. The teardown slot fires at the
419
+ // _inFlight release sites so supersede stops this stream immediately.
420
+ ext(e).Ee = close;
370
421
  // Release check before each next pull: an unobserved lazy node must tear
371
422
  // down (its close above runs via disposal, closing the iterator) instead
372
423
  // of pumping the stream forever with zero subscribers (#2935).
@@ -392,18 +443,18 @@ function handleAsync(e, n, t) {
392
443
  // stash: it must write through the async path or the value is
393
444
  // silently dropped. (The deferred posture never has a caller, so
394
445
  // initialRead starts false there and everything writes through.)
395
- if (c && s) {
446
+ if (c && u) {
396
447
  t = r;
397
448
  f = true;
398
- if (r.done) u = true;
399
- } else if (e.o?.Ee !== n) {
449
+ if (r.done) s = true;
450
+ } else if (e.o?.Ie !== n) {
400
451
  return;
401
452
  } else if (!r.done) {
402
- i = true;
453
+ l = true;
403
454
  asyncWrite(r.value, iterateOrRelease);
404
455
  } else {
405
- u = true;
406
- if (i) {
456
+ s = true;
457
+ if (l) {
407
458
  schedule();
408
459
  flush();
409
460
  } else {
@@ -413,11 +464,11 @@ function handleAsync(e, n, t) {
413
464
  settleAutodispose();
414
465
  }
415
466
  }, t => {
416
- if (c && s) {
467
+ if (c && u) {
417
468
  r = t;
418
469
  a = true;
419
- } else if (e.o?.Ee === n) {
420
- u = true;
470
+ } else if (e.o?.Ie === n) {
471
+ s = true;
421
472
  handleError(t);
422
473
  settleAutodispose();
423
474
  }
@@ -425,27 +476,27 @@ function handleAsync(e, n, t) {
425
476
  c = false;
426
477
  if (a) {
427
478
  // Match the promise branch, but only rethrow during the initial read.
428
- u = true;
479
+ s = true;
429
480
  handleError(r);
430
- if (s) throw r;
481
+ if (u) throw r;
431
482
  return true;
432
483
  }
433
484
  if (f && !t.done) {
434
- l = t.value;
435
- i = true;
485
+ i = t.value;
486
+ l = true;
436
487
  return iterate();
437
488
  }
438
489
  return f && t.done;
439
490
  };
440
491
  const f = iterate();
441
492
  // Later iterate() calls run from asyncWrite, where rethrowing would be unhandled.
442
- s = false;
443
- return i || f;
493
+ u = false;
494
+ return l || f;
444
495
  };
445
496
  // Landed-synchronously verdict for a LIVE iterator drain; null when no live
446
497
  // drain ran (plain promise flight, or a deferred flatten). Drives the
447
498
  // shared NotReady/loading tail below.
448
- let i = null;
499
+ let l = null;
449
500
  // Flatten one async level: a thenable that RESOLVES to an AsyncIterable —
450
501
  // the shape every async stub returning a stream produces — consumes as the
451
502
  // stream itself rather than settling on the iterable object. One level
@@ -460,11 +511,11 @@ function handleAsync(e, n, t) {
460
511
  }
461
512
  if (!t) return false;
462
513
  const r = consumeIterator(e, n);
463
- if (!n) i = r;
514
+ if (!n) l = r;
464
515
  return true;
465
516
  };
466
517
  if (o) {
467
- let t = false, r = false, o, i = true;
518
+ let t = false, r = false, o, l = true;
468
519
  // Close registration for the flattening path. Consumption starts in a
469
520
  // microtask where the ambient owner is gone (or worse, someone else's),
470
521
  // so `cleanup()` can't be used — the close targets el's disposal list
@@ -476,18 +527,18 @@ function handleAsync(e, n, t) {
476
527
  // becomes disposal-bearing, which is exactly the class a directly
477
528
  // returned iterable already occupies.
478
529
  const registerDeferredClose = n => {
479
- if (!e.he) e.he = n; else if (Array.isArray(e.he)) e.he.push(n); else e.he = [ e.he, n ];
530
+ if (!e.Ge) e.Ge = n; else if (Array.isArray(e.Ge)) e.Ge.push(n); else e.Ge = [ e.Ge, n ];
480
531
  };
481
532
  n.then(r => {
482
- if (i) {
483
- l = r;
533
+ if (l) {
534
+ i = r;
484
535
  t = true;
485
- } else if (e.o?.Ee === n && !(e.ie & REACTIVE_DISPOSED) && flattenIfIterable(r, registerDeferredClose)) ; else {
536
+ } else if (e.o?.Ie === n && !(e.ie & REACTIVE_DISPOSED) && flattenIfIterable(r, registerDeferredClose)) ; else {
486
537
  asyncWrite(r);
487
538
  settleAutodispose();
488
539
  }
489
540
  }, e => {
490
- if (i) {
541
+ if (l) {
491
542
  o = e;
492
543
  r = true;
493
544
  } else {
@@ -495,7 +546,7 @@ function handleAsync(e, n, t) {
495
546
  settleAutodispose();
496
547
  }
497
548
  });
498
- i = false;
549
+ l = false;
499
550
  if (r) {
500
551
  // Settle through the same status path an async rejection uses, then
501
552
  // unwind the in-progress synchronous read so the errored node isn't
@@ -507,30 +558,30 @@ function handleAsync(e, n, t) {
507
558
  // is opened — first-flight work on a loadingValue node is loading-class
508
559
  // (invisible to boundaries and transitions); the flight itself is
509
560
  // already registered in _inFlight and lands through asyncWrite.
510
- if (e.Ie) return e.be;
561
+ if (e.Ne) return e.be;
511
562
  globalQueue.initTransition(resolveTransition(e));
512
563
  throw new NotReadyError(context);
513
- } else if (!flattenIfIterable(l)) {
564
+ } else if (!flattenIfIterable(i)) {
514
565
  // Synchronously-resolved promise: the first real answer landed.
515
- e.Ie = false;
566
+ e.Ne = false;
516
567
  }
517
568
  // A sync-resolved promise holding an AsyncIterable flattened LIVE (we
518
569
  // are still inside the synchronous read): full initial-drain semantics
519
570
  // apply and the shared tail below settles the verdict.
520
571
  }
521
572
  if (r) flattenIfIterable(n);
522
- if (i !== null) {
523
- if (!i) {
573
+ if (l !== null) {
574
+ if (!l) {
524
575
  // Loading window: serve commit #0 (see the promise branch above).
525
- if (e.Ie) return e.be;
576
+ if (e.Ne) return e.be;
526
577
  globalQueue.initTransition(resolveTransition(e));
527
578
  throw new NotReadyError(context);
528
579
  }
529
580
  // A sync first yield (or immediate empty completion) is the first real
530
581
  // answer; async yields clear inside asyncWrite.
531
- e.Ie = false;
582
+ e.Ne = false;
532
583
  }
533
- return l;
584
+ return i;
534
585
  }
535
586
 
536
587
  function clearStatus(e, n = false) {
@@ -539,13 +590,13 @@ function clearStatus(e, n = false) {
539
590
  // The pending window is over; its quiet classification dies with it.
540
591
  // (Unconditional: _reask is baked into the node literals, so this is a
541
592
  // plain store to an existing slot — no shape change.)
542
- if (e.o !== null) e.o.pe = false;
593
+ if (e.o !== null) e.o.De = false;
543
594
  e.S = n ? 0 : e.S & STATUS_UNINITIALIZED;
544
595
  if (e.o?._) setPendingError(e);
545
596
  // Update pending signal for isPending() reactivity (companions only exist
546
597
  // once the verdict layer created them, which installs the hooks).
547
- if (e.o?.Ge || e.o?.ge) GlobalQueue.de(e);
548
- if (e.o?.i && e.T & CONFIG_CHILD_COMPANIONS && GlobalQueue.ye !== null) GlobalQueue.ye(e);
598
+ if (e.o?.ye || e.o?.Ce) GlobalQueue.de(e);
599
+ if (e.o?.i && e.T & CONFIG_CHILD_COMPANIONS && GlobalQueue.me !== null) GlobalQueue.me(e);
549
600
  const t = statusNotifierOf(e);
550
601
  if (t) t.call(e);
551
602
  }
@@ -553,30 +604,30 @@ function clearStatus(e, n = false) {
553
604
  function notifyStatus(e, n, t, r, o) {
554
605
  // Wrap regular errors to track source node
555
606
  if (n === STATUS_ERROR && !(t instanceof StatusError) && !(t instanceof NotReadyError)) t = new StatusError(e, t);
556
- const l = n === STATUS_PENDING && t instanceof NotReadyError ? t.source : undefined;
557
- const i = l === e;
558
- const u = n === STATUS_PENDING && e.o?.De !== undefined && !i;
559
- const s = u && hasActiveOverride(e);
607
+ const i = n === STATUS_PENDING && t instanceof NotReadyError ? t.source : undefined;
608
+ const l = i === e;
609
+ const s = n === STATUS_PENDING && e.o?.Pe !== undefined && !l;
610
+ const u = s && hasActiveOverride(e);
560
611
  if (!r) {
561
- if (n === STATUS_PENDING && l) {
562
- addPendingSource(e, l);
612
+ if (n === STATUS_PENDING && i) {
613
+ addPendingSource(e, i);
563
614
  e.S = STATUS_PENDING | e.S & STATUS_UNINITIALIZED;
564
615
  // Preserve the current source on this propagation so render-effect notification
565
616
  // can register every distinct pending source with the transition.
566
- setPendingError(e, l, t);
617
+ setPendingError(e, i, t);
567
618
  } else {
568
619
  clearPendingSources(e);
569
620
  e.S = n | (n !== STATUS_ERROR ? e.S & STATUS_UNINITIALIZED : 0);
570
621
  ext(e)._ = t;
571
622
  }
572
- GlobalQueue.de !== null && GlobalQueue.de(e);
573
- if (e.o?.i && e.T & CONFIG_CHILD_COMPANIONS && GlobalQueue.ye !== null) GlobalQueue.ye(e);
623
+ GlobalQueue.de?.(e);
624
+ if (e.o?.i && e.T & CONFIG_CHILD_COMPANIONS && GlobalQueue.me !== null) GlobalQueue.me(e);
574
625
  }
575
626
  if (o && !r) {
576
627
  assignOrMergeLane(e, o);
577
628
  }
578
- const f = r || s;
579
- const a = r || u ? undefined : o;
629
+ const f = r || u;
630
+ const a = r || s ? undefined : o;
580
631
  const c = statusNotifierOf(e);
581
632
  if (c) {
582
633
  if (r && n === STATUS_PENDING) {
@@ -591,21 +642,21 @@ function notifyStatus(e, n, t, r, o) {
591
642
  }
592
643
  forEachDependent(e, (e, r) => {
593
644
  e.Te = clock;
594
- if (n === STATUS_PENDING && l && !e.o?.le?.has(l) || n !== STATUS_PENDING && (e.o?._ !== t || e.o?.le)) {
645
+ if (n === STATUS_PENDING && i && !e.o?.le?.has(i) || n !== STATUS_PENDING && (e.o?._ !== t || e.o?.le)) {
595
646
  // A pending-observer link is the subscription an `isPending` read created.
596
647
  // It exists so the observer re-runs when the source settles, but it must
597
648
  // not carry a real (non-NotReadyError) error — the synchronous `isPending`
598
649
  // read swallows those, and the async path must match. Re-run the observer
599
650
  // so `isPending` re-evaluates (to not-pending) instead of forwarding.
600
- if (r.me && n !== STATUS_PENDING && !(t instanceof NotReadyError)) {
651
+ if (r.ve && n !== STATUS_PENDING && !(t instanceof NotReadyError)) {
601
652
  enqueueSub(e);
602
653
  schedule();
603
654
  return;
604
655
  }
605
- if (!f && !e._e) queuePendingNode(e);
656
+ if (!f && !e.Ae) queuePendingNode(e);
606
657
  notifyStatus(e, n, t, f, a);
607
658
  }
608
659
  });
609
660
  }
610
661
 
611
- export { addPendingSource, clearStatus, forEachDependent, handleAsync, isThenable, notifyStatus, parkLoadingWindow, releaseSettledDependents, setPendingError, settleErroredDependents, settlePendingSource };
662
+ export { addPendingSource, clearStatus, forEachDependent, handleAsync, isThenable, notifyStatus, parkLoadingWindow, releaseFlightTeardown, releaseSettledDependents, setPendingError, settleErroredDependents, settlePendingSource };
@@ -83,6 +83,60 @@ const CONFIG_HAS_LANE = 1 << 10;
83
83
  * moved into the cold extension (§12), and an unconditional `_x` deref per
84
84
  * marked node measurably taxed the propagation hot path (diamond -22%). */ const CONFIG_FW_CHILDREN = 1 << 12;
85
85
 
86
+ /** Authoritative-view reader (`until()`): while this node computes, reads
87
+ * dodge active optimistic OVERRIDES only — the predicate must observe
88
+ * arriving truth, never the caller's own tentative writes (which would
89
+ * trivially satisfy it). Everything else reads normally, INCLUDING
90
+ * transition-staged `_pendingValue`: staged data is authoritative (optimism
91
+ * lives only in override slots), and a hold that refused staged reads would
92
+ * deadlock on data the open transaction itself is holding (a refresh the
93
+ * action issued lands staged and cannot commit until the hold releases).
94
+ * read() checks the bit on the reading computation (`context`) directly — no
95
+ * ambient flag — so a shared computed the predicate pulls recomputes as
96
+ * itself (no bit) under the normal view, and its cache never forks. */ const CONFIG_AUTHORITATIVE_READ = 1 << 13;
97
+
98
+ /** Sticky mark: an authoritative-view reader read this node PAST an active
99
+ * override. The ack shape — an authoritative arrival EQUAL to the override —
100
+ * rides paths that are deliberately silent under A17 (every ordinary reader
101
+ * sees the override, so an equal landing changes nothing for them). A marked
102
+ * node notifies those readers on such paths anyway, so the landed truth is
103
+ * seen without re-firing ordinary subscribers. Never cleared — only nodes an
104
+ * until() predicate observed mid-override pay. */ const CONFIG_AUTHORITATIVE_OBSERVED = 1 << 14;
105
+
106
+ /** Promise-delivery effect (resolve()/until()): commits its computed value
107
+ * directly even when recomputing under its own held transition. These
108
+ * effects deliver applies on a microtask (#2930) instead of the stashed
109
+ * effect queues, so the value must ride the same immediate schedule — a
110
+ * staged value with an immediate apply delivers stale state (resolve) or
111
+ * deadlocks the hold (until). Safe because the node is a private leaf: no
112
+ * subscriber reads an effect's value, only its own apply does. */ const CONFIG_DIRECT_COMMIT = 1 << 15;
113
+
114
+ /** Fresh-pull reader (awaitable `refresh()`'s waiter effect): a read of a
115
+ * dirty source recomputes it inline even when the height gate defers to the
116
+ * flush. Closes the same-flush ordering race where a waiter created
117
+ * alongside a refresh() mark read the PRE-re-ask value as settled and
118
+ * delivered stale; with the pull, the waiter either parks on the re-ask's
119
+ * pending window (async — woken by the settle walk, which runs on every
120
+ * landing including equal-value ones) or serves its sync answer. resolve()
121
+ * deliberately keeps that race — its contract is "first settled value"
122
+ * (#2930), not "next quiescent state". */ const CONFIG_FRESH_READ = 1 << 16;
123
+
124
+ /** HELD truth (#3164): this node's staged `_pendingValue` is confirming
125
+ * truth riding a transaction that retains optimism, revealed only at that
126
+ * transaction's settle. Two arming sites, one meaning: the store fold
127
+ * (a landing staged into the retaining transaction) and until()'s
128
+ * flip-entanglement (a foreign carrier's staged write, stolen when it
129
+ * flipped the awaited predicate truthy). Until the reveal, ordinary
130
+ * readers — lane and speculative recomputes included — keep committed:
131
+ * the staging notified subscribers as a plain write, so without the mask
132
+ * a mid-hold recompute composes live optimism with the confirming truth,
133
+ * a frame no timeline contains (GabbeV's union tear). Authoritative
134
+ * readers (until()'s predicate) and latest() tunnel through — the
135
+ * exemption that keeps holds deadlock-free. Override-covered nodes never
136
+ * arm: the override is their display and its revert their notification
137
+ * (A17). Cleared at commit (the commit IS the reveal); subscribers masked
138
+ * during the hold are woken by finalizePureQueue's post-revert pass. */ const CONFIG_HELD_TRUTH = 1 << 17;
139
+
86
140
  const STATUS_PENDING = 1 << 0;
87
141
 
88
142
  const STATUS_ERROR = 1 << 1;
@@ -127,4 +181,4 @@ const defaultContext = {};
127
181
  * @internal
128
182
  */ const $REFRESH = Symbol("refresh");
129
183
 
130
- export { $REFRESH, CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, CONFIG_CHILD_COMPANIONS, CONFIG_FW_CHILDREN, CONFIG_HAS_COMPANIONS, CONFIG_HAS_LANE, CONFIG_HAS_SNAPSHOT, CONFIG_IN_SNAPSHOT_SCOPE, CONFIG_NO_SNAPSHOT, CONFIG_OPTIMISTIC, CONFIG_OWNED_WRITE, CONFIG_SYNC, CONFIG_TRANSPARENT, EFFECT_RENDER, EFFECT_TRACKED, EFFECT_USER, NOT_PENDING, NO_SNAPSHOT, OVERRIDE_UNDEFINED, REACTIVE_CHECK, REACTIVE_DIRTY, REACTIVE_DISPOSED, REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, REACTIVE_LAZY, REACTIVE_MANUAL_WRITE, REACTIVE_MISSED_WAKE, REACTIVE_NONE, REACTIVE_OPTIMISTIC_DIRTY, REACTIVE_REASK, REACTIVE_RECOMPUTING_DEPS, REACTIVE_SNAPSHOT_STALE, REACTIVE_ZOMBIE, STATUS_ERROR, STATUS_PENDING, STATUS_UNINITIALIZED, STORE_SNAPSHOT_PROPS, SUPPORTS_PROXY, defaultContext, unwrapOverride };
184
+ export { $REFRESH, CONFIG_AUTHORITATIVE_OBSERVED, CONFIG_AUTHORITATIVE_READ, CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, CONFIG_CHILD_COMPANIONS, CONFIG_DIRECT_COMMIT, CONFIG_FRESH_READ, CONFIG_FW_CHILDREN, CONFIG_HAS_COMPANIONS, CONFIG_HAS_LANE, CONFIG_HAS_SNAPSHOT, CONFIG_HELD_TRUTH, CONFIG_IN_SNAPSHOT_SCOPE, CONFIG_NO_SNAPSHOT, CONFIG_OPTIMISTIC, CONFIG_OWNED_WRITE, CONFIG_SYNC, CONFIG_TRANSPARENT, EFFECT_RENDER, EFFECT_TRACKED, EFFECT_USER, NOT_PENDING, NO_SNAPSHOT, OVERRIDE_UNDEFINED, REACTIVE_CHECK, REACTIVE_DIRTY, REACTIVE_DISPOSED, REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, REACTIVE_LAZY, REACTIVE_MANUAL_WRITE, REACTIVE_MISSED_WAKE, REACTIVE_NONE, REACTIVE_OPTIMISTIC_DIRTY, REACTIVE_REASK, REACTIVE_RECOMPUTING_DEPS, REACTIVE_SNAPSHOT_STALE, REACTIVE_ZOMBIE, STATUS_ERROR, STATUS_PENDING, STATUS_UNINITIALIZED, STORE_SNAPSHOT_PROPS, SUPPORTS_PROXY, defaultContext, unwrapOverride };