@solidjs/signals 2.0.0-beta.17 → 2.0.0-beta.18

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
@@ -39,6 +39,7 @@ const REACTIVE_OPTIMISTIC_DIRTY = 1 << 7;
39
39
  const REACTIVE_SNAPSHOT_STALE = 1 << 8;
40
40
  const REACTIVE_LAZY = 1 << 9;
41
41
  const REACTIVE_MANUAL_WRITE = 1 << 10;
42
+ const REACTIVE_REASK = 1 << 11;
42
43
  const CONFIG_OWNED_WRITE = 1 << 0;
43
44
  const CONFIG_NO_SNAPSHOT = 1 << 1;
44
45
  const CONFIG_TRANSPARENT = 1 << 2;
@@ -256,6 +257,7 @@ function canUseSimpleSyncFlush(queue) {
256
257
  activeLanes.size === 0 &&
257
258
  queue._children.length === 0 &&
258
259
  queue._optimisticNodes.length === 0 &&
260
+ queue._affectsNodes.length === 0 &&
259
261
  queue._optimisticStores.size === 0 &&
260
262
  transientStoreNodes.size === 0
261
263
  );
@@ -269,6 +271,7 @@ function sweepTransientStoreNodes() {
269
271
  }
270
272
  if (node._pendingValue !== NOT_PENDING) continue;
271
273
  if (node._overrideValue !== undefined && node._overrideValue !== NOT_PENDING) continue;
274
+ if (node._affectsCount) continue;
272
275
  transientStoreNodes.delete(node);
273
276
  node._unobserved?.();
274
277
  }
@@ -319,6 +322,10 @@ function mergeTransitionState(target, outgoing) {
319
322
  target._actions.push(...outgoing._actions);
320
323
  for (const lane of activeLanes) if (lane._transition === outgoing) lane._transition = target;
321
324
  target._optimisticNodes.push(...outgoing._optimisticNodes);
325
+ if (outgoing._affectsNodes.length) {
326
+ target._affectsNodes.push(...outgoing._affectsNodes);
327
+ outgoing._affectsNodes.length = 0;
328
+ }
322
329
  for (const store of outgoing._optimisticStores) target._optimisticStores.add(store);
323
330
  for (const [source, reporters] of outgoing._asyncReporters) {
324
331
  let targetReporters = target._asyncReporters.get(source);
@@ -469,11 +476,15 @@ class GlobalQueue extends Queue {
469
476
  _pendingNode = null;
470
477
  _pendingNodes = [];
471
478
  _optimisticNodes = [];
479
+ _affectsNodes = [];
472
480
  _optimisticStores = new Set();
473
481
  static _update;
474
482
  static _dispose;
475
483
  static _runEffect;
476
484
  static _clearOptimisticStore = null;
485
+ static _releaseAffectsScope = null;
486
+ static _propagateAffects = null;
487
+ static _settleAffects = null;
477
488
  flush() {
478
489
  if (this._running) return;
479
490
  this._running = true;
@@ -488,6 +499,7 @@ class GlobalQueue extends Queue {
488
499
  this._pendingNode = null;
489
500
  this._pendingNodes = [];
490
501
  this._optimisticNodes = [];
502
+ this._affectsNodes = [];
491
503
  this._optimisticStores = new Set();
492
504
  runLaneEffects(EFFECT_RENDER);
493
505
  runLaneEffects(EFFECT_USER);
@@ -593,6 +605,7 @@ class GlobalQueue extends Queue {
593
605
  _pendingNodes: [],
594
606
  _asyncReporters: createAsyncReporters(),
595
607
  _optimisticNodes: [],
608
+ _affectsNodes: [],
596
609
  _optimisticStores: new Set(),
597
610
  _actions: [],
598
611
  _queueStash: { _queues: [[], []], _children: [] },
@@ -628,6 +641,10 @@ class GlobalQueue extends Queue {
628
641
  }
629
642
  this._optimisticNodes = activeTransition._optimisticNodes;
630
643
  }
644
+ if (this._affectsNodes !== activeTransition._affectsNodes) {
645
+ activeTransition._affectsNodes.push(...this._affectsNodes);
646
+ this._affectsNodes = activeTransition._affectsNodes;
647
+ }
631
648
  for (const lane of activeLanes) {
632
649
  if (!lane._transition) lane._transition = activeTransition;
633
650
  }
@@ -652,10 +669,16 @@ function queuePendingNode(node) {
652
669
  }
653
670
  globalQueue._pendingNodes.push(node);
654
671
  }
672
+ let reaskArmed = false;
673
+ function armReaskClear() {
674
+ reaskArmed = true;
675
+ }
655
676
  function insertSubs(node, optimistic = false) {
656
677
  const sourceLane = node._optimisticLane || currentOptimisticLane;
657
678
  const hasSnapshot = node._snapshotValue !== undefined;
679
+ const clearReask = reaskArmed;
658
680
  for (let s = node._subs; s !== null; s = s._nextSub) {
681
+ if (clearReask) s._sub._flags &= ~REACTIVE_REASK;
659
682
  if (hasSnapshot && s._sub._config & CONFIG_IN_SNAPSHOT_SCOPE) {
660
683
  s._sub._flags |= REACTIVE_SNAPSHOT_STALE;
661
684
  continue;
@@ -739,6 +762,9 @@ function finalizePureQueue(completingTransition = null, incomplete = false) {
739
762
  }
740
763
  completingTransition._gatedSubs.clear();
741
764
  }
765
+ releaseAffectsMarks(
766
+ completingTransition ? completingTransition._affectsNodes : globalQueue._affectsNodes
767
+ );
742
768
  const optimisticStores = completingTransition
743
769
  ? completingTransition._optimisticStores
744
770
  : globalQueue._optimisticStores;
@@ -763,6 +789,32 @@ function trackOptimisticStore(store) {
763
789
  globalQueue._optimisticStores.add(store);
764
790
  schedule();
765
791
  }
792
+ let activeAffectsMarks = 0;
793
+ function markAffects(node) {
794
+ node._affectsCount = (node._affectsCount || 0) + 1;
795
+ activeAffectsMarks++;
796
+ if (node._affectsCount === 1) updatePendingSignal(node);
797
+ }
798
+ function registerAffectsMark(node) {
799
+ markAffects(node);
800
+ globalQueue._affectsNodes.push(node);
801
+ GlobalQueue._propagateAffects?.(node);
802
+ schedule();
803
+ }
804
+ function releaseAffectsMark(node) {
805
+ activeAffectsMarks--;
806
+ node._affectsCount--;
807
+ if (!node._affectsCount) {
808
+ GlobalQueue._settleAffects?.(node);
809
+ snapCompanionsToState(node);
810
+ GlobalQueue._releaseAffectsScope?.(node);
811
+ }
812
+ }
813
+ function releaseAffectsMarks(nodes) {
814
+ if (!nodes.length) return;
815
+ for (let i = 0; i < nodes.length; i++) releaseAffectsMark(nodes[i]);
816
+ nodes.length = 0;
817
+ }
766
818
  function reassignPendingTransition(pendingNodes) {
767
819
  for (let i = 0; i < pendingNodes.length; i++) {
768
820
  pendingNodes[i]._transition = activeTransition;
@@ -842,7 +894,8 @@ function transitionComplete(transition) {
842
894
  hasActiveOverride(node) &&
843
895
  "_statusFlags" in node &&
844
896
  node._statusFlags & STATUS_PENDING &&
845
- node._error instanceof NotReadyError
897
+ node._error instanceof NotReadyError &&
898
+ !node._error.source?._affectsFor
846
899
  ) {
847
900
  done = false;
848
901
  break;
@@ -1279,7 +1332,7 @@ function setPendingError(el, source, error) {
1279
1332
  }
1280
1333
  function forEachDependent(el, fn) {
1281
1334
  for (let s = el._subs; s !== null; s = s._nextSub) fn(s._sub, s);
1282
- for (let child = el._child; child !== null; child = child._nextChild) {
1335
+ for (let child = el._child ?? null; child !== null; child = child._nextChild) {
1283
1336
  for (let s = child._subs; s !== null; s = s._nextSub) fn(s._sub, s);
1284
1337
  }
1285
1338
  }
@@ -1296,21 +1349,22 @@ function enqueueForRerun(node) {
1296
1349
  insertIntoHeap(node, queue);
1297
1350
  }
1298
1351
  }
1299
- function settlePendingSource(el) {
1352
+ function settlePendingSource(el, source = el, snap = false) {
1300
1353
  let scheduled = false;
1301
1354
  const visited = new Set();
1355
+ const updateCompanions = snap ? snapCompanionsToState : updatePendingSignal;
1302
1356
  const settle = node => {
1303
- if (visited.has(node) || !removePendingSource(node, el)) return;
1357
+ if (visited.has(node) || !removePendingSource(node, source)) return;
1304
1358
  visited.add(node);
1305
1359
  node._time = clock;
1306
- const source = node._pendingSource ?? node._pendingSources?.values().next().value;
1307
- if (source) {
1308
- setPendingError(node, source);
1309
- updatePendingSignal(node);
1360
+ const remaining = node._pendingSource ?? node._pendingSources?.values().next().value;
1361
+ if (remaining) {
1362
+ setPendingError(node, remaining);
1363
+ updateCompanions(node);
1310
1364
  } else {
1311
1365
  node._statusFlags &= ~STATUS_PENDING;
1312
1366
  setPendingError(node);
1313
- updatePendingSignal(node);
1367
+ updateCompanions(node);
1314
1368
  if (node._blocked) {
1315
1369
  enqueueForRerun(node);
1316
1370
  scheduled = true;
@@ -1510,6 +1564,7 @@ function handleAsync(el, result, setter) {
1510
1564
  function clearStatus(el, clearUninitialized = false) {
1511
1565
  if (el._pendingSource || el._pendingSources) clearPendingSources(el);
1512
1566
  if (el._blocked) el._blocked = false;
1567
+ el._reask = false;
1513
1568
  el._statusFlags = clearUninitialized ? 0 : el._statusFlags & STATUS_UNINITIALIZED;
1514
1569
  if (el._error) setPendingError(el);
1515
1570
  if (el._pendingSignal || el._latestValueComputed) updatePendingSignal(el);
@@ -1579,6 +1634,48 @@ function notifyStatus(el, status, error, blockStatus, lane) {
1579
1634
  }
1580
1635
  });
1581
1636
  }
1637
+ function getAffectsSentinel(node) {
1638
+ return (node._affectsSentinel ??= {
1639
+ _name: "affects-sentinel",
1640
+ _affectsFor: node,
1641
+ _flags: 0,
1642
+ _statusFlags: 0,
1643
+ _reask: false,
1644
+ _error: undefined,
1645
+ _subs: null,
1646
+ _deps: null
1647
+ });
1648
+ }
1649
+ function propagateAffectsMark(node) {
1650
+ if (!node._subs && !node._child) return;
1651
+ const sentinel = getAffectsSentinel(node);
1652
+ const error = new NotReadyError(sentinel);
1653
+ forEachDependent(node, sub => {
1654
+ if (sub._pendingSource !== sentinel && !sub._pendingSources?.has(sentinel)) {
1655
+ if (!sub._transition) queuePendingNode(sub);
1656
+ notifyStatus(sub, STATUS_PENDING, error);
1657
+ }
1658
+ });
1659
+ }
1660
+ function applyAffectsReads(el, sources) {
1661
+ let applied = false;
1662
+ for (let i = 0; i < sources.length; i++) {
1663
+ const src = sources[i];
1664
+ if (!src._affectsCount) continue;
1665
+ const sentinel = getAffectsSentinel(src);
1666
+ if (addPendingSource(el, sentinel)) {
1667
+ el._statusFlags |= STATUS_PENDING;
1668
+ setPendingError(el, sentinel);
1669
+ applied = true;
1670
+ }
1671
+ }
1672
+ if (applied) updatePendingSignal(el);
1673
+ }
1674
+ GlobalQueue._propagateAffects = propagateAffectsMark;
1675
+ GlobalQueue._settleAffects = node => {
1676
+ const sentinel = node._affectsSentinel;
1677
+ if (sentinel) settlePendingSource(node, sentinel, true);
1678
+ };
1582
1679
  let externalSourceConfig = null;
1583
1680
  function enableExternalSource(config) {
1584
1681
  const { factory: factory, untrack: untrackFn = fn => fn() } = config;
@@ -1619,6 +1716,7 @@ let latestReadActive = false;
1619
1716
  let context = null;
1620
1717
  let currentOptimisticLane = null;
1621
1718
  let pendingProbe = null;
1719
+ let affectsReads = null;
1622
1720
  let snapshotCaptureActive = false;
1623
1721
  let snapshotSources = null;
1624
1722
  function ownerInSnapshotScope(owner) {
@@ -1691,8 +1789,9 @@ function recompute(el, create = false) {
1691
1789
  }
1692
1790
  let isOptimisticDirty = !!(el._flags & REACTIVE_OPTIMISTIC_DIRTY);
1693
1791
  const hasOverride = el._overrideValue !== undefined && el._overrideValue !== NOT_PENDING;
1694
- !!(el._statusFlags & STATUS_PENDING);
1792
+ const wasPending = !!(el._statusFlags & STATUS_PENDING);
1695
1793
  const wasUninitialized = !!(el._statusFlags & STATUS_UNINITIALIZED);
1794
+ const isReask = !!(el._flags & REACTIVE_REASK) && !(wasPending && !el._reask);
1696
1795
  const oldcontext = context;
1697
1796
  context = el;
1698
1797
  el._depsTail = null;
@@ -1702,6 +1801,9 @@ function recompute(el, create = false) {
1702
1801
  let value = el._pendingValue === NOT_PENDING ? el._value : el._pendingValue;
1703
1802
  let oldHeight = el._height;
1704
1803
  let prevTracking = tracking;
1804
+ const prevAffectsReads = affectsReads;
1805
+ affectsReads = null;
1806
+ let markedReads = null;
1705
1807
  let prevLane = currentOptimisticLane;
1706
1808
  let prevStrictRead = false;
1707
1809
  {
@@ -1757,7 +1859,12 @@ function recompute(el, create = false) {
1757
1859
  updatePendingSignal(lane._source);
1758
1860
  }
1759
1861
  }
1760
- if (e instanceof NotReadyError) el._blocked = true;
1862
+ let reaskChanged = false;
1863
+ if (e instanceof NotReadyError) {
1864
+ el._blocked = true;
1865
+ reaskChanged = wasPending && el._reask !== isReask;
1866
+ el._reask = isReask;
1867
+ }
1761
1868
  notifyStatus(
1762
1869
  el,
1763
1870
  e instanceof NotReadyError ? STATUS_PENDING : STATUS_ERROR,
@@ -1765,12 +1872,15 @@ function recompute(el, create = false) {
1765
1872
  undefined,
1766
1873
  e instanceof NotReadyError ? el._optimisticLane : undefined
1767
1874
  );
1875
+ if (reaskChanged) repollDownstreamVerdicts(el);
1768
1876
  } finally {
1769
1877
  tracking = prevTracking;
1770
1878
  strictRead = prevStrictRead;
1771
1879
  if (isStaleEffect) stale = prevStale;
1772
1880
  el._flags = REACTIVE_NONE | (create ? el._flags & REACTIVE_SNAPSHOT_STALE : 0);
1773
1881
  context = oldcontext;
1882
+ markedReads = affectsReads;
1883
+ affectsReads = prevAffectsReads;
1774
1884
  }
1775
1885
  if (!el._error) {
1776
1886
  trimStaleDeps(el);
@@ -1816,6 +1926,7 @@ function recompute(el, create = false) {
1816
1926
  }
1817
1927
  }
1818
1928
  currentOptimisticLane = prevLane;
1929
+ if (markedReads) applyAffectsReads(el, markedReads);
1819
1930
  const needsPendingCommit =
1820
1931
  el._pendingValue !== NOT_PENDING ||
1821
1932
  el._pendingFirstChild !== null ||
@@ -1892,7 +2003,8 @@ function computed(fn, options) {
1892
2003
  _pendingDisposal: null,
1893
2004
  _pendingFirstChild: null,
1894
2005
  _inFlight: null,
1895
- _transition: null
2006
+ _transition: null,
2007
+ _reask: false
1896
2008
  };
1897
2009
  self._name = options?.name ?? "computed";
1898
2010
  setupComputedNode(self, options);
@@ -1938,6 +2050,7 @@ function createEffectNode(fn, effectFn, errorFn, type, notifyStatus, options) {
1938
2050
  _pendingFirstChild: null,
1939
2051
  _inFlight: null,
1940
2052
  _transition: null,
2053
+ _reask: false,
1941
2054
  _modified: false,
1942
2055
  _prevValue: undefined,
1943
2056
  _effectFn: effectFn,
@@ -2127,7 +2240,11 @@ function read(el) {
2127
2240
  !snapshotCaptureActive &&
2128
2241
  !strictRead
2129
2242
  ) {
2130
- if (c && tracking) link(el, c);
2243
+ if (c && tracking) {
2244
+ link(el, c);
2245
+ if (activeAffectsMarks !== 0 && el._affectsCount && !pendingCheckActive)
2246
+ (affectsReads ??= []).push(el);
2247
+ }
2131
2248
  return !c || el._pendingValue === NOT_PENDING ? el._value : el._pendingValue;
2132
2249
  }
2133
2250
  if (strictRead && owner._statusFlags & STATUS_PENDING) {
@@ -2148,6 +2265,8 @@ function read(el) {
2148
2265
  }
2149
2266
  if (c && tracking) {
2150
2267
  link(el, c, pendingCheckActive);
2268
+ if (activeAffectsMarks !== 0 && el._affectsCount && !pendingCheckActive)
2269
+ (affectsReads ??= []).push(el);
2151
2270
  if (owner._fn) {
2152
2271
  const isZombie = el._flags & REACTIVE_ZOMBIE;
2153
2272
  if (owner._height >= (isZombie ? zombieQueue._min : dirtyQueue._min)) {
@@ -2382,29 +2501,48 @@ function collectPendingSources(el) {
2382
2501
  const owner = el._firewall || el;
2383
2502
  if (owner !== el) pendingProbe.sources.add(owner);
2384
2503
  }
2504
+ function witnessAffects(node) {
2505
+ pendingProbe?.sources.add(node);
2506
+ }
2507
+ function quietPending(el) {
2508
+ if (el._pendingSources) {
2509
+ for (const source of el._pendingSources) if (!source._reask) return false;
2510
+ return true;
2511
+ }
2512
+ if (el._pendingSource) return el._pendingSource._reask;
2513
+ return el._reask;
2514
+ }
2515
+ function newQuestionInFlight(comp) {
2516
+ return (
2517
+ !!(comp._statusFlags & STATUS_PENDING) &&
2518
+ !(comp._statusFlags & STATUS_UNINITIALIZED) &&
2519
+ !quietPending(comp)
2520
+ );
2521
+ }
2385
2522
  function computePendingState(el) {
2386
2523
  const comp = el;
2387
2524
  if (comp._flags & REACTIVE_DISPOSED) return false;
2525
+ if (el._affectsCount) return true;
2388
2526
  const firewall = el._firewall;
2389
- if ((firewall || comp)._optimisticMask) return false;
2390
2527
  if (el._parentSource) {
2391
2528
  const parentNode = el._parentSource;
2392
- if (hasActiveOverride(parentNode)) return false;
2529
+ if (parentNode._affectsCount) return true;
2393
2530
  const parent = parentNode._firewall || parentNode;
2394
- if (parent._optimisticMask) return false;
2395
- return !!(
2396
- parent._statusFlags & STATUS_PENDING && !(parent._statusFlags & STATUS_UNINITIALIZED)
2397
- );
2531
+ return newQuestionInFlight(parent);
2398
2532
  }
2399
- if (hasActiveOverride(el)) return false;
2400
- if (firewall && el._pendingValue !== NOT_PENDING) {
2533
+ if (firewall && el._pendingValue !== NOT_PENDING && !hasActiveOverride(el)) {
2401
2534
  return (
2402
2535
  !!(firewall._flags & REACTIVE_MANUAL_WRITE) ||
2403
- (!firewall._inFlight && !(firewall._statusFlags & STATUS_PENDING))
2536
+ (!firewall._inFlight && !(firewall._statusFlags & STATUS_PENDING)) ||
2537
+ (!!(firewall._statusFlags & STATUS_PENDING) && quietPending(firewall))
2404
2538
  );
2405
2539
  }
2406
- if (el._pendingValue !== NOT_PENDING && !(comp._statusFlags & STATUS_UNINITIALIZED)) return true;
2407
- return !!(comp._statusFlags & STATUS_PENDING && !(comp._statusFlags & STATUS_UNINITIALIZED));
2540
+ if (el._pendingValue !== NOT_PENDING && !(comp._statusFlags & STATUS_UNINITIALIZED)) {
2541
+ if (hasActiveOverride(el))
2542
+ return !el._equals || !el._equals(el._pendingValue, el._overrideValue);
2543
+ return true;
2544
+ }
2545
+ return newQuestionInFlight(comp);
2408
2546
  }
2409
2547
  function syncCompanions(el, value) {
2410
2548
  if (el._pendingSignal) updatePendingSignal(el);
@@ -2421,6 +2559,19 @@ function updateChildCompanions(el) {
2421
2559
  if (child._pendingSignal || child._latestValueComputed) updatePendingSignal(child);
2422
2560
  }
2423
2561
  }
2562
+ function repollDownstreamVerdicts(el) {
2563
+ const visited = new Set();
2564
+ const visit = node => {
2565
+ if (visited.has(node)) return;
2566
+ visited.add(node);
2567
+ if (node._pendingSignal || node._latestValueComputed) updatePendingSignal(node);
2568
+ for (let s = node._subs; s !== null; s = s._nextSub) visit(s._sub);
2569
+ for (let child = node._child ?? null; child !== null; child = child._nextChild) {
2570
+ visit(child);
2571
+ }
2572
+ };
2573
+ visit(el);
2574
+ }
2424
2575
  function snapCompanionsToState(owner) {
2425
2576
  const sig = owner._pendingSignal;
2426
2577
  if (sig && (sig._overrideValue === undefined || sig._overrideValue === NOT_PENDING)) {
@@ -2508,8 +2659,9 @@ function isPending(fn) {
2508
2659
  } catch (e) {
2509
2660
  collectPending();
2510
2661
  if (e instanceof NotReadyError) {
2511
- if (probe.found && !(e.source?._statusFlags & STATUS_UNINITIALIZED)) return true;
2512
- if (context) throw e;
2662
+ const uninitialized = !!(e.source?._statusFlags & STATUS_UNINITIALIZED);
2663
+ if (probe.found && !uninitialized) return true;
2664
+ if (context && uninitialized) throw e;
2513
2665
  }
2514
2666
  return probe.found;
2515
2667
  } finally {
@@ -2554,6 +2706,10 @@ function refresh(target) {
2554
2706
  typeof node._fn === "function" &&
2555
2707
  !(node._flags & (REACTIVE_DISPOSED | REACTIVE_MANUAL_WRITE))
2556
2708
  ) {
2709
+ if (!(node._flags & (REACTIVE_DIRTY | REACTIVE_CHECK | REACTIVE_IN_HEAP))) {
2710
+ node._flags |= REACTIVE_REASK;
2711
+ armReaskClear();
2712
+ }
2557
2713
  node._flags = (node._flags & ~REACTIVE_CHECK) | REACTIVE_DIRTY;
2558
2714
  insertIntoHeap(node, node._flags & REACTIVE_ZOMBIE ? zombieQueue : dirtyQueue);
2559
2715
  schedule();
@@ -3451,7 +3607,8 @@ function createWriteTraps(isActive, onDraftWrite) {
3451
3607
  const $TRACK = Symbol("STORE_TRACK"),
3452
3608
  $TARGET = Symbol("STORE_TARGET"),
3453
3609
  $PROXY = Symbol("STORE_PROXY"),
3454
- $DELETED = Symbol("STORE_DELETED");
3610
+ $DELETED = Symbol("STORE_DELETED"),
3611
+ $AFFECTS = Symbol("STORE_AFFECTS");
3455
3612
  const STORE_VALUE = "v",
3456
3613
  STORE_OVERRIDE = "o",
3457
3614
  STORE_OPTIMISTIC_OVERRIDE = "x",
@@ -3461,8 +3618,7 @@ const STORE_VALUE = "v",
3461
3618
  STORE_WRAP = "w",
3462
3619
  STORE_LOOKUP = "l",
3463
3620
  STORE_FIREWALL = "f",
3464
- STORE_OPTIMISTIC = "p",
3465
- STORE_MASKED = "m";
3621
+ STORE_OPTIMISTIC = "p";
3466
3622
  const STORE_SELF_PENDING = Symbol("STORE_SELF_PENDING");
3467
3623
  function createStoreProxy(value, traps = storeTraps, extend) {
3468
3624
  let newTarget;
@@ -3558,7 +3714,7 @@ function getNodes(target, type) {
3558
3714
  if (!nodes) target[type] = nodes = Object.create(null);
3559
3715
  return nodes;
3560
3716
  }
3561
- function getNode(nodes, property, value, firewall, equals = isEqual, optimistic, snapshotProps) {
3717
+ function getNode(target, nodes, property, value, equals = isEqual, snapshotProps) {
3562
3718
  if (nodes[property]) return nodes[property];
3563
3719
  const s = signal(
3564
3720
  value,
@@ -3570,12 +3726,13 @@ function getNode(nodes, property, value, firewall, equals = isEqual, optimistic,
3570
3726
  if (
3571
3727
  typeof property === "symbol" &&
3572
3728
  property !== $TRACK &&
3729
+ property !== $AFFECTS &&
3573
3730
  symbolKeyedRecords.has(nodes)
3574
3731
  ) {
3575
3732
  const syms = Object.getOwnPropertySymbols(nodes);
3576
3733
  let hasUserSymbol = false;
3577
3734
  for (let i = 0, len = syms.length; i < len; i++) {
3578
- if (syms[i] !== $TRACK) {
3735
+ if (syms[i] !== $TRACK && syms[i] !== $AFFECTS) {
3579
3736
  hasUserSymbol = true;
3580
3737
  break;
3581
3738
  }
@@ -3585,9 +3742,9 @@ function getNode(nodes, property, value, firewall, equals = isEqual, optimistic,
3585
3742
  }
3586
3743
  }
3587
3744
  },
3588
- firewall
3745
+ target[STORE_FIREWALL]
3589
3746
  );
3590
- if (optimistic) {
3747
+ if (target[STORE_OPTIMISTIC]) {
3591
3748
  s._overrideValue = NOT_PENDING;
3592
3749
  }
3593
3750
  if (snapshotProps && property in snapshotProps) {
@@ -3595,21 +3752,92 @@ function getNode(nodes, property, value, firewall, equals = isEqual, optimistic,
3595
3752
  s._snapshotValue = sv === undefined ? NO_SNAPSHOT : sv;
3596
3753
  snapshotSources?.add(s);
3597
3754
  }
3598
- if (typeof property === "symbol" && property !== $TRACK) symbolKeyedRecords.add(nodes);
3755
+ if (typeof property === "symbol" && property !== $TRACK && property !== $AFFECTS)
3756
+ symbolKeyedRecords.add(nodes);
3757
+ if (property !== $AFFECTS && affectsScopes.size) inheritAffectsMarks(s, target[STORE_VALUE]);
3599
3758
  return (nodes[property] = s);
3600
3759
  }
3760
+ function inheritAffectsMarks(node, raw) {
3761
+ for (const [carrier, entry] of affectsScopes) {
3762
+ if (carrier._affectsCount && entry.scope.has(raw)) {
3763
+ markAffects(node);
3764
+ entry.inherited.push(node);
3765
+ }
3766
+ }
3767
+ }
3768
+ const affectsScopes = new Map();
3769
+ function walkAffectsScope(value, entry, found, lookup, visited) {
3770
+ if (!isWrappable(value)) return;
3771
+ const target = value[$TARGET] || (lookup ?? storeLookup).get(value)?.[$TARGET];
3772
+ const raw = target ? target[STORE_VALUE] : value;
3773
+ if (visited.has(raw)) return;
3774
+ visited.add(raw);
3775
+ entry.scope.add(raw);
3776
+ let override;
3777
+ if (target) {
3778
+ collectRecordNodes(target[STORE_NODE], found);
3779
+ collectRecordNodes(target[STORE_HAS], found);
3780
+ override = mergedOverlay(target);
3781
+ lookup = target[STORE_LOOKUP] ?? lookup;
3782
+ }
3783
+ if (Array.isArray(raw)) {
3784
+ const len = override?.length ?? raw.length;
3785
+ for (let i = 0; i < len; i++) {
3786
+ const v = override && i in override ? override[i] : raw[i];
3787
+ if (v !== $DELETED) walkAffectsScope(v, entry, found, lookup, visited);
3788
+ }
3789
+ } else {
3790
+ const keys = getKeys(raw, override);
3791
+ for (let i = 0, l = keys.length; i < l; i++) {
3792
+ const desc = getPropertyDescriptor(raw, override, keys[i]);
3793
+ if (!desc || desc.get) continue;
3794
+ walkAffectsScope(desc.value, entry, found, lookup, visited);
3795
+ }
3796
+ }
3797
+ }
3798
+ function collectRecordNodes(nodes, found) {
3799
+ if (!nodes) return;
3800
+ for (const key of Object.keys(nodes)) found.push(nodes[key]);
3801
+ const syms = Object.getOwnPropertySymbols(nodes);
3802
+ for (let i = 0, l = syms.length; i < l; i++) {
3803
+ if (syms[i] !== $AFFECTS) found.push(nodes[syms[i]]);
3804
+ }
3805
+ }
3806
+ function witnessAffectsMark(target) {
3807
+ const own = target[STORE_NODE]?.[$AFFECTS];
3808
+ if (own?._affectsCount) witnessAffects(own);
3809
+ if (affectsScopes.size) {
3810
+ const raw = target[STORE_VALUE];
3811
+ for (const [carrier, entry] of affectsScopes) {
3812
+ if (carrier !== own && carrier._affectsCount && entry.scope.has(raw)) witnessAffects(carrier);
3813
+ }
3814
+ }
3815
+ }
3816
+ function getStoreAffectsNodes(target, key) {
3817
+ const nodes = getNodes(target, STORE_NODE);
3818
+ if (key === undefined) {
3819
+ const carrier = getNode(target, nodes, $AFFECTS, undefined, false);
3820
+ GlobalQueue._releaseAffectsScope ||= node => {
3821
+ const entry = affectsScopes.get(node);
3822
+ if (!entry) return;
3823
+ affectsScopes.delete(node);
3824
+ for (let i = 0; i < entry.inherited.length; i++) releaseAffectsMark(entry.inherited[i]);
3825
+ };
3826
+ let entry = affectsScopes.get(carrier);
3827
+ if (!entry) affectsScopes.set(carrier, (entry = { scope: new Set(), inherited: [] }));
3828
+ const result = [carrier];
3829
+ walkAffectsScope(target[$PROXY], entry, result, target[STORE_LOOKUP], new Set());
3830
+ return result;
3831
+ }
3832
+ if (nodes[key]) return [nodes[key]];
3833
+ const layer = getOverlayLayer(target, key);
3834
+ const raw = layer ? layer[key] : target[STORE_VALUE][key];
3835
+ const prev = raw === $DELETED ? undefined : raw;
3836
+ return [upsertStoreNode(target, nodes, key, prev, target[STORE_SNAPSHOT_PROPS])];
3837
+ }
3601
3838
  function trackSelf(target, symbol = $TRACK) {
3602
3839
  if (!getObserver()) return;
3603
- read(
3604
- getNode(
3605
- getNodes(target, STORE_NODE),
3606
- symbol,
3607
- undefined,
3608
- target[STORE_FIREWALL],
3609
- false,
3610
- target[STORE_OPTIMISTIC]
3611
- )
3612
- );
3840
+ read(getNode(target, getNodes(target, STORE_NODE), symbol, undefined, false));
3613
3841
  if (
3614
3842
  symbol === $TRACK &&
3615
3843
  !target[STORE_OVERRIDE] &&
@@ -3626,6 +3854,11 @@ function notifySelf(target) {
3626
3854
  target[STORE_OPTIMISTIC] && !projectionWriteActive ? STORE_SELF_PENDING : undefined
3627
3855
  );
3628
3856
  }
3857
+ function mergedOverlay(target) {
3858
+ const override = target[STORE_OVERRIDE];
3859
+ const opt = target[STORE_OPTIMISTIC_OVERRIDE];
3860
+ return override && opt ? { ...override, ...opt } : (opt ?? override);
3861
+ }
3629
3862
  function getKeys(source, override, enumerable = true) {
3630
3863
  const baseKeys = source[$TARGET]
3631
3864
  ? untrack(() => (enumerable ? Object.keys(source) : Reflect.ownKeys(source)))
@@ -3654,17 +3887,6 @@ function getPropertyDescriptor(source, override, property) {
3654
3887
  }
3655
3888
  return Reflect.getOwnPropertyDescriptor(source, property);
3656
3889
  }
3657
- function maskStoreTarget(target, on) {
3658
- const firewall = target[STORE_FIREWALL];
3659
- if (!firewall) return;
3660
- if (!!target[STORE_MASKED] === on) return;
3661
- target[STORE_MASKED] = on;
3662
- const count = (firewall._optimisticMask = (firewall._optimisticMask || 0) + (on ? 1 : -1));
3663
- if ((on && count === 1) || (!on && count === 0)) {
3664
- updatePendingSignal(firewall);
3665
- updateChildCompanions(firewall);
3666
- }
3667
- }
3668
3890
  function prepareStoreWrite(target, store, property) {
3669
3891
  if (target[STORE_OPTIMISTIC]) {
3670
3892
  const firewall = target[STORE_FIREWALL];
@@ -3694,21 +3916,12 @@ function prepareStoreWrite(target, store, property) {
3694
3916
  function armOptimisticStoreWrite(target, store) {
3695
3917
  if (target[STORE_OPTIMISTIC] && !projectionWriteActive) {
3696
3918
  trackOptimisticStore(store);
3697
- maskStoreTarget(target, true);
3698
3919
  }
3699
3920
  }
3700
3921
  function upsertStoreNode(target, nodes, property, prev, snapshotProps) {
3701
3922
  if (nodes[property]) return nodes[property];
3702
3923
  const initial = isWrappable(prev) ? wrap(prev, target) : prev;
3703
- const node = getNode(
3704
- nodes,
3705
- property,
3706
- initial,
3707
- target[STORE_FIREWALL],
3708
- isEqual,
3709
- target[STORE_OPTIMISTIC],
3710
- snapshotProps
3711
- );
3924
+ const node = getNode(target, nodes, property, initial, isEqual, snapshotProps);
3712
3925
  registerTransientStoreNode(node);
3713
3926
  return node;
3714
3927
  }
@@ -3751,6 +3964,7 @@ const storeTraps = {
3751
3964
  if (property === $TARGET) return target;
3752
3965
  if (property === $PROXY) return receiver;
3753
3966
  if (property === $REFRESH) return target[STORE_FIREWALL];
3967
+ if (pendingCheckActive) witnessAffectsMark(target);
3754
3968
  if (property === $TRACK) {
3755
3969
  trackSelf(target);
3756
3970
  return receiver;
@@ -3772,7 +3986,7 @@ const storeTraps = {
3772
3986
  !selfRead &&
3773
3987
  !writeOnly(receiver)
3774
3988
  ) {
3775
- return read(getNode(nodes, property, undefined, target[STORE_FIREWALL]));
3989
+ return read(getNode(target, nodes, property, undefined));
3776
3990
  }
3777
3991
  const overlay = getOverlayLayer(target, property);
3778
3992
  const overridden = !!overlay;
@@ -3820,12 +4034,11 @@ const storeTraps = {
3820
4034
  } else if (getObserver() && !selfRead) {
3821
4035
  return read(
3822
4036
  getNode(
4037
+ target,
3823
4038
  nodes,
3824
4039
  property,
3825
4040
  isWrappable(value) ? wrap(value, target) : value,
3826
- target[STORE_FIREWALL],
3827
4041
  isEqual,
3828
- target[STORE_OPTIMISTIC],
3829
4042
  target[STORE_SNAPSHOT_PROPS]
3830
4043
  )
3831
4044
  );
@@ -3849,15 +4062,14 @@ const storeTraps = {
3849
4062
  },
3850
4063
  has(target, property) {
3851
4064
  if (property === $PROXY || property === $TRACK || property === "__proto__") return true;
4065
+ if (pendingCheckActive) witnessAffectsMark(target);
3852
4066
  const hasLayer = getOverlayLayer(target, property);
3853
4067
  const has = hasLayer ? hasLayer[property] !== $DELETED : property in target[STORE_VALUE];
3854
4068
  if (writeOnly(target[$PROXY]) || getObserver() === target[STORE_FIREWALL]) return has;
3855
4069
  const nodes = getNodes(target, STORE_HAS);
3856
4070
  if (nodes[property]) return read(nodes[property]);
3857
4071
  if (getObserver()) {
3858
- return read(
3859
- getNode(nodes, property, has, target[STORE_FIREWALL], isEqual, target[STORE_OPTIMISTIC])
3860
- );
4072
+ return read(getNode(target, nodes, property, has));
3861
4073
  }
3862
4074
  return has;
3863
4075
  },
@@ -3987,6 +4199,7 @@ const storeTraps = {
3987
4199
  return true;
3988
4200
  },
3989
4201
  ownKeys(target) {
4202
+ if (pendingCheckActive) witnessAffectsMark(target);
3990
4203
  if (getObserver() !== target[STORE_FIREWALL]) trackSelf(target);
3991
4204
  let keys = getKeys(target[STORE_VALUE], target[STORE_OVERRIDE], false);
3992
4205
  if (target[STORE_OPTIMISTIC_OVERRIDE]) {
@@ -4070,6 +4283,58 @@ function createStore(first, second, options) {
4070
4283
  : fn => storeSetter(wrappedStore, fn)
4071
4284
  ];
4072
4285
  }
4286
+ function affects(target, key) {
4287
+ if (arguments.length > 2) {
4288
+ const message =
4289
+ "[INVALID_AFFECTS_TARGET] affects() takes a single optional key — extra keys are " +
4290
+ "not a path. Mark each slot with its own affects(record, key) call, or pass the " +
4291
+ 'nested record itself: affects(state.user, "name").';
4292
+ emitDiagnostic({
4293
+ code: "INVALID_AFFECTS_TARGET",
4294
+ kind: "write",
4295
+ severity: "error",
4296
+ message: message
4297
+ });
4298
+ throw new Error(message);
4299
+ }
4300
+ const storeTarget = target?.[$TARGET];
4301
+ if (storeTarget) {
4302
+ const nodes = getStoreAffectsNodes(storeTarget, key);
4303
+ for (let i = 0; i < nodes.length; i++) registerAffectsMark(nodes[i]);
4304
+ return;
4305
+ }
4306
+ const node = target?.[$REFRESH];
4307
+ if (node) {
4308
+ if (key !== undefined) {
4309
+ const message =
4310
+ "[INVALID_AFFECTS_TARGET] affects() keys are only valid on store targets. " +
4311
+ "An accessor is a single slot — pass it alone, or target the store record that owns the property.";
4312
+ emitDiagnostic({
4313
+ code: "INVALID_AFFECTS_TARGET",
4314
+ kind: "write",
4315
+ severity: "error",
4316
+ message: message,
4317
+ nodeName: node._name
4318
+ });
4319
+ throw new Error(message);
4320
+ }
4321
+ registerAffectsMark(node);
4322
+ return;
4323
+ }
4324
+ {
4325
+ const message =
4326
+ "[INVALID_AFFECTS_TARGET] affects() expects a Solid source accessor or a store node. " +
4327
+ "Pass the store proxy (optionally with a property key) or the original accessor, " +
4328
+ "not a wrapper function or an already-read value.";
4329
+ emitDiagnostic({
4330
+ code: "INVALID_AFFECTS_TARGET",
4331
+ kind: "write",
4332
+ severity: "error",
4333
+ message: message
4334
+ });
4335
+ throw new Error(message);
4336
+ }
4337
+ }
4073
4338
  function createOptimisticStore(first, second, options) {
4074
4339
  GlobalQueue._clearOptimisticStore ||= clearOptimisticStore;
4075
4340
  const derived = typeof first === "function";
@@ -4080,15 +4345,12 @@ function createOptimisticStore(first, second, options) {
4080
4345
  }
4081
4346
  function clearOptimisticStore(store) {
4082
4347
  const target = store[$TARGET];
4083
- if (!target) return;
4084
- maskStoreTarget(target, false);
4085
- if (!target[STORE_OPTIMISTIC_OVERRIDE]) return;
4348
+ if (!target?.[STORE_OPTIMISTIC_OVERRIDE]) return;
4086
4349
  clearOptimisticOverride(target);
4087
4350
  }
4088
4351
  function clearOptimisticOverride(target) {
4089
4352
  const override = target[STORE_OPTIMISTIC_OVERRIDE];
4090
4353
  if (!override) return;
4091
- maskStoreTarget(target, false);
4092
4354
  const nodes = target[STORE_NODE];
4093
4355
  delete target[STORE_OPTIMISTIC_OVERRIDE];
4094
4356
  const wasProjectionWriteActive = projectionWriteActive;
@@ -4255,18 +4517,16 @@ const storePath = Object.assign(
4255
4517
  },
4256
4518
  { DELETE: DELETE }
4257
4519
  );
4258
- function mergedOverlay(target) {
4259
- const override = target[STORE_OVERRIDE];
4260
- const opt = target[STORE_OPTIMISTIC_OVERRIDE];
4261
- return override && opt ? { ...override, ...opt } : (opt ?? override);
4262
- }
4263
4520
  function snapshotImpl(item, track, map, lookup) {
4264
4521
  let target, isArray, override, result, unwrapped, v;
4265
4522
  if (!isWrappable(item)) return item;
4266
4523
  if (map && map.has(item)) return map.get(item);
4267
4524
  if (!map) map = new Map();
4268
4525
  if ((target = item[$TARGET] || lookup?.get(item)?.[$TARGET])) {
4269
- if (track) trackSelf(target, $TRACK);
4526
+ if (track) {
4527
+ trackSelf(target, $TRACK);
4528
+ if (pendingCheckActive) witnessAffectsMark(target);
4529
+ }
4270
4530
  override = mergedOverlay(target);
4271
4531
  isArray = Array.isArray(target[STORE_VALUE]);
4272
4532
  map.set(
@@ -4808,7 +5068,7 @@ class RevealController {
4808
5068
  }
4809
5069
  isMinimallyReady() {
4810
5070
  const order = untrack(this._orderAccessor);
4811
- if (order === "together") return this.isReady();
5071
+ if (order === "together") return this._forEachOwnedSlot(isSlotMinimallyReady);
4812
5072
  if (order === "natural") {
4813
5073
  let hasSlot = false;
4814
5074
  let anyReady = false;
@@ -5135,6 +5395,7 @@ export {
5135
5395
  NotReadyError,
5136
5396
  SUPPORTS_PROXY,
5137
5397
  action,
5398
+ affects,
5138
5399
  clearSnapshots,
5139
5400
  createContext,
5140
5401
  createEffect,