mutts 1.0.10 → 1.0.12

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 (61) hide show
  1. package/README.md +3 -3
  2. package/dist/browser.cjs +244 -3199
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.ts +2 -2
  5. package/dist/browser.dev.cjs +51 -50
  6. package/dist/browser.dev.cjs.map +1 -1
  7. package/dist/browser.dev.d.ts +2 -2
  8. package/dist/browser.dev.esm.js +2 -2
  9. package/dist/browser.esm.js +15 -13
  10. package/dist/browser.esm.js.map +1 -1
  11. package/dist/chunks/{index-CaaQQlPJ.esm.js → index-BUop6B2U.esm.js} +384 -981
  12. package/dist/chunks/index-BUop6B2U.esm.js.map +1 -0
  13. package/dist/chunks/index-yK0HVxHv.cjs +2612 -0
  14. package/dist/chunks/index-yK0HVxHv.cjs.map +1 -0
  15. package/dist/chunks/{node-nKJBk8iJ.esm.js → node-Bo7WU5S2.esm.js} +2 -2
  16. package/dist/chunks/{node-nKJBk8iJ.esm.js.map → node-Bo7WU5S2.esm.js.map} +1 -1
  17. package/dist/chunks/{async-node-3PrbVAbB.cjs → node-Dd0esp5F.cjs} +4 -4
  18. package/dist/chunks/node-Dd0esp5F.cjs.map +1 -0
  19. package/dist/chunks/{proxy-Dtg-bJ3T.cjs → proxy-BvM4yewA.cjs} +441 -342
  20. package/dist/chunks/proxy-BvM4yewA.cjs.map +1 -0
  21. package/dist/chunks/{proxy-r7lARftl.esm.js → proxy-D2C49sXH.esm.js} +401 -330
  22. package/dist/chunks/proxy-D2C49sXH.esm.js.map +1 -0
  23. package/dist/debug.cjs +17 -4
  24. package/dist/debug.cjs.map +1 -1
  25. package/dist/debug.d.ts +2 -2
  26. package/dist/debug.esm.js +17 -3
  27. package/dist/debug.esm.js.map +1 -1
  28. package/dist/index.d.ts +84 -209
  29. package/dist/mutts.umd.js +842 -1362
  30. package/dist/mutts.umd.js.map +1 -1
  31. package/dist/mutts.umd.min.js +1 -1
  32. package/dist/mutts.umd.min.js.map +1 -1
  33. package/dist/node.cjs +51 -49
  34. package/dist/node.cjs.map +1 -1
  35. package/dist/node.d.ts +2 -2
  36. package/dist/node.dev.cjs +51 -49
  37. package/dist/node.dev.cjs.map +1 -1
  38. package/dist/node.dev.d.ts +2 -2
  39. package/dist/node.dev.esm.js +3 -3
  40. package/dist/node.esm.js +3 -3
  41. package/dist/{types-W5vD6m2n.d.ts → types-Bx2PhORg.d.ts} +38 -47
  42. package/docs/ai/api-reference.md +0 -14
  43. package/docs/ai/manual.md +2 -22
  44. package/docs/reactive/advanced.md +13 -14
  45. package/docs/reactive/attend.md +1 -2
  46. package/docs/reactive/collections.md +2 -149
  47. package/docs/reactive/core.md +218 -96
  48. package/docs/reactive/debugging.md +2 -2
  49. package/docs/reactive/resource.md +1 -1
  50. package/docs/reactive.md +1 -3
  51. package/docs/zone.md +1 -1
  52. package/package.json +18 -9
  53. package/dist/chunks/async-browser-BU_IfxYD.cjs +0 -216
  54. package/dist/chunks/async-browser-BU_IfxYD.cjs.map +0 -1
  55. package/dist/chunks/async-core-CRLKP3l-.cjs +0 -29
  56. package/dist/chunks/async-core-CRLKP3l-.cjs.map +0 -1
  57. package/dist/chunks/async-node-3PrbVAbB.cjs.map +0 -1
  58. package/dist/chunks/index-CaaQQlPJ.esm.js.map +0 -1
  59. package/dist/chunks/proxy-Dtg-bJ3T.cjs.map +0 -1
  60. package/dist/chunks/proxy-r7lARftl.esm.js.map +0 -1
  61. package/docs/reactive/scan.md +0 -324
package/dist/mutts.umd.js CHANGED
@@ -4,6 +4,7 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Mutts = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
7
8
  // Queue for hooks registered before the environment is ready (circular dependency fix)
8
9
  const hooks$1 = new Set();
9
10
  const asyncHooks = {
@@ -64,21 +65,23 @@
64
65
  return fn.apply(this, args);
65
66
  }
66
67
  finally {
67
- if (originals$1.queueMicrotask) {
68
+ /* cf BROWSER_ASYNC_POLYFILL.md
69
+ // Note: my fear about this code: in between 2~3~4 microtask waits, some other microtasks might have started, stopped, ...
70
+ // We might be in the middle of another promise hook trying to setup the zone
71
+ // TODO We might wish to have a flag :asyncZone.acquired - like a semaphore - that we falsify here and set back when we setup the zone
72
+ // - but this might perhaps be an overkill creating more problems than it solves
73
+ if (originals.queueMicrotask) {
68
74
  // Double microtask ensures we run after the first await resumption microtask
69
- originals$1.queueMicrotask.call(globalThis, () => {
70
- originals$1.queueMicrotask.call(globalThis, () => {
71
- originals$1.queueMicrotask.call(globalThis, () => {
72
- for (let i = undoers.length - 1; i >= 0; i--)
73
- undoers[i]();
74
- });
75
- });
76
- });
77
- }
78
- else {
79
- for (let i = undoers.length - 1; i >= 0; i--)
80
- undoers[i]();
81
- }
75
+ originals.queueMicrotask.call(globalThis, () => {
76
+ originals.queueMicrotask.call(globalThis, () => {
77
+ originals.queueMicrotask.call(globalThis, () => {
78
+ for (let i = undoers.length - 1; i >= 0; i--) undoers[i]()
79
+ })
80
+ })
81
+ })
82
+ } else {
83
+ for (let i = undoers.length - 1; i >= 0; i--) undoers[i]()
84
+ }*/
82
85
  }
83
86
  };
84
87
  }
@@ -336,16 +339,16 @@
336
339
  if (hasNode && obj instanceof Node) {
337
340
  obj[prop] = value;
338
341
  return true;
339
- }
340
- if (!(obj instanceof Object) && !Reflect.has(obj, prop)) {
342
+ } /*
343
+ if (!(obj instanceof Object) && !Object.hasOwn(obj, prop)) {
341
344
  Object.defineProperty(obj, prop, {
342
345
  value,
343
346
  configurable: true,
344
347
  writable: true,
345
348
  enumerable: true,
346
- });
347
- return true;
348
- }
349
+ })
350
+ return true
351
+ }*/
349
352
  return Reflect.set(obj, prop, value, receiver);
350
353
  },
351
354
  };
@@ -369,15 +372,8 @@
369
372
  return a === b;
370
373
  }
371
374
  // Prototype check
372
- const protoA = Object.getPrototypeOf(a);
373
- const protoB = Object.getPrototypeOf(b);
374
- if (protoA !== protoB) {
375
- console.warn(`[deepCompare] prototype mismatch:`, {
376
- nameA: a?.constructor?.name,
377
- nameB: b?.constructor?.name,
378
- });
375
+ if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b))
379
376
  return false;
380
- }
381
377
  // Circular reference protection
382
378
  let compared = cache.get(a);
383
379
  if (compared?.has(b))
@@ -389,39 +385,21 @@
389
385
  compared.add(b);
390
386
  // Handle specific object types
391
387
  if (Array.isArray(a)) {
392
- if (!Array.isArray(b)) {
393
- console.warn(`[deepCompare] B is not an array`);
388
+ if (!Array.isArray(b) || a.length !== b.length)
394
389
  return false;
395
- }
396
- if (a.length !== b.length) {
397
- console.warn(`[deepCompare] array length mismatch:`, { lenA: a.length, lenB: b.length });
398
- return false;
399
- }
400
390
  for (let i = 0; i < a.length; i++) {
401
- if (!deepCompare(a[i], b[i], cache)) {
402
- console.warn(`[deepCompare] array element mismatch at index ${i}`);
391
+ if (!deepCompare(a[i], b[i], cache))
403
392
  return false;
404
- }
405
393
  }
406
394
  return true;
407
395
  }
408
- if (a instanceof Date) {
409
- const match = b instanceof Date && a.getTime() === b.getTime();
410
- if (!match)
411
- console.warn(`[deepCompare] Date mismatch`);
412
- return match;
413
- }
414
- if (a instanceof RegExp) {
415
- const match = b instanceof RegExp && a.toString() === b.toString();
416
- if (!match)
417
- console.warn(`[deepCompare] RegExp mismatch`);
418
- return match;
419
- }
396
+ if (a instanceof Date)
397
+ return b instanceof Date && a.getTime() === b.getTime();
398
+ if (a instanceof RegExp)
399
+ return b instanceof RegExp && a.toString() === b.toString();
420
400
  if (a instanceof Set) {
421
- if (!(b instanceof Set) || a.size !== b.size) {
422
- console.warn(`[deepCompare] Set size mismatch`);
401
+ if (!(b instanceof Set) || a.size !== b.size)
423
402
  return false;
424
- }
425
403
  for (const val of a) {
426
404
  let found = false;
427
405
  for (const bVal of b) {
@@ -430,18 +408,14 @@
430
408
  break;
431
409
  }
432
410
  }
433
- if (!found) {
434
- console.warn(`[deepCompare] missing Set element`);
411
+ if (!found)
435
412
  return false;
436
- }
437
413
  }
438
414
  return true;
439
415
  }
440
416
  if (a instanceof Map) {
441
- if (!(b instanceof Map) || a.size !== b.size) {
442
- console.warn(`[deepCompare] Map size mismatch`);
417
+ if (!(b instanceof Map) || a.size !== b.size)
443
418
  return false;
444
- }
445
419
  for (const [key, val] of a) {
446
420
  if (!b.has(key)) {
447
421
  let foundMatch = false;
@@ -451,16 +425,11 @@
451
425
  break;
452
426
  }
453
427
  }
454
- if (!foundMatch) {
455
- console.warn(`[deepCompare] missing Map key`);
428
+ if (!foundMatch)
456
429
  return false;
457
- }
458
430
  }
459
- else {
460
- if (!deepCompare(val, b.get(key), cache)) {
461
- console.warn(`[deepCompare] Map value mismatch for key`);
462
- return false;
463
- }
431
+ else if (!deepCompare(val, b.get(key), cache)) {
432
+ return false;
464
433
  }
465
434
  }
466
435
  return true;
@@ -468,29 +437,11 @@
468
437
  // Compare own properties
469
438
  const keysA = Object.keys(a);
470
439
  const keysB = Object.keys(b);
471
- if (keysA.length !== keysB.length) {
472
- console.warn(`[deepCompare] keys length mismatch:`, {
473
- lenA: keysA.length,
474
- lenB: keysB.length,
475
- keysA,
476
- keysB,
477
- a,
478
- b,
479
- });
440
+ if (keysA.length !== keysB.length)
480
441
  return false;
481
- }
482
442
  for (const key of keysA) {
483
- if (!Object.hasOwn(b, key)) {
484
- console.warn(`[deepCompare] missing key ${String(key)} in B`);
443
+ if (!Object.hasOwn(b, key) || !deepCompare(a[key], b[key], cache))
485
444
  return false;
486
- }
487
- if (!deepCompare(a[key], b[key], cache)) {
488
- console.warn(`[deepCompare] value mismatch for key ${String(key)}:`, {
489
- valA: a[key],
490
- valB: b[key],
491
- });
492
- return false;
493
- }
494
445
  }
495
446
  return true;
496
447
  }
@@ -538,6 +489,12 @@
538
489
  });
539
490
  return fn;
540
491
  }
492
+ const _mode = (typeof process !== 'undefined' && process.env?.NODE_ENV) ||
493
+ (typeof ({ url: (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('mutts.umd.js', document.baseURI).href)) }) !== 'undefined' && undefined?.MODE) ||
494
+ 'production';
495
+ const isDev = _mode === 'development';
496
+ const isProd = _mode === 'production';
497
+ const isTest = _mode === 'test';
541
498
 
542
499
  // biome-ignore-all lint/suspicious/noConfusingVoidType: We *love* voids
543
500
  // Standardized decorator system that works with both Legacy and Modern decorators
@@ -1103,9 +1060,7 @@
1103
1060
  const isObject = currentOptions !== null &&
1104
1061
  typeof currentOptions === 'object' &&
1105
1062
  !Array.isArray(currentOptions);
1106
- newArgs[targetIndex] = isObject
1107
- ? { ...defaultOptions, ...currentOptions }
1108
- : defaultOptions;
1063
+ newArgs[targetIndex] = isObject ? { ...defaultOptions, ...currentOptions } : defaultOptions;
1109
1064
  return fn.apply(this, newArgs);
1110
1065
  };
1111
1066
  if (opts.name)
@@ -1852,6 +1807,17 @@
1852
1807
  return chained;
1853
1808
  }
1854
1809
 
1810
+ const debugHooks = {
1811
+ isDevtoolsEnabled: () => false,
1812
+ registerEffect: () => { },
1813
+ getTriggerChain: () => [],
1814
+ captureStack: () => [],
1815
+ captureLineage: () => new Error().stack,
1816
+ formatStack: (stack) => [stack],
1817
+ recordTriggerLink: () => { },
1818
+ decorateError: () => { },
1819
+ };
1820
+
1855
1821
  /******************************************************************************
1856
1822
  Copyright (c) Microsoft Corporation.
1857
1823
 
@@ -1903,38 +1869,17 @@
1903
1869
  }
1904
1870
  return useValue ? value : void 0;
1905
1871
  }
1906
- function __setFunctionName(f, name, prefix) {
1907
- if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
1908
- return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
1909
- }
1910
1872
  function __classPrivateFieldGet(receiver, state, kind, f) {
1911
1873
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
1912
1874
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
1913
1875
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
1914
1876
  }
1915
1877
 
1916
- function __classPrivateFieldSet(receiver, state, value, kind, f) {
1917
- if (kind === "m") throw new TypeError("Private method is not writable");
1918
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
1919
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
1920
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
1921
- }
1922
-
1923
1878
  typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
1924
1879
  var e = new Error(message);
1925
1880
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
1926
1881
  };
1927
1882
 
1928
- const debugHooks = {
1929
- isDevtoolsEnabled: () => false,
1930
- registerEffect: () => { },
1931
- getTriggerChain: () => [],
1932
- captureStack: () => [],
1933
- captureLineage: () => new Error().stack,
1934
- formatStack: (stack) => [stack],
1935
- recordTriggerLink: () => { },
1936
- };
1937
-
1938
1883
  var _ZoneAggregator_zones;
1939
1884
  function isu(z) {
1940
1885
  return z;
@@ -2062,13 +2007,27 @@
2062
2007
  }
2063
2008
  }
2064
2009
  _ZoneAggregator_zones = new WeakMap();
2010
+ /**
2011
+ * Aggregator of zones that should be preserved across async boundaries.
2012
+ * If you add a zone here, it will be preserved across async boundaries.
2013
+ *
2014
+ * @example
2015
+ * ```ts
2016
+ * import { Zone, asyncZone } from 'mutts'
2017
+ * const userZone = new Zone<User>()
2018
+ * asyncZone.add(userZone)
2019
+ * ```
2020
+ */
2065
2021
  const asyncZone = tag('async', new ZoneAggregator());
2066
2022
  asyncHooks.addHook(() => {
2023
+ // capture state before async boundary
2067
2024
  const zone = asyncZone.active;
2068
2025
  return () => {
2026
+ // restore state after async boundary, temporarily
2069
2027
  const prev = asyncZone.active;
2070
2028
  asyncZone.active = zone;
2071
2029
  return () => {
2030
+ // restore previous state from before our restore
2072
2031
  asyncZone.active = prev;
2073
2032
  };
2074
2033
  };
@@ -2138,6 +2097,75 @@
2138
2097
  return fn;
2139
2098
  }
2140
2099
 
2100
+ const effectHistory = tag('effectHistory', new ZoneHistory());
2101
+ tag('effectHistory.present', effectHistory.present);
2102
+ asyncZone.add(effectHistory);
2103
+ /**
2104
+ * Aggregator for zones that need to be tracked along effects.
2105
+ * ie. in each effect, the active zone of the given zoning will be the one active at effect's definition
2106
+ */
2107
+ const effectAggregator = tag('effectAggregator', new ZoneAggregator(effectHistory.present));
2108
+ function isRunning(effect) {
2109
+ const root = getRoot(effect);
2110
+ return effectHistory.some((e) => getRoot(e) === root);
2111
+ }
2112
+ function getActiveEffect() {
2113
+ return effectHistory.present.active;
2114
+ }
2115
+ const cleanups = new WeakMap();
2116
+ /**
2117
+ * Attach cleanup dependencies to an object. When `unlink(obj)` is called,
2118
+ * each dependency is disposed: functions are invoked with the cleanup reason,
2119
+ * objects are recursively `unlink`ed. This forms a cleanup tree.
2120
+ *
2121
+ * @param obj - The owner object
2122
+ * @param cleanupFns - Cleanup callbacks and/or child objects to unlink recursively
2123
+ * @returns The owner object (for chaining)
2124
+ *
2125
+ * @example
2126
+ * ```ts
2127
+ * // Functions are called with CleanupReason
2128
+ * link(parent, () => console.log('disposed'))
2129
+ *
2130
+ * // Objects are recursively unlinked
2131
+ * link(parent, childA, childB)
2132
+ *
2133
+ * // Mixed
2134
+ * link(parent, childObj, () => timer.clear())
2135
+ *
2136
+ * unlink(parent) // disposes childA, childB, calls the function
2137
+ * ```
2138
+ */
2139
+ function link(obj, ...cleanupFns) {
2140
+ const set = cleanups.get(obj);
2141
+ if (!set)
2142
+ cleanups.set(obj, new Set(cleanupFns.filter(Boolean)));
2143
+ else
2144
+ for (const fn of cleanupFns)
2145
+ if (fn)
2146
+ set.add(fn);
2147
+ return obj;
2148
+ }
2149
+ /**
2150
+ * Dispose an object's cleanup dependencies. Functions are called with the
2151
+ * reason; linked objects are recursively unlinked. The cleanup set is removed
2152
+ * so calling `unlink` twice is safe (second call is a no-op).
2153
+ *
2154
+ * @param obj - The object to dispose
2155
+ * @param reason - Optional cleanup reason propagated to callbacks
2156
+ */
2157
+ function unlink(obj, reason) {
2158
+ const set = cleanups.get(obj);
2159
+ if (set) {
2160
+ cleanups.delete(obj);
2161
+ for (const fn of set)
2162
+ if (typeof fn === 'function')
2163
+ fn(reason);
2164
+ else
2165
+ unlink(fn, reason);
2166
+ }
2167
+ }
2168
+
2141
2169
  const effectMarker = {
2142
2170
  enter: 'effect:enter',
2143
2171
  leave: 'effect:leave',
@@ -2200,6 +2228,11 @@
2200
2228
  }
2201
2229
  }
2202
2230
  }
2231
+ // Track native reactivity
2232
+ /**
2233
+ * Symbol to mark class properties as non-reactive
2234
+ */
2235
+ const unreactiveProperties = Symbol('unreactive-properties');
2203
2236
  /**
2204
2237
  * Symbol representing all properties in reactive tracking
2205
2238
  */
@@ -2209,14 +2242,6 @@
2209
2242
  * Used by ownKeys proxy trap — Object.keys(), for..in, Map.keys() depend on this.
2210
2243
  */
2211
2244
  const keysOf = Symbol('keys-of');
2212
- /**
2213
- * Symbol to check if an effect is stopped
2214
- */
2215
- const stopped = Symbol('stopped');
2216
- /**
2217
- * Symbol to access effect cleanup function
2218
- */
2219
- const cleanup = Symbol('cleanup');
2220
2245
  /**
2221
2246
  * Structured error codes for machine-readable diagnosis
2222
2247
  */
@@ -2290,6 +2315,12 @@
2290
2315
  * @param runningChain - The array of effects from the detected one to the currently running one
2291
2316
  */
2292
2317
  skipRunningEffect: (_effect) => { },
2318
+ /**
2319
+ * Debug purpose: called when an effect starts executing.
2320
+ * @param effect - The effect being executed (root function)
2321
+ * @param reaction - false for initial creation, true/CleanupReason for subsequent runs
2322
+ */
2323
+ effectRun: (_effect, _reaction) => { },
2293
2324
  /**
2294
2325
  * Debug purpose: maximum effect chain (like call stack max depth)
2295
2326
  * Used to prevent infinite loops
@@ -2416,34 +2447,6 @@
2416
2447
  enableHistory: true,
2417
2448
  historySize: 50,
2418
2449
  },
2419
- /**
2420
- * Configuration for zone hooks - control which async APIs are hooked
2421
- * Each option controls whether the corresponding async API is wrapped to preserve effect context
2422
- * Only applies when asyncMode is enabled (truthy)
2423
- * @deprecated Should take all when we made sure PIXI.create, Game.create, ... are -> .root()
2424
- */
2425
- zones: {
2426
- /**
2427
- * Hook setTimeout to preserve effect context
2428
- * @default true
2429
- */
2430
- setTimeout: true,
2431
- /**
2432
- * Hook setInterval to preserve effect context
2433
- * @default true
2434
- */
2435
- setInterval: true,
2436
- /**
2437
- * Hook requestAnimationFrame (runs in untracked context when hooked)
2438
- * @default true
2439
- */
2440
- requestAnimationFrame: true,
2441
- /**
2442
- * Hook queueMicrotask to preserve effect context
2443
- * @default true
2444
- */
2445
- queueMicrotask: true,
2446
- },
2447
2450
  };
2448
2451
  function optionCall(name, ...args) {
2449
2452
  const fn = options[name];
@@ -2457,6 +2460,36 @@
2457
2460
  options.warn(`options.${name} threw`, error);
2458
2461
  }
2459
2462
  }
2463
+ /** Production preset: no introspection, heuristic cycle detection, minimal overhead */
2464
+ const prodPreset = {
2465
+ maxEffectReaction: 'throw',
2466
+ cycleHandling: 'production',
2467
+ introspection: null,
2468
+ onMemoizationDiscrepancy: undefined,
2469
+ };
2470
+ /** Development preset (default): introspection on, early cycle detection, warnings */
2471
+ const devPreset = {
2472
+ maxEffectReaction: 'warn',
2473
+ cycleHandling: 'development',
2474
+ introspection: {
2475
+ gatherReasons: { lineages: 'touch' },
2476
+ logErrors: true,
2477
+ enableHistory: true,
2478
+ historySize: 50,
2479
+ },
2480
+ onMemoizationDiscrepancy: undefined,
2481
+ };
2482
+ /** Debug preset: full diagnostics, throws on violations, rich lineage capture */
2483
+ const debugPreset = {
2484
+ maxEffectReaction: 'debug',
2485
+ cycleHandling: 'debug',
2486
+ introspection: {
2487
+ gatherReasons: { lineages: 'both' },
2488
+ logErrors: true,
2489
+ enableHistory: true,
2490
+ historySize: 200,
2491
+ },
2492
+ };
2460
2493
  // --- Proxy State (Merged from proxy-state.ts) ---
2461
2494
  const objectToProxy = new WeakMap();
2462
2495
  const proxyToObject = new WeakMap();
@@ -2476,54 +2509,28 @@
2476
2509
  return proxyToObject.has(obj);
2477
2510
  }
2478
2511
 
2479
- const effectHistory = tag('effectHistory', new ZoneHistory());
2480
- tag('effectHistory.present', effectHistory.present);
2481
- asyncZone.add(effectHistory);
2482
- /**
2483
- * Aggregator for zones that need to be tracked along effects.
2484
- * ie. in each effect, the active zone of the given zoning will be the one active at effect's definition
2485
- */
2486
- const effectAggregator = tag('effectAggregator', new ZoneAggregator(effectHistory.present));
2487
- function isRunning(effect) {
2488
- const root = getRoot(effect);
2489
- return effectHistory.some((e) => getRoot(e) === root);
2490
- }
2491
- function getActiveEffect() {
2492
- return effectHistory.present.active;
2493
- }
2494
- /**
2495
- * ADD a cleanup function to an object using the cleanup symbol.
2496
- * The cleanup function will be called when the object needs to be disposed.
2497
- *
2498
- * Note: most of the time, you don't need to use this function directly.
2499
- * The main use if for the cleanup function to be stored with the object, as GC calls the cleanup function when the *function* is garbage collected.
2500
- *
2501
- * @param obj - The object to attach the cleanup function to
2502
- * @param cleanupFn - The cleanup function to attach
2503
- * @returns The object with the cleanup function attached
2504
- */
2505
- function cleanedBy(obj, cleanupFn) {
2506
- const oldCleanup = obj[cleanup];
2507
- return Object.defineProperty(obj, cleanup, {
2508
- value: oldCleanup
2509
- ? Object.defineProperties((reason) => {
2510
- oldCleanup(reason);
2511
- cleanupFn(reason);
2512
- }, {
2513
- [stopped]: { get: () => oldCleanup[stopped] || cleanupFn[stopped] },
2514
- })
2515
- : cleanupFn,
2516
- writable: false,
2517
- enumerable: false,
2518
- configurable: true,
2519
- });
2520
- }
2521
-
2522
2512
  // Track dependency stacks per (obj, prop, effect)
2523
2513
  let dependencyStacks = new WeakMap();
2514
+ let assertUntrackedFlag = false;
2524
2515
  function resetTracking() {
2525
2516
  dependencyStacks = new WeakMap();
2526
2517
  }
2518
+ /**
2519
+ * Executes a function and throws if any reactive dependencies are tracked during execution.
2520
+ * Used to assert that code runs in an untracked context.
2521
+ */
2522
+ function assertUntracked(fn) {
2523
+ if (assertUntrackedFlag) {
2524
+ throw new Error('assertUntracked: nested calls are not supported');
2525
+ }
2526
+ assertUntrackedFlag = true;
2527
+ try {
2528
+ return fn();
2529
+ }
2530
+ finally {
2531
+ assertUntrackedFlag = false;
2532
+ }
2533
+ }
2527
2534
  function getDependencyStack(effect, obj, prop) {
2528
2535
  const objStacks = dependencyStacks.get(obj);
2529
2536
  if (!objStacks)
@@ -2536,14 +2543,17 @@
2536
2543
  * @param prop - The property name (defaults to allProps)
2537
2544
  */
2538
2545
  function dependant(obj, prop = allProps) {
2546
+ if (assertUntrackedFlag) {
2547
+ throw new Error(`Reactive dependency tracking detected in assertUntracked context: ${String(prop)} on ${obj}`);
2548
+ }
2539
2549
  obj = unwrap(obj);
2540
2550
  const currentActiveEffect = getActiveEffect();
2541
2551
  // Early return if no active effect, tracking disabled, or invalid prop
2542
2552
  if (!currentActiveEffect || (typeof prop === 'symbol' && prop !== allProps && prop !== keysOf))
2543
2553
  return;
2544
- if ('dependencyHook' in currentActiveEffect) {
2545
- // @ts-expect-error We declared it nowhere - it's okay as it's really internal and for edge-case debug purpose only
2546
- currentActiveEffect.dependencyHook(obj, prop);
2554
+ const node = getEffectNode(currentActiveEffect);
2555
+ if ('dependencyHook' in node) {
2556
+ node.dependencyHook(obj, prop);
2547
2557
  }
2548
2558
  let objectWatchers = watchers.get(obj);
2549
2559
  if (!objectWatchers) {
@@ -2680,6 +2690,7 @@
2680
2690
  // consequencesClosure: for each effect, all effects that it triggers (directly or indirectly)
2681
2691
  let causesClosure = new WeakMap();
2682
2692
  let consequencesClosure = new WeakMap();
2693
+ // Batch re-entrance depth and broken state
2683
2694
  let broken = false;
2684
2695
  /**
2685
2696
  * Gets or creates an IterableWeakSet for a closure map
@@ -3333,6 +3344,7 @@
3333
3344
  throw new Error('Activation registry already exists');
3334
3345
  optionCall('beginChain', roots);
3335
3346
  }
3347
+ // TODO: Consider this has been produced but was useless - it might be more correct ?const caller = executingStack.length > 0 ? getActiveEffect() : undefined
3336
3348
  const caller = getActiveEffect();
3337
3349
  // Optimization: If nested and NOT immediate, just join the existing batch
3338
3350
  if (!isNewBatch && !immediate) {
@@ -3486,6 +3498,22 @@
3486
3498
  };
3487
3499
  },
3488
3500
  });
3501
+ /**
3502
+ * Wraps `fn` so it runs within `effect`'s zone context when invoked later.
3503
+ *
3504
+ * Useful for deferred callbacks (event listeners, `DOMContentLoaded`, etc.)
3505
+ * that need sub-effects parented to the original effect.
3506
+ *
3507
+ * @param prev - The effect whose context should be restored, or `undefined` for root context
3508
+ * @param fn - The function to wrap
3509
+ * @returns A function with the same signature that restores the effect context before calling `fn`
3510
+ */
3511
+ function captured(prev, fn) {
3512
+ prev ?? (prev = effectHistory.active);
3513
+ return named(effectMarker.leave, (...args) => {
3514
+ return effectHistory.with(prev, () => fn(...args));
3515
+ });
3516
+ }
3489
3517
  /**
3490
3518
  * Runs `fn` atomically and **always immediately**, batching all reactive effects
3491
3519
  * triggered inside it so they fire only once after `fn` completes.
@@ -3508,19 +3536,13 @@
3508
3536
  return batch(fn, 'immediate');
3509
3537
  }
3510
3538
  const fr = new FinalizationRegistry((f) => f());
3511
- /**
3512
- * @param fn - The effect function to run - provides the cleaner
3513
- * @returns The cleanup function
3514
- */
3515
3539
  /**
3516
3540
  * Creates a reactive effect that automatically re-runs when dependencies change
3517
3541
  * @param fn - The effect function that provides dependencies and may return a cleanup function or Promise
3518
3542
  * @param options - Options for effect execution
3519
3543
  * @returns A cleanup function to stop the effect
3520
3544
  */
3521
- const effect = named(effectMarker.leave, flavored(function effect(
3522
- // biome-ignore lint/suspicious/noConfusingVoidType: Effect callbacks commonly return void
3523
- fn, effectOptions = {}) {
3545
+ const effect = named(effectMarker.leave, flavored(function effect(fn, effectOptions = {}) {
3524
3546
  if (effectOptions?.name)
3525
3547
  Object.defineProperty(fn, 'name', { value: effectOptions.name });
3526
3548
  // Use per-effect asyncMode or fall back to global option
@@ -3544,6 +3566,7 @@
3544
3566
  if (runningPromise) {
3545
3567
  if (asyncMode === 'cancel' && cancelPrevious) {
3546
3568
  // Cancel previous execution
3569
+ abort();
3547
3570
  cancelPrevious();
3548
3571
  cancelPrevious = null;
3549
3572
  runningPromise = null;
@@ -3567,11 +3590,33 @@
3567
3590
  access.reaction = node.nextReason || access.reaction;
3568
3591
  node.nextReason = undefined;
3569
3592
  optionCall('enter', getRoot(fn));
3593
+ optionCall('effectRun', getRoot(fn), access.reaction);
3570
3594
  let result;
3571
3595
  let caught = 0;
3572
- // Default thrower (self)
3573
- let thrower = (error) => {
3574
- throw error;
3596
+ // Define bubbling thrower
3597
+ const thrower = (error) => {
3598
+ const catches = node.catchers;
3599
+ const reason = { type: 'error', error };
3600
+ if (catches)
3601
+ while (caught < catches.length) {
3602
+ cleanupReaction(reason);
3603
+ try {
3604
+ reactionCleanup = catches[caught](error);
3605
+ return;
3606
+ }
3607
+ catch (_e) {
3608
+ caught++;
3609
+ }
3610
+ }
3611
+ if (parent) {
3612
+ const parentNode = getEffectNode(parent);
3613
+ if (parentNode.forwardThrow)
3614
+ parentNode.forwardThrow(error);
3615
+ else
3616
+ throw error;
3617
+ }
3618
+ else
3619
+ throw error;
3575
3620
  };
3576
3621
  node.forwardThrow = thrower;
3577
3622
  let errorToThrow;
@@ -3627,6 +3672,7 @@
3627
3672
  // Create cleanup function for next run
3628
3673
  node.cleanup = (reason) => {
3629
3674
  node.cleanup = undefined;
3675
+ abort();
3630
3676
  cleanupReaction(reason);
3631
3677
  delete node.catchers;
3632
3678
  // Remove this effect from all reactive objects it's watching
@@ -3637,13 +3683,11 @@
3637
3683
  if (objectWatchers) {
3638
3684
  for (const [prop, deps] of objectWatchers.entries()) {
3639
3685
  deps.delete(runEffect);
3640
- if (deps.size === 0) {
3686
+ if (deps.size === 0)
3641
3687
  objectWatchers.delete(prop);
3642
- }
3643
3688
  }
3644
- if (objectWatchers.size === 0) {
3689
+ if (objectWatchers.size === 0)
3645
3690
  watchers.delete(reactiveObj);
3646
- }
3647
3691
  }
3648
3692
  }
3649
3693
  effectToReactiveObjects.delete(runEffect);
@@ -3661,33 +3705,6 @@
3661
3705
  delete node.children;
3662
3706
  }
3663
3707
  };
3664
- // Define bubbling thrower
3665
- thrower = (error) => {
3666
- const catches = node.catchers;
3667
- const reason = { type: 'error', error };
3668
- if (catches)
3669
- while (caught < catches.length) {
3670
- cleanupReaction(reason);
3671
- try {
3672
- reactionCleanup = catches[caught](error);
3673
- return;
3674
- }
3675
- catch (e) {
3676
- caught++;
3677
- }
3678
- }
3679
- if (parent) {
3680
- const parentNode = getEffectNode(parent);
3681
- if (parentNode.forwardThrow)
3682
- parentNode.forwardThrow(error);
3683
- else
3684
- throw error;
3685
- }
3686
- else
3687
- throw error;
3688
- };
3689
- // Update the node's forwardThrow to the bubbling one
3690
- node.forwardThrow = thrower;
3691
3708
  if (errorToThrow)
3692
3709
  thrower(errorToThrow);
3693
3710
  };
@@ -3700,31 +3717,36 @@
3700
3717
  node.parent = parent;
3701
3718
  // let thrower: CatchFunction | undefined // Moved inside runEffect
3702
3719
  let effectStopped = false;
3720
+ let abortController;
3703
3721
  const access = {
3704
3722
  tracked,
3705
3723
  ascend: named(effectMarker.leave, (fn) => ascended(named(effectMarker.enter, () => fn.call(null)))),
3706
3724
  //named(effectMarker.enter, (fn) => ascended(fn)),
3707
3725
  reaction: false,
3726
+ get signal() {
3727
+ if (!abortController) {
3728
+ abortController = new AbortController();
3729
+ }
3730
+ return abortController.signal;
3731
+ },
3708
3732
  };
3709
3733
  let runningPromise = null;
3710
3734
  let cancelPrevious = null;
3711
- if (effectOptions?.dependencyHook) {
3735
+ if (effectOptions?.dependencyHook)
3712
3736
  node.dependencyHook = effectOptions.dependencyHook;
3713
- }
3714
3737
  // Mark the runEffect callback with the original function as its root
3715
3738
  markWithRoot(runEffect, fn);
3716
- function augmentedRv(rv) {
3717
- return Object.defineProperties(rv, {
3718
- [stopped]: {
3719
- get: () => effectStopped,
3720
- },
3721
- });
3722
- }
3723
3739
  // Register strict mode if enabled
3724
3740
  if (effectOptions?.opaque) {
3725
3741
  node.isOpaque = true;
3726
3742
  }
3727
3743
  // Store parent relationship for hierarchy traversal - ALREADY DONE ABOVE via getEffectNode
3744
+ const abort = () => {
3745
+ if (abortController) {
3746
+ abortController.abort(new ReactiveError('[reactive] Effect aborted due to dependency change or stop'));
3747
+ abortController = undefined;
3748
+ }
3749
+ };
3728
3750
  batch(runEffect, 'immediate');
3729
3751
  // Only ROOT effects are registered for GC cleanup and zone tracking
3730
3752
  const isRootEffect = !parent;
@@ -3734,6 +3756,7 @@
3734
3756
  effectStopped = true;
3735
3757
  node.stopped = true;
3736
3758
  // Cancel any running async work
3759
+ abort();
3737
3760
  if (cancelPrevious) {
3738
3761
  cancelPrevious();
3739
3762
  cancelPrevious = null;
@@ -3752,7 +3775,7 @@
3752
3775
  fr.unregister(stopEffect);
3753
3776
  };
3754
3777
  if (isRootEffect) {
3755
- const callIfCollected = augmentedRv((reason) => stopEffect(reason));
3778
+ const callIfCollected = (reason) => stopEffect(reason);
3756
3779
  fr.register(callIfCollected, () => {
3757
3780
  stopEffect({ type: 'gc' });
3758
3781
  optionCall('garbageCollected', fn);
@@ -3766,16 +3789,16 @@
3766
3789
  parentNode.children = new Set();
3767
3790
  }
3768
3791
  const children = parentNode.children;
3769
- const subEffectCleanup = augmentedRv((reason) => {
3792
+ const subEffectCleanup = (reason) => {
3770
3793
  children.delete(subEffectCleanup);
3771
3794
  // Execute this child effect cleanup (which triggers its own mainCleanup)
3772
3795
  stopEffect(reason);
3773
- });
3796
+ };
3774
3797
  children.add(subEffectCleanup);
3775
3798
  return subEffectCleanup;
3776
3799
  }
3777
3800
  // Should not be reachable given isRootEffect check, but for type safety
3778
- return augmentedRv((reason) => stopEffect(reason));
3801
+ return (reason) => stopEffect(reason);
3779
3802
  }, {
3780
3803
  get opaque() {
3781
3804
  return flavorOptions(this, { opaque: true }, { name: 'opaque' });
@@ -3806,9 +3829,11 @@
3806
3829
  get = get.get;
3807
3830
  }
3808
3831
  let programmaticallySetValue = Symbol();
3809
- effect(markWithRoot(() => {
3832
+ effect.named('biDi')(markWithRoot(() => {
3810
3833
  const newValue = get();
3811
- if (unwrap(newValue) !== programmaticallySetValue)
3834
+ const pValue = programmaticallySetValue;
3835
+ programmaticallySetValue = Symbol();
3836
+ if (unwrap(newValue) !== pValue)
3812
3837
  received(newValue);
3813
3838
  }, received));
3814
3839
  return set
@@ -3879,16 +3904,16 @@
3879
3904
  const parents = objectParents.get(changedObject);
3880
3905
  if (!parents)
3881
3906
  return;
3882
- for (const { parent, prop } of parents) {
3907
+ for (const { parent } of parents) {
3883
3908
  // Trigger deep watchers on parent
3884
3909
  const parentDeepWatchers = deepWatchers.get(parent);
3885
3910
  if (parentDeepWatchers) {
3886
3911
  if (options.introspection?.gatherReasons) {
3887
3912
  const gatherReasons = options.introspection.gatherReasons;
3888
3913
  const lineageConfig = gatherReasons.lineages;
3889
- let touchStack;
3914
+ let touchLineage;
3890
3915
  if (lineageConfig === 'touch' || lineageConfig === 'both') {
3891
- touchStack = debugHooks.captureLineage();
3916
+ touchLineage = debugHooks.captureLineage();
3892
3917
  }
3893
3918
  for (const watcher of parentDeepWatchers) {
3894
3919
  const dependencyStack = lineageConfig === 'dependency' || lineageConfig === 'both'
@@ -3901,7 +3926,7 @@
3901
3926
  obj: parent,
3902
3927
  evolution,
3903
3928
  dependency: dependencyStack,
3904
- touch: touchStack,
3929
+ touch: touchLineage,
3905
3930
  });
3906
3931
  }
3907
3932
  }
@@ -3949,11 +3974,13 @@
3949
3974
  return state;
3950
3975
  }
3951
3976
  function collectEffects(obj, evolution, effects, objectWatchers, ...keyChains) {
3952
- getActiveEffect();
3977
+ const sourceEffect = getActiveEffect();
3953
3978
  for (const keys of keyChains)
3954
3979
  for (const key of keys) {
3955
3980
  const deps = objectWatchers.get(key);
3956
3981
  if (deps) {
3982
+ // Make sure `some.prop++` does not keep a dependency to `some.props`
3983
+ deps.delete(sourceEffect);
3957
3984
  for (const effect of deps) {
3958
3985
  const runningChain = isRunning(effect);
3959
3986
  if (runningChain) {
@@ -4003,9 +4030,9 @@
4003
4030
  if (options.introspection?.gatherReasons) {
4004
4031
  const gatherReasons = options.introspection.gatherReasons;
4005
4032
  const lineageConfig = gatherReasons.lineages;
4006
- let touchStack;
4033
+ let touchLineage;
4007
4034
  if (lineageConfig === 'touch' || lineageConfig === 'both') {
4008
- touchStack = debugHooks.captureLineage();
4035
+ touchLineage = debugHooks.captureLineage();
4009
4036
  }
4010
4037
  for (const [effect, dependencyStack] of effects) {
4011
4038
  const node = getEffectNode(effect);
@@ -4015,7 +4042,7 @@
4015
4042
  obj,
4016
4043
  evolution,
4017
4044
  dependency: dependencyStack,
4018
- touch: touchStack,
4045
+ touch: touchLineage,
4019
4046
  });
4020
4047
  }
4021
4048
  }
@@ -4054,10 +4081,10 @@
4054
4081
  }
4055
4082
  effects.add(effect);
4056
4083
  if (gather) {
4057
- let touchStack;
4084
+ let touchLineage;
4058
4085
  let dependencyStack;
4059
4086
  if (lineageConfig === 'touch' || lineageConfig === 'both') {
4060
- touchStack = debugHooks.captureLineage();
4087
+ touchLineage = debugHooks.captureLineage();
4061
4088
  }
4062
4089
  if (lineageConfig === 'dependency' || lineageConfig === 'both') {
4063
4090
  dependencyStack = getDependencyStack(effect, obj, prop);
@@ -4068,7 +4095,7 @@
4068
4095
  obj,
4069
4096
  evolution,
4070
4097
  dependency: dependencyStack,
4071
- touch: touchStack,
4098
+ touch: touchLineage,
4072
4099
  });
4073
4100
  }
4074
4101
  recordActivation(effect, obj, evolution, prop);
@@ -4095,264 +4122,60 @@
4095
4122
  }
4096
4123
  }
4097
4124
 
4098
- const memoizedRegistry = new WeakMap();
4099
- const wrapperRegistry = new WeakMap();
4100
- function getBranch(tree, key) {
4101
- tree.branches ?? (tree.branches = new WeakMap());
4102
- let branch = tree.branches.get(key);
4103
- if (!branch) {
4104
- branch = {};
4105
- tree.branches.set(key, branch);
4125
+ const absent = Symbol('absent');
4126
+ /**
4127
+ * Add unreactive properties to a prototype.
4128
+ * If no set is provided, marks the entire object/prototype as non-reactive (sets [unreactiveProperties] = true).
4129
+ * If a set is provided, merges with existing unreactive properties (never overrides true).
4130
+ */
4131
+ function addUnreactiveProps(proto, set) {
4132
+ if (unreactiveProperties in proto) {
4133
+ const existing = proto[unreactiveProperties];
4134
+ // If already fully unreactive, don't change
4135
+ if (existing === true)
4136
+ return proto;
4137
+ // If no set provided, upgrade to fully unreactive
4138
+ if (!set) {
4139
+ proto[unreactiveProperties] = true;
4140
+ return proto;
4141
+ }
4142
+ // Merge sets
4143
+ set = proto[unreactiveProperties] = new Set(proto[unreactiveProperties]);
4144
+ for (const p of set)
4145
+ existing.add(p);
4146
+ }
4147
+ // If no set, mark as fully unreactive, otherwise create set
4148
+ else
4149
+ proto[unreactiveProperties] = set ? new Set(set) : true;
4150
+ return proto;
4151
+ }
4152
+ /** Check if a property is marked unreactive on obj or any of its prototypes (trap-free) */
4153
+ function isUnreactiveProp(obj, prop) {
4154
+ if (typeof prop === 'symbol' || prop === 'constructor')
4155
+ return true;
4156
+ const marker = obj[unreactiveProperties];
4157
+ return (marker === true || // Fully unreactive
4158
+ marker?.has?.(prop) || // Property is unreactive
4159
+ false);
4160
+ }
4161
+ function nonReactive(...obj) {
4162
+ for (const o of obj) {
4163
+ o[unreactiveProperties] = true;
4106
4164
  }
4107
- return branch;
4108
- }
4109
- function memoizeFunction(fn, opts) {
4110
- const fnRoot = getRoot(fn);
4111
- const existing = memoizedRegistry.get(fnRoot);
4112
- if (existing)
4113
- return existing;
4114
- const cacheRoot = {};
4115
- const memoized = markWithRoot(function memoized(...args) {
4116
- if (args.some((arg) => !(arg && ['object', 'symbol', 'function'].includes(typeof arg)))) {
4117
- if (opts?.lenient)
4118
- return fn.apply(this, args);
4119
- throw new Error('memoize expects non-null object arguments');
4120
- }
4121
- let node = cacheRoot;
4122
- // Note: decorators add `this` as first argument
4123
- for (const arg of args) {
4124
- node = getBranch(node, arg);
4125
- }
4126
- dependant(node, 'memoize');
4127
- if ('result' in node) {
4128
- if (options.onMemoizationDiscrepancy) {
4129
- const wasVerification = options.isVerificationRun;
4130
- options.isVerificationRun = true;
4131
- try {
4132
- const fresh = untracked(() => fn.apply(this, args));
4133
- if (!deepCompare(node.result, fresh)) {
4134
- optionCall('onMemoizationDiscrepancy', node.result, fresh, fn, args, 'calculation');
4135
- }
4136
- }
4137
- finally {
4138
- options.isVerificationRun = wasVerification;
4139
- }
4140
- }
4141
- return node.result;
4142
- }
4143
- // Create memoize internal effect to track dependencies and invalidate cache
4144
- // Use untracked to prevent the effect creation from being affected by parent effects
4145
- node.cleanup = root(() => effect.named('memoize')(() => {
4146
- // Execute the function and track its dependencies
4147
- // The function execution will automatically track dependencies on reactive objects
4148
- node.result = fn.apply(this, args);
4149
- return (reason) => {
4150
- // When dependencies change, clear the cache and notify consumers
4151
- delete node.result;
4152
- touched1(node, { type: 'invalidate', prop: args }, 'memoize');
4153
- // Lazy memoization: stop the effect so it doesn't re-run immediately.
4154
- // It will be re-created on next access.
4155
- if (node.cleanup) {
4156
- node.cleanup({ type: 'invalidate', cause: reason });
4157
- node.cleanup = undefined;
4158
- }
4159
- };
4160
- }, { opaque: true }));
4161
- if (options.onMemoizationDiscrepancy) {
4162
- const wasVerification = options.isVerificationRun;
4163
- options.isVerificationRun = true;
4164
- try {
4165
- const fresh = untracked(() => fn.apply(this, args));
4166
- if (!deepCompare(node.result, fresh)) {
4167
- optionCall('onMemoizationDiscrepancy', node.result, fresh, fn, args, 'comparison');
4168
- }
4169
- }
4170
- finally {
4171
- options.isVerificationRun = wasVerification;
4172
- }
4173
- }
4174
- return node.result;
4175
- }, fn);
4176
- memoizedRegistry.set(fnRoot, memoized);
4177
- memoizedRegistry.set(memoized, memoized);
4178
- return memoized;
4179
- }
4180
- function memoizeObject(target, opts) {
4181
- const existing = memoizedRegistry.get(target);
4182
- if (existing)
4183
- return existing;
4184
- const proxy = new Proxy(target, {
4185
- get(source, prop, receiver) {
4186
- // 1. Walk prototype chain to find descriptor
4187
- let current = source;
4188
- let desc;
4189
- while (current) {
4190
- desc = Object.getOwnPropertyDescriptor(current, prop);
4191
- if (desc)
4192
- break;
4193
- current = Object.getPrototypeOf(current);
4194
- }
4195
- if (!desc)
4196
- return Reflect.get(source, prop, receiver);
4197
- // 2. If getter, memoize
4198
- if (desc.get) {
4199
- const originalGetter = desc.get;
4200
- let wrapper = wrapperRegistry.get(originalGetter);
4201
- if (!wrapper) {
4202
- wrapper = markWithRoot(named(`${String(source?.constructor?.name ?? 'Object')}.${String(prop)}`, (that) => {
4203
- return originalGetter.call(that);
4204
- }), {
4205
- propertyKey: prop,
4206
- });
4207
- const origRoot = originalGetter[rootFunctionSymbol];
4208
- if (origRoot)
4209
- wrapper[rootFunctionSymbol] = origRoot;
4210
- wrapperRegistry.set(originalGetter, wrapper);
4211
- }
4212
- const memoized = memoizeFunction(wrapper, opts);
4213
- return memoized(receiver);
4214
- }
4215
- // 3. Otherwise forward
4216
- return Reflect.get(source, prop, receiver);
4217
- },
4218
- // Forward set to the target (source) to ensure it acts as the receiver for reactivity notifications
4219
- set(source, prop, value, _receiver) {
4220
- // By strictly passing `source` as receiver, we ensure that if `source` is a reactive proxy,
4221
- // it recognizes itself and triggers change notifications.
4222
- return Reflect.set(source, prop, value, source);
4223
- },
4224
- });
4225
- proxyToObject.set(proxy, target);
4226
- memoizedRegistry.set(target, proxy);
4227
- return proxy;
4228
- }
4229
- /**
4230
- * Decorator and function wrapper for memoizing computed values based on reactive dependencies.
4231
- *
4232
- * When used as a decorator on getters or methods, it caches the result and automatically
4233
- * invalidates the cache when reactive dependencies change.
4234
- *
4235
- * When used as a function wrapper, it memoizes based on object arguments (WeakMap-based cache).
4236
- *
4237
- * @example
4238
- * ```typescript
4239
- * class User {
4240
- * @memoize
4241
- * get fullName() {
4242
- * return `${this.firstName} ${this.lastName}`
4243
- * }
4244
- * }
4245
- *
4246
- * // Or as a function wrapper
4247
- * const expensive = memoize((obj: SomeObject) => {
4248
- * return heavyComputation(obj)
4249
- * })
4250
- * ```
4251
- */
4252
- function makeMemoizeDecorator(memoizeOpts) {
4253
- return decorator({
4254
- getter(original, target, propertyKey) {
4255
- return function () {
4256
- let wrapper = wrapperRegistry.get(original);
4257
- if (!wrapper) {
4258
- wrapper = markWithRoot(named(`${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(propertyKey)}`, (that) => {
4259
- return original.call(that);
4260
- }), {
4261
- method: original,
4262
- propertyKey,
4263
- });
4264
- const origRoot = original[rootFunctionSymbol];
4265
- if (origRoot)
4266
- wrapper[rootFunctionSymbol] = origRoot;
4267
- wrapperRegistry.set(original, wrapper);
4268
- }
4269
- const memoized = memoizeFunction(wrapper, memoizeOpts);
4270
- return memoized(this);
4271
- };
4272
- },
4273
- method(original, target, name) {
4274
- return function (...args) {
4275
- let wrapper = wrapperRegistry.get(original);
4276
- if (!wrapper) {
4277
- wrapper = markWithRoot(named(`${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(name)}`, (that, ...args) => {
4278
- return original.call(that, ...args);
4279
- }), {
4280
- method: original,
4281
- propertyKey: name,
4282
- });
4283
- const origRoot = original[rootFunctionSymbol];
4284
- if (origRoot)
4285
- wrapper[rootFunctionSymbol] = origRoot;
4286
- wrapperRegistry.set(original, wrapper);
4287
- }
4288
- const memoized = memoizeFunction(wrapper, memoizeOpts);
4289
- return memoized(this, ...args);
4290
- };
4291
- },
4292
- default: (target) => typeof target === 'object'
4293
- ? memoizeObject(target, memoizeOpts)
4294
- : memoizeFunction(target, memoizeOpts),
4295
- });
4296
- }
4297
- const memoize = flavored(makeMemoizeDecorator(), {
4298
- get lenient() {
4299
- return makeMemoizeDecorator({ lenient: true });
4300
- },
4301
- });
4302
-
4303
- const nonReactiveObjects = new WeakSet();
4304
- const nonReactiveClasses = new WeakSet();
4305
- const unreactiveProps = new WeakMap();
4306
- let unreactivePropsCount = 0;
4307
- function addUnreactiveProps(proto, set) {
4308
- unreactiveProps.set(proto, set);
4309
- unreactivePropsCount++;
4310
- }
4311
- /** Check if a property is marked unreactive on obj or any of its prototypes (trap-free) */
4312
- function isUnreactiveProp(obj, prop) {
4313
- if (!unreactivePropsCount)
4314
- return false;
4315
- let target = obj;
4316
- while (target) {
4317
- if (unreactiveProps.get(target)?.has(prop))
4318
- return true;
4319
- target = Object.getPrototypeOf(target);
4320
- }
4321
- return false;
4322
- }
4323
- const immutables = new Set();
4324
- const absent = Symbol('absent');
4325
- function markNonReactive(...obj) {
4326
- for (const o of obj)
4327
- nonReactiveObjects.add(o);
4328
- return obj[0];
4165
+ return obj[0];
4329
4166
  }
4330
4167
  function nonReactiveClass(...cls) {
4331
4168
  for (const c of cls)
4332
4169
  if (c)
4333
- nonReactiveClasses.add(c.prototype);
4170
+ c.prototype[unreactiveProperties] = true;
4334
4171
  return cls[0];
4335
4172
  }
4336
4173
  function isNonReactive(obj) {
4337
- if (obj === null || typeof obj !== 'object')
4338
- return true;
4339
- if (nonReactiveObjects.has(obj))
4340
- return true;
4341
- // Walk the prototype chain on the raw object to check for non-reactive classes
4342
- let proto = Object.getPrototypeOf(obj);
4343
- while (proto) {
4344
- if (nonReactiveClasses.has(proto))
4345
- return true;
4346
- proto = Object.getPrototypeOf(proto);
4347
- }
4348
- for (const fn of immutables)
4349
- if (fn(obj))
4350
- return true;
4351
- return false;
4174
+ return !obj || obj[unreactiveProperties] === true;
4352
4175
  }
4353
4176
  nonReactiveClass(Date, RegExp, Error, Promise, Function);
4354
4177
  if (typeof window !== 'undefined') {
4355
- markNonReactive(window, document);
4178
+ nonReactive(window, document);
4356
4179
  nonReactiveClass(Node, Element, HTMLElement, EventTarget, HTMLCollection, NodeList);
4357
4180
  }
4358
4181
 
@@ -4374,10 +4197,33 @@
4374
4197
  if ((typeof oldValue !== 'object' && !Array.isArray(oldValue)) ||
4375
4198
  (typeof newValue !== 'object' && !Array.isArray(newValue)))
4376
4199
  return false;
4377
- if (isNonReactive(oldValue) || isNonReactive(newValue))
4200
+ if (isNonReactive(oldValue) /*|| isNonReactive(newValue)*/)
4378
4201
  return false;
4379
4202
  return getPrototypeToken(oldValue) === getPrototypeToken(newValue);
4380
4203
  }
4204
+ /**
4205
+ * Migrate all watcher registrations from oldRef to newRef.
4206
+ * Called when deep touch replaces an object identity without any child value differences,
4207
+ * to prevent watcher orphaning (effects still pointing at the discarded old object).
4208
+ */
4209
+ function migrateWatchers(oldRef, newRef) {
4210
+ const oldMap = watchers.get(oldRef);
4211
+ if (!oldMap)
4212
+ return;
4213
+ // Move the entire watcher map
4214
+ watchers.set(newRef, oldMap);
4215
+ watchers.delete(oldRef);
4216
+ // Update the reverse map (effect → objects it watches)
4217
+ for (const deps of oldMap.values()) {
4218
+ for (const effect of deps) {
4219
+ const objects = effectToReactiveObjects.get(effect);
4220
+ if (objects) {
4221
+ objects.delete(oldRef);
4222
+ objects.add(newRef);
4223
+ }
4224
+ }
4225
+ }
4226
+ }
4381
4227
  /**
4382
4228
  * Centralized function to handle property change notifications with optional recursive touch
4383
4229
  * @param targetObj - The object whose property changed
@@ -4395,7 +4241,15 @@
4395
4241
  const origin = { obj: unwrappedObj, prop };
4396
4242
  // Deep touch: only notify nested property changes with origin filtering
4397
4243
  // Don't notify direct property change - the whole point is to avoid parent effects re-running
4398
- dispatchNotifications(untracked(() => recursiveTouch(oldValue, newValue, new WeakMap(), [], origin)));
4244
+ const changes = untracked(() => recursiveTouch(oldValue, newValue, new WeakMap(), [], origin));
4245
+ // When deep touch found no child differences, the object identity still changed.
4246
+ // Migrate watchers from old → new so the dependency chain is preserved.
4247
+ if (changes.length === 0) {
4248
+ migrateWatchers(unwrap(oldValue), unwrap(newValue));
4249
+ }
4250
+ else {
4251
+ dispatchNotifications(changes);
4252
+ }
4399
4253
  // Notify opaque listeners (like memoize) that always want to know about identity changes
4400
4254
  touchedOpaque(targetObj, evolution, prop);
4401
4255
  }
@@ -4482,12 +4336,11 @@
4482
4336
  for (const key of oldKeys)
4483
4337
  if (!newKeys.has(key))
4484
4338
  local.push({ target: oldObj, evolution: { type: 'del', prop: key }, prop: key, origin });
4485
- for (const key of newKeys)
4486
- if (!oldKeys.has(key))
4487
- local.push({ target: oldObj, evolution: { type: 'add', prop: key }, prop: key, origin });
4488
4339
  for (const key of newKeys) {
4489
- if (!oldKeys.has(key))
4340
+ if (!oldKeys.has(key)) {
4341
+ local.push({ target: oldObj, evolution: { type: 'add', prop: key }, prop: key, origin });
4490
4342
  continue;
4343
+ }
4491
4344
  const oldEntry = unwrap(oldObj[key]);
4492
4345
  const newEntry = unwrap(newObj[key]);
4493
4346
  if (shouldRecurseTouch(oldEntry, newEntry)) {
@@ -4529,11 +4382,10 @@
4529
4382
  if (originWatchers) {
4530
4383
  const originEffects = new Map();
4531
4384
  collectEffects(origin.obj, { type: 'set', prop: origin.prop }, originEffects, originWatchers, [allProps], [origin.prop]);
4532
- for (const effect of originEffects.keys())
4533
- allowedEffects.add(effect);
4385
+ allowedEffects = new Set(originEffects.keys());
4534
4386
  }
4535
4387
  // If no allowed effects, skip all notifications (no one should be notified)
4536
- if (allowedEffects.size === 0)
4388
+ if (!allowedEffects?.size)
4537
4389
  return;
4538
4390
  }
4539
4391
  for (const notification of notifications) {
@@ -4546,7 +4398,6 @@
4546
4398
  let currentEffects;
4547
4399
  const propsArray = [prop];
4548
4400
  if (objectWatchers) {
4549
- // console.log(`[DEBUG] dispatchNotifications: processing ${obj.constructor.name} (has watchers)`)
4550
4401
  currentEffects = new Map();
4551
4402
  const broad = evolution.type !== 'set' ? [allProps, keysOf] : [allProps];
4552
4403
  collectEffects(obj, evolution, currentEffects, objectWatchers, broad, propsArray);
@@ -4582,9 +4433,9 @@
4582
4433
  if (options.introspection?.gatherReasons) {
4583
4434
  const gatherReasons = options.introspection.gatherReasons;
4584
4435
  const lineageConfig = gatherReasons.lineages;
4585
- let touchStack;
4436
+ let touchLineage;
4586
4437
  if (lineageConfig === 'touch' || lineageConfig === 'both') {
4587
- touchStack = debugHooks.captureLineage();
4438
+ touchLineage = debugHooks.captureLineage();
4588
4439
  }
4589
4440
  for (const effect of combinedEffects) {
4590
4441
  const node = getEffectNode(effect);
@@ -4598,7 +4449,7 @@
4598
4449
  obj: unwrap(target),
4599
4450
  evolution,
4600
4451
  dependency: dependencyStack,
4601
- touch: touchStack,
4452
+ touch: touchLineage,
4602
4453
  });
4603
4454
  }
4604
4455
  }
@@ -4611,11 +4462,16 @@
4611
4462
  const wrapProtos = new WeakMap();
4612
4463
  const arrayLengths = new WeakMap();
4613
4464
  const hasReentry = new Set();
4614
- const subsRegister = new WeakMap();
4615
4465
  // Sub-proxy registration for custom reactive behaviors
4466
+ const subsRegister = new WeakMap();
4467
+ // Internal untracked flag for setter/getter operations - only used when testing oldValue while setting a value
4468
+ // TODO: `touched` trigger also compares to old value and should use the internalUntracked flag
4469
+ let internalUntracked = false;
4616
4470
  const reactiveHandlers = {
4617
4471
  [Symbol.toStringTag]: 'MutTs Reactive',
4618
4472
  get(obj, prop, receiver) {
4473
+ if (internalUntracked)
4474
+ return FoolProof.get(obj, prop, receiver);
4619
4475
  if (obj && typeof obj === 'object' && prop !== Symbol.toStringTag) {
4620
4476
  const metaProto = metaProtos.get(obj.constructor);
4621
4477
  if (metaProto && Object.hasOwn(metaProto, prop)) {
@@ -4635,15 +4491,21 @@
4635
4491
  if (wrapProto && Object.hasOwn(wrapProto, prop))
4636
4492
  return wrapProto[prop];
4637
4493
  }
4638
- // Symbols: fast-path — no reactivity tracking, no unreactive check needed
4639
- if (typeof prop === 'symbol')
4640
- return FoolProof.get(obj, prop, receiver);
4641
- // Check if this property is marked as unreactive (WeakMap lookup — no proxy traps)
4642
- if (isUnreactiveProp(obj, prop))
4494
+ // Symbols: fast-path — no reactivity tracking
4495
+ if (typeof prop === 'symbol' || prop === 'constructor' || isUnreactiveProp(obj, prop))
4643
4496
  return FoolProof.get(obj, prop, receiver);
4644
4497
  // Check if property exists using a trap-free walk to avoid triggering
4645
4498
  // the has-trap cascade on prototype chains of reactive proxies.
4646
4499
  const isOwnProp = Object.hasOwn(obj, prop);
4500
+ // For accessor properties, check the unwrapped object to see if it's an accessor
4501
+ // This ensures ignoreAccessors works correctly even after operations like Object.setPrototypeOf
4502
+ // Skip for null-proto objects (pounce scopes) — they never have accessors
4503
+ const shouldIgnoreAccessor = options.ignoreAccessors &&
4504
+ isOwnProp &&
4505
+ Object.getPrototypeOf(obj) !== null &&
4506
+ (isOwnAccessor(receiver, prop) || isOwnAccessor(obj, prop));
4507
+ // Check if property exists using a trap-free walk to avoid triggering
4508
+ // the has-trap cascade on prototype chains of reactive proxies.
4647
4509
  let hasProp = isOwnProp;
4648
4510
  let owner = isOwnProp ? obj : undefined;
4649
4511
  if (!isOwnProp) {
@@ -4658,29 +4520,20 @@
4658
4520
  }
4659
4521
  }
4660
4522
  const isInheritedAccess = hasProp && !isOwnProp;
4661
- // For accessor properties, check the unwrapped object to see if it's an accessor
4662
- // This ensures ignoreAccessors works correctly even after operations like Object.setPrototypeOf
4663
- // Skip for null-proto objects (pounce scopes) — they never have accessors
4664
- const shouldIgnoreAccessor = options.ignoreAccessors &&
4665
- isOwnProp &&
4666
- Object.getPrototypeOf(obj) !== null &&
4667
- (isOwnAccessor(receiver, prop) || isOwnAccessor(obj, prop));
4668
4523
  // Depend if...
4669
4524
  if (!hasProp ||
4670
4525
  (!(options.instanceMembers && isInheritedAccess && obj instanceof Object) &&
4671
4526
  !shouldIgnoreAccessor))
4672
4527
  dependant(obj, prop);
4673
- // Two-Point Tracking: for inherited access on null-proto chains, only track
4674
- // the owning ancestor not every intermediate level. This relies on the
4675
- // "structural stability" contract: key presence in the chain is fixed at
4676
- // creation time, so intermediate levels never gain/lose shadowing properties.
4528
+ // Two-Point Tracking: for inherited access on null-proto chains, also track
4529
+ // the owning ancestor so that writing directly to it triggers dependent effects.
4677
4530
  if (isInheritedAccess && owner && (!options.instanceMembers || !(obj instanceof Object))) {
4678
4531
  dependant(owner, prop);
4679
4532
  }
4680
4533
  // For arrays, use FoolProof.get (Indexer path) for numeric index reactivity.
4681
4534
  // For all other objects, inline Reflect.get directly (skips 3 function calls).
4682
4535
  const value = (subsRegister.get(obj)?.get || FoolProof.get)(obj, prop, receiver);
4683
- if (typeof value === 'object' && value !== null) {
4536
+ if (!isReactive(value) && typeof value === 'object' && value !== null) {
4684
4537
  const reactiveValue = reactiveObject(value);
4685
4538
  // Only create back-references if this object needs them
4686
4539
  if (needsBackReferences(obj)) {
@@ -4691,9 +4544,19 @@
4691
4544
  return value;
4692
4545
  },
4693
4546
  set(obj, prop, value, receiver) {
4694
- const unwrappedReceiver = unwrap(receiver);
4547
+ const unwrapped = unwrap(receiver);
4548
+ if (obj !== unwrapped)
4549
+ return Object.defineProperty(unwrapped, prop, {
4550
+ value,
4551
+ configurable: true,
4552
+ writable: true,
4553
+ enumerable: true,
4554
+ });
4555
+ if (internalUntracked)
4556
+ throw new Error('Internal untracked: setting a value in an getter in a set operation');
4557
+ //return FoolProof.set(obj, prop, value, receiver)
4695
4558
  // Check if this property is marked as unreactive
4696
- if (isUnreactiveProp(obj, prop) || obj !== unwrappedReceiver)
4559
+ if (isUnreactiveProp(obj, prop))
4697
4560
  return FoolProof.set(obj, prop, value, receiver);
4698
4561
  const newValue = unwrap(value);
4699
4562
  // metaProto setter dispatch (e.g., reactive array length)
@@ -4710,16 +4573,19 @@
4710
4573
  // Read old value, using withEffect(undefined, ...) for getter-only accessors to avoid
4711
4574
  // breaking memoization dependency tracking during SET operations
4712
4575
  let oldVal = absent;
4713
- // TODO: Pffft... Find a way to "generalize" this case?
4714
4576
  const isArrayLength = prop === 'length' && Array.isArray(obj);
4715
- if (Reflect.has(unwrappedReceiver, prop)) {
4716
- // We *need* to use `receiver` and not `obj` here, otherwise we break
4717
- // the dependency tracking for memoized getters
4718
- oldVal = isArrayLength
4719
- ? arrayLengths.get(obj) === newValue
4720
- ? newValue
4721
- : absent
4722
- : untracked(() => Reflect.get(obj, prop, receiver));
4577
+ internalUntracked = true;
4578
+ try {
4579
+ if (Reflect.has(obj, prop)) {
4580
+ oldVal = isArrayLength
4581
+ ? arrayLengths.get(obj) === newValue
4582
+ ? newValue
4583
+ : absent
4584
+ : Reflect.get(obj, prop, receiver);
4585
+ }
4586
+ }
4587
+ finally {
4588
+ internalUntracked = false;
4723
4589
  }
4724
4590
  if (objectsWithDeepWatchers.has(obj)) {
4725
4591
  if (typeof oldVal === 'object' && oldVal !== null) {
@@ -4748,7 +4614,8 @@
4748
4614
  cycle: [], // We don't have the full cycle here, but we know it involves obj
4749
4615
  });
4750
4616
  hasReentry.add(obj);
4751
- dependant(obj, prop);
4617
+ if (!internalUntracked && !isUnreactiveProp(obj, prop))
4618
+ dependant(obj, prop);
4752
4619
  const rv = (subsRegister.get(obj)?.has || Reflect.has)(obj, prop);
4753
4620
  hasReentry.delete(obj);
4754
4621
  return rv;
@@ -4774,7 +4641,8 @@
4774
4641
  return subsRegister.get(obj)?.ownKeys?.(obj) || Reflect.ownKeys(obj);
4775
4642
  },
4776
4643
  getOwnPropertyDescriptor(obj, prop) {
4777
- return subsRegister.get(obj)?.getOwnPropertyDescriptor?.(obj, prop) || Reflect.getOwnPropertyDescriptor(obj, prop);
4644
+ return (subsRegister.get(obj)?.getOwnPropertyDescriptor?.(obj, prop) ||
4645
+ Reflect.getOwnPropertyDescriptor(obj, prop));
4778
4646
  },
4779
4647
  };
4780
4648
  const reactiveClasses = new WeakSet();
@@ -4860,14 +4728,14 @@
4860
4728
  ? () => source.values()
4861
4729
  : () => Object.keys(source);
4862
4730
  const keyEffects = new Map();
4863
- const outer = effect(({ ascend }) => {
4731
+ const outer = effect.named('attend')(({ ascend }) => {
4864
4732
  const keys = new Set();
4865
4733
  for (const key of enumerate())
4866
4734
  keys.add(key);
4867
4735
  for (const key of keys) {
4868
4736
  if (keyEffects.has(key))
4869
4737
  continue;
4870
- keyEffects.set(key, ascend(() => effect(() => callback(key))));
4738
+ keyEffects.set(key, ascend(() => effect.named(`attend:${key}`)((access) => callback(key, access))));
4871
4739
  }
4872
4740
  for (const key of Array.from(keyEffects.keys())) {
4873
4741
  if (!keys.has(key)) {
@@ -4883,146 +4751,10 @@
4883
4751
  keyEffects.clear();
4884
4752
  };
4885
4753
  }
4886
- /**
4887
- * Perform a reactive scan over an array of items.
4888
- *
4889
- * This implementation is highly optimized for performance and fine-grained reactivity:
4890
- * - **Incremental Updates**: Changes to an item only trigger re-computation from that
4891
- * point onwards in the result chain.
4892
- * - **Move Optimization**: If items are moved within the array, their accumulated
4893
- * values are reused as long as their predecessor remains the same.
4894
- * - **Duplicate Support**: Correctly handles multiple occurrences of the same object
4895
- * instance using an internal occurrence tracking mechanism.
4896
- * - **Memory Efficient**: Uses `WeakMap` for caching intermediates, which are
4897
- * automatically cleared when source items are garbage collected.
4898
- *
4899
- * @example
4900
- * ```typescript
4901
- * const source = reactive([{ val: 1 }, { val: 2 }, { val: 3 }])
4902
- * const sum = scan(source, (acc, item) => acc + item.val, 0)
4903
- *
4904
- * expect([...sum]).toEqual([1, 3, 6])
4905
- *
4906
- * // Modifying an item only re-computes subsequent sums
4907
- * source[1].val = 10
4908
- * expect([...sum]).toEqual([1, 11, 14])
4909
- * ```
4910
- *
4911
- * @param source The source array of objects (will be made reactive)
4912
- * @param callback The accumulator function called with (accumulator, currentItem)
4913
- * @param initialValue The starting value for the accumulation
4914
- * @returns A reactive array of accumulated values, with a [cleanup] property to stop the tracking
4915
- */
4916
- function scan(source, callback, initialValue) {
4917
- const observedSource = reactive(source);
4918
- const rawResult = [];
4919
- const result = reactive(rawResult);
4920
- // Track effects for each index to dispose them when the array shrinks
4921
- const indexEffects = new Map();
4922
- // Mapping from index to its current intermediate object
4923
- const indexToIntermediate = reactive([]);
4924
- const intermediaries = new WeakMap();
4925
- let Intermediate = (() => {
4926
- var _a;
4927
- let _instanceExtraInitializers = [];
4928
- let _get_acc_decorators;
4929
- return _a = class Intermediate {
4930
- constructor(val, prev) {
4931
- this.val = (__runInitializers(this, _instanceExtraInitializers), val);
4932
- this.prev = prev;
4933
- }
4934
- get acc() {
4935
- const prevAcc = this.prev ? this.prev.acc : initialValue;
4936
- return callback(prevAcc, this.val);
4937
- }
4938
- },
4939
- (() => {
4940
- const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
4941
- _get_acc_decorators = [memoize];
4942
- __esDecorate(_a, null, _get_acc_decorators, { kind: "getter", name: "acc", static: false, private: false, access: { has: obj => "acc" in obj, get: obj => obj.acc }, metadata: _metadata }, null, _instanceExtraInitializers);
4943
- if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
4944
- })(),
4945
- _a;
4946
- })();
4947
- function disposeIndex(index) {
4948
- const stop = indexEffects.get(index);
4949
- if (stop) {
4950
- stop();
4951
- indexEffects.delete(index);
4952
- Reflect.deleteProperty(indexToIntermediate, index);
4953
- Reflect.deleteProperty(result, index);
4954
- }
4955
- }
4956
- const mainEffect = effect(function scanMainEffect({ ascend }) {
4957
- const length = observedSource.length;
4958
- const occurrenceCount = new Map();
4959
- let prev;
4960
- for (let i = 0; i < length; i++)
4961
- if (i in observedSource) {
4962
- const val = observedSource[i];
4963
- if (!(val &&
4964
- (typeof val === 'object' || typeof val === 'function' || typeof val === 'symbol'))) {
4965
- throw new Error('scan: items must be objects (WeakKey) for intermediate caching');
4966
- }
4967
- const count = occurrenceCount.get(val) ?? 0;
4968
- occurrenceCount.set(val, count + 1);
4969
- let list = intermediaries.get(val);
4970
- if (!list) {
4971
- list = [];
4972
- intermediaries.set(val, list);
4973
- }
4974
- let intermediate = list[count];
4975
- if (!intermediate) {
4976
- intermediate = reactive(new Intermediate(val, prev));
4977
- list[count] = intermediate;
4978
- }
4979
- else {
4980
- // Update the link.
4981
- if (untracked(() => intermediate.prev) !== prev) {
4982
- intermediate.prev = prev;
4983
- }
4984
- }
4985
- // Update the reactive mapping for this index
4986
- if (indexToIntermediate[i] !== intermediate) {
4987
- indexToIntermediate[i] = intermediate;
4988
- }
4989
- // If we don't have an effect for this index yet, create one
4990
- if (!indexEffects.has(i)) {
4991
- ascend(() => {
4992
- const index = i;
4993
- const stop = effect(function scanIndexSyncEffect() {
4994
- const inter = indexToIntermediate[index];
4995
- if (inter) {
4996
- const accValue = inter.acc;
4997
- result[index] = accValue;
4998
- }
4999
- });
5000
- indexEffects.set(index, stop);
5001
- });
5002
- }
5003
- prev = intermediate;
5004
- }
5005
- // Cleanup trailing indices
5006
- for (const index of Array.from(indexEffects.keys())) {
5007
- if (index >= length)
5008
- disposeIndex(index);
5009
- }
5010
- // Ensure result length matches source length
5011
- if (rawResult.length !== length) {
5012
- FoolProof.set(result, 'length', length, result);
5013
- }
5014
- });
5015
- return cleanedBy(result, ((reason) => {
5016
- mainEffect(reason);
5017
- for (const stop of indexEffects.values())
5018
- stop(reason);
5019
- indexEffects.clear();
5020
- }));
5021
- }
5022
4754
  function lift(cb) {
5023
4755
  let result;
5024
4756
  let rawResult;
5025
- const liftCleanup = effect.named('lift')(markWithRoot((access) => {
4757
+ const liftCleanup = effect.named(`lift:${cb.name}`)(markWithRoot((access) => {
5026
4758
  const source = cb(access);
5027
4759
  if (!source || typeof source !== 'object')
5028
4760
  throw new Error('lift callback must return an array or object');
@@ -5035,11 +4767,8 @@
5035
4767
  throw new Error('lift callback must return the same type as the previous result');
5036
4768
  if (Array.isArray(source)) {
5037
4769
  const res = result;
5038
- const patches = arrayDiff(res, source);
5039
- for (let i = patches.length - 1; i >= 0; i--) {
5040
- const { indexA, sliceA, sliceB } = patches[i];
4770
+ for (const { indexA, sliceA, sliceB } of arrayDiff(res, source).sort((a, b) => a.indexA - b.indexA))
5041
4771
  res.splice(indexA, sliceA.length, ...sliceB);
5042
- }
5043
4772
  }
5044
4773
  else {
5045
4774
  for (const key of Object.keys(source)) {
@@ -5066,7 +4795,7 @@
5066
4795
  }
5067
4796
  }
5068
4797
  }, cb));
5069
- return cleanedBy(result, liftCleanup);
4798
+ return link(result, liftCleanup);
5070
4799
  }
5071
4800
  /**
5072
4801
  * Reactively maps an array source through `fn`, producing a lazy reactive output array.
@@ -5087,7 +4816,7 @@
5087
4816
  */
5088
4817
  function morphArray(source, fn, options) {
5089
4818
  if (typeof source !== 'function' && !isReactive(source) && options?.pure === true) {
5090
- return source.map(fn);
4819
+ return source.map((i) => fn(i));
5091
4820
  }
5092
4821
  let track;
5093
4822
  const itemEffects = new Map();
@@ -5109,17 +4838,14 @@
5109
4838
  }
5110
4839
  else {
5111
4840
  const indexRef = { value: key };
5112
- let stop;
5113
- track(() => {
5114
- stop = effect.opaque(() => {
5115
- cache[indexRef.value] = fn(input);
5116
- return (reason) => {
5117
- delete cache[indexRef.value];
5118
- touched1(cache, { type: 'invalidate', prop: 'morph' }, String(key));
5119
- stop({ type: 'invalidate', cause: reason });
5120
- };
5121
- });
5122
- });
4841
+ const stop = track(() => effect.named(`morph:${fn.name}:${key}`).opaque((access) => {
4842
+ cache[indexRef.value] = fn(input, access);
4843
+ return (reason) => {
4844
+ delete cache[indexRef.value];
4845
+ touched1(cache, { type: 'invalidate', prop: 'morph' }, String(key));
4846
+ stop?.({ type: 'invalidate', cause: reason });
4847
+ };
4848
+ }));
5123
4849
  itemEffects.set(key, { stop, index: indexRef });
5124
4850
  }
5125
4851
  }
@@ -5136,7 +4862,7 @@
5136
4862
  return Reflect.has(input, prop);
5137
4863
  },
5138
4864
  });
5139
- const stopMain = effect.named('morph:array')(({ ascend }) => {
4865
+ const stopMain = effect.named(`morph:${fn.name}`)(({ ascend }) => {
5140
4866
  track = ascend;
5141
4867
  const newInput = [...(typeof source === 'function' ? source() : source)];
5142
4868
  const diffs = arrayDiff(input, newInput).toSorted((a, b) => b.indexA - a.indexA);
@@ -5151,7 +4877,7 @@
5151
4877
  // We need to move entries in the Map.
5152
4878
  const entries = Array.from(itemEffects.entries()).sort((a, b) => a[0] - b[0]);
5153
4879
  // Remove entries that will be shifted
5154
- for (const [idx, entry] of entries) {
4880
+ for (const [idx, _entry] of entries) {
5155
4881
  if (idx >= diff.indexA + diff.sliceA.length) {
5156
4882
  itemEffects.delete(idx);
5157
4883
  }
@@ -5183,7 +4909,7 @@
5183
4909
  }
5184
4910
  input = newInput;
5185
4911
  });
5186
- return cleanedBy(proxy, (reason) => {
4912
+ return link(proxy, (reason) => {
5187
4913
  stopMain(reason);
5188
4914
  for (const entry of itemEffects.values())
5189
4915
  entry.stop(reason);
@@ -5205,7 +4931,7 @@
5205
4931
  if (!isReactive(source) && options?.pure === true) {
5206
4932
  const res = new Map();
5207
4933
  for (const [k, v] of source)
5208
- res.set(k, fn(v));
4934
+ res.set(k, fn(v, k));
5209
4935
  return res;
5210
4936
  }
5211
4937
  let track;
@@ -5222,15 +4948,15 @@
5222
4948
  function computeItem(key, val) {
5223
4949
  const isPure = options?.pure === true || (typeof options?.pure === 'function' && options.pure(val));
5224
4950
  if (isPure) {
5225
- cache.set(key, track(() => fn(val)));
4951
+ cache.set(key, track(() => fn(val, key)));
5226
4952
  }
5227
4953
  else {
5228
- const stop = track(() => effect.opaque(() => {
5229
- cache.set(key, fn(source.get(key)));
4954
+ const stop = track(() => effect.named(`morph:${fn.name}:${key}`).opaque((access) => {
4955
+ cache.set(key, fn(source.get(key), key, access));
5230
4956
  return (reason) => {
5231
4957
  cache.delete(key);
5232
4958
  touched1(cache, { type: 'invalidate', prop: 'morph' }, String(key));
5233
- stop({ type: 'invalidate', cause: reason });
4959
+ stop?.({ type: 'invalidate', cause: reason });
5234
4960
  };
5235
4961
  }));
5236
4962
  itemEffects.set(key, stop);
@@ -5274,7 +5000,7 @@
5274
5000
  },
5275
5001
  });
5276
5002
  let stateSnapshot = getState(source);
5277
- const stopMain = effect.named('morph:map')(({ ascend }) => {
5003
+ const stopMain = effect.named(`morph:${fn.name}`)(({ ascend }) => {
5278
5004
  track = ascend;
5279
5005
  dependant(source, keysOf);
5280
5006
  while ('evolution' in stateSnapshot) {
@@ -5290,7 +5016,7 @@
5290
5016
  }
5291
5017
  }
5292
5018
  });
5293
- return cleanedBy(proxy, (reason) => {
5019
+ return link(proxy, (reason) => {
5294
5020
  stopMain(reason);
5295
5021
  for (const stop of itemEffects.values())
5296
5022
  stop(reason);
@@ -5312,7 +5038,7 @@
5312
5038
  if (!isReactive(source) && options?.pure === true) {
5313
5039
  const res = {};
5314
5040
  for (const k of Object.keys(source))
5315
- res[k] = fn(source[k]);
5041
+ res[k] = fn(source[k], k);
5316
5042
  return res;
5317
5043
  }
5318
5044
  let track;
@@ -5328,15 +5054,15 @@
5328
5054
  function computeItem(key, val) {
5329
5055
  const isPure = options?.pure === true || (typeof options?.pure === 'function' && options.pure(val));
5330
5056
  if (isPure) {
5331
- cache[key] = track(() => fn(val));
5057
+ cache[key] = track(() => fn(val, key));
5332
5058
  }
5333
5059
  else {
5334
- const stop = track(() => effect.opaque(() => {
5335
- cache[key] = fn(source[key]);
5060
+ const stop = track(() => effect.named(`morph:${fn.name}:${key}`).opaque((access) => {
5061
+ cache[key] = fn(source[key], key, access);
5336
5062
  return (reason) => {
5337
5063
  delete cache[key];
5338
5064
  touched1(cache, { type: 'invalidate', prop: 'morph' }, String(key));
5339
- stop({ type: 'invalidate', cause: reason });
5065
+ stop?.({ type: 'invalidate', cause: reason });
5340
5066
  };
5341
5067
  }));
5342
5068
  itemEffects.set(key, stop);
@@ -5363,7 +5089,7 @@
5363
5089
  },
5364
5090
  });
5365
5091
  let stateSnapshot = getState(source);
5366
- const stopMain = effect.named('morph:record')(({ ascend }) => {
5092
+ const stopMain = effect.named(`morph:${fn.name}`)(({ ascend }) => {
5367
5093
  track = ascend;
5368
5094
  // Track only structural changes on source
5369
5095
  dependant(source, keysOf);
@@ -5380,7 +5106,7 @@
5380
5106
  }
5381
5107
  }
5382
5108
  });
5383
- return cleanedBy(proxy, (reason) => {
5109
+ return link(proxy, (reason) => {
5384
5110
  stopMain(reason);
5385
5111
  for (const stop of itemEffects.values())
5386
5112
  stop(reason);
@@ -5439,7 +5165,7 @@
5439
5165
  const wrappedCallback = markWithRoot((() => callback(target)), callback);
5440
5166
  registerDeepWatcher();
5441
5167
  // Use the existing effect system to register dependencies
5442
- return effect(() => {
5168
+ return effect.named('deepWatch')(() => {
5443
5169
  // Mark the target object as having deep watchers
5444
5170
  objectsWithDeepWatchers.add(target);
5445
5171
  // Track which objects this effect is watching for cleanup
@@ -5475,77 +5201,271 @@
5475
5201
  }
5476
5202
  }
5477
5203
  // Also handle array indices and length
5478
- // biome-ignore lint/suspicious/useIsArray: Check for both native arrays and reactive arrays
5479
- if (Array.isArray(obj) || obj instanceof Array) {
5480
- // Access array length to register dependency on length changes
5481
- const length = obj.length;
5482
- // Access all current array elements to register dependencies
5483
- for (let i = 0; i < length; i++) {
5484
- // Access the array element to register dependency
5485
- const value = obj[i];
5204
+ // Handle arrays and collections using iterators to ensure proxy tracking is triggered
5205
+ if (typeof obj[Symbol.iterator] === 'function') {
5206
+ // Access the iterator to track additions/removals/collection changes
5207
+ for (const value of obj) {
5486
5208
  // Make the value reactive if it's an object
5487
5209
  const reactiveValue = typeof value === 'object' && value !== null ? reactive(value) : value;
5488
5210
  traverseAndTrack(reactiveValue, depth + 1);
5489
5211
  }
5212
+ // Explicitly depend on length so array mutations changing count trigger re-evaluation
5213
+ if ('length' in obj) {
5214
+ dependant(obj, 'length');
5215
+ }
5216
+ // For Maps, also ensure we track values explicitly if the iterator yields entries
5217
+ if (obj instanceof Map) {
5218
+ for (const value of obj.values()) {
5219
+ const reactiveValue = typeof value === 'object' && value !== null ? reactive(value) : value;
5220
+ traverseAndTrack(reactiveValue, depth + 1);
5221
+ }
5222
+ }
5490
5223
  }
5491
- // Handle Set values (deep watch values only, not keys since Sets don't have separate keys)
5492
- else if (obj instanceof Set) {
5493
- // Access all Set values to register dependencies
5494
- for (const value of obj) {
5495
- // Make the value reactive if it's an object
5496
- const reactiveValue = typeof value === 'object' && value !== null ? reactive(value) : value;
5497
- traverseAndTrack(reactiveValue, depth + 1);
5224
+ // Note: WeakSet and WeakMap cannot be iterated, so we can't deep watch their contents
5225
+ // They will only trigger when the collection itself is replaced
5226
+ }
5227
+ // Traverse the target object to register all dependencies
5228
+ // This will register dependencies on all current properties and array elements
5229
+ traverseAndTrack(target);
5230
+ // Only call the callback if immediate is true or if it's not the first run
5231
+ if (immediate) {
5232
+ untracked(() => callback(target));
5233
+ }
5234
+ immediate = true;
5235
+ // Return a cleanup function that properly removes deep watcher tracking
5236
+ return () => {
5237
+ // Get the objects this effect was watching
5238
+ const effectObjects = effectToDeepWatchedObjects.get(wrappedCallback);
5239
+ if (effectObjects) {
5240
+ // Remove deep watcher tracking from all objects this effect was watching
5241
+ for (const obj of effectObjects) {
5242
+ // Check if this object still has other deep watchers
5243
+ const watchers = deepWatchers.get(obj);
5244
+ if (watchers) {
5245
+ // Remove this effect's callback from the watchers
5246
+ watchers.delete(wrappedCallback);
5247
+ // If no more watchers, remove the object from deep watchers tracking
5248
+ if (watchers.size === 0) {
5249
+ deepWatchers.delete(obj);
5250
+ objectsWithDeepWatchers.delete(obj);
5251
+ }
5252
+ }
5253
+ else {
5254
+ // No watchers found, remove from deep watchers tracking
5255
+ objectsWithDeepWatchers.delete(obj);
5256
+ }
5257
+ }
5258
+ // Clean up the tracking data
5259
+ effectToDeepWatchedObjects.delete(wrappedCallback);
5260
+ }
5261
+ };
5262
+ });
5263
+ }
5264
+
5265
+ const memoizedRegistry = new WeakMap();
5266
+ const wrapperRegistry = new WeakMap();
5267
+ function getBranch(tree, key) {
5268
+ tree.branches ?? (tree.branches = new WeakMap());
5269
+ let branch = tree.branches.get(key);
5270
+ if (!branch) {
5271
+ branch = {};
5272
+ tree.branches.set(key, branch);
5273
+ }
5274
+ return branch;
5275
+ }
5276
+ function memoizeFunction(fn, opts) {
5277
+ const fnRoot = getRoot(fn);
5278
+ const existing = memoizedRegistry.get(fnRoot);
5279
+ if (existing)
5280
+ return existing;
5281
+ const cacheRoot = {};
5282
+ const memoized = markWithRoot(function memoized(...args) {
5283
+ if (args.some((arg) => !(arg && ['object', 'symbol', 'function'].includes(typeof arg)))) {
5284
+ if (opts?.lenient)
5285
+ return fn.apply(this, args);
5286
+ throw new Error('memoize expects non-null object arguments');
5287
+ }
5288
+ let node = cacheRoot;
5289
+ // Note: decorators add `this` as first argument
5290
+ for (const arg of args) {
5291
+ node = getBranch(node, arg);
5292
+ }
5293
+ dependant(node, 'memoize');
5294
+ if ('result' in node) {
5295
+ if (options.onMemoizationDiscrepancy) {
5296
+ const wasVerification = options.isVerificationRun;
5297
+ options.isVerificationRun = true;
5298
+ try {
5299
+ const fresh = untracked(() => fn.apply(this, args));
5300
+ if (!deepCompare(node.result, fresh)) {
5301
+ optionCall('onMemoizationDiscrepancy', node.result, fresh, fn, args, 'calculation');
5302
+ }
5303
+ }
5304
+ finally {
5305
+ options.isVerificationRun = wasVerification;
5306
+ }
5307
+ }
5308
+ return node.result;
5309
+ }
5310
+ // Create memoize internal effect to track dependencies and invalidate cache
5311
+ // Use untracked to prevent the effect creation from being affected by parent effects
5312
+ node.cleanup = root(() => effect.named('memoize')(() => {
5313
+ // Execute the function and track its dependencies
5314
+ // The function execution will automatically track dependencies on reactive objects
5315
+ node.result = fn.apply(this, args);
5316
+ return (reason) => {
5317
+ // When dependencies change, clear the cache and notify consumers
5318
+ delete node.result;
5319
+ touched1(node, { type: 'invalidate', prop: args }, 'memoize');
5320
+ // Lazy memoization: stop the effect so it doesn't re-run immediately.
5321
+ // It will be re-created on next access.
5322
+ if (node.cleanup) {
5323
+ node.cleanup({ type: 'invalidate', cause: reason });
5324
+ node.cleanup = undefined;
5325
+ }
5326
+ };
5327
+ }, { opaque: true }));
5328
+ if (options.onMemoizationDiscrepancy) {
5329
+ const wasVerification = options.isVerificationRun;
5330
+ options.isVerificationRun = true;
5331
+ try {
5332
+ const fresh = untracked(() => fn.apply(this, args));
5333
+ if (!deepCompare(node.result, fresh)) {
5334
+ optionCall('onMemoizationDiscrepancy', node.result, fresh, fn, args, 'comparison');
5498
5335
  }
5499
5336
  }
5500
- // Handle Map values (deep watch values only, not keys)
5501
- else if (obj instanceof Map) {
5502
- // Access all Map values to register dependencies
5503
- for (const [_key, value] of obj) {
5504
- // Make the value reactive if it's an object
5505
- const reactiveValue = typeof value === 'object' && value !== null ? reactive(value) : value;
5506
- traverseAndTrack(reactiveValue, depth + 1);
5337
+ finally {
5338
+ options.isVerificationRun = wasVerification;
5339
+ }
5340
+ }
5341
+ return node.result;
5342
+ }, fn);
5343
+ memoizedRegistry.set(fnRoot, memoized);
5344
+ memoizedRegistry.set(memoized, memoized);
5345
+ return memoized;
5346
+ }
5347
+ function memoizeObject(target, opts) {
5348
+ const existing = memoizedRegistry.get(target);
5349
+ if (existing)
5350
+ return existing;
5351
+ const proxy = new Proxy(target, {
5352
+ get(source, prop, receiver) {
5353
+ // 1. Walk prototype chain to find descriptor
5354
+ let current = source;
5355
+ let desc;
5356
+ while (current) {
5357
+ desc = Object.getOwnPropertyDescriptor(current, prop);
5358
+ if (desc)
5359
+ break;
5360
+ current = Object.getPrototypeOf(current);
5361
+ }
5362
+ if (!desc)
5363
+ return Reflect.get(source, prop, receiver);
5364
+ // 2. If getter, memoize
5365
+ if (desc.get) {
5366
+ const originalGetter = desc.get;
5367
+ let wrapper = wrapperRegistry.get(originalGetter);
5368
+ if (!wrapper) {
5369
+ wrapper = markWithRoot(named(`${String(source?.constructor?.name ?? 'Object')}.${String(prop)}`, (that) => {
5370
+ return originalGetter.call(that);
5371
+ }), {
5372
+ propertyKey: prop,
5373
+ });
5374
+ const origRoot = originalGetter[rootFunctionSymbol];
5375
+ if (origRoot)
5376
+ wrapper[rootFunctionSymbol] = origRoot;
5377
+ wrapperRegistry.set(originalGetter, wrapper);
5507
5378
  }
5379
+ const memoized = memoizeFunction(wrapper, opts);
5380
+ return memoized(receiver);
5508
5381
  }
5509
- // Note: WeakSet and WeakMap cannot be iterated, so we can't deep watch their contents
5510
- // They will only trigger when the collection itself is replaced
5511
- }
5512
- // Traverse the target object to register all dependencies
5513
- // This will register dependencies on all current properties and array elements
5514
- traverseAndTrack(target);
5515
- // Only call the callback if immediate is true or if it's not the first run
5516
- if (immediate) {
5517
- untracked(() => callback(target));
5518
- }
5519
- immediate = true;
5520
- // Return a cleanup function that properly removes deep watcher tracking
5521
- return () => {
5522
- // Get the objects this effect was watching
5523
- const effectObjects = effectToDeepWatchedObjects.get(wrappedCallback);
5524
- if (effectObjects) {
5525
- // Remove deep watcher tracking from all objects this effect was watching
5526
- for (const obj of effectObjects) {
5527
- // Check if this object still has other deep watchers
5528
- const watchers = deepWatchers.get(obj);
5529
- if (watchers) {
5530
- // Remove this effect's callback from the watchers
5531
- watchers.delete(wrappedCallback);
5532
- // If no more watchers, remove the object from deep watchers tracking
5533
- if (watchers.size === 0) {
5534
- deepWatchers.delete(obj);
5535
- objectsWithDeepWatchers.delete(obj);
5536
- }
5537
- }
5538
- else {
5539
- // No watchers found, remove from deep watchers tracking
5540
- objectsWithDeepWatchers.delete(obj);
5541
- }
5382
+ // 3. Otherwise forward
5383
+ return Reflect.get(source, prop, receiver);
5384
+ },
5385
+ // Forward set to the target (source) to ensure it acts as the receiver for reactivity notifications
5386
+ set(source, prop, value, _receiver) {
5387
+ // By strictly passing `source` as receiver, we ensure that if `source` is a reactive proxy,
5388
+ // it recognizes itself and triggers change notifications.
5389
+ return Reflect.set(source, prop, value, source);
5390
+ },
5391
+ });
5392
+ proxyToObject.set(proxy, target);
5393
+ memoizedRegistry.set(target, proxy);
5394
+ return proxy;
5395
+ }
5396
+ /**
5397
+ * Decorator and function wrapper for memoizing computed values based on reactive dependencies.
5398
+ *
5399
+ * When used as a decorator on getters or methods, it caches the result and automatically
5400
+ * invalidates the cache when reactive dependencies change.
5401
+ *
5402
+ * When used as a function wrapper, it memoizes based on object arguments (WeakMap-based cache).
5403
+ *
5404
+ * @example
5405
+ * ```typescript
5406
+ * class User {
5407
+ * @memoize
5408
+ * get fullName() {
5409
+ * return `${this.firstName} ${this.lastName}`
5410
+ * }
5411
+ * }
5412
+ *
5413
+ * // Or as a function wrapper
5414
+ * const expensive = memoize((obj: SomeObject) => {
5415
+ * return heavyComputation(obj)
5416
+ * })
5417
+ * ```
5418
+ */
5419
+ function makeMemoizeDecorator(memoizeOpts) {
5420
+ return decorator({
5421
+ getter(original, target, propertyKey) {
5422
+ return function () {
5423
+ let wrapper = wrapperRegistry.get(original);
5424
+ if (!wrapper) {
5425
+ wrapper = markWithRoot(named(`${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(propertyKey)}`, (that) => {
5426
+ return original.call(that);
5427
+ }), {
5428
+ method: original,
5429
+ propertyKey,
5430
+ });
5431
+ const origRoot = original[rootFunctionSymbol];
5432
+ if (origRoot)
5433
+ wrapper[rootFunctionSymbol] = origRoot;
5434
+ wrapperRegistry.set(original, wrapper);
5542
5435
  }
5543
- // Clean up the tracking data
5544
- effectToDeepWatchedObjects.delete(wrappedCallback);
5545
- }
5546
- };
5436
+ const memoized = memoizeFunction(wrapper, memoizeOpts);
5437
+ return memoized(this);
5438
+ };
5439
+ },
5440
+ method(original, target, name) {
5441
+ return function (...args) {
5442
+ let wrapper = wrapperRegistry.get(original);
5443
+ if (!wrapper) {
5444
+ wrapper = markWithRoot(named(`${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(name)}`, (that, ...args) => {
5445
+ return original.call(that, ...args);
5446
+ }), {
5447
+ method: original,
5448
+ propertyKey: name,
5449
+ });
5450
+ const origRoot = original[rootFunctionSymbol];
5451
+ if (origRoot)
5452
+ wrapper[rootFunctionSymbol] = origRoot;
5453
+ wrapperRegistry.set(original, wrapper);
5454
+ }
5455
+ const memoized = memoizeFunction(wrapper, memoizeOpts);
5456
+ return memoized(this, ...args);
5457
+ };
5458
+ },
5459
+ default: (target) => typeof target === 'object'
5460
+ ? memoizeObject(target, memoizeOpts)
5461
+ : memoizeFunction(target, memoizeOpts),
5547
5462
  });
5548
5463
  }
5464
+ const memoize = flavored(makeMemoizeDecorator(), {
5465
+ get lenient() {
5466
+ return makeMemoizeDecorator({ lenient: true });
5467
+ },
5468
+ });
5549
5469
 
5550
5470
  /**
5551
5471
  * Organizes a source object's properties into a target object using a callback function.
@@ -5621,7 +5541,7 @@
5621
5541
  });
5622
5542
  return apply(accessBase, target);
5623
5543
  });
5624
- return cleanedBy(target, (reason) => stop(reason));
5544
+ return link(target, (reason) => stop(reason));
5625
5545
  }
5626
5546
  /**
5627
5547
  * Organizes a property on a target object
@@ -5664,7 +5584,7 @@
5664
5584
  function watchObject(value, changed, { immediate = false, deep = false } = {}) {
5665
5585
  if (deep)
5666
5586
  return deepWatch(value, changed, { immediate });
5667
- return effect(function watchObjectEffect() {
5587
+ return effect.named('watch:object')(() => {
5668
5588
  dependant(value);
5669
5589
  if (immediate)
5670
5590
  changed(value);
@@ -5674,7 +5594,7 @@
5674
5594
  function watchCallBack(value, changed, { immediate = false, deep = false } = {}) {
5675
5595
  let oldValue = unsetYet;
5676
5596
  let deepCleanup;
5677
- const cbCleanup = effect(markWithRoot(function watchCallBackEffect(access) {
5597
+ const cbCleanup = effect.named('watch:callback')(markWithRoot((access) => {
5678
5598
  const newValue = value(access);
5679
5599
  if (oldValue !== newValue) {
5680
5600
  const old = oldValue;
@@ -5692,12 +5612,10 @@
5692
5612
  deepCleanup = deepWatch(newValue, (value) => changed(value, value));
5693
5613
  }
5694
5614
  }, value));
5695
- return Object.defineProperties(() => {
5615
+ return (() => {
5696
5616
  cbCleanup();
5697
5617
  if (deepCleanup)
5698
5618
  deepCleanup();
5699
- }, {
5700
- [stopped]: { get: () => cbCleanup[stopped] },
5701
5619
  });
5702
5620
  }
5703
5621
  //#endregion
@@ -5712,7 +5630,7 @@
5712
5630
  function when(predicate, timeout) {
5713
5631
  return new Promise((resolve, reject) => {
5714
5632
  let timer;
5715
- const stop = effect(function whenEffect(access) {
5633
+ const stop = effect.named('watch:when')((access) => {
5716
5634
  try {
5717
5635
  const value = predicate(access);
5718
5636
  if (value) {
@@ -5749,7 +5667,7 @@
5749
5667
  obj = unwrap(obj);
5750
5668
  if (isNonReactive(obj))
5751
5669
  return obj;
5752
- nonReactiveObjects.add(obj);
5670
+ obj[unreactiveProperties] = true;
5753
5671
  return obj;
5754
5672
  }
5755
5673
  function unreactiveApplication(arg1, ...args) {
@@ -5757,13 +5675,19 @@
5757
5675
  ? shallowNonReactive(arg1)
5758
5676
  : ((original) => {
5759
5677
  // Copy the parent's unreactive properties if they exist
5760
- const parentSet = unreactiveProps.get(original.prototype);
5761
- const set = new Set(parentSet || []);
5762
- // Add all arguments (including the first one)
5763
- set.add(arg1);
5764
- for (const arg of args)
5765
- set.add(arg);
5766
- addUnreactiveProps(original.prototype, set);
5678
+ const parentMarker = original.prototype[unreactiveProperties];
5679
+ // If parent is fully unreactive, child is too
5680
+ if (parentMarker === true) {
5681
+ original.prototype[unreactiveProperties] = true;
5682
+ }
5683
+ else {
5684
+ const set = new Set(parentMarker || []);
5685
+ // Add all arguments (including the first one)
5686
+ set.add(arg1);
5687
+ for (const arg of args)
5688
+ set.add(arg);
5689
+ addUnreactiveProps(original.prototype, set);
5690
+ }
5767
5691
  return original; // Return the class
5768
5692
  });
5769
5693
  }
@@ -5773,11 +5697,26 @@
5773
5697
  */
5774
5698
  const unreactive = decorator({
5775
5699
  class(original) {
5776
- // Called without arguments, mark entire class as non-reactive
5777
- nonReactiveClass(original);
5700
+ original.prototype[unreactiveProperties] = true;
5778
5701
  },
5779
5702
  default: unreactiveApplication,
5780
5703
  });
5704
+ //#endregion
5705
+ //#region resource
5706
+ function lazyInit(resource, load) {
5707
+ const creation = effectHistory.active;
5708
+ let fresh = true;
5709
+ return new Proxy(resource, {
5710
+ [Symbol.toStringTag]: 'LazyInit',
5711
+ get(target, prop) {
5712
+ if (fresh) {
5713
+ captured(creation, load)();
5714
+ fresh = false;
5715
+ }
5716
+ return target[prop];
5717
+ },
5718
+ });
5719
+ }
5781
5720
  /**
5782
5721
  * Creates a reactive resource that automatically tracks async state.
5783
5722
  * @param fetcher - Async function that returns the value. Reactive dependencies are tracked.
@@ -5785,521 +5724,59 @@
5785
5724
  * @returns Reactive Resource object with value, loading, error, latest properties
5786
5725
  */
5787
5726
  function resource(fetcher, options = {}) {
5788
- const state = reactive({
5727
+ const resource = reactive({
5789
5728
  value: options.initialValue,
5790
5729
  loading: true,
5791
5730
  error: undefined,
5792
5731
  latest: options.initialValue,
5793
- reload: () => {
5732
+ reload() {
5794
5733
  reloadSignal.value++;
5795
5734
  },
5796
5735
  });
5797
5736
  const reloadSignal = reactive({ value: 0 });
5737
+ // Solve race conditions: make sure a new fast request is not overloaded by a slow old one
5798
5738
  let counter = 0;
5799
- const stop = effect(function resourceEffect(access) {
5800
- // Track reload signal to enable manual reloading
5801
- if (reloadSignal.value) ;
5802
- const id = ++counter;
5803
- state.loading = true;
5804
- state.error = undefined;
5805
- try {
5806
- const result = fetcher(access);
5807
- if (result instanceof Promise) {
5808
- result.then((val) => {
5809
- if (id === counter) {
5810
- state.value = val;
5811
- state.latest = val;
5812
- state.loading = false;
5813
- }
5814
- }, (err) => {
5815
- if (id === counter) {
5816
- state.error = err;
5817
- state.loading = false;
5818
- }
5819
- });
5820
- }
5821
- else {
5822
- state.value = result;
5823
- state.latest = result;
5824
- state.loading = false;
5825
- }
5826
- }
5827
- catch (err) {
5828
- state.error = err;
5829
- state.loading = false;
5830
- }
5831
- });
5832
- state.stop = stop;
5833
- return state;
5834
- }
5835
- //#endregion
5836
-
5837
- // Helper to work around TypeScript limitation: base class expressions cannot reference class type parameters
5838
- function getRegisterBase() {
5839
- class RegisterBase extends Indexable(ArrayReadForward, {
5840
- get(index) {
5841
- return this[getAt](index);
5842
- },
5843
- set(index, value) {
5844
- this[setAt](index, value);
5845
- },
5846
- getLength() {
5847
- return this.length;
5848
- },
5849
- setLength(value) {
5850
- this.length = value;
5851
- },
5852
- }) {
5853
- toArray() {
5854
- return Array.from(this);
5855
- }
5856
- }
5857
- return RegisterBase;
5858
- }
5859
- let RegisterClass = (() => {
5860
- var _RegisterClass_instances, _RegisterClass_keyFn, _RegisterClass_keys, _RegisterClass_values, _RegisterClass_usage, _RegisterClass_valueInfo, _RegisterClass_keyEffects, _RegisterClass_ascend, _RegisterClass_events, _RegisterClass_rekeyValue;
5861
- let _classDecorators = [unreactive];
5862
- let _classDescriptor;
5863
- let _classExtraInitializers = [];
5864
- let _classThis;
5865
- let _classSuper = getRegisterBase();
5866
- _classThis = class extends _classSuper {
5867
- get [(_RegisterClass_keyFn = new WeakMap(), _RegisterClass_keys = new WeakMap(), _RegisterClass_values = new WeakMap(), _RegisterClass_usage = new WeakMap(), _RegisterClass_valueInfo = new WeakMap(), _RegisterClass_keyEffects = new WeakMap(), _RegisterClass_ascend = new WeakMap(), _RegisterClass_events = new WeakMap(), _RegisterClass_instances = new WeakSet(), forwardArray)]() {
5868
- return this.toArray();
5869
- }
5870
- on(eventOrEvents, cb) {
5871
- // @ts-expect-error Delegate to Eventful
5872
- return __classPrivateFieldGet(this, _RegisterClass_events, "f").on(eventOrEvents, cb);
5873
- }
5874
- off(eventOrEvents, cb) {
5875
- // @ts-expect-error Delegate to Eventful
5876
- __classPrivateFieldGet(this, _RegisterClass_events, "f").off(eventOrEvents, cb);
5877
- }
5878
- /**
5879
- * Register a global hook that receives all events
5880
- */
5881
- hook(cb) {
5882
- return __classPrivateFieldGet(this, _RegisterClass_events, "f").hook(cb);
5883
- }
5884
- constructor(keyFn, initial) {
5885
- super();
5886
- _RegisterClass_instances.add(this);
5887
- _RegisterClass_keyFn.set(this, void 0);
5888
- _RegisterClass_keys.set(this, void 0);
5889
- _RegisterClass_values.set(this, void 0);
5890
- _RegisterClass_usage.set(this, new Map());
5891
- _RegisterClass_valueInfo.set(this, new Map());
5892
- _RegisterClass_keyEffects.set(this, new Set());
5893
- _RegisterClass_ascend.set(this, void 0);
5894
- _RegisterClass_events.set(this, new Eventful()
5895
- /**
5896
- * Register event listeners for CRUD operations
5897
- */
5898
- );
5899
- /* Moved below initialization */
5900
- let ascendGet;
5901
- effect(({ ascend }) => {
5902
- ascendGet = ascend;
5903
- });
5904
- __classPrivateFieldSet(this, _RegisterClass_ascend, ascendGet, "f");
5905
- if (typeof keyFn !== 'function')
5906
- throw new Error('Register requires a key function');
5907
- __classPrivateFieldSet(this, _RegisterClass_keyFn, keyFn, "f");
5908
- __classPrivateFieldSet(this, _RegisterClass_keys, reactive([]), "f");
5909
- __classPrivateFieldSet(this, _RegisterClass_values, reactive(new Map()), "f");
5910
- if (initial)
5911
- this.push(...initial);
5912
- }
5913
- ensureKey(value) {
5914
- let info = __classPrivateFieldGet(this, _RegisterClass_valueInfo, "f").get(value);
5915
- if (info)
5916
- return info.key;
5917
- info = { key: undefined };
5918
- __classPrivateFieldGet(this, _RegisterClass_valueInfo, "f").set(value, info);
5919
- __classPrivateFieldGet(this, _RegisterClass_ascend, "f").call(this, () => {
5920
- const stop = effect(({ reaction }) => {
5921
- const nextKey = __classPrivateFieldGet(this, _RegisterClass_keyFn, "f").call(this, value);
5922
- this.assertValidKey(nextKey);
5923
- const previousKey = info.key;
5924
- if (reaction && previousKey !== undefined && !Object.is(nextKey, previousKey))
5925
- __classPrivateFieldGet(this, _RegisterClass_instances, "m", _RegisterClass_rekeyValue).call(this, value, previousKey, nextKey);
5926
- info.key = nextKey;
5927
- });
5928
- info.stop = stop;
5929
- __classPrivateFieldGet(this, _RegisterClass_keyEffects, "f").add(stop);
5930
- });
5931
- if (info.key === undefined)
5932
- throw new Error('Register key function must return a property key');
5933
- return info.key;
5934
- }
5935
- assertValidKey(key) {
5936
- const type = typeof key;
5937
- if (type !== 'string' && type !== 'number' && type !== 'symbol')
5938
- throw new Error('Register key function must return a property key');
5939
- }
5940
- setKeyValue(key, value, index) {
5941
- const existing = __classPrivateFieldGet(this, _RegisterClass_values, "f").get(key);
5942
- if (existing !== undefined && existing !== value) {
5943
- this.cleanupValue(existing);
5944
- __classPrivateFieldGet(this, _RegisterClass_values, "f").set(key, value);
5945
- if (index !== undefined) {
5946
- __classPrivateFieldGet(this, _RegisterClass_events, "f").emit('update', existing, value, key, index);
5947
- }
5948
- }
5949
- else {
5950
- __classPrivateFieldGet(this, _RegisterClass_values, "f").set(key, value);
5951
- }
5952
- }
5953
- cleanupValue(value) {
5954
- const info = __classPrivateFieldGet(this, _RegisterClass_valueInfo, "f").get(value);
5955
- if (!info)
5956
- return;
5957
- const stop = info.stop;
5958
- if (stop) {
5959
- info.stop = undefined;
5960
- __classPrivateFieldGet(this, _RegisterClass_keyEffects, "f").delete(stop);
5961
- stop();
5962
- }
5963
- __classPrivateFieldGet(this, _RegisterClass_valueInfo, "f").delete(value);
5964
- }
5965
- disposeKeyEffects() {
5966
- for (const value of Array.from(__classPrivateFieldGet(this, _RegisterClass_valueInfo, "f").keys()))
5967
- this.cleanupValue(value);
5968
- __classPrivateFieldGet(this, _RegisterClass_keyEffects, "f").clear();
5969
- }
5970
- incrementUsage(key) {
5971
- const count = __classPrivateFieldGet(this, _RegisterClass_usage, "f").get(key) ?? 0;
5972
- __classPrivateFieldGet(this, _RegisterClass_usage, "f").set(key, count + 1);
5973
- }
5974
- decrementUsage(key, index) {
5975
- const count = __classPrivateFieldGet(this, _RegisterClass_usage, "f").get(key);
5976
- if (!count)
5977
- return;
5978
- if (count <= 1) {
5979
- const value = __classPrivateFieldGet(this, _RegisterClass_values, "f").get(key);
5980
- __classPrivateFieldGet(this, _RegisterClass_usage, "f").delete(key);
5981
- __classPrivateFieldGet(this, _RegisterClass_values, "f").delete(key);
5982
- if (value !== undefined) {
5983
- this.cleanupValue(value);
5984
- const idx = index ?? __classPrivateFieldGet(this, _RegisterClass_keys, "f").indexOf(key);
5985
- __classPrivateFieldGet(this, _RegisterClass_events, "f").emit('delete', value, key, idx);
5986
- }
5987
- }
5988
- else {
5989
- __classPrivateFieldGet(this, _RegisterClass_usage, "f").set(key, count - 1);
5990
- }
5991
- }
5992
- normalizeIndex(index, allowEnd = false) {
5993
- const length = this.length;
5994
- let resolved = index;
5995
- if (resolved < 0)
5996
- resolved = Math.max(length + resolved, 0);
5997
- if (resolved > length) {
5998
- if (allowEnd)
5999
- resolved = length;
6000
- else
6001
- throw new RangeError('Index out of bounds');
6002
- }
6003
- if (!allowEnd && resolved === length)
6004
- throw new RangeError('Index out of bounds');
6005
- return resolved;
6006
- }
6007
- assignAt(index, key, value) {
6008
- const oldKey = __classPrivateFieldGet(this, _RegisterClass_keys, "f")[index];
6009
- if (oldKey !== undefined && Object.is(oldKey, key)) {
6010
- const oldValue = __classPrivateFieldGet(this, _RegisterClass_values, "f").get(key);
6011
- this.setKeyValue(key, value);
6012
- if (oldValue !== undefined && oldValue !== value) {
6013
- __classPrivateFieldGet(this, _RegisterClass_events, "f").emit('update', oldValue, value, key, index);
6014
- }
6015
- return;
6016
- }
6017
- if (oldKey !== undefined)
6018
- this.decrementUsage(oldKey);
6019
- __classPrivateFieldGet(this, _RegisterClass_keys, "f")[index] = key;
6020
- this.incrementUsage(key);
6021
- this.setKeyValue(key, value);
6022
- __classPrivateFieldGet(this, _RegisterClass_events, "f").emit('add', value, key, index);
6023
- }
6024
- insertKeyValue(index, key, value) {
6025
- __classPrivateFieldGet(this, _RegisterClass_keys, "f").splice(index, 0, key);
6026
- this.incrementUsage(key);
6027
- this.setKeyValue(key, value);
6028
- __classPrivateFieldGet(this, _RegisterClass_events, "f").emit('add', value, key, index);
6029
- }
6030
- rebuildFrom(values) {
6031
- this.disposeKeyEffects();
6032
- __classPrivateFieldGet(this, _RegisterClass_keys, "f").splice(0, __classPrivateFieldGet(this, _RegisterClass_keys, "f").length);
6033
- __classPrivateFieldGet(this, _RegisterClass_usage, "f").clear();
6034
- __classPrivateFieldGet(this, _RegisterClass_values, "f").clear();
6035
- for (const value of values) {
6036
- const key = this.ensureKey(value);
6037
- __classPrivateFieldGet(this, _RegisterClass_keys, "f").push(key);
6038
- this.incrementUsage(key);
6039
- __classPrivateFieldGet(this, _RegisterClass_values, "f").set(key, value);
6040
- }
6041
- }
6042
- get length() {
6043
- return __classPrivateFieldGet(this, _RegisterClass_keys, "f").length;
6044
- }
6045
- [(_RegisterClass_rekeyValue = function _RegisterClass_rekeyValue(value, oldKey, newKey) {
6046
- if (Object.is(oldKey, newKey))
6047
- return;
6048
- const existingValue = __classPrivateFieldGet(this, _RegisterClass_values, "f").get(newKey);
6049
- if (existingValue !== undefined && existingValue !== value)
6050
- throw new Error(`Register key collision for key ${String(newKey)}`);
6051
- const count = __classPrivateFieldGet(this, _RegisterClass_usage, "f").get(oldKey);
6052
- if (!count)
6053
- return;
6054
- const existingCount = __classPrivateFieldGet(this, _RegisterClass_usage, "f").get(newKey) ?? 0;
6055
- this.setKeyValue(newKey, value);
6056
- let index = -1;
6057
- for (let i = 0; i < __classPrivateFieldGet(this, _RegisterClass_keys, "f").length; i++)
6058
- if (Object.is(__classPrivateFieldGet(this, _RegisterClass_keys, "f")[i], oldKey)) {
6059
- __classPrivateFieldGet(this, _RegisterClass_keys, "f")[i] = newKey;
6060
- if (index === -1)
6061
- index = i;
6062
- }
6063
- __classPrivateFieldGet(this, _RegisterClass_usage, "f").set(newKey, existingCount + count);
6064
- __classPrivateFieldGet(this, _RegisterClass_usage, "f").delete(oldKey);
6065
- __classPrivateFieldGet(this, _RegisterClass_values, "f").delete(oldKey);
6066
- const updatedInfo = __classPrivateFieldGet(this, _RegisterClass_valueInfo, "f").get(value);
6067
- if (updatedInfo)
6068
- updatedInfo.key = newKey;
6069
- if (index !== -1) {
6070
- __classPrivateFieldGet(this, _RegisterClass_events, "f").emit('rekey', value, oldKey, newKey, index);
6071
- }
6072
- }, getAt)](index) {
6073
- const key = __classPrivateFieldGet(this, _RegisterClass_keys, "f")[index];
6074
- return key === undefined ? undefined : __classPrivateFieldGet(this, _RegisterClass_values, "f").get(key);
6075
- }
6076
- [setAt](index, value) {
6077
- const key = this.ensureKey(value);
6078
- if (index === this.length) {
6079
- this.insertKeyValue(index, key, value);
6080
- return;
6081
- }
6082
- const normalized = this.normalizeIndex(index);
6083
- this.assignAt(normalized, key, value);
6084
- }
6085
- push(...items) {
6086
- for (const item of items) {
6087
- const key = this.ensureKey(item);
6088
- this.insertKeyValue(this.length, key, item);
6089
- }
6090
- return this.length;
6091
- }
6092
- pop() {
6093
- if (!this.length)
6094
- return undefined;
6095
- return this.removeAt(this.length - 1);
6096
- }
6097
- shift() {
6098
- if (!this.length)
6099
- return undefined;
6100
- return this.removeAt(0);
6101
- }
6102
- unshift(...items) {
6103
- let index = 0;
6104
- for (const item of items) {
6105
- const key = this.ensureKey(item);
6106
- this.insertKeyValue(index++, key, item);
6107
- }
6108
- return this.length;
6109
- }
6110
- splice(start, deleteCount, ...items) {
6111
- const normalizedStart = this.normalizeIndex(start, true);
6112
- const maxDeletions = this.length - normalizedStart;
6113
- const actualDelete = Math.min(deleteCount === undefined ? maxDeletions : Math.max(deleteCount, 0), maxDeletions);
6114
- const keysToInsert = [];
6115
- for (const item of items)
6116
- keysToInsert.push(this.ensureKey(item));
6117
- const removedKeys = __classPrivateFieldGet(this, _RegisterClass_keys, "f").splice(normalizedStart, actualDelete, ...keysToInsert);
6118
- const removedValues = [];
6119
- for (let i = 0; i < removedKeys.length; i++) {
6120
- const key = removedKeys[i];
6121
- if (key === undefined)
6122
- continue;
6123
- const value = __classPrivateFieldGet(this, _RegisterClass_values, "f").get(key);
6124
- this.decrementUsage(key, normalizedStart + i);
6125
- removedValues.push(value);
6126
- }
6127
- for (let i = 0; i < keysToInsert.length; i++) {
6128
- const key = keysToInsert[i];
6129
- const value = items[i];
6130
- this.incrementUsage(key);
6131
- this.setKeyValue(key, value);
6132
- }
6133
- return removedValues;
6134
- }
6135
- clear() {
6136
- __classPrivateFieldGet(this, _RegisterClass_keys, "f").length = 0;
6137
- __classPrivateFieldGet(this, _RegisterClass_usage, "f").clear();
6138
- __classPrivateFieldGet(this, _RegisterClass_values, "f").clear();
6139
- this.disposeKeyEffects();
6140
- }
6141
- get(key) {
6142
- return __classPrivateFieldGet(this, _RegisterClass_values, "f").get(key);
6143
- }
6144
- set(key, value) {
6145
- if (__classPrivateFieldGet(this, _RegisterClass_values, "f").has(key))
6146
- this.setKeyValue(key, value);
6147
- }
6148
- remove(key) {
6149
- let index = this.indexOfKey(key);
6150
- while (index !== -1) {
6151
- this.removeAt(index);
6152
- index = this.indexOfKey(key);
6153
- }
6154
- }
6155
- removeAt(index) {
6156
- const [key] = __classPrivateFieldGet(this, _RegisterClass_keys, "f").splice(index, 1);
6157
- if (key === undefined)
6158
- return undefined;
6159
- const value = __classPrivateFieldGet(this, _RegisterClass_values, "f").get(key);
6160
- this.decrementUsage(key, index);
6161
- return value;
6162
- }
6163
- /**
6164
- * Keep only the items for which the predicate returns true.
6165
- * Items for which the predicate returns false are removed.
6166
- *
6167
- * The predicate is evaluated once per distinct key; duplicate keys
6168
- * will follow the same keep/remove decision.
6169
- */
6170
- keep(predicate) {
6171
- const decisions = new Map();
6172
- for (const [index, key] of __classPrivateFieldGet(this, _RegisterClass_keys, "f").entries()) {
6173
- if (decisions.has(key)) {
6174
- if (!decisions.get(key))
6175
- this.removeAt(index);
6176
- continue;
6177
- }
6178
- const value = __classPrivateFieldGet(this, _RegisterClass_values, "f").get(key);
6179
- const shouldKeep = predicate(value);
6180
- decisions.set(key, shouldKeep);
6181
- if (!shouldKeep)
6182
- this.removeAt(index);
6183
- }
6184
- }
6185
- hasKey(key) {
6186
- return __classPrivateFieldGet(this, _RegisterClass_usage, "f").has(key);
6187
- }
6188
- indexOfKey(key) {
6189
- return __classPrivateFieldGet(this, _RegisterClass_keys, "f").indexOf(key);
6190
- }
6191
- mapKeys() {
6192
- return __classPrivateFieldGet(this, _RegisterClass_values, "f").keys();
6193
- }
6194
- update(...values) {
6195
- for (const value of values) {
6196
- const key = this.ensureKey(value);
6197
- if (__classPrivateFieldGet(this, _RegisterClass_values, "f").has(key)) {
6198
- const index = __classPrivateFieldGet(this, _RegisterClass_keys, "f").indexOf(key);
6199
- this.setKeyValue(key, value, index);
5739
+ return lazyInit(resource, () => {
5740
+ link(resource, effect.named('watch:resource')((access) => {
5741
+ // Track reload signal to enable manual reloading
5742
+ void reloadSignal.value;
5743
+ const id = ++counter;
5744
+ resource.loading = true;
5745
+ resource.error = undefined;
5746
+ try {
5747
+ const result = fetcher(access);
5748
+ if (result instanceof Promise) {
5749
+ resource.promise = result
5750
+ .then((val) => {
5751
+ if (id === counter) {
5752
+ resource.value = val;
5753
+ resource.latest = val;
5754
+ resource.loading = false;
5755
+ }
5756
+ })
5757
+ .catch((err) => {
5758
+ if (id === counter) {
5759
+ resource.error = err;
5760
+ resource.loading = false;
5761
+ }
5762
+ });
6200
5763
  }
6201
- }
6202
- }
6203
- upsert(insert, ...values) {
6204
- for (const value of values) {
6205
- const key = this.ensureKey(value);
6206
- if (__classPrivateFieldGet(this, _RegisterClass_values, "f").has(key))
6207
- this.setKeyValue(key, value);
6208
- else
6209
- insert(value);
6210
- }
6211
- }
6212
- entries() {
6213
- const self = this;
6214
- function* iterator() {
6215
- for (let i = 0; i < __classPrivateFieldGet(self, _RegisterClass_keys, "f").length; i++) {
6216
- const val = __classPrivateFieldGet(self, _RegisterClass_values, "f").get(__classPrivateFieldGet(self, _RegisterClass_keys, "f")[i]);
6217
- if (val !== undefined)
6218
- yield [i, val];
5764
+ else {
5765
+ resource.promise = Promise.resolve();
5766
+ resource.value = result;
5767
+ resource.latest = result;
5768
+ resource.loading = false;
6219
5769
  }
6220
5770
  }
6221
- return iterator();
6222
- }
6223
- [Symbol.iterator]() {
6224
- const self = this;
6225
- function* iterator() {
6226
- for (const key of __classPrivateFieldGet(self, _RegisterClass_keys, "f")) {
6227
- const value = __classPrivateFieldGet(self, _RegisterClass_values, "f").get(key);
6228
- if (value !== undefined)
6229
- yield value;
6230
- }
5771
+ catch (err) {
5772
+ resource.promise = Promise.reject(err);
5773
+ resource.error = err;
5774
+ resource.loading = false;
6231
5775
  }
6232
- return iterator();
6233
- }
6234
- toString() {
6235
- return `[Register length=${this.length}]`;
6236
- }
6237
- at(index) {
6238
- const resolved = index < 0 ? this.length + index : index;
6239
- if (resolved < 0 || resolved >= this.length)
6240
- return undefined;
6241
- return this[getAt](resolved);
6242
- }
6243
- reverse() {
6244
- __classPrivateFieldGet(this, _RegisterClass_keys, "f").reverse();
6245
- return this;
6246
- }
6247
- sort(compareFn) {
6248
- const fwdCompareFn = compareFn
6249
- ? (a, b) => compareFn(__classPrivateFieldGet(this, _RegisterClass_values, "f").get(a), __classPrivateFieldGet(this, _RegisterClass_values, "f").get(b))
6250
- : undefined;
6251
- __classPrivateFieldGet(this, _RegisterClass_keys, "f").sort(fwdCompareFn);
6252
- return this;
6253
- }
6254
- fill(value, start = 0, end = this.length) {
6255
- const values = this.toArray();
6256
- values.fill(value, start, end);
6257
- this.rebuildFrom(values);
6258
- return this;
6259
- }
6260
- copyWithin(target, start, end) {
6261
- const values = this.toArray();
6262
- values.copyWithin(target, start, end);
6263
- this.rebuildFrom(values);
6264
- return this;
6265
- }
6266
- };
6267
- __setFunctionName(_classThis, "RegisterClass");
6268
- (() => {
6269
- const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
6270
- __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
6271
- _classThis = _classDescriptor.value;
6272
- if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
6273
- __runInitializers(_classThis, _classExtraInitializers);
6274
- })();
6275
- return _classThis;
6276
- })();
6277
- const Register = RegisterClass;
6278
- /**
6279
- * Creates a reactive Register - an ordered, array-like collection with stable key-based identity.
6280
- *
6281
- * Register combines array semantics (indexable access, ordering, iteration) with Map-like
6282
- * key-based lookups. Items with the same key share the same value instance, making it ideal
6283
- * for UI lists keyed by IDs or when you need to preserve identity across reorders.
6284
- *
6285
- * @param keyFn - Function that extracts the key from each item
6286
- * @param initial - Optional initial items to populate the register
6287
- * @returns A reactive Register instance
6288
- *
6289
- * @example
6290
- * ```typescript
6291
- * const users = register(
6292
- * (user: User) => user.id,
6293
- * [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
6294
- * )
6295
- *
6296
- * users.push({ id: 3, name: 'Charlie' })
6297
- * const bob = users.get(2) // Get by key
6298
- * ```
6299
- */
6300
- function register(keyFn, initial) {
6301
- return new RegisterClass(keyFn, initial);
5776
+ }));
5777
+ });
6302
5778
  }
5779
+ //#endregion
6303
5780
 
6304
5781
  /**
6305
5782
  * Converts an iterator to a generator that yields reactive values
@@ -6382,6 +5859,7 @@
6382
5859
  let _classSuper = Array;
6383
5860
  let _instanceExtraInitializers = [];
6384
5861
  let _fill_decorators;
5862
+ let _copyWithin_decorators;
6385
5863
  let _pop_decorators;
6386
5864
  let _push_decorators;
6387
5865
  let _reverse_decorators;
@@ -6406,6 +5884,9 @@
6406
5884
  fill(value, start, end) {
6407
5885
  return super.fill(unwrap(value), start, end);
6408
5886
  }
5887
+ copyWithin(target, start, end) {
5888
+ return super.copyWithin(target, start, end);
5889
+ }
6409
5890
  filter(predicate, thisArg) {
6410
5891
  return reactive(super.filter((v, i, a) => predicate.call(thisArg, reactive(v), i, a), thisArg));
6411
5892
  }
@@ -6507,7 +5988,7 @@
6507
5988
  dependant(this, keysOf);
6508
5989
  return makeReactiveIterator(super.values());
6509
5990
  }
6510
- [(_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)]() {
5991
+ [(_fill_decorators = [atomic], _copyWithin_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)]() {
6511
5992
  dependant(this, keysOf);
6512
5993
  return makeReactiveIterator(super[Symbol.iterator]());
6513
5994
  }
@@ -6520,7 +6001,6 @@
6520
6001
  : undefined;
6521
6002
  return reactive(super.toSorted(wrappedCompare));
6522
6003
  }
6523
- // biome-ignore lint/complexity/noArguments: Needed to distinguish fn() from fn(undefined)
6524
6004
  toSpliced(start, deleteCount, ...items) {
6525
6005
  if (arguments.length > 2)
6526
6006
  return reactive(super.toSpliced(start, deleteCount, ...items.map(unwrap)));
@@ -6541,6 +6021,7 @@
6541
6021
  (() => {
6542
6022
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
6543
6023
  __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);
6024
+ __esDecorate(_a, null, _copyWithin_decorators, { kind: "method", name: "copyWithin", static: false, private: false, access: { has: obj => "copyWithin" in obj, get: obj => obj.copyWithin }, metadata: _metadata }, null, _instanceExtraInitializers);
6544
6025
  __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);
6545
6026
  __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);
6546
6027
  __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);
@@ -6561,18 +6042,18 @@
6561
6042
  // Implement WeakMap interface methods with reactivity
6562
6043
  delete(key) {
6563
6044
  const hadKey = this.has(key);
6564
- const result = this.delete(key);
6045
+ const result = super.delete(key);
6565
6046
  if (hadKey)
6566
6047
  touched1(contentRef(this), { type: 'del', prop: key }, key);
6567
6048
  return result;
6568
6049
  }
6569
6050
  get(key) {
6570
6051
  dependant(contentRef(this), key);
6571
- return reactive(this.get(key));
6052
+ return reactive(super.get(key));
6572
6053
  }
6573
6054
  has(key) {
6574
6055
  dependant(contentRef(this), key);
6575
- return this.has(key);
6056
+ return super.has(key);
6576
6057
  }
6577
6058
  set(key, value) {
6578
6059
  const hadKey = this.has(key);
@@ -6593,16 +6074,18 @@
6593
6074
  // Implement Map interface methods with reactivity
6594
6075
  get size() {
6595
6076
  dependant(this, 'size'); // The ReactiveMap instance still goes through proxy
6596
- return this.size;
6077
+ return super.size;
6597
6078
  }
6598
6079
  clear() {
6599
6080
  const hadEntries = this.size > 0;
6600
- this.clear();
6081
+ super.clear();
6601
6082
  if (hadEntries) {
6602
6083
  const evolution = { type: 'bunch', method: 'clear' };
6603
6084
  // Clear triggers all effects since all keys are affected
6604
- touched1(this, evolution, 'size');
6605
- touched(contentRef(this), evolution);
6085
+ batch(() => {
6086
+ touched1(this, evolution, 'size');
6087
+ touched(contentRef(this), evolution);
6088
+ });
6606
6089
  }
6607
6090
  }
6608
6091
  entries() {
@@ -6637,32 +6120,36 @@
6637
6120
  // Implement Map methods with reactivity
6638
6121
  delete(key) {
6639
6122
  const hadKey = this.has(key);
6640
- const result = this.delete(key);
6123
+ const result = super.delete(key);
6641
6124
  if (hadKey) {
6642
6125
  const evolution = { type: 'del', prop: key };
6643
- touched1(contentRef(this), evolution, key);
6644
- touched1(this, evolution, 'size');
6126
+ batch(() => {
6127
+ touched1(contentRef(this), evolution, key);
6128
+ touched1(this, evolution, 'size');
6129
+ });
6645
6130
  }
6646
6131
  return result;
6647
6132
  }
6648
6133
  get(key) {
6649
6134
  dependant(contentRef(this), key);
6650
- return reactive(this.get(key));
6135
+ return reactive(super.get(key));
6651
6136
  }
6652
6137
  has(key) {
6653
6138
  dependant(contentRef(this), key);
6654
- return this.has(key);
6139
+ return super.has(key);
6655
6140
  }
6656
6141
  set(key, value) {
6657
6142
  const hadKey = this.has(key);
6658
6143
  const oldValue = this.get(key);
6659
6144
  const reactiveValue = reactive(value);
6660
- this.set(key, reactiveValue);
6145
+ super.set(key, reactiveValue);
6661
6146
  if (!hadKey || oldValue !== reactiveValue) {
6662
- notifyPropertyChange(contentRef(this), key, oldValue, reactiveValue, hadKey);
6663
- // Also notify size change for Map (WeakMap doesn't track size)
6664
- const evolution = { type: hadKey ? 'set' : 'add', prop: key };
6665
- touched1(this, evolution, 'size');
6147
+ batch(() => {
6148
+ notifyPropertyChange(contentRef(this), key, oldValue, reactiveValue, hadKey);
6149
+ // Also notify size change for Map (WeakMap doesn't track size)
6150
+ const evolution = { type: hadKey ? 'set' : 'add', prop: key };
6151
+ touched1(this, evolution, 'size');
6152
+ });
6666
6153
  }
6667
6154
  return this;
6668
6155
  }
@@ -6675,7 +6162,7 @@
6675
6162
  class ReactiveWeakSet extends WeakSet {
6676
6163
  add(value) {
6677
6164
  const had = this.has(value);
6678
- this.add(value);
6165
+ super.add(value);
6679
6166
  if (!had) {
6680
6167
  // touch the specific value and the collection view
6681
6168
  touched1(contentRef(this), { type: 'add', prop: value }, value);
@@ -6685,14 +6172,14 @@
6685
6172
  }
6686
6173
  delete(value) {
6687
6174
  const had = this.has(value);
6688
- const res = this.delete(value);
6175
+ const res = super.delete(value);
6689
6176
  if (had)
6690
6177
  touched1(contentRef(this), { type: 'del', prop: value }, value);
6691
6178
  return res;
6692
6179
  }
6693
6180
  has(value) {
6694
6181
  dependant(contentRef(this), value);
6695
- return this.has(value);
6182
+ return super.has(value);
6696
6183
  }
6697
6184
  }
6698
6185
  /**
@@ -6708,31 +6195,37 @@
6708
6195
  add(value) {
6709
6196
  const had = this.has(value);
6710
6197
  const reactiveValue = reactive(value);
6711
- this.add(reactiveValue);
6198
+ super.add(reactiveValue);
6712
6199
  if (!had) {
6713
6200
  const evolution = { type: 'add', prop: reactiveValue };
6714
6201
  // touch for value-specific and aggregate dependencies
6715
- touched1(contentRef(this), evolution, reactiveValue);
6716
- touched1(this, evolution, 'size');
6202
+ batch(() => {
6203
+ touched1(contentRef(this), evolution, reactiveValue);
6204
+ touched1(this, evolution, 'size');
6205
+ });
6717
6206
  }
6718
6207
  return this;
6719
6208
  }
6720
6209
  clear() {
6721
6210
  const hadEntries = this.size > 0;
6722
- this.clear();
6211
+ super.clear();
6723
6212
  if (hadEntries) {
6724
6213
  const evolution = { type: 'bunch', method: 'clear' };
6725
- touched1(this, evolution, 'size');
6726
- touched(contentRef(this), evolution);
6214
+ batch(() => {
6215
+ touched1(this, evolution, 'size');
6216
+ touched(contentRef(this), evolution);
6217
+ });
6727
6218
  }
6728
6219
  }
6729
6220
  delete(value) {
6730
6221
  const had = this.has(value);
6731
- const res = this.delete(value);
6222
+ const res = super.delete(value);
6732
6223
  if (had) {
6733
6224
  const evolution = { type: 'del', prop: value };
6734
- touched1(contentRef(this), evolution, value);
6735
- touched1(this, evolution, 'size');
6225
+ batch(() => {
6226
+ touched1(contentRef(this), evolution, value);
6227
+ touched1(this, evolution, 'size');
6228
+ });
6736
6229
  }
6737
6230
  return res;
6738
6231
  }
@@ -6771,14 +6264,12 @@
6771
6264
  }
6772
6265
 
6773
6266
  // Register native collection types to use specialized reactive wrappers
6774
- console.time('mutts-reactive-init');
6775
6267
  metaProtos.set(Array, ReactiveArray.prototype);
6776
6268
  metaProtos.set(Set, ReactiveSet.prototype);
6777
6269
  metaProtos.set(WeakSet, ReactiveWeakSet.prototype);
6778
6270
  metaProtos.set(Map, ReactiveMap.prototype);
6779
6271
  metaProtos.set(WeakMap, ReactiveWeakMap.prototype);
6780
6272
  wrapProtos.set(Array, ReactiveArrayWrapper.prototype);
6781
- console.timeEnd('mutts-reactive-init');
6782
6273
  /**
6783
6274
  * Object containing internal reactive system state for debugging and profiling
6784
6275
  */
@@ -6791,7 +6282,6 @@
6791
6282
  objectsWithDeepWatchers,
6792
6283
  deepWatchers,
6793
6284
  effectToDeepWatchedObjects,
6794
- nonReactiveObjects,
6795
6285
  };
6796
6286
 
6797
6287
  // In order to avoid async re-entrance, we could use zone.js or something like that.
@@ -6957,8 +6447,7 @@
6957
6447
  },
6958
6448
  }), {
6959
6449
  warn: (target, propertyKey, message) => {
6960
- // biome-ignore lint/suspicious/noConsole: To be overridden
6961
- console.warn(`${target.constructor.name}.${String(propertyKey)} is deprecated${message ? `: ${message}` : ''}`);
6450
+ options.warn(`${target.constructor.name}.${String(propertyKey)} is deprecated${message ? `: ${message}` : ''}`);
6962
6451
  },
6963
6452
  });
6964
6453
  /**
@@ -7021,12 +6510,11 @@
7021
6510
  });
7022
6511
  }
7023
6512
 
7024
- var version$1 = "1.0.10";
6513
+ var version$1 = "1.0.12";
7025
6514
  var pkg = {
7026
6515
  version: version$1};
7027
6516
 
7028
6517
  const { version } = pkg;
7029
- // Singleton verification
7030
6518
  const GLOBAL_MUTTS_KEY = '__MUTTS_INSTANCE__';
7031
6519
  const globalScope = (typeof globalThis !== 'undefined'
7032
6520
  ? globalThis
@@ -7036,28 +6524,16 @@
7036
6524
  ? global
7037
6525
  : false);
7038
6526
  if (globalScope) {
7039
- // Detect the source of this instance safely across different environments
7040
6527
  let source = 'mutts/index';
7041
- // biome-ignore lint/security/noGlobalEval: Intentional eval for cross-env import.meta detection
7042
- const viteEval = eval;
7043
6528
  try {
7044
6529
  if (typeof __filename !== 'undefined')
7045
6530
  source = __filename;
7046
- else {
7047
- // Using eval to avoid SyntaxError in CJS environments where import.meta is not allowed
7048
- const meta = viteEval('import.meta');
7049
- if (meta && meta.url)
7050
- source = meta.url;
6531
+ else if (typeof ({ url: (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('mutts.umd.js', document.baseURI).href)) }) !== 'undefined' && (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('mutts.umd.js', document.baseURI).href))) {
6532
+ source = (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('mutts.umd.js', document.baseURI).href));
7051
6533
  }
7052
6534
  }
7053
- catch (e) {
7054
- // Fallback for environments where neither is available or accessible
7055
- }
7056
- const currentSourceInfo = {
7057
- version,
7058
- source,
7059
- timestamp: Date.now(),
7060
- };
6535
+ catch (_e) { }
6536
+ const currentSourceInfo = { version, source, timestamp: Date.now() };
7061
6537
  if (globalScope[GLOBAL_MUTTS_KEY]) {
7062
6538
  const existing = globalScope[GLOBAL_MUTTS_KEY];
7063
6539
  throw new Error(`[Mutts] Multiple instances detected!\n` +
@@ -7081,15 +6557,16 @@
7081
6557
  exports.IterableWeakSet = IterableWeakSet;
7082
6558
  exports.ReactiveBase = ReactiveBase;
7083
6559
  exports.ReactiveError = ReactiveError;
7084
- exports.Register = Register;
7085
6560
  exports.Zone = Zone;
7086
6561
  exports.ZoneAggregator = ZoneAggregator;
7087
6562
  exports.ZoneHistory = ZoneHistory;
7088
6563
  exports.addBatchCleanup = addBatchCleanup;
6564
+ exports.addUnreactiveProps = addUnreactiveProps;
7089
6565
  exports.allocated = allocated;
7090
6566
  exports.allocatedValues = allocatedValues;
7091
6567
  exports.arrayDiff = arrayDiff;
7092
6568
  exports.arrayEquals = arrayEquals;
6569
+ exports.assertUntracked = assertUntracked;
7093
6570
  exports.asyncHook = asyncHook;
7094
6571
  exports.asyncHooks = asyncHooks;
7095
6572
  exports.asyncZone = asyncZone;
@@ -7100,12 +6577,12 @@
7100
6577
  exports.cache = cache;
7101
6578
  exports.cached = cached;
7102
6579
  exports.callOnGC = callOnGC;
6580
+ exports.captured = captured;
7103
6581
  exports.caught = caught;
7104
6582
  exports.chainPromise = chainPromise;
7105
- exports.cleanedBy = cleanedBy;
7106
- exports.cleanup = cleanup;
7107
6583
  exports.createFlavor = createFlavor;
7108
6584
  exports.debounce = debounce;
6585
+ exports.debugPreset = debugPreset;
7109
6586
  exports.decorator = decorator;
7110
6587
  exports.deepCompare = deepCompare;
7111
6588
  exports.deepWatch = deepWatch;
@@ -7113,6 +6590,7 @@
7113
6590
  exports.deprecated = deprecated;
7114
6591
  exports.descriptor = descriptor;
7115
6592
  exports.destructor = destructor;
6593
+ exports.devPreset = devPreset;
7116
6594
  exports.effect = effect;
7117
6595
  exports.effectAggregator = effectAggregator;
7118
6596
  exports.flavorOptions = flavorOptions;
@@ -7124,14 +6602,17 @@
7124
6602
  exports.getAt = getAt;
7125
6603
  exports.getState = getState;
7126
6604
  exports.hooks = hooks$1;
7127
- exports.immutables = immutables;
7128
6605
  exports.isCached = isCached;
7129
6606
  exports.isConstructor = isConstructor;
6607
+ exports.isDev = isDev;
7130
6608
  exports.isNonReactive = isNonReactive;
7131
6609
  exports.isObject = isObject;
6610
+ exports.isProd = isProd;
7132
6611
  exports.isReactive = isReactive;
6612
+ exports.isTest = isTest;
7133
6613
  exports.legacyDecorator = legacyDecorator;
7134
6614
  exports.lift = lift;
6615
+ exports.link = link;
7135
6616
  exports.memoize = memoize;
7136
6617
  exports.mixin = mixin;
7137
6618
  exports.modernDecorator = modernDecorator;
@@ -7141,22 +6622,21 @@
7141
6622
  exports.onEffectThrow = onEffectThrow;
7142
6623
  exports.organize = organize;
7143
6624
  exports.organized = organized;
6625
+ exports.prodPreset = prodPreset;
7144
6626
  exports.profileInfo = profileInfo;
7145
6627
  exports.project = morph;
7146
6628
  exports.proxyToObject = proxyToObject;
7147
6629
  exports.reactive = reactive;
7148
6630
  exports.reactiveOptions = options;
7149
- exports.register = register;
7150
6631
  exports.reset = reset;
7151
6632
  exports.resource = resource;
7152
6633
  exports.root = root;
7153
- exports.scan = scan;
7154
6634
  exports.setAt = setAt;
7155
- exports.stopped = stopped;
7156
6635
  exports.tag = tag;
7157
6636
  exports.throttle = throttle;
7158
6637
  exports.touched = touched;
7159
6638
  exports.touched1 = touched1;
6639
+ exports.unlink = unlink;
7160
6640
  exports.unreactive = unreactive;
7161
6641
  exports.untracked = untracked;
7162
6642
  exports.unwrap = unwrap;