@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,22 +1,20 @@
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";
6
-
7
- import { globalQueue, dirtyQueue, Queue, entangleConfirmingTransitions, activeTransition } from "./core/scheduler.js";
3
+ import "./core/verdict.js";
8
4
 
9
- import { CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, EFFECT_USER, $REFRESH, CONFIG_DIRECT_COMMIT, CONFIG_AUTHORITATIVE_READ, CONFIG_FRESH_READ } from "./core/constants.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";
10
6
 
11
- import "./core/invariants.js";
7
+ import { computed, signal, runWithOwner, setMemo, setSignal, markRefresh, read, untrack } from "./core/core.js";
12
8
 
13
9
  import { registerGraph } from "./core/dev.js";
14
10
 
15
- import "./core/verdict.js";
11
+ import { globalQueue, flushTransaction, dirtyQueue, GlobalQueue, resolveTx, passTx, staleReader, joinFuture } from "./core/scheduler.js";
12
+
13
+ import { cleanup, createRoot, getOwner, dispose } from "./core/owner.js";
16
14
 
17
15
  import { effect, trackedEffect } from "./core/effect.js";
18
16
 
19
- import { installOptimisticEngine } from "./core/optimistic.js";
17
+ import { optimisticWrite, truthRead, truthConfirmed } from "./core/lanes.js";
20
18
 
21
19
  /**
22
20
  * Low-level reactive-cleanup primitive. Registers a callback that runs when
@@ -75,7 +73,7 @@ function accessor(e) {
75
73
  function createSignal(e, t) {
76
74
  if (typeof e === "function") {
77
75
  const r = computed(e, t);
78
- r.C &= ~CONFIG_AUTO_DISPOSE;
76
+ r.i &= ~CONFIG_AUTO_DISPOSE;
79
77
  return [ accessor(r), setMemo.bind(null, r) ];
80
78
  }
81
79
  const r = signal(e, t);
@@ -83,6 +81,17 @@ function createSignal(e, t) {
83
81
  return [ accessor(r), setSignal.bind(null, r) ];
84
82
  }
85
83
 
84
+ function createOptimistic(e, t) {
85
+ if (typeof e === "function") {
86
+ const r = computed(e, t);
87
+ r.i &= ~CONFIG_AUTO_DISPOSE;
88
+ return [ accessor(r), optimisticWrite.bind(null, r) ];
89
+ }
90
+ const r = signal(e, t);
91
+ registerGraph(r, getOwner());
92
+ return [ accessor(r), optimisticWrite.bind(null, r) ];
93
+ }
94
+
86
95
  function createMemo(e, t) {
87
96
  return accessor(computed(e, t));
88
97
  }
@@ -275,15 +284,15 @@ function createMemo(e, t) {
275
284
  // sources stayed live (firing the callback for replaced dependencies), each
276
285
  // accumulated arm delivered its own fire, and un-fired arms leaked as live
277
286
  // effect nodes until the owner disposed (#2861).
278
- let i;
279
- return o => {
280
- if (i) {
281
- dispose(i);
282
- i = undefined;
287
+ let o;
288
+ return c => {
289
+ if (o) {
290
+ dispose(o);
291
+ o = undefined;
283
292
  }
284
293
  runWithOwner(n, () => {
285
- effect(() => (o(), i = getOwner()), t => {
286
- i = undefined;
294
+ effect(() => (c(), o = getOwner()), t => {
295
+ o = undefined;
287
296
  r?.();
288
297
  const n = (e.effect || e)?.();
289
298
  if (false && n !== undefined && typeof n !== "function") ;
@@ -298,10 +307,52 @@ function createMemo(e, t) {
298
307
  };
299
308
  }
300
309
 
301
- /** Delivers effect applies on a microtask instead of queueing them (#2930). */ class MicrotaskQueue extends Queue {
302
- enqueue(e, t) {
303
- queueMicrotask(() => t(e));
310
+ /** A promise over a reactive expression (`resolve`, `until`): a pass that
311
+ * runs the expression and settles on a microtask. Not an effect — an
312
+ * effect's run is the flush's effect phase, and a flush parked by the
313
+ * action that is `yield`ing on this promise stashes its runs with the
314
+ * transaction (#2930); the heap runs a pass parked or not. A pending read
315
+ * re-runs it when the source lands (the pass is pending itself); the
316
+ * expression throwing settles the rejection. */
317
+ /** The promise-delivery readers' pass (`resolve`, `until`, `refresh`'s
318
+ * waiter): delivers what it read on a microtask. By posture (#3482, #3490):
319
+ * - the reader of the transaction it was created in (`own`: an action's
320
+ * body, a `refresh()` called there — the waiter reads its slice's frame on
321
+ * a later microtask) delivers that frame — the staged landing an action
322
+ * holds (the action awaits the promise that would otherwise wait on its
323
+ * own settle);
324
+ * - a mainline reader whose pass read a frame another transaction holds
325
+ * joined it (L2, born held — A29) and delivers at that commit: the
326
+ * committed view, never a foreign hold's unrevealed frame. */ function watch(e, t, r, n = 0, o, c = flushTransaction) {
327
+ if (n & CONFIG_AUTHORITATIVE) {
328
+ GlobalQueue.Nn = truthRead;
329
+ GlobalQueue.rn = truthConfirmed;
304
330
  }
331
+ // `config` is on the node for its first pass too (the pass runs inside
332
+ // `computed()`): `until`'s predicate is authoritative from its first read,
333
+ // or an already-displayed guess would satisfy it in a later slice. (REDERIVE
334
+ // after: the first pass's own error is the promise's rejection.)
335
+ computed(() => {
336
+ let n;
337
+ try {
338
+ n = e();
339
+ } catch (e) {
340
+ if (e instanceof NotReadyError) throw e;
341
+ queueMicrotask(() => r(unwrapStatusError(e)));
342
+ return;
343
+ }
344
+ const u = getOwner();
345
+ // (Outside a flush a mainline pass joins through `passTx`.)
346
+ const i = flushTransaction ?? passTx;
347
+ if (u.R & REACTIVE_JOINED && i !== null && (c === null || resolveTx(c) !== resolveTx(i))) {
348
+ staleReader(u, resolveTx(i));
349
+ return;
350
+ }
351
+ o?.(n);
352
+ queueMicrotask(() => t(n));
353
+ }, {
354
+ Ct: n
355
+ }).i |= CONFIG_REDERIVE;
305
356
  }
306
357
 
307
358
  /**
@@ -326,41 +377,7 @@ function createMemo(e, t) {
326
377
  * @param fn a reactive expression to resolve
327
378
  */ function resolve(e) {
328
379
  return new Promise((t, r) => {
329
- createRoot(n => {
330
- // Deliver effect applies on a microtask instead of the owner queue: an
331
- // incomplete transition stashes its effect queues until it settles, but
332
- // an action yielding this promise is itself what keeps the transition
333
- // open — the stashed res() deadlocked the action (#2930). The compute
334
- // still runs in place (under the transaction's view when created inside
335
- // an action step), and status/boundary notifications keep their normal
336
- // route through the inherited queue.
337
- const i = getOwner();
338
- const o = new MicrotaskQueue;
339
- o._parent = i.T;
340
- // notify() forwards up the normal chain
341
- i.T = o;
342
- // A user effect rather than a bare computed: computeds are pull-based and
343
- // are only re-enqueued when a pending source *resolves* — a rejection just
344
- // marks them errored, so nothing would re-run and the promise would never
345
- // settle (#2842). The effect's error channel is notified on rejection.
346
- effect(e, e => {
347
- t(e);
348
- n();
349
- }, e => {
350
- // The error arm already unwraps StatusError (#2840) — `err` is the
351
- // user's original error, matching what error boundaries expose.
352
- r(e);
353
- n();
354
- },
355
- // DIRECT_COMMIT: a source settling INTO the held transaction (e.g. a
356
- // refresh this action issued) stages its landing; the effect's own
357
- // recompute must not stage too, or the microtask apply reads the
358
- // stale mainline value and resolves with old data.
359
- {
360
- user: true,
361
- Qt: CONFIG_DIRECT_COMMIT
362
- });
363
- });
380
+ createRoot(n => watch(e, e => (t(e), n()), e => (r(e), n())));
364
381
  });
365
382
  }
366
383
 
@@ -380,19 +397,10 @@ function createMemo(e, t) {
380
397
  * read:
381
398
  * - Accessor targets resolve with the settled value; store targets resolve
382
399
  * with the store node passed (reads through it are fresh after the await).
383
- * - A failed re-ask rejects with the error (inside an action's generator,
384
- * `yield refresh(x)` throws back at the yield point and the action reverts
385
- * like any other failure).
400
+ * - A failed re-ask rejects with the error.
386
401
  * - Semantics are quiescence, not flight identity: if another refresh (or
387
402
  * any invalidation) supersedes this one mid-flight, the promise waits for
388
403
  * — and delivers — whatever finally lands.
389
- * - Inside an action, truth landing into the held transaction is STAGED;
390
- * the promise still settles then (matching `resolve()`/`until()`, #2930)
391
- * and delivers the staged value — the caller's own optimistic override is
392
- * never the delivered value.
393
- * - The re-ask itself stays verdict-quiet exactly as before: `isPending`
394
- * does not flip for a bare refresh (pair with `affects()` for a visible
395
- * pending window).
396
404
  *
397
405
  * @example
398
406
  * ```ts
@@ -419,59 +427,46 @@ function createMemo(e, t) {
419
427
  // no-ops, so N waiters cost one pull; this also closes the race where a
420
428
  // waiter reads the PRE-re-ask value as settled and delivers stale), after
421
429
  // which the read either parks on the re-ask's pending window (async — the
422
- // settle walk re-runs it on every landing, equal-value and
423
- // staged-under-hold included, and a rejection arrives through the effect's
424
- // error channel) or serves the sync answer. AUTHORITATIVE_READ keeps an
425
- // action's own optimistic override out of the delivered value. resolve()'s
426
- // own eager compute is untouched: created after a refresh it still settles
427
- // stale-while-revalidate (#2930) — its contract is "first settled value",
428
- // not "next quiescent state".
429
-
430
- // An authoritative reader is woken through a late-bound hook when the truth
431
- // lands EQUAL to a standing override (the A17-silent path). Every setter of
432
- // that reader bit must install it — until() does, and this waiter is the
433
- // other one (#3303: refresh of an optimistic in an app that never called
434
- // until() dereferenced the null hook).
435
- installAuthoritativeRead();
436
- markRefresh(t);
437
- const r = new Promise((r, n) => {
430
+ // settle walk re-runs it on every landing, equal-value included, and a
431
+ // rejection arrives through the effect's error channel) or serves the sync
432
+ // answer. resolve()'s own eager compute is untouched: created after a
433
+ // refresh it still settles stale-while-revalidate (#2930) — its contract is
434
+ // "first settled value", not "next quiescent state".
435
+ markRefresh(t);
436
+ // The transaction `refresh()` is called in (an action's slice): the
437
+ // waiter, created on the microtask below, is its reader (`watch`).
438
+ const r = flushTransaction;
439
+ const n = new Promise((n, o) => {
438
440
  queueMicrotask(() => {
439
- // No createRoot: the microtask has no ambient owner, so the effect is
440
- // naturally detached, and settle disposes the node directly — the root
441
- // added ~560B of otherwise-shakeable machinery for nothing but the
442
- // dev-mode NO_OWNER_EFFECT warning, so dev keeps a root husk purely to
443
- // stay quiet. The waiter swaps in its microtask queue during its own
444
- // first compute (before the initial apply enqueue), replacing the
445
- // root-owner plumbing.
446
- // Typed as the effect node, not Owner: the capture runs inside the
447
- // effect's own compute, where the ambient owner IS the effect —
448
- // exactly what dispose() takes.
449
- let i = null;
450
- const make = () => effect(() => {
451
- if (i === null) {
452
- i = getOwner();
453
- const e = new MicrotaskQueue;
454
- e._parent = i.T;
455
- i.T = e;
456
- }
457
- return read(t);
441
+ // The root is load-bearing: an ownerless computed is auto-disposing,
442
+ // and the settle walk releases an unobserved one instead of re-running
443
+ // it, so the waiter would never see the refetch land (#3888). Settle
444
+ // disposes the waiter directly; the capture runs inside the watcher's
445
+ // own pass, where the ambient owner IS the watcher — exactly what
446
+ // dispose() takes.
447
+ let c = null;
448
+ const make = () => watch(() => {
449
+ if (c === null) c = getOwner();
450
+ // Authoritative: the truth beneath a guess (`until`'s view). A
451
+ // guessed node's own re-ask is not answered by it (#3895): park
452
+ // on that flight, or reject with its error.
453
+ const e = read(t);
454
+ if (t.i & CONFIG_GUESS && t.S & (STATUS_PENDING | STATUS_ERROR)) throw t.m?.F;
455
+ return e;
458
456
  }, t => {
459
- r(typeof e === "function" ? t : e);
460
- dispose(i);
457
+ n(typeof e === "function" ? t : e);
458
+ dispose(c);
461
459
  }, e => {
462
- n(e);
463
- dispose(i);
464
- }, {
465
- user: true,
466
- Qt: CONFIG_DIRECT_COMMIT | CONFIG_AUTHORITATIVE_READ | CONFIG_FRESH_READ
467
- });
468
- make();
460
+ o(e);
461
+ dispose(c);
462
+ }, CONFIG_FRESH_READ | CONFIG_AUTHORITATIVE, undefined, r);
463
+ createRoot(make);
469
464
  });
470
465
  });
471
466
  // Fire-and-forget refresh must not turn a failed refetch into an unhandled
472
467
  // rejection; awaiting callers attach their own handlers to `promise`.
473
- r.catch(() => {});
474
- return r;
468
+ n.catch(() => {});
469
+ return n;
475
470
  }
476
471
 
477
472
  /**
@@ -483,115 +478,63 @@ function createMemo(e, t) {
483
478
  *
484
479
  * Where {@link resolve} answers "what is this value" (first settled value,
485
480
  * whatever it is), `until` answers "when does the world confirm this
486
- * condition". The difference matters inside an `action()`: `yield until(...)`
487
- * holds the action's transaction — and any optimistic state riding it — open
488
- * until the condition is independently true.
489
- *
490
- * To make that sound, `until`'s predicate reads the AUTHORITATIVE view — and
491
- * this is the one read-semantics difference from `resolve`, which reads the
492
- * normal (transaction's own) view where overrides are visible:
493
- *
494
- * - **Optimistic overrides are invisible** to the predicate. Your own
495
- * tentative write can never satisfy your own ack, even on the
496
- * single-primitive shape where the optimistic store IS the live-fed store.
497
- * (Derived computeds serve their normal cached values — express the
498
- * condition over sources of truth, not derived views of the overlay.)
499
- * - **Everything else reads normally, including uncommitted transition-staged
500
- * data.** Real data is real wherever it currently lives. This is
501
- * load-bearing, not a loophole: truth that arrives *into* the open
502
- * transaction (a `refresh()` this action issued, an entangled landing)
503
- * stages and cannot commit until the hold releases — a predicate that
504
- * refused staged reads would deadlock on the very data it is waiting for.
481
+ * condition".
505
482
  *
506
483
  * This is the acknowledgment mechanism for mutations confirmed on a live data
507
484
  * channel (sockets, subscriptions, live queries) rather than by the mutation's
508
485
  * own response: correlate by a client-generated id or version in the predicate,
509
486
  * and let truth arrive however it arrives — push, refetch, or another tab.
510
487
  *
511
- * Failure composes with action semantics: a rejection is thrown back into the
512
- * generator at the `yield` point — catchable there, or the action fails and
513
- * its optimistic state reverts.
514
- *
515
488
  * Must be called *outside* a tracking scope.
516
489
  *
517
- * Inside an action, call it from a step: after an `await`, put a bare `yield`
518
- * before `yield until(...)`. The runtime cannot hook an async generator's
519
- * `await` continuation, so the `until(...)` expression — which CREATES the
520
- * predicate's reader — would otherwise run outside the transaction; created
521
- * there it is born held (A29) and replays only at the commit its own promise
522
- * holds open (#3482). See {@link action}.
523
- *
524
490
  * @example
525
491
  * ```ts
526
- * const send = action(async function* (text: string) {
527
- * const clientId = crypto.randomUUID();
528
- * setMessages(m => { m.push({ clientId, text, pending: true }); }); // optimistic
529
- * await socket.send({ clientId, text }); // fire-and-forget transport
530
- * yield; // re-enter the transaction after the await
531
- * // Hold until the live source echoes the write (authoritative view —
532
- * // the optimistic row above cannot satisfy this):
533
- * yield until(() => messages.some(m => m.clientId === clientId), { timeout: 10_000 });
534
- * });
492
+ * const clientId = crypto.randomUUID();
493
+ * await socket.send({ clientId, text }); // fire-and-forget transport
494
+ * // Wait until the live source echoes the write:
495
+ * await until(() => messages.some(m => m.clientId === clientId), { timeout: 10_000 });
535
496
  * ```
536
497
  *
537
- * @param fn a reactive predicate over authoritative state
498
+ * @param fn a reactive predicate
538
499
  * @param options optional `timeout` (ms) and abort `signal`
539
500
  */ function until(e, t) {
540
- // Late-bind the wakeup hook for the A17-silent ack paths (pay-for-use:
541
- // apps that never call until() never retain it).
542
- installAuthoritativeRead();
543
- // Flip-entanglement (#3164 follow-up): the transaction this until() holds
544
- // open (the action's, when yielded from one). The predicate is the user's
545
- // declaration of what confirms it — when a foreign transition's staged
546
- // write flips it truthy, that transition merges here and reveals at the
547
- // joint settle instead of painting the confirmation under live optimism.
548
- const r = activeTransition;
549
- return new Promise((n, i) => {
501
+ return new Promise((r, n) => {
550
502
  const o = t?.signal;
551
- if (o?.aborted) return i(o.reason);
552
- createRoot(c => {
553
- // Same delivery contract as resolve() (#2930): effect applies ride a
554
- // microtask so the promise can settle while the transaction the caller
555
- // yielded it into is still open — that transaction being open is the
556
- // entire point of the hold.
557
- const u = getOwner();
558
- const s = new MicrotaskQueue;
559
- s._parent = u.T;
560
- u.T = s;
561
- let f;
562
- let a;
503
+ if (o?.aborted) return n(o.reason);
504
+ // The awaiting action's transaction, if any (its body runs joined to
505
+ // it). `until()` is a declaration of relatedness (#3164 follow-up): the
506
+ // predicate names the condition that confirms the action, so the frame
507
+ // whose landing flips it truthy is the confirming event by the user's
508
+ // own definition — it reveals WITH the action's settle, not before
509
+ // (`saving=true` beside the confirmed `version=1` is a tear no timeline
510
+ // contains). Updates that do not flip the predicate reveal freely.
511
+ const c = flushTransaction;
512
+ createRoot(u => {
513
+ let i;
514
+ let s;
563
515
  const settle = e => {
564
- if (f !== undefined) clearTimeout(f);
565
- if (a !== undefined) o.removeEventListener("abort", a);
516
+ if (i !== undefined) clearTimeout(i);
517
+ if (s !== undefined) o.removeEventListener("abort", s);
566
518
  e();
567
- c();
519
+ u();
568
520
  };
569
- effect(r === null ? e : () => {
570
- const t = e();
571
- // Runs inside the compute (pure phase): the confirming
572
- // transition's stamps are live and its commit hasn't run, so
573
- // the merge lands before any reveal. Falsy evaluations skip —
574
- // non-flipping updates were never named as the confirmation.
575
- if (t) entangleConfirmingTransitions(getObserver(), r);
576
- return t;
577
- }, e => {
521
+ // Authoritative: the predicate sees the truth, never the action's own
522
+ // optimistic guess — a guess cannot satisfy the wait for its own
523
+ // confirmation.
524
+ watch(e, e => {
578
525
  // Falsy is "not yet": keep the subscription live and wait for the
579
526
  // next evaluation. Only a truthy settled value resolves.
580
- if (e) settle(() => n(e));
581
- }, e => settle(() => i(e)),
582
- // AUTHORITATIVE_READ: overrides invisible to the predicate.
583
- // DIRECT_COMMIT: truth that stages into the held transaction (a
584
- // refresh the action issued) must flow through to the microtask
585
- // apply — a staged effect value would deadlock the hold on data
586
- // the hold itself is keeping uncommitted.
587
- {
588
- user: true,
589
- Qt: CONFIG_AUTHORITATIVE_READ | CONFIG_DIRECT_COMMIT
527
+ if (e) settle(() => r(e));
528
+ }, e => settle(() => n(e)), CONFIG_AUTHORITATIVE, e => {
529
+ // The flip, inside the pass of the flush carrying it: the flush
530
+ // joins the action's transaction, and what it staged is held and
531
+ // revealed with the action.
532
+ if (e && c !== null && globalQueue.X && resolveTx(c).tt !== 0) joinFuture(c);
590
533
  });
591
- if (t?.timeout !== undefined) f = setTimeout(() => settle(() => i(new TimeoutError)), t.timeout);
534
+ if (t?.timeout !== undefined) i = setTimeout(() => settle(() => n(new TimeoutError)), t.timeout);
592
535
  if (o !== undefined) {
593
- a = () => settle(() => i(o.reason));
594
- o.addEventListener("abort", a, {
536
+ s = () => settle(() => n(o.reason));
537
+ o.addEventListener("abort", s, {
595
538
  once: true
596
539
  });
597
540
  }
@@ -599,21 +542,6 @@ function createMemo(e, t) {
599
542
  });
600
543
  }
601
544
 
602
- function createOptimistic(e, t) {
603
- // Install before the node exists: only engine-installed programs can carry
604
- // an _overrideValue slot (same runtime-install pattern as
605
- // GlobalQueue._clearOptimisticStore in createOptimisticStore).
606
- installOptimisticEngine();
607
- if (typeof e === "function") {
608
- const r = optimisticComputed(e, t);
609
- r.C &= ~CONFIG_AUTO_DISPOSE;
610
- return [ accessor(r), setSignal.bind(null, r) ];
611
- }
612
- const r = optimisticSignal(e, t);
613
- registerGraph(r, getOwner());
614
- return [ accessor(r), setSignal.bind(null, r) ];
615
- }
616
-
617
545
  /**
618
546
  * Schedules `callback` to run **once** after the reactive graph has fully
619
547
  * settled — i.e. once every pending async read inside the current owner has
@@ -709,7 +637,7 @@ function createOptimistic(e, t) {
709
637
  * on owner disposal
710
638
  */ function onSettled(e) {
711
639
  const t = getOwner();
712
- t && !(t.C & CONFIG_CHILDREN_FORBIDDEN) ? trackedEffect(() => untrack(e), {
640
+ t && !(t.i & CONFIG_CHILDREN_FORBIDDEN) ? trackedEffect(() => untrack(e), {
713
641
  name: "onSettled"
714
642
  }) : globalQueue.enqueue(EFFECT_USER, function fire() {
715
643
  // Settled means derived. A settle that reverts optimism (or replays
@@ -719,7 +647,7 @@ function createOptimistic(e, t) {
719
647
  // still holding the optimistic value — reads do not pull (#3411). Fall
720
648
  // to the next pass while the heap has work; `run` swapped the queue,
721
649
  // so this lands there, and `enqueue` keeps the drain alive.
722
- if (dirtyQueue.EE >= dirtyQueue.st) return globalQueue.enqueue(EFFECT_USER, fire);
650
+ if (dirtyQueue.Kt >= dirtyQueue.ut) return globalQueue.enqueue(EFFECT_USER, fire);
723
651
  // Unowned, out-of-band fire (no owner, or a children-forbidden one this
724
652
  // one-shot must not bind to): a returned cleanup has no lifecycle to
725
653
  // attach to. Reject it in dev; in production the return is simply
@@ -0,0 +1,142 @@
1
+ import { GlobalQueue } from "../core/scheduler.js";
2
+
3
+ import { optHooks, installAffectsHooks } from "./hooks.js";
4
+
5
+ import { lookupTarget, $OWNER } from "./target.js";
6
+
7
+ import { $TARGET } from "./types.js";
8
+
9
+ /**
10
+ * Store — the store half of `affects()` (plan sec. 29, plan sec. 37; A24 (4), #2882,
11
+ * #2893, #2904).
12
+ *
13
+ * A mark on a store is a mark on nodes (affects.ts: a count on the node, a
14
+ * dependency walk at probe time): `affects(record, key)` marks the slot's
15
+ * leaf; `affects(record)` marks a carrier (the record's `$AFFECTS` leaf)
16
+ * and every live node in the record's subtree — the edges existing readers
17
+ * subscribed through. Coverage is by RAW IDENTITY, not read path (#2882: a
18
+ * captured row proxy, a derived store sharing the source's raw, never read
19
+ * through the declaration's proxy): the identities reachable at the
20
+ * declaration are the mark's `scope`, so a node born inside the window on
21
+ * a covered record inherits the mark (released with the carrier's last),
22
+ * and an untracked probe through a record with no nodes is witnessed into
23
+ * the verdict. A mark declared on a draft (inside a setter) or over an
24
+ * optimistic family walks the view the writer sees.
25
+ *
26
+ * Imported by `affects()`, which installs it on its first store target
27
+ * (#3891, after #3912): a program with stores that never declares a mark
28
+ * carries none of it. It never imports store.ts — the engine's top level —
29
+ * so a program declaring marks without stores carries this module alone:
30
+ * the store's nodes and its wrappability test come through `GlobalQueue`
31
+ * (`_storeNode`, `_storeWrappable`), and node birth and the untracked traps
32
+ * reach back through `affectsHooks` (hooks.ts).
33
+ */
34
+ /** The keyless mark's carrier key: a leaf of the record no read serves. */ const $AFFECTS = Symbol(0);
35
+
36
+ const scopes = new Map;
37
+
38
+ function scopeOf(e, o) {
39
+ let n = scopes.get(e);
40
+ if (n === undefined) scopes.set(e, n = {
41
+ eo: new Set,
42
+ oo: [],
43
+ bt: o
44
+ });
45
+ return n;
46
+ }
47
+
48
+ /** The nodes an `affects(store[, key])` declaration marks (affects.ts
49
+ * registers each). */ function storeMarks(e, o) {
50
+ const n = GlobalQueue.no(e, o === undefined ? $AFFECTS : o);
51
+ const t = scopeOf(n, o);
52
+ if (o === undefined) {
53
+ const o = [ n ];
54
+ walk(e.px, t, o, e.fam, new Set);
55
+ return o;
56
+ }
57
+ t.eo.add(e.v);
58
+ if (e.pb !== null) t.eo.add(e.pb);
59
+ return [ n ];
60
+ }
61
+
62
+ /** The identities reachable from `value` into the scope (the draft's and
63
+ * the committed backing both, when a draft is open; an optimistic family's
64
+ * view), every live node under each record into `found`. Untracked by
65
+ * construction: raws, never traps. */ function walk(e, o, n, t, s) {
66
+ if (!GlobalQueue.so(e)) return;
67
+ const f = e[$TARGET] ?? lookupTarget(e, t);
68
+ let r = f !== undefined ? f.pb ?? f.v : e;
69
+ if (s.has(r)) return;
70
+ s.add(r);
71
+ o.eo.add(r);
72
+ if (f !== undefined) {
73
+ if (f.pb !== null) o.eo.add(f.v);
74
+ // The writer's view: the tick's own unflushed guesses are in motion too.
75
+ if (f.fam?.opt === true) r = optHooks.view(f, r, true);
76
+ const e = f.n;
77
+ if (e !== null) for (const o of Reflect.ownKeys(e))
78
+ // Another mark's carrier is its own channel: counting it here would
79
+ // extend that scope's lifetime to this declaration's.
80
+ if (o !== $AFFECTS) n.push(e[o]);
81
+ const s = f.h;
82
+ if (s !== null) for (const e of Reflect.ownKeys(s)) n.push(s[e]);
83
+ if (f.k !== null) n.push(f.k);
84
+ if (f.dk !== null) n.push(f.dk);
85
+ t = f.fam ?? t;
86
+ }
87
+ for (const e of Reflect.ownKeys(r)) if (e !== $OWNER) {
88
+ const f = Object.getOwnPropertyDescriptor(r, e);
89
+ if (f.get === undefined) walk(f.value, o, n, t, s);
90
+ }
91
+ }
92
+
93
+ /** A live scope covering `t`'s identity (keyed: for `key`). Chained
94
+ * backings (§7b): a view's backing is another store's proxy — marks cover
95
+ * the BASE raw, so every identity along the chain is checked. */ function covering(e, o, n) {
96
+ for (const [t, s] of scopes) {
97
+ if (t === n || s.bt !== undefined && s.bt !== o) continue;
98
+ for (let o = e.v; ;) {
99
+ if (s.eo.has(o)) return t;
100
+ const e = o?.[$TARGET];
101
+ if (e === undefined) break;
102
+ const n = e.pb ?? e.v;
103
+ if (n === o) break;
104
+ o = n;
105
+ }
106
+ }
107
+ return null;
108
+ }
109
+
110
+ /** The store's side: node birth and the untracked traps call it (store.ts,
111
+ * through `affectsHooks`) — with no live mark too: an empty scope map is
112
+ * no work. */ const hooks = {
113
+ // A node born on a record a live mark covers inherits it (released with
114
+ // the carrier's last registration).
115
+ fo(e, o, n) {
116
+ if (scopes.size === 0 || n === $AFFECTS) return;
117
+ const t = covering(e, n, o);
118
+ if (!t) return;
119
+ GlobalQueue.O(o);
120
+ scopes.get(t).oo.push(o);
121
+ },
122
+ // An untracked probe (`isPending(() => s.x)` with no observer) reading a
123
+ // record a live mark covers: no node carries the mark for it — the
124
+ // verdict is told directly.
125
+ ro(e, o) {
126
+ if (e.n?.[$AFFECTS]?.m?.T || covering(e, o)) GlobalQueue.Bt();
127
+ }
128
+ };
129
+
130
+ /** `affects()` calls it before a store-targeted declaration (idempotent). */ function installStoreAffects() {
131
+ installAffectsHooks(hooks);
132
+ }
133
+
134
+ /** The carrier's last mark released (affects.ts): its scope dies, and the
135
+ * nodes that inherited the mark release theirs. */ function releaseMarkScope(e) {
136
+ const o = scopes.get(e);
137
+ if (o === undefined) return;
138
+ scopes.delete(e);
139
+ GlobalQueue.k(o.oo);
140
+ }
141
+
142
+ export { installStoreAffects, releaseMarkScope, storeMarks };
@@ -0,0 +1,13 @@
1
+ let optHooks = null;
2
+
3
+ function installOptHooks(o) {
4
+ optHooks = o;
5
+ }
6
+
7
+ let affectsHooks = null;
8
+
9
+ function installAffectsHooks(o) {
10
+ affectsHooks = o;
11
+ }
12
+
13
+ export { affectsHooks, installAffectsHooks, installOptHooks, optHooks };