@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.mjs CHANGED
@@ -63,6 +63,9 @@ var UNINITIALIZED = Symbol.for("rlabs.signals.uninitialized");
63
63
  var STALE_REACTION = Symbol.for("rlabs.signals.stale_reaction");
64
64
  var STATE_SYMBOL = Symbol.for("rlabs.signals.state");
65
65
  var REACTIVE_MARKER = Symbol.for("rlabs.signals.reactive");
66
+ var BINDING_SYMBOL = Symbol.for("rlabs.signals.binding");
67
+ var LINKED_SYMBOL = Symbol.for("rlabs.signals.linked");
68
+ var SOURCE = 1 << 0;
66
69
  var STATUS_MASK = ~(DIRTY | MAYBE_DIRTY | CLEAN);
67
70
 
68
71
  // src/core/globals.ts
@@ -187,6 +190,9 @@ function setUpdateEffectImpl(impl) {
187
190
  function flushEffects() {
188
191
  const roots = clearQueuedRootEffects();
189
192
  for (const root of roots) {
193
+ if ((root.f & INERT) !== 0) {
194
+ continue;
195
+ }
190
196
  if (isDirty(root)) {
191
197
  updateEffectImpl(root);
192
198
  }
@@ -197,7 +203,7 @@ function processEffectTree(effect) {
197
203
  let child = effect.first;
198
204
  while (child !== null) {
199
205
  const next = child.next;
200
- if (isDirty(child)) {
206
+ if ((child.f & INERT) === 0 && isDirty(child)) {
201
207
  updateEffectImpl(child);
202
208
  }
203
209
  if (child.first !== null) {
@@ -210,6 +216,9 @@ function flushPendingReactions() {
210
216
  const reactions = [...pendingReactions];
211
217
  clearPendingReactions();
212
218
  for (const reaction of reactions) {
219
+ if ((reaction.f & INERT) !== 0) {
220
+ continue;
221
+ }
213
222
  if (isDirty(reaction)) {
214
223
  if ("parent" in reaction) {
215
224
  updateEffectImpl(reaction);
@@ -241,6 +250,9 @@ function flushSync(fn) {
241
250
  continue;
242
251
  }
243
252
  for (const root of roots) {
253
+ if ((root.f & INERT) !== 0) {
254
+ continue;
255
+ }
244
256
  if (isDirty(root)) {
245
257
  updateEffectImpl(root);
246
258
  }
@@ -287,13 +299,63 @@ function get(signal) {
287
299
  }
288
300
  }
289
301
  if ((signal.f & DERIVED) !== 0) {
290
- const derived = signal;
291
- if (isDirty(derived)) {
292
- updateDerived(derived);
293
- }
302
+ updateDerivedChain(signal);
294
303
  }
295
304
  return signal.v;
296
305
  }
306
+ var updateCycleId = 0;
307
+ var processedInCycle = new WeakMap;
308
+ function updateDerivedChain(target) {
309
+ if ((target.f & (DIRTY | MAYBE_DIRTY)) === 0) {
310
+ return;
311
+ }
312
+ const cycleId = ++updateCycleId;
313
+ const chain = [target];
314
+ processedInCycle.set(target, cycleId);
315
+ let idx = 0;
316
+ while (idx < chain.length) {
317
+ const current = chain[idx];
318
+ idx++;
319
+ if ((current.f & (DIRTY | MAYBE_DIRTY)) === 0) {
320
+ continue;
321
+ }
322
+ const deps = current.deps;
323
+ if (deps !== null) {
324
+ for (let i = 0;i < deps.length; i++) {
325
+ const dep = deps[i];
326
+ if ((dep.f & DERIVED) !== 0 && (dep.f & (DIRTY | MAYBE_DIRTY)) !== 0 && processedInCycle.get(dep) !== cycleId) {
327
+ chain.push(dep);
328
+ processedInCycle.set(dep, cycleId);
329
+ }
330
+ }
331
+ }
332
+ }
333
+ for (let i = chain.length - 1;i >= 0; i--) {
334
+ const current = chain[i];
335
+ if ((current.f & (DIRTY | MAYBE_DIRTY)) === 0) {
336
+ continue;
337
+ }
338
+ if ((current.f & DIRTY) !== 0) {
339
+ updateDerived(current);
340
+ } else {
341
+ const deps = current.deps;
342
+ let needsUpdate = false;
343
+ if (deps !== null) {
344
+ for (let j = 0;j < deps.length; j++) {
345
+ if (deps[j].wv > current.wv) {
346
+ needsUpdate = true;
347
+ break;
348
+ }
349
+ }
350
+ }
351
+ if (needsUpdate) {
352
+ updateDerived(current);
353
+ } else {
354
+ setSignalStatus(current, CLEAN);
355
+ }
356
+ }
357
+ }
358
+ }
297
359
  function set(signal, value) {
298
360
  if (activeReaction !== null && (activeReaction.f & DERIVED) !== 0) {
299
361
  throw new Error("Cannot write to signals inside a derived. " + "Deriveds should be pure computations with no side effects.");
@@ -309,20 +371,24 @@ function set(signal, value) {
309
371
  return value;
310
372
  }
311
373
  function markReactions(signal, status) {
312
- const reactions = signal.reactions;
313
- if (reactions === null)
314
- return;
315
- for (let i = 0;i < reactions.length; i++) {
316
- const reaction = reactions[i];
317
- const flags = reaction.f;
318
- const notDirty = (flags & DIRTY) === 0;
319
- if (notDirty) {
320
- setSignalStatus(reaction, status);
321
- }
322
- if ((flags & DERIVED) !== 0) {
323
- markReactions(reaction, MAYBE_DIRTY);
324
- } else if (notDirty) {
325
- scheduleEffect(reaction);
374
+ const stack = [{ signal, status }];
375
+ while (stack.length > 0) {
376
+ const { signal: currentSignal, status: currentStatus } = stack.pop();
377
+ const reactions = currentSignal.reactions;
378
+ if (reactions === null)
379
+ continue;
380
+ for (let i = 0;i < reactions.length; i++) {
381
+ const reaction = reactions[i];
382
+ const flags = reaction.f;
383
+ const notDirty = (flags & DIRTY) === 0;
384
+ if (notDirty) {
385
+ setSignalStatus(reaction, currentStatus);
386
+ }
387
+ if ((flags & DERIVED) !== 0) {
388
+ stack.push({ signal: reaction, status: MAYBE_DIRTY });
389
+ } else if (notDirty) {
390
+ scheduleEffect(reaction);
391
+ }
326
392
  }
327
393
  }
328
394
  }
@@ -333,22 +399,66 @@ function isDirty(reaction) {
333
399
  if ((reaction.f & DIRTY) !== 0) {
334
400
  return true;
335
401
  }
336
- if ((reaction.f & MAYBE_DIRTY) !== 0) {
337
- const deps = reaction.deps;
338
- if (deps !== null) {
339
- for (let i = 0;i < deps.length; i++) {
340
- const dep = deps[i];
341
- if ((dep.f & DERIVED) !== 0) {
342
- if (isDirty(dep)) {
343
- updateDerived(dep);
344
- }
402
+ if ((reaction.f & MAYBE_DIRTY) === 0) {
403
+ return false;
404
+ }
405
+ const toCheck = [reaction];
406
+ const toUpdate = [];
407
+ let idx = 0;
408
+ while (idx < toCheck.length) {
409
+ const current = toCheck[idx];
410
+ idx++;
411
+ if ((current.f & DIRTY) !== 0) {
412
+ continue;
413
+ }
414
+ if ((current.f & MAYBE_DIRTY) === 0) {
415
+ continue;
416
+ }
417
+ const deps2 = current.deps;
418
+ if (deps2 !== null) {
419
+ for (let i = 0;i < deps2.length; i++) {
420
+ const dep = deps2[i];
421
+ if ((dep.f & DERIVED) !== 0 && (dep.f & (DIRTY | MAYBE_DIRTY)) !== 0) {
422
+ toCheck.push(dep);
345
423
  }
346
- if (dep.wv > reaction.wv) {
347
- return true;
424
+ }
425
+ }
426
+ }
427
+ for (let i = toCheck.length - 1;i >= 0; i--) {
428
+ const current = toCheck[i];
429
+ if ((current.f & DERIVED) === 0) {
430
+ continue;
431
+ }
432
+ if ((current.f & DIRTY) !== 0) {
433
+ updateDerived(current);
434
+ } else if ((current.f & MAYBE_DIRTY) !== 0) {
435
+ const deps2 = current.deps;
436
+ let needsUpdate = false;
437
+ if (deps2 !== null) {
438
+ for (let j = 0;j < deps2.length; j++) {
439
+ if (deps2[j].wv > current.wv) {
440
+ needsUpdate = true;
441
+ break;
442
+ }
348
443
  }
349
444
  }
445
+ if (needsUpdate) {
446
+ updateDerived(current);
447
+ } else {
448
+ setSignalStatus(current, CLEAN);
449
+ }
450
+ }
451
+ }
452
+ if ((reaction.f & DIRTY) !== 0) {
453
+ return true;
454
+ }
455
+ const deps = reaction.deps;
456
+ if (deps !== null) {
457
+ for (let i = 0;i < deps.length; i++) {
458
+ if (deps[i].wv > reaction.wv) {
459
+ return true;
460
+ }
350
461
  }
351
- setSignalStatus(reaction, CLEAN);
352
462
  }
353
463
  return false;
354
464
  }
@@ -452,6 +562,7 @@ function updateReaction(reaction) {
452
562
  }
453
563
 
454
564
  // src/primitives/signal.ts
565
+ var SOURCE_SYMBOL = Symbol("signal.source");
455
566
  function source(initialValue, options) {
456
567
  return {
457
568
  f: 0,
@@ -465,9 +576,13 @@ function source(initialValue, options) {
465
576
  function mutableSource(initialValue) {
466
577
  return source(initialValue, { equals: safeEquals });
467
578
  }
579
+ var signalCleanup = new FinalizationRegistry((src) => {
580
+ src.reactions = null;
581
+ });
468
582
  function signal(initialValue, options) {
469
583
  const src = source(initialValue, options);
470
- return {
584
+ const sig = {
585
+ [SOURCE_SYMBOL]: src,
471
586
  get value() {
472
587
  return get(src);
473
588
  },
@@ -475,6 +590,11 @@ function signal(initialValue, options) {
475
590
  set(src, newValue);
476
591
  }
477
592
  };
593
+ signalCleanup.register(sig, src);
594
+ return sig;
595
+ }
596
+ function getSource(sig) {
597
+ return sig[SOURCE_SYMBOL];
478
598
  }
479
599
  var proxyFn = null;
480
600
  function setProxyFn(fn) {
@@ -491,10 +611,21 @@ function stateRaw(initialValue) {
491
611
  }
492
612
 
493
613
  // src/deep/proxy.ts
614
+ var proxyCache = new WeakMap;
615
+ var proxyCleanup = new FinalizationRegistry((data) => {
616
+ data.version.reactions = null;
617
+ for (const src of data.sources.values()) {
618
+ src.reactions = null;
619
+ }
620
+ data.sources.clear();
621
+ });
494
622
  function shouldProxy(value) {
495
623
  if (value === null || typeof value !== "object") {
496
624
  return false;
497
625
  }
626
+ if (BINDING_SYMBOL in value) {
627
+ return false;
628
+ }
498
629
  const proto = Object.getPrototypeOf(value);
499
630
  return proto === Object.prototype || proto === Array.prototype || proto === null;
500
631
  }
@@ -509,6 +640,10 @@ function proxy(value) {
509
640
  if (!shouldProxy(value) || isProxy(value)) {
510
641
  return value;
511
642
  }
643
+ const cached = proxyCache.get(value);
644
+ if (cached) {
645
+ return cached;
646
+ }
512
647
  const sources = new Map;
513
648
  const version = source(0);
514
649
  const isArray = Array.isArray(value);
@@ -528,7 +663,7 @@ function proxy(value) {
528
663
  setActiveReaction(prevReaction);
529
664
  }
530
665
  };
531
- const getSource = (prop, initialValue) => {
666
+ const getSource2 = (prop, initialValue) => {
532
667
  let s = sources.get(prop);
533
668
  if (s === undefined) {
534
669
  s = withParent(() => {
@@ -551,7 +686,7 @@ function proxy(value) {
551
686
  return currentValue.bind(proxyObj);
552
687
  }
553
688
  if (exists || isWritable(target, prop)) {
554
- const s = getSource(prop, currentValue);
689
+ const s = getSource2(prop, currentValue);
555
690
  const val = get(s);
556
691
  if (val === UNINITIALIZED) {
557
692
  return;
@@ -560,7 +695,7 @@ function proxy(value) {
560
695
  }
561
696
  return currentValue;
562
697
  },
563
- set(target, prop, newValue, receiver) {
698
+ set(target, prop, newValue) {
564
699
  const exists = prop in target;
565
700
  let s = sources.get(prop);
566
701
  if (s === undefined) {
@@ -570,29 +705,35 @@ function proxy(value) {
570
705
  s = withParent(() => source(undefined));
571
706
  sources.set(prop, s);
572
707
  }
573
- const proxied = withParent(() => shouldProxy(newValue) ? proxy(newValue) : newValue);
708
+ let proxied;
709
+ if (shouldProxy(newValue)) {
710
+ proxied = withParent(() => proxy(newValue));
711
+ } else {
712
+ proxied = newValue;
713
+ }
574
714
  set(s, proxied);
575
- Reflect.set(target, prop, newValue, receiver);
576
- if (isArray && prop === "length") {
577
- const oldLength = s.v;
578
- const newLength = newValue;
579
- for (let i = newLength;i < oldLength; i++) {
580
- const indexKey = String(i);
581
- const indexSource = sources.get(indexKey);
582
- if (indexSource !== undefined) {
583
- set(indexSource, UNINITIALIZED);
584
- } else if (i in target) {
585
- const deletedSource = withParent(() => source(UNINITIALIZED));
586
- sources.set(indexKey, deletedSource);
715
+ target[prop] = newValue;
716
+ if (isArray) {
717
+ if (prop === "length") {
718
+ const oldLength = s.v;
719
+ const newLength = newValue;
720
+ for (let i = newLength;i < oldLength; i++) {
721
+ const indexKey = String(i);
722
+ const indexSource = sources.get(indexKey);
723
+ if (indexSource !== undefined) {
724
+ set(indexSource, UNINITIALIZED);
725
+ } else if (i in target) {
726
+ const deletedSource = withParent(() => source(UNINITIALIZED));
727
+ sources.set(indexKey, deletedSource);
728
+ }
587
729
  }
588
- }
589
- }
590
- if (isArray && typeof prop === "string") {
591
- const index = Number(prop);
592
- if (Number.isInteger(index) && index >= 0) {
593
- const lengthSource = sources.get("length");
594
- if (lengthSource !== undefined && index >= lengthSource.v) {
595
- set(lengthSource, index + 1);
730
+ } else if (typeof prop === "string") {
731
+ const index = Number(prop);
732
+ if (Number.isInteger(index) && index >= 0) {
733
+ const lengthSource = sources.get("length");
734
+ if (lengthSource !== undefined && index >= lengthSource.v) {
735
+ set(lengthSource, index + 1);
736
+ }
596
737
  }
597
738
  }
598
739
  }
@@ -613,7 +754,7 @@ function proxy(value) {
613
754
  }
614
755
  set(version, get(version) + 1);
615
756
  }
616
- return Reflect.deleteProperty(target, prop);
757
+ return delete target[prop];
617
758
  },
618
759
  has(target, prop) {
619
760
  if (prop === STATE_SYMBOL) {
@@ -651,6 +792,8 @@ function proxy(value) {
651
792
  return Reflect.getOwnPropertyDescriptor(target, prop);
652
793
  }
653
794
  });
795
+ proxyCleanup.register(proxyObj, { sources, version });
796
+ proxyCache.set(value, proxyObj);
654
797
  return proxyObj;
655
798
  }
656
799
  function toRaw(value) {
@@ -663,6 +806,18 @@ function isReactive(value) {
663
806
  return isProxy(value);
664
807
  }
665
808
  // src/primitives/derived.ts
809
+ var derivedCleanup = new FinalizationRegistry((d) => {
810
+ removeReactions(d, 0);
811
+ d.deps = null;
812
+ d.reactions = null;
813
+ const effects = d.effects;
814
+ if (effects !== null) {
815
+ d.effects = null;
816
+ for (let i = 0;i < effects.length; i++) {
817
+ destroyEffectImpl(effects[i]);
818
+ }
819
+ }
820
+ });
666
821
  function createDerived(fn, options) {
667
822
  let flags = DERIVED | DIRTY;
668
823
  const parentDerived = activeReaction !== null && (activeReaction.f & DERIVED) !== 0 ? activeReaction : null;
@@ -713,11 +868,13 @@ function destroyDerivedEffects(derived) {
713
868
  }
714
869
  function derived(fn, options) {
715
870
  const d = createDerived(fn, options);
716
- return {
871
+ const wrapper = {
717
872
  get value() {
718
873
  return get(d);
719
874
  }
720
875
  };
876
+ derivedCleanup.register(wrapper, d);
877
+ return wrapper;
721
878
  }
722
879
  derived.by = derived;
723
880
  function disconnectDerived(derived2) {
@@ -726,6 +883,123 @@ function disconnectDerived(derived2) {
726
883
  derived2.reactions = null;
727
884
  destroyDerivedEffects(derived2);
728
885
  }
886
+ // src/primitives/scope.ts
887
+ var activeScope = null;
888
+ function getCurrentScope() {
889
+ return activeScope;
890
+ }
891
+ function setActiveScope(scope) {
892
+ const prev = activeScope;
893
+ activeScope = scope;
894
+ return prev;
895
+ }
896
+
897
+ class EffectScopeImpl {
898
+ _active = true;
899
+ _paused = false;
900
+ effects = [];
901
+ cleanups = [];
902
+ parent = null;
903
+ scopes = null;
904
+ constructor(detached = false) {
905
+ this.parent = activeScope;
906
+ if (!detached && this.parent) {
907
+ if (this.parent.scopes === null) {
908
+ this.parent.scopes = [];
909
+ }
910
+ this.parent.scopes.push(this);
911
+ }
912
+ }
913
+ get active() {
914
+ return this._active;
915
+ }
916
+ get paused() {
917
+ return this._paused;
918
+ }
919
+ run(fn) {
920
+ if (!this._active) {
921
+ return;
922
+ }
923
+ const prevScope = setActiveScope(this);
924
+ try {
925
+ return fn();
926
+ } finally {
927
+ setActiveScope(prevScope);
928
+ }
929
+ }
930
+ stop() {
931
+ if (!this._active)
932
+ return;
933
+ for (const effect of [...this.effects]) {
934
+ destroyEffect(effect);
935
+ }
936
+ this.effects.length = 0;
937
+ for (const cleanup of this.cleanups) {
938
+ try {
939
+ cleanup();
940
+ } catch {}
941
+ }
942
+ this.cleanups.length = 0;
943
+ if (this.scopes) {
944
+ for (const scope of this.scopes) {
945
+ scope.stop();
946
+ }
947
+ this.scopes.length = 0;
948
+ }
949
+ if (this.parent?.scopes) {
950
+ const idx = this.parent.scopes.indexOf(this);
951
+ if (idx !== -1) {
952
+ this.parent.scopes.splice(idx, 1);
953
+ }
954
+ }
955
+ this._active = false;
956
+ }
957
+ pause() {
958
+ if (!this._active || this._paused)
959
+ return;
960
+ this._paused = true;
961
+ for (const effect of this.effects) {
962
+ effect.f |= INERT;
963
+ }
964
+ if (this.scopes) {
965
+ for (const scope of this.scopes) {
966
+ scope.pause();
967
+ }
968
+ }
969
+ }
970
+ resume() {
971
+ if (!this._active || !this._paused)
972
+ return;
973
+ this._paused = false;
974
+ for (const effect of this.effects) {
975
+ effect.f &= ~INERT;
976
+ if ((effect.f & DIRTY) !== 0) {
977
+ scheduleEffect(effect);
978
+ }
979
+ }
980
+ if (this.scopes) {
981
+ for (const scope of this.scopes) {
982
+ scope.resume();
983
+ }
984
+ }
985
+ }
986
+ }
987
+ function effectScope(detached) {
988
+ return new EffectScopeImpl(detached);
989
+ }
990
+ function onScopeDispose(fn) {
991
+ if (activeScope) {
992
+ activeScope.cleanups.push(fn);
993
+ } else {
994
+ console.warn("onScopeDispose() called outside of scope context");
995
+ }
996
+ }
997
+ function registerEffectWithScope(effect) {
998
+ if (activeScope) {
999
+ activeScope.effects.push(effect);
1000
+ }
1001
+ }
1002
+
729
1003
  // src/primitives/effect.ts
730
1004
  function createEffect(type, fn, sync, push = true) {
731
1005
  const parent = activeEffect;
@@ -741,6 +1015,7 @@ function createEffect(type, fn, sync, push = true) {
741
1015
  next: null,
742
1016
  wv: 0
743
1017
  };
1018
+ registerEffectWithScope(effect);
744
1019
  if (sync) {
745
1020
  updateEffect(effect);
746
1021
  effect.f |= EFFECT_RAN;
@@ -850,7 +1125,9 @@ effect.tracking = function effectTracking() {
850
1125
  return activeEffect !== null;
851
1126
  };
852
1127
  // src/primitives/bind.ts
853
- var BINDING_SYMBOL = Symbol("binding");
1128
+ var bindingCleanup = new FinalizationRegistry((internalSource) => {
1129
+ internalSource.reactions = null;
1130
+ });
854
1131
  function isBinding(value) {
855
1132
  return value !== null && typeof value === "object" && BINDING_SYMBOL in value;
856
1133
  }
@@ -870,10 +1147,13 @@ function bind(source2) {
870
1147
  };
871
1148
  }
872
1149
  let actualSource;
1150
+ let internalSource = null;
873
1151
  if (isBinding(source2) || isSignal(source2)) {
874
1152
  actualSource = source2;
875
1153
  } else {
876
- actualSource = signal(source2);
1154
+ const sig = signal(source2);
1155
+ actualSource = sig;
1156
+ internalSource = getSource(sig) ?? null;
877
1157
  }
878
1158
  const binding = {
879
1159
  [BINDING_SYMBOL]: true,
@@ -884,6 +1164,9 @@ function bind(source2) {
884
1164
  actualSource.value = v;
885
1165
  }
886
1166
  };
1167
+ if (internalSource !== null) {
1168
+ bindingCleanup.register(binding, internalSource);
1169
+ }
887
1170
  return binding;
888
1171
  }
889
1172
  function bindReadonly(source2) {
@@ -930,7 +1213,140 @@ function untrack(fn) {
930
1213
  }
931
1214
  }
932
1215
  var peek = untrack;
1216
+
1217
+ // src/primitives/linked.ts
1218
+ function linkedSignal(config) {
1219
+ let sourceFn;
1220
+ let computation;
1221
+ let equalsFn = Object.is;
1222
+ if (typeof config === "function") {
1223
+ const fn = config;
1224
+ sourceFn = fn;
1225
+ computation = (s) => s;
1226
+ } else {
1227
+ sourceFn = config.source;
1228
+ computation = config.computation;
1229
+ if (config.equal) {
1230
+ equalsFn = config.equal;
1231
+ }
1232
+ }
1233
+ let prevSource = undefined;
1234
+ let prevValue = undefined;
1235
+ let initialized = false;
1236
+ const valueSignal = signal(undefined, { equals: equalsFn });
1237
+ const sourceTracker = derived(() => {
1238
+ const newSource = sourceFn();
1239
+ return newSource;
1240
+ });
1241
+ let manualOverride = false;
1242
+ let lastKnownSource = undefined;
1243
+ const dispose = effect.pre(() => {
1244
+ const currentSource = sourceTracker.value;
1245
+ const sourceChanged = initialized && !Object.is(lastKnownSource, currentSource);
1246
+ if (!initialized || sourceChanged) {
1247
+ const previous = initialized ? { source: prevSource, value: prevValue } : undefined;
1248
+ const newValue = computation(currentSource, previous);
1249
+ prevSource = currentSource;
1250
+ prevValue = newValue;
1251
+ lastKnownSource = currentSource;
1252
+ initialized = true;
1253
+ manualOverride = false;
1254
+ untrack(() => {
1255
+ valueSignal.value = newValue;
1256
+ });
1257
+ }
1258
+ });
1259
+ const accessor = {
1260
+ [LINKED_SYMBOL]: true,
1261
+ get value() {
1262
+ sourceTracker.value;
1263
+ const currentSource = untrack(() => sourceTracker.value);
1264
+ if (initialized && !Object.is(lastKnownSource, currentSource)) {
1265
+ flushSync();
1266
+ }
1267
+ return valueSignal.value;
1268
+ },
1269
+ set value(newValue) {
1270
+ manualOverride = true;
1271
+ prevValue = newValue;
1272
+ valueSignal.value = newValue;
1273
+ },
1274
+ get peek() {
1275
+ return untrack(() => valueSignal.value);
1276
+ }
1277
+ };
1278
+ return accessor;
1279
+ }
1280
+ function isLinkedSignal(value) {
1281
+ return value !== null && typeof value === "object" && LINKED_SYMBOL in value;
1282
+ }
1283
+ // src/primitives/selector.ts
1284
+ function createSelector(source2, fn = (k, v) => k === v) {
1285
+ const subscribers = new Map;
1286
+ const reactionKeys = new WeakMap;
1287
+ let prevValue;
1288
+ let initialized = false;
1289
+ effect.pre(() => {
1290
+ const value = source2();
1291
+ if (initialized && !Object.is(prevValue, value)) {
1292
+ for (const [key, reactions] of subscribers) {
1293
+ const wasSelected = fn(key, prevValue);
1294
+ const isSelected = fn(key, value);
1295
+ if (wasSelected !== isSelected) {
1296
+ const toRemove = [];
1297
+ for (const reaction of reactions) {
1298
+ if ((reaction.f & DESTROYED) !== 0) {
1299
+ toRemove.push(reaction);
1300
+ continue;
1301
+ }
1302
+ setSignalStatus(reaction, DIRTY);
1303
+ if ((reaction.f & EFFECT) !== 0 && (reaction.f & DERIVED) === 0) {
1304
+ scheduleEffect(reaction);
1305
+ }
1306
+ }
1307
+ for (const r of toRemove) {
1308
+ reactions.delete(r);
1309
+ }
1310
+ }
1311
+ }
1312
+ }
1313
+ prevValue = value;
1314
+ initialized = true;
1315
+ });
1316
+ return (key) => {
1317
+ const currentValue = prevValue;
1318
+ if (activeReaction !== null) {
1319
+ const reaction = activeReaction;
1320
+ if ((reaction.f & DESTROYED) === 0) {
1321
+ let keySubscribers = subscribers.get(key);
1322
+ if (!keySubscribers) {
1323
+ keySubscribers = new Set;
1324
+ subscribers.set(key, keySubscribers);
1325
+ }
1326
+ if (!keySubscribers.has(reaction)) {
1327
+ keySubscribers.add(reaction);
1328
+ let keys = reactionKeys.get(reaction);
1329
+ if (!keys) {
1330
+ keys = new Set;
1331
+ reactionKeys.set(reaction, keys);
1332
+ }
1333
+ keys.add(key);
1334
+ }
1335
+ }
1336
+ }
1337
+ return fn(key, currentValue);
1338
+ };
1339
+ }
933
1340
  // src/collections/map.ts
1341
+ var mapCleanup = new FinalizationRegistry((data) => {
1342
+ data.version.reactions = null;
1343
+ data.size.reactions = null;
1344
+ for (const sig of data.keySignals.values()) {
1345
+ sig.reactions = null;
1346
+ }
1347
+ data.keySignals.clear();
1348
+ });
1349
+
934
1350
  class ReactiveMap extends Map {
935
1351
  #keySignals = new Map;
936
1352
  #version = source(0);
@@ -938,6 +1354,11 @@ class ReactiveMap extends Map {
938
1354
  constructor(entries) {
939
1355
  super(entries);
940
1356
  this.#size = source(super.size);
1357
+ mapCleanup.register(this, {
1358
+ keySignals: this.#keySignals,
1359
+ version: this.#version,
1360
+ size: this.#size
1361
+ });
941
1362
  }
942
1363
  #getKeySignal(key) {
943
1364
  let sig = this.#keySignals.get(key);
@@ -1048,6 +1469,15 @@ class ReactiveMap extends Map {
1048
1469
  }
1049
1470
  }
1050
1471
  // src/collections/set.ts
1472
+ var setCleanup = new FinalizationRegistry((data) => {
1473
+ data.version.reactions = null;
1474
+ data.size.reactions = null;
1475
+ for (const sig of data.itemSignals.values()) {
1476
+ sig.reactions = null;
1477
+ }
1478
+ data.itemSignals.clear();
1479
+ });
1480
+
1051
1481
  class ReactiveSet extends Set {
1052
1482
  #itemSignals = new Map;
1053
1483
  #version = source(0);
@@ -1055,6 +1485,11 @@ class ReactiveSet extends Set {
1055
1485
  constructor(values) {
1056
1486
  super(values);
1057
1487
  this.#size = source(super.size);
1488
+ setCleanup.register(this, {
1489
+ itemSignals: this.#itemSignals,
1490
+ version: this.#version,
1491
+ size: this.#size
1492
+ });
1058
1493
  }
1059
1494
  #getItemSignal(item) {
1060
1495
  let sig = this.#itemSignals.get(item);
@@ -1412,10 +1847,13 @@ export {
1412
1847
  readVersion,
1413
1848
  proxy,
1414
1849
  peek,
1850
+ onScopeDispose,
1415
1851
  neverEquals,
1416
1852
  mutableSource,
1417
1853
  markReactions,
1854
+ linkedSignal,
1418
1855
  isReactive,
1856
+ isLinkedSignal,
1419
1857
  isDirty,
1420
1858
  isBinding,
1421
1859
  incrementWriteVersion,
@@ -1423,15 +1861,18 @@ export {
1423
1861
  incrementBatchDepth,
1424
1862
  getWriteVersion,
1425
1863
  getReadVersion,
1864
+ getCurrentScope,
1426
1865
  getBatchDepth,
1427
1866
  get,
1428
1867
  flushSync,
1429
1868
  equals,
1869
+ effectScope,
1430
1870
  effect,
1431
1871
  disconnectDerived,
1432
1872
  destroyEffect,
1433
1873
  derived,
1434
1874
  decrementBatchDepth,
1875
+ createSelector,
1435
1876
  createEquals,
1436
1877
  createEffect,
1437
1878
  createDerived,
@@ -1455,6 +1896,7 @@ export {
1455
1896
  REACTIVE_MARKER,
1456
1897
  REACTION_IS_UPDATING,
1457
1898
  MAYBE_DIRTY,
1899
+ LINKED_SYMBOL,
1458
1900
  INERT,
1459
1901
  EFFECT_RAN,
1460
1902
  EFFECT_PRESERVED,
@@ -1465,5 +1907,6 @@ export {
1465
1907
  DERIVED,
1466
1908
  CLEAN,
1467
1909
  BRANCH_EFFECT,
1468
- BLOCK_EFFECT
1910
+ BLOCK_EFFECT,
1911
+ BINDING_SYMBOL
1469
1912
  };