@solidjs/signals 2.0.0-beta.9 → 2.0.0-rc.0

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 (91) hide show
  1. package/README.md +13 -9
  2. package/dist/dev.js +5971 -1426
  3. package/dist/node.cjs +7681 -3293
  4. package/dist/prod/affects.js +126 -0
  5. package/dist/prod/boundaries.js +572 -0
  6. package/dist/prod/core/action.js +139 -0
  7. package/dist/prod/core/async.js +553 -0
  8. package/dist/prod/core/constants.js +92 -0
  9. package/dist/prod/core/context.js +67 -0
  10. package/dist/prod/core/core.js +817 -0
  11. package/dist/prod/core/dev.js +3 -0
  12. package/dist/prod/core/effect.js +145 -0
  13. package/dist/prod/core/error.js +68 -0
  14. package/dist/prod/core/external.js +98 -0
  15. package/dist/prod/core/graph.js +104 -0
  16. package/dist/prod/core/heap.js +140 -0
  17. package/dist/prod/core/invariants.js +42 -0
  18. package/dist/prod/core/lanes.js +140 -0
  19. package/dist/prod/core/optimistic.js +245 -0
  20. package/dist/prod/core/owner.js +302 -0
  21. package/dist/prod/core/scheduler.js +749 -0
  22. package/dist/prod/core/verdict.js +369 -0
  23. package/dist/prod/index.js +45 -0
  24. package/dist/prod/map.js +317 -0
  25. package/dist/prod/signals.js +375 -0
  26. package/dist/prod/store/optimistic.js +217 -0
  27. package/dist/prod/store/projection.js +231 -0
  28. package/dist/prod/store/reconcile.js +707 -0
  29. package/dist/prod/store/store.js +1081 -0
  30. package/dist/prod/store/storePath.js +103 -0
  31. package/dist/prod/store/utils.js +328 -0
  32. package/dist/types/affects.d.ts +47 -0
  33. package/dist/types/boundaries.d.ts +60 -13
  34. package/dist/types/core/action.d.ts +35 -6
  35. package/dist/types/core/async.d.ts +16 -1
  36. package/dist/types/core/constants.d.ts +35 -6
  37. package/dist/types/core/context.d.ts +10 -2
  38. package/dist/types/core/core.d.ts +58 -63
  39. package/dist/types/core/dev.d.ts +17 -2
  40. package/dist/types/core/effect.d.ts +1 -2
  41. package/dist/types/core/error.d.ts +33 -0
  42. package/dist/types/core/external.d.ts +0 -11
  43. package/dist/types/core/graph.d.ts +2 -1
  44. package/dist/types/core/heap.d.ts +8 -0
  45. package/dist/types/core/index.d.ts +3 -2
  46. package/dist/types/core/invariants.d.ts +59 -0
  47. package/dist/types/core/lanes.d.ts +2 -0
  48. package/dist/types/core/optimistic.d.ts +6 -0
  49. package/dist/types/core/owner.d.ts +50 -3
  50. package/dist/types/core/scheduler.d.ts +82 -10
  51. package/dist/types/core/types.d.ts +66 -1
  52. package/dist/types/core/verdict.d.ts +2 -0
  53. package/dist/types/index.d.ts +3 -2
  54. package/dist/types/map.d.ts +21 -5
  55. package/dist/types/signals.d.ts +164 -12
  56. package/dist/types/store/index.d.ts +1 -1
  57. package/dist/types/store/optimistic.d.ts +3 -3
  58. package/dist/types/store/projection.d.ts +10 -6
  59. package/dist/types/store/reconcile.d.ts +31 -8
  60. package/dist/types/store/store.d.ts +101 -6
  61. package/dist/types-cjs/affects.d.cts +47 -0
  62. package/dist/types-cjs/boundaries.d.cts +60 -13
  63. package/dist/types-cjs/core/action.d.cts +35 -6
  64. package/dist/types-cjs/core/async.d.cts +16 -1
  65. package/dist/types-cjs/core/constants.d.cts +35 -6
  66. package/dist/types-cjs/core/context.d.cts +10 -2
  67. package/dist/types-cjs/core/core.d.cts +58 -63
  68. package/dist/types-cjs/core/dev.d.cts +17 -2
  69. package/dist/types-cjs/core/effect.d.cts +1 -2
  70. package/dist/types-cjs/core/error.d.cts +33 -0
  71. package/dist/types-cjs/core/external.d.cts +0 -11
  72. package/dist/types-cjs/core/graph.d.cts +2 -1
  73. package/dist/types-cjs/core/heap.d.cts +8 -0
  74. package/dist/types-cjs/core/index.d.cts +3 -2
  75. package/dist/types-cjs/core/invariants.d.cts +59 -0
  76. package/dist/types-cjs/core/lanes.d.cts +2 -0
  77. package/dist/types-cjs/core/optimistic.d.cts +6 -0
  78. package/dist/types-cjs/core/owner.d.cts +50 -3
  79. package/dist/types-cjs/core/scheduler.d.cts +82 -10
  80. package/dist/types-cjs/core/types.d.cts +66 -1
  81. package/dist/types-cjs/core/verdict.d.cts +2 -0
  82. package/dist/types-cjs/index.d.cts +3 -2
  83. package/dist/types-cjs/map.d.cts +21 -5
  84. package/dist/types-cjs/signals.d.cts +164 -12
  85. package/dist/types-cjs/store/index.d.cts +1 -1
  86. package/dist/types-cjs/store/optimistic.d.cts +3 -3
  87. package/dist/types-cjs/store/projection.d.cts +10 -6
  88. package/dist/types-cjs/store/reconcile.d.cts +31 -8
  89. package/dist/types-cjs/store/store.d.cts +101 -6
  90. package/package.json +11 -9
  91. package/dist/prod.js +0 -3627
@@ -0,0 +1,317 @@
1
+ import { computed, runWithOwner, signal, setSignal } from "./core/core.js";
2
+
3
+ import { createOwner } from "./core/owner.js";
4
+
5
+ import "./core/scheduler.js";
6
+
7
+ import { CONFIG_AUTO_DISPOSE } from "./core/constants.js";
8
+
9
+ import "./core/invariants.js";
10
+
11
+ import "./core/verdict.js";
12
+
13
+ import "./core/effect.js";
14
+
15
+ import { accessor } from "./signals.js";
16
+
17
+ import { $TRACK } from "./store/store.js";
18
+
19
+ function mapArray(t, s, i) {
20
+ const e = typeof i?.keyed === "function" ? i.keyed : undefined;
21
+ const r = s.length > 1;
22
+ const n = s;
23
+ const h = {
24
+ wt: createOwner(),
25
+ jt: 0,
26
+ Wt: t,
27
+ Mt: [],
28
+ Kt: n,
29
+ xt: [],
30
+ Ut: [],
31
+ $t: e,
32
+ qt: e || i?.keyed === false ? [] : undefined,
33
+ zt: r && i?.keyed !== false ? [] : undefined,
34
+ Bt: i?.keyed === false,
35
+ Ht: i?.fallback
36
+ };
37
+ const o = computed(updateKeyedMap.bind(h));
38
+ // Untracked reads inside the internal owner resolve via _parentComputed; routing
39
+ // them through node lets store-proxy lookups see pending writes (not stale _value).
40
+ h.wt.dt = o;
41
+ o.T &= ~CONFIG_AUTO_DISPOSE;
42
+ return accessor(o);
43
+ }
44
+
45
+ const pureOptions = {
46
+ ownedWrite: true
47
+ };
48
+
49
+ // Exception safety (#2903): a map callback can throw NotReadyError mid-pass
50
+ // (async read), and the computed re-runs the whole pass after settle. Every
51
+ // pass therefore STAGES its work — new rows are created into temp arrays and
52
+ // removals are deferred — and commits to `this` only after every mapper
53
+ // succeeded. An aborted pass disposes just the owners it created and leaves
54
+ // `_items`/`_mappings`/`_nodes`/`_rows`/`_indexes`/`_len` exactly as they
55
+ // were, so the retry diffs against uncorrupted state. Consequence of the
56
+ // strong-abort ordering: removed rows now dispose AFTER the pass's new rows
57
+ // are created (you cannot destroy state before knowing the pass will land).
58
+ function updateKeyedMap() {
59
+ const t = this.Wt() || [], s = t.length;
60
+ t[$TRACK];
61
+ // top level tracking
62
+ runWithOwner(this.wt, () => {
63
+ let i, e, r, n,
64
+ // Mappers write freshly-created row/index signals into the STAGE
65
+ // arrays (`rows`/`indexes`), never into `this._rows`/`this._indexes`.
66
+ h = this.qt ? this.Bt ? () => {
67
+ r[e] = signal(t[e], pureOptions);
68
+ return this.Kt(accessor(r[e]), e);
69
+ } : () => {
70
+ r[e] = signal(t[e], pureOptions);
71
+ n && (n[e] = signal(e, pureOptions));
72
+ return this.Kt(accessor(r[e]), n ? accessor(n[e]) : undefined);
73
+ } : this.zt ? () => {
74
+ const s = t[e];
75
+ n[e] = signal(e, pureOptions);
76
+ return this.Kt(s, accessor(n[e]));
77
+ } : () => {
78
+ const s = t[e];
79
+ return this.Kt(s);
80
+ };
81
+ // fast path for empty arrays
82
+ if (s === 0) {
83
+ if (this.jt !== 0) {
84
+ this.wt.dispose(false);
85
+ this.Ut = [];
86
+ this.Mt = [];
87
+ this.xt = [];
88
+ this.jt = 0;
89
+ this.qt && (this.qt = []);
90
+ this.zt && (this.zt = []);
91
+ }
92
+ if (this.Ht && !this.xt[0]) {
93
+ // an aborted fallback attempt leaves an owner without a mapping;
94
+ // dispose it before re-creating
95
+ this.Ut[0]?.dispose();
96
+ this.xt[0] = runWithOwner(this.Ut[0] = createOwner(), this.Ht);
97
+ }
98
+ }
99
+ // fast path for new create
100
+ else if (this.jt === 0) {
101
+ const o = new Array(s);
102
+ const c = new Array(s);
103
+ r = this.qt && new Array(s);
104
+ n = this.zt && new Array(s);
105
+ try {
106
+ for (e = 0; e < s; e++) o[e] = runWithOwner(c[e] = createOwner(), h);
107
+ } catch (t) {
108
+ for (i = 0; i <= e; i++) c[i]?.dispose();
109
+ throw t;
110
+ }
111
+ // commit
112
+ if (this.Ut[0]) this.Ut[0].dispose();
113
+ // previous fallback
114
+ this.xt = o;
115
+ this.Ut = c;
116
+ r && (this.qt = r);
117
+ n && (this.zt = n);
118
+ this.Mt = t.slice(0);
119
+ this.jt = s;
120
+ } else {
121
+ let o, c, a, f, u, p, w, l, d;
122
+ // skip common prefix
123
+ for (o = 0, c = Math.min(this.jt, s); o < c && (this.Mt[o] === t[o] || this.qt && compare(this.$t, this.Mt[o], t[o])); o++) {
124
+ if (this.qt) setSignal(this.qt[o], t[o]);
125
+ }
126
+ // skip common suffix — counted only; retained entries land in one pass
127
+ // at commit instead of being staged and copied twice
128
+ for (c = this.jt - 1, a = s - 1; c >= o && a >= o && (this.Mt[c] === t[a] || this.qt && compare(this.$t, this.Mt[c], t[a])); c--,
129
+ a--) ;
130
+ // no structural change (every position matched in place at equal
131
+ // length — the common post-reconcile shape): keep the same mapped
132
+ // array identity so downstream consumers don't re-run at all
133
+ if (o === s && this.jt === s) {
134
+ this.Mt = t.slice(0);
135
+ return;
136
+ }
137
+ const O = s - this.jt;
138
+ const m = new Array(s);
139
+ const _ = new Array(s);
140
+ r = this.qt ? new Array(s) : undefined;
141
+ n = this.zt ? new Array(s) : undefined;
142
+ // 0) prepare a map of all indices in the changed window of newItems,
143
+ // scanning backwards so we encounter them in natural order
144
+ p = new Map;
145
+ w = new Array(a + 1);
146
+ for (e = a; e >= o; e--) {
147
+ f = t[e];
148
+ u = this.$t ? this.$t(f) : f;
149
+ i = p.get(u);
150
+ w[e] = i === undefined ? -1 : i;
151
+ p.set(u, e);
152
+ }
153
+ // 1) step through the old changed window and see if items can be found
154
+ // in the new set; if so, stage them at their new positions; if not,
155
+ // queue them for disposal at commit
156
+ for (i = o; i <= c; i++) {
157
+ f = this.Mt[i];
158
+ u = this.$t ? this.$t(f) : f;
159
+ e = p.get(u);
160
+ if (e !== undefined && e !== -1) {
161
+ m[e] = this.xt[i];
162
+ _[e] = this.Ut[i];
163
+ r && (r[e] = this.qt[i]);
164
+ n && (n[e] = this.zt[i]);
165
+ e = w[e];
166
+ p.set(u, e);
167
+ } else (l ??= []).push(this.Ut[i]);
168
+ }
169
+ // 2) create new rows into the temp arrays; an abort disposes only these
170
+ try {
171
+ for (e = o; e <= a; e++) {
172
+ if (_[e] !== undefined) continue;
173
+ (d ??= []).push(_[e] = createOwner());
174
+ m[e] = runWithOwner(_[e], h);
175
+ }
176
+ } catch (t) {
177
+ if (d) for (i = 0; i < d.length; i++) d[i].dispose();
178
+ throw t;
179
+ }
180
+ // 3) commit: land the retained prefix and suffix plus the staged window
181
+ // into the fresh arrays, swap them in (new identity for downstream
182
+ // change propagation), then dispose exited rows
183
+ for (i = 0; i < o; i++) {
184
+ m[i] = this.xt[i];
185
+ _[i] = this.Ut[i];
186
+ r && (r[i] = this.qt[i]);
187
+ n && (n[i] = this.zt[i]);
188
+ }
189
+ for (e = o; e <= a; e++) {
190
+ if (r) setSignal(r[e], t[e]);
191
+ if (n) setSignal(n[e], e);
192
+ }
193
+ for (e = a + 1; e < s; e++) {
194
+ m[e] = this.xt[e - O];
195
+ _[e] = this.Ut[e - O];
196
+ if (r) {
197
+ r[e] = this.qt[e - O];
198
+ setSignal(r[e], t[e]);
199
+ }
200
+ if (n) {
201
+ n[e] = this.zt[e - O];
202
+ if (O !== 0) setSignal(n[e], e);
203
+ }
204
+ }
205
+ this.xt = m;
206
+ this.Ut = _;
207
+ r && (this.qt = r);
208
+ n && (this.zt = n);
209
+ this.jt = s;
210
+ // save a copy of the mapped items for the next update
211
+ this.Mt = t.slice(0);
212
+ if (l) for (i = 0; i < l.length; i++) l[i].dispose();
213
+ }
214
+ });
215
+ return this.xt;
216
+ }
217
+
218
+ /**
219
+ * Reactively renders a callback `count` times, reusing previously-rendered
220
+ * entries when only the count changes. Underlying helper for `<Repeat>`.
221
+ *
222
+ * - `options.from` — start index (default `0`); useful for offset/windowed
223
+ * rendering.
224
+ * - `options.fallback` — accessor returning a value to show when count is `0`.
225
+ *
226
+ * @example
227
+ * ```ts
228
+ * const view = repeat(count, i => `Item ${i}`, { fallback: () => "empty" });
229
+ * ```
230
+ *
231
+ * @description https://docs.solidjs.com/reference/reactive-utilities/repeat
232
+ */ function repeat(t, s, i) {
233
+ const e = s;
234
+ const r = {
235
+ wt: createOwner(),
236
+ jt: 0,
237
+ Jt: 0,
238
+ Vt: t,
239
+ Kt: e,
240
+ Ut: [],
241
+ xt: [],
242
+ Xt: i?.from,
243
+ Ht: i?.fallback
244
+ };
245
+ const n = computed(updateRepeat.bind(r));
246
+ // Same as mapArray: untracked reads inside the internal owner resolve via
247
+ // _parentComputed, so async reads in row callbacks register with the node
248
+ // (pending tracking + post-settle retry) instead of vanishing.
249
+ r.wt.dt = n;
250
+ n.T &= ~CONFIG_AUTO_DISPOSE;
251
+ return accessor(n);
252
+ }
253
+
254
+ // Same staged-commit discipline as `updateKeyedMap` (#2903): the retained
255
+ // window overlap is copied into fresh arrays, missing indexes are created
256
+ // into them, and `this` is only touched — including disposal of rows leaving
257
+ // the window — after every `_map` call succeeded. A NotReadyError mid-pass
258
+ // disposes only the owners this pass created and leaves prior state intact
259
+ // for the post-settle retry. The overlap math also subsumes the previous
260
+ // disjoint-window/front-clear/end-clear/shift special cases.
261
+ function updateRepeat() {
262
+ const t = this.Vt();
263
+ const s = this.Xt?.() || 0;
264
+ runWithOwner(this.wt, () => {
265
+ if (t === 0) {
266
+ if (this.jt !== 0) {
267
+ this.wt.dispose(false);
268
+ this.Ut = [];
269
+ this.xt = [];
270
+ this.jt = 0;
271
+ // Reset offset to match the cleared data (#2767, repro 2).
272
+ this.Jt = 0;
273
+ }
274
+ if (this.Ht && !this.xt[0]) {
275
+ // an aborted fallback attempt leaves an owner without a mapping;
276
+ // dispose it before re-creating
277
+ this.Ut[0]?.dispose();
278
+ this.xt[0] = runWithOwner(this.Ut[0] = createOwner(), this.Ht);
279
+ }
280
+ return;
281
+ }
282
+ const i = s + t;
283
+ const e = this.Jt + this.jt;
284
+ // Retained overlap [keepStart, keepEnd) in global indexes; empty when the
285
+ // windows are disjoint or when coming from empty/fallback.
286
+ const r = Math.max(s, this.Jt);
287
+ const n = Math.min(i, e);
288
+ const h = new Array(t);
289
+ const o = new Array(t);
290
+ for (let t = r; t < n; t++) {
291
+ o[t - s] = this.Ut[t - this.Jt];
292
+ h[t - s] = this.xt[t - this.Jt];
293
+ }
294
+ try {
295
+ for (let t = s; t < i; t++) {
296
+ if (t >= r && t < n) continue;
297
+ h[t - s] = runWithOwner(o[t - s] = createOwner(), () => this.Kt(t));
298
+ }
299
+ } catch (t) {
300
+ for (let t = s; t < i; t++) if ((t < r || t >= n) && o[t - s]) o[t - s].dispose();
301
+ throw t;
302
+ }
303
+ // commit: dispose the previous fallback or the rows leaving the window
304
+ if (this.jt === 0) this.Ut[0]?.dispose(); else for (let t = this.Jt; t < e; t++) if (t < s || t >= i) this.Ut[t - this.Jt].dispose();
305
+ this.xt = h;
306
+ this.Ut = o;
307
+ this.Jt = s;
308
+ this.jt = t;
309
+ });
310
+ return this.xt;
311
+ }
312
+
313
+ function compare(t, s, i) {
314
+ return t ? t(s) === t(i) : true;
315
+ }
316
+
317
+ export { mapArray, repeat };
@@ -0,0 +1,375 @@
1
+ import { computed, optimisticComputed, setSignal, optimisticSignal, runWithOwner, setMemo, signal, read, untrack } from "./core/core.js";
2
+
3
+ import { cleanup, createRoot, getOwner, dispose } from "./core/owner.js";
4
+
5
+ import { globalQueue, Queue } from "./core/scheduler.js";
6
+
7
+ import { CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, EFFECT_USER, $REFRESH } from "./core/constants.js";
8
+
9
+ import "./core/invariants.js";
10
+
11
+ import "./core/verdict.js";
12
+
13
+ import { effect, trackedEffect } from "./core/effect.js";
14
+
15
+ import { installOptimisticEngine } from "./core/optimistic.js";
16
+
17
+ /**
18
+ * Low-level reactive-cleanup primitive. Registers a callback that runs when
19
+ * the surrounding owner is disposed.
20
+ *
21
+ * **In 2.0 user code this is rare.** The two cases where you might reach for
22
+ * it have better-shaped tools:
23
+ *
24
+ * - **Component lifecycle (mount/unmount, listeners, intervals):** use
25
+ * {@link onSettled} and **return** a cleanup function. Setup and teardown
26
+ * stay paired in one block. This replaces the 1.x `onMount` + `onCleanup`
27
+ * pairing.
28
+ * - **Cleanup tied to an effect run:** `onCleanup` does not belong in
29
+ * `createEffect`'s apply phase. If a compute phase genuinely needs per-run
30
+ * teardown, that's usually a sign the work should be a memo/projection
31
+ * instead, or moved to `onSettled` if it's lifecycle-shaped.
32
+ *
33
+ * Where `onCleanup` is the right tool is **library / custom-primitive
34
+ * internals** — coordinating disposal inside a `createRoot` body, or wiring
35
+ * cleanup to a captured owner via `runWithOwner` from a custom factory.
36
+ * Application code rarely needs to write any of those shapes directly.
37
+ *
38
+ * Must be called inside an owner. Calling outside an owner is a no-op (with a
39
+ * dev-mode warning).
40
+ *
41
+ * Cannot be used inside `createTrackedEffect` or `onSettled` — return a
42
+ * cleanup function from the callback body instead.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * // Library shape: thread a resource's disposal into a *captured* owner
47
+ * // from a factory that has no settle-phase setup of its own. `onSettled`
48
+ * // would queue a callback we don't need; `onCleanup` is the leaner
49
+ * // primitive when the only job is "register disposal on this owner".
50
+ * function bindToOwner<T extends { dispose(): void }>(owner: Owner, resource: T): T {
51
+ * runWithOwner(owner, () => onCleanup(() => resource.dispose()));
52
+ * return resource;
53
+ * }
54
+ * ```
55
+ */ function onCleanup(e) {
56
+ return cleanup(e);
57
+ }
58
+
59
+ function accessor(e) {
60
+ const t = read.bind(null, e);
61
+ t[$REFRESH] = e;
62
+ return t;
63
+ }
64
+
65
+ function createSignal(e, t) {
66
+ if (typeof e === "function") {
67
+ const n = computed(e, t);
68
+ n.T &= ~CONFIG_AUTO_DISPOSE;
69
+ return [ accessor(n), setMemo.bind(null, n) ];
70
+ }
71
+ const n = signal(e, t);
72
+ return [ accessor(n), setSignal.bind(null, n) ];
73
+ }
74
+
75
+ function createMemo(e, t) {
76
+ return accessor(computed(e, t));
77
+ }
78
+
79
+ function createEffect(e, t, n) {
80
+ effect(e, t.effect || t, t.error, {
81
+ user: true,
82
+ ...n
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Creates a reactive computation that runs during the render phase as DOM elements
88
+ * are created and updated but not necessarily connected.
89
+ *
90
+ * Same compute / effect split as `createEffect`, but scheduled inside the render
91
+ * queue rather than after it. Reach for this only when authoring renderer
92
+ * plumbing (custom DOM bindings, JSX-generated `insert()` / `spread()` calls).
93
+ * App code should use `createEffect`.
94
+ *
95
+ * ```typescript
96
+ * createRenderEffect<T>(compute, effectFn, options?: EffectOptions);
97
+ * ```
98
+ * @param compute a function that receives its previous value and returns a new value used to react on a computation
99
+ * @param effectFn a function that receives the new value and is used to perform side effects
100
+ * @param options `EffectOptions` -- name, defer, schedule, transparent
101
+ *
102
+ * @example
103
+ * ```ts
104
+ * // Custom directive: bind an element's textContent to a reactive source.
105
+ * function bindText(el: HTMLElement, source: () => string) {
106
+ * createRenderEffect(
107
+ * () => source(),
108
+ * value => { el.textContent = value; }
109
+ * );
110
+ * }
111
+ * ```
112
+ *
113
+ * @description https://docs.solidjs.com/reference/secondary-primitives/create-render-effect
114
+ */ function createRenderEffect(e, t, n) {
115
+ effect(e, t, undefined, n);
116
+ }
117
+
118
+ /**
119
+ * Creates a tracked reactive effect where dependency tracking and side effects happen
120
+ * in the same scope.
121
+ *
122
+ * WARNING: Because tracking and effects happen in the same scope, this primitive
123
+ * may run multiple times for a single change or show tearing (reading inconsistent
124
+ * state). Use only when dynamic subscription patterns require same-scope tracking.
125
+ *
126
+ * ```typescript
127
+ * createTrackedEffect(compute, options?: { name?: string });
128
+ * ```
129
+ * @param compute a function that contains reactive reads to track and returns an optional cleanup function to run on disposal or before next execution
130
+ * @param options -- name
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * createTrackedEffect(() => {
135
+ * const target = focusedNode();
136
+ * if (!target) return;
137
+ *
138
+ * const handler = () => log(target.value());
139
+ * target.on("change", handler);
140
+ *
141
+ * return () => target.off("change", handler);
142
+ * });
143
+ * ```
144
+ *
145
+ * @description https://docs.solidjs.com/reference/secondary-primitives/create-tracked-effect
146
+ */ function createTrackedEffect(e, t) {
147
+ trackedEffect(e, t);
148
+ }
149
+
150
+ /**
151
+ * Creates a reactive computation that runs after the render phase with flexible tracking.
152
+ *
153
+ * ```typescript
154
+ * const track = createReaction(effectFn, options?: EffectOptions);
155
+ * track(() => { // reactive reads });
156
+ * ```
157
+ * @param effectFn a function (or `EffectBundle`) that is called when tracked function is invalidated
158
+ * @param options `EffectOptions` -- name, defer
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * const [count, setCount] = createSignal(0);
163
+ *
164
+ * const track = createReaction(() => {
165
+ * console.log("count changed once, re-arm to listen again");
166
+ * track(() => count()); // re-arm
167
+ * });
168
+ *
169
+ * track(() => count()); // initial arm
170
+ *
171
+ * setCount(1); // logs once, reaction re-armed for next change
172
+ * ```
173
+ *
174
+ * @description https://docs.solidjs.com/reference/secondary-primitives/create-reaction
175
+ */ function createReaction(e, t) {
176
+ let n = undefined;
177
+ cleanup(() => n?.());
178
+ const c = getOwner();
179
+ // The currently armed effect node. `track()` replaces the previous
180
+ // subscription (1.x semantics): without disposing the superseded arm, its
181
+ // sources stayed live (firing the callback for replaced dependencies), each
182
+ // accumulated arm delivered its own fire, and un-fired arms leaked as live
183
+ // effect nodes until the owner disposed (#2861).
184
+ let r;
185
+ return o => {
186
+ if (r) {
187
+ dispose(r);
188
+ r = undefined;
189
+ }
190
+ runWithOwner(c, () => {
191
+ effect(() => (o(), r = getOwner()), t => {
192
+ r = undefined;
193
+ n?.();
194
+ const c = (e.effect || e)?.();
195
+ if (false && c !== undefined && typeof c !== "function") ;
196
+ n = c;
197
+ dispose(t);
198
+ }, e.error, {
199
+ ...false ? {
200
+ ...t,
201
+ name: t?.name ?? "effect"
202
+ } : t,
203
+ user: true,
204
+ defer: true
205
+ });
206
+ });
207
+ };
208
+ }
209
+
210
+ /** Delivers effect applies on a microtask instead of queueing them (#2930). */ class MicrotaskQueue extends Queue {
211
+ enqueue(e, t) {
212
+ queueMicrotask(() => t(e));
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Awaits a reactive expression and returns its first fully-settled value as a
218
+ * `Promise`. Pending async reads (`createMemo` returning a promise, etc.) are
219
+ * waited on; once the expression returns synchronously without `NotReadyError`
220
+ * the promise resolves with that value. If the expression settles with an
221
+ * error instead — including an async source that rejects — the promise
222
+ * rejects with it.
223
+ *
224
+ * Must be called *outside* a tracking scope — it doesn't subscribe, it just
225
+ * resolves the current value once.
226
+ *
227
+ * @example
228
+ * ```ts
229
+ * const user = createMemo(() => fetch(`/users/${id()}`).then(r => r.json()));
230
+ *
231
+ * // outside any reactive scope
232
+ * const initial = await resolve(() => user());
233
+ * ```
234
+ *
235
+ * @param fn a reactive expression to resolve
236
+ */ function resolve(e) {
237
+ return new Promise((t, n) => {
238
+ createRoot(c => {
239
+ // Deliver effect applies on a microtask instead of the owner queue: an
240
+ // incomplete transition stashes its effect queues until it settles, but
241
+ // an action yielding this promise is itself what keeps the transition
242
+ // open — the stashed res() deadlocked the action (#2930). The compute
243
+ // still runs in place (under the transaction's view when created inside
244
+ // an action step), and status/boundary notifications keep their normal
245
+ // route through the inherited queue.
246
+ const r = getOwner();
247
+ const o = new MicrotaskQueue;
248
+ o.ke = r.C;
249
+ // notify() forwards up the normal chain
250
+ r.C = o;
251
+ // A user effect rather than a bare computed: computeds are pull-based and
252
+ // are only re-enqueued when a pending source *resolves* — a rejection just
253
+ // marks them errored, so nothing would re-run and the promise would never
254
+ // settle (#2842). The effect's error channel is notified on rejection.
255
+ effect(e, e => {
256
+ t(e);
257
+ c();
258
+ }, e => {
259
+ // The error arm already unwraps StatusError (#2840) — `err` is the
260
+ // user's original error, matching what error boundaries expose.
261
+ n(e);
262
+ c();
263
+ }, {
264
+ user: true
265
+ });
266
+ });
267
+ });
268
+ }
269
+
270
+ function createOptimistic(e, t) {
271
+ // Install before the node exists: only engine-installed programs can carry
272
+ // an _overrideValue slot (same runtime-install pattern as
273
+ // GlobalQueue._clearOptimisticStore in createOptimisticStore).
274
+ installOptimisticEngine();
275
+ if (typeof e === "function") {
276
+ const n = optimisticComputed(e, t);
277
+ n.T &= ~CONFIG_AUTO_DISPOSE;
278
+ return [ accessor(n), setSignal.bind(null, n) ];
279
+ }
280
+ const n = optimisticSignal(e, t);
281
+ return [ accessor(n), setSignal.bind(null, n) ];
282
+ }
283
+
284
+ /**
285
+ * Schedules `callback` to run **once** after the reactive graph has fully
286
+ * settled — i.e. once every pending async read inside the current owner has
287
+ * resolved and the queue has flushed. Each call registers a single fire; it
288
+ * does not create an ongoing subscription.
289
+ *
290
+ * The canonical lifecycle primitive in 2.0. Three main usages:
291
+ *
292
+ * - **Component-level setup-and-teardown** *(the most common shape)*: run
293
+ * setup after the component's first stable render and **return a cleanup
294
+ * function** to dispose it on owner disposal. This is the replacement for
295
+ * the 1.x `onMount` + `onCleanup` pairing — setup and teardown live in one
296
+ * block, and `onCleanup` is no longer the right tool for component
297
+ * bodies. (`onMount` no longer exists in 2.0.)
298
+ * - **Post-settle "ready" hook:** run once after a component's first stable
299
+ * render — analytics ping, focus, scroll-into-view, etc. No cleanup needed.
300
+ * - **Inside an event handler:** schedule work to run after the action /
301
+ * transition triggered by the event has completed.
302
+ *
303
+ * Reactive reads inside the callback are *not* tracked — to react to
304
+ * subsequent settles, register a new `onSettled` each time.
305
+ *
306
+ * `onCleanup` is **not** allowed inside the callback — return a cleanup
307
+ * function instead. The returned cleanup runs on owner disposal.
308
+ *
309
+ * A cleanup return is only honored when `onSettled` is called from an **owned**
310
+ * scope (e.g. a component body). When it fires out of band from an *unowned*
311
+ * scope — an event handler, a tracked effect, or another `onSettled` — there is
312
+ * no owner lifecycle to bind a cleanup to; returning one is a dev-mode error
313
+ * (and is dropped in production). Use the post-settle/event-handler forms below
314
+ * for one-shot work, and keep setup-with-teardown in an owned scope.
315
+ *
316
+ * @example
317
+ * ```tsx
318
+ * // Component-level setup + teardown — replaces onMount + onCleanup.
319
+ * // Subscribe to an external source on mount, unsubscribe on dispose.
320
+ * function useViewportWidth() {
321
+ * const [width, setWidth] = createSignal(window.innerWidth);
322
+ * onSettled(() => {
323
+ * const onResize = () => setWidth(window.innerWidth);
324
+ * window.addEventListener("resize", onResize);
325
+ * return () => window.removeEventListener("resize", onResize);
326
+ * });
327
+ * return width;
328
+ * }
329
+ * ```
330
+ *
331
+ * @example
332
+ * ```tsx
333
+ * // Post-settle "ready" hook — no cleanup needed.
334
+ * function Dashboard() {
335
+ * const data = createMemo(async () => fetchData());
336
+ *
337
+ * onSettled(() => {
338
+ * analytics.track("dashboard.ready");
339
+ * });
340
+ *
341
+ * return <Loading fallback={<Spinner />}><pre>{data()}</pre></Loading>;
342
+ * }
343
+ * ```
344
+ *
345
+ * @example
346
+ * ```tsx
347
+ * // Event-handler — runs after the action settles.
348
+ * function SaveButton() {
349
+ * const save = action(function* () {
350
+ * yield api.save();
351
+ * });
352
+ *
353
+ * const handleClick = () => {
354
+ * save();
355
+ * onSettled(() => toast("Saved!"));
356
+ * };
357
+ *
358
+ * return <button onClick={handleClick}>Save</button>;
359
+ * }
360
+ * ```
361
+ *
362
+ * @param callback Function to run; may return a cleanup function that fires
363
+ * on owner disposal
364
+ */ function onSettled(e) {
365
+ const t = getOwner();
366
+ t && !(t.T & CONFIG_CHILDREN_FORBIDDEN) ? createTrackedEffect(() => untrack(e), undefined) : globalQueue.enqueue(EFFECT_USER, () => {
367
+ // Unowned, out-of-band fire (no owner, or a children-forbidden one this
368
+ // one-shot must not bind to): a returned cleanup has no lifecycle to
369
+ // attach to. Reject it in dev; in production the return is simply
370
+ // dropped — never bound to an unrelated owner or run eagerly.
371
+ e();
372
+ });
373
+ }
374
+
375
+ export { accessor, createEffect, createMemo, createOptimistic, createReaction, createRenderEffect, createSignal, createTrackedEffect, onCleanup, onSettled, resolve };