mutts 1.0.4 → 1.0.6

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 (75) hide show
  1. package/README.md +2 -1
  2. package/dist/chunks/{_tslib-Mzh1rNsX.esm.js → _tslib-MCKDzsSq.esm.js} +2 -2
  3. package/dist/chunks/_tslib-MCKDzsSq.esm.js.map +1 -0
  4. package/dist/chunks/decorator-BGILvPtN.esm.js +627 -0
  5. package/dist/chunks/decorator-BGILvPtN.esm.js.map +1 -0
  6. package/dist/chunks/decorator-BQ2eBTCj.js +651 -0
  7. package/dist/chunks/decorator-BQ2eBTCj.js.map +1 -0
  8. package/dist/chunks/{index-GRBSx0mB.js → index-CDCOjzTy.js} +543 -495
  9. package/dist/chunks/index-CDCOjzTy.js.map +1 -0
  10. package/dist/chunks/{index-79Kk8D6e.esm.js → index-DiP0RXoZ.esm.js} +452 -404
  11. package/dist/chunks/index-DiP0RXoZ.esm.js.map +1 -0
  12. package/dist/decorator.d.ts +3 -3
  13. package/dist/decorator.esm.js +1 -1
  14. package/dist/decorator.js +1 -1
  15. package/dist/destroyable.esm.js +4 -4
  16. package/dist/destroyable.esm.js.map +1 -1
  17. package/dist/destroyable.js +4 -4
  18. package/dist/destroyable.js.map +1 -1
  19. package/dist/devtools/panel.js.map +1 -1
  20. package/dist/eventful.esm.js +1 -1
  21. package/dist/index.esm.js +48 -3
  22. package/dist/index.esm.js.map +1 -1
  23. package/dist/index.js +50 -4
  24. package/dist/index.js.map +1 -1
  25. package/dist/mutts.umd.js +1 -1
  26. package/dist/mutts.umd.js.map +1 -1
  27. package/dist/mutts.umd.min.js +1 -1
  28. package/dist/mutts.umd.min.js.map +1 -1
  29. package/dist/reactive.d.ts +54 -1
  30. package/dist/reactive.esm.js +3 -3
  31. package/dist/reactive.js +6 -4
  32. package/dist/reactive.js.map +1 -1
  33. package/dist/std-decorators.d.ts +1 -1
  34. package/dist/std-decorators.esm.js +10 -10
  35. package/dist/std-decorators.esm.js.map +1 -1
  36. package/dist/std-decorators.js +10 -10
  37. package/dist/std-decorators.js.map +1 -1
  38. package/docs/ai/manual.md +14 -95
  39. package/docs/reactive/advanced.md +6 -107
  40. package/docs/reactive/core.md +16 -16
  41. package/docs/reactive/debugging.md +158 -0
  42. package/docs/reactive.md +8 -0
  43. package/package.json +16 -66
  44. package/src/decorator.ts +11 -9
  45. package/src/destroyable.ts +5 -5
  46. package/src/index.ts +46 -0
  47. package/src/reactive/array.ts +3 -5
  48. package/src/reactive/change.ts +7 -3
  49. package/src/reactive/debug.ts +1 -1
  50. package/src/reactive/deep-touch.ts +1 -1
  51. package/src/reactive/deep-watch.ts +1 -1
  52. package/src/reactive/effect-context.ts +2 -2
  53. package/src/reactive/effects.ts +114 -17
  54. package/src/reactive/index.ts +3 -2
  55. package/src/reactive/interface.ts +10 -9
  56. package/src/reactive/map.ts +6 -6
  57. package/src/reactive/mapped.ts +2 -3
  58. package/src/reactive/memoize.ts +77 -31
  59. package/src/reactive/project.ts +103 -6
  60. package/src/reactive/proxy.ts +4 -4
  61. package/src/reactive/registry.ts +67 -0
  62. package/src/reactive/set.ts +6 -6
  63. package/src/reactive/tracking.ts +12 -41
  64. package/src/reactive/types.ts +59 -0
  65. package/src/reactive/zone.ts +1 -1
  66. package/src/std-decorators.ts +10 -10
  67. package/src/utils.ts +141 -0
  68. package/dist/chunks/_tslib-Mzh1rNsX.esm.js.map +0 -1
  69. package/dist/chunks/decorator-DLvrD0UF.js +0 -265
  70. package/dist/chunks/decorator-DLvrD0UF.js.map +0 -1
  71. package/dist/chunks/decorator-DqiszP7i.esm.js +0 -253
  72. package/dist/chunks/decorator-DqiszP7i.esm.js.map +0 -1
  73. package/dist/chunks/index-79Kk8D6e.esm.js.map +0 -1
  74. package/dist/chunks/index-GRBSx0mB.js.map +0 -1
  75. /package/{src/reactive/project.project.md → docs/reactive/project.md} +0 -0
@@ -1,6 +1,6 @@
1
- import { i as isConstructor, R as ReflectGet, d as decorator, b as ReflectSet, c as isOwnAccessor, r as renamed } from './decorator-DqiszP7i.esm.js';
1
+ import { i as isConstructor, a as ReflectGet, g as rootFunction, o as options, h as allProps, R as ReactiveError, j as ReactiveErrorCode, k as cleanup$1, s as stopped, d as decorator, n as nonReactiveMark, p as nativeReactive, q as prototypeForwarding, u as unreactiveProperties, b as ReflectSet, f as isOwnAccessor, r as renamed, e as deepCompare, t as projectionInfo } from './decorator-BGILvPtN.esm.js';
2
2
  import { Indexable, setAt, getAt, ArrayReadForward, forwardArray } from '../indexable.esm.js';
3
- import { a as __setFunctionName, b as __esDecorate, c as __runInitializers, d as __classPrivateFieldSet, _ as __classPrivateFieldGet } from './_tslib-Mzh1rNsX.esm.js';
3
+ import { _ as __setFunctionName, a as __esDecorate, b as __runInitializers, c as __classPrivateFieldSet, d as __classPrivateFieldGet } from './_tslib-MCKDzsSq.esm.js';
4
4
 
5
5
  /// <reference lib="esnext.collection" />
6
6
  var _a, _b;
@@ -332,313 +332,6 @@ function mixin(mixinFunction, unwrapFunction) {
332
332
  });
333
333
  }
334
334
 
335
- // biome-ignore-all lint/suspicious/noConfusingVoidType: Type 'void' is not assignable to type 'ScopedCallback | undefined'.
336
- // Argument of type '() => void' is not assignable to parameter of type '(dep: DependencyFunction) => ScopedCallback | undefined'.
337
- // Track native reactivity
338
- const nativeReactive = Symbol('native-reactive');
339
- /**
340
- * Symbol to mark individual objects as non-reactive
341
- */
342
- const nonReactiveMark = Symbol('non-reactive');
343
- /**
344
- * Symbol to mark class properties as non-reactive
345
- */
346
- const unreactiveProperties = Symbol('unreactive-properties');
347
- /**
348
- * Symbol for prototype forwarding in reactive objects
349
- */
350
- const prototypeForwarding = Symbol('prototype-forwarding');
351
- /**
352
- * Symbol representing all properties in reactive tracking
353
- */
354
- const allProps = Symbol('all-props');
355
- // Symbol to mark functions with their root function
356
- const rootFunction = Symbol('root-function');
357
- /**
358
- * Structured error codes for machine-readable diagnosis
359
- */
360
- var ReactiveErrorCode;
361
- (function (ReactiveErrorCode) {
362
- ReactiveErrorCode["CycleDetected"] = "CYCLE_DETECTED";
363
- ReactiveErrorCode["MaxDepthExceeded"] = "MAX_DEPTH_EXCEEDED";
364
- ReactiveErrorCode["MaxReactionExceeded"] = "MAX_REACTION_EXCEEDED";
365
- ReactiveErrorCode["WriteInComputed"] = "WRITE_IN_COMPUTED";
366
- ReactiveErrorCode["TrackingError"] = "TRACKING_ERROR";
367
- })(ReactiveErrorCode || (ReactiveErrorCode = {}));
368
- /**
369
- * Error class for reactive system errors
370
- */
371
- class ReactiveError extends Error {
372
- constructor(message, debugInfo) {
373
- super(message);
374
- this.debugInfo = debugInfo;
375
- this.name = 'ReactiveError';
376
- }
377
- }
378
- // biome-ignore-start lint/correctness/noUnusedFunctionParameters: Interface declaration with empty defaults
379
- /**
380
- * Global options for the reactive system
381
- */
382
- const options = {
383
- /**
384
- * Debug purpose: called when an effect is entered
385
- * @param effect - The effect that is entered
386
- */
387
- enter: (_effect) => { },
388
- /**
389
- * Debug purpose: called when an effect is left
390
- * @param effect - The effect that is left
391
- */
392
- leave: (_effect) => { },
393
- /**
394
- * Debug purpose: called when an effect is chained
395
- * @param target - The effect that is being triggered
396
- * @param caller - The effect that is calling the target
397
- */
398
- chain: (_targets, _caller) => { },
399
- /**
400
- * Debug purpose: called when an effect chain is started
401
- * @param target - The effect that is being triggered
402
- */
403
- beginChain: (_targets) => { },
404
- /**
405
- * Debug purpose: called when an effect chain is ended
406
- */
407
- endChain: () => { },
408
- garbageCollected: (_fn) => { },
409
- /**
410
- * Debug purpose: called when an object is touched
411
- * @param obj - The object that is touched
412
- * @param evolution - The type of change
413
- * @param props - The properties that changed
414
- * @param deps - The dependencies that changed
415
- */
416
- touched: (_obj, _evolution, _props, _deps) => { },
417
- /**
418
- * Debug purpose: called when an effect is skipped because it's already running
419
- * @param effect - The effect that is already running
420
- * @param runningChain - The array of effects from the detected one to the currently running one
421
- */
422
- skipRunningEffect: (_effect, _runningChain) => { },
423
- /**
424
- * Debug purpose: maximum effect chain (like call stack max depth)
425
- * Used to prevent infinite loops
426
- * @default 100
427
- */
428
- maxEffectChain: 100,
429
- /**
430
- * Debug purpose: maximum effect reaction (like call stack max depth)
431
- * Used to prevent infinite loops
432
- * @default 'throw'
433
- */
434
- maxEffectReaction: 'throw',
435
- /**
436
- * How to handle cycles detected in effect batches
437
- * - 'throw': Throw an error with cycle information (default, recommended for development)
438
- * - 'warn': Log a warning and break the cycle by executing one effect
439
- * - 'break': Silently break the cycle by executing one effect (recommended for production)
440
- * - 'strict': Prevent cycle creation by checking graph before execution (throws error)
441
- * @default 'throw'
442
- */
443
- cycleHandling: 'throw',
444
- /**
445
- * Maximum depth for deep watching traversal
446
- * Used to prevent infinite recursion in circular references
447
- * @default 100
448
- */
449
- maxDeepWatchDepth: 100,
450
- /**
451
- * Only react on instance members modification (not inherited properties)
452
- * For instance, do not track class methods
453
- * @default true
454
- */
455
- instanceMembers: true,
456
- /**
457
- * Ignore accessors (getters and setters) and only track direct properties
458
- * @default true
459
- */
460
- ignoreAccessors: true,
461
- /**
462
- * Enable recursive touching when objects with the same prototype are replaced
463
- * When enabled, replacing an object with another of the same prototype triggers
464
- * recursive diffing instead of notifying parent effects
465
- * @default true
466
- */
467
- recursiveTouching: true,
468
- /**
469
- * Default async execution mode for effects that return Promises
470
- * - 'cancel': Cancel previous async execution when dependencies change (default, enables async zone)
471
- * - 'queue': Queue next execution to run after current completes (enables async zone)
472
- * - 'ignore': Ignore new executions while async work is running (enables async zone)
473
- * - false: Disable async zone and async mode handling (effects run concurrently)
474
- *
475
- * **When truthy:** Enables async zone (Promise.prototype wrapping) for automatic context
476
- * preservation in Promise callbacks. Warning: This modifies Promise.prototype globally.
477
- * Only enable if no other library modifies Promise.prototype.
478
- *
479
- * **When false:** Async zone is disabled. Use `tracked()` manually in Promise callbacks.
480
- *
481
- * Can be overridden per-effect via EffectOptions
482
- * @default 'cancel'
483
- */
484
- asyncMode: 'cancel',
485
- // biome-ignore lint/suspicious/noConsole: This is the whole point here
486
- warn: (...args) => console.warn(...args),
487
- /**
488
- * Configuration for the introspection system
489
- */
490
- introspection: {
491
- /**
492
- * Whether to keep a history of mutations for debugging
493
- * @default false
494
- */
495
- enableHistory: false,
496
- /**
497
- * Number of mutations to keep in history
498
- * @default 50
499
- */
500
- historySize: 50,
501
- },
502
- /**
503
- * Configuration for zone hooks - control which async APIs are hooked
504
- * Each option controls whether the corresponding async API is wrapped to preserve effect context
505
- * Only applies when asyncMode is enabled (truthy)
506
- */
507
- zones: {
508
- /**
509
- * Hook setTimeout to preserve effect context
510
- * @default true
511
- */
512
- setTimeout: true,
513
- /**
514
- * Hook setInterval to preserve effect context
515
- * @default true
516
- */
517
- setInterval: true,
518
- /**
519
- * Hook requestAnimationFrame (runs in untracked context when hooked)
520
- * @default true
521
- */
522
- requestAnimationFrame: true,
523
- /**
524
- * Hook queueMicrotask to preserve effect context
525
- * @default true
526
- */
527
- queueMicrotask: true,
528
- },
529
- };
530
-
531
- /**
532
- * Effect context stack for nested tracking (front = active, next = parent)
533
- */
534
- const stack = [];
535
- function captureEffectStack() {
536
- return stack.slice();
537
- }
538
- function isRunning(effect) {
539
- const rootEffect = getRoot(effect);
540
- // Check if the effect is directly in the stack
541
- const rootIndex = stack.indexOf(rootEffect);
542
- if (rootIndex !== -1) {
543
- return stack.slice(0, rootIndex + 1).reverse();
544
- }
545
- // Check if any effect in the stack is a descendant of this effect
546
- // (i.e., walk up the parent chain from each stack effect to see if we reach this effect)
547
- for (let i = 0; i < stack.length; i++) {
548
- const stackEffect = stack[i];
549
- let current = stackEffect;
550
- const visited = new WeakSet();
551
- const ancestorChain = [];
552
- // TODO: That's perhaps a lot of computations for an `assert`
553
- // Walk up the parent chain to find if this effect is an ancestor
554
- while (current && !visited.has(current)) {
555
- visited.add(current);
556
- const currentRoot = getRoot(current);
557
- ancestorChain.push(currentRoot);
558
- if (currentRoot === rootEffect) {
559
- // Found a descendant - build the full chain from ancestor to active
560
- // The ancestorChain contains [descendant, parent, ..., ancestor] (walking up)
561
- // We need [ancestor (effect), ..., parent, descendant, ...stack from descendant to active]
562
- const chainFromAncestor = ancestorChain.reverse(); // [ancestor, ..., descendant]
563
- // Prepend the actual effect we're checking (in case current is a wrapper)
564
- if (chainFromAncestor[0] !== rootEffect) {
565
- chainFromAncestor.unshift(rootEffect);
566
- }
567
- // Append the rest of the stack from the descendant to the active effect
568
- const stackFromDescendant = stack.slice(0, i + 1).reverse(); // [descendant, ..., active]
569
- // Remove duplicate descendant (it's both at end of chainFromAncestor and start of stackFromDescendant)
570
- if (chainFromAncestor.length > 0 && stackFromDescendant.length > 0) {
571
- stackFromDescendant.shift(); // Remove duplicate descendant
572
- }
573
- return [...chainFromAncestor, ...stackFromDescendant];
574
- }
575
- current = effectParent.get(current);
576
- }
577
- }
578
- return false;
579
- }
580
- function withEffectStack(snapshot, fn) {
581
- const previousStack = stack.slice();
582
- assignStack(snapshot);
583
- try {
584
- return fn();
585
- }
586
- finally {
587
- assignStack(previousStack);
588
- }
589
- }
590
- function getActiveEffect() {
591
- return stack[0];
592
- }
593
- /**
594
- * Executes a function with a specific effect context
595
- * @param effect - The effect to use as context
596
- * @param fn - The function to execute
597
- * @param keepParent - Whether to keep the parent effect context
598
- * @returns The result of the function
599
- */
600
- function withEffect(effect, fn) {
601
- // console.log('[Mutts] withEffect', effect ? 'Active' : 'NULL');
602
- if (getRoot(effect) === getRoot(getActiveEffect()))
603
- return fn();
604
- stack.unshift(effect);
605
- try {
606
- return fn();
607
- }
608
- finally {
609
- const recoveredEffect = stack.shift();
610
- if (recoveredEffect !== effect)
611
- throw new ReactiveError('[reactive] Effect stack mismatch');
612
- }
613
- }
614
- function assignStack(values) {
615
- stack.length = 0;
616
- stack.push(...values);
617
- }
618
-
619
- const objectToProxy = new WeakMap();
620
- const proxyToObject = new WeakMap();
621
- function storeProxyRelationship(target, proxy) {
622
- objectToProxy.set(target, proxy);
623
- proxyToObject.set(proxy, target);
624
- }
625
- function getExistingProxy(target) {
626
- return objectToProxy.get(target);
627
- }
628
- function trackProxyObject(proxy, target) {
629
- proxyToObject.set(proxy, target);
630
- }
631
- function unwrap(obj) {
632
- let current = obj;
633
- while (current && typeof current === 'object' && current !== null && proxyToObject.has(current)) {
634
- current = proxyToObject.get(current);
635
- }
636
- return current;
637
- }
638
- function isReactive(obj) {
639
- return proxyToObject.has(obj);
640
- }
641
-
642
335
  // Track which effects are watching which reactive objects for cleanup
643
336
  const effectToReactiveObjects = new WeakMap();
644
337
  // Track effects per reactive object and property
@@ -647,13 +340,29 @@ const watchers = new WeakMap();
647
340
  const effectChildren = new WeakMap();
648
341
  // Track parent effect relationships for hierarchy traversal (used in deep touch filtering)
649
342
  const effectParent = new WeakMap();
343
+ // Track reverse mapping to ensure unicity: One Root -> One Function
344
+ const reverseRoots = new WeakMap();
650
345
  /**
651
346
  * Marks a function with its root function for effect tracking
347
+ * Enforces strict unicity: A root function can only identify ONE function.
652
348
  * @param fn - The function to mark
653
349
  * @param root - The root function
654
350
  * @returns The marked function
655
351
  */
656
352
  function markWithRoot(fn, root) {
353
+ // Check for collision
354
+ const existingRef = reverseRoots.get(root);
355
+ const existing = existingRef?.deref();
356
+ if (existing && existing !== fn) {
357
+ const rootName = root.name || 'anonymous';
358
+ const existingName = existing.name || 'anonymous';
359
+ const fnName = fn.name || 'anonymous';
360
+ throw new Error(`[reactive] Abusive Shared Root detected: Root '${rootName}' is already identifying function '${existingName}'. ` +
361
+ `Cannot reuse it for '${fnName}'. Shared roots cause lost updates and broken identity logic.`);
362
+ }
363
+ // Always update the map so subsequent checks find this one
364
+ // (Last writer wins for the check)
365
+ reverseRoots.set(root, new WeakRef(fn));
657
366
  // Mark fn with the new root
658
367
  return Object.defineProperty(fn, rootFunction, {
659
368
  value: getRoot(root),
@@ -666,64 +375,15 @@ function markWithRoot(fn, root) {
666
375
  * @returns The root function
667
376
  */
668
377
  function getRoot(fn) {
669
- return fn?.[rootFunction] || fn;
378
+ while (fn && rootFunction in fn)
379
+ fn = fn[rootFunction];
380
+ return fn;
670
381
  }
671
382
  // Flag to disable dependency tracking for the current active effect (not globally)
672
383
  const trackingDisabledEffects = new WeakSet();
673
384
  let globalTrackingDisabled = false;
674
- function getTrackingDisabled() {
675
- const active = getActiveEffect();
676
- if (!active)
677
- return globalTrackingDisabled;
678
- return trackingDisabledEffects.has(getRoot(active));
679
- }
680
- function setTrackingDisabled(value) {
681
- const active = getActiveEffect();
682
- if (!active) {
683
- globalTrackingDisabled = value;
684
- return;
685
- }
686
- const root = getRoot(active);
687
- if (value)
688
- trackingDisabledEffects.add(root);
689
- else
690
- trackingDisabledEffects.delete(root);
691
- }
692
- /**
693
- * Marks a property as a dependency of the current effect
694
- * @param obj - The object containing the property
695
- * @param prop - The property name (defaults to allProps)
696
- */
697
- function dependant(obj, prop = allProps) {
698
- obj = unwrap(obj);
699
- const currentActiveEffect = getActiveEffect();
700
- // Early return if no active effect, tracking disabled, or invalid prop
701
- if (!currentActiveEffect ||
702
- getTrackingDisabled() ||
703
- (typeof prop === 'symbol' && prop !== allProps))
704
- return;
705
- registerDependency(obj, prop, currentActiveEffect);
706
- }
707
- function registerDependency(obj, prop, currentActiveEffect) {
708
- let objectWatchers = watchers.get(obj);
709
- if (!objectWatchers) {
710
- objectWatchers = new Map();
711
- watchers.set(obj, objectWatchers);
712
- }
713
- let deps = objectWatchers.get(prop);
714
- if (!deps) {
715
- deps = new Set();
716
- objectWatchers.set(prop, deps);
717
- }
718
- deps.add(currentActiveEffect);
719
- // Track which reactive objects this effect is watching
720
- const effectObjects = effectToReactiveObjects.get(currentActiveEffect);
721
- if (effectObjects) {
722
- effectObjects.add(obj);
723
- }
724
- else {
725
- effectToReactiveObjects.set(currentActiveEffect, new Set([obj]));
726
- }
385
+ function setGlobalTrackingDisabled(value) {
386
+ globalTrackingDisabled = value;
727
387
  }
728
388
 
729
389
  /**
@@ -1078,6 +738,171 @@ function addToMutationHistory(source, target, obj, prop, evolution) {
1078
738
  }
1079
739
  }
1080
740
 
741
+ /**
742
+ * Effect context stack for nested tracking (front = active, next = parent)
743
+ */
744
+ const stack = [];
745
+ function captureEffectStack() {
746
+ return stack.slice();
747
+ }
748
+ function isRunning(effect) {
749
+ const rootEffect = getRoot(effect);
750
+ // Check if the effect is directly in the stack
751
+ const rootIndex = stack.indexOf(rootEffect);
752
+ if (rootIndex !== -1) {
753
+ return stack.slice(0, rootIndex + 1).reverse();
754
+ }
755
+ // Check if any effect in the stack is a descendant of this effect
756
+ // (i.e., walk up the parent chain from each stack effect to see if we reach this effect)
757
+ for (let i = 0; i < stack.length; i++) {
758
+ const stackEffect = stack[i];
759
+ let current = stackEffect;
760
+ const visited = new WeakSet();
761
+ const ancestorChain = [];
762
+ // TODO: That's perhaps a lot of computations for an `assert`
763
+ // Walk up the parent chain to find if this effect is an ancestor
764
+ while (current && !visited.has(current)) {
765
+ visited.add(current);
766
+ const currentRoot = getRoot(current);
767
+ ancestorChain.push(currentRoot);
768
+ if (currentRoot === rootEffect) {
769
+ // Found a descendant - build the full chain from ancestor to active
770
+ // The ancestorChain contains [descendant, parent, ..., ancestor] (walking up)
771
+ // We need [ancestor (effect), ..., parent, descendant, ...stack from descendant to active]
772
+ const chainFromAncestor = ancestorChain.reverse(); // [ancestor, ..., descendant]
773
+ // Prepend the actual effect we're checking (in case current is a wrapper)
774
+ if (chainFromAncestor[0] !== rootEffect) {
775
+ chainFromAncestor.unshift(rootEffect);
776
+ }
777
+ // Append the rest of the stack from the descendant to the active effect
778
+ const stackFromDescendant = stack.slice(0, i + 1).reverse(); // [descendant, ..., active]
779
+ // Remove duplicate descendant (it's both at end of chainFromAncestor and start of stackFromDescendant)
780
+ if (chainFromAncestor.length > 0 && stackFromDescendant.length > 0) {
781
+ stackFromDescendant.shift(); // Remove duplicate descendant
782
+ }
783
+ return [...chainFromAncestor, ...stackFromDescendant];
784
+ }
785
+ current = effectParent.get(current);
786
+ }
787
+ }
788
+ return false;
789
+ }
790
+ function withEffectStack(snapshot, fn) {
791
+ const previousStack = stack.slice();
792
+ assignStack(snapshot);
793
+ try {
794
+ return fn();
795
+ }
796
+ finally {
797
+ assignStack(previousStack);
798
+ }
799
+ }
800
+ function getActiveEffect() {
801
+ return stack[0];
802
+ }
803
+ /**
804
+ * Executes a function with a specific effect context
805
+ * @param effect - The effect to use as context
806
+ * @param fn - The function to execute
807
+ * @param keepParent - Whether to keep the parent effect context
808
+ * @returns The result of the function
809
+ */
810
+ function withEffect(effect, fn) {
811
+ if (getRoot(effect) === getRoot(getActiveEffect()))
812
+ return fn();
813
+ stack.unshift(effect);
814
+ try {
815
+ return fn();
816
+ }
817
+ finally {
818
+ const recoveredEffect = stack.shift();
819
+ if (recoveredEffect !== effect)
820
+ throw new ReactiveError('[reactive] Effect stack mismatch');
821
+ }
822
+ }
823
+ function assignStack(values) {
824
+ stack.length = 0;
825
+ stack.push(...values);
826
+ }
827
+
828
+ const objectToProxy = new WeakMap();
829
+ const proxyToObject = new WeakMap();
830
+ function storeProxyRelationship(target, proxy) {
831
+ objectToProxy.set(target, proxy);
832
+ proxyToObject.set(proxy, target);
833
+ }
834
+ function getExistingProxy(target) {
835
+ return objectToProxy.get(target);
836
+ }
837
+ function trackProxyObject(proxy, target) {
838
+ proxyToObject.set(proxy, target);
839
+ }
840
+ function unwrap(obj) {
841
+ let current = obj;
842
+ while (current && typeof current === 'object' && current !== null && proxyToObject.has(current)) {
843
+ current = proxyToObject.get(current);
844
+ }
845
+ return current;
846
+ }
847
+ function isReactive(obj) {
848
+ return proxyToObject.has(obj);
849
+ }
850
+
851
+ function getTrackingDisabled() {
852
+ const active = getActiveEffect();
853
+ if (!active)
854
+ return globalTrackingDisabled;
855
+ return trackingDisabledEffects.has(getRoot(active));
856
+ }
857
+ function setTrackingDisabled(value) {
858
+ const active = getActiveEffect();
859
+ if (!active) {
860
+ setGlobalTrackingDisabled(value);
861
+ return;
862
+ }
863
+ const root = getRoot(active);
864
+ if (value)
865
+ trackingDisabledEffects.add(root);
866
+ else
867
+ trackingDisabledEffects.delete(root);
868
+ }
869
+ /**
870
+ * Marks a property as a dependency of the current effect
871
+ * @param obj - The object containing the property
872
+ * @param prop - The property name (defaults to allProps)
873
+ */
874
+ function dependant(obj, prop = allProps) {
875
+ obj = unwrap(obj);
876
+ const currentActiveEffect = getActiveEffect();
877
+ // Early return if no active effect, tracking disabled, or invalid prop
878
+ if (!currentActiveEffect ||
879
+ getTrackingDisabled() ||
880
+ (typeof prop === 'symbol' && prop !== allProps))
881
+ return;
882
+ registerDependency(obj, prop, currentActiveEffect);
883
+ }
884
+ function registerDependency(obj, prop, currentActiveEffect) {
885
+ let objectWatchers = watchers.get(obj);
886
+ if (!objectWatchers) {
887
+ objectWatchers = new Map();
888
+ watchers.set(obj, objectWatchers);
889
+ }
890
+ let deps = objectWatchers.get(prop);
891
+ if (!deps) {
892
+ deps = new Set();
893
+ objectWatchers.set(prop, deps);
894
+ }
895
+ deps.add(currentActiveEffect);
896
+ // Track which reactive objects this effect is watching
897
+ const effectObjects = effectToReactiveObjects.get(currentActiveEffect);
898
+ if (effectObjects) {
899
+ effectObjects.add(obj);
900
+ }
901
+ else {
902
+ effectToReactiveObjects.set(currentActiveEffect, new Set([obj]));
903
+ }
904
+ }
905
+
1081
906
  /**
1082
907
  * Zone-like async context preservation for reactive effects
1083
908
  *
@@ -1258,6 +1083,50 @@ function formatRoots(roots, limit = 20) {
1258
1083
  const end = names.slice(-10);
1259
1084
  return `${start.join(' → ')} ... (${names.length - 15} more) ... ${end.join(' → ')}`;
1260
1085
  }
1086
+ // Nested map structure for efficient counting and batch cleanup
1087
+ // batchId -> effect root -> obj -> prop -> count
1088
+ let activationRegistry;
1089
+ const activationLog = new Array(100);
1090
+ function getActivationLog() {
1091
+ return activationLog;
1092
+ }
1093
+ function recordActivation(effect, obj, evolution, prop) {
1094
+ const root = getRoot(effect);
1095
+ if (!activationRegistry)
1096
+ return;
1097
+ let effectData = activationRegistry.get(root);
1098
+ if (!effectData) {
1099
+ effectData = new Map();
1100
+ activationRegistry.set(root, effectData);
1101
+ }
1102
+ let objData = effectData.get(obj);
1103
+ if (!objData) {
1104
+ objData = new Map();
1105
+ effectData.set(obj, objData);
1106
+ }
1107
+ const count = (objData.get(prop) ?? 0) + 1;
1108
+ objData.set(prop, count);
1109
+ // Keep a limited history for diagnostics
1110
+ activationLog.unshift({
1111
+ effect,
1112
+ obj,
1113
+ evolution,
1114
+ prop,
1115
+ });
1116
+ activationLog.pop();
1117
+ if (count >= options.maxTriggerPerBatch) {
1118
+ const effectName = root?.name || 'anonymous';
1119
+ const message = `Aggressive trigger detected: effect "${effectName}" triggered ${count} times in the batch by the same cause.`;
1120
+ if (options.maxEffectReaction === 'throw') {
1121
+ throw new ReactiveError(message, {
1122
+ code: ReactiveErrorCode.MaxReactionExceeded,
1123
+ count,
1124
+ effect: effectName,
1125
+ });
1126
+ }
1127
+ options.warn(`[reactive] ${message}`);
1128
+ }
1129
+ }
1261
1130
  /**
1262
1131
  * Registers a debug callback that is called when the current effect is triggered by a dependency change
1263
1132
  *
@@ -1419,6 +1288,7 @@ function addGraphEdge(callerRoot, targetRoot) {
1419
1288
  * @param end - Target node
1420
1289
  * @param exclude - Node to exclude from the path
1421
1290
  * @returns true if a path exists without going through the excluded node
1291
+ * @todo Can be REALLY costly - optimise or make optional or ...
1422
1292
  */
1423
1293
  function hasPathExcluding(start, end, exclude) {
1424
1294
  if (start === end)
@@ -1547,6 +1417,9 @@ function cleanupEffectFromGraph(effect) {
1547
1417
  // Track currently executing effects to prevent re-execution
1548
1418
  // These are all the effects triggered under `activeEffect`
1549
1419
  let batchQueue;
1420
+ function hasBatched(effect) {
1421
+ return batchQueue?.all.has(getRoot(effect));
1422
+ }
1550
1423
  const batchCleanups = new Set();
1551
1424
  /**
1552
1425
  * Computes and caches in-degrees for all effects in the batch
@@ -1703,6 +1576,12 @@ function wouldCreateCycle(callerRoot, targetRoot) {
1703
1576
  * @param immediate - If true, don't create edges in the dependency graph
1704
1577
  */
1705
1578
  function addToBatch(effect, caller, immediate) {
1579
+ const cleanupFn = effect[cleanup$1];
1580
+ if (cleanupFn)
1581
+ cleanupFn();
1582
+ // If the effect was stopped during cleanup (e.g. lazy memoization), don't add it to the batch
1583
+ if (effect[stopped])
1584
+ return;
1706
1585
  if (!batchQueue)
1707
1586
  return;
1708
1587
  const root = getRoot(effect);
@@ -1972,6 +1851,10 @@ function batch(effect, immediate) {
1972
1851
  }
1973
1852
  else {
1974
1853
  // New batch - initialize
1854
+ if (!activationRegistry)
1855
+ activationRegistry = new Map();
1856
+ else
1857
+ throw new Error('Batch already in progress');
1975
1858
  options.beginChain(roots);
1976
1859
  batchQueue = {
1977
1860
  all: new Map(),
@@ -2050,6 +1933,7 @@ function batch(effect, immediate) {
2050
1933
  return firstReturn.value;
2051
1934
  }
2052
1935
  finally {
1936
+ activationRegistry = undefined;
2053
1937
  batchQueue = undefined;
2054
1938
  options.endChain();
2055
1939
  }
@@ -2120,6 +2004,7 @@ function batch(effect, immediate) {
2120
2004
  return firstReturn.value;
2121
2005
  }
2122
2006
  finally {
2007
+ activationRegistry = undefined;
2123
2008
  batchQueue = undefined;
2124
2009
  options.endChain();
2125
2010
  }
@@ -2132,12 +2017,18 @@ function batch(effect, immediate) {
2132
2017
  const atomic = decorator({
2133
2018
  method(original) {
2134
2019
  return function (...args) {
2135
- return batch(markWithRoot(() => original.apply(this, args), original), 'immediate');
2020
+ const atomicEffect = () => original.apply(this, args);
2021
+ // Debug: helpful to have a name
2022
+ Object.defineProperty(atomicEffect, 'name', { value: `atomic(${original.name})` });
2023
+ return batch(atomicEffect, 'immediate');
2136
2024
  };
2137
2025
  },
2138
2026
  default(original) {
2139
2027
  return function (...args) {
2140
- return batch(markWithRoot(() => original.apply(this, args), original), 'immediate');
2028
+ const atomicEffect = () => original.apply(this, args);
2029
+ // Debug: helpful to have a name
2030
+ Object.defineProperty(atomicEffect, 'name', { value: `atomic(${original.name})` });
2031
+ return batch(atomicEffect, 'immediate');
2141
2032
  };
2142
2033
  },
2143
2034
  });
@@ -2171,7 +2062,7 @@ fn, effectOptions) {
2171
2062
  let cleanup = null;
2172
2063
  // capture the parent effect at creation time for ascend
2173
2064
  const parentsForAscend = captureEffectStack();
2174
- const tracked = markWithRoot((cb) => withEffect(runEffect, cb), fn);
2065
+ const tracked = (cb) => withEffect(runEffect, cb);
2175
2066
  const ascend = (cb) => withEffectStack(parentsForAscend, cb);
2176
2067
  let effectStopped = false;
2177
2068
  let hasReacted = false;
@@ -2304,8 +2195,25 @@ fn, effectOptions) {
2304
2195
  cleanupEffectFromGraph(runEffect);
2305
2196
  fr.unregister(stopEffect);
2306
2197
  };
2198
+ function augmentedRv(rv) {
2199
+ Object.defineProperty(rv, stopped, {
2200
+ get() {
2201
+ return effectStopped;
2202
+ },
2203
+ });
2204
+ Object.defineProperty(rv, cleanup$1, {
2205
+ value: () => {
2206
+ if (cleanup) {
2207
+ const prevCleanup = cleanup;
2208
+ cleanup = null;
2209
+ withEffect(undefined, () => prevCleanup());
2210
+ }
2211
+ },
2212
+ });
2213
+ return rv;
2214
+ }
2307
2215
  if (isRootEffect) {
2308
- const callIfCollected = () => stopEffect();
2216
+ const callIfCollected = augmentedRv(() => stopEffect());
2309
2217
  fr.register(callIfCollected, () => {
2310
2218
  stopEffect();
2311
2219
  options.garbageCollected(fn);
@@ -2318,14 +2226,14 @@ fn, effectOptions) {
2318
2226
  children = new Set();
2319
2227
  effectChildren.set(parent, children);
2320
2228
  }
2321
- const subEffectCleanup = () => {
2229
+ const subEffectCleanup = augmentedRv(() => {
2322
2230
  children.delete(subEffectCleanup);
2323
2231
  if (children.size === 0) {
2324
2232
  effectChildren.delete(parent);
2325
2233
  }
2326
2234
  // Execute this child effect cleanup (which triggers its own mainCleanup)
2327
2235
  stopEffect();
2328
- };
2236
+ });
2329
2237
  children.add(subEffectCleanup);
2330
2238
  return subEffectCleanup;
2331
2239
  }
@@ -2489,7 +2397,11 @@ function collectEffects(obj, evolution, effects, objectWatchers, ...keyChains) {
2489
2397
  options.skipRunningEffect(effect, runningChain);
2490
2398
  continue;
2491
2399
  }
2492
- effects.add(effect);
2400
+ if (!effects.has(effect)) {
2401
+ effects.add(effect);
2402
+ if (!hasBatched(effect))
2403
+ recordActivation(effect, obj, evolution, key);
2404
+ }
2493
2405
  const trackers = effectTrackers.get(effect);
2494
2406
  recordTriggerLink(sourceEffect, effect, obj, key, evolution);
2495
2407
  if (trackers) {
@@ -2557,6 +2469,7 @@ function touchedOpaque(obj, evolution, prop) {
2557
2469
  continue;
2558
2470
  }
2559
2471
  effects.add(effect);
2472
+ recordActivation(effect, obj, evolution, prop);
2560
2473
  const trackers = effectTrackers.get(effect);
2561
2474
  recordTriggerLink(sourceEffect, effect, obj, prop, evolution);
2562
2475
  if (trackers) {
@@ -2919,13 +2832,13 @@ const reactiveHandlers = {
2919
2832
  const receiverDesc = Object.getOwnPropertyDescriptor(unwrappedReceiver, prop);
2920
2833
  const targetDesc = Object.getOwnPropertyDescriptor(unwrappedObj, prop);
2921
2834
  const desc = receiverDesc || targetDesc;
2922
- // If it's a getter-only accessor (has getter but no setter), read without tracking
2923
- // to avoid breaking memoization invalidation when the getter calls memoized functions
2835
+ // We *need* to use `receiver` and not `unwrappedObj` here, otherwise we break
2836
+ // the dependency tracking for memoized getters
2924
2837
  if (desc?.get && !desc?.set) {
2925
- oldVal = withEffect(undefined, () => Reflect.get(unwrappedObj, prop, unwrappedReceiver));
2838
+ oldVal = withEffect(undefined, () => Reflect.get(unwrappedObj, prop, receiver));
2926
2839
  }
2927
2840
  else {
2928
- oldVal = Reflect.get(unwrappedObj, prop, unwrappedReceiver);
2841
+ oldVal = withEffect(undefined, () => Reflect.get(unwrappedObj, prop, receiver));
2929
2842
  }
2930
2843
  }
2931
2844
  if (objectsWithDeepWatchers.has(obj)) {
@@ -3215,12 +3128,12 @@ function watchObject(value, changed, { immediate = false, deep = false } = {}) {
3215
3128
  const myParentEffect = getActiveEffect();
3216
3129
  if (deep)
3217
3130
  return deepWatch(value, changed, { immediate });
3218
- return effect(markWithRoot(function watchObjectEffect() {
3131
+ return effect(function watchObjectEffect() {
3219
3132
  dependant(value);
3220
3133
  if (immediate)
3221
3134
  withEffect(myParentEffect, () => changed(value));
3222
3135
  immediate = true;
3223
- }, changed));
3136
+ });
3224
3137
  }
3225
3138
  function watchCallBack(value, changed, { immediate = false, deep = false } = {}) {
3226
3139
  const myParentEffect = getActiveEffect();
@@ -3229,7 +3142,7 @@ function watchCallBack(value, changed, { immediate = false, deep = false } = {})
3229
3142
  const cbCleanup = effect(markWithRoot(function watchCallBackEffect(access) {
3230
3143
  const newValue = value(access);
3231
3144
  if (oldValue !== newValue)
3232
- withEffect(myParentEffect, markWithRoot(() => {
3145
+ withEffect(myParentEffect, () => {
3233
3146
  if (oldValue === unsetYet) {
3234
3147
  if (immediate)
3235
3148
  changed(newValue);
@@ -3240,9 +3153,9 @@ function watchCallBack(value, changed, { immediate = false, deep = false } = {})
3240
3153
  if (deep) {
3241
3154
  if (deepCleanup)
3242
3155
  deepCleanup();
3243
- deepCleanup = deepWatch(newValue, markWithRoot((value) => changed(value, value), changed));
3156
+ deepCleanup = deepWatch(newValue, (value) => changed(value, value));
3244
3157
  }
3245
- }, changed));
3158
+ });
3246
3159
  }, value));
3247
3160
  return () => {
3248
3161
  cbCleanup();
@@ -3315,9 +3228,9 @@ function cleanedBy(obj, cleanupFn) {
3315
3228
  */
3316
3229
  function derived(compute) {
3317
3230
  const rv = { value: undefined };
3318
- return cleanedBy(rv, untracked(() => effect(markWithRoot(function derivedEffect(access) {
3231
+ return cleanedBy(rv, untracked(() => effect(function derivedEffect(access) {
3319
3232
  rv.value = compute(access);
3320
- }, compute))));
3233
+ })));
3321
3234
  }
3322
3235
 
3323
3236
  /**
@@ -3345,7 +3258,6 @@ function* makeReactiveEntriesIterator(iterator) {
3345
3258
  const native$2 = Symbol('native');
3346
3259
  const isArray = Array.isArray;
3347
3260
  Array.isArray = ((value) => isArray(value) ||
3348
- // biome-ignore lint/suspicious/useIsArray: We are defining it
3349
3261
  (value &&
3350
3262
  typeof value === 'object' &&
3351
3263
  prototypeForwarding in value &&
@@ -3774,6 +3686,7 @@ function reduced(inputs, compute) {
3774
3686
  }
3775
3687
 
3776
3688
  const memoizedRegistry = new WeakMap();
3689
+ const wrapperRegistry = new WeakMap();
3777
3690
  function getBranch(tree, key) {
3778
3691
  tree.branches ?? (tree.branches = new WeakMap());
3779
3692
  let branch = tree.branches.get(key);
@@ -3794,15 +3707,30 @@ function memoizeFunction(fn) {
3794
3707
  if (localArgs.some((arg) => !(arg && ['object', 'symbol', 'function'].includes(typeof arg))))
3795
3708
  throw new Error('memoize expects non-null object arguments');
3796
3709
  let node = cacheRoot;
3710
+ // Note: decorators add `this` as first argument
3797
3711
  for (const arg of localArgs) {
3798
3712
  node = getBranch(node, arg);
3799
3713
  }
3800
3714
  dependant(node, 'memoize');
3801
- if ('result' in node)
3715
+ if ('result' in node) {
3716
+ if (options.onMemoizationDiscrepancy) {
3717
+ const wasVerification = options.isVerificationRun;
3718
+ options.isVerificationRun = true;
3719
+ try {
3720
+ const fresh = untracked(() => fn(...localArgs));
3721
+ if (!deepCompare(node.result, fresh)) {
3722
+ options.onMemoizationDiscrepancy(node.result, fresh, fn, localArgs, 'calculation');
3723
+ }
3724
+ }
3725
+ finally {
3726
+ options.isVerificationRun = wasVerification;
3727
+ }
3728
+ }
3802
3729
  return node.result;
3730
+ }
3803
3731
  // Create memoize internal effect to track dependencies and invalidate cache
3804
3732
  // Use untracked to prevent the effect creation from being affected by parent effects
3805
- node.cleanup = root(() => effect(markWithRoot(() => {
3733
+ node.cleanup = root(() => effect(() => {
3806
3734
  // Execute the function and track its dependencies
3807
3735
  // The function execution will automatically track dependencies on reactive objects
3808
3736
  node.result = fn(...localArgs);
@@ -3810,8 +3738,27 @@ function memoizeFunction(fn) {
3810
3738
  // When dependencies change, clear the cache and notify consumers
3811
3739
  delete node.result;
3812
3740
  touched1(node, { type: 'invalidate', prop: localArgs }, 'memoize');
3741
+ // Lazy memoization: stop the effect so it doesn't re-run immediately.
3742
+ // It will be re-created on next access.
3743
+ if (node.cleanup) {
3744
+ node.cleanup();
3745
+ node.cleanup = undefined;
3746
+ }
3813
3747
  };
3814
- }, fnRoot), { opaque: true }));
3748
+ }, { opaque: true }));
3749
+ if (options.onMemoizationDiscrepancy) {
3750
+ const wasVerification = options.isVerificationRun;
3751
+ options.isVerificationRun = true;
3752
+ try {
3753
+ const fresh = untracked(() => fn(...localArgs));
3754
+ if (!deepCompare(node.result, fresh)) {
3755
+ options.onMemoizationDiscrepancy(node.result, fresh, fn, localArgs, 'comparison');
3756
+ }
3757
+ }
3758
+ finally {
3759
+ options.isVerificationRun = wasVerification;
3760
+ }
3761
+ }
3815
3762
  return node.result;
3816
3763
  }, fn);
3817
3764
  memoizedRegistry.set(fnRoot, memoized);
@@ -3819,19 +3766,37 @@ function memoizeFunction(fn) {
3819
3766
  return memoized;
3820
3767
  }
3821
3768
  const memoize = decorator({
3822
- getter(original, propertyKey) {
3823
- const memoized = memoizeFunction(markWithRoot(renamed((that) => {
3824
- return original.call(that);
3825
- }, `${String(this.constructor.name)}.${String(propertyKey)}`), original));
3769
+ getter(original, target, propertyKey) {
3826
3770
  return function () {
3771
+ let wrapper = wrapperRegistry.get(original);
3772
+ if (!wrapper) {
3773
+ wrapper = markWithRoot(renamed((that) => {
3774
+ return original.call(that);
3775
+ }, `${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(propertyKey)}`), {
3776
+ method: original,
3777
+ propertyKey,
3778
+ ...(original[rootFunction] ? { [rootFunction]: original[rootFunction] } : {}),
3779
+ });
3780
+ wrapperRegistry.set(original, wrapper);
3781
+ }
3782
+ const memoized = memoizeFunction(wrapper);
3827
3783
  return memoized(this);
3828
3784
  };
3829
3785
  },
3830
- method(original, name) {
3831
- const memoized = memoizeFunction(markWithRoot(renamed((that, ...args) => {
3832
- return original.call(that, ...args);
3833
- }, `${String(this.constructor.name)}.${String(name)}`), original));
3786
+ method(original, target, name) {
3834
3787
  return function (...args) {
3788
+ let wrapper = wrapperRegistry.get(original);
3789
+ if (!wrapper) {
3790
+ wrapper = markWithRoot(renamed((that, ...args) => {
3791
+ return original.call(that, ...args);
3792
+ }, `${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(name)}`), {
3793
+ method: original,
3794
+ propertyKey: name,
3795
+ ...(original[rootFunction] ? { [rootFunction]: original[rootFunction] } : {}),
3796
+ });
3797
+ wrapperRegistry.set(original, wrapper);
3798
+ }
3799
+ const memoized = memoizeFunction(wrapper);
3835
3800
  return memoized(this, ...args);
3836
3801
  };
3837
3802
  },
@@ -4241,6 +4206,17 @@ function register(keyFn, initial) {
4241
4206
  return new RegisterClass(keyFn, initial);
4242
4207
  }
4243
4208
 
4209
+ /**
4210
+ * Maps projection effects (item effects) to their projection context
4211
+ */
4212
+ const effectProjectionMetadata = new WeakMap();
4213
+ /**
4214
+ * Returns the projection context of the currently running effect, if any.
4215
+ */
4216
+ function getActiveProjection() {
4217
+ const active = getActiveEffect();
4218
+ return active ? effectProjectionMetadata.get(active) : undefined;
4219
+ }
4244
4220
  function defineAccessValue(access) {
4245
4221
  Object.defineProperty(access, 'value', {
4246
4222
  get: access.get,
@@ -4249,7 +4225,15 @@ function defineAccessValue(access) {
4249
4225
  enumerable: true,
4250
4226
  });
4251
4227
  }
4252
- function makeCleanup(target, effectMap, onDispose) {
4228
+ function makeCleanup(target, effectMap, onDispose, metadata) {
4229
+ if (metadata) {
4230
+ Object.defineProperty(target, projectionInfo, {
4231
+ value: metadata,
4232
+ writable: false,
4233
+ enumerable: false,
4234
+ configurable: true,
4235
+ });
4236
+ }
4253
4237
  return cleanedBy(target, () => {
4254
4238
  onDispose();
4255
4239
  for (const stop of effectMap.values())
@@ -4272,6 +4256,8 @@ function projectArray(source, apply) {
4272
4256
  Reflect.deleteProperty(target, index);
4273
4257
  }
4274
4258
  }
4259
+ const parent = getActiveProjection();
4260
+ const depth = parent ? parent.depth + 1 : 0;
4275
4261
  const cleanupLength = effect(function projectArrayLengthEffect({ ascend }) {
4276
4262
  const length = observedSource.length;
4277
4263
  normalizeTargetLength(length);
@@ -4294,6 +4280,14 @@ function projectArray(source, apply) {
4294
4280
  const produced = apply(accessBase, target);
4295
4281
  target[index] = produced;
4296
4282
  });
4283
+ setEffectName(stop, `project[${depth}]:${index}`);
4284
+ effectProjectionMetadata.set(stop, {
4285
+ source: observedSource,
4286
+ key: index,
4287
+ target,
4288
+ depth,
4289
+ parent,
4290
+ });
4297
4291
  indexEffects.set(i, stop);
4298
4292
  });
4299
4293
  }
@@ -4301,7 +4295,13 @@ function projectArray(source, apply) {
4301
4295
  if (index >= length)
4302
4296
  disposeIndex(index);
4303
4297
  });
4304
- return makeCleanup(target, indexEffects, () => cleanupLength());
4298
+ return makeCleanup(target, indexEffects, () => cleanupLength(), {
4299
+ source: observedSource,
4300
+ target,
4301
+ apply,
4302
+ depth,
4303
+ parent,
4304
+ });
4305
4305
  }
4306
4306
  function projectRegister(source, apply) {
4307
4307
  const observedSource = reactive(source);
@@ -4316,6 +4316,8 @@ function projectRegister(source, apply) {
4316
4316
  target.delete(key);
4317
4317
  }
4318
4318
  }
4319
+ const parent = getActiveProjection();
4320
+ const depth = parent ? parent.depth + 1 : 0;
4319
4321
  const cleanupKeys = effect(function projectRegisterEffect({ ascend }) {
4320
4322
  const keys = new Set();
4321
4323
  for (const key of observedSource.mapKeys())
@@ -4340,6 +4342,14 @@ function projectRegister(source, apply) {
4340
4342
  const produced = apply(accessBase, target);
4341
4343
  target.set(key, produced);
4342
4344
  });
4345
+ setEffectName(stop, `project[${depth}]:${String(key)}`);
4346
+ effectProjectionMetadata.set(stop, {
4347
+ source: observedSource,
4348
+ key,
4349
+ target,
4350
+ depth,
4351
+ parent,
4352
+ });
4343
4353
  keyEffects.set(key, stop);
4344
4354
  });
4345
4355
  }
@@ -4347,7 +4357,13 @@ function projectRegister(source, apply) {
4347
4357
  if (!keys.has(key))
4348
4358
  disposeKey(key);
4349
4359
  });
4350
- return makeCleanup(target, keyEffects, () => cleanupKeys());
4360
+ return makeCleanup(target, keyEffects, () => cleanupKeys(), {
4361
+ source: observedSource,
4362
+ target,
4363
+ apply,
4364
+ depth,
4365
+ parent,
4366
+ });
4351
4367
  }
4352
4368
  function projectRecord(source, apply) {
4353
4369
  const observedSource = reactive(source);
@@ -4361,6 +4377,8 @@ function projectRecord(source, apply) {
4361
4377
  Reflect.deleteProperty(target, key);
4362
4378
  }
4363
4379
  }
4380
+ const parent = getActiveProjection();
4381
+ const depth = parent ? parent.depth + 1 : 0;
4364
4382
  const cleanupKeys = effect(function projectRecordEffect({ ascend }) {
4365
4383
  const keys = new Set();
4366
4384
  for (const key in observedSource)
@@ -4386,6 +4404,14 @@ function projectRecord(source, apply) {
4386
4404
  const produced = apply(accessBase, target);
4387
4405
  target[sourceKey] = produced;
4388
4406
  });
4407
+ setEffectName(stop, `project[${depth}]:${String(key)}`);
4408
+ effectProjectionMetadata.set(stop, {
4409
+ source: observedSource,
4410
+ key,
4411
+ target,
4412
+ depth,
4413
+ parent,
4414
+ });
4389
4415
  keyEffects.set(key, stop);
4390
4416
  });
4391
4417
  }
@@ -4393,7 +4419,13 @@ function projectRecord(source, apply) {
4393
4419
  if (!keys.has(key))
4394
4420
  disposeKey(key);
4395
4421
  });
4396
- return makeCleanup(target, keyEffects, () => cleanupKeys());
4422
+ return makeCleanup(target, keyEffects, () => cleanupKeys(), {
4423
+ source: observedSource,
4424
+ target,
4425
+ apply,
4426
+ depth,
4427
+ parent,
4428
+ });
4397
4429
  }
4398
4430
  function projectMap(source, apply) {
4399
4431
  const observedSource = reactive(source);
@@ -4408,6 +4440,8 @@ function projectMap(source, apply) {
4408
4440
  target.delete(key);
4409
4441
  }
4410
4442
  }
4443
+ const parent = getActiveProjection();
4444
+ const depth = parent ? parent.depth + 1 : 0;
4411
4445
  const cleanupKeys = effect(function projectMapEffect({ ascend }) {
4412
4446
  const keys = new Set();
4413
4447
  for (const key of observedSource.keys())
@@ -4432,6 +4466,14 @@ function projectMap(source, apply) {
4432
4466
  const produced = apply(accessBase, target);
4433
4467
  target.set(key, produced);
4434
4468
  });
4469
+ setEffectName(stop, `project[${depth}]:${String(key)}`);
4470
+ effectProjectionMetadata.set(stop, {
4471
+ source: observedSource,
4472
+ key,
4473
+ target,
4474
+ depth,
4475
+ parent,
4476
+ });
4435
4477
  keyEffects.set(key, stop);
4436
4478
  });
4437
4479
  }
@@ -4439,7 +4481,13 @@ function projectMap(source, apply) {
4439
4481
  if (!keys.has(key))
4440
4482
  disposeKey(key);
4441
4483
  });
4442
- return makeCleanup(target, keyEffects, () => cleanupKeys());
4484
+ return makeCleanup(target, keyEffects, () => cleanupKeys(), {
4485
+ source: observedSource,
4486
+ target,
4487
+ apply,
4488
+ depth,
4489
+ parent,
4490
+ });
4443
4491
  }
4444
4492
  function projectCore(source, apply) {
4445
4493
  if (Array.isArray(source))
@@ -4587,7 +4635,7 @@ class ReactiveWeakMap {
4587
4635
  Object.defineProperties(this, {
4588
4636
  [native$1]: { value: original },
4589
4637
  [prototypeForwarding]: { value: original },
4590
- content: { value: Symbol('content') },
4638
+ content: { value: Symbol('WeakMapContent') },
4591
4639
  [Symbol.toStringTag]: { value: 'ReactiveWeakMap' },
4592
4640
  });
4593
4641
  }
@@ -4627,7 +4675,7 @@ class ReactiveMap {
4627
4675
  Object.defineProperties(this, {
4628
4676
  [native$1]: { value: original },
4629
4677
  [prototypeForwarding]: { value: original },
4630
- content: { value: Symbol('content') },
4678
+ content: { value: Symbol('MapContent') },
4631
4679
  [Symbol.toStringTag]: { value: 'ReactiveMap' },
4632
4680
  });
4633
4681
  }
@@ -4722,7 +4770,7 @@ class ReactiveWeakSet {
4722
4770
  Object.defineProperties(this, {
4723
4771
  [native]: { value: original },
4724
4772
  [prototypeForwarding]: { value: original },
4725
- content: { value: Symbol('content') },
4773
+ content: { value: Symbol('WeakSetContent') },
4726
4774
  [Symbol.toStringTag]: { value: 'ReactiveWeakSet' },
4727
4775
  });
4728
4776
  }
@@ -4757,7 +4805,7 @@ class ReactiveSet {
4757
4805
  Object.defineProperties(this, {
4758
4806
  [native]: { value: original },
4759
4807
  [prototypeForwarding]: { value: original },
4760
- content: { value: Symbol('content') },
4808
+ content: { value: Symbol('SetContent') },
4761
4809
  [Symbol.toStringTag]: { value: 'ReactiveSet' },
4762
4810
  });
4763
4811
  }
@@ -4853,5 +4901,5 @@ const profileInfo = {
4853
4901
  nonReactiveObjects,
4854
4902
  };
4855
4903
 
4856
- export { derived as A, unreactive as B, watch as C, mapped as D, reduced as E, memoize as F, immutables as G, isNonReactive as H, IterableWeakMap as I, registerNativeReactivity as J, project as K, isReactive as L, ReactiveBase as M, reactive as N, unwrap as O, organize as P, organized as Q, ReadOnlyError as R, Register as S, register as T, options as U, ReactiveError as V, isZoneEnabled as W, setZoneEnabled as X, IterableWeakSet as a, touched1 as b, buildReactivityGraph as c, registerObjectForDebug as d, enableDevTools as e, setObjectName as f, getState as g, deepWatch as h, isDevtoolsEnabled as i, addBatchCleanup as j, atomic as k, batch as l, mixin as m, biDi as n, defer as o, profileInfo as p, effect as q, registerEffectForDebug as r, setEffectName as s, touched as t, getActiveEffect as u, root as v, trackEffect as w, untracked as x, cleanedBy as y, cleanup as z };
4857
- //# sourceMappingURL=index-79Kk8D6e.esm.js.map
4904
+ export { mixin as A, organize as B, organized as C, profileInfo as D, project as E, reactive as F, reduced as G, register as H, IterableWeakMap as I, registerEffectForDebug as J, registerNativeReactivity as K, registerObjectForDebug as L, root as M, setEffectName as N, setObjectName as O, setZoneEnabled as P, touched as Q, ReactiveBase as R, touched1 as S, trackEffect as T, unreactive as U, untracked as V, unwrap as W, watch as X, IterableWeakSet as a, ReadOnlyError as b, Register as c, addBatchCleanup as d, atomic as e, batch as f, biDi as g, buildReactivityGraph as h, cleanedBy as i, cleanup as j, deepWatch as k, defer as l, derived as m, effect as n, enableDevTools as o, getActivationLog as p, getActiveEffect as q, getActiveProjection as r, getState as s, immutables as t, isDevtoolsEnabled as u, isNonReactive as v, isReactive as w, isZoneEnabled as x, mapped as y, memoize as z };
4905
+ //# sourceMappingURL=index-DiP0RXoZ.esm.js.map