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

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 (121) hide show
  1. package/dist/dev-shared.js +2059 -3768
  2. package/dist/dev.attribution.js +237 -101
  3. package/dist/dev.js +7466 -8497
  4. package/dist/observe/affects.js +128 -104
  5. package/dist/observe/boundaries.js +492 -665
  6. package/dist/observe/core/action.js +63 -66
  7. package/dist/observe/core/async.js +243 -379
  8. package/dist/observe/core/attribution-queries.js +1 -1
  9. package/dist/observe/core/attribution.js +394 -253
  10. package/dist/observe/core/constants.js +120 -187
  11. package/dist/observe/core/context.js +3 -3
  12. package/dist/observe/core/core.js +874 -1440
  13. package/dist/observe/core/dev.js +3 -3
  14. package/dist/observe/core/effect.js +73 -84
  15. package/dist/observe/core/external.js +5 -5
  16. package/dist/observe/core/graph.js +62 -71
  17. package/dist/observe/core/heap.js +75 -98
  18. package/dist/observe/core/invariants.js +1 -34
  19. package/dist/observe/core/lanes.js +750 -181
  20. package/dist/observe/core/owner.js +80 -101
  21. package/dist/observe/core/scheduler.js +719 -1356
  22. package/dist/observe/core/verdict.js +314 -621
  23. package/dist/observe/flatten.js +79 -0
  24. package/dist/observe/index.js +15 -13
  25. package/dist/observe/map.js +232 -235
  26. package/dist/observe/reveal.js +212 -0
  27. package/dist/observe/signals.js +144 -216
  28. package/dist/observe/store/affects.js +142 -0
  29. package/dist/observe/store/hooks.js +13 -0
  30. package/dist/observe/store/index.js +27 -24
  31. package/dist/observe/store/optimistic.js +571 -0
  32. package/dist/observe/store/projection.js +308 -0
  33. package/dist/observe/store/reconcile.js +259 -0
  34. package/dist/observe/store/store.js +1988 -277
  35. package/dist/observe/store/storePath.js +1 -1
  36. package/dist/{prod/store/next → observe/store}/target.js +17 -18
  37. package/dist/observe/store/types.js +73 -0
  38. package/dist/observe/store/utils.js +1 -13
  39. package/dist/prod/affects.js +130 -106
  40. package/dist/prod/boundaries.js +476 -654
  41. package/dist/prod/core/action.js +71 -74
  42. package/dist/prod/core/async.js +306 -444
  43. package/dist/prod/core/constants.js +117 -186
  44. package/dist/prod/core/context.js +3 -3
  45. package/dist/prod/core/core.js +888 -1462
  46. package/dist/prod/core/effect.js +88 -99
  47. package/dist/prod/core/external.js +5 -5
  48. package/dist/prod/core/graph.js +62 -71
  49. package/dist/prod/core/heap.js +75 -98
  50. package/dist/prod/core/invariants.js +1 -32
  51. package/dist/prod/core/lanes.js +712 -181
  52. package/dist/prod/core/owner.js +75 -96
  53. package/dist/prod/core/scheduler.js +686 -1335
  54. package/dist/prod/core/verdict.js +309 -621
  55. package/dist/prod/flatten.js +77 -0
  56. package/dist/prod/index.js +15 -13
  57. package/dist/prod/map.js +142 -145
  58. package/dist/prod/reveal.js +212 -0
  59. package/dist/prod/signals.js +155 -227
  60. package/dist/prod/store/affects.js +142 -0
  61. package/dist/prod/store/hooks.js +13 -0
  62. package/dist/prod/store/index.js +26 -19
  63. package/dist/prod/store/optimistic.js +566 -0
  64. package/dist/prod/store/projection.js +301 -0
  65. package/dist/prod/store/reconcile.js +259 -0
  66. package/dist/prod/store/store.js +1883 -272
  67. package/dist/prod/store/storePath.js +1 -1
  68. package/dist/{observe/store/next → prod/store}/target.js +13 -17
  69. package/dist/prod/store/types.js +73 -0
  70. package/dist/prod/store/utils.js +1 -11
  71. package/dist/types/affects.d.ts +11 -30
  72. package/dist/types/boundaries.d.ts +66 -176
  73. package/dist/types/carved.d.ts +1 -0
  74. package/dist/types/core/action.d.ts +5 -6
  75. package/dist/types/core/async.d.ts +10 -5
  76. package/dist/types/core/attribution-hooks.d.ts +20 -1
  77. package/dist/types/core/attribution.d.ts +26 -11
  78. package/dist/types/core/constants.d.ts +130 -180
  79. package/dist/types/core/core.d.ts +134 -264
  80. package/dist/types/core/dev.d.ts +2 -2
  81. package/dist/types/core/effect.d.ts +0 -7
  82. package/dist/types/core/graph.d.ts +7 -1
  83. package/dist/types/core/heap.d.ts +5 -7
  84. package/dist/types/core/index.d.ts +5 -5
  85. package/dist/types/core/invariants.d.ts +0 -29
  86. package/dist/types/core/lanes.d.ts +74 -86
  87. package/dist/types/core/owner.d.ts +3 -3
  88. package/dist/types/core/scheduler.d.ts +255 -331
  89. package/dist/types/core/types.d.ts +45 -85
  90. package/dist/types/core/verdict.d.ts +3 -0
  91. package/dist/types/flatten.d.ts +8 -0
  92. package/dist/types/index.d.ts +6 -4
  93. package/dist/types/reveal.d.ts +19 -0
  94. package/dist/types/signals.d.ts +35 -91
  95. package/dist/types/store/affects.d.ts +12 -0
  96. package/dist/types/store/hooks.d.ts +48 -0
  97. package/dist/types/store/index.d.ts +34 -15
  98. package/dist/types/store/optimistic.d.ts +23 -0
  99. package/dist/types/store/projection.d.ts +18 -0
  100. package/dist/types/store/reconcile.d.ts +3 -0
  101. package/dist/types/store/store.d.ts +132 -145
  102. package/dist/types/store/storePath.d.ts +1 -1
  103. package/dist/types/store/target.d.ts +151 -0
  104. package/dist/types/store/types.d.ts +84 -0
  105. package/package.json +1 -1
  106. package/dist/observe/core/optimistic.js +0 -669
  107. package/dist/observe/store/next/optimistic.js +0 -717
  108. package/dist/observe/store/next/projection.js +0 -266
  109. package/dist/observe/store/next/reconcile.js +0 -375
  110. package/dist/observe/store/next/store.js +0 -2608
  111. package/dist/prod/core/optimistic.js +0 -658
  112. package/dist/prod/store/next/optimistic.js +0 -710
  113. package/dist/prod/store/next/projection.js +0 -257
  114. package/dist/prod/store/next/reconcile.js +0 -373
  115. package/dist/prod/store/next/store.js +0 -2484
  116. package/dist/types/core/optimistic.d.ts +0 -6
  117. package/dist/types/store/next/optimistic.d.ts +0 -25
  118. package/dist/types/store/next/projection.d.ts +0 -27
  119. package/dist/types/store/next/reconcile.d.ts +0 -10
  120. package/dist/types/store/next/store.d.ts +0 -145
  121. package/dist/types/store/next/target.d.ts +0 -196
@@ -1,20 +1,18 @@
1
- import { TimeoutError } from "./core/error.js";
1
+ import { TimeoutError, NotReadyError, unwrapStatusError } from "./core/error.js";
2
2
 
3
- import { computed, optimisticComputed, setSignal, optimisticSignal, runWithOwner, setMemo, signal, installAuthoritativeRead, markRefresh, read, untrack } from "./core/core.js";
4
-
5
- import { cleanup, createRoot, getOwner, dispose, getObserver } from "./core/owner.js";
3
+ import "./core/verdict.js";
6
4
 
7
- import { globalQueue, dirtyQueue, Queue, entangleConfirmingTransitions, activeTransition } from "./core/scheduler.js";
5
+ import { CONFIG_CHILDREN_FORBIDDEN, EFFECT_USER, $REFRESH, CONFIG_AUTO_DISPOSE, CONFIG_AUTHORITATIVE, CONFIG_REDERIVE, REACTIVE_JOINED, CONFIG_FRESH_READ, CONFIG_GUESS, STATUS_PENDING, STATUS_ERROR } from "./core/constants.js";
8
6
 
9
- import { CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, EFFECT_USER, $REFRESH, CONFIG_DIRECT_COMMIT, CONFIG_AUTHORITATIVE_READ, CONFIG_FRESH_READ } from "./core/constants.js";
7
+ import { computed, signal, runWithOwner, setMemo, setSignal, markRefresh, read, untrack } from "./core/core.js";
10
8
 
11
- import "./core/invariants.js";
9
+ import { globalQueue, flushTransaction, dirtyQueue, GlobalQueue, resolveTx, passTx, staleReader, joinFuture } from "./core/scheduler.js";
12
10
 
13
- import "./core/verdict.js";
11
+ import { cleanup, createRoot, getOwner, dispose } from "./core/owner.js";
14
12
 
15
13
  import { effect, trackedEffect } from "./core/effect.js";
16
14
 
17
- import { installOptimisticEngine } from "./core/optimistic.js";
15
+ import { optimisticWrite, truthRead, truthConfirmed } from "./core/lanes.js";
18
16
 
19
17
  /**
20
18
  * Low-level reactive-cleanup primitive. Registers a callback that runs when
@@ -72,12 +70,22 @@ function accessor(e) {
72
70
 
73
71
  function createSignal(e, t) {
74
72
  if (typeof e === "function") {
75
- const n = computed(e, t);
76
- n.C &= ~CONFIG_AUTO_DISPOSE;
77
- return [ accessor(n), setMemo.bind(null, n) ];
73
+ const r = computed(e, t);
74
+ r.i &= ~CONFIG_AUTO_DISPOSE;
75
+ return [ accessor(r), setMemo.bind(null, r) ];
78
76
  }
79
- const n = signal(e, t);
80
- return [ accessor(n), setSignal.bind(null, n) ];
77
+ const r = signal(e, t);
78
+ return [ accessor(r), setSignal.bind(null, r) ];
79
+ }
80
+
81
+ function createOptimistic(e, t) {
82
+ if (typeof e === "function") {
83
+ const r = computed(e, t);
84
+ r.i &= ~CONFIG_AUTO_DISPOSE;
85
+ return [ accessor(r), optimisticWrite.bind(null, r) ];
86
+ }
87
+ const r = signal(e, t);
88
+ return [ accessor(r), optimisticWrite.bind(null, r) ];
81
89
  }
82
90
 
83
91
  function createMemo(e, t) {
@@ -151,10 +159,10 @@ function createMemo(e, t) {
151
159
  * ```
152
160
  *
153
161
  * @description https://docs.solidjs.com/reference/basic-reactivity/create-effect
154
- */ function createEffect(e, t, n) {
162
+ */ function createEffect(e, t, r) {
155
163
  effect(e, t.effect || t, t.error, {
156
164
  user: true,
157
- ...n
165
+ ...r
158
166
  });
159
167
  }
160
168
 
@@ -186,8 +194,8 @@ function createMemo(e, t) {
186
194
  * ```
187
195
  *
188
196
  * @description https://docs.solidjs.com/reference/secondary-primitives/create-render-effect
189
- */ function createRenderEffect(e, t, n) {
190
- effect(e, t, undefined, n);
197
+ */ function createRenderEffect(e, t, r) {
198
+ effect(e, t, undefined, r);
191
199
  }
192
200
 
193
201
  /**
@@ -264,27 +272,27 @@ function createMemo(e, t) {
264
272
  *
265
273
  * @description https://docs.solidjs.com/reference/secondary-primitives/create-reaction
266
274
  */ function createReaction(e, t) {
267
- let n = undefined;
268
- cleanup(() => n?.());
269
- const r = getOwner();
275
+ let r = undefined;
276
+ cleanup(() => r?.());
277
+ const n = getOwner();
270
278
  // The currently armed effect node. `track()` replaces the previous
271
279
  // subscription (1.x semantics): without disposing the superseded arm, its
272
280
  // sources stayed live (firing the callback for replaced dependencies), each
273
281
  // accumulated arm delivered its own fire, and un-fired arms leaked as live
274
282
  // effect nodes until the owner disposed (#2861).
275
- let i;
276
- return o => {
277
- if (i) {
278
- dispose(i);
279
- i = undefined;
283
+ let o;
284
+ return c => {
285
+ if (o) {
286
+ dispose(o);
287
+ o = undefined;
280
288
  }
281
- runWithOwner(r, () => {
282
- effect(() => (o(), i = getOwner()), t => {
283
- i = undefined;
284
- n?.();
285
- const r = (e.effect || e)?.();
286
- if (false && r !== undefined && typeof r !== "function") ;
287
- n = r;
289
+ runWithOwner(n, () => {
290
+ effect(() => (c(), o = getOwner()), t => {
291
+ o = undefined;
292
+ r?.();
293
+ const n = (e.effect || e)?.();
294
+ if (false && n !== undefined && typeof n !== "function") ;
295
+ r = n;
288
296
  dispose(t);
289
297
  }, e.error, {
290
298
  ...t,
@@ -295,10 +303,52 @@ function createMemo(e, t) {
295
303
  };
296
304
  }
297
305
 
298
- /** Delivers effect applies on a microtask instead of queueing them (#2930). */ class MicrotaskQueue extends Queue {
299
- enqueue(e, t) {
300
- queueMicrotask(() => t(e));
306
+ /** A promise over a reactive expression (`resolve`, `until`): a pass that
307
+ * runs the expression and settles on a microtask. Not an effect — an
308
+ * effect's run is the flush's effect phase, and a flush parked by the
309
+ * action that is `yield`ing on this promise stashes its runs with the
310
+ * transaction (#2930); the heap runs a pass parked or not. A pending read
311
+ * re-runs it when the source lands (the pass is pending itself); the
312
+ * expression throwing settles the rejection. */
313
+ /** The promise-delivery readers' pass (`resolve`, `until`, `refresh`'s
314
+ * waiter): delivers what it read on a microtask. By posture (#3482, #3490):
315
+ * - the reader of the transaction it was created in (`own`: an action's
316
+ * body, a `refresh()` called there — the waiter reads its slice's frame on
317
+ * a later microtask) delivers that frame — the staged landing an action
318
+ * holds (the action awaits the promise that would otherwise wait on its
319
+ * own settle);
320
+ * - a mainline reader whose pass read a frame another transaction holds
321
+ * joined it (L2, born held — A29) and delivers at that commit: the
322
+ * committed view, never a foreign hold's unrevealed frame. */ function watch(e, t, r, n = 0, o, c = flushTransaction) {
323
+ if (n & CONFIG_AUTHORITATIVE) {
324
+ GlobalQueue.En = truthRead;
325
+ GlobalQueue.un = truthConfirmed;
301
326
  }
327
+ // `config` is on the node for its first pass too (the pass runs inside
328
+ // `computed()`): `until`'s predicate is authoritative from its first read,
329
+ // or an already-displayed guess would satisfy it in a later slice. (REDERIVE
330
+ // after: the first pass's own error is the promise's rejection.)
331
+ computed(() => {
332
+ let n;
333
+ try {
334
+ n = e();
335
+ } catch (e) {
336
+ if (e instanceof NotReadyError) throw e;
337
+ queueMicrotask(() => r(unwrapStatusError(e)));
338
+ return;
339
+ }
340
+ const u = getOwner();
341
+ // (Outside a flush a mainline pass joins through `passTx`.)
342
+ const i = flushTransaction ?? passTx;
343
+ if (u.R & REACTIVE_JOINED && i !== null && (c === null || resolveTx(c) !== resolveTx(i))) {
344
+ staleReader(u, resolveTx(i));
345
+ return;
346
+ }
347
+ o?.(n);
348
+ queueMicrotask(() => t(n));
349
+ }, {
350
+ Me: n
351
+ }).i |= CONFIG_REDERIVE;
302
352
  }
303
353
 
304
354
  /**
@@ -322,42 +372,8 @@ function createMemo(e, t) {
322
372
  *
323
373
  * @param fn a reactive expression to resolve
324
374
  */ function resolve(e) {
325
- return new Promise((t, n) => {
326
- createRoot(r => {
327
- // Deliver effect applies on a microtask instead of the owner queue: an
328
- // incomplete transition stashes its effect queues until it settles, but
329
- // an action yielding this promise is itself what keeps the transition
330
- // open — the stashed res() deadlocked the action (#2930). The compute
331
- // still runs in place (under the transaction's view when created inside
332
- // an action step), and status/boundary notifications keep their normal
333
- // route through the inherited queue.
334
- const i = getOwner();
335
- const o = new MicrotaskQueue;
336
- o._parent = i.T;
337
- // notify() forwards up the normal chain
338
- i.T = o;
339
- // A user effect rather than a bare computed: computeds are pull-based and
340
- // are only re-enqueued when a pending source *resolves* — a rejection just
341
- // marks them errored, so nothing would re-run and the promise would never
342
- // settle (#2842). The effect's error channel is notified on rejection.
343
- effect(e, e => {
344
- t(e);
345
- r();
346
- }, e => {
347
- // The error arm already unwraps StatusError (#2840) — `err` is the
348
- // user's original error, matching what error boundaries expose.
349
- n(e);
350
- r();
351
- },
352
- // DIRECT_COMMIT: a source settling INTO the held transaction (e.g. a
353
- // refresh this action issued) stages its landing; the effect's own
354
- // recompute must not stage too, or the microtask apply reads the
355
- // stale mainline value and resolves with old data.
356
- {
357
- user: true,
358
- yn: CONFIG_DIRECT_COMMIT
359
- });
360
- });
375
+ return new Promise((t, r) => {
376
+ createRoot(n => watch(e, e => (t(e), n()), e => (r(e), n())));
361
377
  });
362
378
  }
363
379
 
@@ -377,19 +393,10 @@ function createMemo(e, t) {
377
393
  * read:
378
394
  * - Accessor targets resolve with the settled value; store targets resolve
379
395
  * with the store node passed (reads through it are fresh after the await).
380
- * - A failed re-ask rejects with the error (inside an action's generator,
381
- * `yield refresh(x)` throws back at the yield point and the action reverts
382
- * like any other failure).
396
+ * - A failed re-ask rejects with the error.
383
397
  * - Semantics are quiescence, not flight identity: if another refresh (or
384
398
  * any invalidation) supersedes this one mid-flight, the promise waits for
385
399
  * — and delivers — whatever finally lands.
386
- * - Inside an action, truth landing into the held transaction is STAGED;
387
- * the promise still settles then (matching `resolve()`/`until()`, #2930)
388
- * and delivers the staged value — the caller's own optimistic override is
389
- * never the delivered value.
390
- * - The re-ask itself stays verdict-quiet exactly as before: `isPending`
391
- * does not flip for a bare refresh (pair with `affects()` for a visible
392
- * pending window).
393
400
  *
394
401
  * @example
395
402
  * ```ts
@@ -416,53 +423,40 @@ function createMemo(e, t) {
416
423
  // no-ops, so N waiters cost one pull; this also closes the race where a
417
424
  // waiter reads the PRE-re-ask value as settled and delivers stale), after
418
425
  // which the read either parks on the re-ask's pending window (async — the
419
- // settle walk re-runs it on every landing, equal-value and
420
- // staged-under-hold included, and a rejection arrives through the effect's
421
- // error channel) or serves the sync answer. AUTHORITATIVE_READ keeps an
422
- // action's own optimistic override out of the delivered value. resolve()'s
423
- // own eager compute is untouched: created after a refresh it still settles
424
- // stale-while-revalidate (#2930) — its contract is "first settled value",
425
- // not "next quiescent state".
426
-
427
- // An authoritative reader is woken through a late-bound hook when the truth
428
- // lands EQUAL to a standing override (the A17-silent path). Every setter of
429
- // that reader bit must install it — until() does, and this waiter is the
430
- // other one (#3303: refresh of an optimistic in an app that never called
431
- // until() dereferenced the null hook).
432
- installAuthoritativeRead();
433
- markRefresh(t);
434
- const n = new Promise((n, r) => {
426
+ // settle walk re-runs it on every landing, equal-value included, and a
427
+ // rejection arrives through the effect's error channel) or serves the sync
428
+ // answer. resolve()'s own eager compute is untouched: created after a
429
+ // refresh it still settles stale-while-revalidate (#2930) — its contract is
430
+ // "first settled value", not "next quiescent state".
431
+ markRefresh(t);
432
+ // The transaction `refresh()` is called in (an action's slice): the
433
+ // waiter, created on the microtask below, is its reader (`watch`).
434
+ const r = flushTransaction;
435
+ const n = new Promise((n, o) => {
435
436
  queueMicrotask(() => {
436
- // No createRoot: the microtask has no ambient owner, so the effect is
437
- // naturally detached, and settle disposes the node directly — the root
438
- // added ~560B of otherwise-shakeable machinery for nothing but the
439
- // dev-mode NO_OWNER_EFFECT warning, so dev keeps a root husk purely to
440
- // stay quiet. The waiter swaps in its microtask queue during its own
441
- // first compute (before the initial apply enqueue), replacing the
442
- // root-owner plumbing.
443
- // Typed as the effect node, not Owner: the capture runs inside the
444
- // effect's own compute, where the ambient owner IS the effect —
445
- // exactly what dispose() takes.
446
- let i = null;
447
- const make = () => effect(() => {
448
- if (i === null) {
449
- i = getOwner();
450
- const e = new MicrotaskQueue;
451
- e._parent = i.T;
452
- i.T = e;
453
- }
454
- return read(t);
437
+ // The root is load-bearing: an ownerless computed is auto-disposing,
438
+ // and the settle walk releases an unobserved one instead of re-running
439
+ // it, so the waiter would never see the refetch land (#3888). Settle
440
+ // disposes the waiter directly; the capture runs inside the watcher's
441
+ // own pass, where the ambient owner IS the watcher — exactly what
442
+ // dispose() takes.
443
+ let c = null;
444
+ const make = () => watch(() => {
445
+ if (c === null) c = getOwner();
446
+ // Authoritative: the truth beneath a guess (`until`'s view). A
447
+ // guessed node's own re-ask is not answered by it (#3895): park
448
+ // on that flight, or reject with its error.
449
+ const e = read(t);
450
+ if (t.i & CONFIG_GUESS && t.S & (STATUS_PENDING | STATUS_ERROR)) throw t.m?.F;
451
+ return e;
455
452
  }, t => {
456
453
  n(typeof e === "function" ? t : e);
457
- dispose(i);
454
+ dispose(c);
458
455
  }, e => {
459
- r(e);
460
- dispose(i);
461
- }, {
462
- user: true,
463
- yn: CONFIG_DIRECT_COMMIT | CONFIG_AUTHORITATIVE_READ | CONFIG_FRESH_READ
464
- });
465
- make();
456
+ o(e);
457
+ dispose(c);
458
+ }, CONFIG_FRESH_READ | CONFIG_AUTHORITATIVE, undefined, r);
459
+ createRoot(make);
466
460
  });
467
461
  });
468
462
  // Fire-and-forget refresh must not turn a failed refetch into an unhandled
@@ -480,115 +474,63 @@ function createMemo(e, t) {
480
474
  *
481
475
  * Where {@link resolve} answers "what is this value" (first settled value,
482
476
  * whatever it is), `until` answers "when does the world confirm this
483
- * condition". The difference matters inside an `action()`: `yield until(...)`
484
- * holds the action's transaction — and any optimistic state riding it — open
485
- * until the condition is independently true.
486
- *
487
- * To make that sound, `until`'s predicate reads the AUTHORITATIVE view — and
488
- * this is the one read-semantics difference from `resolve`, which reads the
489
- * normal (transaction's own) view where overrides are visible:
490
- *
491
- * - **Optimistic overrides are invisible** to the predicate. Your own
492
- * tentative write can never satisfy your own ack, even on the
493
- * single-primitive shape where the optimistic store IS the live-fed store.
494
- * (Derived computeds serve their normal cached values — express the
495
- * condition over sources of truth, not derived views of the overlay.)
496
- * - **Everything else reads normally, including uncommitted transition-staged
497
- * data.** Real data is real wherever it currently lives. This is
498
- * load-bearing, not a loophole: truth that arrives *into* the open
499
- * transaction (a `refresh()` this action issued, an entangled landing)
500
- * stages and cannot commit until the hold releases — a predicate that
501
- * refused staged reads would deadlock on the very data it is waiting for.
477
+ * condition".
502
478
  *
503
479
  * This is the acknowledgment mechanism for mutations confirmed on a live data
504
480
  * channel (sockets, subscriptions, live queries) rather than by the mutation's
505
481
  * own response: correlate by a client-generated id or version in the predicate,
506
482
  * and let truth arrive however it arrives — push, refetch, or another tab.
507
483
  *
508
- * Failure composes with action semantics: a rejection is thrown back into the
509
- * generator at the `yield` point — catchable there, or the action fails and
510
- * its optimistic state reverts.
511
- *
512
484
  * Must be called *outside* a tracking scope.
513
485
  *
514
- * Inside an action, call it from a step: after an `await`, put a bare `yield`
515
- * before `yield until(...)`. The runtime cannot hook an async generator's
516
- * `await` continuation, so the `until(...)` expression — which CREATES the
517
- * predicate's reader — would otherwise run outside the transaction; created
518
- * there it is born held (A29) and replays only at the commit its own promise
519
- * holds open (#3482). See {@link action}.
520
- *
521
486
  * @example
522
487
  * ```ts
523
- * const send = action(async function* (text: string) {
524
- * const clientId = crypto.randomUUID();
525
- * setMessages(m => { m.push({ clientId, text, pending: true }); }); // optimistic
526
- * await socket.send({ clientId, text }); // fire-and-forget transport
527
- * yield; // re-enter the transaction after the await
528
- * // Hold until the live source echoes the write (authoritative view —
529
- * // the optimistic row above cannot satisfy this):
530
- * yield until(() => messages.some(m => m.clientId === clientId), { timeout: 10_000 });
531
- * });
488
+ * const clientId = crypto.randomUUID();
489
+ * await socket.send({ clientId, text }); // fire-and-forget transport
490
+ * // Wait until the live source echoes the write:
491
+ * await until(() => messages.some(m => m.clientId === clientId), { timeout: 10_000 });
532
492
  * ```
533
493
  *
534
- * @param fn a reactive predicate over authoritative state
494
+ * @param fn a reactive predicate
535
495
  * @param options optional `timeout` (ms) and abort `signal`
536
496
  */ function until(e, t) {
537
- // Late-bind the wakeup hook for the A17-silent ack paths (pay-for-use:
538
- // apps that never call until() never retain it).
539
- installAuthoritativeRead();
540
- // Flip-entanglement (#3164 follow-up): the transaction this until() holds
541
- // open (the action's, when yielded from one). The predicate is the user's
542
- // declaration of what confirms it — when a foreign transition's staged
543
- // write flips it truthy, that transition merges here and reveals at the
544
- // joint settle instead of painting the confirmation under live optimism.
545
- const n = activeTransition;
546
- return new Promise((r, i) => {
497
+ return new Promise((r, n) => {
547
498
  const o = t?.signal;
548
- if (o?.aborted) return i(o.reason);
549
- createRoot(c => {
550
- // Same delivery contract as resolve() (#2930): effect applies ride a
551
- // microtask so the promise can settle while the transaction the caller
552
- // yielded it into is still open — that transaction being open is the
553
- // entire point of the hold.
554
- const u = getOwner();
555
- const s = new MicrotaskQueue;
556
- s._parent = u.T;
557
- u.T = s;
558
- let f;
559
- let a;
499
+ if (o?.aborted) return n(o.reason);
500
+ // The awaiting action's transaction, if any (its body runs joined to
501
+ // it). `until()` is a declaration of relatedness (#3164 follow-up): the
502
+ // predicate names the condition that confirms the action, so the frame
503
+ // whose landing flips it truthy is the confirming event by the user's
504
+ // own definition — it reveals WITH the action's settle, not before
505
+ // (`saving=true` beside the confirmed `version=1` is a tear no timeline
506
+ // contains). Updates that do not flip the predicate reveal freely.
507
+ const c = flushTransaction;
508
+ createRoot(u => {
509
+ let i;
510
+ let s;
560
511
  const settle = e => {
561
- if (f !== undefined) clearTimeout(f);
562
- if (a !== undefined) o.removeEventListener("abort", a);
512
+ if (i !== undefined) clearTimeout(i);
513
+ if (s !== undefined) o.removeEventListener("abort", s);
563
514
  e();
564
- c();
515
+ u();
565
516
  };
566
- effect(n === null ? e : () => {
567
- const t = e();
568
- // Runs inside the compute (pure phase): the confirming
569
- // transition's stamps are live and its commit hasn't run, so
570
- // the merge lands before any reveal. Falsy evaluations skip —
571
- // non-flipping updates were never named as the confirmation.
572
- if (t) entangleConfirmingTransitions(getObserver(), n);
573
- return t;
574
- }, e => {
517
+ // Authoritative: the predicate sees the truth, never the action's own
518
+ // optimistic guess — a guess cannot satisfy the wait for its own
519
+ // confirmation.
520
+ watch(e, e => {
575
521
  // Falsy is "not yet": keep the subscription live and wait for the
576
522
  // next evaluation. Only a truthy settled value resolves.
577
523
  if (e) settle(() => r(e));
578
- }, e => settle(() => i(e)),
579
- // AUTHORITATIVE_READ: overrides invisible to the predicate.
580
- // DIRECT_COMMIT: truth that stages into the held transaction (a
581
- // refresh the action issued) must flow through to the microtask
582
- // apply — a staged effect value would deadlock the hold on data
583
- // the hold itself is keeping uncommitted.
584
- {
585
- user: true,
586
- yn: CONFIG_AUTHORITATIVE_READ | CONFIG_DIRECT_COMMIT
524
+ }, e => settle(() => n(e)), CONFIG_AUTHORITATIVE, e => {
525
+ // The flip, inside the pass of the flush carrying it: the flush
526
+ // joins the action's transaction, and what it staged is held and
527
+ // revealed with the action.
528
+ if (e && c !== null && globalQueue.X && resolveTx(c).tt !== 0) joinFuture(c);
587
529
  });
588
- if (t?.timeout !== undefined) f = setTimeout(() => settle(() => i(new TimeoutError)), t.timeout);
530
+ if (t?.timeout !== undefined) i = setTimeout(() => settle(() => n(new TimeoutError)), t.timeout);
589
531
  if (o !== undefined) {
590
- a = () => settle(() => i(o.reason));
591
- o.addEventListener("abort", a, {
532
+ s = () => settle(() => n(o.reason));
533
+ o.addEventListener("abort", s, {
592
534
  once: true
593
535
  });
594
536
  }
@@ -596,20 +538,6 @@ function createMemo(e, t) {
596
538
  });
597
539
  }
598
540
 
599
- function createOptimistic(e, t) {
600
- // Install before the node exists: only engine-installed programs can carry
601
- // an _overrideValue slot (same runtime-install pattern as
602
- // GlobalQueue._clearOptimisticStore in createOptimisticStore).
603
- installOptimisticEngine();
604
- if (typeof e === "function") {
605
- const n = optimisticComputed(e, t);
606
- n.C &= ~CONFIG_AUTO_DISPOSE;
607
- return [ accessor(n), setSignal.bind(null, n) ];
608
- }
609
- const n = optimisticSignal(e, t);
610
- return [ accessor(n), setSignal.bind(null, n) ];
611
- }
612
-
613
541
  /**
614
542
  * Schedules `callback` to run **once** after the reactive graph has fully
615
543
  * settled — i.e. once every pending async read inside the current owner has
@@ -705,7 +633,7 @@ function createOptimistic(e, t) {
705
633
  * on owner disposal
706
634
  */ function onSettled(e) {
707
635
  const t = getOwner();
708
- t && !(t.C & CONFIG_CHILDREN_FORBIDDEN) ? trackedEffect(() => untrack(e), undefined) : globalQueue.enqueue(EFFECT_USER, function fire() {
636
+ t && !(t.i & CONFIG_CHILDREN_FORBIDDEN) ? trackedEffect(() => untrack(e), undefined) : globalQueue.enqueue(EFFECT_USER, function fire() {
709
637
  // Settled means derived. A settle that reverts optimism (or replays
710
638
  // gated reads) only enqueues the affected subscribers; the pass after
711
639
  // the commit re-derives them. Fired in the commit pass, the callback
@@ -713,7 +641,7 @@ function createOptimistic(e, t) {
713
641
  // still holding the optimistic value — reads do not pull (#3411). Fall
714
642
  // to the next pass while the heap has work; `run` swapped the queue,
715
643
  // so this lands there, and `enqueue` keeps the drain alive.
716
- if (dirtyQueue.EE >= dirtyQueue.ln) return globalQueue.enqueue(EFFECT_USER, fire);
644
+ if (dirtyQueue.ft >= dirtyQueue.Ue) return globalQueue.enqueue(EFFECT_USER, fire);
717
645
  // Unowned, out-of-band fire (no owner, or a children-forbidden one this
718
646
  // one-shot must not bind to): a returned cleanup has no lifecycle to
719
647
  // attach to. Reject it in dev; in production the return is simply