@solidjs/signals 2.0.0-rc.5 → 2.0.0-rc.7

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 (66) hide show
  1. package/dist/dev.js +2467 -1322
  2. package/dist/node.cjs +2037 -2428
  3. package/dist/node.dev.cjs +13724 -0
  4. package/dist/prod/boundaries.js +3 -1
  5. package/dist/prod/core/async.js +47 -20
  6. package/dist/prod/core/attribution-hooks.js +3 -0
  7. package/dist/prod/core/constants.js +9 -1
  8. package/dist/prod/core/context.js +10 -16
  9. package/dist/prod/core/core.js +260 -188
  10. package/dist/prod/core/dev.js +2 -0
  11. package/dist/prod/core/effect.js +41 -32
  12. package/dist/prod/core/external.js +2 -2
  13. package/dist/prod/core/graph.js +35 -31
  14. package/dist/prod/core/heap.js +34 -40
  15. package/dist/prod/core/lanes.js +44 -30
  16. package/dist/prod/core/optimistic.js +55 -45
  17. package/dist/prod/core/owner.js +35 -35
  18. package/dist/prod/core/scheduler.js +176 -177
  19. package/dist/prod/core/verdict.js +53 -52
  20. package/dist/prod/index.js +0 -2
  21. package/dist/prod/map.js +104 -94
  22. package/dist/prod/signals.js +119 -35
  23. package/dist/prod/store/next/optimistic.js +217 -163
  24. package/dist/prod/store/next/projection.js +2 -2
  25. package/dist/prod/store/next/reconcile.js +140 -288
  26. package/dist/prod/store/next/store.js +338 -252
  27. package/dist/prod/store/store.js +2 -2
  28. package/dist/types/core/attribution-hooks.d.ts +75 -0
  29. package/dist/types/core/attribution.d.ts +313 -9
  30. package/dist/types/core/constants.d.ts +8 -0
  31. package/dist/types/core/core.d.ts +12 -3
  32. package/dist/types/core/dev.d.ts +56 -9
  33. package/dist/types/core/heap.d.ts +5 -3
  34. package/dist/types/core/invariants.d.ts +1 -1
  35. package/dist/types/core/lanes.d.ts +10 -0
  36. package/dist/types/core/scheduler.d.ts +14 -4
  37. package/dist/types/signals.d.ts +10 -11
  38. package/dist/types/store/index.d.ts +3 -6
  39. package/dist/types/store/next/optimistic.d.ts +4 -2
  40. package/dist/types/store/next/reconcile.d.ts +5 -12
  41. package/dist/types/store/next/store.d.ts +3 -9
  42. package/dist/types/store/next/target.d.ts +30 -46
  43. package/dist/types/store/store.d.ts +14 -9
  44. package/dist/types-cjs/core/attribution-hooks.d.cts +75 -0
  45. package/dist/types-cjs/core/attribution.d.cts +313 -9
  46. package/dist/types-cjs/core/constants.d.cts +8 -0
  47. package/dist/types-cjs/core/core.d.cts +12 -3
  48. package/dist/types-cjs/core/dev.d.cts +56 -9
  49. package/dist/types-cjs/core/heap.d.cts +5 -3
  50. package/dist/types-cjs/core/invariants.d.cts +1 -1
  51. package/dist/types-cjs/core/lanes.d.cts +10 -0
  52. package/dist/types-cjs/core/scheduler.d.cts +14 -4
  53. package/dist/types-cjs/signals.d.cts +10 -11
  54. package/dist/types-cjs/store/index.d.cts +3 -6
  55. package/dist/types-cjs/store/next/optimistic.d.cts +4 -2
  56. package/dist/types-cjs/store/next/reconcile.d.cts +5 -12
  57. package/dist/types-cjs/store/next/store.d.cts +3 -9
  58. package/dist/types-cjs/store/next/target.d.cts +30 -46
  59. package/dist/types-cjs/store/store.d.cts +14 -9
  60. package/package.json +3 -2
  61. package/dist/prod/store/next/patch-hooks.js +0 -13
  62. package/dist/prod/store/next/patch.js +0 -614
  63. package/dist/types/store/next/patch-hooks.d.ts +0 -41
  64. package/dist/types/store/next/patch.d.ts +0 -91
  65. package/dist/types-cjs/store/next/patch-hooks.d.cts +0 -41
  66. package/dist/types-cjs/store/next/patch.d.cts +0 -91
@@ -1,6 +1,6 @@
1
1
  import { TimeoutError } from "./core/error.js";
2
2
 
3
- import { computed, optimisticComputed, setSignal, optimisticSignal, runWithOwner, setMemo, signal, markRefresh, installAuthoritativeRead, read, untrack } from "./core/core.js";
3
+ import { computed, optimisticComputed, setSignal, optimisticSignal, runWithOwner, setMemo, signal, installAuthoritativeRead, markRefresh, read, untrack } from "./core/core.js";
4
4
 
5
5
  import { cleanup, createRoot, getOwner, dispose, getObserver } from "./core/owner.js";
6
6
 
@@ -78,7 +78,74 @@ function createMemo(e, t) {
78
78
  return accessor(computed(e, t));
79
79
  }
80
80
 
81
- function createEffect(e, t, n) {
81
+ /**
82
+ * Creates a reactive effect with **separate compute and effect phases**.
83
+ *
84
+ * - `compute(prev)` runs reactively — *put all reactive reads here*. The
85
+ * returned value is passed to `effect` and is also the new "previous" value
86
+ * for the next run.
87
+ * - `effect(next, prev?)` runs imperatively (untracked) after the queue
88
+ * flushes. *Put DOM writes / fetch / logging / subscriptions here.* It may
89
+ * return a cleanup function which runs before the next effect or on
90
+ * disposal.
91
+ *
92
+ * Reactive reads inside `effect` will *not* re-trigger this effect — that's
93
+ * intentional. If you need a single-phase tracked effect, use
94
+ * `createTrackedEffect` (with the tradeoffs noted there).
95
+ *
96
+ * Pass an `EffectBundle` (`{ effect, error }`) instead of a plain function to
97
+ * intercept **compute-phase** errors — errors thrown by `compute` or arriving
98
+ * from upstream reactive sources (including async rejections), which your own
99
+ * code has no frame to `try/catch`. The `error` handler is the error arm of
100
+ * the effect phase: it runs on the same schedule and in the same imperative,
101
+ * writable scope as `effect` (setting error state via signals is fine), and
102
+ * only for *settled* errors — a transient error that recovers before the
103
+ * effect phase runs `effect` with the recovered value instead, and a held
104
+ * transition defers it exactly as it defers `effect`. Without an `error`
105
+ * handler a compute-phase error is logged and the effect simply skips that
106
+ * run — a non-render effect's reactivity failing does not crash the app.
107
+ * Rethrowing from `error` escalates it to the nearest error boundary
108
+ * (halting the system if none exists).
109
+ *
110
+ * The **effect phase is different**: it is your own imperative code, so handle
111
+ * failures with `try/catch` where they occur. An uncaught effect-phase throw
112
+ * is treated as an unhandled application error — caught by the nearest
113
+ * `createErrorBoundary`/`<Errored>`, and permanently halting the reactive
114
+ * system if there is none. It is *not* routed to the bundle's `error` handler.
115
+ *
116
+ * ```typescript
117
+ * createEffect<T>(compute, effectFn | { effect, error }, options?: EffectOptions);
118
+ * ```
119
+ * @param compute a function that receives its previous value and returns a new value used to react on a computation
120
+ * @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
121
+ * @param options `EffectOptions` -- name, defer, schedule, transparent
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * const [count, setCount] = createSignal(0);
126
+ *
127
+ * createEffect(
128
+ * () => count(), // compute: tracks `count`
129
+ * value => console.log(value) // effect: side effect
130
+ * );
131
+ *
132
+ * setCount(1); // logs 1 after the next flush
133
+ * ```
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * createEffect(
138
+ * () => userId(),
139
+ * id => {
140
+ * const ctrl = new AbortController();
141
+ * fetch(`/users/${id}`, { signal: ctrl.signal });
142
+ * return () => ctrl.abort(); // cleanup before next run / disposal
143
+ * }
144
+ * );
145
+ * ```
146
+ *
147
+ * @description https://docs.solidjs.com/reference/basic-reactivity/create-effect
148
+ */ function createEffect(e, t, n) {
82
149
  effect(e, t.effect || t, t.error, {
83
150
  user: true,
84
151
  ...n
@@ -121,6 +188,16 @@ function createEffect(e, t, n) {
121
188
  * Creates a tracked reactive effect where dependency tracking and side effects happen
122
189
  * in the same scope.
123
190
  *
191
+ * @deprecated Do not use in new code. For a side effect that follows reactive
192
+ * state, use `createEffect(compute, effect)` — it separates tracking from the
193
+ * side effect, knows its dependencies before it runs, and participates in
194
+ * async and transitions. For one-time DOM work after render (measuring,
195
+ * attaching third-party widgets to a ref), use `onSettled`. Tracking from
196
+ * inside the effect phase — the only thing this primitive adds — is retained
197
+ * solely to ease 1.x migration: it runs beside user-effect callbacks after
198
+ * values commit, never holds a transition, and cannot observe a write staged
199
+ * earlier in the same flush by a signal it has not read yet.
200
+ *
124
201
  * WARNING: Because tracking and effects happen in the same scope, this primitive
125
202
  * may run multiple times for a single change or show tearing (reading inconsistent
126
203
  * state). Use only when dynamic subscription patterns require same-scope tracking.
@@ -189,15 +266,15 @@ function createEffect(e, t, n) {
189
266
  // sources stayed live (firing the callback for replaced dependencies), each
190
267
  // accumulated arm delivered its own fire, and un-fired arms leaked as live
191
268
  // effect nodes until the owner disposed (#2861).
192
- let o;
193
- return i => {
194
- if (o) {
195
- dispose(o);
196
- o = undefined;
269
+ let i;
270
+ return o => {
271
+ if (i) {
272
+ dispose(i);
273
+ i = undefined;
197
274
  }
198
275
  runWithOwner(r, () => {
199
- effect(() => (i(), o = getOwner()), t => {
200
- o = undefined;
276
+ effect(() => (o(), i = getOwner()), t => {
277
+ i = undefined;
201
278
  n?.();
202
279
  const r = (e.effect || e)?.();
203
280
  if (false && r !== undefined && typeof r !== "function") ;
@@ -251,11 +328,11 @@ function createEffect(e, t, n) {
251
328
  // still runs in place (under the transaction's view when created inside
252
329
  // an action step), and status/boundary notifications keep their normal
253
330
  // route through the inherited queue.
254
- const o = getOwner();
255
- const i = new MicrotaskQueue;
256
- i.ke = o.C;
331
+ const i = getOwner();
332
+ const o = new MicrotaskQueue;
333
+ o.Le = i.C;
257
334
  // notify() forwards up the normal chain
258
- o.C = i;
335
+ i.C = o;
259
336
  // A user effect rather than a bare computed: computeds are pull-based and
260
337
  // are only re-enqueued when a pending source *resolves* — a rejection just
261
338
  // marks them errored, so nothing would re-run and the promise would never
@@ -275,7 +352,7 @@ function createEffect(e, t, n) {
275
352
  // stale mainline value and resolves with old data.
276
353
  {
277
354
  user: true,
278
- dt: CONFIG_DIRECT_COMMIT
355
+ At: CONFIG_DIRECT_COMMIT
279
356
  });
280
357
  });
281
358
  });
@@ -343,7 +420,14 @@ function createEffect(e, t, n) {
343
420
  // own eager compute is untouched: created after a refresh it still settles
344
421
  // stale-while-revalidate (#2930) — its contract is "first settled value",
345
422
  // not "next quiescent state".
346
- markRefresh(t);
423
+
424
+ // An authoritative reader is woken through a late-bound hook when the truth
425
+ // lands EQUAL to a standing override (the A17-silent path). Every setter of
426
+ // that reader bit must install it — until() does, and this waiter is the
427
+ // other one (#3303: refresh of an optimistic in an app that never called
428
+ // until() dereferenced the null hook).
429
+ installAuthoritativeRead();
430
+ markRefresh(t);
347
431
  const n = new Promise((n, r) => {
348
432
  queueMicrotask(() => {
349
433
  // No createRoot: the microtask has no ambient owner, so the effect is
@@ -356,24 +440,24 @@ function createEffect(e, t, n) {
356
440
  // Typed as the effect node, not Owner: the capture runs inside the
357
441
  // effect's own compute, where the ambient owner IS the effect —
358
442
  // exactly what dispose() takes.
359
- let o = null;
443
+ let i = null;
360
444
  const make = () => effect(() => {
361
- if (o === null) {
362
- o = getOwner();
445
+ if (i === null) {
446
+ i = getOwner();
363
447
  const e = new MicrotaskQueue;
364
- e.ke = o.C;
365
- o.C = e;
448
+ e.Le = i.C;
449
+ i.C = e;
366
450
  }
367
451
  return read(t);
368
452
  }, t => {
369
453
  n(typeof e === "function" ? t : e);
370
- dispose(o);
454
+ dispose(i);
371
455
  }, e => {
372
456
  r(e);
373
- dispose(o);
457
+ dispose(i);
374
458
  }, {
375
459
  user: true,
376
- dt: CONFIG_DIRECT_COMMIT | CONFIG_AUTHORITATIVE_READ | CONFIG_FRESH_READ
460
+ At: CONFIG_DIRECT_COMMIT | CONFIG_AUTHORITATIVE_READ | CONFIG_FRESH_READ
377
461
  });
378
462
  make();
379
463
  });
@@ -448,9 +532,9 @@ function createEffect(e, t, n) {
448
532
  // write flips it truthy, that transition merges here and reveals at the
449
533
  // joint settle instead of painting the confirmation under live optimism.
450
534
  const n = activeTransition;
451
- return new Promise((r, o) => {
452
- const i = t?.signal;
453
- if (i?.aborted) return o(i.reason);
535
+ return new Promise((r, i) => {
536
+ const o = t?.signal;
537
+ if (o?.aborted) return i(o.reason);
454
538
  createRoot(c => {
455
539
  // Same delivery contract as resolve() (#2930): effect applies ride a
456
540
  // microtask so the promise can settle while the transaction the caller
@@ -458,13 +542,13 @@ function createEffect(e, t, n) {
458
542
  // entire point of the hold.
459
543
  const s = getOwner();
460
544
  const u = new MicrotaskQueue;
461
- u.ke = s.C;
545
+ u.Le = s.C;
462
546
  s.C = u;
463
547
  let f;
464
548
  let a;
465
549
  const settle = e => {
466
550
  if (f !== undefined) clearTimeout(f);
467
- if (a !== undefined) i.removeEventListener("abort", a);
551
+ if (a !== undefined) o.removeEventListener("abort", a);
468
552
  e();
469
553
  c();
470
554
  };
@@ -480,7 +564,7 @@ function createEffect(e, t, n) {
480
564
  // Falsy is "not yet": keep the subscription live and wait for the
481
565
  // next evaluation. Only a truthy settled value resolves.
482
566
  if (e) settle(() => r(e));
483
- }, e => settle(() => o(e)),
567
+ }, e => settle(() => i(e)),
484
568
  // AUTHORITATIVE_READ: overrides invisible to the predicate.
485
569
  // DIRECT_COMMIT: truth that stages into the held transaction (a
486
570
  // refresh the action issued) must flow through to the microtask
@@ -488,12 +572,12 @@ function createEffect(e, t, n) {
488
572
  // the hold itself is keeping uncommitted.
489
573
  {
490
574
  user: true,
491
- dt: CONFIG_AUTHORITATIVE_READ | CONFIG_DIRECT_COMMIT
575
+ At: CONFIG_AUTHORITATIVE_READ | CONFIG_DIRECT_COMMIT
492
576
  });
493
- if (t?.timeout !== undefined) f = setTimeout(() => settle(() => o(new TimeoutError)), t.timeout);
494
- if (i !== undefined) {
495
- a = () => settle(() => o(i.reason));
496
- i.addEventListener("abort", a, {
577
+ if (t?.timeout !== undefined) f = setTimeout(() => settle(() => i(new TimeoutError)), t.timeout);
578
+ if (o !== undefined) {
579
+ a = () => settle(() => i(o.reason));
580
+ o.addEventListener("abort", a, {
497
581
  once: true
498
582
  });
499
583
  }
@@ -610,7 +694,7 @@ function createOptimistic(e, t) {
610
694
  * on owner disposal
611
695
  */ function onSettled(e) {
612
696
  const t = getOwner();
613
- t && !(t.T & CONFIG_CHILDREN_FORBIDDEN) ? createTrackedEffect(() => untrack(e), undefined) : globalQueue.enqueue(EFFECT_USER, () => {
697
+ t && !(t.T & CONFIG_CHILDREN_FORBIDDEN) ? trackedEffect(() => untrack(e), undefined) : globalQueue.enqueue(EFFECT_USER, () => {
614
698
  // Unowned, out-of-band fire (no owner, or a children-forbidden one this
615
699
  // one-shot must not bind to): a returned cleanup has no lifecycle to
616
700
  // attach to. Reject it in dev; in production the return is simply