@rlabs-inc/signals 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +114 -0
  2. package/dist/collections/map.d.ts.map +1 -1
  3. package/dist/collections/set.d.ts.map +1 -1
  4. package/dist/core/constants.d.ts +6 -0
  5. package/dist/core/constants.d.ts.map +1 -1
  6. package/dist/deep/proxy.d.ts.map +1 -1
  7. package/dist/index.d.ts +7 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +504 -61
  10. package/dist/index.mjs +504 -61
  11. package/dist/primitives/bind.d.ts.map +1 -1
  12. package/dist/primitives/derived.d.ts.map +1 -1
  13. package/dist/primitives/effect.d.ts.map +1 -1
  14. package/dist/primitives/linked.d.ts +73 -0
  15. package/dist/primitives/linked.d.ts.map +1 -0
  16. package/dist/primitives/scope.d.ts +96 -0
  17. package/dist/primitives/scope.d.ts.map +1 -0
  18. package/dist/primitives/selector.d.ts +46 -0
  19. package/dist/primitives/selector.d.ts.map +1 -0
  20. package/dist/primitives/signal.d.ts +5 -0
  21. package/dist/primitives/signal.d.ts.map +1 -1
  22. package/dist/reactivity/scheduling.d.ts.map +1 -1
  23. package/dist/reactivity/tracking.d.ts +5 -0
  24. package/dist/reactivity/tracking.d.ts.map +1 -1
  25. package/dist/v2/bench-compare.d.ts +2 -0
  26. package/dist/v2/bench-compare.d.ts.map +1 -0
  27. package/dist/v2/bench.d.ts +5 -0
  28. package/dist/v2/bench.d.ts.map +1 -0
  29. package/dist/v2/bind.d.ts +94 -0
  30. package/dist/v2/bind.d.ts.map +1 -0
  31. package/dist/v2/collections.d.ts +133 -0
  32. package/dist/v2/collections.d.ts.map +1 -0
  33. package/dist/v2/compat-test.d.ts +2 -0
  34. package/dist/v2/compat-test.d.ts.map +1 -0
  35. package/dist/v2/debug-array.d.ts +2 -0
  36. package/dist/v2/debug-array.d.ts.map +1 -0
  37. package/dist/v2/debug-diamond.d.ts +2 -0
  38. package/dist/v2/debug-diamond.d.ts.map +1 -0
  39. package/dist/v2/index.d.ts +7 -0
  40. package/dist/v2/index.d.ts.map +1 -0
  41. package/dist/v2/primitives.d.ts +120 -0
  42. package/dist/v2/primitives.d.ts.map +1 -0
  43. package/dist/v2/proxy.d.ts +10 -0
  44. package/dist/v2/proxy.d.ts.map +1 -0
  45. package/dist/v2/registry.d.ts +35 -0
  46. package/dist/v2/registry.d.ts.map +1 -0
  47. package/dist/v2/stress.d.ts +7 -0
  48. package/dist/v2/stress.d.ts.map +1 -0
  49. package/dist/v2/test-suite.d.ts +2 -0
  50. package/dist/v2/test-suite.d.ts.map +1 -0
  51. package/dist/v2/test-v1.d.ts +2 -0
  52. package/dist/v2/test-v1.d.ts.map +1 -0
  53. package/package.json +7 -1
package/dist/index.js CHANGED
@@ -54,10 +54,13 @@ __export(exports_src, {
54
54
  readVersion: () => readVersion,
55
55
  proxy: () => proxy,
56
56
  peek: () => peek,
57
+ onScopeDispose: () => onScopeDispose,
57
58
  neverEquals: () => neverEquals,
58
59
  mutableSource: () => mutableSource,
59
60
  markReactions: () => markReactions,
61
+ linkedSignal: () => linkedSignal,
60
62
  isReactive: () => isReactive,
63
+ isLinkedSignal: () => isLinkedSignal,
61
64
  isDirty: () => isDirty,
62
65
  isBinding: () => isBinding,
63
66
  incrementWriteVersion: () => incrementWriteVersion,
@@ -65,15 +68,18 @@ __export(exports_src, {
65
68
  incrementBatchDepth: () => incrementBatchDepth,
66
69
  getWriteVersion: () => getWriteVersion,
67
70
  getReadVersion: () => getReadVersion,
71
+ getCurrentScope: () => getCurrentScope,
68
72
  getBatchDepth: () => getBatchDepth,
69
73
  get: () => get,
70
74
  flushSync: () => flushSync,
71
75
  equals: () => equals,
76
+ effectScope: () => effectScope,
72
77
  effect: () => effect,
73
78
  disconnectDerived: () => disconnectDerived,
74
79
  destroyEffect: () => destroyEffect,
75
80
  derived: () => derived,
76
81
  decrementBatchDepth: () => decrementBatchDepth,
82
+ createSelector: () => createSelector,
77
83
  createEquals: () => createEquals,
78
84
  createEffect: () => createEffect,
79
85
  createDerived: () => createDerived,
@@ -97,6 +103,7 @@ __export(exports_src, {
97
103
  REACTIVE_MARKER: () => REACTIVE_MARKER,
98
104
  REACTION_IS_UPDATING: () => REACTION_IS_UPDATING,
99
105
  MAYBE_DIRTY: () => MAYBE_DIRTY,
106
+ LINKED_SYMBOL: () => LINKED_SYMBOL,
100
107
  INERT: () => INERT,
101
108
  EFFECT_RAN: () => EFFECT_RAN,
102
109
  EFFECT_PRESERVED: () => EFFECT_PRESERVED,
@@ -107,7 +114,8 @@ __export(exports_src, {
107
114
  DERIVED: () => DERIVED,
108
115
  CLEAN: () => CLEAN,
109
116
  BRANCH_EFFECT: () => BRANCH_EFFECT,
110
- BLOCK_EFFECT: () => BLOCK_EFFECT
117
+ BLOCK_EFFECT: () => BLOCK_EFFECT,
118
+ BINDING_SYMBOL: () => BINDING_SYMBOL
111
119
  });
112
120
  module.exports = __toCommonJS(exports_src);
113
121
 
@@ -176,6 +184,9 @@ var UNINITIALIZED = Symbol.for("rlabs.signals.uninitialized");
176
184
  var STALE_REACTION = Symbol.for("rlabs.signals.stale_reaction");
177
185
  var STATE_SYMBOL = Symbol.for("rlabs.signals.state");
178
186
  var REACTIVE_MARKER = Symbol.for("rlabs.signals.reactive");
187
+ var BINDING_SYMBOL = Symbol.for("rlabs.signals.binding");
188
+ var LINKED_SYMBOL = Symbol.for("rlabs.signals.linked");
189
+ var SOURCE = 1 << 0;
179
190
  var STATUS_MASK = ~(DIRTY | MAYBE_DIRTY | CLEAN);
180
191
 
181
192
  // src/core/globals.ts
@@ -300,6 +311,9 @@ function setUpdateEffectImpl(impl) {
300
311
  function flushEffects() {
301
312
  const roots = clearQueuedRootEffects();
302
313
  for (const root of roots) {
314
+ if ((root.f & INERT) !== 0) {
315
+ continue;
316
+ }
303
317
  if (isDirty(root)) {
304
318
  updateEffectImpl(root);
305
319
  }
@@ -310,7 +324,7 @@ function processEffectTree(effect) {
310
324
  let child = effect.first;
311
325
  while (child !== null) {
312
326
  const next = child.next;
313
- if (isDirty(child)) {
327
+ if ((child.f & INERT) === 0 && isDirty(child)) {
314
328
  updateEffectImpl(child);
315
329
  }
316
330
  if (child.first !== null) {
@@ -323,6 +337,9 @@ function flushPendingReactions() {
323
337
  const reactions = [...pendingReactions];
324
338
  clearPendingReactions();
325
339
  for (const reaction of reactions) {
340
+ if ((reaction.f & INERT) !== 0) {
341
+ continue;
342
+ }
326
343
  if (isDirty(reaction)) {
327
344
  if ("parent" in reaction) {
328
345
  updateEffectImpl(reaction);
@@ -354,6 +371,9 @@ function flushSync(fn) {
354
371
  continue;
355
372
  }
356
373
  for (const root of roots) {
374
+ if ((root.f & INERT) !== 0) {
375
+ continue;
376
+ }
357
377
  if (isDirty(root)) {
358
378
  updateEffectImpl(root);
359
379
  }
@@ -400,13 +420,63 @@ function get(signal) {
400
420
  }
401
421
  }
402
422
  if ((signal.f & DERIVED) !== 0) {
403
- const derived = signal;
404
- if (isDirty(derived)) {
405
- updateDerived(derived);
406
- }
423
+ updateDerivedChain(signal);
407
424
  }
408
425
  return signal.v;
409
426
  }
427
+ var updateCycleId = 0;
428
+ var processedInCycle = new WeakMap;
429
+ function updateDerivedChain(target) {
430
+ if ((target.f & (DIRTY | MAYBE_DIRTY)) === 0) {
431
+ return;
432
+ }
433
+ const cycleId = ++updateCycleId;
434
+ const chain = [target];
435
+ processedInCycle.set(target, cycleId);
436
+ let idx = 0;
437
+ while (idx < chain.length) {
438
+ const current = chain[idx];
439
+ idx++;
440
+ if ((current.f & (DIRTY | MAYBE_DIRTY)) === 0) {
441
+ continue;
442
+ }
443
+ const deps = current.deps;
444
+ if (deps !== null) {
445
+ for (let i = 0;i < deps.length; i++) {
446
+ const dep = deps[i];
447
+ if ((dep.f & DERIVED) !== 0 && (dep.f & (DIRTY | MAYBE_DIRTY)) !== 0 && processedInCycle.get(dep) !== cycleId) {
448
+ chain.push(dep);
449
+ processedInCycle.set(dep, cycleId);
450
+ }
451
+ }
452
+ }
453
+ }
454
+ for (let i = chain.length - 1;i >= 0; i--) {
455
+ const current = chain[i];
456
+ if ((current.f & (DIRTY | MAYBE_DIRTY)) === 0) {
457
+ continue;
458
+ }
459
+ if ((current.f & DIRTY) !== 0) {
460
+ updateDerived(current);
461
+ } else {
462
+ const deps = current.deps;
463
+ let needsUpdate = false;
464
+ if (deps !== null) {
465
+ for (let j = 0;j < deps.length; j++) {
466
+ if (deps[j].wv > current.wv) {
467
+ needsUpdate = true;
468
+ break;
469
+ }
470
+ }
471
+ }
472
+ if (needsUpdate) {
473
+ updateDerived(current);
474
+ } else {
475
+ setSignalStatus(current, CLEAN);
476
+ }
477
+ }
478
+ }
479
+ }
410
480
  function set(signal, value) {
411
481
  if (activeReaction !== null && (activeReaction.f & DERIVED) !== 0) {
412
482
  throw new Error("Cannot write to signals inside a derived. " + "Deriveds should be pure computations with no side effects.");
@@ -422,20 +492,24 @@ function set(signal, value) {
422
492
  return value;
423
493
  }
424
494
  function markReactions(signal, status) {
425
- const reactions = signal.reactions;
426
- if (reactions === null)
427
- return;
428
- for (let i = 0;i < reactions.length; i++) {
429
- const reaction = reactions[i];
430
- const flags = reaction.f;
431
- const notDirty = (flags & DIRTY) === 0;
432
- if (notDirty) {
433
- setSignalStatus(reaction, status);
434
- }
435
- if ((flags & DERIVED) !== 0) {
436
- markReactions(reaction, MAYBE_DIRTY);
437
- } else if (notDirty) {
438
- scheduleEffect(reaction);
495
+ const stack = [{ signal, status }];
496
+ while (stack.length > 0) {
497
+ const { signal: currentSignal, status: currentStatus } = stack.pop();
498
+ const reactions = currentSignal.reactions;
499
+ if (reactions === null)
500
+ continue;
501
+ for (let i = 0;i < reactions.length; i++) {
502
+ const reaction = reactions[i];
503
+ const flags = reaction.f;
504
+ const notDirty = (flags & DIRTY) === 0;
505
+ if (notDirty) {
506
+ setSignalStatus(reaction, currentStatus);
507
+ }
508
+ if ((flags & DERIVED) !== 0) {
509
+ stack.push({ signal: reaction, status: MAYBE_DIRTY });
510
+ } else if (notDirty) {
511
+ scheduleEffect(reaction);
512
+ }
439
513
  }
440
514
  }
441
515
  }
@@ -446,22 +520,66 @@ function isDirty(reaction) {
446
520
  if ((reaction.f & DIRTY) !== 0) {
447
521
  return true;
448
522
  }
449
- if ((reaction.f & MAYBE_DIRTY) !== 0) {
450
- const deps = reaction.deps;
451
- if (deps !== null) {
452
- for (let i = 0;i < deps.length; i++) {
453
- const dep = deps[i];
454
- if ((dep.f & DERIVED) !== 0) {
455
- if (isDirty(dep)) {
456
- updateDerived(dep);
457
- }
523
+ if ((reaction.f & MAYBE_DIRTY) === 0) {
524
+ return false;
525
+ }
526
+ const toCheck = [reaction];
527
+ const toUpdate = [];
528
+ let idx = 0;
529
+ while (idx < toCheck.length) {
530
+ const current = toCheck[idx];
531
+ idx++;
532
+ if ((current.f & DIRTY) !== 0) {
533
+ continue;
534
+ }
535
+ if ((current.f & MAYBE_DIRTY) === 0) {
536
+ continue;
537
+ }
538
+ const deps2 = current.deps;
539
+ if (deps2 !== null) {
540
+ for (let i = 0;i < deps2.length; i++) {
541
+ const dep = deps2[i];
542
+ if ((dep.f & DERIVED) !== 0 && (dep.f & (DIRTY | MAYBE_DIRTY)) !== 0) {
543
+ toCheck.push(dep);
458
544
  }
459
- if (dep.wv > reaction.wv) {
460
- return true;
545
+ }
546
+ }
547
+ }
548
+ for (let i = toCheck.length - 1;i >= 0; i--) {
549
+ const current = toCheck[i];
550
+ if ((current.f & DERIVED) === 0) {
551
+ continue;
552
+ }
553
+ if ((current.f & DIRTY) !== 0) {
554
+ updateDerived(current);
555
+ } else if ((current.f & MAYBE_DIRTY) !== 0) {
556
+ const deps2 = current.deps;
557
+ let needsUpdate = false;
558
+ if (deps2 !== null) {
559
+ for (let j = 0;j < deps2.length; j++) {
560
+ if (deps2[j].wv > current.wv) {
561
+ needsUpdate = true;
562
+ break;
563
+ }
461
564
  }
462
565
  }
566
+ if (needsUpdate) {
567
+ updateDerived(current);
568
+ } else {
569
+ setSignalStatus(current, CLEAN);
570
+ }
571
+ }
572
+ }
573
+ if ((reaction.f & DIRTY) !== 0) {
574
+ return true;
575
+ }
576
+ const deps = reaction.deps;
577
+ if (deps !== null) {
578
+ for (let i = 0;i < deps.length; i++) {
579
+ if (deps[i].wv > reaction.wv) {
580
+ return true;
581
+ }
463
582
  }
464
- setSignalStatus(reaction, CLEAN);
465
583
  }
466
584
  return false;
467
585
  }
@@ -565,6 +683,7 @@ function updateReaction(reaction) {
565
683
  }
566
684
 
567
685
  // src/primitives/signal.ts
686
+ var SOURCE_SYMBOL = Symbol("signal.source");
568
687
  function source(initialValue, options) {
569
688
  return {
570
689
  f: 0,
@@ -578,9 +697,13 @@ function source(initialValue, options) {
578
697
  function mutableSource(initialValue) {
579
698
  return source(initialValue, { equals: safeEquals });
580
699
  }
700
+ var signalCleanup = new FinalizationRegistry((src) => {
701
+ src.reactions = null;
702
+ });
581
703
  function signal(initialValue, options) {
582
704
  const src = source(initialValue, options);
583
- return {
705
+ const sig = {
706
+ [SOURCE_SYMBOL]: src,
584
707
  get value() {
585
708
  return get(src);
586
709
  },
@@ -588,6 +711,11 @@ function signal(initialValue, options) {
588
711
  set(src, newValue);
589
712
  }
590
713
  };
714
+ signalCleanup.register(sig, src);
715
+ return sig;
716
+ }
717
+ function getSource(sig) {
718
+ return sig[SOURCE_SYMBOL];
591
719
  }
592
720
  var proxyFn = null;
593
721
  function setProxyFn(fn) {
@@ -604,10 +732,21 @@ function stateRaw(initialValue) {
604
732
  }
605
733
 
606
734
  // src/deep/proxy.ts
735
+ var proxyCache = new WeakMap;
736
+ var proxyCleanup = new FinalizationRegistry((data) => {
737
+ data.version.reactions = null;
738
+ for (const src of data.sources.values()) {
739
+ src.reactions = null;
740
+ }
741
+ data.sources.clear();
742
+ });
607
743
  function shouldProxy(value) {
608
744
  if (value === null || typeof value !== "object") {
609
745
  return false;
610
746
  }
747
+ if (BINDING_SYMBOL in value) {
748
+ return false;
749
+ }
611
750
  const proto = Object.getPrototypeOf(value);
612
751
  return proto === Object.prototype || proto === Array.prototype || proto === null;
613
752
  }
@@ -622,6 +761,10 @@ function proxy(value) {
622
761
  if (!shouldProxy(value) || isProxy(value)) {
623
762
  return value;
624
763
  }
764
+ const cached = proxyCache.get(value);
765
+ if (cached) {
766
+ return cached;
767
+ }
625
768
  const sources = new Map;
626
769
  const version = source(0);
627
770
  const isArray = Array.isArray(value);
@@ -641,7 +784,7 @@ function proxy(value) {
641
784
  setActiveReaction(prevReaction);
642
785
  }
643
786
  };
644
- const getSource = (prop, initialValue) => {
787
+ const getSource2 = (prop, initialValue) => {
645
788
  let s = sources.get(prop);
646
789
  if (s === undefined) {
647
790
  s = withParent(() => {
@@ -664,7 +807,7 @@ function proxy(value) {
664
807
  return currentValue.bind(proxyObj);
665
808
  }
666
809
  if (exists || isWritable(target, prop)) {
667
- const s = getSource(prop, currentValue);
810
+ const s = getSource2(prop, currentValue);
668
811
  const val = get(s);
669
812
  if (val === UNINITIALIZED) {
670
813
  return;
@@ -673,7 +816,7 @@ function proxy(value) {
673
816
  }
674
817
  return currentValue;
675
818
  },
676
- set(target, prop, newValue, receiver) {
819
+ set(target, prop, newValue) {
677
820
  const exists = prop in target;
678
821
  let s = sources.get(prop);
679
822
  if (s === undefined) {
@@ -683,29 +826,35 @@ function proxy(value) {
683
826
  s = withParent(() => source(undefined));
684
827
  sources.set(prop, s);
685
828
  }
686
- const proxied = withParent(() => shouldProxy(newValue) ? proxy(newValue) : newValue);
829
+ let proxied;
830
+ if (shouldProxy(newValue)) {
831
+ proxied = withParent(() => proxy(newValue));
832
+ } else {
833
+ proxied = newValue;
834
+ }
687
835
  set(s, proxied);
688
- Reflect.set(target, prop, newValue, receiver);
689
- if (isArray && prop === "length") {
690
- const oldLength = s.v;
691
- const newLength = newValue;
692
- for (let i = newLength;i < oldLength; i++) {
693
- const indexKey = String(i);
694
- const indexSource = sources.get(indexKey);
695
- if (indexSource !== undefined) {
696
- set(indexSource, UNINITIALIZED);
697
- } else if (i in target) {
698
- const deletedSource = withParent(() => source(UNINITIALIZED));
699
- sources.set(indexKey, deletedSource);
836
+ target[prop] = newValue;
837
+ if (isArray) {
838
+ if (prop === "length") {
839
+ const oldLength = s.v;
840
+ const newLength = newValue;
841
+ for (let i = newLength;i < oldLength; i++) {
842
+ const indexKey = String(i);
843
+ const indexSource = sources.get(indexKey);
844
+ if (indexSource !== undefined) {
845
+ set(indexSource, UNINITIALIZED);
846
+ } else if (i in target) {
847
+ const deletedSource = withParent(() => source(UNINITIALIZED));
848
+ sources.set(indexKey, deletedSource);
849
+ }
700
850
  }
701
- }
702
- }
703
- if (isArray && typeof prop === "string") {
704
- const index = Number(prop);
705
- if (Number.isInteger(index) && index >= 0) {
706
- const lengthSource = sources.get("length");
707
- if (lengthSource !== undefined && index >= lengthSource.v) {
708
- set(lengthSource, index + 1);
851
+ } else if (typeof prop === "string") {
852
+ const index = Number(prop);
853
+ if (Number.isInteger(index) && index >= 0) {
854
+ const lengthSource = sources.get("length");
855
+ if (lengthSource !== undefined && index >= lengthSource.v) {
856
+ set(lengthSource, index + 1);
857
+ }
709
858
  }
710
859
  }
711
860
  }
@@ -726,7 +875,7 @@ function proxy(value) {
726
875
  }
727
876
  set(version, get(version) + 1);
728
877
  }
729
- return Reflect.deleteProperty(target, prop);
878
+ return delete target[prop];
730
879
  },
731
880
  has(target, prop) {
732
881
  if (prop === STATE_SYMBOL) {
@@ -764,6 +913,8 @@ function proxy(value) {
764
913
  return Reflect.getOwnPropertyDescriptor(target, prop);
765
914
  }
766
915
  });
916
+ proxyCleanup.register(proxyObj, { sources, version });
917
+ proxyCache.set(value, proxyObj);
767
918
  return proxyObj;
768
919
  }
769
920
  function toRaw(value) {
@@ -776,6 +927,18 @@ function isReactive(value) {
776
927
  return isProxy(value);
777
928
  }
778
929
  // src/primitives/derived.ts
930
+ var derivedCleanup = new FinalizationRegistry((d) => {
931
+ removeReactions(d, 0);
932
+ d.deps = null;
933
+ d.reactions = null;
934
+ const effects = d.effects;
935
+ if (effects !== null) {
936
+ d.effects = null;
937
+ for (let i = 0;i < effects.length; i++) {
938
+ destroyEffectImpl(effects[i]);
939
+ }
940
+ }
941
+ });
779
942
  function createDerived(fn, options) {
780
943
  let flags = DERIVED | DIRTY;
781
944
  const parentDerived = activeReaction !== null && (activeReaction.f & DERIVED) !== 0 ? activeReaction : null;
@@ -826,11 +989,13 @@ function destroyDerivedEffects(derived) {
826
989
  }
827
990
  function derived(fn, options) {
828
991
  const d = createDerived(fn, options);
829
- return {
992
+ const wrapper = {
830
993
  get value() {
831
994
  return get(d);
832
995
  }
833
996
  };
997
+ derivedCleanup.register(wrapper, d);
998
+ return wrapper;
834
999
  }
835
1000
  derived.by = derived;
836
1001
  function disconnectDerived(derived2) {
@@ -839,6 +1004,123 @@ function disconnectDerived(derived2) {
839
1004
  derived2.reactions = null;
840
1005
  destroyDerivedEffects(derived2);
841
1006
  }
1007
+ // src/primitives/scope.ts
1008
+ var activeScope = null;
1009
+ function getCurrentScope() {
1010
+ return activeScope;
1011
+ }
1012
+ function setActiveScope(scope) {
1013
+ const prev = activeScope;
1014
+ activeScope = scope;
1015
+ return prev;
1016
+ }
1017
+
1018
+ class EffectScopeImpl {
1019
+ _active = true;
1020
+ _paused = false;
1021
+ effects = [];
1022
+ cleanups = [];
1023
+ parent = null;
1024
+ scopes = null;
1025
+ constructor(detached = false) {
1026
+ this.parent = activeScope;
1027
+ if (!detached && this.parent) {
1028
+ if (this.parent.scopes === null) {
1029
+ this.parent.scopes = [];
1030
+ }
1031
+ this.parent.scopes.push(this);
1032
+ }
1033
+ }
1034
+ get active() {
1035
+ return this._active;
1036
+ }
1037
+ get paused() {
1038
+ return this._paused;
1039
+ }
1040
+ run(fn) {
1041
+ if (!this._active) {
1042
+ return;
1043
+ }
1044
+ const prevScope = setActiveScope(this);
1045
+ try {
1046
+ return fn();
1047
+ } finally {
1048
+ setActiveScope(prevScope);
1049
+ }
1050
+ }
1051
+ stop() {
1052
+ if (!this._active)
1053
+ return;
1054
+ for (const effect of [...this.effects]) {
1055
+ destroyEffect(effect);
1056
+ }
1057
+ this.effects.length = 0;
1058
+ for (const cleanup of this.cleanups) {
1059
+ try {
1060
+ cleanup();
1061
+ } catch {}
1062
+ }
1063
+ this.cleanups.length = 0;
1064
+ if (this.scopes) {
1065
+ for (const scope of this.scopes) {
1066
+ scope.stop();
1067
+ }
1068
+ this.scopes.length = 0;
1069
+ }
1070
+ if (this.parent?.scopes) {
1071
+ const idx = this.parent.scopes.indexOf(this);
1072
+ if (idx !== -1) {
1073
+ this.parent.scopes.splice(idx, 1);
1074
+ }
1075
+ }
1076
+ this._active = false;
1077
+ }
1078
+ pause() {
1079
+ if (!this._active || this._paused)
1080
+ return;
1081
+ this._paused = true;
1082
+ for (const effect of this.effects) {
1083
+ effect.f |= INERT;
1084
+ }
1085
+ if (this.scopes) {
1086
+ for (const scope of this.scopes) {
1087
+ scope.pause();
1088
+ }
1089
+ }
1090
+ }
1091
+ resume() {
1092
+ if (!this._active || !this._paused)
1093
+ return;
1094
+ this._paused = false;
1095
+ for (const effect of this.effects) {
1096
+ effect.f &= ~INERT;
1097
+ if ((effect.f & DIRTY) !== 0) {
1098
+ scheduleEffect(effect);
1099
+ }
1100
+ }
1101
+ if (this.scopes) {
1102
+ for (const scope of this.scopes) {
1103
+ scope.resume();
1104
+ }
1105
+ }
1106
+ }
1107
+ }
1108
+ function effectScope(detached) {
1109
+ return new EffectScopeImpl(detached);
1110
+ }
1111
+ function onScopeDispose(fn) {
1112
+ if (activeScope) {
1113
+ activeScope.cleanups.push(fn);
1114
+ } else {
1115
+ console.warn("onScopeDispose() called outside of scope context");
1116
+ }
1117
+ }
1118
+ function registerEffectWithScope(effect) {
1119
+ if (activeScope) {
1120
+ activeScope.effects.push(effect);
1121
+ }
1122
+ }
1123
+
842
1124
  // src/primitives/effect.ts
843
1125
  function createEffect(type, fn, sync, push = true) {
844
1126
  const parent = activeEffect;
@@ -854,6 +1136,7 @@ function createEffect(type, fn, sync, push = true) {
854
1136
  next: null,
855
1137
  wv: 0
856
1138
  };
1139
+ registerEffectWithScope(effect);
857
1140
  if (sync) {
858
1141
  updateEffect(effect);
859
1142
  effect.f |= EFFECT_RAN;
@@ -963,7 +1246,9 @@ effect.tracking = function effectTracking() {
963
1246
  return activeEffect !== null;
964
1247
  };
965
1248
  // src/primitives/bind.ts
966
- var BINDING_SYMBOL = Symbol("binding");
1249
+ var bindingCleanup = new FinalizationRegistry((internalSource) => {
1250
+ internalSource.reactions = null;
1251
+ });
967
1252
  function isBinding(value) {
968
1253
  return value !== null && typeof value === "object" && BINDING_SYMBOL in value;
969
1254
  }
@@ -983,10 +1268,13 @@ function bind(source2) {
983
1268
  };
984
1269
  }
985
1270
  let actualSource;
1271
+ let internalSource = null;
986
1272
  if (isBinding(source2) || isSignal(source2)) {
987
1273
  actualSource = source2;
988
1274
  } else {
989
- actualSource = signal(source2);
1275
+ const sig = signal(source2);
1276
+ actualSource = sig;
1277
+ internalSource = getSource(sig) ?? null;
990
1278
  }
991
1279
  const binding = {
992
1280
  [BINDING_SYMBOL]: true,
@@ -997,6 +1285,9 @@ function bind(source2) {
997
1285
  actualSource.value = v;
998
1286
  }
999
1287
  };
1288
+ if (internalSource !== null) {
1289
+ bindingCleanup.register(binding, internalSource);
1290
+ }
1000
1291
  return binding;
1001
1292
  }
1002
1293
  function bindReadonly(source2) {
@@ -1043,7 +1334,140 @@ function untrack(fn) {
1043
1334
  }
1044
1335
  }
1045
1336
  var peek = untrack;
1337
+
1338
+ // src/primitives/linked.ts
1339
+ function linkedSignal(config) {
1340
+ let sourceFn;
1341
+ let computation;
1342
+ let equalsFn = Object.is;
1343
+ if (typeof config === "function") {
1344
+ const fn = config;
1345
+ sourceFn = fn;
1346
+ computation = (s) => s;
1347
+ } else {
1348
+ sourceFn = config.source;
1349
+ computation = config.computation;
1350
+ if (config.equal) {
1351
+ equalsFn = config.equal;
1352
+ }
1353
+ }
1354
+ let prevSource = undefined;
1355
+ let prevValue = undefined;
1356
+ let initialized = false;
1357
+ const valueSignal = signal(undefined, { equals: equalsFn });
1358
+ const sourceTracker = derived(() => {
1359
+ const newSource = sourceFn();
1360
+ return newSource;
1361
+ });
1362
+ let manualOverride = false;
1363
+ let lastKnownSource = undefined;
1364
+ const dispose = effect.pre(() => {
1365
+ const currentSource = sourceTracker.value;
1366
+ const sourceChanged = initialized && !Object.is(lastKnownSource, currentSource);
1367
+ if (!initialized || sourceChanged) {
1368
+ const previous = initialized ? { source: prevSource, value: prevValue } : undefined;
1369
+ const newValue = computation(currentSource, previous);
1370
+ prevSource = currentSource;
1371
+ prevValue = newValue;
1372
+ lastKnownSource = currentSource;
1373
+ initialized = true;
1374
+ manualOverride = false;
1375
+ untrack(() => {
1376
+ valueSignal.value = newValue;
1377
+ });
1378
+ }
1379
+ });
1380
+ const accessor = {
1381
+ [LINKED_SYMBOL]: true,
1382
+ get value() {
1383
+ sourceTracker.value;
1384
+ const currentSource = untrack(() => sourceTracker.value);
1385
+ if (initialized && !Object.is(lastKnownSource, currentSource)) {
1386
+ flushSync();
1387
+ }
1388
+ return valueSignal.value;
1389
+ },
1390
+ set value(newValue) {
1391
+ manualOverride = true;
1392
+ prevValue = newValue;
1393
+ valueSignal.value = newValue;
1394
+ },
1395
+ get peek() {
1396
+ return untrack(() => valueSignal.value);
1397
+ }
1398
+ };
1399
+ return accessor;
1400
+ }
1401
+ function isLinkedSignal(value) {
1402
+ return value !== null && typeof value === "object" && LINKED_SYMBOL in value;
1403
+ }
1404
+ // src/primitives/selector.ts
1405
+ function createSelector(source2, fn = (k, v) => k === v) {
1406
+ const subscribers = new Map;
1407
+ const reactionKeys = new WeakMap;
1408
+ let prevValue;
1409
+ let initialized = false;
1410
+ effect.pre(() => {
1411
+ const value = source2();
1412
+ if (initialized && !Object.is(prevValue, value)) {
1413
+ for (const [key, reactions] of subscribers) {
1414
+ const wasSelected = fn(key, prevValue);
1415
+ const isSelected = fn(key, value);
1416
+ if (wasSelected !== isSelected) {
1417
+ const toRemove = [];
1418
+ for (const reaction of reactions) {
1419
+ if ((reaction.f & DESTROYED) !== 0) {
1420
+ toRemove.push(reaction);
1421
+ continue;
1422
+ }
1423
+ setSignalStatus(reaction, DIRTY);
1424
+ if ((reaction.f & EFFECT) !== 0 && (reaction.f & DERIVED) === 0) {
1425
+ scheduleEffect(reaction);
1426
+ }
1427
+ }
1428
+ for (const r of toRemove) {
1429
+ reactions.delete(r);
1430
+ }
1431
+ }
1432
+ }
1433
+ }
1434
+ prevValue = value;
1435
+ initialized = true;
1436
+ });
1437
+ return (key) => {
1438
+ const currentValue = prevValue;
1439
+ if (activeReaction !== null) {
1440
+ const reaction = activeReaction;
1441
+ if ((reaction.f & DESTROYED) === 0) {
1442
+ let keySubscribers = subscribers.get(key);
1443
+ if (!keySubscribers) {
1444
+ keySubscribers = new Set;
1445
+ subscribers.set(key, keySubscribers);
1446
+ }
1447
+ if (!keySubscribers.has(reaction)) {
1448
+ keySubscribers.add(reaction);
1449
+ let keys = reactionKeys.get(reaction);
1450
+ if (!keys) {
1451
+ keys = new Set;
1452
+ reactionKeys.set(reaction, keys);
1453
+ }
1454
+ keys.add(key);
1455
+ }
1456
+ }
1457
+ }
1458
+ return fn(key, currentValue);
1459
+ };
1460
+ }
1046
1461
  // src/collections/map.ts
1462
+ var mapCleanup = new FinalizationRegistry((data) => {
1463
+ data.version.reactions = null;
1464
+ data.size.reactions = null;
1465
+ for (const sig of data.keySignals.values()) {
1466
+ sig.reactions = null;
1467
+ }
1468
+ data.keySignals.clear();
1469
+ });
1470
+
1047
1471
  class ReactiveMap extends Map {
1048
1472
  #keySignals = new Map;
1049
1473
  #version = source(0);
@@ -1051,6 +1475,11 @@ class ReactiveMap extends Map {
1051
1475
  constructor(entries) {
1052
1476
  super(entries);
1053
1477
  this.#size = source(super.size);
1478
+ mapCleanup.register(this, {
1479
+ keySignals: this.#keySignals,
1480
+ version: this.#version,
1481
+ size: this.#size
1482
+ });
1054
1483
  }
1055
1484
  #getKeySignal(key) {
1056
1485
  let sig = this.#keySignals.get(key);
@@ -1161,6 +1590,15 @@ class ReactiveMap extends Map {
1161
1590
  }
1162
1591
  }
1163
1592
  // src/collections/set.ts
1593
+ var setCleanup = new FinalizationRegistry((data) => {
1594
+ data.version.reactions = null;
1595
+ data.size.reactions = null;
1596
+ for (const sig of data.itemSignals.values()) {
1597
+ sig.reactions = null;
1598
+ }
1599
+ data.itemSignals.clear();
1600
+ });
1601
+
1164
1602
  class ReactiveSet extends Set {
1165
1603
  #itemSignals = new Map;
1166
1604
  #version = source(0);
@@ -1168,6 +1606,11 @@ class ReactiveSet extends Set {
1168
1606
  constructor(values) {
1169
1607
  super(values);
1170
1608
  this.#size = source(super.size);
1609
+ setCleanup.register(this, {
1610
+ itemSignals: this.#itemSignals,
1611
+ version: this.#version,
1612
+ size: this.#size
1613
+ });
1171
1614
  }
1172
1615
  #getItemSignal(item) {
1173
1616
  let sig = this.#itemSignals.get(item);