mutts 1.0.8 → 1.0.9

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 (226) hide show
  1. package/README.md +19 -2
  2. package/dist/browser.cjs +1030 -4406
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.ts +310 -573
  5. package/dist/browser.esm.js +18 -24
  6. package/dist/browser.esm.js.map +1 -1
  7. package/dist/chunks/{async-browser-CA0jPWIi.cjs → async-browser-lvzLOCgk.cjs} +17 -24
  8. package/dist/chunks/async-browser-lvzLOCgk.cjs.map +1 -0
  9. package/dist/chunks/{async-core-UqHzvJ-S.cjs → async-core-CRLKP3l-.cjs} +8 -4
  10. package/dist/chunks/async-core-CRLKP3l-.cjs.map +1 -0
  11. package/dist/chunks/{async-node-BYHuGTni.cjs → async-node-C3DeIb0y.cjs} +5 -12
  12. package/dist/chunks/async-node-C3DeIb0y.cjs.map +1 -0
  13. package/dist/chunks/index-2vea86wD.esm.js +3011 -0
  14. package/dist/chunks/index-2vea86wD.esm.js.map +1 -0
  15. package/dist/chunks/index-CtA2AWl3.esm.js +4376 -0
  16. package/dist/chunks/index-CtA2AWl3.esm.js.map +1 -0
  17. package/dist/chunks/index-VTO-b2vR.cjs +4461 -0
  18. package/dist/chunks/index-VTO-b2vR.cjs.map +1 -0
  19. package/dist/{reactive → debug}/debug.d.ts +19 -8
  20. package/dist/debug/debug.d.ts.map +1 -0
  21. package/dist/debug/index.d.ts +4 -0
  22. package/dist/debug/index.d.ts.map +1 -0
  23. package/dist/debug/lineage-panel.d.ts +5 -0
  24. package/dist/debug/lineage-panel.d.ts.map +1 -0
  25. package/dist/debug/lineage.d.ts +79 -0
  26. package/dist/debug/lineage.d.ts.map +1 -0
  27. package/dist/debug.cjs +35 -0
  28. package/dist/debug.cjs.map +1 -0
  29. package/dist/debug.d.ts +209 -0
  30. package/dist/debug.esm.js +2 -0
  31. package/dist/debug.esm.js.map +1 -0
  32. package/dist/index.d.ts +1 -13
  33. package/dist/mutts.umd.js +1 -1
  34. package/dist/mutts.umd.js.map +1 -1
  35. package/dist/mutts.umd.min.js +1 -1
  36. package/dist/mutts.umd.min.js.map +1 -1
  37. package/dist/node.cjs +65 -63
  38. package/dist/node.cjs.map +1 -1
  39. package/dist/node.d.ts +2 -1
  40. package/dist/node.esm.js +5 -11
  41. package/dist/node.esm.js.map +1 -1
  42. package/dist/{async → src/async}/browser.d.ts.map +1 -1
  43. package/dist/{async → src/async}/index.d.ts +2 -1
  44. package/dist/src/async/index.d.ts.map +1 -0
  45. package/dist/{async → src/async}/node.d.ts.map +1 -1
  46. package/dist/src/decorator.d.ts.map +1 -0
  47. package/dist/src/destroyable.d.ts.map +1 -0
  48. package/dist/src/entry-browser.d.ts.map +1 -0
  49. package/dist/src/entry-node.d.ts.map +1 -0
  50. package/dist/src/eventful.d.ts +20 -0
  51. package/dist/src/eventful.d.ts.map +1 -0
  52. package/dist/src/flavored.d.ts +33 -0
  53. package/dist/src/flavored.d.ts.map +1 -0
  54. package/dist/src/index.d.ts +14 -0
  55. package/dist/src/index.d.ts.map +1 -0
  56. package/dist/src/indexable.d.ts.map +1 -0
  57. package/dist/{introspection.d.ts → src/introspection.d.ts} +1 -1
  58. package/dist/src/introspection.d.ts.map +1 -0
  59. package/dist/src/iterableWeak.d.ts.map +1 -0
  60. package/dist/src/mixins.d.ts.map +1 -0
  61. package/dist/src/promiseChain.d.ts.map +1 -0
  62. package/dist/{reactive → src/reactive}/array.d.ts +9 -10
  63. package/dist/src/reactive/array.d.ts.map +1 -0
  64. package/dist/src/reactive/buffer.d.ts +120 -0
  65. package/dist/src/reactive/buffer.d.ts.map +1 -0
  66. package/dist/{reactive → src/reactive}/change.d.ts +2 -2
  67. package/dist/src/reactive/change.d.ts.map +1 -0
  68. package/dist/src/reactive/deep-touch.d.ts.map +1 -0
  69. package/dist/{reactive → src/reactive}/deep-watch-state.d.ts +3 -3
  70. package/dist/src/reactive/deep-watch-state.d.ts.map +1 -0
  71. package/dist/{reactive → src/reactive}/deep-watch.d.ts +2 -1
  72. package/dist/src/reactive/deep-watch.d.ts.map +1 -0
  73. package/dist/src/reactive/describe.d.ts +12 -0
  74. package/dist/src/reactive/describe.d.ts.map +1 -0
  75. package/dist/src/reactive/effect-context.d.ts +34 -0
  76. package/dist/src/reactive/effect-context.d.ts.map +1 -0
  77. package/dist/{reactive → src/reactive}/effects.d.ts +31 -18
  78. package/dist/src/reactive/effects.d.ts.map +1 -0
  79. package/dist/src/reactive/index.d.ts +19 -0
  80. package/dist/src/reactive/index.d.ts.map +1 -0
  81. package/dist/{reactive → src/reactive}/map.d.ts +0 -2
  82. package/dist/src/reactive/map.d.ts.map +1 -0
  83. package/dist/src/reactive/memoize.d.ts +28 -0
  84. package/dist/src/reactive/memoize.d.ts.map +1 -0
  85. package/dist/src/reactive/non-reactive-state.d.ts.map +1 -0
  86. package/dist/src/reactive/non-reactive.d.ts.map +1 -0
  87. package/dist/{reactive → src/reactive}/project.d.ts +1 -2
  88. package/dist/src/reactive/project.d.ts.map +1 -0
  89. package/dist/src/reactive/proxy-state.d.ts.map +1 -0
  90. package/dist/src/reactive/proxy.d.ts.map +1 -0
  91. package/dist/{reactive → src/reactive}/record.d.ts +2 -3
  92. package/dist/src/reactive/record.d.ts.map +1 -0
  93. package/dist/{reactive → src/reactive}/register.d.ts +61 -0
  94. package/dist/src/reactive/register.d.ts.map +1 -0
  95. package/dist/{reactive → src/reactive}/registry.d.ts +6 -5
  96. package/dist/src/reactive/registry.d.ts.map +1 -0
  97. package/dist/{reactive → src/reactive}/set.d.ts +0 -2
  98. package/dist/src/reactive/set.d.ts.map +1 -0
  99. package/dist/src/reactive/tracking.d.ts.map +1 -0
  100. package/dist/{reactive → src/reactive}/types.d.ts +69 -21
  101. package/dist/src/reactive/types.d.ts.map +1 -0
  102. package/dist/{reactive/interface.d.ts → src/reactive/watch.d.ts} +7 -23
  103. package/dist/src/reactive/watch.d.ts.map +1 -0
  104. package/dist/{std-decorators.d.ts → src/std-decorators.d.ts} +1 -6
  105. package/dist/src/std-decorators.d.ts.map +1 -0
  106. package/dist/src/utils.d.ts.map +1 -0
  107. package/dist/{zone.d.ts → src/zone.d.ts} +2 -2
  108. package/dist/src/zone.d.ts.map +1 -0
  109. package/dist/types-DaHFfhlN.d.ts +414 -0
  110. package/docs/ai/api-reference.md +1 -1
  111. package/docs/ai/manual.md +443 -17
  112. package/docs/events.md +57 -2
  113. package/docs/flavored.md +210 -0
  114. package/docs/reactive/advanced.md +2 -3
  115. package/docs/reactive/attend.md +143 -0
  116. package/docs/reactive/collections.md +55 -0
  117. package/docs/reactive/core.md +120 -4
  118. package/docs/reactive/debugging.md +8 -10
  119. package/docs/reactive/describe.md +85 -0
  120. package/docs/reactive/error-handling.md +121 -0
  121. package/docs/reactive/project.md +1 -1
  122. package/docs/reactive/scan.md +215 -0
  123. package/docs/reactive.md +2 -0
  124. package/docs/std-decorators.md +102 -51
  125. package/docs/zone.md +3 -7
  126. package/package.json +6 -1
  127. package/src/async/browser.ts +110 -106
  128. package/src/async/index.ts +15 -11
  129. package/src/async/node.ts +5 -17
  130. package/src/eventful.ts +82 -49
  131. package/src/flavored.ts +106 -0
  132. package/src/index.d.ts +12 -12
  133. package/src/index.ts +2 -2
  134. package/src/introspection.ts +1 -1
  135. package/src/reactive/array.ts +29 -36
  136. package/src/reactive/buffer.ts +187 -27
  137. package/src/reactive/change.ts +16 -23
  138. package/src/reactive/deep-touch.ts +19 -14
  139. package/src/reactive/deep-watch-state.ts +3 -3
  140. package/src/reactive/deep-watch.ts +7 -4
  141. package/src/reactive/describe.ts +39 -0
  142. package/src/reactive/effect-context.ts +69 -4
  143. package/src/reactive/effects.ts +445 -393
  144. package/src/reactive/index.ts +12 -15
  145. package/src/reactive/map.ts +9 -25
  146. package/src/reactive/memoize.ts +26 -3
  147. package/src/reactive/project.md +107 -0
  148. package/src/reactive/project.ts +48 -71
  149. package/src/reactive/proxy.ts +21 -3
  150. package/src/reactive/record.ts +30 -45
  151. package/src/reactive/register.ts +138 -19
  152. package/src/reactive/registry.ts +22 -9
  153. package/src/reactive/set.ts +8 -21
  154. package/src/reactive/tracking.ts +9 -12
  155. package/src/reactive/types.ts +104 -24
  156. package/src/reactive/{interface.ts → watch.ts} +29 -59
  157. package/src/std-decorators.ts +56 -17
  158. package/src/utils.ts +2 -3
  159. package/src/zone.ts +28 -24
  160. package/dist/async/index.d.ts.map +0 -1
  161. package/dist/chunks/async-browser-CA0jPWIi.cjs.map +0 -1
  162. package/dist/chunks/async-core-UqHzvJ-S.cjs.map +0 -1
  163. package/dist/chunks/async-node-BYHuGTni.cjs.map +0 -1
  164. package/dist/chunks/index-DhaOVusv.esm.js +0 -6411
  165. package/dist/chunks/index-DhaOVusv.esm.js.map +0 -1
  166. package/dist/decorator.d.ts.map +0 -1
  167. package/dist/destroyable.d.ts.map +0 -1
  168. package/dist/entry-browser.d.ts.map +0 -1
  169. package/dist/entry-node.d.ts.map +0 -1
  170. package/dist/eventful.d.ts +0 -18
  171. package/dist/eventful.d.ts.map +0 -1
  172. package/dist/index.d.ts.map +0 -1
  173. package/dist/indexable.d.ts.map +0 -1
  174. package/dist/introspection.d.ts.map +0 -1
  175. package/dist/iterableWeak.d.ts.map +0 -1
  176. package/dist/mixins.d.ts.map +0 -1
  177. package/dist/promiseChain.d.ts.map +0 -1
  178. package/dist/reactive/array.d.ts.map +0 -1
  179. package/dist/reactive/buffer.d.ts +0 -44
  180. package/dist/reactive/buffer.d.ts.map +0 -1
  181. package/dist/reactive/change.d.ts.map +0 -1
  182. package/dist/reactive/debug.d.ts.map +0 -1
  183. package/dist/reactive/deep-touch.d.ts.map +0 -1
  184. package/dist/reactive/deep-watch-state.d.ts.map +0 -1
  185. package/dist/reactive/deep-watch.d.ts.map +0 -1
  186. package/dist/reactive/effect-context.d.ts +0 -7
  187. package/dist/reactive/effect-context.d.ts.map +0 -1
  188. package/dist/reactive/effects.d.ts.map +0 -1
  189. package/dist/reactive/index.d.ts +0 -20
  190. package/dist/reactive/index.d.ts.map +0 -1
  191. package/dist/reactive/interface.d.ts.map +0 -1
  192. package/dist/reactive/map.d.ts.map +0 -1
  193. package/dist/reactive/memoize.d.ts +0 -5
  194. package/dist/reactive/memoize.d.ts.map +0 -1
  195. package/dist/reactive/non-reactive-state.d.ts.map +0 -1
  196. package/dist/reactive/non-reactive.d.ts.map +0 -1
  197. package/dist/reactive/project.d.ts.map +0 -1
  198. package/dist/reactive/proxy-state.d.ts.map +0 -1
  199. package/dist/reactive/proxy.d.ts.map +0 -1
  200. package/dist/reactive/record.d.ts.map +0 -1
  201. package/dist/reactive/register.d.ts.map +0 -1
  202. package/dist/reactive/registry.d.ts.map +0 -1
  203. package/dist/reactive/set.d.ts.map +0 -1
  204. package/dist/reactive/tracking.d.ts.map +0 -1
  205. package/dist/reactive/types.d.ts.map +0 -1
  206. package/dist/std-decorators.d.ts.map +0 -1
  207. package/dist/utils.d.ts.map +0 -1
  208. package/dist/zone.d.ts.map +0 -1
  209. package/src/reactive/debug.ts +0 -517
  210. /package/dist/{async → src/async}/browser.d.ts +0 -0
  211. /package/dist/{async → src/async}/node.d.ts +0 -0
  212. /package/dist/{decorator.d.ts → src/decorator.d.ts} +0 -0
  213. /package/dist/{destroyable.d.ts → src/destroyable.d.ts} +0 -0
  214. /package/dist/{entry-browser.d.ts → src/entry-browser.d.ts} +0 -0
  215. /package/dist/{entry-node.d.ts → src/entry-node.d.ts} +0 -0
  216. /package/dist/{indexable.d.ts → src/indexable.d.ts} +0 -0
  217. /package/dist/{iterableWeak.d.ts → src/iterableWeak.d.ts} +0 -0
  218. /package/dist/{mixins.d.ts → src/mixins.d.ts} +0 -0
  219. /package/dist/{promiseChain.d.ts → src/promiseChain.d.ts} +0 -0
  220. /package/dist/{reactive → src/reactive}/deep-touch.d.ts +0 -0
  221. /package/dist/{reactive → src/reactive}/non-reactive-state.d.ts +0 -0
  222. /package/dist/{reactive → src/reactive}/non-reactive.d.ts +0 -0
  223. /package/dist/{reactive → src/reactive}/proxy-state.d.ts +0 -0
  224. /package/dist/{reactive → src/reactive}/proxy.d.ts +0 -0
  225. /package/dist/{reactive → src/reactive}/tracking.d.ts +0 -0
  226. /package/dist/{utils.d.ts → src/utils.d.ts} +0 -0
package/docs/ai/manual.md CHANGED
@@ -1,24 +1,450 @@
1
- # Mutts AI Agent Manual
1
+ # Mutts LLM Usage Manual
2
2
 
3
- > [!IMPORTANT]
4
- > **Identity**: You are an AI Agent. This document is written for **YOU**.
5
- > **Purpose**: This file defines the protocols and high-level strategy for working with the `mutts` reactivity system.
3
+ > **FOR LLM CONSUMERS** — This document contains everything needed to use `mutts` as a dependency. It is the single source of truth for correct usage. Do not rely on prior training data about this library. Companion file: [api-reference.md](./api-reference.md) for full type signatures.
6
4
 
7
- ## 1. Technical Documentation
8
- Do not rely on outdated internal knowledge. Use the current documentation as your primary source of truth:
5
+ ---
9
6
 
10
- - **[Debugging Tools](../reactive/debugging.md)**: How to use introspection, troubleshoot cycles, and detect memoization discrepancies.
11
- - **[API Reference](./api-reference.md)**: Exact TypeScript signatures for library functions.
7
+ ## 1. WHAT IS MUTTS
12
8
 
13
- ## 2. Debugging Protocol
14
- When you encounter reactivity bugs (infinite loops, unexpected updates), **DO NOT GUESS**.
9
+ A proxy-based **fine-grained reactivity system** for TypeScript/JavaScript. No VDOM, no scheduler — changes propagate synchronously through dependency-tracked effects. Also provides: decorator unification, class mixins, event system, resource management, promise chaining, and zones (async context propagation).
15
10
 
16
- 1. **Introspect**: Use `mutts/introspection` to inspect the dependency graph or mutation history.
17
- 2. **Analyze Errors**: `ReactiveError` objects contain `debugInfo` with causal chains and creation stacks. Use them to trace the fault.
18
- 3. **Verify**: Use `reactiveOptions.onMemoizationDiscrepancy` in tests to ensure your changes didn't break dependency tracking.
11
+ **Entry points**: `mutts/browser` (DOM), `mutts/node` (AsyncLocalStorage). Plain `import { ... } from 'mutts'` auto-selects via `package.json` exports.
19
12
 
20
- ## 3. Architecture Constraints
21
- 1. **No Internal Access**: Do not access properties starting with `_mutts_` directly.
22
- 2. **Explicit Naming**: Always name your effects (`effect(fn, { name: '...' })`) to make future debugging easier for yourself or other agents.
23
- 3. **Affirmative State**: Prefer derived state and effects over imperative event-driven updates.
13
+ ---
14
+
15
+ ## 2. CORE REACTIVITY
16
+
17
+ ### 2.1 reactive() — Make objects trackable
18
+
19
+ ```ts
20
+ import { reactive, effect } from 'mutts'
21
+
22
+ const state = reactive({ count: 0, name: 'John' })
23
+ ```
24
+
25
+ - Wraps an object in a `Proxy` that tracks property reads (dependencies) and writes (notifications).
26
+ - Same object always returns the same proxy.
27
+ - Works with: plain objects, `Object.create(null)`, class instances, arrays, Map, Set, WeakMap, WeakSet.
28
+ - `unwrap(proxy)` recovers the original. `isReactive(obj)` checks.
29
+
30
+ ### 2.2 effect() — React to changes
31
+
32
+ ```ts
33
+ const stop = effect(({ reaction, tracked, ascend }) => {
34
+ console.log(state.count) // tracked automatically
35
+ return () => { /* cleanup before next run or disposal */ }
36
+ })
37
+
38
+ state.count++ // triggers effect
39
+ stop() // disposes effect permanently
40
+ ```
41
+
42
+ - **Auto-tracks**: any reactive property read inside the callback becomes a dependency.
43
+ - **Cleanup**: return a function — called before each re-run and on disposal.
44
+ - **`reaction`**: `false` on first run, `true` on subsequent triggers.
45
+ - **`tracked(fn)`**: restores tracking context in async/unmanaged callbacks.
46
+ - **`ascend(fn)`**: tracks dependencies in the *parent* effect instead.
47
+ - **Parent-child**: effects created inside other effects are children; disposing a parent disposes all children.
48
+ - **GC**: unreferenced top-level effects may be garbage-collected. Store the cleanup reference to keep alive.
49
+
50
+ **Modifiers** (chainable):
51
+ ```ts
52
+ effect.opaque(() => { ... }) // identity-only tracking (no deep touch)
53
+ effect.named('label')(() => { ... }) // named for debugging
54
+ effect.opaque.named('x')(() => { ... })
55
+ ```
56
+
57
+ ### 2.3 memoize() — Cached computed values
58
+
59
+ ```ts
60
+ const doubled = memoize((obj: { value: number }) => obj.value * 2)
61
+ // Arguments must be WeakMap-compatible (objects/symbols). No primitives.
62
+ // Cache invalidates when tracked reactive reads inside fn change.
63
+ // Same fn passed to memoize() multiple times returns same wrapper.
64
+ ```
65
+
66
+ As decorator: `@memoize` on getters (per-instance cache) or methods (per-instance+args).
67
+
68
+ ### 2.4 untracked() — Escape tracking
69
+
70
+ ```ts
71
+ untracked(() => {
72
+ // Reactive reads here are NOT tracked by the enclosing effect
73
+ })
74
+ ```
75
+
76
+ ### 2.5 unreactive() — Opt out
77
+
78
+ ```ts
79
+ unreactive(obj) // mark object as non-reactive
80
+ unreactive(MyClass) // mark entire class
81
+ @unreactive('id', 'meta') // mark specific properties on a @reactive class
82
+ ```
83
+
84
+ ### 2.6 atomic() — Batch mutations
85
+
86
+ ```ts
87
+ const updateBoth = atomic((a, b) => {
88
+ state.a = a
89
+ state.b = b
90
+ // Effects fire ONCE after both mutations, not twice
91
+ })
92
+ updateBoth(10, 20)
93
+ ```
94
+
95
+ Also works as `@atomic` decorator on class methods.
96
+
97
+ ### 2.7 defer() / addBatchCleanup() — Avoid cycles
98
+
99
+ When an effect needs to mutate state it reads, defer the mutation:
100
+ ```ts
101
+ effect(() => {
102
+ const len = state.items.length
103
+ defer(() => { state.processedCount = len }) // runs after batch completes
104
+ })
105
+ ```
106
+ - Runs synchronously after outermost batch. FIFO order. No microtask delay.
107
+ - Outside a batch: executes immediately.
108
+
109
+ ### 2.8 biDi() — Bidirectional binding
110
+
111
+ Bridges reactive state ↔ external (DOM, third-party). Prevents infinite loops automatically.
112
+ ```ts
113
+ const provide = biDi(
114
+ (v) => inputElement.value = v, // external setter
115
+ { get: () => model.value, set: (v) => model.value = v }
116
+ )
117
+ inputElement.addEventListener('input', () => provide(inputElement.value))
118
+ ```
119
+
120
+ ### 2.9 watch() — Observe changes
121
+
122
+ ```ts
123
+ // Watch a specific derivation
124
+ watch(() => state.count, (newVal, oldVal) => { ... })
125
+
126
+ // Watch any property on an object
127
+ watch(state, () => { ... })
128
+
129
+ // Deep watch (higher overhead)
130
+ watch(state, () => { ... }, { deep: true })
131
+ ```
132
+
133
+ ---
134
+
135
+ ## 3. COLLECTIONS
136
+
137
+ All standard collections get reactive wrappers via `reactive()`:
138
+
139
+ | Type | Tracking |
140
+ |------|----------|
141
+ | `reactive([])` | Per-index, `.length`, iteration (allProps) |
142
+ | `reactive(new Map())` | Per-key, `.size`, iteration |
143
+ | `reactive(new Set())` | Per-value, `.size`, iteration |
144
+ | `reactive(new WeakMap())` | Per-key only |
145
+ | `reactive(new WeakSet())` | Per-value only |
146
+
147
+ Array methods (`push`, `splice`, `sort`, etc.) all trigger reactivity.
148
+
149
+
150
+ ### 3.1 Register — Keyed ordered collection
151
+
152
+ ```ts
153
+ import { Register } from 'mutts/reactive'
154
+
155
+ const list = new Register(item => item.id, [
156
+ { id: 1, label: 'Alpha' },
157
+ { id: 2, label: 'Bravo' },
158
+ ])
159
+
160
+ list.push({ id: 3, label: 'Charlie' })
161
+ list.get(2) // lookup by key, O(1)
162
+ list.set(2, newItem) // update by key
163
+ list.remove(2) // remove by key
164
+ list.keep(item => item.active) // filter in-place
165
+ list.upsert(v => list.push(v), ...items) // update or insert
166
+ ```
167
+
168
+ Full array surface (`map`, `filter`, `reduce`, `sort`, etc.) + CRUD events (`on('add', ...)`, `on('delete', ...)`, `on('update', ...)`, `on('rekey', ...)`).
169
+
170
+ ---
171
+
172
+ ## 4. COLLECTION TRANSFORMS
173
+
174
+ ### 4.1 project() — Per-entry reactive map
175
+
176
+ The reactive replacement for `.map()`. Each entry gets its own effect — only changed entries recompute.
177
+
178
+ ```ts
179
+ import { project, cleanup } from 'mutts'
180
+
181
+ // Array
182
+ const names = project(users, ({ get }) => get().name.toUpperCase())
183
+ // names[0] recomputes only when users[0] changes
184
+
185
+ // Record
186
+ const grades = project.record(scores, ({ get }) => get() >= 90 ? 'A' : 'B')
187
+
188
+ // Map
189
+ const totals = project.map(inventory, ({ get }) => get().count)
190
+
191
+ // Auto-dispatch
192
+ const doubled = project(source, ({ get }) => get() * 2)
193
+
194
+ // Cleanup
195
+ names[cleanup]()
196
+ ```
197
+
198
+ **Access object**: `{ get(), set(v), key, source, old, value }`
199
+
200
+ ### 4.2 attend() — Per-key lifecycle
201
+
202
+ Creates inner effect per key. Disposed when key disappears.
203
+
204
+ ```ts
205
+ import { attend } from 'mutts'
206
+
207
+ attend(reactiveRecord, (key) => {
208
+ console.log(`${key} = ${reactiveRecord[key]}`)
209
+ return () => console.log(`cleanup: ${key}`)
210
+ })
211
+ // Also works with: arrays, Maps, Sets, or raw () => Iterable<Key>
212
+ ```
213
+
214
+ ### 4.3 organized() — Per-key record transform with side effects
215
+
216
+ ```ts
217
+ const doubled = organized(source, (access, target) => {
218
+ target[access.key] = access.get() * 2
219
+ return () => delete target[access.key]
220
+ })
221
+ ```
222
+
223
+ ### 4.4 describe() — Reactive Object.defineProperties
224
+
225
+ ```ts
226
+ const descriptors = reactive({ foo: { value: 1, enumerable: true } })
227
+ const target = describe(descriptors) // target.foo === 1
228
+ descriptors.bar = { get: () => 42, enumerable: true } // target.bar appears
229
+ delete descriptors.foo // target.foo disappears
230
+ ```
231
+
232
+ ### 4.5 scan() — Reactive accumulation
233
+
234
+ ```ts
235
+ const result = scan(source, (acc, item) => acc + item.val, 0)
236
+ // result is reactive array of intermediates: [1, 3, 6]
237
+ // Changing source[1].val only recomputes from index 1 onward
238
+ result[cleanup]()
239
+ ```
240
+
241
+ Items must be objects (WeakMap keys). Move-optimized (reorders reuse cached intermediates).
242
+
243
+ ### 4.6 lift() — Sync a computed array/object
244
+
245
+ ```ts
246
+ const filtered = lift(() => items.filter(x => x.active))
247
+ // Only changed elements sync — element-wise diff, not full rebuild
248
+ filtered[cleanup]()
249
+ ```
250
+
251
+ ---
252
+
253
+ ## 5. EVOLUTION TRACKING
254
+
255
+ ```ts
256
+ import { getState } from 'mutts/reactive'
257
+
258
+ let state = getState(obj)
259
+ effect(() => {
260
+ while ('evolution' in state) {
261
+ console.log(state.evolution) // { type: 'set'|'add'|'del'|'bunch', prop, method }
262
+ state = state.next
263
+ }
264
+ })
265
+ ```
266
+
267
+ **Recursive touching**: replacing an object with another of the same prototype triggers fine-grained per-property diffs, not a wholesale parent notification. Disable with `reactiveOptions.recursiveTouching = false`.
268
+
269
+ ---
270
+
271
+ ## 6. ERROR HANDLING
272
+
273
+ ```ts
274
+ import { onEffectThrow } from 'mutts'
275
+
276
+ effect(() => {
277
+ onEffectThrow((error) => {
278
+ console.error('Caught:', error)
279
+ // return without throwing = handled
280
+ // throw = try next handler
281
+ // return function = cleanup on effect disposal
282
+ })
283
+ // ... code that might throw
284
+ })
285
+ ```
286
+
287
+ - Multiple handlers tried in order. Unhandled errors propagate to parent effect chain.
288
+ - **Must register before throwing code** (handlers cleared on re-run).
289
+ - Does **not** catch async errors — use `.catch()` on Promises.
290
+
291
+ ---
292
+
293
+ ## 7. DEBUGGING
294
+
295
+ ```ts
296
+ import { reactiveOptions } from 'mutts/reactive'
297
+
298
+ // Cycle detection modes
299
+ reactiveOptions.cycleHandling = 'development' // default — graph-based, throws immediately
300
+ reactiveOptions.cycleHandling = 'debug' // full transitive closure, detailed paths
301
+ reactiveOptions.cycleHandling = 'production' // heuristic via maxTriggerPerBatch (fastest)
302
+
303
+ // Memoization discrepancy (double-run detection)
304
+ reactiveOptions.onMemoizationDiscrepancy = (cached, fresh, fn, args, cause) => {
305
+ throw new Error(`Discrepancy in ${fn.name}: ${cause}`)
306
+ }
307
+
308
+ // Lifecycle hooks
309
+ reactiveOptions.enter = (effect) => { ... }
310
+ reactiveOptions.leave = (effect) => { ... }
311
+ reactiveOptions.touched = (obj, evolution) => { ... }
312
+
313
+ // Introspection (memory-intensive, dev only)
314
+ import { enableIntrospection, getDependencyGraph, getMutationHistory } from 'mutts/introspection'
315
+ enableIntrospection({ historySize: 100 })
316
+ ```
317
+
318
+ **ReactiveError.debugInfo** contains: `causalChain`, `creationStack`, `cycle` (for CYCLE_DETECTED).
319
+
320
+ ---
321
+
322
+ ## 8. ZONES (Async Context)
323
+
324
+ ```ts
325
+ import { Zone, asyncZone } from 'mutts/zone'
326
+
327
+ const requestId = new Zone<string>()
328
+ asyncZone.add(requestId) // register for async propagation
329
+
330
+ requestId.with('req-123', async () => {
331
+ await somePromise()
332
+ requestId.active // still 'req-123'
333
+ })
334
+ ```
335
+
336
+ - `Zone<T>`: stack-based storage. `.with(value, fn)`, `.active`, `.root(fn)`.
337
+ - `ZoneHistory<T>`: tracks history for cycle detection.
338
+ - `ZoneAggregator`: combines multiple zones. `asyncZone` is the global one.
339
+ - `.zoned`: snapshot current context for manual bridging into unmanaged callbacks.
340
+ - Reactivity uses zones internally (`effectHistory` zone tracks active effect across await).
341
+
342
+ ---
343
+
344
+ ## 9. OTHER MODULES
345
+
346
+ ### 9.1 Decorators (`mutts/decorator`)
347
+
348
+ Unified system for legacy (`experimentalDecorators`) and modern (Stage 3):
349
+ ```ts
350
+ import { decorator } from 'mutts/decorator'
351
+ const myDec = decorator({ method(original, name) { ... }, class(target) { ... } })
352
+ ```
353
+
354
+ Standard decorators: `@cached`, `@debounce(ms)`, `@throttle(ms)`, `@deprecated(msg)`.
355
+
356
+ ### 9.2 Mixin (`mutts/mixin`)
357
+
358
+ ```ts
359
+ import { mixin } from 'mutts/mixin'
360
+ const Countable = mixin((base) => class extends base { count = 0; increment() { this.count++ } })
361
+
362
+ class A extends Countable { } // as base class
363
+ class B extends Countable(OtherBase) { } // as mixin
364
+ // Cached: same base always returns same mixed class
365
+ ```
366
+
367
+ ### 9.3 Eventful (`mutts/eventful`)
368
+
369
+ Type-safe event system:
370
+ ```ts
371
+ import { Eventful } from 'mutts/eventful'
372
+ interface MyEvents { click: (x: number, y: number) => void }
373
+ class Button extends Eventful<MyEvents> { }
374
+
375
+ const btn = new Button()
376
+ btn.on.click((x, y) => { ... }) // dot notation
377
+ btn.emit.click(100, 200)
378
+ btn.off.click()
379
+ const unhook = btn.hook((event, ...args) => { ... }) // global listener
380
+ ```
381
+
382
+ ### 9.4 Destroyable (`mutts/destroyable`)
383
+
384
+ Resource management with `FinalizationRegistry` and `Symbol.dispose`:
385
+ ```ts
386
+ import { Destroyable, allocated, destructor } from 'mutts/destroyable'
387
+
388
+ class FileHandler extends Destroyable() {
389
+ @allocated accessor filePath: string
390
+ [destructor](alloc) { console.log(`Closing: ${alloc.filePath}`) }
391
+ }
392
+ // Destroyed objects throw DestructionError on access.
393
+ // Explicit: Destroyable.destroy(instance) or future `using` statement.
394
+ ```
395
+
396
+ ### 9.5 Flavored Functions (`mutts/flavored`)
397
+
398
+ Chainable property modifiers on functions:
399
+ ```ts
400
+ import { flavored, flavorOptions } from 'mutts'
401
+ const greet = flavored(
402
+ (name: string, opts?: { loud?: boolean }) => opts?.loud ? name.toUpperCase() : name,
403
+ { get loud() { return flavorOptions(this, { loud: true }) } }
404
+ )
405
+ greet.loud('hi') // "HI"
406
+ ```
407
+
408
+ ### 9.6 Indexable (`mutts/indexable`)
409
+
410
+ Numeric index access (`obj[0]`) on custom classes via Proxy:
411
+ ```ts
412
+ import { Indexable } from 'mutts/indexable'
413
+ const MyCollection = Indexable(BaseClass, {
414
+ get(index) { return this.data[index] },
415
+ set(index, v) { this.data[index] = v }
416
+ })
417
+ ```
418
+
419
+ ### 9.7 PromiseChain (`mutts/promiseChain`)
420
+
421
+ Fluent chaining on Promises without intermediate `await`:
422
+ ```ts
423
+ import { chainPromise } from 'mutts/promiseChain'
424
+ const theme = await chainPromise(api.getUser('123')).getProfile().getSettings().theme
425
+ ```
426
+
427
+ ---
428
+
429
+ ## 10. PHILOSOPHY & TRAPS
430
+
431
+ ### Affirmative State
432
+ Declare `Y = f(X)`. Do NOT say "when X changes, update Y". The system ensures consistency.
433
+
434
+ ### Events Are Legacy
435
+ Only use events for DOM interaction or external APIs. Internal logic should use reactive derivations (`effect`, `memoize`, `project`).
436
+
437
+ ### Cleanup Semantics
438
+ Cleanup functions release reactive subscriptions, NOT undo side effects. If an element is being removed, there's no point resetting its attributes.
439
+
440
+ ### Key Traps
441
+
442
+ | Trap | Fix |
443
+ |------|-----|
444
+ | `memoize` with primitive args | Args must be objects/symbols (WeakMap keys) |
445
+ | Bare reactive reads lose context after `await` | Register zones in `asyncZone` or use `tracked(() => ...)` |
446
+ | Self-triggering effects | Use `defer()` / `addBatchCleanup()` |
447
+ | `queueMicrotask` for cycle avoidance | Use `defer()` instead (synchronous, batch-aware) |
448
+ | Deep watch overhead | Prefer explicit property reads + recursive touching |
449
+ | `_mutts_*` properties are debug/introspection helpers | Use them for logging, tracing, and profiling only — not as stable API |
24
450
 
package/docs/events.md CHANGED
@@ -32,8 +32,9 @@ class MyClass extends Eventful<MyEvents> {
32
32
 
33
33
  #### `on(events: Partial<Events>): void`
34
34
  #### `on<EventType extends keyof Events>(event: EventType, cb: Events[EventType]): () => void`
35
+ #### `on.eventName(cb: Events['eventName']): () => void`
35
36
 
36
- Registers event listeners. Can be called with either a single event and callback, or an object containing multiple events and their callbacks.
37
+ Registers event listeners. Can be called with either a single event and callback, an object containing multiple events, or using dot notation.
37
38
 
38
39
  **Parameters:**
39
40
  - `event`: The event name (when using single event overload)
@@ -49,6 +50,11 @@ const unsubscribe = myObject.on('userLogin', (userId, timestamp) => {
49
50
  console.log(`User ${userId} logged in at ${timestamp}`)
50
51
  })
51
52
 
53
+ // Dot notation (equivalent to above)
54
+ const unsubscribe = myObject.on.userLogin((userId, timestamp) => {
55
+ console.log(`User ${userId} logged in at ${timestamp}`)
56
+ })
57
+
52
58
  // Multiple events
53
59
  myObject.on({
54
60
  userLogin: (userId, timestamp) => console.log('Login:', userId),
@@ -59,8 +65,9 @@ myObject.on({
59
65
 
60
66
  #### `off(events: Partial<Events>): void`
61
67
  #### `off<EventType extends keyof Events>(event: EventType, cb?: Events[EventType]): void`
68
+ #### `off.eventName(cb?: Events['eventName']): void`
62
69
 
63
- Removes event listeners. Can be called with either a single event (and optional callback), or an object containing multiple events.
70
+ Removes event listeners. Can be called with either a single event (and optional callback), an object containing multiple events, or using dot notation.
64
71
 
65
72
  **Parameters:**
66
73
  - `event`: The event name (when using single event overload)
@@ -72,9 +79,15 @@ Removes event listeners. Can be called with either a single event (and optional
72
79
  // Remove specific callback
73
80
  myObject.off('userLogin', myCallback)
74
81
 
82
+ // Remove specific callback using dot notation
83
+ myObject.off.userLogin(myCallback)
84
+
75
85
  // Remove all listeners for an event
76
86
  myObject.off('userLogin')
77
87
 
88
+ // Remove all listeners for an event using dot notation
89
+ myObject.off.userLogin()
90
+
78
91
  // Remove multiple events
79
92
  myObject.off({
80
93
  userLogin: myCallback,
@@ -83,6 +96,7 @@ myObject.off({
83
96
  ```
84
97
 
85
98
  #### `emit<EventType extends keyof Events>(event: EventType, ...args: Parameters<Events[EventType]>): void`
99
+ #### `emit.eventName(...args: Parameters<Events['eventName']>): void`
86
100
 
87
101
  Emits an event, calling all registered listeners and global hooks.
88
102
 
@@ -93,8 +107,13 @@ Emits an event, calling all registered listeners and global hooks.
93
107
  **Example:**
94
108
  ```typescript
95
109
  myObject.emit('userLogin', 'user123', new Date())
110
+ myObject.emit.userLogin('user123', new Date()) // Dot notation
111
+
96
112
  myObject.emit('dataUpdate', [1, 2, 3])
113
+ myObject.emit.dataUpdate([1, 2, 3]) // Dot notation
114
+
97
115
  myObject.emit('error', new Error('Something went wrong'))
116
+ myObject.emit.error(new Error('Something went wrong')) // Dot notation
98
117
  ```
99
118
 
100
119
  #### `hook(cb: <EventType extends keyof Events>(event: EventType, ...args: Parameters<Events[EventType]>) => void): () => void`
@@ -113,6 +132,42 @@ const unsubscribe = myObject.hook((event, ...args) => {
113
132
  })
114
133
  ```
115
134
 
135
+ ## Dot Notation Syntax
136
+
137
+ For convenience, `on`, `off`, and `emit` all support a dot notation syntax that provides a cleaner alternative to the string-based API.
138
+
139
+ | String Notation | Dot Notation |
140
+ |----------------|--------------|
141
+ | `obj.on('event', cb)` | `obj.on.event(cb)` |
142
+ | `obj.off('event', cb)` | `obj.off.event(cb)` |
143
+ | `obj.off('event')` | `obj.off.event()` |
144
+ | `obj.emit('event', args)` | `obj.emit.event(args)` |
145
+
146
+ Both forms are functionally equivalent. The dot notation provides:
147
+
148
+ - **Cleaner syntax**: No quotes needed around event names
149
+ - **Better IDE support**: Autocomplete for event names
150
+ - **Type safety**: Full TypeScript inference for event arguments
151
+
152
+ **Example:**
153
+
154
+ ```typescript
155
+ class Button extends Eventful<{ click: (x: number, y: number) => void; hover: () => void }> {}
156
+
157
+ const button = new Button()
158
+
159
+ // Register listener using dot notation
160
+ button.on.click((x, y) => console.log(`Clicked at ${x}, ${y}`))
161
+
162
+ // Emit using dot notation
163
+ button.emit.click(100, 200)
164
+
165
+ // Remove listener using dot notation
166
+ button.off.click(myCallback)
167
+ // Or remove all click listeners
168
+ button.off.click()
169
+ ```
170
+
116
171
  ## Usage Examples
117
172
 
118
173
  ### Basic Event Handling