@ng-org/alien-deepsignals 0.1.2-alpha.5 → 0.1.2-alpha.7

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 (62) hide show
  1. package/README.md +55 -374
  2. package/dist/core.d.ts +66 -60
  3. package/dist/core.d.ts.map +1 -1
  4. package/dist/core.js +70 -82
  5. package/dist/deepSignal.d.ts +19 -5
  6. package/dist/deepSignal.d.ts.map +1 -1
  7. package/dist/deepSignal.js +214 -141
  8. package/dist/effect.d.ts.map +1 -1
  9. package/dist/effect.js +8 -1
  10. package/dist/hooks/react/useDeepSignal.d.ts +1 -1
  11. package/dist/hooks/react/useDeepSignal.d.ts.map +1 -1
  12. package/dist/hooks/react/useDeepSignal.js +5 -2
  13. package/dist/hooks/svelte/index.d.ts +0 -1
  14. package/dist/hooks/svelte/index.d.ts.map +1 -1
  15. package/dist/hooks/svelte/useDeepSignal.svelte.d.ts +3 -15
  16. package/dist/hooks/svelte/useDeepSignal.svelte.d.ts.map +1 -1
  17. package/dist/hooks/svelte/useDeepSignal.svelte.js +29 -63
  18. package/dist/hooks/svelte4/index.d.ts +4 -0
  19. package/dist/hooks/svelte4/index.d.ts.map +1 -0
  20. package/dist/hooks/svelte4/index.js +8 -0
  21. package/dist/hooks/svelte4/useDeepSignal.svelte.d.ts +27 -0
  22. package/dist/hooks/svelte4/useDeepSignal.svelte.d.ts.map +1 -0
  23. package/dist/hooks/svelte4/useDeepSignal.svelte.js +91 -0
  24. package/dist/hooks/vue/useDeepSignal.d.ts +2 -2
  25. package/dist/hooks/vue/useDeepSignal.d.ts.map +1 -1
  26. package/dist/hooks/vue/useDeepSignal.js +22 -74
  27. package/dist/index.d.ts +1 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +8 -2
  30. package/dist/test/frontend/astro-app/src/components/ReactPanel.d.ts.map +1 -1
  31. package/dist/test/frontend/astro-app/src/components/{ReactPanel.js → ReactPanel.jsx} +56 -45
  32. package/dist/test/frontend/playwright/crossFrameworkHooks.spec.js +13 -3
  33. package/dist/test/frontend/utils/mockData.d.ts.map +1 -1
  34. package/dist/test/frontend/utils/mockData.js +13 -4
  35. package/dist/test/lib/index.test.js +16 -3
  36. package/dist/test/lib/misc.test.js +104 -1
  37. package/dist/test/lib/watch.test.js +2 -11
  38. package/dist/types.d.ts +94 -19
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/types.js +9 -0
  41. package/dist/watch.d.ts +37 -0
  42. package/dist/watch.d.ts.map +1 -1
  43. package/dist/watch.js +35 -0
  44. package/package.json +8 -5
  45. package/dist/test/frontend/astro-app/src/components/PerfSuiteClient.d.ts +0 -4
  46. package/dist/test/frontend/astro-app/src/components/PerfSuiteClient.d.ts.map +0 -1
  47. package/dist/test/frontend/astro-app/src/components/PerfSuiteClient.js +0 -225
  48. package/dist/test/frontend/astro-app/src/components/perf/react/ReactPerfDeep.d.ts +0 -4
  49. package/dist/test/frontend/astro-app/src/components/perf/react/ReactPerfDeep.d.ts.map +0 -1
  50. package/dist/test/frontend/astro-app/src/components/perf/react/ReactPerfDeep.js +0 -150
  51. package/dist/test/frontend/astro-app/src/components/perf/react/ReactPerfNative.d.ts +0 -4
  52. package/dist/test/frontend/astro-app/src/components/perf/react/ReactPerfNative.d.ts.map +0 -1
  53. package/dist/test/frontend/astro-app/src/components/perf/react/ReactPerfNative.js +0 -184
  54. package/dist/test/frontend/playwright/perfSuite.spec.d.ts +0 -2
  55. package/dist/test/frontend/playwright/perfSuite.spec.d.ts.map +0 -1
  56. package/dist/test/frontend/playwright/perfSuite.spec.js +0 -128
  57. package/dist/test/frontend/utils/perfScenarios.d.ts +0 -15
  58. package/dist/test/frontend/utils/perfScenarios.d.ts.map +0 -1
  59. package/dist/test/frontend/utils/perfScenarios.js +0 -287
  60. package/dist/test/lib/core.test.d.ts +0 -2
  61. package/dist/test/lib/core.test.d.ts.map +0 -1
  62. package/dist/test/lib/core.test.js +0 -53
@@ -17,19 +17,23 @@ exports.getDeepSignalVersion = getDeepSignalVersion;
17
17
  exports.shallow = shallow;
18
18
  exports.setSetEntrySyntheticId = setSetEntrySyntheticId;
19
19
  exports.addWithId = addWithId;
20
+ exports.getRaw = getRaw;
20
21
  const core_1 = require("./core");
21
22
  const iteratorHelpers_1 = require("./iteratorHelpers");
22
23
  /** The current proxy object for the raw object (others might exist but are not the current / clean ones). */
23
24
  const rawToProxy = new WeakMap();
24
25
  const rawToMeta = new WeakMap();
25
- // TODO: We can move them to the meta objects.
26
+ /**
27
+ * Mapping of raw objects to child signals.
28
+ * We store them globally because the same object can be attached in different locations.
29
+ */
26
30
  const propertiesToSignals = new WeakMap();
27
31
  const iterableSignals = new WeakMap();
28
32
  const ignored = new WeakSet();
33
+ // TODO: We can just add this to the root directly
29
34
  const rootStates = new Map();
30
35
  const pendingRoots = new Set();
31
36
  const supported = new Set([Object, Array, Set]);
32
- const descriptor = Object.getOwnPropertyDescriptor;
33
37
  let blankNodeCounter = 0;
34
38
  let tmpIdCounter = 0;
35
39
  const wellKnownSymbols = new Set([
@@ -62,51 +66,89 @@ function shouldProxy(value) {
62
66
  !ignored.has(value));
63
67
  }
64
68
  /**
65
- * Get or create the map in `proxySignals` for key `proxy`.
69
+ * Get or create the map in `proxySignals` for raw object to `Map<property key, signal>`
66
70
  */
67
- function ensureSignalMap(rawObj) {
71
+ function getOrCreateSignalMap(rawObj) {
68
72
  if (!propertiesToSignals.has(rawObj))
69
73
  propertiesToSignals.set(rawObj, new Map());
70
74
  return propertiesToSignals.get(rawObj);
71
75
  }
72
76
  /**
73
77
  * Write a new value into a cached signal, creating it if needed.
74
- * Does nothing computed signals.
78
+ * Does nothing on computed signals.
75
79
  */
76
- function setSignalValue(signals, key, value) {
80
+ function setSignalValue(meta, key, value) {
81
+ const signals = propertiesToSignals.get(meta.raw);
77
82
  if (!signals.has(key)) {
78
- signals.set(key, (0, core_1.signal)(value));
79
- return;
83
+ // Add new signal.
84
+ signals.set(key, {
85
+ alienSignal: (0, core_1.alienSignal)(value),
86
+ // Do not add external subscribers yet, they will be created on first read.
87
+ externalSubscribers: new Map(),
88
+ });
80
89
  }
81
- const existing = signals.get(key);
82
- if (typeof existing.set === "function") {
83
- existing.set(value);
90
+ else {
91
+ // Update existing signal.
92
+ const existingSignal = signals.get(key);
93
+ existingSignal.alienSignal(value);
94
+ existingSignal.externalSubscribers
95
+ .values()
96
+ .forEach((subscriber) => subscriber.onSet(value));
84
97
  }
85
98
  }
86
- /** Track mutations that affect object iteration order/length. */
87
- function ensureIterableSignal(target) {
88
- if (!iterableSignals.has(target))
89
- iterableSignals.set(target, (0, core_1.signal)(0));
90
- return iterableSignals.get(target);
99
+ /** Gets the current value of a signal and notifies external subscribers about read. */
100
+ function getValFromSignalRecord(meta, record) {
101
+ const val = record.alienSignal();
102
+ // Notify external subscribers (usually frontend frameworks) about property access.
103
+ meta.options.subscriberFactories?.forEach((createSubscriber) => {
104
+ let subscriber = record.externalSubscribers.get(createSubscriber);
105
+ if (!subscriber) {
106
+ // Create new subscriber
107
+ subscriber = createSubscriber();
108
+ record.externalSubscribers.set(createSubscriber, subscriber);
109
+ }
110
+ subscriber.onGet();
111
+ });
112
+ return val;
113
+ }
114
+ /** Sets the current value of a signal and notifies external subscribers about update. */
115
+ function setValToSignalRecord(record, value) {
116
+ record.alienSignal(value);
117
+ // Notify external subscribers (usually frontend frameworks) about property update.
118
+ record.externalSubscribers.forEach((subscriber) => subscriber.onSet(value));
119
+ }
120
+ /** Track mutations that affect object iteration order/length with a counter signal. */
121
+ function ensureIterableSignal(meta, target) {
122
+ // Create new one if none exists.
123
+ if (!iterableSignals.has(target)) {
124
+ iterableSignals.set(target, {
125
+ alienSignal: (0, core_1.alienSignal)(0),
126
+ externalSubscribers: new Map(),
127
+ });
128
+ }
129
+ const signal = iterableSignals.get(target);
130
+ getValFromSignalRecord(meta, signal);
91
131
  }
92
- /** Notify all iteration-based subscribers that the container changed shape. */
93
- function touchIterable(target) {
132
+ /** Notify all iteration-based subscribers that the container changed shape, by increasing the counter signal. */
133
+ function touchIterable(meta, target) {
94
134
  if (!iterableSignals.has(target))
95
135
  return;
96
- const sig = iterableSignals.get(target);
97
- sig.set(sig() + 1);
98
- }
99
- /** True when the target has a getter defined for the provided key. */
100
- function hasGetter(target, key) {
101
- return typeof descriptor(target, key)?.get === "function";
136
+ const signalRecord = iterableSignals.get(target);
137
+ const oldCountVal = getValFromSignalRecord(meta, signalRecord);
138
+ const newVal = oldCountVal + 1;
139
+ setValToSignalRecord(signalRecord, newVal);
102
140
  }
103
141
  /**
104
- * Ensure that `signals` has a computed signal for the property with `key` on target.
105
- * TODO: Why is this necessary?
142
+ * Create computed signals for getter functions.
106
143
  */
107
- function ensureComputed(signals, target, key, receiver) {
108
- if (!signals.has(key) && hasGetter(target, key)) {
109
- signals.set(key, (0, core_1.computed)(() => Reflect.get(target, key, receiver)));
144
+ function ensureProxiedGetter(signals, target, key, receiver) {
145
+ if (!signals.has(key) &&
146
+ typeof Object.getOwnPropertyDescriptor(target, key)?.get === "function" // If we have a getter?
147
+ ) {
148
+ signals.set(key, {
149
+ alienSignal: (0, core_1.computed)(() => Reflect.get(target, key, receiver)),
150
+ externalSubscribers: new Map(),
151
+ });
110
152
  }
111
153
  }
112
154
  /** Escape JSON-pointer-like path segments so patches remain unambiguous. */
@@ -121,18 +163,23 @@ function isReactiveSymbol(key) {
121
163
  return typeof key === "symbol" && !wellKnownSymbols.has(key);
122
164
  }
123
165
  /**
124
- * Replaces the old proxy to a raw object with a new one.
166
+ * Replaces the old rawToProxy object with a proxy object.
125
167
  * Used so that we can indicate modifications along the path of a change by equality checks.
168
+ * All values remain the same.
169
+ *
170
+ * Does nothing if the deep signal's options `replaceProxiesInBranchOnChange` is false.
126
171
  */
127
- function replaceProxy(meta) {
128
- if (!meta.parent || !meta.key)
172
+ function replaceProxyMaybe(meta) {
173
+ if (!meta.parent ||
174
+ !meta.key ||
175
+ !meta.options.replaceProxiesInBranchOnChange)
129
176
  return;
130
177
  // Create a new proxy for this raw object -- frontend libs like react need this to recognize changes along this path.
131
178
  const handlers = meta.raw instanceof Set ? setHandlers : objectHandlers;
132
179
  const proxy = new Proxy(meta.raw, handlers);
133
180
  rawToProxy.set(meta.raw, proxy);
134
- const signal = propertiesToSignals.get(meta.parent.raw)?.get(meta.key);
135
- signal?.(proxy);
181
+ const signal = getOrCreateSignalMap(meta.parent.raw).get(meta.key);
182
+ signal?.alienSignal(proxy);
136
183
  }
137
184
  /**
138
185
  * Walk the metadata chain to build the patch path for a property access.
@@ -160,7 +207,7 @@ function buildPath(meta, key, skipEscape = false) {
160
207
  let cursor = meta;
161
208
  while (cursor && cursor.parent && cursor.key !== undefined) {
162
209
  push(cursor.key, !!cursor.isSyntheticId);
163
- replaceProxy(cursor);
210
+ replaceProxyMaybe(cursor);
164
211
  cursor = cursor.parent;
165
212
  }
166
213
  return path;
@@ -169,7 +216,7 @@ function buildPath(meta, key, skipEscape = false) {
169
216
  function resolveContainerPath(meta) {
170
217
  if (!meta || !meta.parent || meta.key === undefined)
171
218
  return [];
172
- replaceProxy(meta);
219
+ replaceProxyMaybe(meta);
173
220
  return buildPath(meta.parent, meta.key, !!meta.isSyntheticId);
174
221
  }
175
222
  /**
@@ -293,16 +340,17 @@ function initializeObjectTreeIfNoListeners(meta, basePath, value, inSet) {
293
340
  initializeObjectTree(meta, value, basePath ?? [], inSet);
294
341
  }
295
342
  /**
296
- * Return (or create) a proxy for a nested value.
343
+ * Return (or create) a proxy for a property value.
297
344
  * Ensures the linkage between parent and child in metadata.
298
345
  * Does not proxy and returns `value` if @see shouldProxy returns false.
299
- * Returns value if parent has no metadata record.
346
+ * Assumes parent has proxy.
300
347
  */
301
348
  function ensureChildProxy(rawChild, parent, key, isSyntheticId = false) {
302
349
  if (!shouldProxy(rawChild))
303
350
  return rawChild;
304
351
  const parentRaw = parent[RAW_KEY] || parent;
305
- const parentMeta = rawToMeta?.get(parentRaw);
352
+ const parentMeta = rawToMeta.get(parentRaw);
353
+ // Child is already proxied, ensure the linkage from parent to child.
306
354
  if (rawToProxy.has(rawChild)) {
307
355
  const proxied = rawToProxy.get(rawChild);
308
356
  const proxiedMeta = rawToMeta.get(rawChild);
@@ -313,8 +361,6 @@ function ensureChildProxy(rawChild, parent, key, isSyntheticId = false) {
313
361
  }
314
362
  return proxied;
315
363
  }
316
- if (!parentMeta)
317
- return rawChild;
318
364
  // Create proxy if none exists yet.
319
365
  const proxy = createProxy(rawChild, parentMeta.root, parentMeta.options, parentMeta, key, isSyntheticId);
320
366
  return proxy;
@@ -336,8 +382,8 @@ function ensureSetInfo(meta) {
336
382
  * Assign (or reuse) a synthetic identifier for a Set entry, respecting user options:
337
383
  * - Use user-provided propGenerator for synthetic ids and add add returned extra properties
338
384
  * - Check if the object has a property of `syntheticIdPropertyName` (default `@id`)
339
- * - Use a blank node id as a fallback.
340
- * - Add object and id to `idForObject` and `objectForId` maps.
385
+ * - Use a blank node ID as a fallback.
386
+ * - Add object and ID to `idForObject` and `objectForId` maps.
341
387
  */
342
388
  function assignSyntheticId(meta, entry, path, inSet) {
343
389
  const rawEntry = entry?.[RAW_KEY] ?? entry;
@@ -410,7 +456,7 @@ function createProxy(target, root, options, parentMeta, key, isSyntheticId) {
410
456
  root,
411
457
  options,
412
458
  };
413
- propertiesToSignals.set(target, new Map());
459
+ getOrCreateSignalMap(target);
414
460
  rawToMeta.set(target, meta);
415
461
  rawToProxy.set(target, proxy);
416
462
  return proxy;
@@ -507,25 +553,22 @@ function emitPatchesForNew(value, meta, basePath, inSet = false) {
507
553
  * and dependent effects are notified. Recreates/proxies values for indices < length
508
554
  * and clears signals for indices >= length.
509
555
  */
510
- function refreshNumericIndexSignals(target, receiver) {
511
- const sigs = propertiesToSignals.get(target);
512
- if (!sigs)
513
- return;
514
- for (const k of Array.from(sigs.keys())) {
556
+ function refreshNumericIndexSignals(meta, target, receiver) {
557
+ const signals = getOrCreateSignalMap(meta.raw);
558
+ for (const k of Array.from(signals.keys())) {
515
559
  if (typeof k === "string" && /^\d+$/.test(k)) {
516
560
  const idx = Number(k);
517
561
  if (idx >= target.length) {
518
- const existing = sigs.get(k);
519
- if (existing &&
520
- typeof existing.set === "function") {
521
- existing.set(undefined);
562
+ const existing = signals.get(k);
563
+ if (existing) {
564
+ setSignalValue(meta, k, undefined);
522
565
  }
523
- sigs.delete(k);
566
+ signals.delete(k);
524
567
  }
525
568
  else {
526
569
  const val = target[idx];
527
570
  const proxied = ensureChildProxy(val, receiver, idx);
528
- setSignalValue(sigs, k, proxied);
571
+ setSignalValue(meta, k, proxied);
529
572
  }
530
573
  }
531
574
  }
@@ -559,7 +602,7 @@ const getArrayMutationProxy = (target, key, receiver) => {
559
602
  // Update length of proxy explicitly.
560
603
  receiver.length = target.length;
561
604
  // Refresh numeric index signals so shifted indices don't return stale values.
562
- refreshNumericIndexSignals(target, receiver);
605
+ refreshNumericIndexSignals(meta, target, receiver);
563
606
  };
564
607
  }
565
608
  else if (key === "splice") {
@@ -591,7 +634,7 @@ const getArrayMutationProxy = (target, key, receiver) => {
591
634
  ensureChildProxy(items[i], target, start + i);
592
635
  }
593
636
  // Refresh numeric index signals so shifted indices don't return stale values.
594
- refreshNumericIndexSignals(target, receiver);
637
+ refreshNumericIndexSignals(meta, target, receiver);
595
638
  // Update length of proxy explicitly.
596
639
  receiver.length = target.length;
597
640
  return deletedItems;
@@ -619,7 +662,7 @@ const getArrayMutationProxy = (target, key, receiver) => {
619
662
  // Update length of proxy explicitly.
620
663
  receiver.length = target.length;
621
664
  // Refresh numeric index signals so shifted indices don't return stale values.
622
- refreshNumericIndexSignals(target, receiver);
665
+ refreshNumericIndexSignals(meta, target, receiver);
623
666
  return deletedItems;
624
667
  };
625
668
  }
@@ -632,13 +675,14 @@ const objectHandlers = {
632
675
  return target;
633
676
  if (key === META_KEY)
634
677
  return rawToMeta.get(target);
635
- // TODO: Why are we doing this?
678
+ const meta = rawToMeta.get(target);
636
679
  if (typeof key === "symbol") {
637
680
  if (key === Symbol.iterator) {
638
- const iterableSig = ensureIterableSignal(target);
639
- iterableSig();
681
+ // Ensure that signal exists tracking iteration-based dependencies.
682
+ ensureIterableSignal(meta, target);
640
683
  }
641
684
  if (!isReactiveSymbol(key))
685
+ // No reactivity for well-known symbols
642
686
  return Reflect.get(target, key, receiver);
643
687
  }
644
688
  // Array helper handling. We need that because otherwise, every array position change would go as a separate operation through the proxy.
@@ -650,20 +694,20 @@ const objectHandlers = {
650
694
  }
651
695
  }
652
696
  // Get object map from key to signal.
653
- const signals = ensureSignalMap(target);
654
- // Ensure that target object is signal.
655
- ensureComputed(signals, target, key, receiver);
697
+ const signals = getOrCreateSignalMap(meta.raw);
698
+ // If the property value is a getter, create a computed signal for it.
699
+ ensureProxiedGetter(signals, target, key, receiver);
656
700
  // Add signal if it does not exist already and did not have a getter.
657
701
  if (!signals.has(key)) {
658
702
  let rawChild = Reflect.get(target, key, receiver);
659
703
  if (typeof rawChild === "function")
660
704
  return rawChild.bind(receiver ?? target);
661
705
  const childProxyOrRaw = ensureChildProxy(rawChild, receiver, key);
662
- signals.set(key, (0, core_1.signal)(childProxyOrRaw));
706
+ setSignalValue(meta, key, childProxyOrRaw);
663
707
  }
664
- // Call and return signal
708
+ // Call and return signal.
665
709
  const sig = signals.get(key);
666
- return sig();
710
+ return getValFromSignalRecord(meta, sig);
667
711
  },
668
712
  set(target, key, value, receiver) {
669
713
  // Skip reactivity for symbols.
@@ -674,9 +718,6 @@ const objectHandlers = {
674
718
  throw new Error(`Cannot modify readonly property '${String(key)}'`);
675
719
  }
676
720
  const path = meta ? buildPath(meta, key) : undefined;
677
- const desc = descriptor(target, key);
678
- const hasAccessor = !!desc &&
679
- (typeof desc.get === "function" || typeof desc.set === "function");
680
721
  const proxied = ensureChildProxy(value, target, key);
681
722
  const rawValue = value?.[RAW_KEY] ?? value;
682
723
  const hadKey = Object.prototype.hasOwnProperty.call(target, key);
@@ -690,12 +731,10 @@ const objectHandlers = {
690
731
  }
691
732
  // === Set value on actual target ===
692
733
  const result = Reflect.set(target, key, rawValue, receiver);
693
- if (!hasAccessor) {
694
- const signals = ensureSignalMap(target);
695
- setSignalValue(signals, key, proxied);
696
- }
734
+ // Set signal value.
735
+ setSignalValue(meta, key, proxied);
697
736
  if (!hadKey)
698
- touchIterable(target);
737
+ touchIterable(meta, target);
699
738
  if (meta && path && typeof rawValue === "object") {
700
739
  initializeObjectTreeIfNoListeners(meta, path, rawValue, false);
701
740
  }
@@ -738,18 +777,15 @@ const objectHandlers = {
738
777
  const hadKey = Object.prototype.hasOwnProperty.call(target, key);
739
778
  const result = Reflect.deleteProperty(target, key);
740
779
  if (hadKey) {
741
- if (propertiesToSignals.has(target)) {
742
- // Trigger signal
743
- const signals = propertiesToSignals.get(target);
744
- const existing = signals.get(key);
745
- if (existing &&
746
- typeof existing.set === "function") {
747
- existing.set(undefined);
748
- }
749
- signals.delete(key);
750
- }
780
+ // Trigger signal
781
+ const signals = getOrCreateSignalMap(meta.raw);
782
+ const existing = signals.get(key);
783
+ if (existing)
784
+ setValToSignalRecord(existing, undefined);
785
+ signals.delete(key);
751
786
  // Notify listeners
752
- touchIterable(target);
787
+ touchIterable(meta, target);
788
+ // Schedule remove patch.
753
789
  schedulePatch(meta, () => ({
754
790
  path: buildPath(meta, key),
755
791
  op: "remove",
@@ -758,30 +794,22 @@ const objectHandlers = {
758
794
  return result;
759
795
  },
760
796
  ownKeys(target) {
761
- const sig = ensureIterableSignal(target);
762
- sig();
797
+ const meta = rawToMeta.get(target);
798
+ const sig = ensureIterableSignal(meta, target);
763
799
  return Reflect.ownKeys(target);
764
800
  },
765
801
  };
766
- /**
767
- * Guarantee Set iteration always surfaces proxies, even when raw values were stored.
768
- */
769
- function ensureEntryProxy(raw, entry, syntheticKey, meta) {
770
- return shouldProxy(entry)
771
- ? ensureChildProxy(entry, raw, syntheticKey, true)
772
- : entry;
773
- }
774
802
  /** Wrap the underlying Set iterator so each value is proxied before leaving the trap. */
775
803
  function createSetIterator(target, receiver, mapValue) {
804
+ const meta = rawToMeta.get(target);
776
805
  const iterator = target.values();
777
- const iterableSignal = ensureIterableSignal(target);
778
- iterableSignal();
806
+ ensureIterableSignal(meta, target);
779
807
  return (0, iteratorHelpers_1.createIteratorWithHelpers)(() => {
780
808
  const next = iterator.next();
781
809
  if (next.done)
782
810
  return next;
783
811
  const meta = rawToMeta.get(target);
784
- const proxied = ensureEntryProxy(target, next.value, assignSyntheticId(meta, next.value, [], true), meta);
812
+ const proxied = ensureChildProxy(next.value, target, assignSyntheticId(meta, next.value, [], true), true);
785
813
  return {
786
814
  value: mapValue(proxied),
787
815
  done: false,
@@ -797,36 +825,33 @@ const setHandlers = {
797
825
  if (key === META_KEY)
798
826
  return meta;
799
827
  if (key === "size") {
800
- const sig = ensureIterableSignal(target);
801
- sig();
828
+ ensureIterableSignal(meta, target);
802
829
  return target.size;
803
830
  }
804
831
  if (key === "first") {
805
832
  return function first() {
806
- const iterableSig = ensureIterableSignal(target);
807
- iterableSig();
808
833
  const iterator = target.values().next();
809
834
  if (iterator.done)
810
835
  return undefined;
811
- return ensureEntryProxy(target, iterator.value, assignSyntheticId(meta, iterator.value, [], true), meta);
836
+ const proxy = ensureChildProxy(iterator.value, target, assignSyntheticId(meta, iterator.value, [], true), true);
837
+ ensureIterableSignal(meta, target);
838
+ return proxy;
812
839
  };
813
840
  }
814
841
  if (key === "getById") {
815
842
  return function getById(id) {
816
- const iterableSig = ensureIterableSignal(target);
817
- iterableSig();
818
843
  if (!meta?.setInfo)
819
844
  return undefined;
820
845
  const entry = meta.setInfo.objectForId.get(String(id));
821
846
  if (!entry)
822
847
  return undefined;
823
- return ensureEntryProxy(target, entry, String(id), meta);
848
+ const proxy = ensureChildProxy(entry, target, String(id));
849
+ ensureIterableSignal(meta, target);
850
+ return proxy;
824
851
  };
825
852
  }
826
853
  if (key === "getBy") {
827
854
  return function getBy(graphIri, subjectIri) {
828
- const iterableSig = ensureIterableSignal(target);
829
- iterableSig();
830
855
  return this.getById(`${graphIri}|${subjectIri}`);
831
856
  };
832
857
  }
@@ -834,26 +859,30 @@ const setHandlers = {
834
859
  return function add(value) {
835
860
  const containerPath = resolveContainerPath(meta);
836
861
  const rawValue = value[RAW_KEY] ?? value;
837
- const sizeBefore = target.size;
838
- const result = target.add(rawValue);
839
- if (target.size !== sizeBefore) {
840
- touchIterable(target);
841
- if (rawValue && typeof rawValue === "object") {
842
- const synthetic = assignSyntheticId(meta, rawValue, containerPath, true);
843
- initializeObjectTreeIfNoListeners(meta, [...containerPath, synthetic], rawValue, true);
844
- ensureEntryProxy(target, rawValue, synthetic, meta);
845
- schedulePatch(meta, () => emitPatchesForNew(rawValue, meta, [...containerPath, synthetic], true));
846
- }
847
- else {
848
- const literal = snapshotLiteral(rawValue);
849
- if (literal !== undefined) {
850
- schedulePatch(meta, () => ({
851
- path: containerPath,
852
- op: "add",
853
- type: "set",
854
- value: [literal],
855
- }));
856
- }
862
+ if (target.has(rawValue)) {
863
+ // Nothing to do.
864
+ return receiver;
865
+ }
866
+ target.add(rawValue);
867
+ if (rawValue && typeof rawValue === "object") {
868
+ // Case: Object in set
869
+ const synthetic = assignSyntheticId(meta, rawValue, containerPath, true);
870
+ initializeObjectTreeIfNoListeners(meta, [...containerPath, synthetic], rawValue, true);
871
+ ensureChildProxy(rawValue, target, synthetic, true);
872
+ touchIterable(meta, target);
873
+ schedulePatch(meta, () => emitPatchesForNew(rawValue, meta, [...containerPath, synthetic], true));
874
+ }
875
+ else {
876
+ // Case: Literal in set
877
+ touchIterable(meta, target);
878
+ const literal = snapshotLiteral(rawValue);
879
+ if (literal !== undefined) {
880
+ schedulePatch(meta, () => ({
881
+ path: containerPath,
882
+ op: "add",
883
+ type: "set",
884
+ value: [literal],
885
+ }));
857
886
  }
858
887
  }
859
888
  return receiver;
@@ -861,14 +890,16 @@ const setHandlers = {
861
890
  }
862
891
  if (key === "delete") {
863
892
  return function deleteEntry(value) {
864
- const containerPath = resolveContainerPath(meta);
865
893
  const rawValue = value?.[RAW_KEY] ?? value;
866
894
  const synthetic = rawValue && typeof rawValue === "object"
867
895
  ? ensureSetInfo(meta).idForObject.get(rawValue)
868
896
  : rawValue;
869
897
  const existed = target.delete(rawValue);
898
+ if (existed) {
899
+ touchIterable(meta, target);
900
+ }
870
901
  if (existed && synthetic !== undefined) {
871
- touchIterable(target);
902
+ const containerPath = resolveContainerPath(meta);
872
903
  if (rawValue && typeof rawValue === "object") {
873
904
  schedulePatch(meta, () => ({
874
905
  path: [...containerPath, synthetic],
@@ -893,13 +924,16 @@ const setHandlers = {
893
924
  }
894
925
  if (key === "clear") {
895
926
  return function clear() {
927
+ // Nothing to do.
928
+ if (target.size === 0)
929
+ return;
896
930
  const containerPath = resolveContainerPath(meta);
897
931
  if (meta.setInfo) {
898
932
  meta.setInfo.objectForId.clear();
899
933
  meta.setInfo.idForObject = new WeakMap();
900
934
  }
901
935
  target.clear();
902
- touchIterable(target);
936
+ touchIterable(meta, target);
903
937
  schedulePatch(meta, () => ({
904
938
  path: containerPath,
905
939
  op: "add",
@@ -938,13 +972,12 @@ const setHandlers = {
938
972
  }
939
973
  if (key === "forEach") {
940
974
  return function forEach(callback, thisArg) {
941
- const iterableSig = ensureIterableSignal(target);
942
- iterableSig();
975
+ ensureIterableSignal(meta, target);
943
976
  let index = 0;
944
977
  const expectsIteratorSignature = callback.length <= 2;
945
978
  const iteratorCallback = callback;
946
979
  target.forEach((entry) => {
947
- const proxied = ensureEntryProxy(target, entry, assignSyntheticId(meta, entry, [], true), meta);
980
+ const proxied = ensureChildProxy(entry, target, assignSyntheticId(meta, entry, [], true), true);
948
981
  if (expectsIteratorSignature) {
949
982
  iteratorCallback.call(thisArg, proxied, index++);
950
983
  }
@@ -956,10 +989,23 @@ const setHandlers = {
956
989
  }
957
990
  if (key === "has") {
958
991
  return function has(value) {
992
+ ensureIterableSignal(meta, target);
959
993
  return target.has(value?.[RAW_KEY] ?? value);
960
994
  };
961
995
  }
962
- return Reflect.get(target, key, receiver);
996
+ // All other cases:
997
+ const res = target[key];
998
+ if (typeof res === "function") {
999
+ // For all other functions on sets, return a wrapped function
1000
+ // that calls ensureIterableSignal() before.
1001
+ return (...props) => {
1002
+ ensureIterableSignal(meta, target);
1003
+ return res.bind(target)(...props);
1004
+ };
1005
+ }
1006
+ else {
1007
+ return res;
1008
+ }
963
1009
  },
964
1010
  };
965
1011
  /** Runtime guard that checks whether a value is a deepSignal proxy. */
@@ -967,13 +1013,27 @@ function isDeepSignal(value) {
967
1013
  return !!value?.[RAW_KEY];
968
1014
  }
969
1015
  /**
970
- * Create a deep reactive proxy for objects, arrays or Sets.
971
- * Returns the input itself, if it's a deepSignal already.
972
- * Throws if provided with unsupported input types.
1016
+ * MAIN ENTRY POINT to create a deep reactive proxy for objects, arrays or Sets.
1017
+ *
1018
+ * If input is a deepSignal already and options are provided,
1019
+ * the added subscriberFactories are joined with the existing ones
1020
+ * and `replaceProxiesInBranchOnChange` is or-ed with the current value.
1021
+ *
1022
+ *
1023
+ * @throws if provided with unsupported input types.
973
1024
  */
974
1025
  function deepSignal(input, options) {
975
- if (isDeepSignal(input))
1026
+ // Is the input already a signal?
1027
+ if (isDeepSignal(input)) {
1028
+ // Add possibly new external subscribers to existing ones.
1029
+ const meta = rawToMeta.get(input[RAW_KEY]);
1030
+ meta.options.subscriberFactories =
1031
+ meta.options.subscriberFactories.union(options?.subscriberFactories ?? new Set());
1032
+ meta.options.replaceProxiesInBranchOnChange =
1033
+ meta?.options.replaceProxiesInBranchOnChange ||
1034
+ options?.replaceProxiesInBranchOnChange;
976
1035
  return input;
1036
+ }
977
1037
  if (!shouldProxy(input))
978
1038
  throw new Error("deepSignal() expects an object, array, or Set");
979
1039
  if (rawToProxy.has(input))
@@ -982,6 +1042,8 @@ function deepSignal(input, options) {
982
1042
  const rootState = {
983
1043
  options: {
984
1044
  syntheticIdPropertyName: DEFAULT_SYNTHETIC_ID_PROPERTY_NAME,
1045
+ replaceProxiesInBranchOnChange: false,
1046
+ subscriberFactories: new Set(),
985
1047
  ...options,
986
1048
  },
987
1049
  version: 0,
@@ -1030,7 +1092,14 @@ function getDeepSignalVersion(root) {
1030
1092
  return undefined;
1031
1093
  return rootStates.get(rootId)?.version;
1032
1094
  }
1033
- /** Mark an object so deepSignal skips proxying it (shallow boundary). */
1095
+ /** Mark an object so deepSignal skips proxying it (shallow boundary).
1096
+ *
1097
+ * @example
1098
+ * ```ts
1099
+ * import { shallow } from "alien-deepsignals";
1100
+ * state.config = shallow({ huge: { blob: true } });
1101
+ * ```
1102
+ */
1034
1103
  function shallow(obj) {
1035
1104
  ignored.add(obj);
1036
1105
  return obj;
@@ -1058,3 +1127,7 @@ function addWithId(set, entry, id) {
1058
1127
  }
1059
1128
  return entry;
1060
1129
  }
1130
+ /** Get the original, raw value of a deep signal. */
1131
+ function getRaw(value) {
1132
+ return value?.[RAW_KEY] ?? value;
1133
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"effect.d.ts","sourceRoot":"","sources":["../src/effect.ts"],"names":[],"mappings":"AAKA,KAAK,eAAe,GAAG,CAAC,OAAO,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AAErD,sEAAsE;AACtE,wBAAgB,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,EAAE,eAAe,KAAK,IAAI,cA2BtE"}
1
+ {"version":3,"file":"effect.d.ts","sourceRoot":"","sources":["../src/effect.ts"],"names":[],"mappings":"AAYA,KAAK,eAAe,GAAG,CAAC,OAAO,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AAErD,sEAAsE;AACtE,wBAAgB,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,EAAE,eAAe,KAAK,IAAI,cA2BtE"}