@solidjs/signals 0.13.10 → 0.13.12

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.
package/dist/dev.js CHANGED
@@ -145,21 +145,53 @@ function adjustHeight(el, heap) {
145
145
  }
146
146
  }
147
147
  const hooks = {};
148
- const DEV = {
148
+ const diagnosticListeners = new Set();
149
+ const diagnosticCaptures = new Set();
150
+ let diagnosticSequence = 0;
151
+ const diagnostics = {
152
+ subscribe(listener) {
153
+ diagnosticListeners.add(listener);
154
+ return () => diagnosticListeners.delete(listener);
155
+ },
156
+ capture() {
157
+ const events = [];
158
+ diagnosticCaptures.add(events);
159
+ return {
160
+ get events() {
161
+ return events;
162
+ },
163
+ clear() {
164
+ events.length = 0;
165
+ },
166
+ stop() {
167
+ diagnosticCaptures.delete(events);
168
+ return [...events];
169
+ }
170
+ };
171
+ }
172
+ };
173
+ const DEV$1 = {
149
174
  hooks: hooks,
175
+ diagnostics: diagnostics,
150
176
  getChildren: getChildren,
151
177
  getSignals: getSignals,
152
178
  getParent: getParent,
153
179
  getSources: getSources,
154
180
  getObservers: getObservers
155
181
  };
182
+ function emitDiagnostic(event) {
183
+ const entry = { sequence: ++diagnosticSequence, ...event };
184
+ for (const listener of diagnosticListeners) listener(entry);
185
+ for (const capture of diagnosticCaptures) capture.push(entry);
186
+ return entry;
187
+ }
156
188
  function registerGraph(value, owner) {
157
189
  value._owner = owner;
158
190
  if (owner) {
159
191
  if (!owner._signals) owner._signals = [];
160
192
  owner._signals.push(value);
161
193
  }
162
- DEV.hooks.onGraph?.(value, owner);
194
+ DEV$1.hooks.onGraph?.(value, owner);
163
195
  }
164
196
  function clearSignals(node) {
165
197
  node._signals = undefined;
@@ -252,13 +284,13 @@ function setTrackedQueueCallback(value) {
252
284
  function mergeTransitionState(target, outgoing) {
253
285
  outgoing._done = target;
254
286
  target._actions.push(...outgoing._actions);
255
- for (const lane of activeLanes) {
256
- if (lane._transition === outgoing) lane._transition = target;
257
- }
287
+ for (const lane of activeLanes) if (lane._transition === outgoing) lane._transition = target;
258
288
  target._optimisticNodes.push(...outgoing._optimisticNodes);
259
289
  for (const store of outgoing._optimisticStores) target._optimisticStores.add(store);
260
- for (const node of outgoing._asyncNodes) {
261
- if (!target._asyncNodes.includes(node)) target._asyncNodes.push(node);
290
+ for (const [source, reporters] of outgoing._asyncReporters) {
291
+ let targetReporters = target._asyncReporters.get(source);
292
+ if (!targetReporters) target._asyncReporters.set(source, (targetReporters = new Set()));
293
+ for (const reporter of reporters) targetReporters.add(reporter);
262
294
  }
263
295
  }
264
296
  function resolveOptimisticNodes(nodes) {
@@ -424,7 +456,7 @@ class GlobalQueue extends Queue {
424
456
  this.run(EFFECT_RENDER);
425
457
  runLaneEffects(EFFECT_USER);
426
458
  this.run(EFFECT_USER);
427
- if (true) DEV.hooks.onUpdate?.();
459
+ if (true) DEV$1.hooks.onUpdate?.();
428
460
  } finally {
429
461
  this._running = false;
430
462
  }
@@ -435,10 +467,11 @@ class GlobalQueue extends Queue {
435
467
  const actualError = error !== undefined ? error : node._error;
436
468
  if (activeTransition && actualError) {
437
469
  const source = actualError.source;
438
- if (!activeTransition._asyncNodes.includes(source)) {
439
- activeTransition._asyncNodes.push(source);
440
- schedule();
441
- }
470
+ let reporters = activeTransition._asyncReporters.get(source);
471
+ if (!reporters) activeTransition._asyncReporters.set(source, (reporters = new Set()));
472
+ const prevSize = reporters.size;
473
+ reporters.add(node);
474
+ if (reporters.size !== prevSize) schedule();
442
475
  }
443
476
  if (_enforceLoadingBoundary) _hitUnhandledAsync = true;
444
477
  }
@@ -454,7 +487,7 @@ class GlobalQueue extends Queue {
454
487
  activeTransition = transition ?? {
455
488
  _time: clock,
456
489
  _pendingNodes: [],
457
- _asyncNodes: [],
490
+ _asyncReporters: new Map(),
458
491
  _optimisticNodes: [],
459
492
  _optimisticStores: new Set(),
460
493
  _actions: [],
@@ -593,13 +626,37 @@ function flush() {
593
626
  function runQueue(queue, type) {
594
627
  for (let i = 0; i < queue.length; i++) queue[i](type);
595
628
  }
629
+ function reporterBlocksSource(reporter, source) {
630
+ if (reporter._flags & (REACTIVE_ZOMBIE | REACTIVE_DISPOSED)) return false;
631
+ if (reporter._pendingSource === source || reporter._pendingSources?.has(source)) return true;
632
+ for (let dep = reporter._deps; dep; dep = dep._nextDep) {
633
+ let current = dep._dep;
634
+ while (current) {
635
+ if (current === source || current._firewall === source) return true;
636
+ current = current._parentSource;
637
+ }
638
+ }
639
+ return !!(
640
+ reporter._statusFlags & STATUS_PENDING &&
641
+ reporter._error instanceof NotReadyError &&
642
+ reporter._error.source === source
643
+ );
644
+ }
596
645
  function transitionComplete(transition) {
597
646
  if (transition._done) return true;
598
647
  if (transition._actions.length) return false;
599
648
  let done = true;
600
- for (let i = 0; i < transition._asyncNodes.length; i++) {
601
- const node = transition._asyncNodes[i];
602
- if (node._statusFlags & STATUS_PENDING && node._error?.source === node) {
649
+ for (const [source, reporters] of transition._asyncReporters) {
650
+ let hasLive = false;
651
+ for (const reporter of reporters) {
652
+ if (reporterBlocksSource(reporter, source)) {
653
+ hasLive = true;
654
+ break;
655
+ }
656
+ reporters.delete(reporter);
657
+ }
658
+ if (!hasLive) transition._asyncReporters.delete(source);
659
+ else if (source._statusFlags & STATUS_PENDING && source._error?.source === source) {
603
660
  done = false;
604
661
  break;
605
662
  }
@@ -907,7 +964,7 @@ function createOwner(options) {
907
964
  parent._firstChild = owner;
908
965
  }
909
966
  }
910
- DEV.hooks.onOwner?.(owner);
967
+ DEV$1.hooks.onOwner?.(owner);
911
968
  return owner;
912
969
  }
913
970
  function createRoot(init, options) {
@@ -1473,7 +1530,7 @@ function computed(fn, initialValue, options) {
1473
1530
  context._firstChild = self;
1474
1531
  }
1475
1532
  }
1476
- DEV.hooks.onOwner?.(self);
1533
+ DEV$1.hooks.onOwner?.(self);
1477
1534
  if (parent) self._height = parent._height + 1;
1478
1535
  if (snapshotCaptureActive && ownerInSnapshotScope(context)) self._inSnapshotScope = true;
1479
1536
  if (externalSourceConfig) {
@@ -1618,10 +1675,20 @@ function read(el) {
1618
1675
  }
1619
1676
  const owner = el._firewall || el;
1620
1677
  if (strictRead && owner._statusFlags & STATUS_PENDING) {
1621
- throw new Error(
1678
+ const message =
1622
1679
  `Reading a pending async value directly in ${strictRead}. ` +
1623
- `Async values must be read within a tracking scope (JSX, a memo, or an effect's compute function).`
1624
- );
1680
+ `Async values must be read within a tracking scope (JSX, a memo, or an effect's compute function).`;
1681
+ emitDiagnostic({
1682
+ code: "PENDING_ASYNC_UNTRACKED_READ",
1683
+ kind: "async",
1684
+ severity: "error",
1685
+ message: message,
1686
+ ownerId: c?.id,
1687
+ ownerName: c?._name,
1688
+ nodeName: owner?._name,
1689
+ data: { strictRead: strictRead }
1690
+ });
1691
+ throw new Error(message);
1625
1692
  }
1626
1693
  if (c && tracking) {
1627
1694
  if (el._fn && el._flags & REACTIVE_DISPOSED) recompute(el);
@@ -1642,10 +1709,19 @@ function read(el) {
1642
1709
  if (owner._statusFlags & STATUS_PENDING) {
1643
1710
  if (c && !(stale && owner._transition && activeTransition !== owner._transition)) {
1644
1711
  if (c?._childrenForbidden) {
1645
- console.warn(
1712
+ const message =
1646
1713
  "Reading a pending async value inside createTrackedEffect or onSettled will throw. " +
1647
- "Use createEffect instead which supports async-aware reactivity."
1648
- );
1714
+ "Use createEffect instead which supports async-aware reactivity.";
1715
+ emitDiagnostic({
1716
+ code: "PENDING_ASYNC_FORBIDDEN_SCOPE",
1717
+ kind: "async",
1718
+ severity: "warn",
1719
+ message: message,
1720
+ ownerId: c.id,
1721
+ ownerName: c._name,
1722
+ nodeName: owner?._name
1723
+ });
1724
+ console.warn(message);
1649
1725
  }
1650
1726
  if (currentOptimisticLane) {
1651
1727
  const pendingLane = owner._optimisticLane;
@@ -1680,11 +1756,22 @@ function read(el) {
1680
1756
  return snapshot;
1681
1757
  }
1682
1758
  }
1683
- if (strictRead)
1684
- console.warn(
1759
+ if (strictRead) {
1760
+ const message =
1685
1761
  `Reactive value read directly in ${strictRead} will not update. ` +
1686
- `Move it into a tracking scope (JSX, a memo, or an effect's compute function).`
1687
- );
1762
+ `Move it into a tracking scope (JSX, a memo, or an effect's compute function).`;
1763
+ emitDiagnostic({
1764
+ code: "STRICT_READ_UNTRACKED",
1765
+ kind: "strict-read",
1766
+ severity: "warn",
1767
+ message: message,
1768
+ ownerId: c?.id,
1769
+ ownerName: c?._name,
1770
+ nodeName: owner?._name,
1771
+ data: { strictRead: strictRead }
1772
+ });
1773
+ console.warn(message);
1774
+ }
1688
1775
  if (el._overrideValue !== undefined && el._overrideValue !== NOT_PENDING) {
1689
1776
  if (c && stale && shouldReadStashedOptimisticValue(el)) return el._value;
1690
1777
  return el._overrideValue;
@@ -1701,8 +1788,21 @@ function read(el) {
1701
1788
  : el._pendingValue;
1702
1789
  }
1703
1790
  function setSignal(el, v) {
1704
- if (!el._pureWrite && !context?._childrenForbidden && context && el._firewall !== context)
1705
- console.warn("A Signal was written to in an owned scope.");
1791
+ if (!el._pureWrite && !context?._childrenForbidden && context && el._firewall !== context) {
1792
+ const message =
1793
+ "Writing to a Signal inside an owned scope (component, computation) is not allowed. " +
1794
+ "Move the write outside or set the `pureWrite` option if this is intentional.";
1795
+ emitDiagnostic({
1796
+ code: "SIGNAL_WRITE_IN_OWNED_SCOPE",
1797
+ kind: "write",
1798
+ severity: "error",
1799
+ message: message,
1800
+ ownerId: context.id,
1801
+ ownerName: context._name,
1802
+ nodeName: el._name
1803
+ });
1804
+ throw new Error(message);
1805
+ }
1706
1806
  if (el._transition && activeTransition !== el._transition)
1707
1807
  globalQueue.initTransition(el._transition);
1708
1808
  const isOptimistic = el._overrideValue !== undefined && !projectionWriteActive;
@@ -1749,10 +1849,19 @@ function setSignal(el, v) {
1749
1849
  return v;
1750
1850
  }
1751
1851
  function runWithOwner(owner, fn) {
1752
- if (owner && owner._flags & REACTIVE_DISPOSED)
1753
- console.warn(
1754
- "runWithOwner called with a disposed owner. Children created inside will never be disposed."
1755
- );
1852
+ if (owner && owner._flags & REACTIVE_DISPOSED) {
1853
+ const message =
1854
+ "runWithOwner called with a disposed owner. Children created inside will never be disposed.";
1855
+ emitDiagnostic({
1856
+ code: "RUN_WITH_DISPOSED_OWNER",
1857
+ kind: "owner",
1858
+ severity: "warn",
1859
+ message: message,
1860
+ ownerId: owner.id,
1861
+ ownerName: owner._name
1862
+ });
1863
+ console.warn(message);
1864
+ }
1756
1865
  const oldContext = context;
1757
1866
  const prevTracking = tracking;
1758
1867
  context = owner;
@@ -1953,6 +2062,14 @@ function effect(compute, effect, error, initialValue, options) {
1953
2062
  if (_hitUnhandledAsync) {
1954
2063
  resetUnhandledAsync();
1955
2064
  const err = new Error("An async value must be rendered inside a Loading boundary.");
2065
+ emitDiagnostic({
2066
+ code: "ASYNC_OUTSIDE_LOADING_BOUNDARY",
2067
+ kind: "async",
2068
+ severity: "error",
2069
+ message: err.message,
2070
+ ownerId: node.id,
2071
+ ownerName: node._name
2072
+ });
1956
2073
  if (!node._queue.notify(node, STATUS_ERROR, STATUS_ERROR)) throw err;
1957
2074
  }
1958
2075
  }
@@ -1964,8 +2081,19 @@ function effect(compute, effect, error, initialValue, options) {
1964
2081
  : runEffect.call(node));
1965
2082
  initialized = true;
1966
2083
  cleanup(() => node._cleanup?.());
1967
- if (!node._parent)
1968
- console.warn("Effects created outside a reactive context will never be disposed");
2084
+ if (!node._parent) {
2085
+ const message = "Effects created outside a reactive context will never be disposed";
2086
+ emitDiagnostic({
2087
+ code: "NO_OWNER_EFFECT",
2088
+ kind: "lifecycle",
2089
+ severity: "warn",
2090
+ message: message,
2091
+ ownerId: node.id,
2092
+ ownerName: node._name,
2093
+ data: { effectType: "effect" }
2094
+ });
2095
+ console.warn(message);
2096
+ }
1969
2097
  }
1970
2098
  function runEffect() {
1971
2099
  if (!this._modified || this._flags & REACTIVE_DISPOSED) return;
@@ -2034,8 +2162,19 @@ function trackedEffect(fn, options) {
2034
2162
  node._run = run;
2035
2163
  node._queue.enqueue(EFFECT_USER, run);
2036
2164
  cleanup(() => node._cleanup?.());
2037
- if (!node._parent)
2038
- console.warn("Effects created outside a reactive context will never be disposed");
2165
+ if (!node._parent) {
2166
+ const message = "Effects created outside a reactive context will never be disposed";
2167
+ emitDiagnostic({
2168
+ code: "NO_OWNER_EFFECT",
2169
+ kind: "lifecycle",
2170
+ severity: "warn",
2171
+ message: message,
2172
+ ownerId: node.id,
2173
+ ownerName: node._name,
2174
+ data: { effectType: "trackedEffect" }
2175
+ });
2176
+ console.warn(message);
2177
+ }
2039
2178
  }
2040
2179
  function restoreTransition(transition, fn) {
2041
2180
  globalQueue.initTransition(transition);
@@ -2084,11 +2223,28 @@ function action(genFn) {
2084
2223
  function onCleanup(fn) {
2085
2224
  {
2086
2225
  const owner = getOwner();
2087
- if (!owner) console.warn("onCleanup called outside a reactive context will never be run");
2088
- else if (owner._childrenForbidden)
2089
- throw new Error(
2090
- "Cannot use onCleanup inside createTrackedEffect or onSettled; return a cleanup function instead"
2091
- );
2226
+ if (!owner) {
2227
+ const message = "onCleanup called outside a reactive context will never be run";
2228
+ emitDiagnostic({
2229
+ code: "NO_OWNER_CLEANUP",
2230
+ kind: "lifecycle",
2231
+ severity: "warn",
2232
+ message: message
2233
+ });
2234
+ console.warn(message);
2235
+ } else if (owner._childrenForbidden) {
2236
+ const message =
2237
+ "Cannot use onCleanup inside createTrackedEffect or onSettled; return a cleanup function instead";
2238
+ emitDiagnostic({
2239
+ code: "CLEANUP_IN_FORBIDDEN_SCOPE",
2240
+ kind: "lifecycle",
2241
+ severity: "error",
2242
+ message: message,
2243
+ ownerId: owner.id,
2244
+ ownerName: owner._name
2245
+ });
2246
+ throw new Error(message);
2247
+ }
2092
2248
  }
2093
2249
  return cleanup(fn);
2094
2250
  }
@@ -2610,11 +2766,20 @@ const storeTraps = {
2610
2766
  );
2611
2767
  }
2612
2768
  }
2613
- if (strictRead && typeof property === "string")
2614
- console.warn(
2769
+ if (strictRead && typeof property === "string") {
2770
+ const message =
2615
2771
  `Reactive value read directly in ${strictRead} will not update. ` +
2616
- `Move it into a tracking scope (JSX, a memo, or an effect's compute function).`
2617
- );
2772
+ `Move it into a tracking scope (JSX, a memo, or an effect's compute function).`;
2773
+ emitDiagnostic({
2774
+ code: "STRICT_READ_UNTRACKED",
2775
+ kind: "strict-read",
2776
+ severity: "warn",
2777
+ message: message,
2778
+ nodeName: String(property),
2779
+ data: { strictRead: strictRead, property: String(property), source: "store" }
2780
+ });
2781
+ console.warn(message);
2782
+ }
2618
2783
  return isWrappable(value) ? wrap(value, target) : value;
2619
2784
  },
2620
2785
  has(target, property) {
@@ -2703,7 +2868,7 @@ const storeTraps = {
2703
2868
  lengthValue !== undefined && nodes.length && setSignal(nodes.length, lengthValue);
2704
2869
  }
2705
2870
  nodes[$TRACK] && setSignal(nodes[$TRACK], undefined);
2706
- if (true) DEV.hooks.onStoreNodeUpdate?.(target[$PROXY], property, value, prev);
2871
+ if (true) DEV$1.hooks.onStoreNodeUpdate?.(target[$PROXY], property, value, prev);
2707
2872
  });
2708
2873
  }
2709
2874
  return true;
@@ -3577,8 +3742,17 @@ class CollectionQueue extends Queue {
3577
3742
  }
3578
3743
  }
3579
3744
  function createCollectionBoundary(type, fn, fallback, onFn) {
3580
- if (!getOwner())
3581
- console.warn("Boundaries created outside a reactive context will never be disposed.");
3745
+ if (!getOwner()) {
3746
+ const message = "Boundaries created outside a reactive context will never be disposed.";
3747
+ emitDiagnostic({
3748
+ code: "NO_OWNER_BOUNDARY",
3749
+ kind: "lifecycle",
3750
+ severity: "warn",
3751
+ message: message,
3752
+ data: { boundaryType: type === STATUS_PENDING ? "loading" : "error" }
3753
+ });
3754
+ console.warn(message);
3755
+ }
3582
3756
  const owner = createOwner();
3583
3757
  if (_revealUsed) setContext(RevealControllerContext, null, owner);
3584
3758
  const queue = new CollectionQueue(type);
@@ -3704,6 +3878,7 @@ function flattenArray(children, results = [], options) {
3704
3878
  if (notReady) throw notReady;
3705
3879
  return needsUnwrap;
3706
3880
  }
3881
+ const DEV = DEV$1;
3707
3882
  export {
3708
3883
  $PROXY,
3709
3884
  $REFRESH,