mutts 1.0.9 → 1.0.10

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 (204) hide show
  1. package/README.md +58 -33
  2. package/dist/browser.cjs +1371 -1206
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.ts +2 -1392
  5. package/dist/browser.dev.cjs +110 -0
  6. package/dist/browser.dev.cjs.map +1 -0
  7. package/dist/browser.dev.d.ts +2 -0
  8. package/dist/browser.dev.esm.js +5 -0
  9. package/dist/browser.dev.esm.js.map +1 -0
  10. package/dist/browser.esm.js +6 -87
  11. package/dist/browser.esm.js.map +1 -1
  12. package/dist/chunks/{async-browser-lvzLOCgk.cjs → async-browser-BU_IfxYD.cjs} +4 -85
  13. package/dist/chunks/async-browser-BU_IfxYD.cjs.map +1 -0
  14. package/dist/chunks/{async-node-C3DeIb0y.cjs → async-node-3PrbVAbB.cjs} +3 -1
  15. package/dist/chunks/async-node-3PrbVAbB.cjs.map +1 -0
  16. package/dist/chunks/{index-2vea86wD.esm.js → index-CaaQQlPJ.esm.js} +1116 -953
  17. package/dist/chunks/index-CaaQQlPJ.esm.js.map +1 -0
  18. package/dist/chunks/node-nKJBk8iJ.esm.js +96 -0
  19. package/dist/chunks/node-nKJBk8iJ.esm.js.map +1 -0
  20. package/dist/chunks/{index-VTO-b2vR.cjs → proxy-Dtg-bJ3T.cjs} +2285 -2983
  21. package/dist/chunks/proxy-Dtg-bJ3T.cjs.map +1 -0
  22. package/dist/chunks/{index-CtA2AWl3.esm.js → proxy-r7lARftl.esm.js} +2273 -2948
  23. package/dist/chunks/proxy-r7lARftl.esm.js.map +1 -0
  24. package/dist/debug.cjs +973 -28
  25. package/dist/debug.cjs.map +1 -1
  26. package/dist/debug.d.ts +36 -37
  27. package/dist/debug.esm.js +949 -1
  28. package/dist/debug.esm.js.map +1 -1
  29. package/dist/devtools/manifest.json +1 -1
  30. package/dist/devtools/panel.html +1 -1
  31. package/dist/devtools/panel.js +107 -94
  32. package/dist/devtools/panel.js.map +1 -1
  33. package/dist/index.d.ts +1447 -1
  34. package/dist/mutts.umd.js +7167 -1
  35. package/dist/mutts.umd.js.map +1 -1
  36. package/dist/mutts.umd.min.js +1 -1
  37. package/dist/mutts.umd.min.js.map +1 -1
  38. package/dist/node.cjs +62 -60
  39. package/dist/node.cjs.map +1 -1
  40. package/dist/node.d.ts +2 -2
  41. package/dist/node.dev.cjs +110 -0
  42. package/dist/node.dev.cjs.map +1 -0
  43. package/dist/node.dev.d.ts +2 -0
  44. package/dist/node.dev.esm.js +6 -0
  45. package/dist/node.dev.esm.js.map +1 -0
  46. package/dist/node.esm.js +4 -97
  47. package/dist/node.esm.js.map +1 -1
  48. package/dist/{types-DaHFfhlN.d.ts → types-W5vD6m2n.d.ts} +99 -44
  49. package/docs/ai/api-reference.md +11 -18
  50. package/docs/ai/manual.md +308 -230
  51. package/docs/reactive/advanced.md +312 -2
  52. package/docs/reactive/attend.md +1 -2
  53. package/docs/reactive/collections.md +21 -39
  54. package/docs/reactive/core.md +7 -37
  55. package/docs/reactive/debugging.md +117 -10
  56. package/docs/reactive/error-handling.md +10 -10
  57. package/docs/reactive/resource.md +125 -0
  58. package/docs/reactive/scan.md +32 -1
  59. package/docs/reactive.md +3 -2
  60. package/docs/utils.md +70 -0
  61. package/package.json +59 -30
  62. package/dist/chunks/async-browser-lvzLOCgk.cjs.map +0 -1
  63. package/dist/chunks/async-node-C3DeIb0y.cjs.map +0 -1
  64. package/dist/chunks/index-2vea86wD.esm.js.map +0 -1
  65. package/dist/chunks/index-CtA2AWl3.esm.js.map +0 -1
  66. package/dist/chunks/index-VTO-b2vR.cjs.map +0 -1
  67. package/dist/debug/debug.d.ts +0 -122
  68. package/dist/debug/debug.d.ts.map +0 -1
  69. package/dist/debug/index.d.ts +0 -4
  70. package/dist/debug/index.d.ts.map +0 -1
  71. package/dist/debug/lineage-panel.d.ts +0 -5
  72. package/dist/debug/lineage-panel.d.ts.map +0 -1
  73. package/dist/debug/lineage.d.ts +0 -79
  74. package/dist/debug/lineage.d.ts.map +0 -1
  75. package/dist/src/async/browser.d.ts +0 -2
  76. package/dist/src/async/browser.d.ts.map +0 -1
  77. package/dist/src/async/index.d.ts +0 -19
  78. package/dist/src/async/index.d.ts.map +0 -1
  79. package/dist/src/async/node.d.ts +0 -2
  80. package/dist/src/async/node.d.ts.map +0 -1
  81. package/dist/src/decorator.d.ts +0 -106
  82. package/dist/src/decorator.d.ts.map +0 -1
  83. package/dist/src/destroyable.d.ts +0 -87
  84. package/dist/src/destroyable.d.ts.map +0 -1
  85. package/dist/src/entry-browser.d.ts +0 -3
  86. package/dist/src/entry-browser.d.ts.map +0 -1
  87. package/dist/src/entry-node.d.ts +0 -3
  88. package/dist/src/entry-node.d.ts.map +0 -1
  89. package/dist/src/eventful.d.ts +0 -20
  90. package/dist/src/eventful.d.ts.map +0 -1
  91. package/dist/src/flavored.d.ts +0 -33
  92. package/dist/src/flavored.d.ts.map +0 -1
  93. package/dist/src/index.d.ts +0 -14
  94. package/dist/src/index.d.ts.map +0 -1
  95. package/dist/src/indexable.d.ts +0 -243
  96. package/dist/src/indexable.d.ts.map +0 -1
  97. package/dist/src/introspection.d.ts +0 -27
  98. package/dist/src/introspection.d.ts.map +0 -1
  99. package/dist/src/iterableWeak.d.ts +0 -53
  100. package/dist/src/iterableWeak.d.ts.map +0 -1
  101. package/dist/src/mixins.d.ts +0 -25
  102. package/dist/src/mixins.d.ts.map +0 -1
  103. package/dist/src/promiseChain.d.ts +0 -20
  104. package/dist/src/promiseChain.d.ts.map +0 -1
  105. package/dist/src/reactive/array.d.ts +0 -48
  106. package/dist/src/reactive/array.d.ts.map +0 -1
  107. package/dist/src/reactive/buffer.d.ts +0 -120
  108. package/dist/src/reactive/buffer.d.ts.map +0 -1
  109. package/dist/src/reactive/change.d.ts +0 -29
  110. package/dist/src/reactive/change.d.ts.map +0 -1
  111. package/dist/src/reactive/deep-touch.d.ts +0 -28
  112. package/dist/src/reactive/deep-touch.d.ts.map +0 -1
  113. package/dist/src/reactive/deep-watch-state.d.ts +0 -25
  114. package/dist/src/reactive/deep-watch-state.d.ts.map +0 -1
  115. package/dist/src/reactive/deep-watch.d.ts +0 -20
  116. package/dist/src/reactive/deep-watch.d.ts.map +0 -1
  117. package/dist/src/reactive/describe.d.ts +0 -12
  118. package/dist/src/reactive/describe.d.ts.map +0 -1
  119. package/dist/src/reactive/effect-context.d.ts +0 -34
  120. package/dist/src/reactive/effect-context.d.ts.map +0 -1
  121. package/dist/src/reactive/effects.d.ts +0 -164
  122. package/dist/src/reactive/effects.d.ts.map +0 -1
  123. package/dist/src/reactive/index.d.ts +0 -19
  124. package/dist/src/reactive/index.d.ts.map +0 -1
  125. package/dist/src/reactive/map.d.ts +0 -28
  126. package/dist/src/reactive/map.d.ts.map +0 -1
  127. package/dist/src/reactive/memoize.d.ts +0 -28
  128. package/dist/src/reactive/memoize.d.ts.map +0 -1
  129. package/dist/src/reactive/non-reactive-state.d.ts +0 -9
  130. package/dist/src/reactive/non-reactive-state.d.ts.map +0 -1
  131. package/dist/src/reactive/non-reactive.d.ts +0 -11
  132. package/dist/src/reactive/non-reactive.d.ts.map +0 -1
  133. package/dist/src/reactive/project.d.ts +0 -40
  134. package/dist/src/reactive/project.d.ts.map +0 -1
  135. package/dist/src/reactive/proxy-state.d.ts +0 -8
  136. package/dist/src/reactive/proxy-state.d.ts.map +0 -1
  137. package/dist/src/reactive/proxy.d.ts +0 -23
  138. package/dist/src/reactive/proxy.d.ts.map +0 -1
  139. package/dist/src/reactive/record.d.ts +0 -115
  140. package/dist/src/reactive/record.d.ts.map +0 -1
  141. package/dist/src/reactive/register.d.ts +0 -125
  142. package/dist/src/reactive/register.d.ts.map +0 -1
  143. package/dist/src/reactive/registry.d.ts +0 -21
  144. package/dist/src/reactive/registry.d.ts.map +0 -1
  145. package/dist/src/reactive/set.d.ts +0 -26
  146. package/dist/src/reactive/set.d.ts.map +0 -1
  147. package/dist/src/reactive/tracking.d.ts +0 -7
  148. package/dist/src/reactive/tracking.d.ts.map +0 -1
  149. package/dist/src/reactive/types.d.ts +0 -424
  150. package/dist/src/reactive/types.d.ts.map +0 -1
  151. package/dist/src/reactive/watch.d.ts +0 -48
  152. package/dist/src/reactive/watch.d.ts.map +0 -1
  153. package/dist/src/std-decorators.d.ts +0 -45
  154. package/dist/src/std-decorators.d.ts.map +0 -1
  155. package/dist/src/utils.d.ts +0 -49
  156. package/dist/src/utils.d.ts.map +0 -1
  157. package/dist/src/zone.d.ts +0 -40
  158. package/dist/src/zone.d.ts.map +0 -1
  159. package/docs/reactive/describe.md +0 -85
  160. package/docs/reactive/project.md +0 -93
  161. package/src/async/browser.ts +0 -323
  162. package/src/async/index.ts +0 -27
  163. package/src/async/node.ts +0 -92
  164. package/src/decorator.ts +0 -272
  165. package/src/destroyable.ts +0 -199
  166. package/src/entry-browser.ts +0 -5
  167. package/src/entry-node.ts +0 -5
  168. package/src/eventful.ts +0 -110
  169. package/src/flavored.ts +0 -106
  170. package/src/index.d.ts +0 -12
  171. package/src/index.ts +0 -64
  172. package/src/indexable.ts +0 -526
  173. package/src/introspection.ts +0 -59
  174. package/src/iterableWeak.ts +0 -233
  175. package/src/mixins.ts +0 -123
  176. package/src/promiseChain.ts +0 -110
  177. package/src/reactive/array.ts +0 -500
  178. package/src/reactive/buffer.ts +0 -328
  179. package/src/reactive/change.ts +0 -131
  180. package/src/reactive/deep-touch.ts +0 -273
  181. package/src/reactive/deep-watch-state.ts +0 -82
  182. package/src/reactive/deep-watch.ts +0 -171
  183. package/src/reactive/describe.ts +0 -39
  184. package/src/reactive/effect-context.ts +0 -83
  185. package/src/reactive/effects.ts +0 -1434
  186. package/src/reactive/index.ts +0 -72
  187. package/src/reactive/map.ts +0 -142
  188. package/src/reactive/memoize.ts +0 -186
  189. package/src/reactive/non-reactive-state.ts +0 -49
  190. package/src/reactive/non-reactive.ts +0 -43
  191. package/src/reactive/project.md +0 -107
  192. package/src/reactive/project.ts +0 -430
  193. package/src/reactive/proxy-state.ts +0 -27
  194. package/src/reactive/proxy.ts +0 -282
  195. package/src/reactive/record.ts +0 -181
  196. package/src/reactive/register.ts +0 -538
  197. package/src/reactive/registry.ts +0 -72
  198. package/src/reactive/set.ts +0 -117
  199. package/src/reactive/tracking.ts +0 -41
  200. package/src/reactive/types.ts +0 -520
  201. package/src/reactive/watch.ts +0 -180
  202. package/src/std-decorators.ts +0 -256
  203. package/src/utils.ts +0 -300
  204. package/src/zone.ts +0 -142
@@ -1,4 +1,4 @@
1
- import { r as decorator, a3 as markWithRoot, a4 as rootFunction, V as renamed, a5 as getRoot, a6 as dependant, T as options, a0 as untracked, s as deepCompare, a7 as optionCall, X as root, v as effect, $ as touched1, S as reactive, n as cleanedBy, a8 as __runInitializers, a9 as __esDecorate, F as FoolProof, aa as objectsWithDeepWatchers, ab as effectToDeepWatchedObjects, ac as deepWatchers, H as isNonReactive, a1 as unwrap, ad as stopped, ae as nonReactiveClass, af as unreactiveProperties, ag as nonReactiveMark, ah as nonReactiveObjects, ai as __setFunctionName, aj as __classPrivateFieldGet, ak as __classPrivateFieldSet, al as setEffectName, O as named, B as getActiveEffect, am as projectionInfo, _ as touched, p as contentRef, an as notifyPropertyChange, ao as metaProtos, ap as objectParents, aq as watchers, ar as effectToReactiveObjects, as as proxyToObject, at as objectToProxy, y as flavored } from './index-CtA2AWl3.esm.js';
1
+ import { r as decorator, x as flavored, a3 as markWithRoot, O as named, a4 as rootFunctionSymbol, S as proxyToObject, a5 as getRoot, a6 as dependant, U as options, a0 as untracked, s as deepCompare, a7 as optionCall, W as root, u as effect, $ as touched1, K as isReactive, T as reactive, _ as touched, o as cleanedBy, C as getState, a8 as keysOf, a9 as __runInitializers, aa as __esDecorate, ab as FoolProof, ac as objectsWithDeepWatchers, ad as effectToDeepWatchedObjects, ae as deepWatchers, H as isNonReactive, a1 as unwrap, af as registerDeepWatcher, w as flavorOptions, X as stopped, ag as nonReactiveClass, ah as unreactiveProps, ai as addUnreactiveProps, aj as nonReactiveObjects, ak as __setFunctionName, al as __classPrivateFieldGet, am as __classPrivateFieldSet, l as atomic, an as contentRef, ao as notifyPropertyChange, ap as metaProtos, aq as wrapProtos, ar as objectParents, as as watchers, at as effectToReactiveObjects, P as objectToProxy } from './proxy-r7lARftl.esm.js';
2
2
 
3
3
  // Integrated with `using` statement via Symbol.dispose
4
4
  const fr = new FinalizationRegistry((f) => f());
@@ -105,6 +105,154 @@ function callOnGC(cb) {
105
105
  return forward;
106
106
  }
107
107
 
108
+ /** Max edit distance before bailing out to a single "replace all" patch */
109
+ const BAILOUT_D = 256;
110
+ /**
111
+ * Myers' diff producing grouped patches: `{indexA, indexB, sliceA, sliceB}[]`.
112
+ * - O(N) for identical or prefix/suffix-only differences
113
+ * - O(ND) for small D, with a hard bailout at D=BAILOUT_D → single replacement patch
114
+ */
115
+ function arrayDiff(A, B) {
116
+ let start = 0;
117
+ let endA = A.length;
118
+ let endB = B.length;
119
+ // Trim common prefix
120
+ while (start < endA && start < endB && A[start] === B[start])
121
+ start++;
122
+ // Trim common suffix
123
+ while (endA > start && endB > start && A[endA - 1] === B[endB - 1]) {
124
+ endA--;
125
+ endB--;
126
+ }
127
+ const lenA = endA - start;
128
+ const lenB = endB - start;
129
+ if (lenA === 0 && lenB === 0)
130
+ return [];
131
+ if (lenA === 0)
132
+ return [{ indexA: start, indexB: start, sliceA: [], sliceB: B.slice(start, endB) }];
133
+ if (lenB === 0)
134
+ return [{ indexA: start, indexB: start, sliceA: A.slice(start, endA), sliceB: [] }];
135
+ // Myers with bailout
136
+ const maxD = Math.min(lenA + lenB, BAILOUT_D);
137
+ const vSize = 2 * maxD + 1;
138
+ const vOffset = maxD;
139
+ const V = new Int32Array(vSize);
140
+ V[vOffset + 1] = 0;
141
+ const history = [];
142
+ for (let d = 0; d <= maxD; d++) {
143
+ for (let k = -d; k <= d; k += 2) {
144
+ let x;
145
+ if (k === -d || (k !== d && V[vOffset + k - 1] < V[vOffset + k + 1])) {
146
+ x = V[vOffset + k + 1];
147
+ }
148
+ else {
149
+ x = V[vOffset + k - 1] + 1;
150
+ }
151
+ let y = x - k;
152
+ while (x < lenA && y < lenB && A[start + x] === B[start + y]) {
153
+ x++;
154
+ y++;
155
+ }
156
+ V[vOffset + k] = x;
157
+ if (x >= lenA && y >= lenB)
158
+ return buildPatches(history, A, B, start, x, y, d, k, vOffset);
159
+ }
160
+ history.push(new Int32Array(V));
161
+ }
162
+ // Bailout: too many differences
163
+ return [
164
+ { indexA: start, indexB: start, sliceA: A.slice(start, endA), sliceB: B.slice(start, endB) },
165
+ ];
166
+ }
167
+ function buildPatches(history, A, B, offset, finalX, finalY, finalD, finalK, vOffset) {
168
+ // Backtrack from (finalX, finalY) at step finalD to step 0, collecting ops in reverse
169
+ const ops = []; // 0=eq, 1=ins, 2=del
170
+ let x = finalX;
171
+ let y = finalY;
172
+ let k = finalK;
173
+ for (let d = finalD; d > 0; d--) {
174
+ const prev = history[d - 1];
175
+ let prevK;
176
+ let down;
177
+ if (k === -d) {
178
+ prevK = k + 1;
179
+ down = true;
180
+ }
181
+ else if (k === d) {
182
+ prevK = k - 1;
183
+ down = false;
184
+ }
185
+ else if (prev[vOffset + k - 1] < prev[vOffset + k + 1]) {
186
+ prevK = k + 1;
187
+ down = true;
188
+ }
189
+ else {
190
+ prevK = k - 1;
191
+ down = false;
192
+ }
193
+ const prevXEnd = prev[vOffset + prevK];
194
+ const prevYEnd = prevXEnd - prevK;
195
+ const xStart = down ? prevXEnd : prevXEnd + 1;
196
+ const yStart = down ? prevYEnd + 1 : prevXEnd + 1 - k;
197
+ // Diagonal matches (pushed in reverse)
198
+ while (x > xStart && y > yStart) {
199
+ ops.push(0);
200
+ x--;
201
+ y--;
202
+ }
203
+ // The edit step
204
+ if (down) {
205
+ ops.push(1); // ins
206
+ y--;
207
+ }
208
+ else {
209
+ ops.push(2); // del
210
+ x--;
211
+ }
212
+ k = prevK;
213
+ }
214
+ // Walk ops forward (they were pushed in reverse), grouping contiguous edits
215
+ const patches = [];
216
+ let currA = offset;
217
+ let currB = offset;
218
+ let sliceA = [];
219
+ let sliceB = [];
220
+ let patchA = -1;
221
+ let patchB = -1;
222
+ const flush = () => {
223
+ if (patchA !== -1) {
224
+ patches.push({ indexA: patchA, indexB: patchB, sliceA, sliceB });
225
+ sliceA = [];
226
+ sliceB = [];
227
+ patchA = -1;
228
+ }
229
+ };
230
+ for (let i = ops.length - 1; i >= 0; i--) {
231
+ const op = ops[i];
232
+ if (op === 0) {
233
+ flush();
234
+ currA++;
235
+ currB++;
236
+ }
237
+ else if (op === 1) {
238
+ if (patchA === -1) {
239
+ patchA = currA;
240
+ patchB = currB;
241
+ }
242
+ sliceB.push(B[currB++]);
243
+ }
244
+ else {
245
+ if (patchA === -1) {
246
+ patchA = currA;
247
+ patchB = currB;
248
+ }
249
+ sliceA.push(A[currA++]);
250
+ }
251
+ }
252
+ flush();
253
+ return patches;
254
+ }
255
+
108
256
  var _a, _b;
109
257
  const events = Symbol('events');
110
258
  const hooks = Symbol('hooks');
@@ -607,19 +755,21 @@ function getBranch(tree, key) {
607
755
  }
608
756
  return branch;
609
757
  }
610
- function memoizeFunction(fn) {
758
+ function memoizeFunction(fn, opts) {
611
759
  const fnRoot = getRoot(fn);
612
760
  const existing = memoizedRegistry.get(fnRoot);
613
761
  if (existing)
614
762
  return existing;
615
763
  const cacheRoot = {};
616
- const memoized = markWithRoot((...args) => {
617
- const localArgs = args; //: Args = maxArgs !== undefined ? (args.slice(0, maxArgs) as Args) : args
618
- if (localArgs.some((arg) => !(arg && ['object', 'symbol', 'function'].includes(typeof arg))))
764
+ const memoized = markWithRoot(function memoized(...args) {
765
+ if (args.some((arg) => !(arg && ['object', 'symbol', 'function'].includes(typeof arg)))) {
766
+ if (opts?.lenient)
767
+ return fn.apply(this, args);
619
768
  throw new Error('memoize expects non-null object arguments');
769
+ }
620
770
  let node = cacheRoot;
621
771
  // Note: decorators add `this` as first argument
622
- for (const arg of localArgs) {
772
+ for (const arg of args) {
623
773
  node = getBranch(node, arg);
624
774
  }
625
775
  dependant(node, 'memoize');
@@ -628,9 +778,9 @@ function memoizeFunction(fn) {
628
778
  const wasVerification = options.isVerificationRun;
629
779
  options.isVerificationRun = true;
630
780
  try {
631
- const fresh = untracked(() => fn(...localArgs));
781
+ const fresh = untracked(() => fn.apply(this, args));
632
782
  if (!deepCompare(node.result, fresh)) {
633
- optionCall('onMemoizationDiscrepancy', node.result, fresh, fn, localArgs, 'calculation');
783
+ optionCall('onMemoizationDiscrepancy', node.result, fresh, fn, args, 'calculation');
634
784
  }
635
785
  }
636
786
  finally {
@@ -641,18 +791,18 @@ function memoizeFunction(fn) {
641
791
  }
642
792
  // Create memoize internal effect to track dependencies and invalidate cache
643
793
  // Use untracked to prevent the effect creation from being affected by parent effects
644
- node.cleanup = root(() => effect(() => {
794
+ node.cleanup = root(() => effect.named('memoize')(() => {
645
795
  // Execute the function and track its dependencies
646
796
  // The function execution will automatically track dependencies on reactive objects
647
- node.result = fn(...localArgs);
648
- return () => {
797
+ node.result = fn.apply(this, args);
798
+ return (reason) => {
649
799
  // When dependencies change, clear the cache and notify consumers
650
800
  delete node.result;
651
- touched1(node, { type: 'invalidate', prop: localArgs }, 'memoize');
801
+ touched1(node, { type: 'invalidate', prop: args }, 'memoize');
652
802
  // Lazy memoization: stop the effect so it doesn't re-run immediately.
653
803
  // It will be re-created on next access.
654
804
  if (node.cleanup) {
655
- node.cleanup();
805
+ node.cleanup({ type: 'invalidate', cause: reason });
656
806
  node.cleanup = undefined;
657
807
  }
658
808
  };
@@ -661,9 +811,9 @@ function memoizeFunction(fn) {
661
811
  const wasVerification = options.isVerificationRun;
662
812
  options.isVerificationRun = true;
663
813
  try {
664
- const fresh = untracked(() => fn(...localArgs));
814
+ const fresh = untracked(() => fn.apply(this, args));
665
815
  if (!deepCompare(node.result, fresh)) {
666
- optionCall('onMemoizationDiscrepancy', node.result, fresh, fn, localArgs, 'comparison');
816
+ optionCall('onMemoizationDiscrepancy', node.result, fresh, fn, args, 'comparison');
667
817
  }
668
818
  }
669
819
  finally {
@@ -676,6 +826,55 @@ function memoizeFunction(fn) {
676
826
  memoizedRegistry.set(memoized, memoized);
677
827
  return memoized;
678
828
  }
829
+ function memoizeObject(target, opts) {
830
+ const existing = memoizedRegistry.get(target);
831
+ if (existing)
832
+ return existing;
833
+ const proxy = new Proxy(target, {
834
+ get(source, prop, receiver) {
835
+ // 1. Walk prototype chain to find descriptor
836
+ let current = source;
837
+ let desc;
838
+ while (current) {
839
+ desc = Object.getOwnPropertyDescriptor(current, prop);
840
+ if (desc)
841
+ break;
842
+ current = Object.getPrototypeOf(current);
843
+ }
844
+ if (!desc)
845
+ return Reflect.get(source, prop, receiver);
846
+ // 2. If getter, memoize
847
+ if (desc.get) {
848
+ const originalGetter = desc.get;
849
+ let wrapper = wrapperRegistry.get(originalGetter);
850
+ if (!wrapper) {
851
+ wrapper = markWithRoot(named(`${String(source?.constructor?.name ?? 'Object')}.${String(prop)}`, (that) => {
852
+ return originalGetter.call(that);
853
+ }), {
854
+ propertyKey: prop,
855
+ });
856
+ const origRoot = originalGetter[rootFunctionSymbol];
857
+ if (origRoot)
858
+ wrapper[rootFunctionSymbol] = origRoot;
859
+ wrapperRegistry.set(originalGetter, wrapper);
860
+ }
861
+ const memoized = memoizeFunction(wrapper, opts);
862
+ return memoized(receiver);
863
+ }
864
+ // 3. Otherwise forward
865
+ return Reflect.get(source, prop, receiver);
866
+ },
867
+ // Forward set to the target (source) to ensure it acts as the receiver for reactivity notifications
868
+ set(source, prop, value, _receiver) {
869
+ // By strictly passing `source` as receiver, we ensure that if `source` is a reactive proxy,
870
+ // it recognizes itself and triggers change notifications.
871
+ return Reflect.set(source, prop, value, source);
872
+ },
873
+ });
874
+ proxyToObject.set(proxy, target);
875
+ memoizedRegistry.set(target, proxy);
876
+ return proxy;
877
+ }
679
878
  /**
680
879
  * Decorator and function wrapper for memoizing computed values based on reactive dependencies.
681
880
  *
@@ -699,46 +898,55 @@ function memoizeFunction(fn) {
699
898
  * })
700
899
  * ```
701
900
  */
702
- const memoize = decorator({
703
- getter(original, target, propertyKey) {
704
- return function () {
705
- let wrapper = wrapperRegistry.get(original);
706
- if (!wrapper) {
707
- wrapper = markWithRoot(renamed((that) => {
708
- return original.call(that);
709
- }, `${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(propertyKey)}`), {
710
- method: original,
711
- propertyKey,
712
- ...(original[rootFunction]
713
- ? { [rootFunction]: original[rootFunction] }
714
- : {}),
715
- });
716
- wrapperRegistry.set(original, wrapper);
717
- }
718
- const memoized = memoizeFunction(wrapper);
719
- return memoized(this);
720
- };
721
- },
722
- method(original, target, name) {
723
- return function (...args) {
724
- let wrapper = wrapperRegistry.get(original);
725
- if (!wrapper) {
726
- wrapper = markWithRoot(renamed((that, ...args) => {
727
- return original.call(that, ...args);
728
- }, `${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(name)}`), {
729
- method: original,
730
- propertyKey: name,
731
- ...(original[rootFunction]
732
- ? { [rootFunction]: original[rootFunction] }
733
- : {}),
734
- });
735
- wrapperRegistry.set(original, wrapper);
736
- }
737
- const memoized = memoizeFunction(wrapper);
738
- return memoized(this, ...args);
739
- };
901
+ function makeMemoizeDecorator(memoizeOpts) {
902
+ return decorator({
903
+ getter(original, target, propertyKey) {
904
+ return function () {
905
+ let wrapper = wrapperRegistry.get(original);
906
+ if (!wrapper) {
907
+ wrapper = markWithRoot(named(`${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(propertyKey)}`, (that) => {
908
+ return original.call(that);
909
+ }), {
910
+ method: original,
911
+ propertyKey,
912
+ });
913
+ const origRoot = original[rootFunctionSymbol];
914
+ if (origRoot)
915
+ wrapper[rootFunctionSymbol] = origRoot;
916
+ wrapperRegistry.set(original, wrapper);
917
+ }
918
+ const memoized = memoizeFunction(wrapper, memoizeOpts);
919
+ return memoized(this);
920
+ };
921
+ },
922
+ method(original, target, name) {
923
+ return function (...args) {
924
+ let wrapper = wrapperRegistry.get(original);
925
+ if (!wrapper) {
926
+ wrapper = markWithRoot(named(`${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(name)}`, (that, ...args) => {
927
+ return original.call(that, ...args);
928
+ }), {
929
+ method: original,
930
+ propertyKey: name,
931
+ });
932
+ const origRoot = original[rootFunctionSymbol];
933
+ if (origRoot)
934
+ wrapper[rootFunctionSymbol] = origRoot;
935
+ wrapperRegistry.set(original, wrapper);
936
+ }
937
+ const memoized = memoizeFunction(wrapper, memoizeOpts);
938
+ return memoized(this, ...args);
939
+ };
940
+ },
941
+ default: (target) => typeof target === 'object'
942
+ ? memoizeObject(target, memoizeOpts)
943
+ : memoizeFunction(target, memoizeOpts),
944
+ });
945
+ }
946
+ const memoize = flavored(makeMemoizeDecorator(), {
947
+ get lenient() {
948
+ return makeMemoizeDecorator({ lenient: true });
740
949
  },
741
- default: memoizeFunction,
742
950
  });
743
951
 
744
952
  function attend(source, callback) {
@@ -768,10 +976,10 @@ function attend(source, callback) {
768
976
  }
769
977
  }
770
978
  });
771
- return () => {
772
- outer();
979
+ return (reason) => {
980
+ outer(reason);
773
981
  for (const stop of keyEffects.values())
774
- stop();
982
+ stop(reason);
775
983
  keyEffects.clear();
776
984
  };
777
985
  }
@@ -807,7 +1015,8 @@ function attend(source, callback) {
807
1015
  */
808
1016
  function scan(source, callback, initialValue) {
809
1017
  const observedSource = reactive(source);
810
- const result = reactive([]);
1018
+ const rawResult = [];
1019
+ const result = reactive(rawResult);
811
1020
  // Track effects for each index to dispose them when the array shrinks
812
1021
  const indexEffects = new Map();
813
1022
  // Mapping from index to its current intermediate object
@@ -840,117 +1049,473 @@ function scan(source, callback, initialValue) {
840
1049
  if (stop) {
841
1050
  stop();
842
1051
  indexEffects.delete(index);
843
- untracked(() => {
844
- Reflect.deleteProperty(indexToIntermediate, index);
845
- Reflect.deleteProperty(result, index);
846
- });
1052
+ Reflect.deleteProperty(indexToIntermediate, index);
1053
+ Reflect.deleteProperty(result, index);
847
1054
  }
848
1055
  }
849
1056
  const mainEffect = effect(function scanMainEffect({ ascend }) {
850
1057
  const length = observedSource.length;
851
1058
  const occurrenceCount = new Map();
852
1059
  let prev;
853
- for (let i = 0; i < length; i++) {
854
- const val = FoolProof.get(observedSource, i, observedSource);
855
- if (!(val && (typeof val === 'object' || typeof val === 'function' || typeof val === 'symbol'))) {
856
- throw new Error('scan: items must be objects (WeakKey) for intermediate caching');
857
- }
858
- const count = occurrenceCount.get(val) ?? 0;
859
- occurrenceCount.set(val, count + 1);
860
- let list = intermediaries.get(val);
861
- if (!list) {
862
- list = [];
863
- intermediaries.set(val, list);
864
- }
865
- let intermediate = list[count];
866
- if (!intermediate) {
867
- intermediate = reactive(new Intermediate(val, prev));
868
- list[count] = intermediate;
869
- }
870
- else {
871
- // Update the link.
872
- if (untracked(() => intermediate.prev) !== prev) {
873
- intermediate.prev = prev;
1060
+ for (let i = 0; i < length; i++)
1061
+ if (i in observedSource) {
1062
+ const val = observedSource[i];
1063
+ if (!(val &&
1064
+ (typeof val === 'object' || typeof val === 'function' || typeof val === 'symbol'))) {
1065
+ throw new Error('scan: items must be objects (WeakKey) for intermediate caching');
874
1066
  }
875
- }
876
- // Update the reactive mapping for this index
877
- if (indexToIntermediate[i] !== intermediate) {
878
- indexToIntermediate[i] = intermediate;
879
- }
880
- // If we don't have an effect for this index yet, create one
881
- if (!indexEffects.has(i)) {
882
- ascend(() => {
883
- const index = i;
884
- const stop = effect(function scanIndexSyncEffect() {
885
- const inter = indexToIntermediate[index];
886
- if (inter) {
887
- const accValue = inter.acc;
888
- untracked(() => {
1067
+ const count = occurrenceCount.get(val) ?? 0;
1068
+ occurrenceCount.set(val, count + 1);
1069
+ let list = intermediaries.get(val);
1070
+ if (!list) {
1071
+ list = [];
1072
+ intermediaries.set(val, list);
1073
+ }
1074
+ let intermediate = list[count];
1075
+ if (!intermediate) {
1076
+ intermediate = reactive(new Intermediate(val, prev));
1077
+ list[count] = intermediate;
1078
+ }
1079
+ else {
1080
+ // Update the link.
1081
+ if (untracked(() => intermediate.prev) !== prev) {
1082
+ intermediate.prev = prev;
1083
+ }
1084
+ }
1085
+ // Update the reactive mapping for this index
1086
+ if (indexToIntermediate[i] !== intermediate) {
1087
+ indexToIntermediate[i] = intermediate;
1088
+ }
1089
+ // If we don't have an effect for this index yet, create one
1090
+ if (!indexEffects.has(i)) {
1091
+ ascend(() => {
1092
+ const index = i;
1093
+ const stop = effect(function scanIndexSyncEffect() {
1094
+ const inter = indexToIntermediate[index];
1095
+ if (inter) {
1096
+ const accValue = inter.acc;
889
1097
  result[index] = accValue;
890
- });
891
- }
1098
+ }
1099
+ });
1100
+ indexEffects.set(index, stop);
892
1101
  });
893
- indexEffects.set(index, stop);
894
- });
1102
+ }
1103
+ prev = intermediate;
895
1104
  }
896
- prev = intermediate;
897
- }
898
1105
  // Cleanup trailing indices
899
1106
  for (const index of Array.from(indexEffects.keys())) {
900
1107
  if (index >= length)
901
1108
  disposeIndex(index);
902
1109
  }
903
1110
  // Ensure result length matches source length
904
- untracked(() => {
905
- if (result.length !== length) {
906
- FoolProof.set(result, 'length', length, result);
907
- }
908
- });
1111
+ if (rawResult.length !== length) {
1112
+ FoolProof.set(result, 'length', length, result);
1113
+ }
909
1114
  });
910
- return cleanedBy(result, (() => {
911
- mainEffect();
1115
+ return cleanedBy(result, ((reason) => {
1116
+ mainEffect(reason);
912
1117
  for (const stop of indexEffects.values())
913
- stop();
1118
+ stop(reason);
914
1119
  indexEffects.clear();
915
1120
  }));
916
1121
  }
917
1122
  function lift(cb) {
918
1123
  let result;
919
- const liftCleanup = effect((access) => {
1124
+ let rawResult;
1125
+ const liftCleanup = effect.named('lift')(markWithRoot((access) => {
920
1126
  const source = cb(access);
921
1127
  if (!source || typeof source !== 'object')
922
1128
  throw new Error('lift callback must return an array or object');
1129
+ const sourceProto = Object.getPrototypeOf(source);
923
1130
  if (!result) {
924
- if (Array.isArray(source)) {
925
- result = reactive([]);
1131
+ rawResult = Array.isArray(source) ? [] : Object.create(sourceProto);
1132
+ result = reactive(rawResult);
1133
+ }
1134
+ if (sourceProto !== Object.getPrototypeOf(result))
1135
+ throw new Error('lift callback must return the same type as the previous result');
1136
+ if (Array.isArray(source)) {
1137
+ const res = result;
1138
+ const patches = arrayDiff(res, source);
1139
+ for (let i = patches.length - 1; i >= 0; i--) {
1140
+ const { indexA, sliceA, sliceB } = patches[i];
1141
+ res.splice(indexA, sliceA.length, ...sliceB);
926
1142
  }
927
- else {
928
- result = reactive({});
1143
+ }
1144
+ else {
1145
+ for (const key of Object.keys(source)) {
1146
+ const had = key in rawResult;
1147
+ const newDesc = Object.getOwnPropertyDescriptor(source, key);
1148
+ if (had) {
1149
+ const oldDesc = Object.getOwnPropertyDescriptor(rawResult, key);
1150
+ const sameAccessor = oldDesc && newDesc.get && oldDesc.get === newDesc.get;
1151
+ Object.defineProperty(rawResult, key, newDesc);
1152
+ if (!sameAccessor &&
1153
+ rawResult[key] !==
1154
+ (oldDesc ? (oldDesc.get ? oldDesc.get() : oldDesc.value) : undefined))
1155
+ touched1(rawResult, { type: 'set', prop: key }, key);
1156
+ }
1157
+ else {
1158
+ Object.defineProperty(rawResult, key, newDesc);
1159
+ touched1(rawResult, { type: 'add', prop: key }, key);
1160
+ }
929
1161
  }
1162
+ for (const key of Object.keys(rawResult))
1163
+ if (!(key in source)) {
1164
+ delete rawResult[key];
1165
+ touched1(rawResult, { type: 'del', prop: key }, key);
1166
+ }
1167
+ }
1168
+ }, cb));
1169
+ return cleanedBy(result, liftCleanup);
1170
+ }
1171
+ /**
1172
+ * Reactively maps an array source through `fn`, producing a lazy reactive output array.
1173
+ *
1174
+ * Each source item gets its own isolated effect (via `root()`) so that changes to one item
1175
+ * only recompute that item's projection. Structural changes (push, splice, reorder) are detected
1176
+ * via `arrayDiff` and surgically applied to the output cache.
1177
+ *
1178
+ * The source can be a reactive array or a function returning an array. When a function is provided,
1179
+ * the function is re-evaluated inside an effect whenever its dependencies change.
1180
+ *
1181
+ * Output elements are computed lazily — accessing `result[i]` triggers computation if not yet cached.
1182
+ *
1183
+ * @param source - A reactive array or a function returning an array
1184
+ * @param fn - Mapping function applied to each element
1185
+ * @param options - Optional purity hints to skip per-item effects
1186
+ * @returns A readonly reactive array with a `[cleanup]` method to dispose all effects
1187
+ */
1188
+ function morphArray(source, fn, options) {
1189
+ if (typeof source !== 'function' && !isReactive(source) && options?.pure === true) {
1190
+ return source.map(fn);
1191
+ }
1192
+ let track;
1193
+ const itemEffects = new Map();
1194
+ const cache = [];
1195
+ let input = [];
1196
+ function stopItem(key) {
1197
+ const entry = itemEffects.get(key);
1198
+ if (entry) {
1199
+ entry.stop({ type: 'stopped' });
1200
+ itemEffects.delete(key);
1201
+ }
1202
+ }
1203
+ function computeItem(key, input) {
1204
+ const isPure = options?.pure === true || (typeof options?.pure === 'function' && options.pure(input));
1205
+ if (isPure) {
1206
+ track(() => {
1207
+ cache[key] = fn(input);
1208
+ });
1209
+ }
1210
+ else {
1211
+ const indexRef = { value: key };
1212
+ let stop;
1213
+ track(() => {
1214
+ stop = effect.opaque(() => {
1215
+ cache[indexRef.value] = fn(input);
1216
+ return (reason) => {
1217
+ delete cache[indexRef.value];
1218
+ touched1(cache, { type: 'invalidate', prop: 'morph' }, String(key));
1219
+ stop({ type: 'invalidate', cause: reason });
1220
+ };
1221
+ });
1222
+ });
1223
+ itemEffects.set(key, { stop, index: indexRef });
930
1224
  }
931
- if (Array.isArray(source) && Array.isArray(result)) {
932
- if (result.length !== source.length)
933
- result.length = source.length;
934
- for (let i = 0; i < source.length; i++)
935
- if (result[i] !== source[i])
936
- result[i] = source[i];
1225
+ }
1226
+ const proxy = reactive(cache, {
1227
+ get(cache, prop) {
1228
+ const n = typeof prop === 'string' ? Number(prop) : NaN;
1229
+ if (Number.isNaN(n))
1230
+ return cache[prop];
1231
+ if (!(n in cache))
1232
+ computeItem(n, input[n]);
1233
+ return cache[n];
1234
+ },
1235
+ has(_cache, prop) {
1236
+ return Reflect.has(input, prop);
1237
+ },
1238
+ });
1239
+ const stopMain = effect.named('morph:array')(({ ascend }) => {
1240
+ track = ascend;
1241
+ const newInput = [...(typeof source === 'function' ? source() : source)];
1242
+ const diffs = arrayDiff(input, newInput).toSorted((a, b) => b.indexA - a.indexA);
1243
+ if (diffs.length > 0) {
1244
+ for (const diff of diffs) {
1245
+ // Stop items in removed range
1246
+ for (let i = diff.indexA; i < diff.indexA + diff.sliceA.length; i++)
1247
+ stopItem(i);
1248
+ // Shift existing itemEffects in the Map to match the new indices
1249
+ const shift = diff.sliceB.length - diff.sliceA.length;
1250
+ if (shift !== 0) {
1251
+ // We need to move entries in the Map.
1252
+ const entries = Array.from(itemEffects.entries()).sort((a, b) => a[0] - b[0]);
1253
+ // Remove entries that will be shifted
1254
+ for (const [idx, entry] of entries) {
1255
+ if (idx >= diff.indexA + diff.sliceA.length) {
1256
+ itemEffects.delete(idx);
1257
+ }
1258
+ }
1259
+ // Re-add them with shifted indices
1260
+ for (const [idx, entry] of entries) {
1261
+ if (idx >= diff.indexA + diff.sliceA.length) {
1262
+ const newIdx = idx + shift;
1263
+ entry.index.value = newIdx;
1264
+ itemEffects.set(newIdx, entry);
1265
+ }
1266
+ }
1267
+ }
1268
+ // Splice the cache
1269
+ cache.splice(diff.indexA, diff.sliceA.length, ...new Array(diff.sliceB.length).fill(undefined));
1270
+ // Make holes for lazy computation
1271
+ for (let i = diff.indexA; i < diff.indexA + diff.sliceB.length; i++)
1272
+ delete cache[i];
1273
+ }
1274
+ const invalidates = new Set([keysOf]);
1275
+ if (input.length !== newInput.length)
1276
+ invalidates.add('length');
1277
+ for (const diff of diffs) {
1278
+ const max = Math.max(diff.sliceA.length, diff.sliceB.length);
1279
+ for (let i = 0; i < max; i++)
1280
+ invalidates.add(String(diff.indexA + i));
1281
+ }
1282
+ touched(cache, { type: 'bunch', method: 'morph-input' }, invalidates);
1283
+ }
1284
+ input = newInput;
1285
+ });
1286
+ return cleanedBy(proxy, (reason) => {
1287
+ stopMain(reason);
1288
+ for (const entry of itemEffects.values())
1289
+ entry.stop(reason);
1290
+ itemEffects.clear();
1291
+ });
1292
+ }
1293
+ /**
1294
+ * Reactively maps a `Map` source through `fn`, producing a reactive output Map.
1295
+ *
1296
+ * Each key gets its own isolated effect so that value changes for one key only recompute
1297
+ * that key's projection. Key additions and removals are tracked via `keysOf` dependency.
1298
+ *
1299
+ * @param source - A reactive Map
1300
+ * @param fn - Mapping function applied to each value
1301
+ * @param options - Optional purity hints to skip per-key effects
1302
+ * @returns A reactive Map with a `[cleanup]` method to dispose all effects
1303
+ */
1304
+ function morphMap(source, fn, options) {
1305
+ if (!isReactive(source) && options?.pure === true) {
1306
+ const res = new Map();
1307
+ for (const [k, v] of source)
1308
+ res.set(k, fn(v));
1309
+ return res;
1310
+ }
1311
+ let track;
1312
+ const itemEffects = new Map();
1313
+ const cache = new Map();
1314
+ Object.defineProperty(cache, 'constructor', { value: Object, enumerable: false });
1315
+ function stopItem(key) {
1316
+ const stop = itemEffects.get(key);
1317
+ if (stop) {
1318
+ stop({ type: 'stopped' });
1319
+ itemEffects.delete(key);
937
1320
  }
938
- else if (!Array.isArray(source) && !Array.isArray(result)) {
939
- Object.assign(result, source);
940
- for (const key of Object.keys(result))
941
- if (!(key in source))
942
- delete result[key];
1321
+ }
1322
+ function computeItem(key, val) {
1323
+ const isPure = options?.pure === true || (typeof options?.pure === 'function' && options.pure(val));
1324
+ if (isPure) {
1325
+ cache.set(key, track(() => fn(val)));
943
1326
  }
944
1327
  else {
945
- throw new Error('lift callback must return the same type as the previous result');
1328
+ const stop = track(() => effect.opaque(() => {
1329
+ cache.set(key, fn(source.get(key)));
1330
+ return (reason) => {
1331
+ cache.delete(key);
1332
+ touched1(cache, { type: 'invalidate', prop: 'morph' }, String(key));
1333
+ stop({ type: 'invalidate', cause: reason });
1334
+ };
1335
+ }));
1336
+ itemEffects.set(key, stop);
1337
+ }
1338
+ }
1339
+ const proxy = reactive(cache, {
1340
+ get(cache, prop) {
1341
+ if (prop === 'get')
1342
+ return (key) => {
1343
+ if (!cache.has(key) && source.has(key))
1344
+ computeItem(key, source.get(key));
1345
+ return cache.get(key);
1346
+ };
1347
+ if (prop === 'has')
1348
+ return (key) => {
1349
+ return source.has(key);
1350
+ };
1351
+ if (prop === 'keys')
1352
+ return () => {
1353
+ return source.keys();
1354
+ };
1355
+ if (prop === 'values')
1356
+ return function* () {
1357
+ for (const key of source.keys()) {
1358
+ yield proxy.get(key);
1359
+ }
1360
+ };
1361
+ if (prop === 'entries')
1362
+ return function* () {
1363
+ for (const key of source.keys()) {
1364
+ yield [key, proxy.get(key)];
1365
+ }
1366
+ };
1367
+ if (prop === Symbol.iterator)
1368
+ return function* () {
1369
+ for (const key of source.keys()) {
1370
+ yield [key, proxy.get(key)];
1371
+ }
1372
+ };
1373
+ return cache[prop];
1374
+ },
1375
+ });
1376
+ let stateSnapshot = getState(source);
1377
+ const stopMain = effect.named('morph:map')(({ ascend }) => {
1378
+ track = ascend;
1379
+ dependant(source, keysOf);
1380
+ while ('evolution' in stateSnapshot) {
1381
+ const { evolution } = stateSnapshot;
1382
+ stateSnapshot = stateSnapshot.next;
1383
+ if (evolution.type === 'add') {
1384
+ touched1(cache, evolution, evolution.prop);
1385
+ }
1386
+ else if (evolution.type === 'del') {
1387
+ stopItem(evolution.prop);
1388
+ cache.delete(evolution.prop);
1389
+ touched1(cache, evolution, evolution.prop);
1390
+ }
946
1391
  }
947
1392
  });
948
- return cleanedBy(result, liftCleanup);
1393
+ return cleanedBy(proxy, (reason) => {
1394
+ stopMain(reason);
1395
+ for (const stop of itemEffects.values())
1396
+ stop(reason);
1397
+ itemEffects.clear();
1398
+ });
949
1399
  }
950
-
951
- function isObject(value) {
952
- return typeof value === 'object' && value !== null;
1400
+ /**
1401
+ * Reactively maps a record/object source through `fn`, producing a reactive output record.
1402
+ *
1403
+ * Each key gets its own isolated effect so that value changes for one key only recompute
1404
+ * that key's projection. Key additions and removals are tracked automatically.
1405
+ *
1406
+ * @param source - A reactive record
1407
+ * @param fn - Mapping function applied to each value
1408
+ * @param options - Optional purity hints to skip per-key effects
1409
+ * @returns A reactive record with a `[cleanup]` method to dispose all effects
1410
+ */
1411
+ function morphRecord(source, fn, options) {
1412
+ if (!isReactive(source) && options?.pure === true) {
1413
+ const res = {};
1414
+ for (const k of Object.keys(source))
1415
+ res[k] = fn(source[k]);
1416
+ return res;
1417
+ }
1418
+ let track;
1419
+ const itemEffects = new Map();
1420
+ const cache = {};
1421
+ function stopItem(key) {
1422
+ const stop = itemEffects.get(key);
1423
+ if (stop) {
1424
+ stop({ type: 'stopped' });
1425
+ itemEffects.delete(key);
1426
+ }
1427
+ }
1428
+ function computeItem(key, val) {
1429
+ const isPure = options?.pure === true || (typeof options?.pure === 'function' && options.pure(val));
1430
+ if (isPure) {
1431
+ cache[key] = track(() => fn(val));
1432
+ }
1433
+ else {
1434
+ const stop = track(() => effect.opaque(() => {
1435
+ cache[key] = fn(source[key]);
1436
+ return (reason) => {
1437
+ delete cache[key];
1438
+ touched1(cache, { type: 'invalidate', prop: 'morph' }, String(key));
1439
+ stop({ type: 'invalidate', cause: reason });
1440
+ };
1441
+ }));
1442
+ itemEffects.set(key, stop);
1443
+ }
1444
+ }
1445
+ function get(prop) {
1446
+ if (!(prop in cache) && prop in source)
1447
+ computeItem(prop, source[prop]);
1448
+ return cache[prop];
1449
+ }
1450
+ const proxy = reactive(cache, {
1451
+ get(_, prop) {
1452
+ return get(prop);
1453
+ },
1454
+ has(_, prop) {
1455
+ return prop in source;
1456
+ },
1457
+ ownKeys() {
1458
+ return Reflect.ownKeys(source);
1459
+ },
1460
+ getOwnPropertyDescriptor(_cache, prop) {
1461
+ if (prop in source)
1462
+ return { configurable: true, enumerable: true, get: () => get(prop) };
1463
+ },
1464
+ });
1465
+ let stateSnapshot = getState(source);
1466
+ const stopMain = effect.named('morph:record')(({ ascend }) => {
1467
+ track = ascend;
1468
+ // Track only structural changes on source
1469
+ dependant(source, keysOf);
1470
+ while ('evolution' in stateSnapshot) {
1471
+ const { evolution } = stateSnapshot;
1472
+ stateSnapshot = stateSnapshot.next;
1473
+ if (evolution.type === 'add') {
1474
+ touched1(cache, evolution, evolution.prop);
1475
+ }
1476
+ else if (evolution.type === 'del') {
1477
+ stopItem(evolution.prop);
1478
+ delete cache[evolution.prop];
1479
+ touched1(cache, evolution, evolution.prop);
1480
+ }
1481
+ }
1482
+ });
1483
+ return cleanedBy(proxy, (reason) => {
1484
+ stopMain(reason);
1485
+ for (const stop of itemEffects.values())
1486
+ stop(reason);
1487
+ itemEffects.clear();
1488
+ });
953
1489
  }
1490
+ /**
1491
+ * Reactively maps a collection (array, Map, or record) through a per-entry function.
1492
+ *
1493
+ * Each entry in the source gets its own reactive context — when only one entry's dependencies
1494
+ * change, only that entry's projection recomputes. Structural changes (additions, removals,
1495
+ * reorders) are detected via diffing and applied surgically.
1496
+ *
1497
+ * Use `morph.pure(source, fn)` when `fn` has no reactive reads (skips per-item effects).
1498
+ *
1499
+ * @example
1500
+ * ```ts
1501
+ * const users = reactive([{ name: 'John' }, { name: 'Jane' }])
1502
+ * const names = morph(users, u => u.name.toUpperCase())
1503
+ * // names[0] = 'JOHN', names[1] = 'JANE'
1504
+ * // Changing users[0].name only recomputes names[0]
1505
+ * ```
1506
+ */
1507
+ const morph = flavored(function morph(source, fn, options) {
1508
+ if (Array.isArray(source) || typeof source === 'function')
1509
+ return morphArray(source, fn, options);
1510
+ if (source instanceof Map)
1511
+ return morphMap(source, fn, options);
1512
+ return morphRecord(source, fn, options);
1513
+ }, {
1514
+ get pure() {
1515
+ return (source, fn, _opt) => this(source, fn, { pure: true });
1516
+ },
1517
+ });
1518
+
954
1519
  /**
955
1520
  * Deep watch an object and all its nested properties
956
1521
  * @param target - The object to watch deeply
@@ -972,6 +1537,7 @@ function deepWatch(target, callback, { immediate = false } = {}) {
972
1537
  throw new Error('Target of deep watching must be an object');
973
1538
  // Create a wrapper callback that matches EffectTrigger signature
974
1539
  const wrappedCallback = markWithRoot((() => callback(target)), callback);
1540
+ registerDeepWatcher();
975
1541
  // Use the existing effect system to register dependencies
976
1542
  return effect(() => {
977
1543
  // Mark the target object as having deep watchers
@@ -988,7 +1554,7 @@ function deepWatch(target, callback, { immediate = false } = {}) {
988
1554
  const visited = new WeakSet();
989
1555
  function traverseAndTrack(obj, depth = 0) {
990
1556
  // Prevent infinite recursion and excessive depth
991
- if (!obj || visited.has(obj) || !isObject(obj) || depth > options.maxDeepWatchDepth)
1557
+ if (!obj || visited.has(obj) || typeof obj !== 'object' || depth > options.maxDeepWatchDepth)
992
1558
  return;
993
1559
  // Do not traverse into unreactive objects
994
1560
  if (isNonReactive(obj))
@@ -1047,8 +1613,9 @@ function deepWatch(target, callback, { immediate = false } = {}) {
1047
1613
  // This will register dependencies on all current properties and array elements
1048
1614
  traverseAndTrack(target);
1049
1615
  // Only call the callback if immediate is true or if it's not the first run
1050
- if (immediate)
1051
- callback(target);
1616
+ if (immediate) {
1617
+ untracked(() => callback(target));
1618
+ }
1052
1619
  immediate = true;
1053
1620
  // Return a cleanup function that properly removes deep watcher tracking
1054
1621
  return () => {
@@ -1081,30 +1648,103 @@ function deepWatch(target, callback, { immediate = false } = {}) {
1081
1648
  }
1082
1649
 
1083
1650
  /**
1084
- * Converts an iterator to a generator that yields reactive values
1651
+ * Organizes a source object's properties into a target object using a callback function.
1652
+ * This creates a reactive mapping between source properties and a target object,
1653
+ * automatically handling property additions, updates, and removals.
1654
+ *
1655
+ * @template Source - The type of the source object
1656
+ * @template Target - The type of the target object (defaults to Record<PropertyKey, any>)
1657
+ *
1658
+ * @param {Source} source - The source object to organize
1659
+ * @param {OrganizedCallback<Source, Target>} apply - Callback function that defines how each source property is mapped to the target
1660
+ * @param {Target} [baseTarget={}] - Optional base target object to use (will be made reactive if not already)
1661
+ *
1662
+ * @returns {OrganizedResult<Target>} The target object with cleanup capability
1663
+ *
1664
+ * @example
1665
+ * // Organize user permissions into role-based access
1666
+ * const user = reactive({ isAdmin: true, canEdit: false });
1667
+ * const permissions = organized(
1668
+ * user,
1669
+ * (access, target) => {
1670
+ * if (access.key === 'isAdmin') {
1671
+ * target.hasFullAccess = access.value;
1672
+ * }
1673
+ * target[`can${access.key.charAt(0).toUpperCase() + access.key.slice(1)}`] = access.value;
1674
+ * }
1675
+ * );
1676
+ *
1677
+ * @example
1678
+ * // Transform object structure with cleanup
1679
+ * const source = reactive({ firstName: 'John', lastName: 'Doe' });
1680
+ * const formatted = organized(
1681
+ * source,
1682
+ * (access, target) => {
1683
+ * if (access.key === 'firstName' || access.key === 'lastName') {
1684
+ * target.fullName = `${source.firstName} ${source.lastName}`.trim();
1685
+ * }
1686
+ * }
1687
+ * );
1688
+ *
1689
+ * @example
1690
+ * // Using with cleanup in a component
1691
+ * effect(() => {
1692
+ * const data = fetchData();
1693
+ * const organizedData = organized(data, (access, target) => {
1694
+ * // Transform data
1695
+ * });
1696
+ *
1697
+ * // The cleanup will be called automatically when the effect is disposed
1698
+ * return () => organizedData[cleanup]();
1699
+ * });
1085
1700
  */
1086
- function* makeReactiveIterator(iterator) {
1087
- let result = iterator.next();
1088
- while (!result.done) {
1089
- yield reactive(result.value);
1090
- result = iterator.next();
1091
- }
1701
+ function organized(source, apply, baseTarget = {}) {
1702
+ const observedSource = reactive(source);
1703
+ const target = reactive(baseTarget);
1704
+ const stop = attend(() => {
1705
+ const keys = [];
1706
+ for (const key in observedSource)
1707
+ keys.push(key);
1708
+ return keys;
1709
+ }, (key) => {
1710
+ const sourceKey = key;
1711
+ const accessBase = {
1712
+ key: sourceKey,
1713
+ get: () => FoolProof.get(observedSource, sourceKey, observedSource),
1714
+ set: (value) => FoolProof.set(observedSource, sourceKey, value, observedSource),
1715
+ };
1716
+ Object.defineProperty(accessBase, 'value', {
1717
+ get: accessBase.get,
1718
+ set: accessBase.set,
1719
+ configurable: true,
1720
+ enumerable: true,
1721
+ });
1722
+ return apply(accessBase, target);
1723
+ });
1724
+ return cleanedBy(target, (reason) => stop(reason));
1092
1725
  }
1093
1726
  /**
1094
- * Converts an iterator of key-value pairs to a generator that yields reactive key-value pairs
1095
- */
1096
- function* makeReactiveEntriesIterator(iterator) {
1097
- let result = iterator.next();
1098
- while (!result.done) {
1099
- const [key, value] = result.value;
1100
- yield [reactive(key), reactive(value)];
1101
- result = iterator.next();
1102
- }
1727
+ * Organizes a property on a target object
1728
+ * Shortcut for defineProperty/delete with touched signal
1729
+ * @param target - The target object
1730
+ * @param property - The property to organize
1731
+ * @param access - The access object
1732
+ * @returns The property descriptor
1733
+ */
1734
+ function organize(target, property, access) {
1735
+ Object.defineProperty(target, property, {
1736
+ get: access.get,
1737
+ set: access.set,
1738
+ configurable: true,
1739
+ enumerable: true,
1740
+ });
1741
+ touched1(target, { type: 'set', prop: property }, property);
1742
+ return () => delete target[property];
1103
1743
  }
1104
1744
 
1105
1745
  //#region watch
1106
1746
  const unsetYet = Symbol('unset-yet');
1107
- function watch(value, //object | ((dep: DependencyAccess) => object),
1747
+ const watch = flavored(function watch(value, //object | ((dep: DependencyAccess) => object),
1108
1748
  changed, options = {}) {
1109
1749
  return typeof value === 'function'
1110
1750
  ? watchCallBack(value, changed, options)
@@ -1113,7 +1753,14 @@ changed, options = {}) {
1113
1753
  : (() => {
1114
1754
  throw new Error('watch: value must be a function or an object');
1115
1755
  })();
1116
- }
1756
+ }, {
1757
+ get deep() {
1758
+ return flavorOptions(this, { deep: true });
1759
+ },
1760
+ get immediate() {
1761
+ return flavorOptions(this, { immediate: true });
1762
+ },
1763
+ });
1117
1764
  function watchObject(value, changed, { immediate = false, deep = false } = {}) {
1118
1765
  if (deep)
1119
1766
  return deepWatch(value, changed, { immediate });
@@ -1129,13 +1776,15 @@ function watchCallBack(value, changed, { immediate = false, deep = false } = {})
1129
1776
  let deepCleanup;
1130
1777
  const cbCleanup = effect(markWithRoot(function watchCallBackEffect(access) {
1131
1778
  const newValue = value(access);
1132
- if (oldValue !== newValue)
1133
- if (oldValue === unsetYet) {
1779
+ if (oldValue !== newValue) {
1780
+ const old = oldValue;
1781
+ if (old === unsetYet) {
1134
1782
  if (immediate)
1135
- changed(newValue);
1783
+ untracked(() => changed(newValue));
1136
1784
  }
1137
1785
  else
1138
- changed(newValue, oldValue);
1786
+ untracked(() => changed(newValue, old));
1787
+ }
1139
1788
  oldValue = newValue;
1140
1789
  if (deep) {
1141
1790
  if (deepCleanup)
@@ -1152,6 +1801,45 @@ function watchCallBack(value, changed, { immediate = false, deep = false } = {})
1152
1801
  });
1153
1802
  }
1154
1803
  //#endregion
1804
+ //#region when
1805
+ /**
1806
+ * Returns a promise that resolves when the predicate returns a truthy value.
1807
+ * The predicate is evaluated reactively — it re-runs whenever its dependencies change.
1808
+ * @param predicate - Reactive function that returns a value; resolves when truthy
1809
+ * @param timeout - Optional timeout in milliseconds — rejects if condition is not met within this duration
1810
+ * @returns Promise that resolves with the first truthy return value
1811
+ */
1812
+ function when(predicate, timeout) {
1813
+ return new Promise((resolve, reject) => {
1814
+ let timer;
1815
+ const stop = effect(function whenEffect(access) {
1816
+ try {
1817
+ const value = predicate(access);
1818
+ if (value) {
1819
+ if (timer !== undefined)
1820
+ clearTimeout(timer);
1821
+ timer = undefined;
1822
+ queueMicrotask(() => stop());
1823
+ resolve(value);
1824
+ }
1825
+ }
1826
+ catch (error) {
1827
+ if (timer !== undefined)
1828
+ clearTimeout(timer);
1829
+ timer = undefined;
1830
+ reject(error);
1831
+ }
1832
+ });
1833
+ if (timeout !== undefined) {
1834
+ timer = setTimeout(() => {
1835
+ stop();
1836
+ timer = undefined;
1837
+ reject(new Error(`when: timed out after ${timeout}ms`));
1838
+ }, timeout);
1839
+ }
1840
+ });
1841
+ }
1842
+ //#endregion
1155
1843
  //#region nonReactive
1156
1844
  /**
1157
1845
  * Mark an object as non-reactive. This object and all its properties will never be made reactive.
@@ -1161,19 +1849,7 @@ function shallowNonReactive(obj) {
1161
1849
  obj = unwrap(obj);
1162
1850
  if (isNonReactive(obj))
1163
1851
  return obj;
1164
- try {
1165
- Object.defineProperty(obj, nonReactiveMark, {
1166
- value: true,
1167
- writable: false,
1168
- enumerable: false,
1169
- configurable: true,
1170
- });
1171
- }
1172
- catch { }
1173
- if (!(nonReactiveMark in obj))
1174
- nonReactiveObjects.add(obj);
1175
- // Finally, not deep
1176
- //for (const key in obj) deepNonReactive(obj[key])
1852
+ nonReactiveObjects.add(obj);
1177
1853
  return obj;
1178
1854
  }
1179
1855
  function unreactiveApplication(arg1, ...args) {
@@ -1181,11 +1857,13 @@ function unreactiveApplication(arg1, ...args) {
1181
1857
  ? shallowNonReactive(arg1)
1182
1858
  : ((original) => {
1183
1859
  // Copy the parent's unreactive properties if they exist
1184
- original.prototype[unreactiveProperties] = new Set(original.prototype[unreactiveProperties] || []);
1860
+ const parentSet = unreactiveProps.get(original.prototype);
1861
+ const set = new Set(parentSet || []);
1185
1862
  // Add all arguments (including the first one)
1186
- original.prototype[unreactiveProperties].add(arg1);
1863
+ set.add(arg1);
1187
1864
  for (const arg of args)
1188
- original.prototype[unreactiveProperties].add(arg);
1865
+ set.add(arg);
1866
+ addUnreactiveProps(original.prototype, set);
1189
1867
  return original; // Return the class
1190
1868
  });
1191
1869
  }
@@ -1200,6 +1878,60 @@ const unreactive = decorator({
1200
1878
  },
1201
1879
  default: unreactiveApplication,
1202
1880
  });
1881
+ /**
1882
+ * Creates a reactive resource that automatically tracks async state.
1883
+ * @param fetcher - Async function that returns the value. Reactive dependencies are tracked.
1884
+ * @param options - Resource options (initialValue)
1885
+ * @returns Reactive Resource object with value, loading, error, latest properties
1886
+ */
1887
+ function resource(fetcher, options = {}) {
1888
+ const state = reactive({
1889
+ value: options.initialValue,
1890
+ loading: true,
1891
+ error: undefined,
1892
+ latest: options.initialValue,
1893
+ reload: () => {
1894
+ reloadSignal.value++;
1895
+ },
1896
+ });
1897
+ const reloadSignal = reactive({ value: 0 });
1898
+ let counter = 0;
1899
+ const stop = effect(function resourceEffect(access) {
1900
+ // Track reload signal to enable manual reloading
1901
+ if (reloadSignal.value) ;
1902
+ const id = ++counter;
1903
+ state.loading = true;
1904
+ state.error = undefined;
1905
+ try {
1906
+ const result = fetcher(access);
1907
+ if (result instanceof Promise) {
1908
+ result.then((val) => {
1909
+ if (id === counter) {
1910
+ state.value = val;
1911
+ state.latest = val;
1912
+ state.loading = false;
1913
+ }
1914
+ }, (err) => {
1915
+ if (id === counter) {
1916
+ state.error = err;
1917
+ state.loading = false;
1918
+ }
1919
+ });
1920
+ }
1921
+ else {
1922
+ state.value = result;
1923
+ state.latest = result;
1924
+ state.loading = false;
1925
+ }
1926
+ }
1927
+ catch (err) {
1928
+ state.error = err;
1929
+ state.loading = false;
1930
+ }
1931
+ });
1932
+ state.stop = stop;
1933
+ return state;
1934
+ }
1203
1935
  //#endregion
1204
1936
 
1205
1937
  // Helper to work around TypeScript limitation: base class expressions cannot reference class type parameters
@@ -1670,410 +2402,25 @@ function register(keyFn, initial) {
1670
2402
  }
1671
2403
 
1672
2404
  /**
1673
- * Maps projection effects (item effects) to their projection context
1674
- */
1675
- const effectProjectionMetadata = new WeakMap();
1676
- /**
1677
- * Returns the projection context of the currently running effect, if any.
2405
+ * Converts an iterator to a generator that yields reactive values
1678
2406
  */
1679
- function getActiveProjection() {
1680
- const active = getActiveEffect();
1681
- return active ? effectProjectionMetadata.get(active) : undefined;
1682
- }
1683
- function setActiveProjection(projection) {
1684
- const active = getActiveEffect();
1685
- if (active)
1686
- effectProjectionMetadata.set(active, projection);
1687
- }
1688
- function defineAccessValue(access) {
1689
- Object.defineProperty(access, 'value', {
1690
- get: access.get,
1691
- set: access.set,
1692
- configurable: true,
1693
- enumerable: true,
1694
- });
1695
- }
1696
- function makeCleanup(target, effectMap, onDispose, metadata) {
1697
- if (metadata) {
1698
- Object.defineProperty(target, projectionInfo, {
1699
- value: metadata,
1700
- writable: false,
1701
- enumerable: false,
1702
- configurable: true,
1703
- });
1704
- }
1705
- return cleanedBy(target, () => {
1706
- onDispose();
1707
- for (const stop of effectMap.values())
1708
- stop?.();
1709
- effectMap.clear();
1710
- });
1711
- }
1712
- function projectArray(source, apply) {
1713
- source = reactive(source);
1714
- const target = reactive([]);
1715
- const indexEffects = new Map();
1716
- function normalizeTargetLength(length) {
1717
- FoolProof.set(target, 'length', length, target);
1718
- }
1719
- function disposeIndex(index) {
1720
- const stopEffect = indexEffects.get(index);
1721
- if (stopEffect) {
1722
- indexEffects.delete(index);
1723
- stopEffect();
1724
- Reflect.deleteProperty(target, index);
1725
- }
1726
- }
1727
- const parent = getActiveProjection();
1728
- const depth = parent ? parent.depth + 1 : 0;
1729
- const cleanupLength = effect(named(`project:${source[Symbol.toStringTag]}.length`, ({ ascend }) => {
1730
- const length = source.length;
1731
- normalizeTargetLength(length);
1732
- const existing = Array.from(indexEffects.keys());
1733
- for (let i = 0; i < length; i++) {
1734
- if (indexEffects.has(i))
1735
- continue;
1736
- ascend(function projectArrayIndexAscend() {
1737
- const index = i;
1738
- const stop = effect(named(`project:${source[Symbol.toStringTag]}[${index}]`, ({ reaction }) => {
1739
- if (!reaction)
1740
- setActiveProjection({ source, key: index, target, depth, parent });
1741
- const previous = untracked(() => target[index]);
1742
- const accessBase = {
1743
- key: index,
1744
- source,
1745
- get: () => FoolProof.get(source, index, source),
1746
- set: (value) => FoolProof.set(source, index, value, source),
1747
- old: previous,
1748
- };
1749
- defineAccessValue(accessBase);
1750
- target[index] = apply(accessBase, target);
1751
- }));
1752
- setEffectName(stop, `project[${depth}]:${index}`);
1753
- indexEffects.set(i, stop);
1754
- });
1755
- }
1756
- for (const index of existing)
1757
- if (index >= length)
1758
- disposeIndex(index);
1759
- }));
1760
- return makeCleanup(target, indexEffects, () => cleanupLength(), {
1761
- source,
1762
- target,
1763
- apply,
1764
- depth,
1765
- parent,
1766
- });
1767
- }
1768
- function projectRegister(source, apply) {
1769
- source = reactive(source);
1770
- const rawTarget = new Map();
1771
- const target = reactive(rawTarget);
1772
- const keyEffects = new Map();
1773
- function disposeKey(key) {
1774
- const stopEffect = keyEffects.get(key);
1775
- if (stopEffect) {
1776
- stopEffect();
1777
- keyEffects.delete(key);
1778
- target.delete(key);
1779
- }
1780
- }
1781
- const parent = getActiveProjection();
1782
- const depth = parent ? parent.depth + 1 : 0;
1783
- const cleanupKeys = effect(function projectRegisterEffect({ ascend }) {
1784
- const keys = new Set();
1785
- for (const key of source.mapKeys())
1786
- keys.add(key);
1787
- for (const key of keys) {
1788
- if (keyEffects.has(key))
1789
- continue;
1790
- ascend(() => {
1791
- const stop = effect(function projectRegisterKeyEffect({ reaction }) {
1792
- if (!reaction)
1793
- setActiveProjection({ source, key, target, depth, parent });
1794
- const previous = untracked(() => target.get(key));
1795
- const accessBase = {
1796
- key,
1797
- source: source,
1798
- get: () => source.get(key),
1799
- set: (value) => {
1800
- source.set(key, value);
1801
- return true;
1802
- },
1803
- old: previous,
1804
- };
1805
- defineAccessValue(accessBase);
1806
- const produced = apply(accessBase, target);
1807
- target.set(key, produced);
1808
- });
1809
- setEffectName(stop, `project[${depth}]:${String(key)}`);
1810
- keyEffects.set(key, stop);
1811
- });
1812
- }
1813
- for (const key of Array.from(keyEffects.keys()))
1814
- if (!keys.has(key))
1815
- disposeKey(key);
1816
- });
1817
- return makeCleanup(target, keyEffects, () => cleanupKeys(), {
1818
- source: source,
1819
- target,
1820
- apply,
1821
- depth,
1822
- parent,
1823
- });
1824
- }
1825
- function projectRecord(source, apply) {
1826
- source = reactive(source);
1827
- const target = reactive({});
1828
- const keyEffects = new Map();
1829
- function disposeKey(key) {
1830
- const stopEffect = keyEffects.get(key);
1831
- if (stopEffect) {
1832
- stopEffect();
1833
- keyEffects.delete(key);
1834
- Reflect.deleteProperty(target, key);
1835
- }
1836
- }
1837
- const parent = getActiveProjection();
1838
- const depth = parent ? parent.depth + 1 : 0;
1839
- const cleanupKeys = effect(function projectRecordEffect({ ascend }) {
1840
- const keys = new Set();
1841
- for (const key in source)
1842
- keys.add(key);
1843
- const observed = Reflect.ownKeys(source);
1844
- for (const key of observed)
1845
- keys.add(key);
1846
- for (const key of keys) {
1847
- if (keyEffects.has(key))
1848
- continue;
1849
- ascend(() => {
1850
- const stop = effect(function projectRecordKeyEffect({ reaction }) {
1851
- if (!reaction)
1852
- setActiveProjection({ source, key, target, depth, parent });
1853
- const sourceKey = key;
1854
- const previous = untracked(() => target[key]);
1855
- const accessBase = {
1856
- key: sourceKey,
1857
- source: source,
1858
- get: () => FoolProof.get(source, sourceKey, source),
1859
- set: (value) => FoolProof.set(source, sourceKey, value, source),
1860
- old: previous,
1861
- };
1862
- defineAccessValue(accessBase);
1863
- const produced = apply(accessBase, target);
1864
- target[sourceKey] = produced;
1865
- });
1866
- setEffectName(stop, `project[${depth}]:${String(key)}`);
1867
- keyEffects.set(key, stop);
1868
- });
1869
- }
1870
- for (const key of Array.from(keyEffects.keys()))
1871
- if (!keys.has(key))
1872
- disposeKey(key);
1873
- });
1874
- return makeCleanup(target, keyEffects, () => cleanupKeys(), {
1875
- source: source,
1876
- target,
1877
- apply,
1878
- depth,
1879
- parent,
1880
- });
1881
- }
1882
- function projectMap(source, apply) {
1883
- source = reactive(source);
1884
- const rawTarget = new Map();
1885
- const target = reactive(rawTarget);
1886
- const keyEffects = new Map();
1887
- function disposeKey(key) {
1888
- const stopEffect = keyEffects.get(key);
1889
- if (stopEffect) {
1890
- stopEffect();
1891
- keyEffects.delete(key);
1892
- target.delete(key);
1893
- }
2407
+ function* makeReactiveIterator(iterator) {
2408
+ let result = iterator.next();
2409
+ while (!result.done) {
2410
+ yield reactive(result.value);
2411
+ result = iterator.next();
1894
2412
  }
1895
- const parent = getActiveProjection();
1896
- const depth = parent ? parent.depth + 1 : 0;
1897
- const cleanupKeys = effect(function projectMapEffect({ ascend }) {
1898
- const keys = new Set();
1899
- for (const key of source.keys())
1900
- keys.add(key);
1901
- for (const key of keys) {
1902
- if (keyEffects.has(key))
1903
- continue;
1904
- ascend(() => {
1905
- const stop = effect(function projectMapKeyEffect({ reaction }) {
1906
- if (!reaction)
1907
- setActiveProjection({ source, key, target, depth, parent });
1908
- const previous = untracked(() => target.get(key));
1909
- const accessBase = {
1910
- key,
1911
- source: source,
1912
- get: () => source.get(key),
1913
- set: (value) => {
1914
- source.set(key, value);
1915
- return true;
1916
- },
1917
- old: previous,
1918
- };
1919
- defineAccessValue(accessBase);
1920
- const produced = apply(accessBase, target);
1921
- target.set(key, produced);
1922
- });
1923
- setEffectName(stop, `project[${depth}]:${String(key)}`);
1924
- keyEffects.set(key, stop);
1925
- });
1926
- }
1927
- for (const key of Array.from(keyEffects.keys()))
1928
- if (!keys.has(key))
1929
- disposeKey(key);
1930
- });
1931
- return makeCleanup(target, keyEffects, () => cleanupKeys(), {
1932
- source: source,
1933
- target,
1934
- apply,
1935
- depth,
1936
- parent,
1937
- });
1938
- }
1939
- function projectCore(source, apply) {
1940
- if (Array.isArray(source))
1941
- return projectArray(source, apply);
1942
- if (source instanceof Map)
1943
- return projectMap(source, apply);
1944
- if (source instanceof Register)
1945
- return projectRegister(source, apply);
1946
- if (source && (source.constructor === Object || source.constructor === undefined))
1947
- return projectRecord(source, apply);
1948
- throw new Error('Unsupported source type');
1949
- }
1950
- const project = Object.assign(projectCore, {
1951
- array: projectArray,
1952
- register: projectRegister,
1953
- record: projectRecord,
1954
- map: projectMap,
1955
- });
1956
-
1957
- /**
1958
- * Organizes a source object's properties into a target object using a callback function.
1959
- * This creates a reactive mapping between source properties and a target object,
1960
- * automatically handling property additions, updates, and removals.
1961
- *
1962
- * @template Source - The type of the source object
1963
- * @template Target - The type of the target object (defaults to Record<PropertyKey, any>)
1964
- *
1965
- * @param {Source} source - The source object to organize
1966
- * @param {OrganizedCallback<Source, Target>} apply - Callback function that defines how each source property is mapped to the target
1967
- * @param {Target} [baseTarget={}] - Optional base target object to use (will be made reactive if not already)
1968
- *
1969
- * @returns {OrganizedResult<Target>} The target object with cleanup capability
1970
- *
1971
- * @example
1972
- * // Organize user permissions into role-based access
1973
- * const user = reactive({ isAdmin: true, canEdit: false });
1974
- * const permissions = organized(
1975
- * user,
1976
- * (access, target) => {
1977
- * if (access.key === 'isAdmin') {
1978
- * target.hasFullAccess = access.value;
1979
- * }
1980
- * target[`can${access.key.charAt(0).toUpperCase() + access.key.slice(1)}`] = access.value;
1981
- * }
1982
- * );
1983
- *
1984
- * @example
1985
- * // Transform object structure with cleanup
1986
- * const source = reactive({ firstName: 'John', lastName: 'Doe' });
1987
- * const formatted = organized(
1988
- * source,
1989
- * (access, target) => {
1990
- * if (access.key === 'firstName' || access.key === 'lastName') {
1991
- * target.fullName = `${source.firstName} ${source.lastName}`.trim();
1992
- * }
1993
- * }
1994
- * );
1995
- *
1996
- * @example
1997
- * // Using with cleanup in a component
1998
- * effect(() => {
1999
- * const data = fetchData();
2000
- * const organizedData = organized(data, (access, target) => {
2001
- * // Transform data
2002
- * });
2003
- *
2004
- * // The cleanup will be called automatically when the effect is disposed
2005
- * return () => organizedData[cleanup]();
2006
- * });
2007
- */
2008
- function organized(source, apply, baseTarget = {}) {
2009
- const observedSource = reactive(source);
2010
- const target = reactive(baseTarget);
2011
- const stop = attend(() => {
2012
- const keys = [];
2013
- for (const key in observedSource)
2014
- keys.push(key);
2015
- return keys;
2016
- }, (key) => {
2017
- const sourceKey = key;
2018
- const accessBase = {
2019
- key: sourceKey,
2020
- get: () => FoolProof.get(observedSource, sourceKey, observedSource),
2021
- set: (value) => FoolProof.set(observedSource, sourceKey, value, observedSource),
2022
- };
2023
- Object.defineProperty(accessBase, 'value', {
2024
- get: accessBase.get,
2025
- set: accessBase.set,
2026
- configurable: true,
2027
- enumerable: true,
2028
- });
2029
- return apply(accessBase, target);
2030
- });
2031
- return cleanedBy(target, stop);
2032
- }
2033
- /**
2034
- * Organizes a property on a target object
2035
- * Shortcut for defineProperty/delete with touched signal
2036
- * @param target - The target object
2037
- * @param property - The property to organize
2038
- * @param access - The access object
2039
- * @returns The property descriptor
2040
- */
2041
- function organize(target, property, access) {
2042
- Object.defineProperty(target, property, {
2043
- get: access.get,
2044
- set: access.set,
2045
- configurable: true,
2046
- enumerable: true,
2047
- });
2048
- touched1(target, { type: 'set', prop: property }, property);
2049
- return () => delete target[property];
2050
2413
  }
2051
-
2052
2414
  /**
2053
- * Reactively defines properties on a target object based on a descriptors record.
2054
- *
2055
- * It tracks the keys of the descriptors record and defines/updates/removes
2056
- * properties on the target object as they change.
2057
- *
2058
- * @param descriptors - A reactive record of property descriptors.
2059
- * @param target - The object to define properties on (defaults to a new object).
2060
- * @returns The target object with reactive property definitions.
2415
+ * Converts an iterator of key-value pairs to a generator that yields reactive key-value pairs
2061
2416
  */
2062
- function describe(descriptors, target = Object.create(null)) {
2063
- descriptors = reactive(descriptors);
2064
- const stop = attend(() => Reflect.ownKeys(descriptors), (key) => {
2065
- const desc = descriptors[key];
2066
- if (desc) {
2067
- Object.defineProperty(target, key, {
2068
- enumerable: true,
2069
- ...desc,
2070
- configurable: true,
2071
- });
2072
- touched1(target, { type: 'set', prop: key }, key);
2073
- }
2074
- return () => Reflect.deleteProperty(target, key);
2075
- });
2076
- return cleanedBy(target, stop);
2417
+ function* makeReactiveEntriesIterator(iterator) {
2418
+ let result = iterator.next();
2419
+ while (!result.done) {
2420
+ const [key, value] = result.value;
2421
+ yield [reactive(key), reactive(value)];
2422
+ result = iterator.next();
2423
+ }
2077
2424
  }
2078
2425
 
2079
2426
  function* index(i, { length = true } = {}) {
@@ -2081,14 +2428,6 @@ function* index(i, { length = true } = {}) {
2081
2428
  yield 'length';
2082
2429
  yield i;
2083
2430
  }
2084
- function* range(a, b, { length = false } = {}) {
2085
- const start = Math.min(a, b);
2086
- const end = Math.max(a, b);
2087
- if (length)
2088
- yield 'length';
2089
- for (let i = start; i <= end; i++)
2090
- yield i;
2091
- }
2092
2431
  class Indexer extends Array {
2093
2432
  get(i) {
2094
2433
  dependant(this, i);
@@ -2103,20 +2442,28 @@ class Indexer extends Array {
2103
2442
  }
2104
2443
  }
2105
2444
  const indexLess = { get: FoolProof.get, set: FoolProof.set };
2445
+ // Fast numeric-string check: first char is a digit (0-9)
2446
+ function asIndex(prop) {
2447
+ const c = prop.charCodeAt(0);
2448
+ if (c < 48 || c > 57)
2449
+ return -1; // not 0-9
2450
+ const n = +prop; // coerce — faster than parseInt, handles "0", "12", etc.
2451
+ return n === (n | 0) && n >= 0 ? n : -1;
2452
+ }
2106
2453
  Object.assign(FoolProof, {
2107
2454
  get(obj, prop, receiver) {
2108
- if (obj instanceof Array && typeof prop === 'string') {
2109
- const index = parseInt(prop);
2110
- if (!Number.isNaN(index))
2111
- return Indexer.prototype.get.call(obj, index);
2455
+ if (Array.isArray(obj) && typeof prop === 'string') {
2456
+ const i = asIndex(prop);
2457
+ if (i >= 0)
2458
+ return Indexer.prototype.get.call(obj, i);
2112
2459
  }
2113
2460
  return indexLess.get(obj, prop, receiver);
2114
2461
  },
2115
2462
  set(obj, prop, value, receiver) {
2116
- if (obj instanceof Array && typeof prop === 'string') {
2117
- const index = parseInt(prop);
2118
- if (!Number.isNaN(index))
2119
- return Indexer.prototype.set.call(obj, index, value);
2463
+ if (Array.isArray(obj) && typeof prop === 'string') {
2464
+ const i = asIndex(prop);
2465
+ if (i >= 0)
2466
+ return Indexer.prototype.set.call(obj, i, value);
2120
2467
  }
2121
2468
  return indexLess.set(obj, prop, value, receiver);
2122
2469
  },
@@ -2125,374 +2472,186 @@ class ReactiveArray extends Array {
2125
2472
  toJSON() {
2126
2473
  return this;
2127
2474
  }
2128
- get length() {
2129
- dependant(this, 'length');
2130
- return this.length;
2131
- }
2132
- set length(value) {
2133
- const oldLength = this.length;
2134
- if (oldLength === value)
2135
- return;
2136
- try {
2137
- this.length = value;
2138
- }
2139
- finally {
2140
- touched(this, { type: 'set', prop: 'length' }, range(oldLength, value, { length: true }));
2141
- }
2142
- }
2143
- // Safe array access with negative indices
2144
- at(index) {
2145
- const actualIndex = index < 0 ? this.length + index : index;
2146
- dependant(this, actualIndex);
2147
- if (index < 0)
2148
- dependant(this, 'length');
2149
- if (actualIndex < 0 || actualIndex >= this.length)
2150
- return undefined;
2151
- return reactive(this[actualIndex]);
2152
- }
2153
- // Immutable versions of mutator methods
2154
- toReversed() {
2155
- dependant(this);
2156
- return reactive(this.toReversed());
2157
- }
2158
- toSorted(compareFn) {
2159
- dependant(this);
2160
- return reactive(this.toSorted(compareFn));
2161
- }
2162
- toSpliced(start, deleteCount, ...items) {
2163
- dependant(this);
2164
- return deleteCount === undefined
2165
- ? this.toSpliced(start)
2166
- : this.toSpliced(start, deleteCount, ...items);
2167
- }
2168
- with(index, value) {
2169
- dependant(this);
2170
- return reactive(this.with(index, value));
2171
- }
2172
- // Iterator methods with reactivity tracking
2173
- entries() {
2174
- dependant(this);
2175
- return makeReactiveEntriesIterator(this.entries());
2176
- }
2177
- keys() {
2178
- dependant(this, 'length');
2179
- return this.keys();
2180
- }
2181
- values() {
2182
- dependant(this);
2183
- return makeReactiveIterator(this.values());
2184
- }
2185
- [Symbol.iterator]() {
2186
- dependant(this);
2187
- const nativeIterator = this[Symbol.iterator]();
2188
- return {
2189
- next() {
2190
- const result = nativeIterator.next();
2191
- if (result.done) {
2192
- return result;
2193
- }
2194
- return { value: reactive(result.value), done: false };
2195
- },
2196
- [Symbol.iterator]() {
2197
- return this;
2198
- },
2199
- [Symbol.dispose]() { },
2200
- };
2201
- }
2202
- indexOf(searchElement, fromIndex) {
2203
- const length = this.length;
2204
- let i = fromIndex === undefined ? 0 : fromIndex;
2205
- if (i < 0)
2206
- i = Math.max(length + i, 0);
2207
- const unwrappedSearch = unwrap(searchElement);
2208
- for (; i < length; i++) {
2209
- dependant(this, i);
2210
- const item = this[i];
2211
- if (item === searchElement || item === unwrappedSearch || unwrap(item) === unwrappedSearch) {
2212
- return i;
2475
+ }
2476
+ /**
2477
+ * This is a wrapper class for Array that adds reactive behavior.
2478
+ * It extends Array and overrides methods to add reactive behavior, while making sure that the internal representation is not reactive.
2479
+ */
2480
+ let ReactiveArrayWrapper = (() => {
2481
+ var _a;
2482
+ let _classSuper = Array;
2483
+ let _instanceExtraInitializers = [];
2484
+ let _fill_decorators;
2485
+ let _pop_decorators;
2486
+ let _push_decorators;
2487
+ let _reverse_decorators;
2488
+ let _shift_decorators;
2489
+ let _sort_decorators;
2490
+ let _splice_decorators;
2491
+ let _unshift_decorators;
2492
+ return _a = class ReactiveArrayWrapper extends _classSuper {
2493
+ at(index) {
2494
+ return reactive(super.at(index));
2213
2495
  }
2214
- }
2215
- dependant(this, 'length');
2216
- return -1;
2217
- }
2218
- lastIndexOf(searchElement, fromIndex) {
2219
- const length = this.length;
2220
- let i = fromIndex === undefined ? length - 1 : fromIndex;
2221
- if (i >= length)
2222
- i = length - 1;
2223
- if (i < 0)
2224
- i = Math.max(length + i, -1); // -1 ensures loop condition i >= 0 works correctly
2225
- const unwrappedSearch = unwrap(searchElement);
2226
- for (; i >= 0; i--) {
2227
- dependant(this, i);
2228
- const item = this[i];
2229
- if (item === searchElement || item === unwrappedSearch || unwrap(item) === unwrappedSearch) {
2230
- return i;
2231
- }
2232
- }
2233
- // If we scanned the whole relevant part and didn't find it, we depend on length
2234
- // (because adding elements might shift indices or add the element)
2235
- // Actually for lastIndexOf, if we start from end, length dependency is implicit in the start index calculation?
2236
- // But if we return -1, it means we didn't find it.
2237
- // If we push an element, should lastIndexOf update?
2238
- // Yes, if the new element is the one we are looking for.
2239
- dependant(this, 'length');
2240
- return -1;
2241
- }
2242
- includes(searchElement, fromIndex) {
2243
- return this.indexOf(searchElement, fromIndex) !== -1;
2244
- }
2245
- find(predicateOrElement, thisArg) {
2246
- if (typeof predicateOrElement === 'function') {
2247
- const predicate = predicateOrElement;
2248
- const length = this.length;
2249
- for (let i = 0; i < length; i++) {
2250
- dependant(this, i);
2251
- const val = reactive(this[i]);
2252
- if (predicate.call(thisArg, val, i, this)) {
2253
- return val;
2254
- }
2496
+ concat(...items) {
2497
+ return reactive(super.concat(...items.map(unwrap)));
2498
+ }
2499
+ entries() {
2500
+ dependant(this, keysOf);
2501
+ return makeReactiveEntriesIterator(super.entries());
2502
+ }
2503
+ every(predicate, thisArg) {
2504
+ return super.every((v, i, a) => predicate.call(thisArg, reactive(v), i, a), thisArg);
2505
+ }
2506
+ fill(value, start, end) {
2507
+ return super.fill(unwrap(value), start, end);
2508
+ }
2509
+ filter(predicate, thisArg) {
2510
+ return reactive(super.filter((v, i, a) => predicate.call(thisArg, reactive(v), i, a), thisArg));
2511
+ }
2512
+ find(predicate, thisArg) {
2513
+ return reactive(super.find((v, i, a) => predicate.call(thisArg, reactive(v), i, a), thisArg));
2514
+ }
2515
+ findIndex(predicate, thisArg) {
2516
+ return super.findIndex((v, i, a) => predicate.call(thisArg, reactive(v), i, a), thisArg);
2517
+ }
2518
+ findLast(predicate, thisArg) {
2519
+ return reactive(super.findLast((v, i, a) => predicate.call(thisArg, reactive(v), i, a), thisArg));
2520
+ }
2521
+ findLastIndex(predicate, thisArg) {
2522
+ return super.findLastIndex((v, i, a) => predicate.call(thisArg, reactive(v), i, a), thisArg);
2523
+ }
2524
+ flat(depth) {
2525
+ return reactive(super.flat(depth));
2526
+ }
2527
+ flatMap(callbackfn, thisArg) {
2528
+ return reactive(super.flatMap((v, i, a) => unwrap(callbackfn.call(thisArg, reactive(v), i, a)), thisArg));
2529
+ }
2530
+ forEach(callbackfn, thisArg) {
2531
+ super.forEach((v, i, a) => {
2532
+ callbackfn.call(thisArg, reactive(v), i, a);
2533
+ }, thisArg);
2534
+ }
2535
+ includes(searchElement, fromIndex) {
2536
+ return arguments.length > 1
2537
+ ? super.includes(unwrap(searchElement), fromIndex)
2538
+ : super.includes(unwrap(searchElement));
2539
+ }
2540
+ indexOf(searchElement, fromIndex) {
2541
+ return arguments.length > 1
2542
+ ? super.indexOf(unwrap(searchElement), fromIndex)
2543
+ : super.indexOf(unwrap(searchElement));
2544
+ }
2545
+ join(separator) {
2546
+ return super.join(separator);
2547
+ }
2548
+ keys() {
2549
+ dependant(this, 'length');
2550
+ return super.keys();
2551
+ }
2552
+ lastIndexOf(searchElement, fromIndex) {
2553
+ return arguments.length > 1
2554
+ ? super.lastIndexOf(unwrap(searchElement), fromIndex)
2555
+ : super.lastIndexOf(unwrap(searchElement));
2556
+ }
2557
+ map(callbackfn, thisArg) {
2558
+ return reactive(super.map((v, i, a) => unwrap(callbackfn.call(thisArg, reactive(v), i, a)), thisArg));
2559
+ }
2560
+ pop() {
2561
+ return reactive(super.pop());
2562
+ }
2563
+ push(...items) {
2564
+ return super.push(...items.map(unwrap));
2565
+ }
2566
+ reduce(callbackfn, initialValue) {
2567
+ return reactive(arguments.length > 1
2568
+ ? super.reduce((acc, v, i, a) => unwrap(callbackfn(acc, reactive(v), i, a)), initialValue)
2569
+ : super.reduce((acc, v, i, a) => unwrap(callbackfn(acc, reactive(v), i, a))));
2570
+ }
2571
+ reduceRight(callbackfn, initialValue) {
2572
+ return reactive(arguments.length > 1
2573
+ ? super.reduceRight((acc, v, i, a) => unwrap(callbackfn(acc, reactive(v), i, a)), initialValue)
2574
+ : super.reduceRight((acc, v, i, a) => unwrap(callbackfn(acc, reactive(v), i, a))));
2575
+ }
2576
+ reverse() {
2577
+ return reactive(super.reverse());
2578
+ }
2579
+ shift() {
2580
+ return reactive(super.shift());
2581
+ }
2582
+ slice(start, end) {
2583
+ return reactive(super.slice(start, end));
2584
+ }
2585
+ some(predicate, thisArg) {
2586
+ return super.some((v, i, a) => predicate.call(thisArg, reactive(v), i, a), thisArg);
2587
+ }
2588
+ sort(compareFn) {
2589
+ const wrappedCompare = compareFn
2590
+ ? (a, b) => compareFn(reactive(a), reactive(b))
2591
+ : undefined;
2592
+ return super.sort(wrappedCompare);
2255
2593
  }
2256
- dependant(this, 'length');
2257
- return undefined;
2258
- }
2259
- const fromIndex = typeof thisArg === 'number' ? thisArg : undefined;
2260
- const index = this.indexOf(predicateOrElement, fromIndex);
2261
- if (index === -1)
2262
- return undefined;
2263
- return reactive(this[index]);
2264
- }
2265
- findIndex(predicateOrElement, thisArg) {
2266
- if (typeof predicateOrElement === 'function') {
2267
- const predicate = predicateOrElement;
2268
- const length = this.length;
2269
- for (let i = 0; i < length; i++) {
2270
- dependant(this, i);
2271
- const val = reactive(this[i]);
2272
- if (predicate.call(thisArg, val, i, this)) {
2273
- return i;
2274
- }
2594
+ splice(start, deleteCount, ...items) {
2595
+ if (arguments.length > 2)
2596
+ return reactive(super.splice(start, deleteCount, ...items.map(unwrap)));
2597
+ if (arguments.length === 2)
2598
+ return reactive(super.splice(start, deleteCount));
2599
+ if (arguments.length === 1)
2600
+ return reactive(super.splice(start));
2601
+ return reactive([]);
2275
2602
  }
2276
- dependant(this, 'length');
2277
- return -1;
2278
- }
2279
- const fromIndex = typeof thisArg === 'number' ? thisArg : undefined;
2280
- return this.indexOf(predicateOrElement, fromIndex);
2281
- }
2282
- flat(depth) {
2283
- dependant(this);
2284
- return reactive(depth === undefined ? this.flat() : this.flat(depth));
2285
- }
2286
- flatMap(callbackfn, thisArg) {
2287
- dependant(this);
2288
- return reactive(this.flatMap((item, index, array) => callbackfn.call(thisArg, reactive(item), index, array), thisArg));
2289
- }
2290
- filter(callbackfn, thisArg) {
2291
- dependant(this);
2292
- return reactive(this.filter((item, index, array) => callbackfn(reactive(item), index, array), thisArg));
2293
- }
2294
- map(callbackfn, thisArg) {
2295
- dependant(this);
2296
- return reactive(this.map((item, index, array) => callbackfn(reactive(item), index, array), thisArg));
2297
- }
2298
- reduce(callbackfn, initialValue) {
2299
- dependant(this);
2300
- const result = initialValue === undefined
2301
- ? this.reduce(callbackfn)
2302
- : this.reduce(callbackfn, initialValue);
2303
- return reactive(result);
2304
- }
2305
- reduceRight(callbackfn, initialValue) {
2306
- dependant(this);
2307
- const result = initialValue !== undefined
2308
- ? this.reduceRight(callbackfn, initialValue)
2309
- : this.reduceRight(callbackfn);
2310
- return reactive(result);
2311
- }
2312
- slice(start, end) {
2313
- for (const i of range(start || 0, end || this.length - 1))
2314
- dependant(this, i);
2315
- return start === undefined
2316
- ? this.slice()
2317
- : end === undefined
2318
- ? this.slice(start)
2319
- : this.slice(start, end);
2320
- }
2321
- concat(...items) {
2322
- dependant(this);
2323
- return reactive(this.concat(...items));
2324
- }
2325
- join(separator) {
2326
- dependant(this);
2327
- return this.join(separator);
2328
- }
2329
- forEach(callbackfn, thisArg) {
2330
- dependant(this);
2331
- this.forEach((value, index, array) => {
2332
- callbackfn.call(thisArg, reactive(value), index, array);
2333
- });
2334
- }
2335
- every(callbackfn, thisArg) {
2336
- const length = this.length;
2337
- for (let i = 0; i < length; i++) {
2338
- dependant(this, i);
2339
- if (!callbackfn.call(thisArg, reactive(this[i]), i, this)) {
2340
- return false;
2603
+ unshift(...items) {
2604
+ return super.unshift(...items.map(unwrap));
2341
2605
  }
2342
- }
2343
- dependant(this, 'length');
2344
- return true;
2345
- }
2346
- some(callbackfn, thisArg) {
2347
- const length = this.length;
2348
- for (let i = 0; i < length; i++) {
2349
- dependant(this, i);
2350
- if (callbackfn.call(thisArg, reactive(this[i]), i, this)) {
2351
- return true;
2606
+ values() {
2607
+ dependant(this, keysOf);
2608
+ return makeReactiveIterator(super.values());
2352
2609
  }
2353
- }
2354
- dependant(this, 'length');
2355
- return false;
2356
- }
2357
- // Side-effectful
2358
- push(...items) {
2359
- const oldLength = this.length;
2360
- try {
2361
- return this.push(...items);
2362
- }
2363
- finally {
2364
- touched(this, { type: 'bunch', method: 'push' }, range(oldLength, oldLength + items.length - 1, { length: true }));
2365
- }
2366
- }
2367
- pop() {
2368
- if (this.length === 0)
2369
- return undefined;
2370
- try {
2371
- return reactive(this.pop());
2372
- }
2373
- finally {
2374
- touched(this, { type: 'bunch', method: 'pop' }, index(this.length));
2375
- }
2376
- }
2377
- shift() {
2378
- if (this.length === 0)
2379
- return undefined;
2380
- try {
2381
- return reactive(this.shift());
2382
- }
2383
- finally {
2384
- touched(this, { type: 'bunch', method: 'shift' }, range(0, this.length + 1, { length: true }));
2385
- }
2386
- }
2387
- unshift(...items) {
2388
- try {
2389
- return this.unshift(...items);
2390
- }
2391
- finally {
2392
- touched(this, { type: 'bunch', method: 'unshift' }, range(0, this.length - items.length, { length: true }));
2393
- }
2394
- }
2395
- splice(start, deleteCount, ...items) {
2396
- const oldLength = this.length;
2397
- // Normalize start index
2398
- let actualStart = start;
2399
- if (actualStart < 0)
2400
- actualStart = Math.max(oldLength + actualStart, 0);
2401
- else
2402
- actualStart = Math.min(actualStart, oldLength);
2403
- // Normalize deleteCount
2404
- let actualDeleteCount = deleteCount;
2405
- if (actualDeleteCount === undefined) {
2406
- actualDeleteCount = oldLength - actualStart;
2407
- }
2408
- else {
2409
- actualDeleteCount = Math.max(0, Math.min(actualDeleteCount, oldLength - actualStart));
2410
- }
2411
- try {
2412
- if (deleteCount === undefined)
2413
- return reactive(this.splice(start));
2414
- return reactive(this.splice(start, deleteCount, ...items));
2415
- }
2416
- finally {
2417
- touched(this, { type: 'bunch', method: 'splice' }, actualDeleteCount === items.length
2418
- ? range(actualStart, actualStart + actualDeleteCount - 1)
2419
- : range(actualStart, oldLength + Math.max(items.length - actualDeleteCount, 0), {
2420
- length: true,
2421
- }));
2422
- }
2423
- }
2424
- reverse() {
2425
- try {
2426
- return this.reverse();
2427
- }
2428
- finally {
2429
- touched(this, { type: 'bunch', method: 'reverse' }, range(0, this.length - 1));
2430
- }
2431
- }
2432
- sort(compareFn) {
2433
- compareFn = compareFn || ((a, b) => a.toString().localeCompare(b.toString()));
2434
- try {
2435
- return this.sort((a, b) => compareFn(reactive(a), reactive(b)));
2436
- }
2437
- finally {
2438
- touched(this, { type: 'bunch', method: 'sort' }, range(0, this.length - 1));
2439
- }
2440
- }
2441
- fill(value, start, end) {
2442
- const len = this.length;
2443
- let k = start === undefined ? 0 : start;
2444
- if (k < 0)
2445
- k = Math.max(len + k, 0);
2446
- else
2447
- k = Math.min(k, len);
2448
- let final = end === undefined ? len : end;
2449
- if (final < 0)
2450
- final = Math.max(len + final, 0);
2451
- else
2452
- final = Math.min(final, len);
2453
- try {
2454
- if (start === undefined)
2455
- return this.fill(value);
2456
- if (end === undefined)
2457
- return this.fill(value, start);
2458
- return this.fill(value, start, end);
2459
- }
2460
- finally {
2461
- if (final > k) {
2462
- touched(this, { type: 'bunch', method: 'fill' }, range(k, final - 1));
2610
+ [(_fill_decorators = [atomic], _pop_decorators = [atomic], _push_decorators = [atomic], _reverse_decorators = [atomic], _shift_decorators = [atomic], _sort_decorators = [atomic], _splice_decorators = [atomic], _unshift_decorators = [atomic], Symbol.iterator)]() {
2611
+ dependant(this, keysOf);
2612
+ return makeReactiveIterator(super[Symbol.iterator]());
2463
2613
  }
2464
- }
2465
- }
2466
- copyWithin(target, start, end) {
2467
- try {
2468
- if (end === undefined)
2469
- return this.copyWithin(target, start);
2470
- return this.copyWithin(target, start, end);
2471
- }
2472
- finally {
2473
- const len = this.length;
2474
- let to = target;
2475
- if (to < 0)
2476
- to = Math.max(len + to, 0);
2477
- else if (to >= len)
2478
- to = len;
2479
- let from = start;
2480
- if (from < 0)
2481
- from = Math.max(len + from, 0);
2482
- else if (from >= len)
2483
- from = len;
2484
- let final = end === undefined ? len : end;
2485
- if (final < 0)
2486
- final = Math.max(len + final, 0);
2487
- else if (final >= len)
2488
- final = len;
2489
- const count = Math.min(final - from, len - to);
2490
- if (count > 0) {
2491
- touched(this, { type: 'bunch', method: 'copyWithin' }, range(to, to + count - 1));
2614
+ toReversed() {
2615
+ return reactive(super.toReversed());
2616
+ }
2617
+ toSorted(compareFn) {
2618
+ const wrappedCompare = compareFn
2619
+ ? (a, b) => compareFn(reactive(a), reactive(b))
2620
+ : undefined;
2621
+ return reactive(super.toSorted(wrappedCompare));
2622
+ }
2623
+ // biome-ignore lint/complexity/noArguments: Needed to distinguish fn() from fn(undefined)
2624
+ toSpliced(start, deleteCount, ...items) {
2625
+ if (arguments.length > 2)
2626
+ return reactive(super.toSpliced(start, deleteCount, ...items.map(unwrap)));
2627
+ if (arguments.length === 2)
2628
+ return reactive(super.toSpliced(start, deleteCount));
2629
+ if (arguments.length === 1)
2630
+ return reactive(super.toSpliced(start));
2631
+ return reactive([...this]);
2632
+ }
2633
+ with(index, value) {
2634
+ return reactive(super.with(index, unwrap(value)));
2635
+ }
2636
+ constructor() {
2637
+ super(...arguments);
2638
+ __runInitializers(this, _instanceExtraInitializers);
2492
2639
  }
2493
- }
2494
- }
2495
- }
2640
+ },
2641
+ (() => {
2642
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
2643
+ __esDecorate(_a, null, _fill_decorators, { kind: "method", name: "fill", static: false, private: false, access: { has: obj => "fill" in obj, get: obj => obj.fill }, metadata: _metadata }, null, _instanceExtraInitializers);
2644
+ __esDecorate(_a, null, _pop_decorators, { kind: "method", name: "pop", static: false, private: false, access: { has: obj => "pop" in obj, get: obj => obj.pop }, metadata: _metadata }, null, _instanceExtraInitializers);
2645
+ __esDecorate(_a, null, _push_decorators, { kind: "method", name: "push", static: false, private: false, access: { has: obj => "push" in obj, get: obj => obj.push }, metadata: _metadata }, null, _instanceExtraInitializers);
2646
+ __esDecorate(_a, null, _reverse_decorators, { kind: "method", name: "reverse", static: false, private: false, access: { has: obj => "reverse" in obj, get: obj => obj.reverse }, metadata: _metadata }, null, _instanceExtraInitializers);
2647
+ __esDecorate(_a, null, _shift_decorators, { kind: "method", name: "shift", static: false, private: false, access: { has: obj => "shift" in obj, get: obj => obj.shift }, metadata: _metadata }, null, _instanceExtraInitializers);
2648
+ __esDecorate(_a, null, _sort_decorators, { kind: "method", name: "sort", static: false, private: false, access: { has: obj => "sort" in obj, get: obj => obj.sort }, metadata: _metadata }, null, _instanceExtraInitializers);
2649
+ __esDecorate(_a, null, _splice_decorators, { kind: "method", name: "splice", static: false, private: false, access: { has: obj => "splice" in obj, get: obj => obj.splice }, metadata: _metadata }, null, _instanceExtraInitializers);
2650
+ __esDecorate(_a, null, _unshift_decorators, { kind: "method", name: "unshift", static: false, private: false, access: { has: obj => "unshift" in obj, get: obj => obj.unshift }, metadata: _metadata }, null, _instanceExtraInitializers);
2651
+ if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
2652
+ })(),
2653
+ _a;
2654
+ })();
2496
2655
 
2497
2656
  /**
2498
2657
  * Reactive wrapper around JavaScript's WeakMap class
@@ -2555,7 +2714,7 @@ class ReactiveMap extends Map {
2555
2714
  this.forEach(callbackfn, thisArg);
2556
2715
  }
2557
2716
  keys() {
2558
- dependant(contentRef(this));
2717
+ dependant(contentRef(this), keysOf);
2559
2718
  return this.keys();
2560
2719
  }
2561
2720
  values() {
@@ -2712,11 +2871,14 @@ class ReactiveSet extends Set {
2712
2871
  }
2713
2872
 
2714
2873
  // Register native collection types to use specialized reactive wrappers
2874
+ console.time('mutts-reactive-init');
2715
2875
  metaProtos.set(Array, ReactiveArray.prototype);
2716
2876
  metaProtos.set(Set, ReactiveSet.prototype);
2717
2877
  metaProtos.set(WeakSet, ReactiveWeakSet.prototype);
2718
2878
  metaProtos.set(Map, ReactiveMap.prototype);
2719
2879
  metaProtos.set(WeakMap, ReactiveWeakMap.prototype);
2880
+ wrapProtos.set(Array, ReactiveArrayWrapper.prototype);
2881
+ console.timeEnd('mutts-reactive-init');
2720
2882
  /**
2721
2883
  * Object containing internal reactive system state for debugging and profiling
2722
2884
  */
@@ -2959,7 +3121,7 @@ function throttle(delay) {
2959
3121
  });
2960
3122
  }
2961
3123
 
2962
- var version$1 = "1.0.9";
3124
+ var version$1 = "1.0.10";
2963
3125
  var pkg = {
2964
3126
  version: version$1};
2965
3127
 
@@ -2976,6 +3138,7 @@ const globalScope = (typeof globalThis !== 'undefined'
2976
3138
  if (globalScope) {
2977
3139
  // Detect the source of this instance safely across different environments
2978
3140
  let source = 'mutts/index';
3141
+ // biome-ignore lint/security/noGlobalEval: Intentional eval for cross-env import.meta detection
2979
3142
  const viteEval = eval;
2980
3143
  try {
2981
3144
  if (typeof __filename !== 'undefined')
@@ -3007,5 +3170,5 @@ if (globalScope) {
3007
3170
  globalScope[GLOBAL_MUTTS_KEY] = currentSourceInfo;
3008
3171
  }
3009
3172
 
3010
- export { ArrayReadForward as A, setAt as B, throttle as C, Destroyable as D, Eventful as E, unreactive as F, watch as G, Indexable as I, Register as R, DestructionError as a, allocated as b, allocatedValues as c, attend as d, cache as e, cached as f, callOnGC as g, chainPromise as h, debounce as i, deepWatch as j, deprecated as k, describe as l, descriptor as m, destructor as n, forwardArray as o, getActiveProjection as p, getAt as q, isCached as r, lift as s, memoize as t, organize as u, organized as v, profileInfo as w, project as x, register as y, scan as z };
3011
- //# sourceMappingURL=index-2vea86wD.esm.js.map
3173
+ export { ArrayReadForward as A, setAt as B, throttle as C, Destroyable as D, Eventful as E, unreactive as F, watch as G, when as H, Indexable as I, Register as R, DestructionError as a, allocated as b, allocatedValues as c, arrayDiff as d, attend as e, cache as f, cached as g, callOnGC as h, chainPromise as i, debounce as j, deepWatch as k, deprecated as l, descriptor as m, destructor as n, forwardArray as o, getAt as p, isCached as q, lift as r, memoize as s, morph as t, organize as u, organized as v, profileInfo as w, register as x, resource as y, scan as z };
3174
+ //# sourceMappingURL=index-CaaQQlPJ.esm.js.map