mutts 1.0.8 → 1.0.10

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 (195) hide show
  1. package/README.md +77 -35
  2. package/dist/browser.cjs +2144 -5355
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.ts +2 -1655
  5. package/dist/browser.dev.cjs +110 -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 +20 -107
  11. package/dist/browser.esm.js.map +1 -1
  12. package/dist/chunks/{async-browser-CA0jPWIi.cjs → async-browser-BU_IfxYD.cjs} +19 -107
  13. package/dist/chunks/async-browser-BU_IfxYD.cjs.map +1 -0
  14. package/dist/chunks/{async-core-UqHzvJ-S.cjs → async-core-CRLKP3l-.cjs} +8 -4
  15. package/dist/chunks/async-core-CRLKP3l-.cjs.map +1 -0
  16. package/dist/chunks/{async-node-BYHuGTni.cjs → async-node-3PrbVAbB.cjs} +7 -12
  17. package/dist/chunks/async-node-3PrbVAbB.cjs.map +1 -0
  18. package/dist/chunks/index-CaaQQlPJ.esm.js +3174 -0
  19. package/dist/chunks/index-CaaQQlPJ.esm.js.map +1 -0
  20. package/dist/chunks/node-nKJBk8iJ.esm.js +96 -0
  21. package/dist/chunks/node-nKJBk8iJ.esm.js.map +1 -0
  22. package/dist/chunks/proxy-Dtg-bJ3T.cjs +3763 -0
  23. package/dist/chunks/proxy-Dtg-bJ3T.cjs.map +1 -0
  24. package/dist/chunks/proxy-r7lARftl.esm.js +3701 -0
  25. package/dist/chunks/proxy-r7lARftl.esm.js.map +1 -0
  26. package/dist/debug.cjs +980 -0
  27. package/dist/debug.cjs.map +1 -0
  28. package/dist/debug.d.ts +208 -0
  29. package/dist/debug.esm.js +950 -0
  30. package/dist/debug.esm.js.map +1 -0
  31. package/dist/devtools/manifest.json +1 -1
  32. package/dist/devtools/panel.html +1 -1
  33. package/dist/devtools/panel.js +107 -94
  34. package/dist/devtools/panel.js.map +1 -1
  35. package/dist/index.d.ts +1447 -13
  36. package/dist/mutts.umd.js +7167 -1
  37. package/dist/mutts.umd.js.map +1 -1
  38. package/dist/mutts.umd.min.js +1 -1
  39. package/dist/mutts.umd.min.js.map +1 -1
  40. package/dist/node.cjs +70 -66
  41. package/dist/node.cjs.map +1 -1
  42. package/dist/node.d.ts +2 -1
  43. package/dist/node.dev.cjs +110 -0
  44. package/dist/node.dev.cjs.map +1 -0
  45. package/dist/node.dev.d.ts +2 -0
  46. package/dist/node.dev.esm.js +6 -0
  47. package/dist/node.dev.esm.js.map +1 -0
  48. package/dist/node.esm.js +4 -103
  49. package/dist/node.esm.js.map +1 -1
  50. package/dist/{reactive/types.d.ts → types-W5vD6m2n.d.ts} +186 -93
  51. package/docs/ai/api-reference.md +12 -19
  52. package/docs/ai/manual.md +521 -17
  53. package/docs/events.md +57 -2
  54. package/docs/flavored.md +210 -0
  55. package/docs/reactive/advanced.md +314 -5
  56. package/docs/reactive/attend.md +142 -0
  57. package/docs/reactive/collections.md +76 -39
  58. package/docs/reactive/core.md +100 -14
  59. package/docs/reactive/debugging.md +123 -18
  60. package/docs/reactive/error-handling.md +121 -0
  61. package/docs/reactive/resource.md +125 -0
  62. package/docs/reactive/scan.md +247 -1
  63. package/docs/reactive.md +4 -1
  64. package/docs/std-decorators.md +102 -51
  65. package/docs/utils.md +70 -0
  66. package/docs/zone.md +3 -7
  67. package/package.json +64 -30
  68. package/dist/async/browser.d.ts +0 -2
  69. package/dist/async/browser.d.ts.map +0 -1
  70. package/dist/async/index.d.ts +0 -18
  71. package/dist/async/index.d.ts.map +0 -1
  72. package/dist/async/node.d.ts +0 -2
  73. package/dist/async/node.d.ts.map +0 -1
  74. package/dist/chunks/async-browser-CA0jPWIi.cjs.map +0 -1
  75. package/dist/chunks/async-core-UqHzvJ-S.cjs.map +0 -1
  76. package/dist/chunks/async-node-BYHuGTni.cjs.map +0 -1
  77. package/dist/chunks/index-DhaOVusv.esm.js +0 -6411
  78. package/dist/chunks/index-DhaOVusv.esm.js.map +0 -1
  79. package/dist/decorator.d.ts +0 -106
  80. package/dist/decorator.d.ts.map +0 -1
  81. package/dist/destroyable.d.ts +0 -87
  82. package/dist/destroyable.d.ts.map +0 -1
  83. package/dist/entry-browser.d.ts +0 -3
  84. package/dist/entry-browser.d.ts.map +0 -1
  85. package/dist/entry-node.d.ts +0 -3
  86. package/dist/entry-node.d.ts.map +0 -1
  87. package/dist/eventful.d.ts +0 -18
  88. package/dist/eventful.d.ts.map +0 -1
  89. package/dist/index.d.ts.map +0 -1
  90. package/dist/indexable.d.ts +0 -243
  91. package/dist/indexable.d.ts.map +0 -1
  92. package/dist/introspection.d.ts +0 -27
  93. package/dist/introspection.d.ts.map +0 -1
  94. package/dist/iterableWeak.d.ts +0 -53
  95. package/dist/iterableWeak.d.ts.map +0 -1
  96. package/dist/mixins.d.ts +0 -25
  97. package/dist/mixins.d.ts.map +0 -1
  98. package/dist/promiseChain.d.ts +0 -20
  99. package/dist/promiseChain.d.ts.map +0 -1
  100. package/dist/reactive/array.d.ts +0 -49
  101. package/dist/reactive/array.d.ts.map +0 -1
  102. package/dist/reactive/buffer.d.ts +0 -44
  103. package/dist/reactive/buffer.d.ts.map +0 -1
  104. package/dist/reactive/change.d.ts +0 -29
  105. package/dist/reactive/change.d.ts.map +0 -1
  106. package/dist/reactive/debug.d.ts +0 -111
  107. package/dist/reactive/debug.d.ts.map +0 -1
  108. package/dist/reactive/deep-touch.d.ts +0 -28
  109. package/dist/reactive/deep-touch.d.ts.map +0 -1
  110. package/dist/reactive/deep-watch-state.d.ts +0 -25
  111. package/dist/reactive/deep-watch-state.d.ts.map +0 -1
  112. package/dist/reactive/deep-watch.d.ts +0 -19
  113. package/dist/reactive/deep-watch.d.ts.map +0 -1
  114. package/dist/reactive/effect-context.d.ts +0 -7
  115. package/dist/reactive/effect-context.d.ts.map +0 -1
  116. package/dist/reactive/effects.d.ts +0 -151
  117. package/dist/reactive/effects.d.ts.map +0 -1
  118. package/dist/reactive/index.d.ts +0 -20
  119. package/dist/reactive/index.d.ts.map +0 -1
  120. package/dist/reactive/interface.d.ts +0 -64
  121. package/dist/reactive/interface.d.ts.map +0 -1
  122. package/dist/reactive/map.d.ts +0 -30
  123. package/dist/reactive/map.d.ts.map +0 -1
  124. package/dist/reactive/memoize.d.ts +0 -5
  125. package/dist/reactive/memoize.d.ts.map +0 -1
  126. package/dist/reactive/non-reactive-state.d.ts +0 -9
  127. package/dist/reactive/non-reactive-state.d.ts.map +0 -1
  128. package/dist/reactive/non-reactive.d.ts +0 -11
  129. package/dist/reactive/non-reactive.d.ts.map +0 -1
  130. package/dist/reactive/project.d.ts +0 -41
  131. package/dist/reactive/project.d.ts.map +0 -1
  132. package/dist/reactive/proxy-state.d.ts +0 -8
  133. package/dist/reactive/proxy-state.d.ts.map +0 -1
  134. package/dist/reactive/proxy.d.ts +0 -23
  135. package/dist/reactive/proxy.d.ts.map +0 -1
  136. package/dist/reactive/record.d.ts +0 -116
  137. package/dist/reactive/record.d.ts.map +0 -1
  138. package/dist/reactive/register.d.ts +0 -64
  139. package/dist/reactive/register.d.ts.map +0 -1
  140. package/dist/reactive/registry.d.ts +0 -20
  141. package/dist/reactive/registry.d.ts.map +0 -1
  142. package/dist/reactive/set.d.ts +0 -28
  143. package/dist/reactive/set.d.ts.map +0 -1
  144. package/dist/reactive/tracking.d.ts +0 -7
  145. package/dist/reactive/tracking.d.ts.map +0 -1
  146. package/dist/reactive/types.d.ts.map +0 -1
  147. package/dist/std-decorators.d.ts +0 -50
  148. package/dist/std-decorators.d.ts.map +0 -1
  149. package/dist/utils.d.ts +0 -49
  150. package/dist/utils.d.ts.map +0 -1
  151. package/dist/zone.d.ts +0 -40
  152. package/dist/zone.d.ts.map +0 -1
  153. package/docs/reactive/project.md +0 -93
  154. package/src/async/browser.ts +0 -319
  155. package/src/async/index.ts +0 -23
  156. package/src/async/node.ts +0 -104
  157. package/src/decorator.ts +0 -272
  158. package/src/destroyable.ts +0 -199
  159. package/src/entry-browser.ts +0 -5
  160. package/src/entry-node.ts +0 -5
  161. package/src/eventful.ts +0 -77
  162. package/src/index.d.ts +0 -12
  163. package/src/index.ts +0 -64
  164. package/src/indexable.ts +0 -526
  165. package/src/introspection.ts +0 -59
  166. package/src/iterableWeak.ts +0 -233
  167. package/src/mixins.ts +0 -123
  168. package/src/promiseChain.ts +0 -110
  169. package/src/reactive/array.ts +0 -507
  170. package/src/reactive/buffer.ts +0 -168
  171. package/src/reactive/change.ts +0 -138
  172. package/src/reactive/debug.ts +0 -517
  173. package/src/reactive/deep-touch.ts +0 -268
  174. package/src/reactive/deep-watch-state.ts +0 -82
  175. package/src/reactive/deep-watch.ts +0 -168
  176. package/src/reactive/effect-context.ts +0 -18
  177. package/src/reactive/effects.ts +0 -1382
  178. package/src/reactive/index.ts +0 -75
  179. package/src/reactive/interface.ts +0 -210
  180. package/src/reactive/map.ts +0 -158
  181. package/src/reactive/memoize.ts +0 -163
  182. package/src/reactive/non-reactive-state.ts +0 -49
  183. package/src/reactive/non-reactive.ts +0 -43
  184. package/src/reactive/project.ts +0 -453
  185. package/src/reactive/proxy-state.ts +0 -27
  186. package/src/reactive/proxy.ts +0 -264
  187. package/src/reactive/record.ts +0 -196
  188. package/src/reactive/register.ts +0 -419
  189. package/src/reactive/registry.ts +0 -59
  190. package/src/reactive/set.ts +0 -130
  191. package/src/reactive/tracking.ts +0 -44
  192. package/src/reactive/types.ts +0 -440
  193. package/src/std-decorators.ts +0 -217
  194. package/src/utils.ts +0 -301
  195. package/src/zone.ts +0 -138
@@ -0,0 +1,142 @@
1
+ # Reactive Enumeration (`attend`)
2
+
3
+ The `attend` utility reactively iterates over the entries of a collection, running a per-key effect that is automatically created when a key appears and disposed when it disappears.
4
+
5
+ ## Overview
6
+
7
+ `attend` is the reactive equivalent of `forEach`. It:
8
+ - Tracks the **keys** (or indices, or values) of a collection inside an outer effect.
9
+ - **Creates** an inner effect for each key, via `ascend`.
10
+ - **Disposes** the inner effect when the key is removed from the collection.
11
+ - Allows the callback to return a **cleanup function** (like a regular effect closer).
12
+
13
+ This is the foundational lifecycle primitive that `organized` is built on.
14
+
15
+ ## API
16
+
17
+ ```typescript
18
+ // Raw enumeration callback
19
+ function attend<Key>(
20
+ enumerate: () => Iterable<Key>,
21
+ callback: (key: Key) => EffectCloser | void
22
+ ): ScopedCallback
23
+
24
+ // Collection shorthands
25
+ function attend<T>(source: readonly T[], callback: (index: number) => EffectCloser | void): ScopedCallback
26
+ function attend<K, V>(source: Map<K, V>, callback: (key: K) => EffectCloser | void): ScopedCallback
27
+ function attend<T>(source: Set<T>, callback: (value: T) => EffectCloser | void): ScopedCallback
28
+ function attend<S extends Record<PropertyKey, any>>(source: S, callback: (key: keyof S & string) => EffectCloser | void): ScopedCallback
29
+ ```
30
+
31
+ ### Parameters
32
+
33
+ - **`source`** or **`enumerate`**: Either a collection (array, record, Map, Set) or a callback returning an `Iterable<Key>`. The enumeration runs inside the outer effect, so reactive reads (e.g. `source.length`, `Object.keys(source)`) are tracked automatically.
34
+ - **`callback`**: Called per key inside an inner effect. May return a cleanup function that runs when the key is removed or before the inner effect re-executes.
35
+
36
+ ### Returns
37
+
38
+ A `ScopedCallback` that tears down all inner effects and the outer effect.
39
+
40
+ ## Basic Usage
41
+
42
+ ### Record
43
+
44
+ ```typescript
45
+ import { reactive, attend } from 'mutts'
46
+
47
+ const config = reactive({ theme: 'dark', lang: 'en' })
48
+
49
+ const stop = attend(config, (key) => {
50
+ console.log(`${key} = ${config[key]}`)
51
+ return () => console.log(`cleanup: ${key}`)
52
+ })
53
+
54
+ // Logs: "theme = dark", "lang = en"
55
+
56
+ config.debug = true
57
+ // Logs: "debug = true"
58
+
59
+ delete config.lang
60
+ // Logs: "cleanup: lang"
61
+
62
+ config.theme = 'light'
63
+ // Logs: "cleanup: theme" then "theme = light"
64
+ // (inner effect re-runs: previous cleanup fires, then new execution)
65
+
66
+ stop()
67
+ // Disposes everything
68
+ ```
69
+
70
+ ### Array
71
+
72
+ ```typescript
73
+ const items = reactive([10, 20, 30])
74
+
75
+ attend(items, (i) => {
76
+ console.log(`[${i}] = ${items[i]}`)
77
+ })
78
+
79
+ items.push(40)
80
+ // Logs: "[3] = 40"
81
+ ```
82
+
83
+ ### Map
84
+
85
+ ```typescript
86
+ const registry = reactive(new Map([['a', 1]]))
87
+
88
+ attend(registry, (key) => {
89
+ console.log(`${key} -> ${registry.get(key)}`)
90
+ return () => console.log(`removed: ${key}`)
91
+ })
92
+
93
+ registry.set('b', 2) // Logs: "b -> 2"
94
+ registry.delete('a') // Logs: "removed: a"
95
+ ```
96
+
97
+ ### Set
98
+
99
+ ```typescript
100
+ const tags = reactive(new Set(['alpha', 'beta']))
101
+
102
+ attend(tags, (value) => {
103
+ console.log(`tag: ${value}`)
104
+ return () => console.log(`untagged: ${value}`)
105
+ })
106
+
107
+ tags.add('gamma') // Logs: "tag: gamma"
108
+ tags.delete('alpha') // Logs: "untagged: alpha"
109
+ ```
110
+
111
+ ### Raw enumeration callback
112
+
113
+ For custom iteration logic or non-standard collections:
114
+
115
+ ```typescript
116
+ const source = reactive({ a: 1, b: 2 })
117
+
118
+ attend(
119
+ () => Reflect.ownKeys(source),
120
+ (key) => {
121
+ console.log(key, source[key])
122
+ }
123
+ )
124
+ ```
125
+
126
+ ## How it Works
127
+
128
+ 1. An **outer effect** calls `enumerate()` (or derives it from the collection type), collecting the current keys into a `Set`.
129
+ 2. For each **new key**, `ascend` creates an inner effect that runs the callback.
130
+ 3. For each **removed key**, the corresponding inner effect is stopped (which triggers its cleanup).
131
+ 4. The **inner effect** tracks its own reactive dependencies — so if a value changes for an existing key, only that key's effect re-runs.
132
+
133
+ ## Relationship to Other Primitives
134
+
135
+ | Primitive | Uses `attend`? | Purpose |
136
+ |---|---|---|
137
+ | `attend` | — | Reactive lifecycle per key |
138
+ | `organized` | ✅ | Reactive record mapping with access objects |
139
+ | `project` | ❌ | Reactive collection mapping (manages a target + projection context) |
140
+ | `scan` | ❌ | Reactive accumulation (sequential key dependency) |
141
+
142
+ `project` and `scan` have additional concerns (target management, sequential dependencies) that go beyond `attend`'s independent-key lifecycle model.
@@ -347,26 +347,81 @@ The `Register` exposes additional methods and behaviors that standard arrays do
347
347
  - `toArray(): T[]` materializes the current values into a plain array.
348
348
  - `toString(): string` returns a concise description like `[Register length=3]`.
349
349
 
350
+ ### Register CRUD Events
351
+
352
+ `Register` emits lifecycle events for add, delete, update, and rekey operations. This enables side effects like logging, syncing with external systems, or triggering derived updates.
353
+
354
+ ```typescript
355
+ const list = register(({id}: { id: number }) => id)
356
+
357
+ // Listen to individual events
358
+ list.on('add', (item, key, index) => {
359
+ console.log(`Added ${key} at index ${index}:`, item)
360
+ })
361
+
362
+ list.on('delete', (item, key, index) => {
363
+ console.log(`Removed ${key} from index ${index}:`, item)
364
+ })
365
+
366
+ list.on('update', (oldItem, newItem, key, index) => {
367
+ console.log(`Updated ${key} at index ${index}`)
368
+ })
369
+
370
+ list.on('rekey', (item, oldKey, newKey, index) => {
371
+ console.log(`Key changed from ${oldKey} to ${newKey}`)
372
+ })
373
+
374
+ // Bulk event registration
375
+ list.on({
376
+ add: (item) => console.log('Added:', item),
377
+ delete: (item) => console.log('Deleted:', item),
378
+ })
379
+
380
+ // Global hook - receive all events
381
+ const unhook = list.hook((event, ...args) => {
382
+ console.log(`Event: ${String(event)}`, args)
383
+ })
384
+
385
+ // Unsubscribe
386
+ const unsubscribe = list.on('add', handler)
387
+ unsubscribe()
388
+ ```
389
+
390
+ **Event Types:**
391
+
392
+ | Event | Arguments | Description |
393
+ |-------|-----------|-------------|
394
+ | `add` | `(item, key, index)` | New item added to register |
395
+ | `delete` | `(item, key, index)` | Item removed from register |
396
+ | `update` | `(oldItem, newItem, key, index)` | Item value updated (same key) |
397
+ | `rekey` | `(item, oldKey, newKey, index)` | Item's key changed |
398
+
399
+ **Use Cases:**
400
+ - Audit logging
401
+ - Syncing with databases
402
+ - Triggering notifications
403
+ - Cascading updates to dependent systems
404
+
350
405
  Notes:
351
406
  - Direct length modification via `list.length = n` is not supported; use `splice` instead.
352
407
  - Assigning to an index (`list[i] = value`) uses the key function to bind that slot to `value`’s key.
353
408
 
354
409
  ## Class Reactivity
355
410
 
356
- ## Projection
411
+ ## Morphing
357
412
 
358
- ### `project()`
413
+ ### `morph()`
359
414
 
360
- `project()` provides a unified API for transforming reactive collections (arrays, records, and maps) into new reactive collections. Each source entry gets its own reactive effect that recomputes only when that specific entry changes, enabling granular updates perfect for rendering pipelines.
415
+ `morph()` provides a unified API for transforming reactive collections (arrays, records, and maps) into new reactive collections. Each source entry gets its own reactive effect that recomputes only when that specific entry changes, enabling granular updates perfect for rendering pipelines.
361
416
 
362
417
  #### Basic Usage
363
418
 
364
419
  ```typescript
365
- import { cleanup, project, reactive } from 'mutts/reactive'
420
+ import { cleanup, morph, reactive } from 'mutts/reactive'
366
421
 
367
422
  // Arrays
368
423
  const users = reactive([{ name: 'John', age: 30 }, { name: 'Jane', age: 25 }])
369
- const names = project.array(users, ({ get }) => get()?.name.toUpperCase() ?? '')
424
+ const names = morph(users, (user) => user.name.toUpperCase())
370
425
 
371
426
  console.log(names) // ['JOHN', 'JANE']
372
427
 
@@ -375,8 +430,7 @@ console.log(names[0]) // 'JOHNNY' - only index 0 recomputed
375
430
 
376
431
  // Records
377
432
  const scores = reactive({ math: 90, science: 85 })
378
- const grades = project.record(scores, ({ get }) => {
379
- const score = get()
433
+ const grades = morph(scores, (score) => {
380
434
  return score >= 90 ? 'A' : score >= 80 ? 'B' : 'C'
381
435
  })
382
436
 
@@ -389,7 +443,7 @@ const inventory = reactive(new Map([
389
443
  ['apples', { count: 10 }],
390
444
  ['oranges', { count: 5 }]
391
445
  ]))
392
- const totals = project.map(inventory, ({ get }) => get()?.count ?? 0)
446
+ const totals = morph(inventory, (item) => item.count)
393
447
 
394
448
  console.log(totals.get('apples')) // 10
395
449
  inventory.get('apples')!.count = 15
@@ -398,41 +452,25 @@ console.log(totals.get('apples')) // 15 - only 'apples' key recomputed
398
452
 
399
453
  #### Automatic Type Selection
400
454
 
401
- You can use `project()` directly and it will automatically select the appropriate helper based on the source type:
455
+ The `morph()` function automatically selects the appropriate helper based on the source type:
402
456
 
403
457
  ```typescript
404
- // Automatically uses project.array
405
- const doubled = project([1, 2, 3], ({ get }) => get() * 2)
458
+ // Arrays
459
+ const doubled = morph([1, 2, 3], (x) => x * 2)
406
460
 
407
- // Automatically uses project.record
408
- const upper = project({ a: 'hello', b: 'world' }, ({ get }) => get()?.toUpperCase() ?? '')
461
+ // Records
462
+ const upper = morph({ a: 'hello', b: 'world' }, (s) => s.toUpperCase())
409
463
 
410
- // Automatically uses project.map
411
- const counts = project(new Map([['x', 1], ['y', 2]]), ({ get }) => get() * 2)
464
+ // Maps
465
+ const counts = morph(new Map([['x', 1], ['y', 2]]), (v) => v * 2)
412
466
  ```
413
467
 
414
- #### Access Object
415
-
416
- The callback receives a `ProjectAccess` object with:
468
+ #### Callback Signature
417
469
 
418
- - **`get()`**: Function that returns the current source value for this key/index
419
- - **`set(value)`**: Function to update the source value (if the source is mutable)
420
- - **`key`**: The current key or index
421
- - **`source`**: Reference to the original source collection
422
- - **`old`**: Previously computed result for this entry (undefined on first run)
423
- - **`value`**: Computed property that mirrors `get()` (for convenience)
470
+ The callback function receives the current value of the entry being processed:
424
471
 
425
472
  ```typescript
426
- const transformed = project.array(items, (access) => {
427
- // Access the source value
428
- const item = access.get()
429
-
430
- // Access the key/index
431
- console.log(`Processing index ${access.key}`)
432
-
433
- // Leverage previous result
434
- console.log(`Previous result: ${access.old}`)
435
-
473
+ const transformed = morph(items, (item) => {
436
474
  // Transform and return
437
475
  return item.value * 2
438
476
  })
@@ -450,9 +488,8 @@ const users = reactive([
450
488
  ])
451
489
 
452
490
  let computeCount = 0
453
- const summaries = project.array(users, ({ get }) => {
491
+ const summaries = morph(users, (user) => {
454
492
  computeCount++
455
- const user = get()
456
493
  return `${user.name}: ${user.score}`
457
494
  })
458
495
 
@@ -470,11 +507,11 @@ console.log(computeCount) // 5 (only new index 3 computed)
470
507
 
471
508
  #### Key Addition and Removal
472
509
 
473
- `project()` automatically handles keys being added or removed from the source:
510
+ `morph()` automatically handles keys being added or removed from the source:
474
511
 
475
512
  ```typescript
476
513
  const source = reactive({ a: 1, b: 2 })
477
- const doubled = project.record(source, ({ get }) => get() * 2)
514
+ const doubled = morph(source, (val) => val * 2)
478
515
 
479
516
  console.log(doubled.a) // 2
480
517
  console.log(doubled.b) // 4
@@ -493,7 +530,7 @@ console.log('a' in doubled) // false (automatically removed)
493
530
  The returned object includes a `cleanup` symbol that stops all reactive effects:
494
531
 
495
532
  ```typescript
496
- const result = project.array(items, ({ get }) => get() * 2)
533
+ const result = morph(items, (item) => item * 2)
497
534
 
498
535
  // Later, when done
499
536
  result[cleanup]() // Stops all effects and cleans up
@@ -7,19 +7,17 @@
7
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
11
  - [Atomic Operations](./advanced.md#atomic-operations)
11
12
  - [Advanced Effects](./advanced.md#advanced-effects)
12
13
  - [Evolution Tracking](./advanced.md#evolution-tracking)
13
14
  - [Prototype Chains and Pure Objects](./advanced.md#prototype-chains-and-pure-objects)
14
15
  - [Recursive Touching](./advanced.md#recursive-touching)
15
- - [Why Not Deep Watching?](./advanced.md#why-not-deep-watching)
16
- - [Collections](./advanced.md#collections)
17
- - [Register](./advanced.md#register)
18
- - [Class Reactivity](./advanced.md#class-reactivity)
19
- - [Non-Reactive System](./advanced.md#non-reactive-system)
20
- - [Array Mapping](./advanced.md#array-mapping)
21
- - [Projection](./advanced.md#projection)
22
- - [Record Organization](./advanced.md#record-organization)
16
+ - [Collections](./collections.md)
17
+ - [Register](./collections.md#register)
18
+ - [Class Reactivity](#class-reactivity)
19
+ - [Non-Reactive System](#non-reactive-system)
20
+ - [Morphing](./advanced.md#morph)
23
21
  - [Memoization](./advanced.md#memoization)
24
22
  - [Debugging and Development](./advanced.md#debugging-and-development)
25
23
  - [Cycle Detection](./advanced.md#cycle-detection)
@@ -105,7 +103,7 @@ const result = memoized(user)
105
103
  **5. Map over arrays:**
106
104
  ```typescript
107
105
  const source = reactive([1, 2, 3])
108
- const doubled = project(source, ({ value }) => value * 2)
106
+ const doubled = morph(source, ({ value }) => value * 2)
109
107
  // [2, 4, 6]
110
108
 
111
109
  source.push(4) // doubled automatically becomes [2, 4, 6, 8]
@@ -202,7 +200,7 @@ reactiveObj.count = 5 // Triggers effect
202
200
  - Objects with prototypes: `reactive(Object.create(parent))`
203
201
  - Class instances: `reactive(new MyClass())`
204
202
 
205
- See [Prototype Chains and Pure Objects](#prototype-chains-and-pure-objects) for detailed information about prototype chain handling.
203
+ See [Prototype Chains and Pure Objects](./advanced.md#prototype-chains-and-pure-objects) for detailed information about prototype chain handling.
206
204
 
207
205
  ### `effect()`
208
206
 
@@ -448,7 +446,7 @@ state.c = 15 // Does NOT trigger effect
448
446
 
449
447
  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
448
 
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.
449
+ 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.
452
450
 
453
451
  #### The Problem with Async Effects
454
452
 
@@ -466,7 +464,7 @@ effect(() => {
466
464
  const another = state.name // ✅ Tracked (active effect is still set)
467
465
  })
468
466
 
469
- // Async effect WITHOUT configureAsyncZone() - context is lost after await
467
+ // Async effect WITHOUT zone registration - context is lost after await
470
468
  effect(async () => {
471
469
  const value = state.count // ✅ Tracked
472
470
 
@@ -476,7 +474,7 @@ effect(async () => {
476
474
  const another = state.name // ❌ NOT tracked
477
475
  })
478
476
 
479
- // Async effect WITH configureAsyncZone() - context is preserved
477
+ // Async effect WITH zone registration - context is preserved
480
478
  effect(async () => {
481
479
  const value = state.count // ✅ Tracked
482
480
 
@@ -497,7 +495,7 @@ effect(async ({ tracked }) => {
497
495
 
498
496
  #### Key Benefits of the Zone System
499
497
 
500
- 1. **Automatic Restoration**: With `configureAsyncZone()`, most native async APIs (Promises, timers) automatically preserve the reactive context.
498
+ 1. **Automatic Restoration**: With zones registered in `asyncZone`, most native async APIs (Promises, timers) automatically preserve the reactive context.
501
499
  2. **Manual Control**: `access.tracked()` allows you to manually "passport" the context into third-party libraries or unmanaged callbacks.
502
500
 
503
501
  ### Using `ascend` for Parent Effect Tracking
@@ -603,6 +601,94 @@ reactiveOptions.leave = (effect) => console.log('Leaving effect:', effect)
603
601
  reactiveOptions.chain = (caller, target) => console.log('Chaining:', caller, '->', target)
604
602
  ```
605
603
 
604
+ ### Effect Modifiers
605
+
606
+ The `effect` function provides convenient shortcut modifiers for common options. These can be chained for more concise syntax:
607
+
608
+ #### `.opaque`
609
+
610
+ 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.
611
+
612
+ ```typescript
613
+ import { effect, reactive } from 'mutts/reactive'
614
+
615
+ const item = reactive({ id: 1, data: { value: 10 } })
616
+
617
+ // Regular effect - triggers on any property change
618
+ effect(() => {
619
+ console.log('Item data:', item.data.value) // Triggers on item.data.value changes
620
+ })
621
+
622
+ // Opaque effect - only triggers when item.data reference changes
623
+ effect.opaque(() => {
624
+ console.log('Data object:', item.data) // Only triggers when item.data is replaced
625
+ })
626
+
627
+ item.data.value = 20 // Triggers regular effect, NOT opaque effect
628
+ item.data = { value: 30 } // Triggers BOTH effects
629
+ ```
630
+
631
+ **Use cases for opaque effects:**
632
+ - When you only care about object identity (e.g., cache keys, memoization)
633
+ - When deep watching would be too expensive
634
+ - When working with external data that shouldn't trigger deep reactivity
635
+
636
+ #### `.named(name)`
637
+
638
+ Creates a named effect for easier debugging and profiling. The name appears in DevTools and debug logs.
639
+
640
+ ```typescript
641
+ import { effect, reactive } from 'mutts/reactive'
642
+
643
+ const state = reactive({ count: 0 })
644
+
645
+ // Create a named effect
646
+ effect.named('counter-effect')(() => {
647
+ console.log('Count:', state.count)
648
+ })
649
+
650
+ // Named effects can also be combined with other options
651
+ effect.named('data-loader').opaque(() => {
652
+ console.log('Loading data...')
653
+ })
654
+ ```
655
+
656
+ **Benefits of named effects:**
657
+ - Easier identification in DevTools
658
+ - Better stack traces during debugging
659
+ - Helpful for performance profiling
660
+
661
+ #### Combining Modifiers
662
+
663
+ Modifiers can be chained in any order:
664
+
665
+ ```typescript
666
+ // Named opaque effect
667
+ effect.named('my-effect').opaque(() => {
668
+ // Effect code
669
+ })
670
+
671
+ // These are equivalent - order doesn't matter for the result
672
+ effect.opaque.named('my-effect')(() => {
673
+ // Effect code
674
+ })
675
+ ```
676
+
677
+ Note: The modifiers return new effect functions with the options pre-applied, so they can be stored and reused:
678
+
679
+ ```typescript
680
+ // Create a reusable named effect factory
681
+ const createDataEffect = effect.named('data-layer')
682
+
683
+ createDataEffect(() => {
684
+ console.log('Effect 1')
685
+ })
686
+
687
+ createDataEffect(() => {
688
+ console.log('Effect 2')
689
+ })
690
+ ```
691
+
606
692
 
607
693
  ### `@reactive` Decorator
608
694