@solidjs/signals 2.0.0-rc.2 → 2.0.0-rc.20

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 (198) hide show
  1. package/dist/dev-shared.js +6505 -0
  2. package/dist/dev.attribution.js +3484 -0
  3. package/dist/dev.js +10227 -9508
  4. package/dist/observe/affects-marks.js +86 -0
  5. package/dist/observe/affects.js +112 -0
  6. package/dist/observe/attribution.js +7 -0
  7. package/dist/observe/boundaries.js +696 -0
  8. package/dist/observe/core/action.js +182 -0
  9. package/dist/observe/core/async.js +824 -0
  10. package/dist/observe/core/attribution-costs.js +66 -0
  11. package/dist/observe/core/attribution-feedback.js +280 -0
  12. package/dist/observe/core/attribution-hooks.js +83 -0
  13. package/dist/observe/core/attribution-queries.js +43 -0
  14. package/dist/observe/core/attribution.js +3079 -0
  15. package/dist/observe/core/constants.js +269 -0
  16. package/dist/observe/core/context.js +63 -0
  17. package/dist/observe/core/copies.js +10 -0
  18. package/dist/observe/core/core.js +1707 -0
  19. package/dist/observe/core/dev.js +394 -0
  20. package/dist/observe/core/effect.js +193 -0
  21. package/dist/observe/core/error-hooks.js +71 -0
  22. package/dist/observe/core/error.js +82 -0
  23. package/dist/observe/core/external.js +97 -0
  24. package/dist/observe/core/graph.js +162 -0
  25. package/dist/observe/core/heap.js +124 -0
  26. package/dist/observe/core/identities.js +62 -0
  27. package/dist/observe/core/invariants.js +12 -0
  28. package/dist/observe/core/lanes.js +972 -0
  29. package/dist/observe/core/owner.js +371 -0
  30. package/dist/observe/core/scheduler.js +1157 -0
  31. package/dist/observe/core/truth.js +47 -0
  32. package/dist/observe/core/verdict.js +470 -0
  33. package/dist/observe/deferred.js +186 -0
  34. package/dist/observe/flatten.js +79 -0
  35. package/dist/observe/from.js +248 -0
  36. package/dist/observe/index.js +71 -0
  37. package/dist/observe/map.js +593 -0
  38. package/dist/observe/reveal.js +212 -0
  39. package/dist/observe/signals.js +717 -0
  40. package/dist/observe/store/affects.js +142 -0
  41. package/dist/observe/store/edges.js +39 -0
  42. package/dist/observe/store/hooks.js +16 -0
  43. package/dist/observe/store/index.js +47 -0
  44. package/dist/observe/store/optimistic.js +713 -0
  45. package/dist/observe/store/projection.js +345 -0
  46. package/dist/observe/store/reconcile.js +281 -0
  47. package/dist/observe/store/store.js +2127 -0
  48. package/dist/observe/store/storePath.js +103 -0
  49. package/dist/observe/store/target.js +46 -0
  50. package/dist/observe/store/types.js +83 -0
  51. package/dist/observe/store/utils.js +1063 -0
  52. package/dist/prod/affects-marks.js +86 -0
  53. package/dist/prod/affects.js +98 -112
  54. package/dist/prod/attribution.js +45 -0
  55. package/dist/prod/boundaries.js +578 -468
  56. package/dist/prod/core/action.js +92 -59
  57. package/dist/prod/core/async.js +522 -336
  58. package/dist/prod/core/attribution-hooks.js +3 -0
  59. package/dist/prod/core/constants.js +168 -44
  60. package/dist/prod/core/context.js +12 -16
  61. package/dist/prod/core/copies.js +10 -0
  62. package/dist/prod/core/core.js +1345 -627
  63. package/dist/prod/core/dev.js +123 -1
  64. package/dist/prod/core/effect.js +113 -80
  65. package/dist/prod/core/error-hooks.js +71 -0
  66. package/dist/prod/core/error.js +18 -4
  67. package/dist/prod/core/external.js +6 -7
  68. package/dist/prod/core/graph.js +119 -72
  69. package/dist/prod/core/heap.js +85 -109
  70. package/dist/prod/core/identities.js +62 -0
  71. package/dist/prod/core/invariants.js +1 -32
  72. package/dist/prod/core/lanes.js +931 -132
  73. package/dist/prod/core/owner.js +192 -129
  74. package/dist/prod/core/scheduler.js +948 -642
  75. package/dist/prod/core/truth.js +47 -0
  76. package/dist/prod/core/verdict.js +409 -420
  77. package/dist/prod/deferred.js +184 -0
  78. package/dist/prod/flatten.js +77 -0
  79. package/dist/prod/from.js +248 -0
  80. package/dist/prod/index.js +40 -16
  81. package/dist/prod/map.js +481 -211
  82. package/dist/prod/reveal.js +212 -0
  83. package/dist/prod/signals.js +401 -84
  84. package/dist/prod/store/affects.js +142 -0
  85. package/dist/prod/store/edges.js +39 -0
  86. package/dist/prod/store/hooks.js +16 -0
  87. package/dist/prod/store/index.js +27 -18
  88. package/dist/prod/store/optimistic.js +707 -0
  89. package/dist/prod/store/projection.js +331 -0
  90. package/dist/prod/store/reconcile.js +281 -0
  91. package/dist/prod/store/store.js +1952 -267
  92. package/dist/prod/store/storePath.js +1 -1
  93. package/dist/prod/store/target.js +43 -0
  94. package/dist/prod/store/types.js +83 -0
  95. package/dist/prod/store/utils.js +997 -138
  96. package/dist/types/affects-marks.d.ts +14 -0
  97. package/dist/types/affects.d.ts +12 -30
  98. package/dist/types/attribution.d.ts +19 -0
  99. package/dist/types/attribution.prod.d.ts +22 -0
  100. package/dist/types/boundaries.d.ts +99 -124
  101. package/dist/types/carved.d.ts +1 -0
  102. package/dist/types/core/action.d.ts +23 -9
  103. package/dist/types/core/async.d.ts +34 -6
  104. package/dist/types/core/attribution-costs.d.ts +35 -0
  105. package/dist/types/core/attribution-feedback.d.ts +133 -0
  106. package/dist/types/core/attribution-hooks.d.ts +307 -8
  107. package/dist/types/core/attribution-queries.d.ts +24 -0
  108. package/dist/types/core/attribution.d.ts +802 -72
  109. package/dist/types/core/constants.d.ts +195 -38
  110. package/dist/types/core/copies.d.ts +54 -0
  111. package/dist/types/core/core.d.ts +295 -65
  112. package/dist/types/core/dev.d.ts +509 -22
  113. package/dist/types/core/effect.d.ts +1 -1
  114. package/dist/types/core/error-hooks.d.ts +71 -0
  115. package/dist/types/core/error.d.ts +18 -1
  116. package/dist/types/core/graph.d.ts +40 -1
  117. package/dist/types/core/heap.d.ts +5 -5
  118. package/dist/types/core/identities.d.ts +17 -0
  119. package/dist/types/core/index.d.ts +11 -7
  120. package/dist/types/core/invariants.d.ts +1 -26
  121. package/dist/types/core/lanes.d.ts +74 -43
  122. package/dist/types/core/owner.d.ts +27 -9
  123. package/dist/types/core/scheduler.d.ts +353 -102
  124. package/dist/types/core/truth.d.ts +18 -0
  125. package/dist/types/core/types.d.ts +97 -71
  126. package/dist/types/core/verdict.d.ts +35 -0
  127. package/dist/types/deferred.d.ts +28 -0
  128. package/dist/types/flatten.d.ts +8 -0
  129. package/dist/types/from.d.ts +47 -0
  130. package/dist/types/index.d.ts +33 -7
  131. package/dist/types/map.d.ts +4 -2
  132. package/dist/types/reveal.d.ts +19 -0
  133. package/dist/types/signals.d.ts +196 -45
  134. package/dist/types/store/affects.d.ts +12 -0
  135. package/dist/types/store/edges.d.ts +9 -0
  136. package/dist/types/store/hooks.d.ts +62 -0
  137. package/dist/types/store/index.d.ts +42 -15
  138. package/dist/types/store/optimistic.d.ts +24 -45
  139. package/dist/types/store/projection.d.ts +17 -68
  140. package/dist/types/store/reconcile.d.ts +3 -46
  141. package/dist/types/store/store.d.ts +133 -136
  142. package/dist/types/store/storePath.d.ts +1 -1
  143. package/dist/types/store/target.d.ts +166 -0
  144. package/dist/types/store/types.d.ts +105 -0
  145. package/dist/types/store/utils.d.ts +188 -0
  146. package/package.json +31 -26
  147. package/dist/node.cjs +0 -8745
  148. package/dist/prod/core/optimistic.js +0 -265
  149. package/dist/prod/store/next/optimistic.js +0 -385
  150. package/dist/prod/store/next/projection.js +0 -172
  151. package/dist/prod/store/next/reconcile.js +0 -331
  152. package/dist/prod/store/next/store.js +0 -1499
  153. package/dist/prod/store/next/target.js +0 -20
  154. package/dist/types/core/optimistic.d.ts +0 -6
  155. package/dist/types/store/next/optimistic.d.ts +0 -20
  156. package/dist/types/store/next/projection.d.ts +0 -8
  157. package/dist/types/store/next/reconcile.d.ts +0 -3
  158. package/dist/types/store/next/store.d.ts +0 -77
  159. package/dist/types/store/next/target.d.ts +0 -117
  160. package/dist/types-cjs/affects.d.cts +0 -47
  161. package/dist/types-cjs/boundaries.d.cts +0 -175
  162. package/dist/types-cjs/core/action.d.cts +0 -64
  163. package/dist/types-cjs/core/async.d.cts +0 -21
  164. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -52
  165. package/dist/types-cjs/core/attribution.d.cts +0 -186
  166. package/dist/types-cjs/core/constants.d.cts +0 -97
  167. package/dist/types-cjs/core/context.d.cts +0 -36
  168. package/dist/types-cjs/core/core.d.cts +0 -163
  169. package/dist/types-cjs/core/dev.d.cts +0 -81
  170. package/dist/types-cjs/core/effect.d.cts +0 -30
  171. package/dist/types-cjs/core/error.d.cts +0 -47
  172. package/dist/types-cjs/core/external.d.cts +0 -15
  173. package/dist/types-cjs/core/graph.d.cts +0 -6
  174. package/dist/types-cjs/core/heap.d.cts +0 -22
  175. package/dist/types-cjs/core/index.d.cts +0 -13
  176. package/dist/types-cjs/core/invariants.d.cts +0 -59
  177. package/dist/types-cjs/core/lanes.d.cts +0 -44
  178. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  179. package/dist/types-cjs/core/owner.d.cts +0 -124
  180. package/dist/types-cjs/core/scheduler.d.cts +0 -180
  181. package/dist/types-cjs/core/types.d.cts +0 -192
  182. package/dist/types-cjs/core/verdict.d.cts +0 -2
  183. package/dist/types-cjs/index.d.cts +0 -10
  184. package/dist/types-cjs/map.d.cts +0 -69
  185. package/dist/types-cjs/package.json +0 -3
  186. package/dist/types-cjs/signals.d.cts +0 -624
  187. package/dist/types-cjs/store/index.d.cts +0 -19
  188. package/dist/types-cjs/store/next/optimistic.d.cts +0 -20
  189. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  190. package/dist/types-cjs/store/next/reconcile.d.cts +0 -3
  191. package/dist/types-cjs/store/next/store.d.cts +0 -77
  192. package/dist/types-cjs/store/next/target.d.cts +0 -117
  193. package/dist/types-cjs/store/optimistic.d.cts +0 -45
  194. package/dist/types-cjs/store/projection.d.cts +0 -70
  195. package/dist/types-cjs/store/reconcile.d.cts +0 -46
  196. package/dist/types-cjs/store/store.d.cts +0 -138
  197. package/dist/types-cjs/store/storePath.d.cts +0 -58
  198. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -1,18 +1,22 @@
1
- import { computed, optimisticComputed, setSignal, optimisticSignal, runWithOwner, setMemo, signal, read, untrack } from "./core/core.js";
1
+ import { TimeoutError, NotReadyError, unwrapStatusError } from "./core/error.js";
2
2
 
3
- import { cleanup, createRoot, getOwner, dispose } from "./core/owner.js";
3
+ import "./core/verdict.js";
4
4
 
5
- import { globalQueue, Queue } 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";
6
6
 
7
- import { CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, EFFECT_USER, $REFRESH } from "./core/constants.js";
7
+ import { computed, signal, runWithOwner, setMemo, setSignal, markRefresh, read, untrack } from "./core/core.js";
8
8
 
9
- import "./core/invariants.js";
9
+ import { schedule, globalQueue, flushTransaction, dirtyQueue, GlobalQueue, resolveTx, passTx, staleReader, joinFuture } from "./core/scheduler.js";
10
10
 
11
- import "./core/verdict.js";
11
+ import { cleanup, createRoot, getOwner, dispose } from "./core/owner.js";
12
+
13
+ import { signalHook } from "./core/identities.js";
12
14
 
13
15
  import { effect, trackedEffect } from "./core/effect.js";
14
16
 
15
- import { installOptimisticEngine } from "./core/optimistic.js";
17
+ import { optimisticWrite } from "./core/lanes.js";
18
+
19
+ import { truthRead, truthConfirmed } from "./core/truth.js";
16
20
 
17
21
  /**
18
22
  * Low-level reactive-cleanup primitive. Registers a callback that runs when
@@ -41,6 +45,12 @@ import { installOptimisticEngine } from "./core/optimistic.js";
41
45
  * Cannot be used inside `createTrackedEffect` or `onSettled` — return a
42
46
  * cleanup function from the callback body instead.
43
47
  *
48
+ * Cleanups run in unwind order: an owner's children are disposed before its
49
+ * own cleanups, and within one owner later registrations run before earlier
50
+ * ones. In production a component body shares its enclosing owner, so
51
+ * register cleanup before creating children when the order between them
52
+ * matters.
53
+ *
44
54
  * @example
45
55
  * ```ts
46
56
  * // Library shape: thread a resource's disposal into a *captured* owner
@@ -64,22 +74,100 @@ function accessor(e) {
64
74
 
65
75
  function createSignal(e, t) {
66
76
  if (typeof e === "function") {
67
- const n = computed(e, t);
68
- n.T &= ~CONFIG_AUTO_DISPOSE;
69
- return [ accessor(n), setMemo.bind(null, n) ];
77
+ const r = computed(e, t);
78
+ r.l &= ~CONFIG_AUTO_DISPOSE;
79
+ return [ accessor(r), setMemo.bind(null, r) ];
80
+ }
81
+ const r = signal(e, t);
82
+ if (signalHook !== null) signalHook(r);
83
+ return [ accessor(r), setSignal.bind(null, r) ];
84
+ }
85
+
86
+ function createOptimistic(e, t) {
87
+ if (typeof e === "function") {
88
+ const r = computed(e, t);
89
+ r.l &= ~CONFIG_AUTO_DISPOSE;
90
+ return [ accessor(r), optimisticWrite.bind(null, r) ];
70
91
  }
71
- const n = signal(e, t);
72
- return [ accessor(n), setSignal.bind(null, n) ];
92
+ const r = signal(e, t);
93
+ return [ accessor(r), optimisticWrite.bind(null, r) ];
73
94
  }
74
95
 
75
96
  function createMemo(e, t) {
76
97
  return accessor(computed(e, t));
77
98
  }
78
99
 
79
- function createEffect(e, t, n) {
100
+ /**
101
+ * Creates a reactive effect with **separate compute and effect phases**.
102
+ *
103
+ * - `compute(prev)` runs reactively — *put all reactive reads here*. The
104
+ * returned value is passed to `effect` and is also the new "previous" value
105
+ * for the next run.
106
+ * - `effect(next, prev?)` runs imperatively (untracked) after the queue
107
+ * flushes. *Put DOM writes / fetch / logging / subscriptions here.* It may
108
+ * return a cleanup function which runs before the next effect or on
109
+ * disposal.
110
+ *
111
+ * Reactive reads inside `effect` will *not* re-trigger this effect — that's
112
+ * intentional. If you need a single-phase tracked effect, use
113
+ * `createTrackedEffect` (with the tradeoffs noted there).
114
+ *
115
+ * Pass an `EffectBundle` (`{ effect, error }`) instead of a plain function to
116
+ * intercept **compute-phase** errors — errors thrown by `compute` or arriving
117
+ * from upstream reactive sources (including async rejections), which your own
118
+ * code has no frame to `try/catch`. The `error` handler is the error arm of
119
+ * the effect phase: it runs on the same schedule and in the same imperative,
120
+ * writable scope as `effect` (setting error state via signals is fine), and
121
+ * only for *settled* errors — a transient error that recovers before the
122
+ * effect phase runs `effect` with the recovered value instead, and a held
123
+ * transition defers it exactly as it defers `effect`. Without an `error`
124
+ * handler a compute-phase error is logged and the effect simply skips that
125
+ * run — a non-render effect's reactivity failing does not crash the app.
126
+ * Rethrowing from `error` escalates it to the nearest error boundary
127
+ * (halting the system if none exists).
128
+ *
129
+ * The **effect phase is different**: it is your own imperative code, so handle
130
+ * failures with `try/catch` where they occur. An uncaught effect-phase throw
131
+ * is treated as an unhandled application error — caught by the nearest
132
+ * `createErrorBoundary`/`<Errored>`, and permanently halting the reactive
133
+ * system if there is none. It is *not* routed to the bundle's `error` handler.
134
+ *
135
+ * ```typescript
136
+ * createEffect<T>(compute, effectFn | { effect, error }, options?: EffectOptions);
137
+ * ```
138
+ * @param compute a function that receives its previous value and returns a new value used to react on a computation
139
+ * @param effectFn a function that receives the new value and is used to perform side effects (return a cleanup function), or an `EffectBundle` with `effect` and `error` handlers
140
+ * @param options `EffectOptions` -- name, defer, schedule, transparent
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * const [count, setCount] = createSignal(0);
145
+ *
146
+ * createEffect(
147
+ * () => count(), // compute: tracks `count`
148
+ * value => console.log(value) // effect: side effect
149
+ * );
150
+ *
151
+ * setCount(1); // logs 1 after the next flush
152
+ * ```
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * createEffect(
157
+ * () => userId(),
158
+ * id => {
159
+ * const ctrl = new AbortController();
160
+ * fetch(`/users/${id}`, { signal: ctrl.signal });
161
+ * return () => ctrl.abort(); // cleanup before next run / disposal
162
+ * }
163
+ * );
164
+ * ```
165
+ *
166
+ * @description https://v2.solidjs.com/reference/solid-js/reactivity/create-effect
167
+ */ function createEffect(e, t, r) {
80
168
  effect(e, t.effect || t, t.error, {
81
169
  user: true,
82
- ...n
170
+ ...r
83
171
  });
84
172
  }
85
173
 
@@ -110,15 +198,25 @@ function createEffect(e, t, n) {
110
198
  * }
111
199
  * ```
112
200
  *
113
- * @description https://docs.solidjs.com/reference/secondary-primitives/create-render-effect
114
- */ function createRenderEffect(e, t, n) {
115
- effect(e, t, undefined, n);
201
+ * @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-render-effect
202
+ */ function createRenderEffect(e, t, r) {
203
+ effect(e, t, undefined, r);
116
204
  }
117
205
 
118
206
  /**
119
207
  * Creates a tracked reactive effect where dependency tracking and side effects happen
120
208
  * in the same scope.
121
209
  *
210
+ * @deprecated Do not use in new code. For a side effect that follows reactive
211
+ * state, use `createEffect(compute, effect)` — it separates tracking from the
212
+ * side effect, knows its dependencies before it runs, and participates in
213
+ * async and transitions. For one-time DOM work after render (measuring,
214
+ * attaching third-party widgets to a ref), use `onSettled`. Tracking from
215
+ * inside the effect phase — the only thing this primitive adds — is retained
216
+ * solely to ease 1.x migration: it runs beside user-effect callbacks after
217
+ * values commit, never holds a transition, and cannot observe a write staged
218
+ * earlier in the same flush by a signal it has not read yet.
219
+ *
122
220
  * WARNING: Because tracking and effects happen in the same scope, this primitive
123
221
  * may run multiple times for a single change or show tearing (reading inconsistent
124
222
  * state). Use only when dynamic subscription patterns require same-scope tracking.
@@ -148,7 +246,7 @@ function createEffect(e, t, n) {
148
246
  * });
149
247
  * ```
150
248
  *
151
- * @description https://docs.solidjs.com/reference/secondary-primitives/create-tracked-effect
249
+ * @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-tracked-effect
152
250
  */ function createTrackedEffect(e, t) {
153
251
  trackedEffect(e, t);
154
252
  }
@@ -177,35 +275,37 @@ function createEffect(e, t, n) {
177
275
  * setCount(1); // logs once, reaction re-armed for next change
178
276
  * ```
179
277
  *
180
- * @description https://docs.solidjs.com/reference/secondary-primitives/create-reaction
278
+ * @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-reaction
181
279
  */ function createReaction(e, t) {
182
- let n = undefined;
183
- cleanup(() => n?.());
184
- const c = getOwner();
280
+ let r = undefined;
281
+ cleanup(() => r?.());
282
+ const n = getOwner();
185
283
  // The currently armed effect node. `track()` replaces the previous
186
284
  // subscription (1.x semantics): without disposing the superseded arm, its
187
285
  // sources stayed live (firing the callback for replaced dependencies), each
188
286
  // accumulated arm delivered its own fire, and un-fired arms leaked as live
189
287
  // effect nodes until the owner disposed (#2861).
190
- let r;
191
- return o => {
192
- if (r) {
193
- dispose(r);
194
- r = undefined;
288
+ let o;
289
+ return c => {
290
+ if (o) {
291
+ dispose(o);
292
+ o = undefined;
195
293
  }
196
- runWithOwner(c, () => {
197
- effect(() => (o(), r = getOwner()), t => {
198
- r = undefined;
199
- n?.();
200
- const c = (e.effect || e)?.();
201
- if (false && c !== undefined && typeof c !== "function") ;
202
- n = c;
294
+ runWithOwner(n, () => {
295
+ effect(() => {
296
+ const e = getOwner();
297
+ o = e;
298
+ c();
299
+ return e;
300
+ }, t => {
301
+ o = undefined;
302
+ r?.();
303
+ const n = (e.effect || e)?.();
304
+ if (false && n !== undefined && typeof n !== "function") ;
305
+ r = n;
203
306
  dispose(t);
204
307
  }, e.error, {
205
- ...false ? {
206
- ...t,
207
- name: t?.name ?? "effect"
208
- } : t,
308
+ ...t,
209
309
  user: true,
210
310
  defer: true
211
311
  });
@@ -213,10 +313,58 @@ function createEffect(e, t, n) {
213
313
  };
214
314
  }
215
315
 
216
- /** Delivers effect applies on a microtask instead of queueing them (#2930). */ class MicrotaskQueue extends Queue {
217
- enqueue(e, t) {
218
- queueMicrotask(() => t(e));
316
+ /** A promise over a reactive expression (`resolve`, `until`): a pass that
317
+ * runs the expression and settles on a microtask. Not an effect — an
318
+ * effect's run is the flush's effect phase, and a flush parked by the
319
+ * action that is `yield`ing on this promise stashes its runs with the
320
+ * transaction (#2930); the heap runs a pass parked or not. A pending read
321
+ * re-runs it when the source lands (the pass is pending itself); the
322
+ * expression throwing settles the rejection. */
323
+ /** The promise-delivery readers' pass (`resolve`, `until`, `refresh`'s
324
+ * waiter): delivers what it read on a microtask. By posture (#3482, #3490):
325
+ * - the reader of the transaction it was created in (`own`: an action's
326
+ * body, a `refresh()` called there — the waiter reads its slice's frame on
327
+ * a later microtask) delivers that frame — the staged landing an action
328
+ * holds (the action awaits the promise that would otherwise wait on its
329
+ * own settle);
330
+ * - a mainline reader whose pass read a frame another transaction holds
331
+ * joined it (L2, born held — A29) and delivers at that commit: the
332
+ * committed view, never a foreign hold's unrevealed frame. */ function watch(e, t, r, n = 0, o, c = flushTransaction) {
333
+ if (n & CONFIG_AUTHORITATIVE) {
334
+ GlobalQueue.rn = truthRead;
335
+ GlobalQueue.sn = truthConfirmed;
219
336
  }
337
+ // `config` is on the node for its first pass too (the pass runs inside
338
+ // `computed()`): `until`'s predicate is authoritative from its first read,
339
+ // or an already-displayed guess would satisfy it in a later slice. (REDERIVE
340
+ // after: the first pass's own error is the promise's rejection.)
341
+ computed(() => {
342
+ let u, i = false;
343
+ try {
344
+ u = e();
345
+ } catch (e) {
346
+ if (e instanceof NotReadyError) throw e;
347
+ // An error is an answer (D-41): delivered by the same frame rule.
348
+ u = unwrapStatusError(e);
349
+ i = true;
350
+ }
351
+ const s = getOwner();
352
+ // A truth reader waits out a `createDeferred` flight its value derives
353
+ // from, which serves everyone else the stale answer (D-40).
354
+ const f = n & CONFIG_AUTHORITATIVE && GlobalQueue.er?.(s);
355
+ if (f) throw new NotReadyError(f);
356
+ // (Outside a flush a mainline pass joins through `passTx`.)
357
+ const a = flushTransaction ?? passTx;
358
+ if (s.R & REACTIVE_JOINED && a !== null && (c === null || resolveTx(c) !== resolveTx(a))) {
359
+ staleReader(s, resolveTx(a));
360
+ return;
361
+ }
362
+ if (i) return void queueMicrotask(() => r(u));
363
+ o?.(u);
364
+ queueMicrotask(() => t(u));
365
+ }, {
366
+ xe: n
367
+ }).l |= CONFIG_REDERIVE;
220
368
  }
221
369
 
222
370
  /**
@@ -240,51 +388,170 @@ function createEffect(e, t, n) {
240
388
  *
241
389
  * @param fn a reactive expression to resolve
242
390
  */ function resolve(e) {
243
- return new Promise((t, n) => {
244
- createRoot(c => {
245
- // Deliver effect applies on a microtask instead of the owner queue: an
246
- // incomplete transition stashes its effect queues until it settles, but
247
- // an action yielding this promise is itself what keeps the transition
248
- // open — the stashed res() deadlocked the action (#2930). The compute
249
- // still runs in place (under the transaction's view when created inside
250
- // an action step), and status/boundary notifications keep their normal
251
- // route through the inherited queue.
252
- const r = getOwner();
253
- const o = new MicrotaskQueue;
254
- o.ve = r.C;
255
- // notify() forwards up the normal chain
256
- r.C = o;
257
- // A user effect rather than a bare computed: computeds are pull-based and
258
- // are only re-enqueued when a pending source *resolves* — a rejection just
259
- // marks them errored, so nothing would re-run and the promise would never
260
- // settle (#2842). The effect's error channel is notified on rejection.
261
- effect(e, e => {
262
- t(e);
263
- c();
391
+ return new Promise((t, r) => {
392
+ createRoot(n => watch(e, e => (t(e), n()), e => (r(e), n())));
393
+ });
394
+ }
395
+
396
+ /**
397
+ * Invalidates one reactive source, forcing it to re-execute even if its inputs
398
+ * haven't changed, and returns a promise for the target's NEXT QUIESCENT
399
+ * STATE — the re-ask (and anything that supersedes it) has settled.
400
+ *
401
+ * Pass either a Solid-created accessor or a projected store created from
402
+ * `createStore(fn, ...)` / `createProjection(...)`. `refresh()` is a
403
+ * write-like invalidation operation: it does not read the target's value, and
404
+ * refreshing a plain signal accessor is a no-op that resolves immediately.
405
+ *
406
+ * The returned promise is safe to ignore (fire-and-forget refresh is
407
+ * unchanged, and a failed refetch will not surface an unhandled rejection).
408
+ * Awaiting it gives imperative flows the settle point without a reactive
409
+ * read:
410
+ * - Accessor targets resolve with the settled value; store targets resolve
411
+ * with the store node passed (reads through it are fresh after the await).
412
+ * - A failed re-ask rejects with the error.
413
+ * - Semantics are quiescence, not flight identity: if another refresh (or
414
+ * any invalidation) supersedes this one mid-flight, the promise waits for
415
+ * — and delivers — whatever finally lands.
416
+ *
417
+ * @example
418
+ * ```ts
419
+ * const user = createMemo(async () => fetch(`/users/${id()}`).then(r => r.json()));
420
+ *
421
+ * // Fire-and-forget re-fetch
422
+ * <button onClick={() => refresh(user)}>Reload</button>;
423
+ *
424
+ * // Imperative settle point
425
+ * const fresh = await refresh(user);
426
+ * ```
427
+ */ function refresh(e) {
428
+ const t = e?.[$REFRESH];
429
+ if (!t) {
430
+ return Promise.resolve(undefined);
431
+ }
432
+ // Mark now, watch on a microtask. The waiter is resolve()'s machinery with
433
+ // two extra reader bits, but it must NOT compute at call time (effects
434
+ // recompute eagerly on creation): same-tick refreshes coalesce into ONE
435
+ // re-ask only because every mark lands before anything pulls, and eager
436
+ // per-call pulls turned three refreshes into three fetches. Deferred, the
437
+ // waiter's first read sees the coalesced state: FRESH_READ pulls the node
438
+ // through recompute if it is still dirty (self-deduping — a clean node
439
+ // no-ops, so N waiters cost one pull; this also closes the race where a
440
+ // waiter reads the PRE-re-ask value as settled and delivers stale), after
441
+ // which the read either parks on the re-ask's pending window (async — the
442
+ // settle walk re-runs it on every landing, equal-value included, and a
443
+ // rejection arrives through the effect's error channel) or serves the sync
444
+ // answer. resolve()'s own eager compute is untouched: created after a
445
+ // refresh it still settles stale-while-revalidate (#2930) — its contract is
446
+ // "first settled value", not "next quiescent state".
447
+ markRefresh(t);
448
+ // The transaction `refresh()` is called in (an action's slice): the
449
+ // waiter, created on the microtask below, is its reader (`watch`).
450
+ const r = flushTransaction;
451
+ const n = new Promise((n, o) => {
452
+ queueMicrotask(() => {
453
+ // The root is load-bearing: an ownerless computed is auto-disposing,
454
+ // and the settle walk releases an unobserved one instead of re-running
455
+ // it, so the waiter would never see the refetch land (#3888). Settle
456
+ // disposes the waiter directly; the capture runs inside the watcher's
457
+ // own pass, where the ambient owner IS the watcher — exactly what
458
+ // dispose() takes.
459
+ let c = null;
460
+ const make = () => watch(() => {
461
+ if (c === null) c = getOwner();
462
+ // Authoritative: the truth beneath a guess (`until`'s view). A
463
+ // guessed node's own re-ask is not answered by it (#3895): park
464
+ // on that flight, or reject with its error.
465
+ const e = read(t);
466
+ if (t.l & CONFIG_GUESS && t.m & (STATUS_PENDING | STATUS_ERROR)) throw t.T?.U;
467
+ return e;
468
+ }, t => {
469
+ n(typeof e === "function" ? t : e);
470
+ dispose(c);
264
471
  }, e => {
265
- // The error arm already unwraps StatusError (#2840) — `err` is the
266
- // user's original error, matching what error boundaries expose.
267
- n(e);
268
- c();
269
- }, {
270
- user: true
271
- });
472
+ o(e);
473
+ dispose(c);
474
+ }, CONFIG_FRESH_READ | CONFIG_AUTHORITATIVE, undefined, r);
475
+ createRoot(make);
272
476
  });
273
477
  });
478
+ // Fire-and-forget refresh must not turn a failed refetch into an unhandled
479
+ // rejection; awaiting callers attach their own handlers to `promise`.
480
+ n.catch(() => {});
481
+ return n;
274
482
  }
275
483
 
276
- function createOptimistic(e, t) {
277
- // Install before the node exists: only engine-installed programs can carry
278
- // an _overrideValue slot (same runtime-install pattern as
279
- // GlobalQueue._clearOptimisticStore in createOptimisticStore).
280
- installOptimisticEngine();
281
- if (typeof e === "function") {
282
- const n = optimisticComputed(e, t);
283
- n.T &= ~CONFIG_AUTO_DISPOSE;
284
- return [ accessor(n), setSignal.bind(null, n) ];
285
- }
286
- const n = optimisticSignal(e, t);
287
- return [ accessor(n), setSignal.bind(null, n) ];
484
+ /**
485
+ * Awaits a reactive predicate and resolves the first time it settles *truthy*,
486
+ * with that (narrowed) value. Falsy results and pending async reads both mean
487
+ * "not yet": the subscription stays live and re-evaluates as sources change.
488
+ * If the predicate settles with an error — a throw, or an async source that
489
+ * rejects — the promise rejects with it, as do timeout and abort.
490
+ *
491
+ * Where {@link resolve} answers "what is this value" (first settled value,
492
+ * whatever it is), `until` answers "when does the world confirm this
493
+ * condition".
494
+ *
495
+ * This is the acknowledgment mechanism for mutations confirmed on a live data
496
+ * channel (sockets, subscriptions, live queries) rather than by the mutation's
497
+ * own response: correlate by a client-generated id or version in the predicate,
498
+ * and let truth arrive however it arrives — push, refetch, or another tab.
499
+ *
500
+ * Must be called *outside* a tracking scope.
501
+ *
502
+ * @example
503
+ * ```ts
504
+ * const clientId = crypto.randomUUID();
505
+ * await socket.send({ clientId, text }); // fire-and-forget transport
506
+ * // Wait until the live source echoes the write:
507
+ * await until(() => messages.some(m => m.clientId === clientId), { timeout: 10_000 });
508
+ * ```
509
+ *
510
+ * @param fn a reactive predicate
511
+ * @param options optional `timeout` (ms) and abort `signal`
512
+ */ function until(e, t) {
513
+ return new Promise((r, n) => {
514
+ const o = t?.signal;
515
+ if (o?.aborted) return n(o.reason);
516
+ // The awaiting action's transaction, if any (its body runs joined to
517
+ // it). `until()` is a declaration of relatedness (#3164 follow-up): the
518
+ // predicate names the condition that confirms the action, so the frame
519
+ // whose landing flips it truthy is the confirming event by the user's
520
+ // own definition — it reveals WITH the action's settle, not before
521
+ // (`saving=true` beside the confirmed `version=1` is a tear no timeline
522
+ // contains). Updates that do not flip the predicate reveal freely.
523
+ const c = flushTransaction;
524
+ createRoot(u => {
525
+ let i;
526
+ let s;
527
+ const settle = e => {
528
+ if (i !== undefined) clearTimeout(i);
529
+ if (s !== undefined) o.removeEventListener("abort", s);
530
+ e();
531
+ u();
532
+ };
533
+ // Authoritative: the predicate sees the truth, never the action's own
534
+ // optimistic guess — a guess cannot satisfy the wait for its own
535
+ // confirmation.
536
+ watch(e, e => {
537
+ // Falsy is "not yet": keep the subscription live and wait for the
538
+ // next evaluation. Only a truthy settled value resolves.
539
+ if (e) settle(() => r(e));
540
+ }, e => settle(() => n(e)), CONFIG_AUTHORITATIVE, e => {
541
+ // The flip, inside the pass of the flush carrying it: the flush
542
+ // joins the action's transaction, and what it staged is held and
543
+ // revealed with the action.
544
+ if (e && c !== null && globalQueue.Y && resolveTx(c).tt !== 0) joinFuture(c);
545
+ });
546
+ if (t?.timeout !== undefined) i = setTimeout(() => settle(() => n(new TimeoutError)), t.timeout);
547
+ if (o !== undefined) {
548
+ s = () => settle(() => n(o.reason));
549
+ o.addEventListener("abort", s, {
550
+ once: true
551
+ });
552
+ }
553
+ });
554
+ });
288
555
  }
289
556
 
290
557
  /**
@@ -382,13 +649,63 @@ function createOptimistic(e, t) {
382
649
  * on owner disposal
383
650
  */ function onSettled(e) {
384
651
  const t = getOwner();
385
- t && !(t.T & CONFIG_CHILDREN_FORBIDDEN) ? createTrackedEffect(() => untrack(e), undefined) : globalQueue.enqueue(EFFECT_USER, () => {
652
+ t && !(t.l & CONFIG_CHILDREN_FORBIDDEN) ? trackedEffect(() => untrack(e), undefined) : globalQueue.enqueue(EFFECT_USER, function fire() {
653
+ // Settled means derived. A settle that reverts optimism (or replays
654
+ // gated reads) only enqueues the affected subscribers; the pass after
655
+ // the commit re-derives them. Fired in the commit pass, the callback
656
+ // read the optimistic source already reverted beside a sync memo of it
657
+ // still holding the optimistic value — reads do not pull (#3411). Fall
658
+ // to the next pass while the heap has work; `run` swapped the queue,
659
+ // so this lands there, and `enqueue` keeps the drain alive.
660
+ if (dirtyQueue.Et >= dirtyQueue.Fe) return globalQueue.enqueue(EFFECT_USER, fire);
386
661
  // Unowned, out-of-band fire (no owner, or a children-forbidden one this
387
662
  // one-shot must not bind to): a returned cleanup has no lifecycle to
388
663
  // attach to. Reject it in dev; in production the return is simply
389
664
  // dropped — never bound to an unrelated owner or run eagerly.
390
- e();
665
+ e();
391
666
  });
392
667
  }
393
668
 
394
- export { accessor, createEffect, createMemo, createOptimistic, createReaction, createRenderEffect, createSignal, createTrackedEffect, onCleanup, onSettled, resolve };
669
+ /**
670
+ * Schedules `callback` to run **once**, right after the pending flush
671
+ * finishes, in the same microtask checkpoint (before the browser paints).
672
+ * Unlike `onSettled`, it never waits on async work: if that flush is held by
673
+ * a pending transition, the callback still runs.
674
+ *
675
+ * Its main use is keeping a write out of another update's hold. Writes made
676
+ * in the same synchronous frame are applied together, so when one of them
677
+ * starts async work (a route change, a fetch), the others are held with it
678
+ * until it resolves. A write made in `onFlush` happens after that flush, so
679
+ * it is its own update: it commits on its own unless something it changes
680
+ * also derives from the held work (a memo or `createEffect` reading both),
681
+ * in which case it joins that hold as any later write would.
682
+ *
683
+ * Call it from event handlers and effect callbacks (including
684
+ * `createTrackedEffect` and `onSettled` bodies). In an owned scope such as a
685
+ * component body or a computation it warns in dev: use `onSettled` there.
686
+ *
687
+ * - A write in the callback lands after any write made inline in the same
688
+ * frame. Don't write the same signal both inside and outside it; use a
689
+ * functional setter if the order matters.
690
+ * - Several `onFlush` callbacks run in call order and batch into one flush.
691
+ * - `queueMicrotask` is not a substitute: called before the frame's first
692
+ * write, it runs before that flush and its write joins it.
693
+ *
694
+ * @example
695
+ * ```tsx
696
+ * // A menu closes now even when the consumer's onSelect starts async work.
697
+ * const select = (id: string) => {
698
+ * onFlush(() => setOpen(false));
699
+ * props.onSelect?.(id);
700
+ * };
701
+ * ```
702
+ *
703
+ * @param callback Function to run once after the pending flush
704
+ */ function onFlush(e) {
705
+ // The frame's flush is queued (or already draining) ahead of this
706
+ // microtask, so the callback's writes start a flush of their own.
707
+ schedule();
708
+ queueMicrotask(e);
709
+ }
710
+
711
+ export { accessor, createEffect, createMemo, createOptimistic, createReaction, createRenderEffect, createSignal, createTrackedEffect, onCleanup, onFlush, onSettled, refresh, resolve, until };