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
@@ -42,7 +42,7 @@ The implementation consists of:
42
42
  2. **Intermediates**: Class instances that link `val` and `prev`. They expose an `acc` getter decorated with `@memoize`.
43
43
  3. **Index Sync Effects**: Granular effects (one per result index) that subscribe to `indexToIntermediate[i].acc`.
44
44
 
45
- This "Project-like" architecture ensures that the main loop only does structural work, while the actual logic propagation is handled by the dependency chain of the intermediates.
45
+ This "Morph-like" architecture ensures that the main loop only does structural work, while the actual logic propagation is handled by the dependency chain of the intermediates.
46
46
 
47
47
  ## API Reference
48
48
 
@@ -76,3 +76,249 @@ result[cleanup]()
76
76
  | **Modify Item at `i`** | O(N) calls (entire reduction) | O(N-i) calls |
77
77
  | **Append Item** | O(N+1) calls | 1 call |
78
78
  | **Move Item** | O(N) calls | O(affected chain) |
79
+
80
+ ---
81
+
82
+ # Lift
83
+
84
+ The `lift` function transforms a callback that returns an array or object into a reactive array/object that automatically synchronizes with the source whenever dependencies change.
85
+
86
+ ## Overview
87
+
88
+ `lift` is useful when you have a reactive computation that produces an array or object, and you want that result to be reactive itself. It efficiently syncs only the elements that differ from the previous result, minimizing DOM updates and downstream effects.
89
+
90
+ ## Basic Usage
91
+
92
+ ### Array Example
93
+
94
+ ```typescript
95
+ import { reactive, lift } from 'mutts/reactive'
96
+
97
+ const items = reactive([1, 2, 3])
98
+ const doubled = lift(() => items.map(x => x * 2))
99
+
100
+ console.log([...doubled]) // [2, 4, 6]
101
+
102
+ items.push(4)
103
+ console.log([...doubled]) // [2, 4, 6, 8]
104
+ ```
105
+
106
+ ### Object Example
107
+
108
+ ```typescript
109
+ import { reactive, lift } from 'mutts/reactive'
110
+
111
+ const user = reactive({ name: 'John', age: 30 })
112
+ const profile = lift(() => ({
113
+ displayName: user.name.toUpperCase(),
114
+ isAdult: user.age >= 18,
115
+ description: `${user.name} is ${user.age} years old`
116
+ }))
117
+
118
+ console.log(profile.displayName) // JOHN
119
+ console.log(profile.isAdult) // true
120
+
121
+ user.name = 'Jane'
122
+ console.log(profile.displayName) // JANE
123
+ console.log(profile.description) // Jane is 30 years old
124
+ ```
125
+
126
+ ## How it Works
127
+
128
+ `lift` creates a reactive array or object and sets up an effect that:
129
+ 1. Calls the provided callback to get the source array or object
130
+ 2. Compares the source with the current reactive result
131
+ 3. Updates only the elements/properties that have changed
132
+ 4. Adjusts the structure if needed (array length or object properties)
133
+
134
+ For arrays, this approach preserves references to unchanged elements and triggers minimal reactive updates. For objects, it uses `Object.assign()` to merge changes and removes properties that no longer exist in the source.
135
+
136
+ ## API Reference
137
+
138
+ ```typescript
139
+ function lift<Output extends (any[] | object)>(
140
+ cb: (access: EffectAccess) => Output
141
+ ): Output & { [cleanup]: ScopedCallback }
142
+ ```
143
+
144
+ ### Parameters
145
+ - `cb`: A callback function that returns an array or object. The callback is tracked reactively, so accessing reactive values inside it will cause the result to update when those values change. The callback receives an `EffectAccess` parameter for advanced use cases.
146
+
147
+ ### Returns
148
+ A reactive array or object that stays synchronized with the callback's result. The result includes a `[cleanup]` symbol that can be called to stop tracking.
149
+
150
+ ```typescript
151
+ import { cleanup } from 'mutts/reactive'
152
+ // ...
153
+ doubled[cleanup]()
154
+ profile[cleanup]()
155
+ ```
156
+
157
+ ## Use Cases
158
+
159
+ ### Dynamic Filtering (Arrays)
160
+
161
+ ```typescript
162
+ const allItems = reactive([
163
+ { id: 1, active: true, name: 'Item 1' },
164
+ { id: 2, active: false, name: 'Item 2' },
165
+ { id: 3, active: true, name: 'Item 3' },
166
+ ])
167
+
168
+ const activeItems = lift(() => allItems.filter(item => item.active))
169
+
170
+ // activeItems automatically updates when items change or active status changes
171
+ allItems[1].active = true
172
+ console.log(activeItems.length) // 3
173
+ ```
174
+
175
+ ### Computed Transformations (Arrays)
176
+
177
+ ```typescript
178
+ const numbers = reactive([1, 2, 3, 4, 5])
179
+ const multiplier = reactive({ value: 2 })
180
+
181
+ const scaled = lift(() => numbers.map(n => n * multiplier.value))
182
+
183
+ multiplier.value = 3
184
+ // scaled is now [3, 6, 9, 12, 15]
185
+ ```
186
+
187
+ ### Conditional Array Construction
188
+
189
+ ```typescript
190
+ const showExtras = reactive({ value: false })
191
+ const baseItems = reactive(['A', 'B', 'C'])
192
+
193
+ const displayItems = lift(() =>
194
+ showExtras.value
195
+ ? [...baseItems, 'Extra 1', 'Extra 2']
196
+ : baseItems
197
+ )
198
+
199
+ showExtras.value = true
200
+ // displayItems is now ['A', 'B', 'C', 'Extra 1', 'Extra 2']
201
+ ```
202
+
203
+ ### Computed Object Properties
204
+
205
+ ```typescript
206
+ const user = reactive({ firstName: 'John', lastName: 'Doe', age: 30 })
207
+ const settings = reactive({ theme: 'dark', language: 'en' })
208
+
209
+ const userProfile = lift(() => ({
210
+ fullName: `${user.firstName} ${user.lastName}`,
211
+ isMinor: user.age < 18,
212
+ displayTheme: settings.theme === 'dark' ? 'Dark Mode' : 'Light Mode',
213
+ locale: settings.language.toUpperCase()
214
+ }))
215
+
216
+ user.firstName = 'Jane'
217
+ // userProfile.fullName is now 'Jane Doe'
218
+
219
+ settings.theme = 'light'
220
+ // userProfile.displayTheme is now 'Light Mode'
221
+ ```
222
+
223
+ ### Dynamic Object Composition
224
+
225
+ ```typescript
226
+ const baseConfig = reactive({ api: 'https://api.example.com', timeout: 5000 })
227
+ const userPrefs = reactive({ retries: 3, logging: false })
228
+ const envVars = reactive({ debug: true, version: '1.0.0' })
229
+
230
+ const fullConfig = lift(() => ({
231
+ ...baseConfig,
232
+ ...userPrefs,
233
+ environment: envVars.debug ? 'development' : 'production',
234
+ version: envVars.version,
235
+ logging: envVars.debug || userPrefs.logging
236
+ }))
237
+
238
+ envVars.debug = false
239
+ // fullConfig.environment becomes 'production'
240
+
241
+ userPrefs.logging = true
242
+ // fullConfig.logging becomes true
243
+ ```
244
+
245
+ ### Conditional Object Properties
246
+
247
+ ```typescript
248
+ const user = reactive({ role: 'admin', permissions: ['read', 'write'] })
249
+ const showAdvanced = reactive({ value: true })
250
+
251
+ const userInterface = lift(() => {
252
+ const base = {
253
+ canEdit: user.permissions.includes('write'),
254
+ userName: user.role
255
+ }
256
+
257
+ return showAdvanced.value ? {
258
+ ...base,
259
+ isAdmin: user.role === 'admin',
260
+ permissionCount: user.permissions.length
261
+ } : base
262
+ })
263
+
264
+ showAdvanced.value = false
265
+ // userInterface no longer has isAdmin and permissionCount properties
266
+ ```
267
+
268
+ ## Comparison with `scan`
269
+
270
+ | Feature | `lift` | `scan` |
271
+ | :--- | :--- | :--- |
272
+ | **Purpose** | Synchronize with computed arrays/objects | Accumulate values with intermediates |
273
+ | **Input** | Callback returning array/object | Source array + accumulator function |
274
+ | **Output** | Reactive array/object | Reactive array of accumulated values |
275
+ | **Optimization** | Element-wise/property-wise sync | Intermediate caching + move optimization |
276
+ | **Use Case** | Derived arrays/objects (map, filter, computed properties) | Cumulative operations (sum, reduce) |
277
+ | **Data Types** | Arrays and objects | Arrays only (object items required) |
278
+
279
+ ## Comparison with Recursive Touching (Deep Touch)
280
+
281
+ When you assign a new array/object to a reactive property (`state.items = newArray`), the reactive system performs a **recursive touch** — it diffs old vs new element-by-element and fires per-index notifications on the *same proxy*. This raises the question: is `lift` redundant?
282
+
283
+ | | Recursive Touching | `lift` |
284
+ | :--- | :--- | :--- |
285
+ | **Trigger** | Direct assignment to a reactive property | Any reactive dependency change inside the callback |
286
+ | **Scope** | Same-shape replacement of one value | Arbitrary computation → stable reactive output |
287
+ | **Identity** | Same proxy, same object | Returns a **new persistent proxy** that outlives re-evaluations |
288
+ | **Use case** | `state.user = fetchedUser` — fine-grained diff on assignment | `lift(() => items.filter(x => x.active))` — derived collection |
289
+
290
+ Deep touching makes `lift` unnecessary for **replacement** patterns (`state.items = newItems`). `lift` remains essential for **derived collections** where the result is a transformation (filter, map, reshape) rather than a direct assignment — there is no single property to assign to, and the whole output is recomputed from scratch each time.
291
+
292
+ ## Comparison with `memoize`
293
+
294
+ Both `lift` and `memoize` compute derived values from reactive dependencies, but they differ in evaluation strategy and output type.
295
+
296
+ | | `memoize` | `lift` |
297
+ | :--- | :--- | :--- |
298
+ | **Evaluation** | Lazy — invalidates on dep change, recomputes on next read | Eager — recomputes immediately on dep change |
299
+ | **Return type** | The raw return value of the function | A **stable reactive proxy** (array or object) |
300
+ | **Downstream reactivity** | Consumers get a new value each time (identity changes) | Consumers see per-property/per-index diffs on the *same* proxy |
301
+ | **Arguments** | Keyed by object args (WeakMap cache tree) | No args — closure over reactive deps |
302
+ | **Decorator** | Yes (`@memoize` on getters/methods) | No |
303
+ | **Cleanup** | Automatic (WeakMap GC) | Explicit `result[cleanup]()` |
304
+
305
+ **When to use which:**
306
+ - **`lift`** for derived collections where downstream consumers (e.g., `morph()`, effects) benefit from per-element diffing on a stable proxy.
307
+ - **`memoize`** for parameterized caching (`memoize((user) => expensiveCompute(user))`) or lazy evaluation where recomputation should only happen on access.
308
+ - For a scalar result read in one place, they are nearly interchangeable — prefer `memoize` for its laziness and automatic cleanup.
309
+
310
+ ## Performance Considerations
311
+
312
+ ### Arrays
313
+ - **Efficient Updates**: Only changed elements are updated, not the entire array
314
+ - **Length Adjustments**: Array length changes are handled separately from element updates
315
+ - **Reference Stability**: Unchanged elements maintain their references
316
+
317
+ ### Objects
318
+ - **Property-wise Updates**: Only changed properties are updated using `Object.assign()`
319
+ - **Property Addition/Removal**: Properties are added or removed as needed when the source object structure changes
320
+ - **Reference Stability**: The reactive object maintains its identity while properties are updated
321
+
322
+ ### General
323
+ - **Cleanup**: Remember to call the cleanup function when the lifted array/object is no longer needed to prevent memory leaks
324
+ - **Type Consistency**: The callback must return the same type (array or object) on subsequent calls
package/docs/reactive.md CHANGED
@@ -11,10 +11,13 @@ The Mutts Reactive System documentation has been split into focused sections for
11
11
  * **[Reactive Collections](./reactive/collections.md#collections)**: Map, Set, WeakMap, WeakSet
12
12
  * **[Reactive Arrays](./reactive/collections.md#reactivearray)**: Full array method support
13
13
  * **[Register](./reactive/collections.md#register)**: ID-keyed ordered collections
14
- * **[Projections](./reactive/collections.md#projection)**: `project`, `organized`
14
+ * **[Morphing](./reactive/collections.md#morph)**: `morph`, `organized`
15
+ * **[Attend](./reactive/attend.md)**: Reactive enumeration (`attend`)
15
16
  * **[Scan](./reactive/scan.md)**: Reactive scan and accumulation
17
+ * **[Resource](./reactive/resource.md)**: Async state tracking (`resource`)
16
18
 
17
19
  ## [Advanced Topics](./reactive/advanced.md)
20
+ * **[Choosing the Right Primitive](./reactive/advanced.md#choosing-the-right-reactive-primitive)**: Comparison table of effect-value functions (memoize, lift, project, scan, etc.)
18
21
  * **[Atomic Operations](./reactive/advanced.md#atomic-operations)**: Batching and Bidirectional binding
19
22
  * **[Evolution Tracking](./reactive/advanced.md#evolution-tracking)**: History introspection
20
23
  * **[Prototype Chains](./reactive/advanced.md#prototype-chains-and-pure-objects)**: Advanced inheritance patterns
@@ -189,13 +189,13 @@ When the decorator is applied to a non-getter method:
189
189
  3. **Memory overhead**: Cached values are stored indefinitely
190
190
  4. **Getter-only**: Can only be applied to getter methods, not setters or regular methods
191
191
 
192
- ## Describe
192
+ ## Descriptor
193
193
 
194
- The `describe` decorator provides a clean, reusable way to configure property descriptors (enumerable, configurable, writable) for class properties. This decorator uses a functional approach that makes it easy to create reusable descriptor configurations.
194
+ The `descriptor` decorator provides a clean, reusable way to configure property descriptors (enumerable, configurable, writable) for class properties. This decorator uses a functional approach that makes it easy to create reusable descriptor configurations.
195
195
 
196
196
  ## API Reference
197
197
 
198
- ### `describe(descriptor: PropertyDescriptor): <T>(...properties: (keyof T)[]) => GenericClassDecorator<T>`
198
+ ### `descriptor(descriptor: PropertyDescriptor): <T>(...properties: (keyof T)[]) => GenericClassDecorator<T>`
199
199
 
200
200
  A function that creates a decorator to configure property descriptors for specified properties.
201
201
 
@@ -207,29 +207,68 @@ A function that creates a decorator to configure property descriptors for specif
207
207
 
208
208
  **Returns:** A function that takes property names and returns a class decorator
209
209
 
210
+ **Flavors:**
211
+ The `descriptor` function is flavored with the following chainable properties:
212
+ - `.enumerable` - Sets enumerable to true
213
+ - `.hidden` - Sets enumerable to false
214
+ - `.configurable` - Sets configurable to true
215
+ - `.frozen` - Sets configurable to false
216
+ - `.writable` - Sets writable to true
217
+ - `.readonly` - Sets writable to false
218
+
210
219
  **Usage Pattern:**
211
220
  ```typescript
212
- const readonly = describe({ writable: false })
213
- const hidden = describe({ enumerable: false })
214
- const locked = describe({ configurable: false })
221
+ // Using flavors for single properties
222
+ @descriptor.readonly('id', 'createdAt')
223
+ @descriptor.hidden('_private')
224
+ class MyClass { }
215
225
 
216
- @readonly('id', 'createdAt')
217
- @hidden('_private')
218
- @locked('critical')
226
+ // For multiple descriptor properties on the same property, use object syntax
227
+ @descriptor({ writable: false, enumerable: false, configurable: false })('secret')
219
228
  class MyClass { }
229
+
230
+ // Note: Stacking multiple flavored decorators on the same property is not recommended
231
+ // as each decorator creates a new subclass and only the last applied takes effect
220
232
  ```
221
233
 
222
234
  ## Usage Examples
223
235
 
236
+ ### Using Flavors
237
+
238
+ ```typescript
239
+ import { descriptor } from 'mutts/std-decorators'
240
+
241
+ // Use flavors directly for cleaner syntax
242
+ @descriptor.readonly('id', 'createdAt')
243
+ @descriptor.hidden('_private', '_cache')
244
+ @descriptor.frozen('critical')
245
+ class User {
246
+ id: string = 'user-123'
247
+ name: string = 'John'
248
+ _private: string = 'secret'
249
+ _cache: Map<string, any> = new Map()
250
+ createdAt: Date = new Date()
251
+ critical: string = 'locked'
252
+
253
+ constructor(name: string) {
254
+ this.name = name
255
+ }
256
+ }
257
+
258
+ const user = new User('Alice')
259
+ console.log(Object.keys(user)) // ['name', 'createdAt'] - only enumerable properties
260
+ // user.id = 'new-id' // TypeError: Cannot assign to read only property 'id'
261
+ ```
262
+
224
263
  ### Creating Reusable Decorators
225
264
 
226
265
  ```typescript
227
- import { describe } from 'mutts/std-decorators'
266
+ import { descriptor } from 'mutts/std-decorators'
228
267
 
229
268
  // Create reusable descriptor configurations
230
- const readonly = describe({ writable: false })
231
- const hidden = describe({ enumerable: false })
232
- const locked = describe({ configurable: false })
269
+ const readonly = descriptor.readonly
270
+ const hidden = descriptor.hidden
271
+ const locked = descriptor.frozen
233
272
 
234
273
  // Use them on classes
235
274
  @readonly('id', 'createdAt')
@@ -255,11 +294,10 @@ console.log(Object.keys(user)) // ['id', 'name', 'createdAt', 'critical'] - only
255
294
  ### Making Properties Non-Enumerable
256
295
 
257
296
  ```typescript
258
- import { describe } from 'mutts/std-decorators'
297
+ import { descriptor } from 'mutts/std-decorators'
259
298
 
260
- const hidden = describe({ enumerable: false })
261
-
262
- @hidden('_internal', '_cache', 'debug')
299
+ // Use the hidden flavor directly
300
+ @descriptor.hidden('_internal', '_cache', 'debug')
263
301
  class CacheManager {
264
302
  public data: any[] = []
265
303
  _internal: Map<string, any> = new Map()
@@ -280,13 +318,10 @@ console.log(Object.getOwnPropertyNames(cache)) // ['data', '_internal', '_cache'
280
318
  ### Read-Only Properties
281
319
 
282
320
  ```typescript
283
- import { describe } from 'mutts/std-decorators'
284
-
285
- const readonly = describe({ writable: false })
286
- const readonlyLocked = describe({ writable: false, configurable: false })
321
+ import { descriptor } from 'mutts/std-decorators'
287
322
 
288
- @readonly('createdAt', 'version')
289
- @readonlyLocked('id')
323
+ // Use flavors for read-only properties
324
+ @descriptor.readonly('createdAt', 'version')
290
325
  class Document {
291
326
  id: string
292
327
  title: string
@@ -306,25 +341,41 @@ class Document {
306
341
  }
307
342
 
308
343
  const doc = new Document('doc-1', 'My Document')
309
- // doc.id = 'new-id' // TypeError: Cannot assign to read only property 'id'
310
344
  // doc.createdAt = new Date() // TypeError: Cannot assign to read only property 'createdAt'
345
+ // doc.version = 2 // TypeError: Cannot assign to read only property 'version'
311
346
  doc.updateTitle('Updated Title') // This works
312
347
  ```
313
348
 
314
- ### Configuration Control
349
+ ### Combining Multiple Flavors
315
350
 
316
351
  ```typescript
317
- import { describe } from 'mutts/std-decorators'
352
+ import { descriptor } from 'mutts/std-decorators'
318
353
 
319
- const locked = describe({ configurable: false })
320
- const frozen = describe({ configurable: false, writable: false })
354
+ // Stack multiple flavored decorators
355
+ @descriptor.readonly('secret')
356
+ @descriptor.hidden('secret')
357
+ @descriptor.frozen('secret')
358
+ class SecureData {
359
+ public info: string = 'public'
360
+ secret: string = 'top secret'
361
+ }
321
362
 
322
- @locked('_sealed')
323
- @frozen('_frozen')
363
+ const data = new SecureData()
364
+ // data.secret = 'leaked' // TypeError: Cannot assign to read only property 'secret'
365
+ console.log(Object.keys(data)) // ['info'] - secret is not enumerable
366
+ // Object.defineProperty(data, 'secret', { value: 'new' }) // TypeError: Cannot redefine property
367
+ ```
368
+
369
+ ### Configuration Control
370
+
371
+ ```typescript
372
+ import { descriptor } from 'mutts/std-decorators'
373
+
374
+ // Use flavors for configuration control
375
+ @descriptor.frozen('_sealed')
324
376
  class SecureObject {
325
377
  public data: any
326
378
  _sealed: string = 'cannot be reconfigured'
327
- _frozen: string = 'cannot be changed or reconfigured'
328
379
 
329
380
  constructor(data: any) {
330
381
  this.data = data
@@ -333,41 +384,41 @@ class SecureObject {
333
384
 
334
385
  const obj = new SecureObject({ key: 'value' })
335
386
  // Object.defineProperty(obj, '_sealed', { value: 'new' }) // TypeError: Cannot redefine property
336
- // Object.defineProperty(obj, '_frozen', { value: 'new' }) // TypeError: Cannot redefine property
337
387
  ```
338
388
 
339
389
  ## Implementation Details
340
390
 
341
391
  ### Functional Approach
342
392
 
343
- The `describe` decorator uses a functional approach that separates descriptor configuration from property selection:
393
+ The `descriptor` function uses a flavored approach that provides direct access to common configurations:
344
394
 
345
395
  ```typescript
346
- // Create reusable configurations once
347
- const readonly = describe({ writable: false })
348
- const hidden = describe({ enumerable: false })
349
-
350
- // Apply to multiple classes with different properties
351
- @readonly('id', 'createdAt')
396
+ // Use flavors directly for cleaner syntax
397
+ @descriptor.readonly('id', 'createdAt')
398
+ @descriptor.hidden('internal')
352
399
  class User { }
353
400
 
401
+ // Or create reusable aliases
402
+ const readonly = descriptor.readonly
403
+ const hidden = descriptor.hidden
404
+
354
405
  @readonly('version', 'buildDate')
355
406
  class Package { }
356
407
  ```
357
408
 
358
409
  ### Common Decorator Patterns
359
410
 
360
- The functional approach makes it easy to create common decorator patterns:
411
+ The flavored approach makes it easy to create common decorator patterns:
361
412
 
362
413
  ```typescript
363
- import { describe } from './std-decorators'
414
+ import { descriptor } from './std-decorators'
364
415
 
365
- // Common reusable decorators
366
- export const readonly = describe({ writable: false })
367
- export const hidden = describe({ enumerable: false })
368
- export const locked = describe({ configurable: false })
369
- export const frozen = describe({ writable: false, configurable: false })
370
- export const private = describe({ enumerable: false, configurable: false })
416
+ // Common reusable decorators using flavors
417
+ export const readonly = descriptor.readonly
418
+ export const hidden = descriptor.hidden
419
+ export const locked = descriptor.frozen
420
+ export const frozen = (() => descriptor.readonly.frozen)()
421
+ export const private = (() => descriptor.hidden.frozen)()
371
422
 
372
423
  // Usage examples
373
424
  @readonly('id', 'createdAt')
@@ -389,7 +440,7 @@ The decorator merges the provided descriptor configuration with the existing pro
389
440
 
390
441
  ```typescript
391
442
  // Original property might have { enumerable: true, writable: true, configurable: true }
392
- // After @readonly('prop') where readonly = describe({ writable: false })
443
+ // After @descriptor.readonly('prop')
393
444
  // Final descriptor: { enumerable: true, writable: false, configurable: true }
394
445
  ```
395
446
 
@@ -399,9 +450,9 @@ The property descriptor configuration is applied in the constructor after callin
399
450
 
400
451
  ## Best Practices
401
452
 
402
- 1. **Create reusable configurations**: Define descriptor configurations once and reuse them
403
- 2. **Use descriptive names**: Name your descriptor configurations clearly (`readonly`, `hidden`, `locked`)
404
- 3. **Combine multiple decorators**: Stack multiple `describe` decorators for complex configurations
453
+ 1. **Use flavors directly**: Prefer `descriptor.readonly` over creating aliases for simple cases
454
+ 2. **Create reusable aliases**: For complex or frequently used combinations, create aliases
455
+ 3. **Combine multiple decorators**: Stack multiple flavored decorators for complex configurations
405
456
  4. **Use for encapsulation**: Hide internal properties from enumeration
406
457
  5. **Control immutability**: Make critical properties read-only
407
458
  6. **Prevent reconfiguration**: Lock important properties from being modified
package/docs/utils.md ADDED
@@ -0,0 +1,70 @@
1
+ # Utilities
2
+
3
+ Mutts provides a collection of lightweight, high-performance utility functions. These are used extensively within the reactive engine but are also exported for general application logic.
4
+
5
+ ## Collection Utilities
6
+
7
+ ### `zip(...arrays)`
8
+
9
+ A generator that yields tuples containing elements from each input array. It continues until the **longest** array is exhausted (returning `undefined` for shorter arrays).
10
+
11
+ ```typescript
12
+ import { zip } from 'mutts';
13
+
14
+ const names = ['Alice', 'Bob'];
15
+ const scores = [100, 95, 80];
16
+
17
+ for (const [name, score] of zip(names, scores)) {
18
+ console.log(`${name}: ${score}`);
19
+ }
20
+ // Alice: 100
21
+ // Bob: 95
22
+ // undefined: 80
23
+ ```
24
+
25
+ > [!NOTE]
26
+ > `zip` is implemented as a generator for memory efficiency. If you need a plain array, spread the result: `[...zip(a, b)]`.
27
+
28
+
29
+ ### `deepCompare(a, b)`
30
+
31
+ A robust deep comparison utility that handles circular references and various built-in types.
32
+
33
+ - **Supported Types**: Objects, Arrays, `Set`, `Map`, `Date`, `RegExp`.
34
+ - **Circular References**: Safely handled via internal tracking.
35
+ - **Prototypes**: Objects must have matching prototypes to be considered equal.
36
+
37
+ ```typescript
38
+ import { deepCompare } from 'mutts';
39
+
40
+ const obj1 = { date: new Date(0), map: new Map([['a', 1]]) };
41
+ const obj2 = { date: new Date(0), map: new Map([['a', 1]]) };
42
+
43
+ deepCompare(obj1, obj2); // true
44
+ ```
45
+
46
+ ## Type Reflection
47
+
48
+ ### `isConstructor(fn)` / `isObject(value)`
49
+
50
+ Utilities for robust type checking without the pitfalls of `typeof`.
51
+
52
+ - `isConstructor`: Returns `true` if the function is a `class` or a native constructor (like `Array`).
53
+ - `isObject`: Returns `true` for plain objects. Returns `false` for `null`, `Array`, `Date`, `Map`, etc.
54
+
55
+ ## Debugging & Metadata
56
+
57
+ ### `tag(name, obj)`
58
+
59
+ Applies a debugging "tag" to an object. It sets `Symbol.toStringTag` and overrides `toString()` so the object appears clearly in logs and DevTools.
60
+
61
+ ### `named(name, fn)`
62
+
63
+ Renames a function for better stack traces. If the function already has a name, it appends the new name using `::` as a separator (e.g., `original::new`).
64
+
65
+ ```typescript
66
+ const myFn = named('Enhanced', () => {});
67
+ console.log(myFn.name); // "Enhanced"
68
+ ```
69
+
70
+ ---
package/docs/zone.md CHANGED
@@ -21,20 +21,17 @@ console.log(myZone.active); // undefined
21
21
 
22
22
  ## Async Propagation
23
23
 
24
- By default, zones are lost when an async operation yields control (e.g., after `await`). To fix this, `mutts` provides `configureAsyncZone()`.
24
+ By default, zones are lost when an async operation yields control (e.g., after `await`). To fix this, register zones in `asyncZone` the global `ZoneAggregator` — which automatically preserves context across async boundaries via `asyncHooks`.
25
25
 
26
26
  ```typescript
27
- import { configureAsyncZone, asyncZone, Zone } from 'mutts/zone';
27
+ import { asyncZone, Zone } from 'mutts/zone';
28
28
 
29
29
  const requestId = new Zone<string>();
30
30
 
31
31
  // 1. Tell the global aggregator to track this zone
32
32
  asyncZone.add(requestId);
33
33
 
34
- // 2. Patch global async primitives (once per app)
35
- configureAsyncZone();
36
-
37
- // 3. Usage
34
+ // 2. Usage
38
35
  requestId.with("req-123", async () => {
39
36
  await somePromise();
40
37
  // Context is automatically preserved across await!
@@ -43,7 +40,6 @@ requestId.with("req-123", async () => {
43
40
  ```
44
41
 
45
42
  > [!WARNING]
46
- > TODO
47
43
  > **Browser Limitations**: In browser environments (where `AsyncLocalStorage` is unavailable), `mutts` relies on monkey-patching global async primitives (Promise, setTimeout, etc.) to propagate zones. This is generally less robust than Node.js's `async_hooks` and may fail to track context across:
48
44
  > * Native `async/await` boundaries in some modern browsers if not transpiled.
49
45
  > * Concurrent modifications to global prototypes by other libraries.