mutts 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +2 -1
  2. package/dist/browser.d.ts +2 -0
  3. package/dist/browser.esm.js +70 -0
  4. package/dist/browser.esm.js.map +1 -0
  5. package/dist/browser.js +161 -0
  6. package/dist/browser.js.map +1 -0
  7. package/dist/chunks/{index-Cvxdw6Ax.js → index-BFYK02LG.js} +5377 -4059
  8. package/dist/chunks/index-BFYK02LG.js.map +1 -0
  9. package/dist/chunks/{index-qiWwozOc.esm.js → index-CNR6QRUl.esm.js} +5247 -3963
  10. package/dist/chunks/index-CNR6QRUl.esm.js.map +1 -0
  11. package/dist/mutts.umd.js +1 -1
  12. package/dist/mutts.umd.js.map +1 -1
  13. package/dist/mutts.umd.min.js +1 -1
  14. package/dist/mutts.umd.min.js.map +1 -1
  15. package/dist/node.d.ts +2 -0
  16. package/dist/node.esm.js +45 -0
  17. package/dist/node.esm.js.map +1 -0
  18. package/dist/node.js +136 -0
  19. package/dist/node.js.map +1 -0
  20. package/docs/ai/api-reference.md +0 -2
  21. package/docs/ai/manual.md +14 -95
  22. package/docs/reactive/advanced.md +7 -111
  23. package/docs/reactive/collections.md +0 -125
  24. package/docs/reactive/core.md +27 -24
  25. package/docs/reactive/debugging.md +168 -0
  26. package/docs/reactive/project.md +1 -1
  27. package/docs/reactive/scan.md +78 -0
  28. package/docs/reactive.md +8 -6
  29. package/docs/std-decorators.md +1 -0
  30. package/docs/zone.md +88 -0
  31. package/package.json +47 -65
  32. package/src/async/browser.ts +87 -0
  33. package/src/async/index.ts +8 -0
  34. package/src/async/node.ts +46 -0
  35. package/src/decorator.ts +15 -9
  36. package/src/destroyable.ts +4 -4
  37. package/src/index.ts +54 -0
  38. package/src/indexable.ts +42 -0
  39. package/src/mixins.ts +2 -2
  40. package/src/reactive/array.ts +149 -141
  41. package/src/reactive/buffer.ts +168 -0
  42. package/src/reactive/change.ts +3 -3
  43. package/src/reactive/debug.ts +1 -1
  44. package/src/reactive/deep-touch.ts +1 -1
  45. package/src/reactive/deep-watch.ts +1 -1
  46. package/src/reactive/effect-context.ts +15 -91
  47. package/src/reactive/effects.ts +138 -170
  48. package/src/reactive/index.ts +10 -13
  49. package/src/reactive/interface.ts +20 -33
  50. package/src/reactive/map.ts +48 -61
  51. package/src/reactive/memoize.ts +87 -31
  52. package/src/reactive/project.ts +43 -22
  53. package/src/reactive/proxy.ts +18 -43
  54. package/src/reactive/record.ts +3 -3
  55. package/src/reactive/register.ts +5 -7
  56. package/src/reactive/registry.ts +59 -0
  57. package/src/reactive/set.ts +42 -56
  58. package/src/reactive/tracking.ts +5 -62
  59. package/src/reactive/types.ts +79 -19
  60. package/src/std-decorators.ts +9 -9
  61. package/src/utils.ts +203 -19
  62. package/src/zone.ts +127 -0
  63. package/dist/chunks/_tslib-BgjropY9.js +0 -81
  64. package/dist/chunks/_tslib-BgjropY9.js.map +0 -1
  65. package/dist/chunks/_tslib-Mzh1rNsX.esm.js +0 -75
  66. package/dist/chunks/_tslib-Mzh1rNsX.esm.js.map +0 -1
  67. package/dist/chunks/decorator-DLvrD0UF.js +0 -265
  68. package/dist/chunks/decorator-DLvrD0UF.js.map +0 -1
  69. package/dist/chunks/decorator-DqiszP7i.esm.js +0 -253
  70. package/dist/chunks/decorator-DqiszP7i.esm.js.map +0 -1
  71. package/dist/chunks/index-Cvxdw6Ax.js.map +0 -1
  72. package/dist/chunks/index-qiWwozOc.esm.js.map +0 -1
  73. package/dist/decorator.d.ts +0 -107
  74. package/dist/decorator.esm.js +0 -2
  75. package/dist/decorator.esm.js.map +0 -1
  76. package/dist/decorator.js +0 -11
  77. package/dist/decorator.js.map +0 -1
  78. package/dist/destroyable.d.ts +0 -90
  79. package/dist/destroyable.esm.js +0 -109
  80. package/dist/destroyable.esm.js.map +0 -1
  81. package/dist/destroyable.js +0 -116
  82. package/dist/destroyable.js.map +0 -1
  83. package/dist/eventful.d.ts +0 -20
  84. package/dist/eventful.esm.js +0 -66
  85. package/dist/eventful.esm.js.map +0 -1
  86. package/dist/eventful.js +0 -68
  87. package/dist/eventful.js.map +0 -1
  88. package/dist/index.d.ts +0 -19
  89. package/dist/index.esm.js +0 -8
  90. package/dist/index.esm.js.map +0 -1
  91. package/dist/index.js +0 -95
  92. package/dist/index.js.map +0 -1
  93. package/dist/indexable.d.ts +0 -243
  94. package/dist/indexable.esm.js +0 -285
  95. package/dist/indexable.esm.js.map +0 -1
  96. package/dist/indexable.js +0 -291
  97. package/dist/indexable.js.map +0 -1
  98. package/dist/promiseChain.d.ts +0 -21
  99. package/dist/promiseChain.esm.js +0 -78
  100. package/dist/promiseChain.esm.js.map +0 -1
  101. package/dist/promiseChain.js +0 -80
  102. package/dist/promiseChain.js.map +0 -1
  103. package/dist/reactive.d.ts +0 -885
  104. package/dist/reactive.esm.js +0 -5
  105. package/dist/reactive.esm.js.map +0 -1
  106. package/dist/reactive.js +0 -59
  107. package/dist/reactive.js.map +0 -1
  108. package/dist/std-decorators.d.ts +0 -52
  109. package/dist/std-decorators.esm.js +0 -196
  110. package/dist/std-decorators.esm.js.map +0 -1
  111. package/dist/std-decorators.js +0 -204
  112. package/dist/std-decorators.js.map +0 -1
  113. package/src/reactive/mapped.ts +0 -129
  114. package/src/reactive/zone.ts +0 -208
@@ -105,7 +105,7 @@ const result = memoized(user)
105
105
  **5. Map over arrays:**
106
106
  ```typescript
107
107
  const source = reactive([1, 2, 3])
108
- const doubled = mapped(source, x => x * 2)
108
+ const doubled = project(source, ({ value }) => value * 2)
109
109
  // [2, 4, 6]
110
110
 
111
111
  source.push(4) // doubled automatically becomes [2, 4, 6, 8]
@@ -446,13 +446,13 @@ state.c = 15 // Does NOT trigger effect
446
446
 
447
447
  ### Async Effects and the `access` Parameter
448
448
 
449
- The `effect` function provides a special `access` parameter with `tracked` and `ascend` functions that restore the active effect context for dependency tracking in asynchronous operations. This is crucial because async functions lose the global active effect context when they yield control.
449
+ The `effect` function provides a special `access` parameter with `tracked` and `ascend` functions that restore the active effect context for dependency tracking in asynchronous operations.
450
450
 
451
- #### The Problem with Async Effects
451
+ In modern `mutts`, this is powered by the **Zone system**. When you use `configureAsyncZone()`, the active effect context is automatically preserved across `await` points and timers, making manual use of `tracked` optional for these cases.
452
452
 
453
- When an effect function is synchronous, reactive property access automatically tracks dependencies, however, in async functions, the active effect context is lost when the function yields control.
453
+ #### The Problem with Async Effects
454
454
 
455
- The `access.tracked()` function restores the active effect context for dependency tracking.
455
+ Traditionally, in JavaScript, async functions lose their context when they yield control.
456
456
 
457
457
  #### Understanding the active effect context
458
458
 
@@ -463,37 +463,42 @@ The reactive system uses a global active effect variable to track which effect i
463
463
  effect(() => {
464
464
  // active effect = this effect
465
465
  const value = state.count // ✅ Tracked (active effect is set)
466
- // active effect = this effect (still set)
467
466
  const another = state.name // ✅ Tracked (active effect is still set)
468
467
  })
469
468
 
470
- // Async effect - active effect is lost after await
469
+ // Async effect WITHOUT configureAsyncZone() - context is lost after await
471
470
  effect(async () => {
472
- // active effect = this effect
473
- const value = state.count // ✅ Tracked (active effect is set)
471
+ const value = state.count // ✅ Tracked
474
472
 
475
- await someAsyncOperation() // Function exits, active effect = undefined
473
+ await someAsyncOperation()
476
474
 
477
- // active effect = undefined (lost!)
478
- const another = state.name // ❌ NOT tracked (no active effect)
475
+ // context is lost!
476
+ const another = state.name // ❌ NOT tracked
479
477
  })
480
478
 
481
- // Async effect with access.tracked() - active effect is restored
482
- effect(async ({ tracked }) => {
483
- // active effect = this effect
484
- const value = state.count // ✅ Tracked (active effect is set)
479
+ // Async effect WITH configureAsyncZone() - context is preserved
480
+ effect(async () => {
481
+ const value = state.count // ✅ Tracked
482
+
483
+ await someAsyncOperation()
485
484
 
486
- await someAsyncOperation() // Function exits, active effect = undefined
485
+ // context is automatically restored!
486
+ const another = state.name // ✅ Tracked
487
+ })
488
+
489
+ // Using access.tracked() for manual restoration
490
+ effect(async ({ tracked }) => {
491
+ await someAsyncOperation()
487
492
 
488
- // tracked() temporarily restores active effect for the callback
489
- const another = tracked(() => state.name) // ✅ Tracked (active effect restored)
493
+ // Useful for non-patched APIs or explicit scoping
494
+ const another = tracked(() => state.name) // ✅ Tracked
490
495
  })
491
496
  ```
492
497
 
493
- #### Key Benefits of `access.tracked()` in Async Effects
498
+ #### Key Benefits of the Zone System
494
499
 
495
- 1. **Restored Context**: `access.tracked()` temporarily restores the active effect context for dependency tracking
496
- 2. **Consistent Tracking**: Reactive property access works the same way before and after `await`
500
+ 1. **Automatic Restoration**: With `configureAsyncZone()`, most native async APIs (Promises, timers) automatically preserve the reactive context.
501
+ 2. **Manual Control**: `access.tracked()` allows you to manually "passport" the context into third-party libraries or unmanaged callbacks.
497
502
 
498
503
  ### Using `ascend` for Parent Effect Tracking
499
504
 
@@ -518,8 +523,6 @@ effect(({ ascend }) => {
518
523
  }
519
524
  })
520
525
  ```
521
- <|tool▁calls▁begin|><|tool▁call▁begin|>
522
- grep
523
526
 
524
527
  **When to use `ascend`:**
525
528
  - When creating child effects that should be cleaned up with their parent
@@ -0,0 +1,168 @@
1
+ # Debugging Tools
2
+
3
+ The `mutts` reactive system provides several built-in tools to help track down synchronization issues, dependency leaks, and infinite loops. These tools are primarily exposed through the `reactiveOptions` object.
4
+
5
+ > [!WARNING]
6
+ > **Performance Cost**: Most debugging tools incur a significant performance overhead. They should be enabled only during development or within test environments. Re-running computations for discrepancy detection, in particular, effectively doubles the cost of reactive updates.
7
+
8
+ ## The `reactiveOptions` Reference
9
+
10
+ The `reactiveOptions` object (exported from `mutts/reactive`) allows you to hook into the reactive system's internals.
11
+
12
+ ```typescript
13
+ import { reactiveOptions } from 'mutts/reactive';
14
+ ```
15
+
16
+ ### Lifecycle Hooks
17
+
18
+ These hooks are called during the execution of effects and computed values.
19
+
20
+ - **`enter(effect: Function)`**: Called when an effect or memoized function starts executing.
21
+ - **`leave(effect: Function)`**: Called when an effect or memoized function finishes.
22
+ - **`touched(obj: any, evolution: Evolution)`**: Called whenever a reactive object is "touched" (accessed or modified). This is the lowest-level hook for observing system activity.
23
+
24
+ ### Effect Chaining & Batching
25
+
26
+ - **`beginChain(targets: Function[]) / endChain()`**: Called when a batch of effects starts and ends its execution.
27
+ - **`maxEffectChain`**: (Default: `100`) Limits the depth of synchronous effect triggering to prevent stack overflows.
28
+ - **`maxTriggerPerBatch`**: (Default: `10`) Limits how many times a single effect can be triggered within the same batch. Useful for detecting aggressive re-computation or infinite cycles in `cycleHandling: 'none'` mode.
29
+
30
+ ## Cycle Detection
31
+
32
+ `mutts` automatically detects circular dependencies (e.g., Effect A updates State X, which triggers Effect B, which updates State Y, which triggers Effect A).
33
+
34
+ ### Configuration
35
+
36
+ You can control how cycles are handled via `reactiveOptions.cycleHandling`:
37
+
38
+ - **`'none'`** (Default): High-performance FIFO mode. Disables the dependency graph and topological sorting.
39
+ - **`'throw'`**: Throws a `ReactiveError` with a detailed path.
40
+ - **`'warn'`**: Logs a warning but breaks the cycle to allow the application to continue.
41
+ - **`'break'`**: Silently breaks the cycle.
42
+ - **`'strict'`**: Performs a graph check *before* execution to prevent cycles from even starting. This has the highest overhead.
43
+
44
+ ### Topological vs. Flat Mode Detection
45
+
46
+ | Mode | `cycleHandling` | Detection Method | Error Code |
47
+ | :--- | :--- | :--- | :--- |
48
+ | **Topological** | `'throw'` (or other) | **Mathematical**: Analyzes the dependency graph. | `CYCLE_DETECTED` |
49
+ | **Flat Mode** | `'none'` (Default) | **Heuristic**: Counts executions per batch. | `MAX_REACTION_EXCEEDED` |
50
+
51
+ In **Topological mode**, the system maintains a transitive closure of all effects, allowing it to know instantly if an effect is its own cause. In **Flat mode**, the system is "blind" to the graph and relies on the execution threshold (`maxTriggerPerBatch`) to interrupt infinite loops.
52
+
53
+ ## Memoization Discrepancy Detection
54
+
55
+ The most powerful debugging tool in `mutts` is the **Discrepancy Detector**. It helps identify "missing dependencies"—reactive values used inside a computation that the system isn't tracking.
56
+
57
+ ### How it Works
58
+
59
+ When `reactiveOptions.onMemoizationDiscrepancy` is set:
60
+ 1. Every time a `memoize()` function is called, it checks its cache.
61
+ 2. If a cached value exists, the function is immediately **executed a second time** in a completely untracked context.
62
+ 3. If the results differ, your callback is triggered.
63
+
64
+ ### Usage
65
+
66
+ ```typescript
67
+ reactiveOptions.onMemoizationDiscrepancy = (cached, fresh, fn, args, cause) => {
68
+ console.error(`Discrepancy in ${fn.name || 'anonymous'}!`, {
69
+ cached,
70
+ fresh,
71
+ cause // 'calculation' or 'comparison'
72
+ });
73
+ throw new Error('Memoization discrepancy detected');
74
+ };
75
+ ```
76
+
77
+ If the cause is 'comparison', it means that, at first computation, calling the function twice gives different results.
78
+
79
+ If the cause is 'calculation', it means that, when asked the value while the cache was still valid, the function returned a different result.
80
+
81
+ ### `isVerificationRun`
82
+
83
+ During the "second run" of a discrepancy check, `reactiveOptions.isVerificationRun` is set to `true`. You can use this flag in your own code to avoid side effects (like incrementing counters) that should only happen once during the primary execution.
84
+
85
+ > [!IMPORTANT]
86
+ > Do not modify `isVerificationRun` manually; it is managed by the engine.
87
+
88
+ ## Introspection API
89
+
90
+ For programmatic analysis of the reactive system, `mutts` provides a dedicated introspection module. This is particularly useful for AI agents and developer tools.
91
+
92
+ ```typescript
93
+ import { enableIntrospection, getDependencyGraph, getMutationHistory, snapshot } from 'mutts/introspection';
94
+ ```
95
+
96
+ ### Enabling Introspection
97
+
98
+ Introspection features (like history tracking) are memory-intensive and disabled by default.
99
+
100
+ ```typescript
101
+ // Enable history tracking (default size: 50)
102
+ enableIntrospection({ historySize: 100 });
103
+ ```
104
+
105
+ ### Dependency Graph
106
+
107
+ You can retrieve the full dependency graph to understand how objects and effects are linked.
108
+
109
+ ```typescript
110
+ const graph = getDependencyGraph();
111
+ // Returns: { nodes: Array<EffectNode | ObjectNode>, edges: Array<GraphEdge> }
112
+ ```
113
+
114
+ ### Mutation History
115
+
116
+ If `enableHistory` is on, you can inspect the sequence of mutations that have occurred.
117
+
118
+ ```typescript
119
+ const history = getMutationHistory();
120
+ // Each record contains type, property, old/new values, and causal source.
121
+ ```
122
+
123
+ ## Structured Error Handling
124
+
125
+ When the reactive system encounters a critical failure (like a cycle or max depth exceeded), it throws a `ReactiveError` containing rich diagnostic information.
126
+
127
+ ### `ReactiveErrorCode`
128
+
129
+ Always check `error.debugInfo.code` to identify the failure type:
130
+ - `CYCLE_DETECTED`: A circular dependency was found.
131
+ - `MAX_DEPTH_EXCEEDED`: The synchronous effect chain reached `maxEffectChain`.
132
+ - `MAX_REACTION_EXCEEDED`: An effect was triggered too many times in a single batch.
133
+ - `WRITE_IN_COMPUTED`: An attempt was made to modify reactive state inside a `memoize` or `derived` function.
134
+
135
+ ### Rich Debug Info
136
+
137
+ The `debugInfo` property on `ReactiveError` includes:
138
+ - **`causalChain`**: A string array describing the logical path of modifications leading to the error.
139
+ - **`creationStack`**: The stack trace of where the effect was originally created, helping you locate the source in your code.
140
+ - **`cycle`**: (For `CYCLE_DETECTED`) The names of the effects that form the loop.
141
+
142
+ ## Best Practices for Debugging
143
+
144
+ ### Naming Effects
145
+
146
+ Always provide a name for your effects to make debug logs and error messages readable:
147
+
148
+ ```typescript
149
+ effect(() => {
150
+ // ...
151
+ }, { name: 'UpdateSidebarCounter' });
152
+ ```
153
+
154
+ ### Activation & Deactivation
155
+
156
+ Since these are runtime options, you can toggle them based on your environment:
157
+
158
+ ```typescript
159
+ if (process.env.NODE_ENV === 'development') {
160
+ reactiveOptions.cycleHandling = 'throw';
161
+ reactiveOptions.onMemoizationDiscrepancy = myHandler;
162
+ enableIntrospection();
163
+ } else {
164
+ // Ensure they are off in production for performance
165
+ reactiveOptions.onMemoizationDiscrepancy = undefined;
166
+ reactiveOptions.cycleHandling = 'break';
167
+ }
168
+ ```
@@ -12,7 +12,7 @@
12
12
 
13
13
  - `memoize` caches results but invalidates on `Map.set`, so large effects still re-run.
14
14
  - `organized` (designed for `Record` sources) creates per-key effects so downstream work reruns only for the touched key; this matches the desired behaviour.
15
- - **Gap:** `organized` operates on plain objects: key enumeration relies on property iteration and `ReflectGet/Set`. Registers and other keyed collections (`Map`, `Register`, custom stores) need the same per-entry orchestration without converting to records.
15
+ - **Gap:** `organized` operates on plain objects: key enumeration relies on property iteration and `FoolProof.get/set`. Registers and other keyed collections (`Map`, `Register`, custom stores) need the same per-entry orchestration without converting to records.
16
16
 
17
17
  ### Completed Evolution: `project` Implementation
18
18
 
@@ -0,0 +1,78 @@
1
+ # Reactive Scan
2
+
3
+ The `scan` function perform a reactive accumulation over an array of items. Unlike a standard `Array.reduce`, it is designed to be highly efficient in a reactive system, particularly when items are moved or changed, by returning a reactive array of all intermediate results.
4
+
5
+ ## Overview
6
+
7
+ In a typical reactive system, calling `array.reduce(...)` inside an `effect` means the entire reduction re-runs every time the array structure or a single item changes.
8
+
9
+ Reactive `scan` solves this by maintaining a chain of **reactive intermediates**. Each item in the source array is linked to an intermediate that depends on the *previous* intermediate's result.
10
+
11
+ ## Key Features
12
+
13
+ - **Fine-Grained Reactivity**: Changing a property on an item only re-computes the accumulated value for that item and its successors.
14
+ - **Move Optimization**: If a subsequence of items moves together (e.g., sorting or splicing), their intermediates are reused. As long as an item's predecessor in the array hasn't changed, its accumulated value is hit from the cache.
15
+ - **Duplicate Support**: Correctly handles multiple occurrences of the same object instance.
16
+ - **Memory Safety**: Uses `WeakMap` for intermediate storage, ensuring data is cleared when source items are garbage collected.
17
+ - **Granular Sync**: Uses per-index effects to sync results, preventing broad dependency tracking of the source array in every calculation.
18
+
19
+ ## Basic Usage
20
+
21
+ ```typescript
22
+ import { reactive, scan } from 'mutts/reactive'
23
+
24
+ const source = reactive([
25
+ { id: 'A', val: 1 },
26
+ { id: 'B', val: 2 },
27
+ { id: 'C', val: 3 },
28
+ ])
29
+
30
+ // result is a reactive array: [1, 3, 6]
31
+ const result = scan(source, (acc, item) => acc + item.val, 0)
32
+
33
+ // Updating an item only re-computes for that position and successors
34
+ source[1].val = 10
35
+ // result stays [1, 11, 14]
36
+ ```
37
+
38
+ ## How it Works
39
+
40
+ The implementation consists of:
41
+ 1. **A Main Effect**: Tracks the structure of the source array (length and item identities). It manages a list of `Intermediate` objects and stays updated on their `prev` links.
42
+ 2. **Intermediates**: Class instances that link `val` and `prev`. They expose an `acc` getter decorated with `@memoize`.
43
+ 3. **Index Sync Effects**: Granular effects (one per result index) that subscribe to `indexToIntermediate[i].acc`.
44
+
45
+ This "Project-like" architecture ensures that the main loop only does structural work, while the actual logic propagation is handled by the dependency chain of the intermediates.
46
+
47
+ ## API Reference
48
+
49
+ ```typescript
50
+ function scan<Input extends object, Output>(
51
+ source: readonly Input[],
52
+ callback: (acc: Output, val: Input) => Output,
53
+ initialValue: Output
54
+ ): ScanResult<Output>
55
+ ```
56
+
57
+ ### Parameters
58
+ - `source`: The source array. All items must be objects (WeakKeys) to enable intermediate caching.
59
+ - `callback`: The accumulator function `(acc, val) => nextAcc`.
60
+ - `initialValue`: The value used as the accumulator for the first item.
61
+
62
+ ### Returns
63
+ A reactive array of accumulated values. It includes a `[cleanup]` symbol that should be called to stop the reactive tracking.
64
+
65
+ ```typescript
66
+ import { cleanup } from 'mutts/reactive'
67
+ // ...
68
+ result[cleanup]()
69
+ ```
70
+
71
+ ## Performance Comparison
72
+
73
+ | Operation | Standard `Array.reduce` in `effect` | Reactive `scan` |
74
+ | :--- | :--- | :--- |
75
+ | **Initial Run** | O(N) calls | O(N) calls |
76
+ | **Modify Item at `i`** | O(N) calls (entire reduction) | O(N-i) calls |
77
+ | **Append Item** | O(N+1) calls | 1 call |
78
+ | **Move Item** | O(N) calls | O(affected chain) |
package/docs/reactive.md CHANGED
@@ -11,7 +11,8 @@ The Mutts Reactive System documentation has been split into focused sections for
11
11
  * **[Reactive Collections](./reactive/collections.md#collections)**: Map, Set, WeakMap, WeakSet
12
12
  * **[Reactive Arrays](./reactive/collections.md#reactivearray)**: Full array method support
13
13
  * **[Register](./reactive/collections.md#register)**: ID-keyed ordered collections
14
- * **[Projections](./reactive/collections.md#projection)**: `project`, `mapped`, `organized`
14
+ * **[Projections](./reactive/collections.md#projection)**: `project`, `organized`
15
+ * **[Scan](./reactive/scan.md)**: Reactive scan and accumulation
15
16
 
16
17
  ## [Advanced Topics](./reactive/advanced.md)
17
18
  * **[Atomic Operations](./reactive/advanced.md#atomic-operations)**: Batching and Bidirectional binding
@@ -20,9 +21,10 @@ The Mutts Reactive System documentation has been split into focused sections for
20
21
  * **[Memoization](./reactive/advanced.md#memoization)**: Caching strategies
21
22
  * **[Debugging](./reactive/advanced.md#debugging-and-development)**: Cycle detection and troubleshooting
22
23
 
23
- ## Debugging tools
24
+ ## [Debugging and Troubleshooting](./reactive/debugging.md)
25
+ * **[Reactive Options](./reactive/debugging.md#the-reactiveoptions-reference)**: Global debug hooks and configuration
26
+ * **[Cycle Detection](./reactive/debugging.md#cycle-detection)**: Configuration and troubleshooting circular dependencies
27
+ * **[Memoization Discrepancy](./reactive/debugging.md#memoization-discrepancy-detection)**: Identifying missing dependencies
28
+ * **[Introspection API](./reactive/debugging.md#introspection-api)**: Programmatic analysis and dependency graphs
24
29
 
25
- The reactive system is currently in a gamma state. The interface is quite fixed, the debugging tools are in place but :
26
- - still unfinished
27
- - not deactivatable
28
- - harming the performances of the application
30
+ * **[Performance](./reactive/debugging.md#performance-cost)**: Understanding the cost of debugging tools
@@ -1,3 +1,4 @@
1
+ TODO: redo the doc here
1
2
  # Standard decorators
2
3
 
3
4
  A TypeScript library that provides standard decorators that should stop being re-implemented for the 50th time
package/docs/zone.md ADDED
@@ -0,0 +1,88 @@
1
+ # Zones (`mutts/zone`)
2
+
3
+ Zones provide a high-performance **context management system** that follows the execution flow, ensuring your variables "stay put" even across asynchronous boundaries like `Promises`, `setTimeout`, or `queueMicrotask`.
4
+
5
+ ## Basic Usage
6
+
7
+ A `Zone` represents a piece of storage that is scoped to the current execution block.
8
+
9
+ ```typescript
10
+ import { Zone } from 'mutts/zone';
11
+
12
+ const myZone = new Zone<string>();
13
+
14
+ myZone.with("context-value", () => {
15
+ // Inside this function, the zone is active
16
+ console.log(myZone.active); // "context-value"
17
+ });
18
+
19
+ console.log(myZone.active); // undefined
20
+ ```
21
+
22
+ ## Async Propagation
23
+
24
+ By default, zones are lost when an async operation yields control (e.g., after `await`). To fix this, `mutts` provides `configureAsyncZone()`.
25
+
26
+ ```typescript
27
+ import { configureAsyncZone, asyncZone, Zone } from 'mutts/zone';
28
+
29
+ const requestId = new Zone<string>();
30
+
31
+ // 1. Tell the global aggregator to track this zone
32
+ asyncZone.add(requestId);
33
+
34
+ // 2. Patch global async primitives (once per app)
35
+ configureAsyncZone();
36
+
37
+ // 3. Usage
38
+ requestId.with("req-123", async () => {
39
+ await somePromise();
40
+ // Context is automatically preserved across await!
41
+ console.log(requestId.active); // "req-123"
42
+ });
43
+ ```
44
+
45
+ ## Core API
46
+
47
+ ### `AZone<T>` (Abstract)
48
+ The base class for all zone implementations.
49
+ - `active: T | undefined`: The current value in the zone.
50
+ - `with<R>(value: T, fn: () => R): R`: Executes `fn` with `value` set as active.
51
+ - `root<R>(fn: () => R): R`: Executes `fn` with the zone cleared (undefined).
52
+ - `zoned: FunctionWrapper`: A getter that returns a function which, when called, restores the zone to its **current** state.
53
+
54
+ ### `Zone<T>`
55
+ Simple stack-based storage.
56
+
57
+ ### `ZoneHistory<T>`
58
+ A zone wrapper that maintains a `history` of previously active values in the current stack.
59
+ - Useful for **Cycle Detection**.
60
+ - Prevents re-entering the same value if already in the history.
61
+ - `present: AZone<T>`: Access the current value without the history overhead.
62
+
63
+ ### `ZoneAggregator`
64
+ Combines multiple zones into one.
65
+ - Entering an aggregator (with `.with()`) enters all its member zones.
66
+ - `asyncZone` is a global aggregator used for the async patches.
67
+
68
+ ## Manual Context Bridging
69
+
70
+ If you are using an API that `mutts` doesn't automatically patch, you can use the `.zoned` capture mechanism:
71
+
72
+ ```typescript
73
+ const wrap = myZone.zoned; // Snapshot the current context
74
+
75
+ // Pass the wrapper to an unmanaged callback
76
+ externalLib.on('event', () => {
77
+ wrap(() => {
78
+ console.log("Context is back:", myZone.active);
79
+ });
80
+ });
81
+ ```
82
+
83
+ ## Integration with Reactivity
84
+
85
+ The `mutts` reactivity system uses zones internally to track the `activeEffect`.
86
+ - Every `effect()` execution runs inside the `effectHistory` zone.
87
+ - Circular dependency detection is powered by `ZoneHistory`.
88
+ - Async effects survive `await` because `effectHistory` is a member of the global `asyncZone`.
package/package.json CHANGED
@@ -1,71 +1,50 @@
1
1
  {
2
2
  "name": "mutts",
3
3
  "description": "Modern UTility TS: A collection of TypeScript utilities",
4
- "version": "1.0.5",
5
- "main": "dist/index.js",
6
- "module": "dist/index.esm.js",
7
- "types": "dist/index.d.ts",
4
+ "version": "1.0.7",
5
+ "main": "dist/browser.js",
6
+ "module": "dist/browser.esm.js",
7
+ "types": "dist/browser.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "types": "./dist/index.d.ts",
11
- "source": "./src/index.ts",
12
- "import": "./dist/index.esm.js",
13
- "require": "./dist/index.js",
14
- "script": "./dist/mutts.umd.min.js"
10
+ "node": {
11
+ "types": "./dist/node.d.ts",
12
+ "import": "./dist/node.esm.js",
13
+ "require": "./dist/node.js"
14
+ },
15
+ "default": {
16
+ "types": "./dist/browser.d.ts",
17
+ "import": "./dist/browser.esm.js",
18
+ "require": "./dist/browser.js"
19
+ }
15
20
  },
16
- "./decorator": {
17
- "types": "./dist/decorator.d.ts",
18
- "source": "./src/decorator.ts",
19
- "import": "./dist/decorator.esm.js",
20
- "require": "./dist/decorator.js"
21
+ "./browser": {
22
+ "types": "./dist/browser.d.ts",
23
+ "import": "./dist/browser.esm.js",
24
+ "require": "./dist/browser.js"
21
25
  },
22
- "./reactive": {
23
- "types": "./dist/reactive.d.ts",
24
- "source": "./src/reactive/index.ts",
25
- "import": "./dist/reactive.esm.js",
26
- "require": "./dist/reactive.js"
27
- },
28
- "./eventful": {
29
- "types": "./dist/eventful.d.ts",
30
- "source": "./src/eventful.ts",
31
- "import": "./dist/eventful.esm.js",
32
- "require": "./dist/eventful.js"
33
- },
34
- "./indexable": {
35
- "types": "./dist/indexable.d.ts",
36
- "source": "./src/indexable.ts",
37
- "import": "./dist/indexable.esm.js",
38
- "require": "./dist/indexable.js"
39
- },
40
- "./promiseChain": {
41
- "types": "./dist/promiseChain.d.ts",
42
- "source": "./src/promiseChain.ts",
43
- "import": "./dist/promiseChain.esm.js",
44
- "require": "./dist/promiseChain.js"
45
- },
46
- "./destroyable": {
47
- "types": "./dist/destroyable.d.ts",
48
- "source": "./src/destroyable.ts",
49
- "import": "./dist/destroyable.esm.js",
50
- "require": "./dist/destroyable.js"
51
- },
52
- "./std-decorators": {
53
- "types": "./dist/std-decorators.d.ts",
54
- "source": "./src/std-decorators.ts",
55
- "import": "./dist/std-decorators.esm.js",
56
- "require": "./dist/std-decorators.js"
26
+ "./node": {
27
+ "types": "./dist/node.d.ts",
28
+ "import": "./dist/node.esm.js",
29
+ "require": "./dist/node.js"
57
30
  },
58
31
  "./src": {
59
- "import": "./src/index.ts"
60
- },
61
- "./src/*": {
62
- "import": "./src/*"
32
+ "node": {
33
+ "types": "./src/async/node.ts",
34
+ "import": "./src/async/node.ts"
35
+ },
36
+ "default": {
37
+ "types": "./src/async/browser.ts",
38
+ "import": "./src/async/browser.ts"
39
+ }
63
40
  },
64
- "./umd": {
65
- "browser": "./dist/mutts.umd.js"
41
+ "./src/browser": {
42
+ "types": "./src/async/browser.ts",
43
+ "import": "./src/async/browser.ts"
66
44
  },
67
- "./umd.min": {
68
- "browser": "./dist/mutts.umd.min.js"
45
+ "./src/node": {
46
+ "types": "./src/async/node.ts",
47
+ "import": "./src/async/node.ts"
69
48
  }
70
49
  },
71
50
  "files": [
@@ -80,14 +59,14 @@
80
59
  "build": "npm run build:js && npm run build:devtools",
81
60
  "build:watch": "rollup -c --watch",
82
61
  "prepublishOnly": "npm run build",
83
- "test": "node --expose-gc node_modules/.bin/jest",
84
- "test:coverage": "node --expose-gc node_modules/.bin/jest --coverage",
85
- "test:coverage:watch": "node --expose-gc node_modules/.bin/jest --coverage --watch",
86
- "test:legacy": "TSCONFIG=tsconfig.legacy.json node node_modules/.bin/jest --detectOpenHandles --testPathPatterns=decorator",
87
- "test:modern": "TSCONFIG=tsconfig.modern.json node node_modules/.bin/jest --detectOpenHandles --testPathPatterns=decorator",
88
- "test:profile": "RUN_PROFILING=1 node --expose-gc node_modules/.bin/jest --testPathPatterns=profiling",
89
- "test:profile:benchmark": "RUN_PROFILING=1 node --expose-gc node_modules/.bin/jest --testPathPatterns=profiling --testNamePattern=benchmark",
90
- "test:profile:detailed": "RUN_PROFILING=1 node --prof node_modules/.bin/jest --testPathPatterns=profiling --no-coverage",
62
+ "test": "NODE_OPTIONS=--expose-gc jest",
63
+ "test:coverage": "NODE_OPTIONS=--expose-gc jest --coverage",
64
+ "test:coverage:watch": "NODE_OPTIONS=--expose-gc jest --coverage --watch",
65
+ "test:legacy": "TSCONFIG=tsconfig.legacy.json jest --detectOpenHandles --testPathPatterns=decorator",
66
+ "test:modern": "TSCONFIG=tsconfig.modern.json jest --detectOpenHandles --testPathPatterns=decorator",
67
+ "test:profile": "RUN_PROFILING=1 NODE_OPTIONS=--expose-gc jest --testPathPatterns=profiling",
68
+ "test:profile:benchmark": "RUN_PROFILING=1 NODE_OPTIONS=--expose-gc jest --testPathPatterns=profiling --testNamePattern=benchmark",
69
+ "test:profile:detailed": "RUN_PROFILING=1 node --prof node_modules/jest/bin/jest.js --testPathPatterns=profiling --no-coverage",
91
70
  "benchmark:save": "tsx tests/profiling/benchmark.ts save",
92
71
  "benchmark:compare": "tsx tests/profiling/benchmark.ts compare",
93
72
  "benchmark:list": "tsx tests/profiling/benchmark.ts list",
@@ -123,11 +102,14 @@
123
102
  },
124
103
  "devDependencies": {
125
104
  "@biomejs/biome": "^2.0.6",
105
+ "@jest/globals": "^30.2.0",
126
106
  "@rollup/plugin-commonjs": "^28.0.6",
107
+ "@rollup/plugin-json": "^6.1.0",
127
108
  "@rollup/plugin-node-resolve": "^16.0.1",
128
109
  "@rollup/plugin-terser": "^0.4.4",
129
110
  "@rollup/plugin-typescript": "^12.1.4",
130
111
  "@types/jest": "^30.0.0",
112
+ "@types/node": "^22.10.10",
131
113
  "jest": "^30.0.4",
132
114
  "rollup": "^4.52.2",
133
115
  "rollup-plugin-copy": "^3.5.0",