mutts 1.0.9 → 1.0.11

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 (206) hide show
  1. package/README.md +60 -35
  2. package/dist/browser.cjs +1333 -1760
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.ts +2 -1392
  5. package/dist/browser.dev.cjs +114 -0
  6. package/dist/browser.dev.cjs.map +1 -0
  7. package/dist/browser.dev.d.ts +2 -0
  8. package/dist/browser.dev.esm.js +5 -0
  9. package/dist/browser.dev.esm.js.map +1 -0
  10. package/dist/browser.esm.js +18 -97
  11. package/dist/browser.esm.js.map +1 -1
  12. package/dist/chunks/{async-browser-lvzLOCgk.cjs → async-browser-Dgr5CreQ.cjs} +16 -95
  13. package/dist/chunks/async-browser-Dgr5CreQ.cjs.map +1 -0
  14. package/dist/chunks/{async-node-C3DeIb0y.cjs → async-node-3PrbVAbB.cjs} +3 -1
  15. package/dist/chunks/async-node-3PrbVAbB.cjs.map +1 -0
  16. package/dist/chunks/index-Sf74wXTV.esm.js +2577 -0
  17. package/dist/chunks/index-Sf74wXTV.esm.js.map +1 -0
  18. package/dist/chunks/node-Bo7WU5S2.esm.js +96 -0
  19. package/dist/chunks/node-Bo7WU5S2.esm.js.map +1 -0
  20. package/dist/chunks/{index-VTO-b2vR.cjs → proxy-Cc79Lrzj.cjs} +2593 -3216
  21. package/dist/chunks/proxy-Cc79Lrzj.cjs.map +1 -0
  22. package/dist/chunks/{index-CtA2AWl3.esm.js → proxy-D2C49sXH.esm.js} +2570 -3174
  23. package/dist/chunks/proxy-D2C49sXH.esm.js.map +1 -0
  24. package/dist/debug.cjs +987 -28
  25. package/dist/debug.cjs.map +1 -1
  26. package/dist/debug.d.ts +37 -38
  27. package/dist/debug.esm.js +963 -1
  28. package/dist/debug.esm.js.map +1 -1
  29. package/dist/devtools/manifest.json +1 -1
  30. package/dist/devtools/panel.html +1 -1
  31. package/dist/devtools/panel.js +107 -94
  32. package/dist/devtools/panel.js.map +1 -1
  33. package/dist/index.d.ts +1322 -1
  34. package/dist/mutts.umd.js +6647 -1
  35. package/dist/mutts.umd.js.map +1 -1
  36. package/dist/mutts.umd.min.js +1 -1
  37. package/dist/mutts.umd.min.js.map +1 -1
  38. package/dist/node.cjs +69 -63
  39. package/dist/node.cjs.map +1 -1
  40. package/dist/node.d.ts +2 -2
  41. package/dist/node.dev.cjs +114 -0
  42. package/dist/node.dev.cjs.map +1 -0
  43. package/dist/node.dev.d.ts +2 -0
  44. package/dist/node.dev.esm.js +6 -0
  45. package/dist/node.dev.esm.js.map +1 -0
  46. package/dist/node.esm.js +4 -97
  47. package/dist/node.esm.js.map +1 -1
  48. package/dist/{types-DaHFfhlN.d.ts → types-Bx2PhORg.d.ts} +134 -88
  49. package/docs/ai/api-reference.md +11 -32
  50. package/docs/ai/manual.md +297 -239
  51. package/docs/reactive/advanced.md +318 -9
  52. package/docs/reactive/attend.md +2 -4
  53. package/docs/reactive/collections.md +22 -187
  54. package/docs/reactive/core.md +223 -131
  55. package/docs/reactive/debugging.md +119 -12
  56. package/docs/reactive/error-handling.md +10 -10
  57. package/docs/reactive/resource.md +125 -0
  58. package/docs/reactive.md +3 -4
  59. package/docs/utils.md +70 -0
  60. package/docs/zone.md +1 -1
  61. package/package.json +76 -38
  62. package/dist/chunks/async-browser-lvzLOCgk.cjs.map +0 -1
  63. package/dist/chunks/async-node-C3DeIb0y.cjs.map +0 -1
  64. package/dist/chunks/index-2vea86wD.esm.js +0 -3011
  65. package/dist/chunks/index-2vea86wD.esm.js.map +0 -1
  66. package/dist/chunks/index-CtA2AWl3.esm.js.map +0 -1
  67. package/dist/chunks/index-VTO-b2vR.cjs.map +0 -1
  68. package/dist/debug/debug.d.ts +0 -122
  69. package/dist/debug/debug.d.ts.map +0 -1
  70. package/dist/debug/index.d.ts +0 -4
  71. package/dist/debug/index.d.ts.map +0 -1
  72. package/dist/debug/lineage-panel.d.ts +0 -5
  73. package/dist/debug/lineage-panel.d.ts.map +0 -1
  74. package/dist/debug/lineage.d.ts +0 -79
  75. package/dist/debug/lineage.d.ts.map +0 -1
  76. package/dist/src/async/browser.d.ts +0 -2
  77. package/dist/src/async/browser.d.ts.map +0 -1
  78. package/dist/src/async/index.d.ts +0 -19
  79. package/dist/src/async/index.d.ts.map +0 -1
  80. package/dist/src/async/node.d.ts +0 -2
  81. package/dist/src/async/node.d.ts.map +0 -1
  82. package/dist/src/decorator.d.ts +0 -106
  83. package/dist/src/decorator.d.ts.map +0 -1
  84. package/dist/src/destroyable.d.ts +0 -87
  85. package/dist/src/destroyable.d.ts.map +0 -1
  86. package/dist/src/entry-browser.d.ts +0 -3
  87. package/dist/src/entry-browser.d.ts.map +0 -1
  88. package/dist/src/entry-node.d.ts +0 -3
  89. package/dist/src/entry-node.d.ts.map +0 -1
  90. package/dist/src/eventful.d.ts +0 -20
  91. package/dist/src/eventful.d.ts.map +0 -1
  92. package/dist/src/flavored.d.ts +0 -33
  93. package/dist/src/flavored.d.ts.map +0 -1
  94. package/dist/src/index.d.ts +0 -14
  95. package/dist/src/index.d.ts.map +0 -1
  96. package/dist/src/indexable.d.ts +0 -243
  97. package/dist/src/indexable.d.ts.map +0 -1
  98. package/dist/src/introspection.d.ts +0 -27
  99. package/dist/src/introspection.d.ts.map +0 -1
  100. package/dist/src/iterableWeak.d.ts +0 -53
  101. package/dist/src/iterableWeak.d.ts.map +0 -1
  102. package/dist/src/mixins.d.ts +0 -25
  103. package/dist/src/mixins.d.ts.map +0 -1
  104. package/dist/src/promiseChain.d.ts +0 -20
  105. package/dist/src/promiseChain.d.ts.map +0 -1
  106. package/dist/src/reactive/array.d.ts +0 -48
  107. package/dist/src/reactive/array.d.ts.map +0 -1
  108. package/dist/src/reactive/buffer.d.ts +0 -120
  109. package/dist/src/reactive/buffer.d.ts.map +0 -1
  110. package/dist/src/reactive/change.d.ts +0 -29
  111. package/dist/src/reactive/change.d.ts.map +0 -1
  112. package/dist/src/reactive/deep-touch.d.ts +0 -28
  113. package/dist/src/reactive/deep-touch.d.ts.map +0 -1
  114. package/dist/src/reactive/deep-watch-state.d.ts +0 -25
  115. package/dist/src/reactive/deep-watch-state.d.ts.map +0 -1
  116. package/dist/src/reactive/deep-watch.d.ts +0 -20
  117. package/dist/src/reactive/deep-watch.d.ts.map +0 -1
  118. package/dist/src/reactive/describe.d.ts +0 -12
  119. package/dist/src/reactive/describe.d.ts.map +0 -1
  120. package/dist/src/reactive/effect-context.d.ts +0 -34
  121. package/dist/src/reactive/effect-context.d.ts.map +0 -1
  122. package/dist/src/reactive/effects.d.ts +0 -164
  123. package/dist/src/reactive/effects.d.ts.map +0 -1
  124. package/dist/src/reactive/index.d.ts +0 -19
  125. package/dist/src/reactive/index.d.ts.map +0 -1
  126. package/dist/src/reactive/map.d.ts +0 -28
  127. package/dist/src/reactive/map.d.ts.map +0 -1
  128. package/dist/src/reactive/memoize.d.ts +0 -28
  129. package/dist/src/reactive/memoize.d.ts.map +0 -1
  130. package/dist/src/reactive/non-reactive-state.d.ts +0 -9
  131. package/dist/src/reactive/non-reactive-state.d.ts.map +0 -1
  132. package/dist/src/reactive/non-reactive.d.ts +0 -11
  133. package/dist/src/reactive/non-reactive.d.ts.map +0 -1
  134. package/dist/src/reactive/project.d.ts +0 -40
  135. package/dist/src/reactive/project.d.ts.map +0 -1
  136. package/dist/src/reactive/proxy-state.d.ts +0 -8
  137. package/dist/src/reactive/proxy-state.d.ts.map +0 -1
  138. package/dist/src/reactive/proxy.d.ts +0 -23
  139. package/dist/src/reactive/proxy.d.ts.map +0 -1
  140. package/dist/src/reactive/record.d.ts +0 -115
  141. package/dist/src/reactive/record.d.ts.map +0 -1
  142. package/dist/src/reactive/register.d.ts +0 -125
  143. package/dist/src/reactive/register.d.ts.map +0 -1
  144. package/dist/src/reactive/registry.d.ts +0 -21
  145. package/dist/src/reactive/registry.d.ts.map +0 -1
  146. package/dist/src/reactive/set.d.ts +0 -26
  147. package/dist/src/reactive/set.d.ts.map +0 -1
  148. package/dist/src/reactive/tracking.d.ts +0 -7
  149. package/dist/src/reactive/tracking.d.ts.map +0 -1
  150. package/dist/src/reactive/types.d.ts +0 -424
  151. package/dist/src/reactive/types.d.ts.map +0 -1
  152. package/dist/src/reactive/watch.d.ts +0 -48
  153. package/dist/src/reactive/watch.d.ts.map +0 -1
  154. package/dist/src/std-decorators.d.ts +0 -45
  155. package/dist/src/std-decorators.d.ts.map +0 -1
  156. package/dist/src/utils.d.ts +0 -49
  157. package/dist/src/utils.d.ts.map +0 -1
  158. package/dist/src/zone.d.ts +0 -40
  159. package/dist/src/zone.d.ts.map +0 -1
  160. package/docs/reactive/describe.md +0 -85
  161. package/docs/reactive/project.md +0 -93
  162. package/docs/reactive/scan.md +0 -293
  163. package/src/async/browser.ts +0 -323
  164. package/src/async/index.ts +0 -27
  165. package/src/async/node.ts +0 -92
  166. package/src/decorator.ts +0 -272
  167. package/src/destroyable.ts +0 -199
  168. package/src/entry-browser.ts +0 -5
  169. package/src/entry-node.ts +0 -5
  170. package/src/eventful.ts +0 -110
  171. package/src/flavored.ts +0 -106
  172. package/src/index.d.ts +0 -12
  173. package/src/index.ts +0 -64
  174. package/src/indexable.ts +0 -526
  175. package/src/introspection.ts +0 -59
  176. package/src/iterableWeak.ts +0 -233
  177. package/src/mixins.ts +0 -123
  178. package/src/promiseChain.ts +0 -110
  179. package/src/reactive/array.ts +0 -500
  180. package/src/reactive/buffer.ts +0 -328
  181. package/src/reactive/change.ts +0 -131
  182. package/src/reactive/deep-touch.ts +0 -273
  183. package/src/reactive/deep-watch-state.ts +0 -82
  184. package/src/reactive/deep-watch.ts +0 -171
  185. package/src/reactive/describe.ts +0 -39
  186. package/src/reactive/effect-context.ts +0 -83
  187. package/src/reactive/effects.ts +0 -1434
  188. package/src/reactive/index.ts +0 -72
  189. package/src/reactive/map.ts +0 -142
  190. package/src/reactive/memoize.ts +0 -186
  191. package/src/reactive/non-reactive-state.ts +0 -49
  192. package/src/reactive/non-reactive.ts +0 -43
  193. package/src/reactive/project.md +0 -107
  194. package/src/reactive/project.ts +0 -430
  195. package/src/reactive/proxy-state.ts +0 -27
  196. package/src/reactive/proxy.ts +0 -282
  197. package/src/reactive/record.ts +0 -181
  198. package/src/reactive/register.ts +0 -538
  199. package/src/reactive/registry.ts +0 -72
  200. package/src/reactive/set.ts +0 -117
  201. package/src/reactive/tracking.ts +0 -41
  202. package/src/reactive/types.ts +0 -520
  203. package/src/reactive/watch.ts +0 -180
  204. package/src/std-decorators.ts +0 -256
  205. package/src/utils.ts +0 -300
  206. package/src/zone.ts +0 -142
@@ -4,27 +4,22 @@
4
4
 
5
5
  - [Introduction](#introduction)
6
6
  - [Getting Started](#getting-started)
7
- - [5-Minute Quick Start](#5-minute-quick-start)
7
+ - [5-Minute Quick Start](#5-minute-quick-start)
8
8
  - [Core API](#core-api)
9
9
  - [Effect System](#effect-system)
10
- - [Error Handling](./error-handling.md)
10
+ - [Error Handling](./error-handling.md)
11
11
  - [Atomic Operations](./advanced.md#atomic-operations)
12
12
  - [Advanced Effects](./advanced.md#advanced-effects)
13
13
  - [Evolution Tracking](./advanced.md#evolution-tracking)
14
14
  - [Prototype Chains and Pure Objects](./advanced.md#prototype-chains-and-pure-objects)
15
15
  - [Recursive Touching](./advanced.md#recursive-touching)
16
- - [Why Not Deep Watching?](./advanced.md#why-not-deep-watching)
17
- - [Collections](./advanced.md#collections)
18
- - [Register](./advanced.md#register)
19
- - [Class Reactivity](./advanced.md#class-reactivity)
20
- - [Non-Reactive System](./advanced.md#non-reactive-system)
21
- - [Array Mapping](./advanced.md#array-mapping)
22
- - [Projection](./advanced.md#projection)
23
- - [Reactive Property Description](./describe.md)
24
- - [Record Organization](./advanced.md#record-organization)
16
+ - [Collections](./collections.md)
17
+ - [Class Reactivity](#class-reactivity)
18
+ - [Non-Reactive System](#non-reactive-system)
19
+ - [Morphing](./advanced.md#morph)
25
20
  - [Memoization](./advanced.md#memoization)
26
21
  - [Debugging and Development](./advanced.md#debugging-and-development)
27
- - [Cycle Detection](./advanced.md#cycle-detection)
22
+ - [Cycle Detection](./advanced.md#cycle-detection)
28
23
 
29
24
  ## Introduction
30
25
 
@@ -44,10 +39,13 @@ Reactivity is a programming paradigm where the system automatically tracks depen
44
39
  ### Basic Example
45
40
 
46
41
  ```typescript
47
- import { reactive, effect } from 'mutts/reactive'
42
+ import { reactive, effect } from 'mutts'
48
43
 
49
44
  // Create a reactive object
50
- const user = reactive({ name: "John", age: 30 })
45
+ const user = reactive({
46
+ name: "John",
47
+ age: 30
48
+ })
51
49
 
52
50
  // Create an effect that depends on user properties
53
51
  effect(() => {
@@ -55,8 +53,8 @@ effect(() => {
55
53
  })
56
54
 
57
55
  // When properties change, the effect automatically re-runs
58
- user.name = "Jane" // Triggers effect
59
- user.age = 25 // Triggers effect
56
+ user.name = "Jane" // Triggers effect
57
+ user.age = 25 // Triggers effect
60
58
  ```
61
59
 
62
60
  ## Getting Started
@@ -66,38 +64,45 @@ user.age = 25 // Triggers effect
66
64
  Learn the essentials in 5 minutes:
67
65
 
68
66
  **1. Make state reactive:**
67
+
69
68
  ```typescript
70
- import { reactive, effect } from 'mutts/reactive'
69
+ import { reactive, effect } from 'mutts'
71
70
 
72
- const state = reactive({ count: 0, name: "John" })
71
+ const state = reactive({
72
+ count: 0,
73
+ name: "John"
74
+ })
73
75
  ```
74
76
 
75
77
  **2. React to changes:**
78
+
76
79
  ```typescript
77
80
  // Effects automatically re-run when dependencies change
78
81
  effect(() => {
79
- console.log(`Hello ${state.name}, count is ${state.count}`)
82
+ console.log(`Hello ${state.name}, count is ${state.count}`)
80
83
  })
81
84
 
82
- state.count++ // Triggers effect
83
- state.name = "Jane" // Triggers effect
85
+ state.count++ // Triggers effect
86
+ state.name = "Jane" // Triggers effect
84
87
  ```
85
88
 
86
89
  **3. Work with arrays:**
90
+
87
91
  ```typescript
88
92
  const items = reactive([1, 2, 3])
89
93
 
90
94
  effect(() => {
91
- console.log(`Array length: ${items.length}`)
95
+ console.log(`Array length: ${items.length}`)
92
96
  })
93
97
 
94
- items.push(4) // Triggers effect
98
+ items.push(4) // Triggers effect
95
99
  ```
96
100
 
97
101
  **4. Use memoization:**
102
+
98
103
  ```typescript
99
104
  const memoized = memoize((user: User) => {
100
- return expensiveComputation(user)
105
+ return expensiveComputation(user)
101
106
  })
102
107
 
103
108
  // Only recomputes when user changes
@@ -105,25 +110,12 @@ const result = memoized(user)
105
110
  ```
106
111
 
107
112
  **5. Map over arrays:**
108
- ```typescript
109
- const source = reactive([1, 2, 3])
110
- const doubled = project(source, ({ value }) => value * 2)
111
- // [2, 4, 6]
112
113
 
113
- source.push(4) // doubled automatically becomes [2, 4, 6, 8]
114
- ```
115
-
116
- **6. Define properties reactively:**
117
114
  ```typescript
118
- const target = {}
119
- const descriptors = reactive({
120
- foo: { value: 1, enumerable: true }
121
- })
122
- describe(descriptors, target)
123
- // target.foo is 1
115
+ const source = reactive([1, 2, 3])
116
+ const doubled = morph(source, value => value * 2) // [2, 4, 6]
124
117
 
125
- descriptors.bar = { value: 2, enumerable: true }
126
- // target.bar is 2 automatically
118
+ source.push(4) // doubled automatically becomes [2, 4, 6, 8]
127
119
  ```
128
120
 
129
121
  **Ready to go!** Continue reading for advanced features.
@@ -139,7 +131,7 @@ npm install mutts
139
131
  ### Basic Usage
140
132
 
141
133
  ```typescript
142
- import { reactive, effect } from 'mutts/reactive'
134
+ import { reactive, effect } from 'mutts'
143
135
 
144
136
  // Make an object reactive
145
137
  const state = reactive({
@@ -157,17 +149,19 @@ effect(() => {
157
149
  })
158
150
 
159
151
  // Changes trigger effects automatically
160
- state.count++ // Triggers first effect
152
+ state.count++ // Triggers first effect
161
153
  state.message = "Hi" // Triggers second effect
162
154
  ```
163
155
 
164
156
  ### Hello World Example
165
157
 
166
158
  ```typescript
167
- import { reactive, effect } from 'mutts/reactive'
159
+ import { reactive, effect } from 'mutts'
168
160
 
169
161
  // Simple counter
170
- const counter = reactive({ value: 0 })
162
+ const counter = reactive({
163
+ value: 0
164
+ })
171
165
 
172
166
  effect(() => {
173
167
  document.body.innerHTML = `Count: ${counter.value}`
@@ -195,11 +189,14 @@ function reactive<T extends Record<PropertyKey, any>>(target: T): T
195
189
  **Returns:** A reactive proxy of the original object
196
190
 
197
191
  **Example:**
192
+
198
193
  ```typescript
199
- const obj = { count: 0 }
200
- const reactiveObj = reactive(obj)
194
+ const obj = {
195
+ count: 0
196
+ }
197
+
198
+ const reactiveObj = reactive(obj) // reactiveObj is now reactive
201
199
 
202
- // reactiveObj is now reactive
203
200
  effect(() => {
204
201
  console.log(reactiveObj.count) // Tracks dependency
205
202
  })
@@ -209,28 +206,13 @@ reactiveObj.count = 5 // Triggers effect
209
206
 
210
207
  **Note:** The same object will always return the same proxy instance.
211
208
 
212
- **Pure Objects and Prototypes:**
213
-
214
- `reactive()` works with any object type, including:
209
+ **Pure Objects and Prototypes:** `reactive()` works with any object type, including:
215
210
  - Normal objects: `reactive({ x: 1 })`
216
211
  - Pure objects: `reactive(Object.create(null))`
217
212
  - Objects with prototypes: `reactive(Object.create(parent))`
218
213
  - Class instances: `reactive(new MyClass())`
219
214
 
220
- See [Prototype Chains and Pure Objects](#prototype-chains-and-pure-objects) for detailed information about prototype chain handling.
221
-
222
- ### `describe()`
223
-
224
- Reactively defines properties on a target object based on a descriptors record.
225
-
226
- ```typescript
227
- function describe<T extends object>(
228
- descriptors: Record<PropertyKey, PropertyDescriptor>,
229
- target: T = Object.create(null) as T
230
- ): T
231
- ```
232
-
233
- See the [Reactive Property Description](./describe.md) guide for details.
215
+ See [Prototype Chains and Pure Objects](./advanced.md#prototype-chains-and-pure-objects) for detailed information about prototype chain handling.
234
216
 
235
217
  ### `effect()`
236
218
 
@@ -238,9 +220,9 @@ Creates a reactive effect that automatically re-runs when dependencies change.
238
220
 
239
221
  ```typescript
240
222
  interface DependencyAccess {
241
- tracked: DependencyFunction // Track dependencies in the current effect
242
- ascend: DependencyFunction // Track dependencies in the parent effect
243
- reaction: boolean // true after the first run
223
+ tracked: DependencyFunction // Track dependencies in the current effect
224
+ ascend: DependencyFunction // Track dependencies in the parent effect
225
+ reaction: boolean // true after the first run
244
226
  }
245
227
 
246
228
  function effect(
@@ -261,16 +243,21 @@ function effect(
261
243
  **Returns:** A cleanup function to stop the effect
262
244
 
263
245
  **Example:**
246
+
264
247
  ```typescript
265
- const state = reactive({ count: 0, mood: 'happy' })
248
+ const state = reactive({
249
+ count: 0,
250
+ mood: 'happy'
251
+ })
266
252
 
267
253
  const cleanup = effect(() => {
268
254
  console.log(`Count is: ${state.count}`)
255
+
269
256
  // Optional cleanup called before next run
270
257
  return () => console.log('Cleaning up...')
271
258
  })
272
259
 
273
- state.count++ // Does trigger: 1- the cleaning, 2- the effect
260
+ state.count++ // Does trigger: 1- the cleaning, 2- the effect
274
261
  state.mood = 'surprised' // Does not trigger the effect
275
262
 
276
263
  // Later...
@@ -290,8 +277,13 @@ effect(({ reaction }) => {
290
277
  ```
291
278
 
292
279
  **Using effect with arguments (useful in loops):**
280
+
293
281
  ```typescript
294
- const items = reactive([{ id: 1 }, { id: 2 }, { id: 3 }])
282
+ const items = reactive([
283
+ { id: 1 },
284
+ { id: 2 },
285
+ { id: 3 }
286
+ ])
295
287
 
296
288
  // Create effects in a loop, passing loop variables
297
289
  for (let i = 0; i < items.length; i++) {
@@ -310,8 +302,12 @@ function unwrap<T>(proxy: T): T
310
302
  ```
311
303
 
312
304
  **Example:**
305
+
313
306
  ```typescript
314
- const original = { count: 0 }
307
+ const original = {
308
+ count: 0
309
+ }
310
+
315
311
  const reactive = reactive(original)
316
312
  const unwrapped = unwrap(reactive)
317
313
 
@@ -342,7 +338,10 @@ function isNonReactive(obj: any): boolean
342
338
  Effects are the core of the reactive system. They automatically track dependencies and re-run when those dependencies change.
343
339
 
344
340
  ```typescript
345
- const state = reactive({ count: 0, name: "John" })
341
+ const state = reactive({
342
+ count: 0,
343
+ name: "John"
344
+ })
346
345
 
347
346
  effect(() => {
348
347
  // This effect depends on state.count
@@ -350,7 +349,7 @@ effect(() => {
350
349
  })
351
350
 
352
351
  // Only changing count triggers the effect
353
- state.count = 5 // Triggers effect
352
+ state.count = 5 // Triggers effect
354
353
  state.name = "Jane" // Does NOT trigger effect
355
354
  ```
356
355
 
@@ -359,7 +358,9 @@ state.name = "Jane" // Does NOT trigger effect
359
358
  Effects return cleanup functions that you can call to stop tracking dependencies.
360
359
 
361
360
  ```typescript
362
- const state = reactive({ count: 0 })
361
+ const state = reactive({
362
+ count: 0
363
+ })
363
364
 
364
365
  const stopEffect = effect(() => {
365
366
  console.log(`Count: ${state.count}`)
@@ -379,13 +380,17 @@ The reactive system provides **automatic cleanup** for effects, making memory ma
379
380
 
380
381
  1. **Parent-Child Cleanup**: When an effect is created inside another effect, it becomes a "child" of the parent effect. When the parent effect is cleaned up, it also cleans up its child effects.
381
382
 
382
- 2. **Garbage Collection Cleanup**: For top-level effects (not created inside other effects), the system uses JavaScript's garbage collection to automatically clean them up when their cleanup function no longer referenced.
383
+ 2. **Garbage Collection Cleanup**: For top-level effects (not created inside other effects), the system uses JavaScript's garbage collection to automatically clean them up when their cleanup function is no longer referenced.
383
384
 
384
385
  #### Examples
385
386
 
386
387
  **Parent-Child Cleanup:**
388
+
387
389
  ```typescript
388
- const state = reactive({ a: 1, b: 2 })
390
+ const state = reactive({
391
+ a: 1,
392
+ b: 2
393
+ })
389
394
 
390
395
  const stopParent = effect(() => {
391
396
  state.a
@@ -404,8 +409,11 @@ stopParent() // Logs (order may vary): "Child cleanup", then "Parent cleanup"
404
409
  ```
405
410
 
406
411
  **Garbage Collection Cleanup:**
412
+
407
413
  ```typescript
408
- const state = reactive({ value: 1 })
414
+ const state = reactive({
415
+ value: 1
416
+ })
409
417
 
410
418
  // Top-level effect - automatically cleaned up via garbage collection
411
419
  effect(() => {
@@ -421,7 +429,10 @@ effect(() => {
421
429
  While cleanup can be automatic via GC, you should **store and remember** cleanup functions both to prevent the effect from being garbage-collected (keeping it alive) and to perform immediate cleanup when needed. If you don't hold a reference to the cleanup (or to the effect), the effect can be collected and its cleanup called automatically by GC; storing a reference keeps it active under your control:
422
430
 
423
431
  ```typescript
424
- const state = reactive({ value: 1 })
432
+ const state = reactive({
433
+ value: 1
434
+ })
435
+
425
436
  const activeEffects: (() => void)[] = []
426
437
 
427
438
  // Store cleanup functions for effects with side effects
@@ -447,19 +458,79 @@ activeEffects.forEach(stop => stop())
447
458
  ```
448
459
 
449
460
  **Key Points:**
450
-
451
461
  - **You do not have to call cleanup** - GC may clean up effects when no references remain
452
462
  - **You may want to call cleanup** - especially for effects with side effects
453
463
  - **Store cleanup references to keep effects alive** - holding a reference prevents GC cleanup and gives you explicit stop control
454
464
  - **Parent cleanup cleans child effects** - stopping a parent also stops its child effects
455
465
  - **Child effects are referenced by parent effects** - and therefore are not subject to GC cleanups
456
466
 
467
+ ### Object Lifecycle with `link` / `unlink`
468
+
469
+ While effects handle their own cleanup automatically, **derived reactive objects** (morph results, lifted arrays, processed children, etc.) often need explicit lifecycle management. `link` and `unlink` solve this by forming a **cleanup tree** — an ownership graph independent of the effect hierarchy.
470
+
471
+ ```typescript
472
+ import { link, unlink } from 'mutts'
473
+ ```
474
+
475
+ #### Attaching cleanup dependencies
476
+
477
+ `link(owner, ...deps)` attaches dependencies to an owner object. Dependencies can be:
478
+ - **Functions** — called with an optional `CleanupReason` on disposal
479
+ - **Objects** — recursively `unlink`ed on disposal
480
+
481
+ ```typescript
482
+ const parent = reactive({
483
+ items: []
484
+ })
485
+
486
+ // Attach a cleanup callback
487
+ link(parent, () => console.log('parent disposed'))
488
+
489
+ // Attach child objects — they will be recursively unlinked
490
+ const childA = morph(parent.items, ({ value }) => value * 2)
491
+ const childB = morph(parent.items, ({ value }) => value + 1)
492
+
493
+ link(parent, childA, childB)
494
+
495
+ // Mixed: objects and functions together
496
+ link(parent, childA, () => timer.clear())
497
+ ```
498
+
499
+ #### Disposing an object
500
+
501
+ `unlink(obj)` disposes all dependencies attached to `obj`:
502
+
503
+ ```typescript
504
+ unlink(parent)
505
+
506
+ // 1. childA is recursively unlinked (its own deps disposed)
507
+ // 2. childB is recursively unlinked
508
+ // 3. the callback runs: "parent disposed"
509
+ ```
510
+
511
+ Calling `unlink` twice is safe — the second call is a no-op. Not calling `unlink` is also fine — the object will be cleaned up when garbage is collected.
512
+
513
+ #### When to use `link`/`unlink` vs effects
514
+
515
+ | Scenario | Use |
516
+ |---|---|
517
+ | Reacting to state changes | `effect` |
518
+ | Tying a derived object's lifetime to its owner | `link` / `unlink` |
519
+ | Cleaning up timers, listeners, DOM nodes | `effect` return or `link` callback |
520
+ | Building a cleanup tree across multiple reactive objects | `link` / `unlink` |
521
+
522
+ **Rule of thumb**: if the thing you're cleaning up is a *reactive object* (not a function), prefer `link`. If it's a *side effect* (timer, listener), prefer an `effect` return or a `link` callback.
523
+
457
524
  ### Effect Dependencies
458
525
 
459
526
  Effects automatically track which reactive properties they access.
460
527
 
461
528
  ```typescript
462
- const state = reactive({ a: 1, b: 2, c: 3 })
529
+ const state = reactive({
530
+ a: 1,
531
+ b: 2,
532
+ c: 3
533
+ })
463
534
 
464
535
  effect(() => {
465
536
  // Only tracks state.a and state.b
@@ -474,13 +545,11 @@ state.c = 15 // Does NOT trigger effect
474
545
 
475
546
  ### Async Effects and the `access` Parameter
476
547
 
477
- 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.
478
-
479
- In modern `mutts`, this is powered by the **Zone system**. When zones are registered in `asyncZone`, the active effect context is automatically preserved across `await` points and timers, making manual use of `tracked` optional for these cases.
548
+ 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. In modern `mutts`, this is powered by the **Zone system**. When zones are registered in `asyncZone`, the active effect context is automatically preserved across `await` points and timers, making manual use of `tracked` optional for these cases.
480
549
 
481
550
  #### The Problem with Async Effects
482
551
 
483
- Traditionally, in JavaScript, async functions lose their context when they yield control.
552
+ Traditionally, in JavaScript, async functions lose their context when they yield control.
484
553
 
485
554
  #### Understanding the active effect context
486
555
 
@@ -497,36 +566,28 @@ effect(() => {
497
566
  // Async effect WITHOUT zone registration - context is lost after await
498
567
  effect(async () => {
499
568
  const value = state.count // ✅ Tracked
500
-
501
- await someAsyncOperation()
502
-
503
- // context is lost!
569
+ await someAsyncOperation() // context is lost!
504
570
  const another = state.name // ❌ NOT tracked
505
571
  })
506
572
 
507
573
  // Async effect WITH zone registration - context is preserved
508
574
  effect(async () => {
509
575
  const value = state.count // ✅ Tracked
510
-
511
- await someAsyncOperation()
512
-
513
- // context is automatically restored!
576
+ await someAsyncOperation() // context is automatically restored!
514
577
  const another = state.name // ✅ Tracked
515
578
  })
516
579
 
517
580
  // Using access.tracked() for manual restoration
518
581
  effect(async ({ tracked }) => {
519
- await someAsyncOperation()
520
-
521
- // Useful for non-patched APIs or explicit scoping
582
+ await someAsyncOperation() // Useful for non-patched APIs or explicit scoping
522
583
  const another = tracked(() => state.name) // ✅ Tracked
523
584
  })
524
585
  ```
525
586
 
526
587
  #### Key Benefits of the Zone System
527
588
 
528
- 1. **Automatic Restoration**: With zones registered in `asyncZone`, most native async APIs (Promises, timers) automatically preserve the reactive context.
529
- 2. **Manual Control**: `access.tracked()` allows you to manually "passport" the context into third-party libraries or unmanaged callbacks.
589
+ 1. **Automatic Restoration**: With zones registered in `asyncZone`, most native async APIs (Promises, timers) automatically preserve the reactive context.
590
+ 2. **Manual Control**: `access.tracked()` allows you to manually "passport" the context into third-party libraries or unmanaged callbacks.
530
591
 
531
592
  ### Using `ascend` for Parent Effect Tracking
532
593
 
@@ -562,18 +623,21 @@ effect(({ ascend }) => {
562
623
  Effects can be created inside other effects and will have separate effect scopes:
563
624
 
564
625
  ```typescript
565
- import { effect, reactive } from 'mutts/reactive'
626
+ import { effect, reactive } from 'mutts'
566
627
 
567
- const state = reactive({ a: 0, b: 0 })
628
+ const state = reactive({
629
+ a: 0,
630
+ b: 0
631
+ })
568
632
 
569
633
  const stopOuter = effect(() => {
570
634
  state.a
571
-
635
+
572
636
  // Create an inner effect with its own scope
573
637
  const stopInner = effect(() => {
574
638
  state.b
575
639
  })
576
-
640
+
577
641
  // Return cleanup function for the inner effect
578
642
  return stopInner
579
643
  })
@@ -584,21 +648,25 @@ const stopOuter = effect(() => {
584
648
  The `untracked()` function allows you to run code without tracking dependencies, which can be useful for creating effects or performing operations that shouldn't be part of the current effect's dependency graph.
585
649
 
586
650
  ```typescript
587
- import { effect, untracked, reactive } from 'mutts/reactive'
651
+ import { effect, untracked, reactive } from 'mutts'
588
652
 
589
- const state = reactive({ a: 0, b: 0 })
653
+ const state = reactive({
654
+ a: 0,
655
+ b: 0
656
+ })
590
657
 
591
658
  effect(() => {
592
659
  state.a
593
-
660
+
594
661
  // Create an inner effect without tracking the creation under the outer effect
595
662
  let stopInner: (() => void) | undefined
663
+
596
664
  untracked(() => {
597
665
  stopInner = effect(() => {
598
666
  state.b
599
667
  })
600
668
  })
601
-
669
+
602
670
  // Optionally stop it immediately to avoid accumulating watchers
603
671
  stopInner && stopInner()
604
672
  })
@@ -614,7 +682,7 @@ effect(() => {
614
682
  Configure the reactive system behavior:
615
683
 
616
684
  ```typescript
617
- import { options as reactiveOptions } from 'mutts/reactive'
685
+ import { options as reactiveOptions } from 'mutts'
618
686
 
619
687
  // Set maximum effect chain depth
620
688
  reactiveOptions.maxEffectChain = 50
@@ -640,9 +708,14 @@ The `effect` function provides convenient shortcut modifiers for common options.
640
708
  Creates an opaque effect that tracks object references rather than deep content. This is useful when you want effects to re-run only when the object identity changes, not when its properties change.
641
709
 
642
710
  ```typescript
643
- import { effect, reactive } from 'mutts/reactive'
711
+ import { effect, reactive } from 'mutts'
644
712
 
645
- const item = reactive({ id: 1, data: { value: 10 } })
713
+ const item = reactive({
714
+ id: 1,
715
+ data: {
716
+ value: 10
717
+ }
718
+ })
646
719
 
647
720
  // Regular effect - triggers on any property change
648
721
  effect(() => {
@@ -654,8 +727,9 @@ effect.opaque(() => {
654
727
  console.log('Data object:', item.data) // Only triggers when item.data is replaced
655
728
  })
656
729
 
657
- item.data.value = 20 // Triggers regular effect, NOT opaque effect
658
- item.data = { value: 30 } // Triggers BOTH effects
730
+ item.data.value = 20 // Triggers regular effect, NOT opaque effect
731
+
732
+ item.data = { value: 30 } // Triggers BOTH effects
659
733
  ```
660
734
 
661
735
  **Use cases for opaque effects:**
@@ -668,9 +742,11 @@ item.data = { value: 30 } // Triggers BOTH effects
668
742
  Creates a named effect for easier debugging and profiling. The name appears in DevTools and debug logs.
669
743
 
670
744
  ```typescript
671
- import { effect, reactive } from 'mutts/reactive'
745
+ import { effect, reactive } from 'mutts'
672
746
 
673
- const state = reactive({ count: 0 })
747
+ const state = reactive({
748
+ count: 0
749
+ })
674
750
 
675
751
  // Create a named effect
676
752
  effect.named('counter-effect')(() => {
@@ -719,13 +795,14 @@ createDataEffect(() => {
719
795
  })
720
796
  ```
721
797
 
798
+ ## Class Reactivity
722
799
 
723
800
  ### `@reactive` Decorator
724
801
 
725
802
  The `@reactive` decorator makes class instances automatically reactive. This is the recommended approach for adding reactivity to classes.
726
803
 
727
804
  ```typescript
728
- import { reactive } from 'mutts/reactive'
805
+ import { reactive } from 'mutts'
729
806
 
730
807
  @reactive
731
808
  class User {
@@ -757,7 +834,7 @@ user.name = "Jane" // Triggers effect
757
834
  You can also use the functional syntax for making classes reactive:
758
835
 
759
836
  ```typescript
760
- import { reactive } from 'mutts/reactive'
837
+ import { reactive } from 'mutts'
761
838
 
762
839
  class User {
763
840
  name: string
@@ -789,11 +866,14 @@ user.name = "Jane" // Triggers effect
789
866
  For complex inheritance trees, especially when you need to solve constructor reactivity issues, extend `ReactiveBase`:
790
867
 
791
868
  ```typescript
792
- import { ReactiveBase, reactive } from 'mutts/reactive'
869
+ import { ReactiveBase, reactive } from 'mutts'
793
870
 
794
871
  class GameObject extends ReactiveBase {
795
872
  id = 'game-object'
796
- position = { x: 0, y: 0 }
873
+ position = {
874
+ x: 0,
875
+ y: 0
876
+ }
797
877
  }
798
878
 
799
879
  class Entity extends GameObject {
@@ -813,11 +893,10 @@ effect(() => {
813
893
  })
814
894
 
815
895
  player.position.x = 10 // Triggers effect
816
- player.health = 80 // Triggers effect
896
+ player.health = 80 // Triggers effect
817
897
  ```
818
898
 
819
899
  **Advantages of `ReactiveBase`:**
820
-
821
900
  1. **Constructor Reactivity**: Solves the issue where `this` in the constructor is not yet reactive
822
901
  2. **Inheritance Safety**: Prevents reactivity from being added to prototype chains in complex inheritance trees
823
902
  3. **No Side Effects**: The base class itself has no effect - it only enables proper reactivity when combined with `@reactive`
@@ -829,7 +908,7 @@ player.health = 80 // Triggers effect
829
908
  - You want the cleanest, most modern syntax
830
909
  - You don't need to modify or use `this` in the constructor
831
910
 
832
- **Use `ReactiveBase` + `@reactive` when:**
911
+ **Use `ReactiveBase`+`@reactive` when:**
833
912
  - You have complex inheritance trees (like game objects, UI components)
834
913
  - You need to modify or use `this` in the constructor
835
914
  - You want to prevent reactivity from being added to prototype chains
@@ -884,7 +963,7 @@ effect(() => {
884
963
  console.log('Cart items:', cart.items)
885
964
  })
886
965
 
887
- cart.addItem('Apple') // Triggers effect
966
+ cart.addItem('Apple') // Triggers effect
888
967
  cart.removeItem('Apple') // Triggers effect
889
968
  ```
890
969
 
@@ -937,15 +1016,23 @@ function unreactive(target: Constructor<T>): Constructor<T>
937
1016
 
938
1017
  ```typescript
939
1018
  // Mark individual object as non-reactive
940
- const obj = { count: 0 }
1019
+ const obj = {
1020
+ count: 0
1021
+ }
1022
+
941
1023
  unreactive(obj)
1024
+
942
1025
  const reactiveObj = reactive(obj) // Returns obj unchanged
943
1026
 
944
1027
  // Mark entire class as non-reactive
945
1028
  class Utility {
946
- static helper() { return 'help' }
1029
+ static helper() {
1030
+ return 'help'
1031
+ }
947
1032
  }
1033
+
948
1034
  unreactive(Utility)
1035
+
949
1036
  const instance = new Utility()
950
1037
  const reactiveInstance = reactive(instance) // Returns instance unchanged
951
1038
  ```
@@ -959,7 +1046,6 @@ Mark class properties as non-reactive using class-level syntax.
959
1046
  @unreactive('id')
960
1047
  class User {
961
1048
  id: string = 'user-123'
962
-
963
1049
  name: string = 'John'
964
1050
  age: number = 30
965
1051
  }
@@ -968,7 +1054,7 @@ const user = new User()
968
1054
 
969
1055
  effect(() => {
970
1056
  console.log(user.name, user.age) // Tracks these
971
- console.log(user.id) // Does NOT track this
1057
+ console.log(user.id) // Does NOT track this
972
1058
  })
973
1059
 
974
1060
  user.name = 'Jane' // Triggers effect
@@ -1070,23 +1156,29 @@ Non-reactive objects can improve performance:
1070
1156
  ```typescript
1071
1157
  // Good: Mark large, rarely-changing objects as non-reactive
1072
1158
  const config = unreactive({
1073
- apiEndpoints: { /* large config object */ },
1074
- featureFlags: { /* many flags */ }
1159
+ apiEndpoints: {
1160
+ /* large config object */
1161
+ },
1162
+ featureFlags: {
1163
+ /* many flags */
1164
+ }
1075
1165
  })
1076
1166
 
1077
1167
  // Good: Mark utility classes as non-reactive
1078
1168
  class MathUtils {
1079
1169
  static PI = 3.14159
1080
- static square(x: number) { return x * x }
1170
+
1171
+ static square(x: number) {
1172
+ return x * x
1173
+ }
1081
1174
  }
1175
+
1082
1176
  unreactive(MathUtils)
1083
1177
 
1084
1178
  // Good: Mark properties that don't need reactivity
1085
1179
  class User {
1086
- @unreactive
1087
- metadata: any = {} // Large metadata object
1180
+ @unreactive metadata: any = {} // Large metadata object
1088
1181
 
1089
1182
  name: string = 'John' // This should be reactive
1090
1183
  }
1091
- ```
1092
-
1184
+ ```