@solidjs/signals 0.13.3 → 0.13.5

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
@@ -153,12 +153,16 @@ let scheduled = false;
153
153
  let projectionWriteActive = false;
154
154
  let _enforceLoadingBoundary = false;
155
155
  let _hitUnhandledAsync = false;
156
+ let stashedOptimisticReads = null;
156
157
  function resetUnhandledAsync() {
157
158
  _hitUnhandledAsync = false;
158
159
  }
159
160
  function enforceLoadingBoundary(enabled) {
160
161
  _enforceLoadingBoundary = enabled;
161
162
  }
163
+ function shouldReadStashedOptimisticValue(node) {
164
+ return !!stashedOptimisticReads?.has(node);
165
+ }
162
166
  function runLaneEffects(type) {
163
167
  for (const lane of activeLanes) {
164
168
  if (lane._mergedInto || lane._pendingAsync.size > 0) continue;
@@ -169,14 +173,89 @@ function runLaneEffects(type) {
169
173
  }
170
174
  }
171
175
  }
176
+ function queueStashedOptimisticEffects(node) {
177
+ for (let s = node._subs; s !== null; s = s._nextSub) {
178
+ const sub = s._sub;
179
+ if (!sub._type) continue;
180
+ if (sub._type === EFFECT_TRACKED) {
181
+ if (!sub._modified) {
182
+ sub._modified = true;
183
+ sub._queue.enqueue(EFFECT_USER, sub._run);
184
+ }
185
+ continue;
186
+ }
187
+ const queue = sub._flags & REACTIVE_ZOMBIE ? zombieQueue : dirtyQueue;
188
+ if (queue._min > sub._height) queue._min = sub._height;
189
+ insertIntoHeap(sub, queue);
190
+ }
191
+ }
172
192
  function setProjectionWriteActive(value) {
173
193
  projectionWriteActive = value;
174
194
  }
195
+ function mergeTransitionState(target, outgoing) {
196
+ outgoing._done = target;
197
+ target._actions.push(...outgoing._actions);
198
+ for (const lane of activeLanes) {
199
+ if (lane._transition === outgoing) lane._transition = target;
200
+ }
201
+ target._optimisticNodes.push(...outgoing._optimisticNodes);
202
+ for (const store of outgoing._optimisticStores) target._optimisticStores.add(store);
203
+ for (const node of outgoing._asyncNodes) {
204
+ if (!target._asyncNodes.includes(node)) target._asyncNodes.push(node);
205
+ }
206
+ }
207
+ function resolveOptimisticNodes(nodes) {
208
+ for (let i = 0; i < nodes.length; i++) {
209
+ const node = nodes[i];
210
+ node._optimisticLane = undefined;
211
+ if (node._pendingValue !== NOT_PENDING) {
212
+ node._value = node._pendingValue;
213
+ node._pendingValue = NOT_PENDING;
214
+ }
215
+ const prevOverride = node._overrideValue;
216
+ node._overrideValue = NOT_PENDING;
217
+ if (prevOverride !== NOT_PENDING && node._value !== prevOverride) insertSubs(node, true);
218
+ node._transition = null;
219
+ }
220
+ nodes.length = 0;
221
+ }
222
+ function cleanupCompletedLanes(completingTransition) {
223
+ for (const lane of activeLanes) {
224
+ const owned = completingTransition
225
+ ? lane._transition === completingTransition
226
+ : !lane._transition;
227
+ if (!owned) continue;
228
+ if (!lane._mergedInto) {
229
+ if (lane._effectQueues[0].length) runQueue(lane._effectQueues[0], EFFECT_RENDER);
230
+ if (lane._effectQueues[1].length) runQueue(lane._effectQueues[1], EFFECT_USER);
231
+ }
232
+ if (lane._source._optimisticLane === lane) lane._source._optimisticLane = undefined;
233
+ lane._pendingAsync.clear();
234
+ lane._effectQueues[0].length = 0;
235
+ lane._effectQueues[1].length = 0;
236
+ activeLanes.delete(lane);
237
+ signalLanes.delete(lane._source);
238
+ }
239
+ }
175
240
  function schedule() {
176
241
  if (scheduled) return;
177
242
  scheduled = true;
178
243
  if (!globalQueue._running && !projectionWriteActive) queueMicrotask(flush);
179
244
  }
245
+ function addTransitionBlocker(node) {
246
+ if (activeTransition && !activeTransition._asyncNodes.includes(node)) {
247
+ activeTransition._asyncNodes.push(node);
248
+ }
249
+ }
250
+ function removeTransitionBlocker(node) {
251
+ const remove = list => {
252
+ if (!list) return;
253
+ const index = list.indexOf(node);
254
+ if (index >= 0) list.splice(index, 1);
255
+ };
256
+ remove(node._transition?._asyncNodes);
257
+ remove(activeTransition?._asyncNodes);
258
+ }
180
259
  class Queue {
181
260
  _parent = null;
182
261
  _queues = [[], []];
@@ -256,19 +335,32 @@ class GlobalQueue extends Queue {
256
335
  if (activeTransition) {
257
336
  const isComplete = transitionComplete(activeTransition);
258
337
  if (!isComplete) {
259
- let t = activeTransition;
338
+ const stashedTransition = activeTransition;
260
339
  runHeap(zombieQueue, GlobalQueue._update);
261
340
  this._pendingNodes = [];
262
341
  this._optimisticNodes = [];
263
342
  this._optimisticStores = new Set();
264
343
  runLaneEffects(EFFECT_RENDER);
265
344
  runLaneEffects(EFFECT_USER);
266
- this.stashQueues(activeTransition._queueStash);
345
+ this.stashQueues(stashedTransition._queueStash);
267
346
  clock++;
268
347
  scheduled = dirtyQueue._max >= dirtyQueue._min;
269
- reassignPendingTransition(activeTransition._pendingNodes);
348
+ reassignPendingTransition(stashedTransition._pendingNodes);
270
349
  activeTransition = null;
271
- finalizePureQueue(null, true);
350
+ if (!stashedTransition._actions.length && stashedTransition._optimisticNodes.length) {
351
+ stashedOptimisticReads = new Set();
352
+ for (let i = 0; i < stashedTransition._optimisticNodes.length; i++) {
353
+ const node = stashedTransition._optimisticNodes[i];
354
+ if (node._fn || node._pureWrite) continue;
355
+ stashedOptimisticReads.add(node);
356
+ queueStashedOptimisticEffects(node);
357
+ }
358
+ }
359
+ try {
360
+ finalizePureQueue(null, true);
361
+ } finally {
362
+ stashedOptimisticReads = null;
363
+ }
272
364
  return;
273
365
  }
274
366
  this._pendingNodes !== activeTransition._pendingNodes &&
@@ -297,13 +389,12 @@ class GlobalQueue extends Queue {
297
389
  if (mask & STATUS_PENDING) {
298
390
  if (flags & STATUS_PENDING) {
299
391
  const actualError = error !== undefined ? error : node._error;
300
- if (
301
- activeTransition &&
302
- actualError &&
303
- !activeTransition._asyncNodes.includes(actualError.source)
304
- ) {
305
- activeTransition._asyncNodes.push(actualError.source);
306
- schedule();
392
+ if (activeTransition && actualError) {
393
+ const source = actualError.source;
394
+ if (!activeTransition._asyncNodes.includes(source)) {
395
+ activeTransition._asyncNodes.push(source);
396
+ schedule();
397
+ }
307
398
  }
308
399
  if (_enforceLoadingBoundary) _hitUnhandledAsync = true;
309
400
  }
@@ -328,37 +419,35 @@ class GlobalQueue extends Queue {
328
419
  };
329
420
  } else if (transition) {
330
421
  const outgoing = activeTransition;
331
- outgoing._done = transition;
332
- transition._actions.push(...outgoing._actions);
333
- for (const lane of activeLanes) {
334
- if (lane._transition === outgoing) lane._transition = transition;
335
- }
336
- transition._optimisticNodes.push(...outgoing._optimisticNodes);
337
- for (const store of outgoing._optimisticStores) {
338
- transition._optimisticStores.add(store);
339
- }
422
+ mergeTransitionState(transition, outgoing);
340
423
  transitions.delete(outgoing);
341
424
  activeTransition = transition;
342
425
  }
343
426
  transitions.add(activeTransition);
344
427
  activeTransition._time = clock;
345
- for (let i = 0; i < this._pendingNodes.length; i++) {
346
- const n = this._pendingNodes[i];
347
- n._transition = activeTransition;
348
- activeTransition._pendingNodes.push(n);
349
- }
350
- this._pendingNodes = activeTransition._pendingNodes;
351
- for (let i = 0; i < this._optimisticNodes.length; i++) {
352
- const node = this._optimisticNodes[i];
353
- node._transition = activeTransition;
354
- activeTransition._optimisticNodes.push(node);
355
- }
356
- this._optimisticNodes = activeTransition._optimisticNodes;
428
+ if (this._pendingNodes !== activeTransition._pendingNodes) {
429
+ for (let i = 0; i < this._pendingNodes.length; i++) {
430
+ const node = this._pendingNodes[i];
431
+ node._transition = activeTransition;
432
+ activeTransition._pendingNodes.push(node);
433
+ }
434
+ this._pendingNodes = activeTransition._pendingNodes;
435
+ }
436
+ if (this._optimisticNodes !== activeTransition._optimisticNodes) {
437
+ for (let i = 0; i < this._optimisticNodes.length; i++) {
438
+ const node = this._optimisticNodes[i];
439
+ node._transition = activeTransition;
440
+ activeTransition._optimisticNodes.push(node);
441
+ }
442
+ this._optimisticNodes = activeTransition._optimisticNodes;
443
+ }
357
444
  for (const lane of activeLanes) {
358
445
  if (!lane._transition) lane._transition = activeTransition;
359
446
  }
360
- for (const store of this._optimisticStores) activeTransition._optimisticStores.add(store);
361
- this._optimisticStores = activeTransition._optimisticStores;
447
+ if (this._optimisticStores !== activeTransition._optimisticStores) {
448
+ for (const store of this._optimisticStores) activeTransition._optimisticStores.add(store);
449
+ this._optimisticStores = activeTransition._optimisticStores;
450
+ }
362
451
  }
363
452
  }
364
453
  function insertSubs(node, optimistic = false) {
@@ -398,40 +487,21 @@ function commitPendingNodes() {
398
487
  n._pendingValue = NOT_PENDING;
399
488
  if (n._type && n._type !== EFFECT_TRACKED) n._modified = true;
400
489
  }
401
- if (n._statusFlags & STATUS_PENDING) {
402
- const _src = n._error?.source;
403
- if (_src && !(_src._statusFlags & STATUS_PENDING)) {
404
- n._statusFlags &= -6;
405
- n._error = null;
406
- }
407
- } else n._statusFlags &= ~STATUS_UNINITIALIZED;
490
+ if (!(n._statusFlags & STATUS_PENDING)) n._statusFlags &= ~STATUS_UNINITIALIZED;
408
491
  if (n._fn) GlobalQueue._dispose(n, false, true);
409
492
  }
410
493
  pendingNodes.length = 0;
411
494
  }
412
495
  function finalizePureQueue(completingTransition = null, incomplete = false) {
413
- let resolvePending = !incomplete;
496
+ const resolvePending = !incomplete;
414
497
  if (resolvePending) commitPendingNodes();
415
498
  if (!incomplete) checkBoundaryChildren(globalQueue);
416
499
  if (dirtyQueue._max >= dirtyQueue._min) runHeap(dirtyQueue, GlobalQueue._update);
417
500
  if (resolvePending) {
418
501
  commitPendingNodes();
419
- const optimisticNodes = completingTransition
420
- ? completingTransition._optimisticNodes
421
- : globalQueue._optimisticNodes;
422
- for (let i = 0; i < optimisticNodes.length; i++) {
423
- const n = optimisticNodes[i];
424
- n._optimisticLane = undefined;
425
- if (n._pendingValue !== NOT_PENDING) {
426
- n._value = n._pendingValue;
427
- n._pendingValue = NOT_PENDING;
428
- }
429
- const prevOverride = n._overrideValue;
430
- n._overrideValue = NOT_PENDING;
431
- if (prevOverride !== NOT_PENDING && n._value !== prevOverride) insertSubs(n, true);
432
- n._transition = null;
433
- }
434
- optimisticNodes.length = 0;
502
+ resolveOptimisticNodes(
503
+ completingTransition ? completingTransition._optimisticNodes : globalQueue._optimisticNodes
504
+ );
435
505
  const optimisticStores = completingTransition
436
506
  ? completingTransition._optimisticStores
437
507
  : globalQueue._optimisticStores;
@@ -442,22 +512,7 @@ function finalizePureQueue(completingTransition = null, incomplete = false) {
442
512
  optimisticStores.clear();
443
513
  schedule();
444
514
  }
445
- for (const lane of activeLanes) {
446
- const owned = completingTransition
447
- ? lane._transition === completingTransition
448
- : !lane._transition;
449
- if (!owned) continue;
450
- if (!lane._mergedInto) {
451
- if (lane._effectQueues[0].length) runQueue(lane._effectQueues[0], EFFECT_RENDER);
452
- if (lane._effectQueues[1].length) runQueue(lane._effectQueues[1], EFFECT_USER);
453
- }
454
- if (lane._source._optimisticLane === lane) lane._source._optimisticLane = undefined;
455
- lane._pendingAsync.clear();
456
- lane._effectQueues[0].length = 0;
457
- lane._effectQueues[1].length = 0;
458
- activeLanes.delete(lane);
459
- signalLanes.delete(lane._source);
460
- }
515
+ cleanupCompletedLanes(completingTransition);
461
516
  }
462
517
  }
463
518
  function checkBoundaryChildren(queue) {
@@ -478,7 +533,7 @@ function reassignPendingTransition(pendingNodes) {
478
533
  const globalQueue = new GlobalQueue();
479
534
  function flush() {
480
535
  let count = 0;
481
- while (scheduled) {
536
+ while (scheduled || activeTransition) {
482
537
  if (++count === 1e5) throw new Error("Potential Infinite Loop Detected.");
483
538
  globalQueue.flush();
484
539
  }
@@ -560,6 +615,9 @@ function resolveLane(el) {
560
615
  el._optimisticLane = undefined;
561
616
  return undefined;
562
617
  }
618
+ function resolveTransition(el) {
619
+ return resolveLane(el)?._transition ?? el._transition;
620
+ }
563
621
  function hasActiveOverride(el) {
564
622
  return !!(el._overrideValue !== undefined && el._overrideValue !== NOT_PENDING);
565
623
  }
@@ -584,6 +642,96 @@ function assignOrMergeLane(el, sourceLane) {
584
642
  }
585
643
  el._optimisticLane = sourceLane;
586
644
  }
645
+ function addPendingSource(el, source) {
646
+ if (el._pendingSource === source || el._pendingSources?.has(source)) return false;
647
+ if (!el._pendingSource) {
648
+ el._pendingSource = source;
649
+ return true;
650
+ }
651
+ if (!el._pendingSources) {
652
+ el._pendingSources = new Set([el._pendingSource, source]);
653
+ } else {
654
+ el._pendingSources.add(source);
655
+ }
656
+ el._pendingSource = undefined;
657
+ return true;
658
+ }
659
+ function removePendingSource(el, source) {
660
+ if (el._pendingSource) {
661
+ if (el._pendingSource !== source) return false;
662
+ el._pendingSource = undefined;
663
+ return true;
664
+ }
665
+ if (!el._pendingSources?.delete(source)) return false;
666
+ if (el._pendingSources.size === 1) {
667
+ el._pendingSource = el._pendingSources.values().next().value;
668
+ el._pendingSources = undefined;
669
+ } else if (el._pendingSources.size === 0) {
670
+ el._pendingSources = undefined;
671
+ }
672
+ return true;
673
+ }
674
+ function clearPendingSources(el) {
675
+ el._pendingSource = undefined;
676
+ el._pendingSources?.clear();
677
+ el._pendingSources = undefined;
678
+ }
679
+ function setPendingError(el, source, error) {
680
+ if (!source) {
681
+ el._error = null;
682
+ return;
683
+ }
684
+ if (error instanceof NotReadyError && error.source === source) {
685
+ el._error = error;
686
+ return;
687
+ }
688
+ const current = el._error;
689
+ if (!(current instanceof NotReadyError) || current.source !== source) {
690
+ el._error = new NotReadyError(source);
691
+ }
692
+ }
693
+ function forEachDependent(el, fn) {
694
+ for (let s = el._subs; s !== null; s = s._nextSub) fn(s._sub);
695
+ for (let child = el._child; child !== null; child = child._nextChild) {
696
+ for (let s = child._subs; s !== null; s = s._nextSub) fn(s._sub);
697
+ }
698
+ }
699
+ function settlePendingSource(el) {
700
+ let scheduled = false;
701
+ const visited = new Set();
702
+ const settle = node => {
703
+ if (visited.has(node) || !removePendingSource(node, el)) return;
704
+ visited.add(node);
705
+ node._time = clock;
706
+ const source = node._pendingSource ?? node._pendingSources?.values().next().value;
707
+ if (source) {
708
+ setPendingError(node, source);
709
+ updatePendingSignal(node);
710
+ } else {
711
+ node._statusFlags &= ~STATUS_PENDING;
712
+ setPendingError(node);
713
+ updatePendingSignal(node);
714
+ if (node._blocked) {
715
+ if (node._type === EFFECT_TRACKED) {
716
+ const tracked = node;
717
+ if (!tracked._modified) {
718
+ tracked._modified = true;
719
+ tracked._queue.enqueue(EFFECT_USER, tracked._run);
720
+ }
721
+ } else {
722
+ const queue = node._flags & REACTIVE_ZOMBIE ? zombieQueue : dirtyQueue;
723
+ if (queue._min > node._height) queue._min = node._height;
724
+ insertIntoHeap(node, queue);
725
+ }
726
+ scheduled = true;
727
+ }
728
+ node._blocked = false;
729
+ }
730
+ forEachDependent(node, settle);
731
+ };
732
+ forEachDependent(el, settle);
733
+ if (scheduled) schedule();
734
+ }
587
735
  function handleAsync(el, result, setter) {
588
736
  const isObject = typeof result === "object" && result !== null;
589
737
  const iterator = isObject && untrack(() => result[Symbol.asyncIterator]);
@@ -596,14 +744,14 @@ function handleAsync(el, result, setter) {
596
744
  let syncValue;
597
745
  const handleError = error => {
598
746
  if (el._inFlight !== result) return;
599
- globalQueue.initTransition(el._transition);
747
+ globalQueue.initTransition(resolveTransition(el));
600
748
  notifyStatus(el, error instanceof NotReadyError ? STATUS_PENDING : STATUS_ERROR, error);
601
749
  el._time = clock;
602
750
  };
603
751
  const asyncWrite = (value, then) => {
604
752
  if (el._inFlight !== result) return;
605
753
  if (el._flags & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY)) return;
606
- globalQueue.initTransition(el._transition);
754
+ globalQueue.initTransition(resolveTransition(el));
607
755
  clearStatus(el);
608
756
  const lane = resolveLane(el);
609
757
  if (lane) lane._pendingAsync.delete(el);
@@ -630,6 +778,7 @@ function handleAsync(el, result, setter) {
630
778
  } else {
631
779
  setSignal(el, () => value);
632
780
  }
781
+ settlePendingSource(el);
633
782
  schedule();
634
783
  flush();
635
784
  then?.();
@@ -650,7 +799,7 @@ function handleAsync(el, result, setter) {
650
799
  );
651
800
  isSync = false;
652
801
  if (!resolved) {
653
- globalQueue.initTransition(el._transition);
802
+ globalQueue.initTransition(resolveTransition(el));
654
803
  throw new NotReadyError(context);
655
804
  }
656
805
  }
@@ -686,15 +835,18 @@ function handleAsync(el, result, setter) {
686
835
  };
687
836
  const immediatelyDone = iterate();
688
837
  if (!hadSyncValue && !immediatelyDone) {
689
- globalQueue.initTransition(el._transition);
838
+ globalQueue.initTransition(resolveTransition(el));
690
839
  throw new NotReadyError(context);
691
840
  }
692
841
  }
693
842
  return syncValue;
694
843
  }
695
- function clearStatus(el) {
696
- el._statusFlags = el._statusFlags & STATUS_UNINITIALIZED;
697
- el._error = null;
844
+ function clearStatus(el, clearUninitialized = false) {
845
+ clearPendingSources(el);
846
+ removeTransitionBlocker(el);
847
+ el._blocked = false;
848
+ el._statusFlags = clearUninitialized ? 0 : el._statusFlags & STATUS_UNINITIALIZED;
849
+ setPendingError(el);
698
850
  updatePendingSignal(el);
699
851
  el._notifyStatus?.();
700
852
  }
@@ -705,25 +857,30 @@ function notifyStatus(el, status, error, blockStatus, lane) {
705
857
  !(error instanceof NotReadyError)
706
858
  )
707
859
  error = new StatusError(el, error);
708
- const isSource = error instanceof NotReadyError && error.source === el;
860
+ const pendingSource =
861
+ status === STATUS_PENDING && error instanceof NotReadyError ? error.source : undefined;
862
+ const isSource = pendingSource === el;
709
863
  const isOptimisticBoundary =
710
864
  status === STATUS_PENDING && el._overrideValue !== undefined && !isSource;
711
865
  const startsBlocking = isOptimisticBoundary && hasActiveOverride(el);
712
866
  if (!blockStatus) {
713
- el._statusFlags =
714
- status | (status !== STATUS_ERROR ? el._statusFlags & STATUS_UNINITIALIZED : 0);
715
- el._error = error;
867
+ if (status === STATUS_PENDING && pendingSource) {
868
+ addPendingSource(el, pendingSource);
869
+ el._statusFlags = STATUS_PENDING | (el._statusFlags & STATUS_UNINITIALIZED);
870
+ setPendingError(el, el._pendingSource ?? el._pendingSources?.values().next().value, error);
871
+ if (pendingSource === el) addTransitionBlocker(el);
872
+ } else {
873
+ clearPendingSources(el);
874
+ removeTransitionBlocker(el);
875
+ el._statusFlags =
876
+ status | (status !== STATUS_ERROR ? el._statusFlags & STATUS_UNINITIALIZED : 0);
877
+ el._error = error;
878
+ }
716
879
  updatePendingSignal(el);
717
880
  }
718
881
  if (lane && !blockStatus) {
719
882
  assignOrMergeLane(el, lane);
720
883
  }
721
- if (startsBlocking && activeTransition && error instanceof NotReadyError) {
722
- const source = error.source;
723
- if (!activeTransition._asyncNodes.includes(source)) {
724
- activeTransition._asyncNodes.push(source);
725
- }
726
- }
727
884
  const downstreamBlockStatus = blockStatus || startsBlocking;
728
885
  const downstreamLane = blockStatus || isOptimisticBoundary ? undefined : lane;
729
886
  if (el._notifyStatus) {
@@ -734,84 +891,43 @@ function notifyStatus(el, status, error, blockStatus, lane) {
734
891
  }
735
892
  return;
736
893
  }
737
- for (let s = el._subs; s !== null; s = s._nextSub) {
738
- s._sub._time = clock;
739
- if (s._sub._error !== error) {
740
- !s._sub._transition && globalQueue._pendingNodes.push(s._sub);
741
- notifyStatus(s._sub, status, error, downstreamBlockStatus, downstreamLane);
742
- }
743
- }
744
- for (let child = el._child; child !== null; child = child._nextChild) {
745
- for (let s = child._subs; s !== null; s = s._nextSub) {
746
- s._sub._time = clock;
747
- if (s._sub._error !== error) {
748
- !s._sub._transition && globalQueue._pendingNodes.push(s._sub);
749
- notifyStatus(s._sub, status, error, downstreamBlockStatus, downstreamLane);
750
- }
894
+ forEachDependent(el, sub => {
895
+ sub._time = clock;
896
+ if (
897
+ (status === STATUS_PENDING &&
898
+ pendingSource &&
899
+ sub._pendingSource !== pendingSource &&
900
+ !sub._pendingSources?.has(pendingSource)) ||
901
+ (status !== STATUS_PENDING &&
902
+ (sub._error !== error || sub._pendingSource || sub._pendingSources))
903
+ ) {
904
+ !sub._transition && globalQueue._pendingNodes.push(sub);
905
+ notifyStatus(sub, status, error, downstreamBlockStatus, downstreamLane);
751
906
  }
752
- }
753
- }
754
- function unlinkSubs(link) {
755
- const dep = link._dep;
756
- const nextDep = link._nextDep;
757
- const nextSub = link._nextSub;
758
- const prevSub = link._prevSub;
759
- if (nextSub !== null) nextSub._prevSub = prevSub;
760
- else dep._subsTail = prevSub;
761
- if (prevSub !== null) prevSub._nextSub = nextSub;
762
- else {
763
- dep._subs = nextSub;
764
- if (nextSub === null) {
765
- dep._unobserved?.();
766
- dep._fn && !dep._preventAutoDisposal && !(dep._flags & REACTIVE_ZOMBIE) && unobserved(dep);
767
- }
768
- }
769
- return nextDep;
770
- }
771
- function unobserved(el) {
772
- deleteFromHeap(el, el._flags & REACTIVE_ZOMBIE ? zombieQueue : dirtyQueue);
773
- let dep = el._deps;
774
- while (dep !== null) {
775
- dep = unlinkSubs(dep);
776
- }
777
- el._deps = null;
778
- disposeChildren(el, true);
779
- }
780
- function link(dep, sub) {
781
- const prevDep = sub._depsTail;
782
- if (prevDep !== null && prevDep._dep === dep) return;
783
- let nextDep = null;
784
- const isRecomputing = sub._flags & REACTIVE_RECOMPUTING_DEPS;
785
- if (isRecomputing) {
786
- nextDep = prevDep !== null ? prevDep._nextDep : sub._deps;
787
- if (nextDep !== null && nextDep._dep === dep) {
788
- sub._depsTail = nextDep;
789
- return;
790
- }
791
- }
792
- const prevSub = dep._subsTail;
793
- if (prevSub !== null && prevSub._sub === sub && (!isRecomputing || isValidLink(prevSub, sub)))
794
- return;
795
- const newLink =
796
- (sub._depsTail =
797
- dep._subsTail =
798
- { _dep: dep, _sub: sub, _nextDep: nextDep, _prevSub: prevSub, _nextSub: null });
799
- if (prevDep !== null) prevDep._nextDep = newLink;
800
- else sub._deps = newLink;
801
- if (prevSub !== null) prevSub._nextSub = newLink;
802
- else dep._subs = newLink;
907
+ });
803
908
  }
804
- function isValidLink(checkLink, sub) {
805
- const depsTail = sub._depsTail;
806
- if (depsTail !== null) {
807
- let link = sub._deps;
808
- do {
809
- if (link === checkLink) return true;
810
- if (link === depsTail) break;
811
- link = link._nextDep;
812
- } while (link !== null);
909
+ let externalSourceConfig = null;
910
+ function enableExternalSource(config) {
911
+ const { factory: factory, untrack: untrackFn = fn => fn() } = config;
912
+ if (externalSourceConfig) {
913
+ const { factory: oldFactory, untrack: oldUntrack } = externalSourceConfig;
914
+ externalSourceConfig = {
915
+ factory: (fn, trigger) => {
916
+ const oldSource = oldFactory(fn, trigger);
917
+ const source = factory(x => oldSource.track(x), trigger);
918
+ return {
919
+ track: x => source.track(x),
920
+ dispose() {
921
+ source.dispose();
922
+ oldSource.dispose();
923
+ }
924
+ };
925
+ },
926
+ untrack: fn => oldUntrack(() => untrackFn(fn))
927
+ };
928
+ } else {
929
+ externalSourceConfig = { factory: factory, untrack: untrackFn };
813
930
  }
814
- return false;
815
931
  }
816
932
  const PENDING_OWNER = {};
817
933
  function markDisposal(el) {
@@ -900,7 +1016,7 @@ function getObserver() {
900
1016
  function getOwner() {
901
1017
  return context;
902
1018
  }
903
- function onCleanup(fn) {
1019
+ function cleanup(fn) {
904
1020
  if (!context) return fn;
905
1021
  if (!context._disposal) context._disposal = fn;
906
1022
  else if (Array.isArray(context._disposal)) context._disposal.push(fn);
@@ -933,8 +1049,10 @@ function createOwner(options) {
933
1049
  disposeChildren(owner, self);
934
1050
  }
935
1051
  };
936
- if (leafEffectActive && parent) {
937
- throw new Error("Cannot create reactive primitives inside createTrackedEffect");
1052
+ if (parent?._childrenForbidden) {
1053
+ throw new Error(
1054
+ "Cannot create reactive primitives inside createTrackedEffect or owner-backed onSettled"
1055
+ );
938
1056
  }
939
1057
  if (parent) {
940
1058
  const lastChild = parent._firstChild;
@@ -951,136 +1069,67 @@ function createRoot(init, options) {
951
1069
  const owner = createOwner(options);
952
1070
  return runWithOwner(owner, () => init(owner.dispose));
953
1071
  }
954
- let leafEffectActive = false;
955
- function effect(compute, effect, error, initialValue, options) {
956
- let initialized = false;
957
- const node = computed(
958
- options?.render ? p => staleValues(() => compute(p)) : compute,
959
- initialValue,
960
- {
961
- ...options,
962
- equals: () => {
963
- node._modified = !node._error;
964
- if (initialized) node._queue.enqueue(node._type, runEffect.bind(node));
965
- return false;
966
- },
967
- lazy: true
968
- }
969
- );
970
- node._prevValue = initialValue;
971
- node._effectFn = effect;
972
- node._errorFn = error;
973
- node._cleanup = undefined;
974
- node._type = options?.render ? EFFECT_RENDER : EFFECT_USER;
975
- node._notifyStatus = (status, error) => {
976
- const actualStatus = status !== undefined ? status : node._statusFlags;
977
- const actualError = error !== undefined ? error : node._error;
978
- if (actualStatus & STATUS_ERROR) {
979
- let err = actualError;
980
- node._queue.notify(node, STATUS_PENDING, 0);
981
- if (node._type === EFFECT_USER) {
982
- try {
983
- return node._errorFn
984
- ? node._errorFn(err, () => {
985
- node._cleanup?.();
986
- node._cleanup = undefined;
987
- })
988
- : console.error(err);
989
- } catch (e) {
990
- err = e;
991
- }
992
- }
993
- if (!node._queue.notify(node, STATUS_ERROR, STATUS_ERROR)) throw err;
994
- } else if (node._type === EFFECT_RENDER) {
995
- node._queue.notify(node, STATUS_PENDING | STATUS_ERROR, actualStatus, actualError);
996
- if (_hitUnhandledAsync) {
997
- resetUnhandledAsync();
998
- const err = new Error("An async value must be rendered inside a Loading boundary.");
999
- if (!node._queue.notify(node, STATUS_ERROR, STATUS_ERROR)) throw err;
1000
- }
1072
+ function unlinkSubs(link) {
1073
+ const dep = link._dep;
1074
+ const nextDep = link._nextDep;
1075
+ const nextSub = link._nextSub;
1076
+ const prevSub = link._prevSub;
1077
+ if (nextSub !== null) nextSub._prevSub = prevSub;
1078
+ else dep._subsTail = prevSub;
1079
+ if (prevSub !== null) prevSub._nextSub = nextSub;
1080
+ else {
1081
+ dep._subs = nextSub;
1082
+ if (nextSub === null) {
1083
+ dep._unobserved?.();
1084
+ dep._fn && !dep._preventAutoDisposal && !(dep._flags & REACTIVE_ZOMBIE) && unobserved(dep);
1001
1085
  }
1002
- };
1003
- recompute(node, true);
1004
- !options?.defer &&
1005
- (node._type === EFFECT_USER
1006
- ? node._queue.enqueue(node._type, runEffect.bind(node))
1007
- : runEffect.call(node));
1008
- initialized = true;
1009
- onCleanup(() => node._cleanup?.());
1010
- if (!node._parent)
1011
- console.warn("Effects created outside a reactive context will never be disposed");
1012
- }
1013
- function runEffect() {
1014
- if (!this._modified || this._flags & REACTIVE_DISPOSED) return;
1015
- let prevStrictRead = false;
1016
- {
1017
- prevStrictRead = setStrictRead("an effect callback");
1018
1086
  }
1019
- this._cleanup?.();
1020
- this._cleanup = undefined;
1021
- try {
1022
- this._cleanup = this._effectFn(this._value, this._prevValue);
1023
- } catch (error) {
1024
- this._error = new StatusError(this, error);
1025
- this._statusFlags |= STATUS_ERROR;
1026
- if (!this._queue.notify(this, STATUS_ERROR, STATUS_ERROR)) throw error;
1027
- } finally {
1028
- setStrictRead(prevStrictRead);
1029
- this._prevValue = this._value;
1030
- this._modified = false;
1087
+ return nextDep;
1088
+ }
1089
+ function unobserved(el) {
1090
+ deleteFromHeap(el, el._flags & REACTIVE_ZOMBIE ? zombieQueue : dirtyQueue);
1091
+ let dep = el._deps;
1092
+ while (dep !== null) {
1093
+ dep = unlinkSubs(dep);
1031
1094
  }
1095
+ el._deps = null;
1096
+ disposeChildren(el, true);
1032
1097
  }
1033
- function trackedEffect(fn, options) {
1034
- const run = () => {
1035
- if (!node._modified || node._flags & REACTIVE_DISPOSED) return;
1036
- node._modified = false;
1037
- recompute(node);
1038
- };
1039
- const node = computed(
1040
- () => {
1041
- leafEffectActive = true;
1042
- try {
1043
- node._cleanup?.();
1044
- node._cleanup = undefined;
1045
- node._cleanup = staleValues(fn) || undefined;
1046
- } finally {
1047
- leafEffectActive = false;
1048
- }
1049
- },
1050
- undefined,
1051
- { ...options, lazy: true }
1052
- );
1053
- node._cleanup = undefined;
1054
- node._modified = true;
1055
- node._type = EFFECT_TRACKED;
1056
- node._run = run;
1057
- node._queue.enqueue(EFFECT_USER, run);
1058
- onCleanup(() => node._cleanup?.());
1059
- if (!node._parent)
1060
- console.warn("Effects created outside a reactive context will never be disposed");
1098
+ function link(dep, sub) {
1099
+ const prevDep = sub._depsTail;
1100
+ if (prevDep !== null && prevDep._dep === dep) return;
1101
+ let nextDep = null;
1102
+ const isRecomputing = sub._flags & REACTIVE_RECOMPUTING_DEPS;
1103
+ if (isRecomputing) {
1104
+ nextDep = prevDep !== null ? prevDep._nextDep : sub._deps;
1105
+ if (nextDep !== null && nextDep._dep === dep) {
1106
+ sub._depsTail = nextDep;
1107
+ return;
1108
+ }
1109
+ }
1110
+ const prevSub = dep._subsTail;
1111
+ if (prevSub !== null && prevSub._sub === sub && (!isRecomputing || isValidLink(prevSub, sub)))
1112
+ return;
1113
+ const newLink =
1114
+ (sub._depsTail =
1115
+ dep._subsTail =
1116
+ { _dep: dep, _sub: sub, _nextDep: nextDep, _prevSub: prevSub, _nextSub: null });
1117
+ if (prevDep !== null) prevDep._nextDep = newLink;
1118
+ else sub._deps = newLink;
1119
+ if (prevSub !== null) prevSub._nextSub = newLink;
1120
+ else dep._subs = newLink;
1061
1121
  }
1062
- let externalSourceConfig = null;
1063
- function enableExternalSource(config) {
1064
- const { factory: factory, untrack: untrackFn = fn => fn() } = config;
1065
- if (externalSourceConfig) {
1066
- const { factory: oldFactory, untrack: oldUntrack } = externalSourceConfig;
1067
- externalSourceConfig = {
1068
- factory: (fn, trigger) => {
1069
- const oldSource = oldFactory(fn, trigger);
1070
- const source = factory(x => oldSource.track(x), trigger);
1071
- return {
1072
- track: x => source.track(x),
1073
- dispose() {
1074
- source.dispose();
1075
- oldSource.dispose();
1076
- }
1077
- };
1078
- },
1079
- untrack: fn => oldUntrack(() => untrackFn(fn))
1080
- };
1081
- } else {
1082
- externalSourceConfig = { factory: factory, untrack: untrackFn };
1122
+ function isValidLink(checkLink, sub) {
1123
+ const depsTail = sub._depsTail;
1124
+ if (depsTail !== null) {
1125
+ let link = sub._deps;
1126
+ do {
1127
+ if (link === checkLink) return true;
1128
+ if (link === depsTail) break;
1129
+ link = link._nextDep;
1130
+ } while (link !== null);
1083
1131
  }
1132
+ return false;
1084
1133
  }
1085
1134
  GlobalQueue._update = recompute;
1086
1135
  GlobalQueue._dispose = disposeChildren;
@@ -1184,7 +1233,7 @@ function recompute(el, create = false) {
1184
1233
  }
1185
1234
  try {
1186
1235
  value = handleAsync(el, el._fn(value));
1187
- clearStatus(el);
1236
+ clearStatus(el, create);
1188
1237
  const resolvedLane = resolveLane(el);
1189
1238
  if (resolvedLane) {
1190
1239
  resolvedLane._pendingAsync.delete(el);
@@ -1199,6 +1248,7 @@ function recompute(el, create = false) {
1199
1248
  updatePendingSignal(lane._source);
1200
1249
  }
1201
1250
  }
1251
+ if (e instanceof NotReadyError) el._blocked = true;
1202
1252
  notifyStatus(
1203
1253
  el,
1204
1254
  e instanceof NotReadyError ? STATUS_PENDING : STATUS_ERROR,
@@ -1319,8 +1369,10 @@ function computed(fn, initialValue, options) {
1319
1369
  self._name = options?.name ?? "computed";
1320
1370
  self._prevHeap = self;
1321
1371
  const parent = context?._root ? context._parentComputed : context;
1322
- if (leafEffectActive && context) {
1323
- throw new Error("Cannot create reactive primitives inside createTrackedEffect");
1372
+ if (context?._childrenForbidden) {
1373
+ throw new Error(
1374
+ "Cannot create reactive primitives inside createTrackedEffect or owner-backed onSettled"
1375
+ );
1324
1376
  }
1325
1377
  if (context) {
1326
1378
  const lastChild = context._firstChild;
@@ -1338,7 +1390,7 @@ function computed(fn, initialValue, options) {
1338
1390
  const source = externalSourceConfig.factory(self._fn, () => {
1339
1391
  setSignal(bridgeSignal, undefined);
1340
1392
  });
1341
- onCleanup(() => source.dispose());
1393
+ cleanup(() => source.dispose());
1342
1394
  self._fn = prev => {
1343
1395
  read(bridgeSignal);
1344
1396
  return source.track(prev);
@@ -1441,12 +1493,19 @@ function read(el) {
1441
1493
  return value;
1442
1494
  }
1443
1495
  if (pendingCheckActive) {
1444
- const target = el._firewall || el;
1445
- const pendingSig = getPendingSignal(target);
1496
+ const firewall = el._firewall;
1446
1497
  const prevCheck = pendingCheckActive;
1447
1498
  pendingCheckActive = false;
1448
- if (read(pendingSig)) {
1449
- foundPending = true;
1499
+ if (firewall && el._overrideValue !== undefined) {
1500
+ if (
1501
+ el._overrideValue !== NOT_PENDING &&
1502
+ (firewall._inFlight || !!(firewall._statusFlags & STATUS_PENDING))
1503
+ ) {
1504
+ foundPending = true;
1505
+ }
1506
+ } else {
1507
+ if (read(getPendingSignal(el))) foundPending = true;
1508
+ if (firewall && read(getPendingSignal(firewall))) foundPending = true;
1450
1509
  }
1451
1510
  pendingCheckActive = prevCheck;
1452
1511
  return el._value;
@@ -1482,9 +1541,13 @@ function read(el) {
1482
1541
  }
1483
1542
  }
1484
1543
  if (owner._statusFlags & STATUS_PENDING) {
1485
- const _errSource = owner._error?.source;
1486
- if (_errSource && !(_errSource._statusFlags & STATUS_PENDING)) clearStatus(owner);
1487
- else if (c && !(stale && owner._transition && activeTransition !== owner._transition)) {
1544
+ if (c && !(stale && owner._transition && activeTransition !== owner._transition)) {
1545
+ if (c?._childrenForbidden) {
1546
+ console.warn(
1547
+ "Reading a pending async value inside createTrackedEffect or onSettled will throw. " +
1548
+ "Use createEffect instead which supports async-aware reactivity."
1549
+ );
1550
+ }
1488
1551
  if (currentOptimisticLane) {
1489
1552
  const pendingLane = owner._optimisticLane;
1490
1553
  const lane = findLane(currentOptimisticLane);
@@ -1520,8 +1583,10 @@ function read(el) {
1520
1583
  `Reactive value read directly in ${strictRead} will not update. ` +
1521
1584
  `Move it into a tracking scope (JSX, a memo, or an effect's compute function).`
1522
1585
  );
1523
- if (el._overrideValue !== undefined && el._overrideValue !== NOT_PENDING)
1586
+ if (el._overrideValue !== undefined && el._overrideValue !== NOT_PENDING) {
1587
+ if (c && stale && shouldReadStashedOptimisticValue(el)) return el._value;
1524
1588
  return el._overrideValue;
1589
+ }
1525
1590
  return !c ||
1526
1591
  (currentOptimisticLane !== null &&
1527
1592
  (el._overrideValue !== undefined ||
@@ -1534,7 +1599,7 @@ function read(el) {
1534
1599
  : el._pendingValue;
1535
1600
  }
1536
1601
  function setSignal(el, v) {
1537
- if (!el._pureWrite && !leafEffectActive && context && el._firewall !== context)
1602
+ if (!el._pureWrite && !context?._childrenForbidden && context && el._firewall !== context)
1538
1603
  console.warn("A Signal was written to in an owned scope.");
1539
1604
  if (el._transition && activeTransition !== el._transition)
1540
1605
  globalQueue.initTransition(el._transition);
@@ -1559,7 +1624,7 @@ function setSignal(el, v) {
1559
1624
  }
1560
1625
  if (isOptimistic) {
1561
1626
  const firstOverride = el._overrideValue === NOT_PENDING;
1562
- if (!firstOverride && el._transition) globalQueue.initTransition(el._transition);
1627
+ if (!firstOverride) globalQueue.initTransition(resolveTransition(el));
1563
1628
  if (firstOverride) {
1564
1629
  el._pendingValue = el._value;
1565
1630
  globalQueue._optimisticNodes.push(el);
@@ -1582,6 +1647,10 @@ function setSignal(el, v) {
1582
1647
  return v;
1583
1648
  }
1584
1649
  function runWithOwner(owner, fn) {
1650
+ if (owner && owner._flags & REACTIVE_DISPOSED)
1651
+ console.warn(
1652
+ "runWithOwner called with a disposed owner. Children created inside will never be disposed."
1653
+ );
1585
1654
  const oldContext = context;
1586
1655
  const prevTracking = tracking;
1587
1656
  context = owner;
@@ -1605,6 +1674,10 @@ function getPendingSignal(el) {
1605
1674
  }
1606
1675
  function computePendingState(el) {
1607
1676
  const comp = el;
1677
+ const firewall = el._firewall;
1678
+ if (firewall && el._pendingValue !== NOT_PENDING) {
1679
+ return !firewall._inFlight && !(firewall._statusFlags & STATUS_PENDING);
1680
+ }
1608
1681
  if (el._overrideValue !== undefined && el._overrideValue !== NOT_PENDING) {
1609
1682
  if (comp._statusFlags & STATUS_PENDING && !(comp._statusFlags & STATUS_UNINITIALIZED))
1610
1683
  return true;
@@ -1614,6 +1687,9 @@ function computePendingState(el) {
1614
1687
  }
1615
1688
  return true;
1616
1689
  }
1690
+ if (el._overrideValue !== undefined && el._overrideValue === NOT_PENDING && !el._parentSource) {
1691
+ return false;
1692
+ }
1617
1693
  if (el._pendingValue !== NOT_PENDING && !(comp._statusFlags & STATUS_UNINITIALIZED)) return true;
1618
1694
  return !!(comp._statusFlags & STATUS_PENDING && !(comp._statusFlags & STATUS_UNINITIALIZED));
1619
1695
  }
@@ -1731,6 +1807,117 @@ function hasContext(context, owner) {
1731
1807
  function isUndefined(value) {
1732
1808
  return typeof value === "undefined";
1733
1809
  }
1810
+ function effect(compute, effect, error, initialValue, options) {
1811
+ let initialized = false;
1812
+ const node = computed(
1813
+ options?.render ? p => staleValues(() => compute(p)) : compute,
1814
+ initialValue,
1815
+ {
1816
+ ...options,
1817
+ equals: () => {
1818
+ node._modified = !node._error;
1819
+ if (initialized) node._queue.enqueue(node._type, runEffect.bind(node));
1820
+ return false;
1821
+ },
1822
+ lazy: true
1823
+ }
1824
+ );
1825
+ node._prevValue = initialValue;
1826
+ node._effectFn = effect;
1827
+ node._errorFn = error;
1828
+ node._cleanup = undefined;
1829
+ node._type = options?.render ? EFFECT_RENDER : EFFECT_USER;
1830
+ node._notifyStatus = (status, error) => {
1831
+ const actualStatus = status !== undefined ? status : node._statusFlags;
1832
+ const actualError = error !== undefined ? error : node._error;
1833
+ if (actualStatus & STATUS_ERROR) {
1834
+ let err = actualError;
1835
+ node._queue.notify(node, STATUS_PENDING, 0);
1836
+ if (node._type === EFFECT_USER) {
1837
+ try {
1838
+ return node._errorFn
1839
+ ? node._errorFn(err, () => {
1840
+ node._cleanup?.();
1841
+ node._cleanup = undefined;
1842
+ })
1843
+ : console.error(err);
1844
+ } catch (e) {
1845
+ err = e;
1846
+ }
1847
+ }
1848
+ if (!node._queue.notify(node, STATUS_ERROR, STATUS_ERROR)) throw err;
1849
+ } else if (node._type === EFFECT_RENDER) {
1850
+ node._queue.notify(node, STATUS_PENDING | STATUS_ERROR, actualStatus, actualError);
1851
+ if (_hitUnhandledAsync) {
1852
+ resetUnhandledAsync();
1853
+ const err = new Error("An async value must be rendered inside a Loading boundary.");
1854
+ if (!node._queue.notify(node, STATUS_ERROR, STATUS_ERROR)) throw err;
1855
+ }
1856
+ }
1857
+ };
1858
+ recompute(node, true);
1859
+ !options?.defer &&
1860
+ (node._type === EFFECT_USER
1861
+ ? node._queue.enqueue(node._type, runEffect.bind(node))
1862
+ : runEffect.call(node));
1863
+ initialized = true;
1864
+ cleanup(() => node._cleanup?.());
1865
+ if (!node._parent)
1866
+ console.warn("Effects created outside a reactive context will never be disposed");
1867
+ }
1868
+ function runEffect() {
1869
+ if (!this._modified || this._flags & REACTIVE_DISPOSED) return;
1870
+ let prevStrictRead = false;
1871
+ {
1872
+ prevStrictRead = setStrictRead("an effect callback");
1873
+ }
1874
+ this._cleanup?.();
1875
+ this._cleanup = undefined;
1876
+ try {
1877
+ this._cleanup = this._effectFn(this._value, this._prevValue);
1878
+ } catch (error) {
1879
+ this._error = new StatusError(this, error);
1880
+ this._statusFlags |= STATUS_ERROR;
1881
+ if (!this._queue.notify(this, STATUS_ERROR, STATUS_ERROR)) throw error;
1882
+ } finally {
1883
+ setStrictRead(prevStrictRead);
1884
+ this._prevValue = this._value;
1885
+ this._modified = false;
1886
+ }
1887
+ }
1888
+ function trackedEffect(fn, options) {
1889
+ const run = () => {
1890
+ if (!node._modified || node._flags & REACTIVE_DISPOSED) return;
1891
+ node._modified = false;
1892
+ recompute(node);
1893
+ };
1894
+ const node = computed(
1895
+ () => {
1896
+ node._cleanup?.();
1897
+ node._cleanup = undefined;
1898
+ node._cleanup = staleValues(fn) || undefined;
1899
+ },
1900
+ undefined,
1901
+ { ...options, lazy: true }
1902
+ );
1903
+ node._cleanup = undefined;
1904
+ node._childrenForbidden = true;
1905
+ node._modified = true;
1906
+ node._type = EFFECT_TRACKED;
1907
+ node._notifyStatus = (status, error) => {
1908
+ const actualStatus = status !== undefined ? status : node._statusFlags;
1909
+ if (actualStatus & STATUS_ERROR) {
1910
+ node._queue.notify(node, STATUS_PENDING, 0);
1911
+ const err = error !== undefined ? error : node._error;
1912
+ if (!node._queue.notify(node, STATUS_ERROR, STATUS_ERROR)) throw err;
1913
+ }
1914
+ };
1915
+ node._run = run;
1916
+ node._queue.enqueue(EFFECT_USER, run);
1917
+ cleanup(() => node._cleanup?.());
1918
+ if (!node._parent)
1919
+ console.warn("Effects created outside a reactive context will never be disposed");
1920
+ }
1734
1921
  function restoreTransition(transition, fn) {
1735
1922
  globalQueue.initTransition(transition);
1736
1923
  const result = fn();
@@ -1775,6 +1962,17 @@ function action(genFn) {
1775
1962
  step();
1776
1963
  });
1777
1964
  }
1965
+ function onCleanup(fn) {
1966
+ {
1967
+ const owner = getOwner();
1968
+ if (!owner) console.warn("onCleanup called outside a reactive context will never be run");
1969
+ else if (owner._childrenForbidden)
1970
+ throw new Error(
1971
+ "Cannot use onCleanup inside createTrackedEffect or onSettled; return a cleanup function instead"
1972
+ );
1973
+ }
1974
+ return cleanup(fn);
1975
+ }
1778
1976
  function accessor(node) {
1779
1977
  const fn = read.bind(null, node);
1780
1978
  fn.$r = true;
@@ -1808,16 +2006,16 @@ function createTrackedEffect(compute, options) {
1808
2006
  trackedEffect(compute, { ...options, name: options?.name ?? "trackedEffect" });
1809
2007
  }
1810
2008
  function createReaction(effectFn, options) {
1811
- let cleanup = undefined;
1812
- onCleanup(() => cleanup?.());
2009
+ let cl = undefined;
2010
+ cleanup(() => cl?.());
1813
2011
  const owner = getOwner();
1814
2012
  return tracking => {
1815
2013
  runWithOwner(owner, () => {
1816
2014
  effect(
1817
2015
  () => (tracking(), getOwner()),
1818
2016
  node => {
1819
- cleanup?.();
1820
- cleanup = (effectFn.effect || effectFn)?.();
2017
+ cl?.();
2018
+ cl = (effectFn.effect || effectFn)?.();
1821
2019
  dispose(node);
1822
2020
  },
1823
2021
  effectFn.error,
@@ -1828,6 +2026,11 @@ function createReaction(effectFn, options) {
1828
2026
  };
1829
2027
  }
1830
2028
  function resolve(fn) {
2029
+ if (getObserver()) {
2030
+ throw new Error(
2031
+ "Cannot call resolve inside a reactive scope; it only resolves the current value and does not track updates."
2032
+ );
2033
+ }
1831
2034
  return new Promise((res, rej) => {
1832
2035
  createRoot(dispose => {
1833
2036
  computed(() => {
@@ -1850,7 +2053,8 @@ function createOptimistic(first, second, third) {
1850
2053
  return [accessor(node), setSignal.bind(null, node)];
1851
2054
  }
1852
2055
  function onSettled(callback) {
1853
- getOwner()
2056
+ const owner = getOwner();
2057
+ owner && !owner._childrenForbidden
1854
2058
  ? createTrackedEffect(() => untrack(callback))
1855
2059
  : globalQueue.enqueue(EFFECT_USER, () => {
1856
2060
  const cleanup = callback();
@@ -2025,8 +2229,16 @@ function createProjectionInternal(fn, initialValue = {}, options) {
2025
2229
  const wrappedStore = wrapProjection(initialValue);
2026
2230
  node = computed(() => {
2027
2231
  const owner = getOwner();
2028
- storeSetter(new Proxy(wrappedStore, writeTraps), s => {
2029
- const value = handleAsync(owner, fn(s), value => {
2232
+ let settled = false;
2233
+ let result;
2234
+ const draft = new Proxy(
2235
+ wrappedStore,
2236
+ createWriteTraps(() => !settled || owner._inFlight === result)
2237
+ );
2238
+ storeSetter(draft, s => {
2239
+ result = fn(s);
2240
+ settled = true;
2241
+ const value = handleAsync(owner, result, value => {
2030
2242
  value !== s &&
2031
2243
  value !== undefined &&
2032
2244
  storeSetter(wrappedStore, reconcile(value, options?.key || "id"));
@@ -2040,42 +2252,47 @@ function createProjectionInternal(fn, initialValue = {}, options) {
2040
2252
  function createProjection(fn, initialValue = {}, options) {
2041
2253
  return createProjectionInternal(fn, initialValue, options).store;
2042
2254
  }
2043
- const writeTraps = {
2044
- get(_, prop) {
2045
- let value;
2046
- setWriteOverride(true);
2047
- setProjectionWriteActive(true);
2048
- try {
2049
- value = _[prop];
2050
- } finally {
2051
- setWriteOverride(false);
2052
- setProjectionWriteActive(false);
2053
- }
2054
- return typeof value === "object" && value !== null ? new Proxy(value, writeTraps) : value;
2055
- },
2056
- set(_, prop, value) {
2057
- setWriteOverride(true);
2058
- setProjectionWriteActive(true);
2059
- try {
2060
- _[prop] = value;
2061
- } finally {
2062
- setWriteOverride(false);
2063
- setProjectionWriteActive(false);
2064
- }
2065
- return true;
2066
- },
2067
- deleteProperty(_, prop) {
2068
- setWriteOverride(true);
2069
- setProjectionWriteActive(true);
2070
- try {
2071
- delete _[prop];
2072
- } finally {
2073
- setWriteOverride(false);
2074
- setProjectionWriteActive(false);
2255
+ function createWriteTraps(isActive) {
2256
+ const traps = {
2257
+ get(_, prop) {
2258
+ let value;
2259
+ setWriteOverride(true);
2260
+ setProjectionWriteActive(true);
2261
+ try {
2262
+ value = _[prop];
2263
+ } finally {
2264
+ setWriteOverride(false);
2265
+ setProjectionWriteActive(false);
2266
+ }
2267
+ return typeof value === "object" && value !== null ? new Proxy(value, traps) : value;
2268
+ },
2269
+ set(_, prop, value) {
2270
+ if (isActive && !isActive()) return true;
2271
+ setWriteOverride(true);
2272
+ setProjectionWriteActive(true);
2273
+ try {
2274
+ _[prop] = value;
2275
+ } finally {
2276
+ setWriteOverride(false);
2277
+ setProjectionWriteActive(false);
2278
+ }
2279
+ return true;
2280
+ },
2281
+ deleteProperty(_, prop) {
2282
+ if (isActive && !isActive()) return true;
2283
+ setWriteOverride(true);
2284
+ setProjectionWriteActive(true);
2285
+ try {
2286
+ delete _[prop];
2287
+ } finally {
2288
+ setWriteOverride(false);
2289
+ setProjectionWriteActive(false);
2290
+ }
2291
+ return true;
2075
2292
  }
2076
- return true;
2077
- }
2078
- };
2293
+ };
2294
+ return traps;
2295
+ }
2079
2296
  const $TRACK = Symbol("STORE_TRACK"),
2080
2297
  $TARGET = Symbol("STORE_TARGET"),
2081
2298
  $PROXY = Symbol("STORE_PROXY"),
@@ -2485,10 +2702,18 @@ function createOptimisticProjectionInternal(fn, initialValue = {}, options) {
2485
2702
  if (fn) {
2486
2703
  node = computed(() => {
2487
2704
  const owner = getOwner();
2705
+ let settled = false;
2706
+ let result;
2707
+ const draft = new Proxy(
2708
+ wrappedStore,
2709
+ createWriteTraps(() => !settled || owner._inFlight === result)
2710
+ );
2488
2711
  setProjectionWriteActive(true);
2489
2712
  try {
2490
- storeSetter(new Proxy(wrappedStore, writeTraps), s => {
2491
- const value = handleAsync(owner, fn(s), value => {
2713
+ storeSetter(draft, s => {
2714
+ result = fn(s);
2715
+ settled = true;
2716
+ const value = handleAsync(owner, result, value => {
2492
2717
  setProjectionWriteActive(true);
2493
2718
  try {
2494
2719
  value !== s &&
@@ -3013,7 +3238,7 @@ function boundaryComputed(fn, propagationMask) {
3013
3238
  function createBoundChildren(owner, fn, queue, mask) {
3014
3239
  const parentQueue = owner._queue;
3015
3240
  parentQueue.addChild((owner._queue = queue));
3016
- onCleanup(() => parentQueue.removeChild(owner._queue));
3241
+ cleanup(() => parentQueue.removeChild(owner._queue));
3017
3242
  return runWithOwner(owner, () => {
3018
3243
  const c = computed(fn);
3019
3244
  return boundaryComputed(() => staleValues(() => flatten(read(c))), mask);
@@ -3053,6 +3278,9 @@ class CollectionQueue extends Queue {
3053
3278
  }
3054
3279
  if (this._collectionType & STATUS_PENDING && this._initialized)
3055
3280
  return super.notify(node, type, flags, error);
3281
+ if (this._collectionType & STATUS_PENDING && flags & STATUS_ERROR) {
3282
+ return super.notify(node, STATUS_ERROR, flags, error);
3283
+ }
3056
3284
  if (flags & this._collectionType) {
3057
3285
  const source = error?.source || node._error?.source;
3058
3286
  if (source) {
@@ -3066,7 +3294,11 @@ class CollectionQueue extends Queue {
3066
3294
  }
3067
3295
  checkSources() {
3068
3296
  for (const source of this._sources) {
3069
- if (!(source._statusFlags & this._collectionType)) this._sources.delete(source);
3297
+ if (
3298
+ !(source._statusFlags & this._collectionType) &&
3299
+ !(this._collectionType & STATUS_ERROR && source._statusFlags & STATUS_PENDING)
3300
+ )
3301
+ this._sources.delete(source);
3070
3302
  }
3071
3303
  if (!this._sources.size) {
3072
3304
  setSignal(this._disabled, false);
@@ -3079,6 +3311,8 @@ class CollectionQueue extends Queue {
3079
3311
  }
3080
3312
  }
3081
3313
  function createCollectionBoundary(type, fn, fallback, onFn) {
3314
+ if (!getOwner())
3315
+ console.warn("Boundaries created outside a reactive context will never be disposed.");
3082
3316
  const owner = createOwner();
3083
3317
  const queue = new CollectionQueue(type);
3084
3318
  if (onFn) queue._onFn = onFn;