mutts 1.0.10 → 1.0.12

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 (61) hide show
  1. package/README.md +3 -3
  2. package/dist/browser.cjs +244 -3199
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.ts +2 -2
  5. package/dist/browser.dev.cjs +51 -50
  6. package/dist/browser.dev.cjs.map +1 -1
  7. package/dist/browser.dev.d.ts +2 -2
  8. package/dist/browser.dev.esm.js +2 -2
  9. package/dist/browser.esm.js +15 -13
  10. package/dist/browser.esm.js.map +1 -1
  11. package/dist/chunks/{index-CaaQQlPJ.esm.js → index-BUop6B2U.esm.js} +384 -981
  12. package/dist/chunks/index-BUop6B2U.esm.js.map +1 -0
  13. package/dist/chunks/index-yK0HVxHv.cjs +2612 -0
  14. package/dist/chunks/index-yK0HVxHv.cjs.map +1 -0
  15. package/dist/chunks/{node-nKJBk8iJ.esm.js → node-Bo7WU5S2.esm.js} +2 -2
  16. package/dist/chunks/{node-nKJBk8iJ.esm.js.map → node-Bo7WU5S2.esm.js.map} +1 -1
  17. package/dist/chunks/{async-node-3PrbVAbB.cjs → node-Dd0esp5F.cjs} +4 -4
  18. package/dist/chunks/node-Dd0esp5F.cjs.map +1 -0
  19. package/dist/chunks/{proxy-Dtg-bJ3T.cjs → proxy-BvM4yewA.cjs} +441 -342
  20. package/dist/chunks/proxy-BvM4yewA.cjs.map +1 -0
  21. package/dist/chunks/{proxy-r7lARftl.esm.js → proxy-D2C49sXH.esm.js} +401 -330
  22. package/dist/chunks/proxy-D2C49sXH.esm.js.map +1 -0
  23. package/dist/debug.cjs +17 -4
  24. package/dist/debug.cjs.map +1 -1
  25. package/dist/debug.d.ts +2 -2
  26. package/dist/debug.esm.js +17 -3
  27. package/dist/debug.esm.js.map +1 -1
  28. package/dist/index.d.ts +84 -209
  29. package/dist/mutts.umd.js +842 -1362
  30. package/dist/mutts.umd.js.map +1 -1
  31. package/dist/mutts.umd.min.js +1 -1
  32. package/dist/mutts.umd.min.js.map +1 -1
  33. package/dist/node.cjs +51 -49
  34. package/dist/node.cjs.map +1 -1
  35. package/dist/node.d.ts +2 -2
  36. package/dist/node.dev.cjs +51 -49
  37. package/dist/node.dev.cjs.map +1 -1
  38. package/dist/node.dev.d.ts +2 -2
  39. package/dist/node.dev.esm.js +3 -3
  40. package/dist/node.esm.js +3 -3
  41. package/dist/{types-W5vD6m2n.d.ts → types-Bx2PhORg.d.ts} +38 -47
  42. package/docs/ai/api-reference.md +0 -14
  43. package/docs/ai/manual.md +2 -22
  44. package/docs/reactive/advanced.md +13 -14
  45. package/docs/reactive/attend.md +1 -2
  46. package/docs/reactive/collections.md +2 -149
  47. package/docs/reactive/core.md +218 -96
  48. package/docs/reactive/debugging.md +2 -2
  49. package/docs/reactive/resource.md +1 -1
  50. package/docs/reactive.md +1 -3
  51. package/docs/zone.md +1 -1
  52. package/package.json +18 -9
  53. package/dist/chunks/async-browser-BU_IfxYD.cjs +0 -216
  54. package/dist/chunks/async-browser-BU_IfxYD.cjs.map +0 -1
  55. package/dist/chunks/async-core-CRLKP3l-.cjs +0 -29
  56. package/dist/chunks/async-core-CRLKP3l-.cjs.map +0 -1
  57. package/dist/chunks/async-node-3PrbVAbB.cjs.map +0 -1
  58. package/dist/chunks/index-CaaQQlPJ.esm.js.map +0 -1
  59. package/dist/chunks/proxy-Dtg-bJ3T.cjs.map +0 -1
  60. package/dist/chunks/proxy-r7lARftl.esm.js.map +0 -1
  61. package/docs/reactive/scan.md +0 -324
@@ -1,324 +0,0 @@
1
- # Reactive Scan
2
-
3
- The `scan` function perform a reactive accumulation over an array of items. Unlike a standard `Array.reduce`, it is designed to be highly efficient in a reactive system, particularly when items are moved or changed, by returning a reactive array of all intermediate results.
4
-
5
- ## Overview
6
-
7
- In a typical reactive system, calling `array.reduce(...)` inside an `effect` means the entire reduction re-runs every time the array structure or a single item changes.
8
-
9
- Reactive `scan` solves this by maintaining a chain of **reactive intermediates**. Each item in the source array is linked to an intermediate that depends on the *previous* intermediate's result.
10
-
11
- ## Key Features
12
-
13
- - **Fine-Grained Reactivity**: Changing a property on an item only re-computes the accumulated value for that item and its successors.
14
- - **Move Optimization**: If a subsequence of items moves together (e.g., sorting or splicing), their intermediates are reused. As long as an item's predecessor in the array hasn't changed, its accumulated value is hit from the cache.
15
- - **Duplicate Support**: Correctly handles multiple occurrences of the same object instance.
16
- - **Memory Safety**: Uses `WeakMap` for intermediate storage, ensuring data is cleared when source items are garbage collected.
17
- - **Granular Sync**: Uses per-index effects to sync results, preventing broad dependency tracking of the source array in every calculation.
18
-
19
- ## Basic Usage
20
-
21
- ```typescript
22
- import { reactive, scan } from 'mutts/reactive'
23
-
24
- const source = reactive([
25
- { id: 'A', val: 1 },
26
- { id: 'B', val: 2 },
27
- { id: 'C', val: 3 },
28
- ])
29
-
30
- // result is a reactive array: [1, 3, 6]
31
- const result = scan(source, (acc, item) => acc + item.val, 0)
32
-
33
- // Updating an item only re-computes for that position and successors
34
- source[1].val = 10
35
- // result stays [1, 11, 14]
36
- ```
37
-
38
- ## How it Works
39
-
40
- The implementation consists of:
41
- 1. **A Main Effect**: Tracks the structure of the source array (length and item identities). It manages a list of `Intermediate` objects and stays updated on their `prev` links.
42
- 2. **Intermediates**: Class instances that link `val` and `prev`. They expose an `acc` getter decorated with `@memoize`.
43
- 3. **Index Sync Effects**: Granular effects (one per result index) that subscribe to `indexToIntermediate[i].acc`.
44
-
45
- This "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
-
47
- ## API Reference
48
-
49
- ```typescript
50
- function scan<Input extends object, Output>(
51
- source: readonly Input[],
52
- callback: (acc: Output, val: Input) => Output,
53
- initialValue: Output
54
- ): ScanResult<Output>
55
- ```
56
-
57
- ### Parameters
58
- - `source`: The source array. All items must be objects (WeakKeys) to enable intermediate caching.
59
- - `callback`: The accumulator function `(acc, val) => nextAcc`.
60
- - `initialValue`: The value used as the accumulator for the first item.
61
-
62
- ### Returns
63
- A reactive array of accumulated values. It includes a `[cleanup]` symbol that should be called to stop the reactive tracking.
64
-
65
- ```typescript
66
- import { cleanup } from 'mutts/reactive'
67
- // ...
68
- result[cleanup]()
69
- ```
70
-
71
- ## Performance Comparison
72
-
73
- | Operation | Standard `Array.reduce` in `effect` | Reactive `scan` |
74
- | :--- | :--- | :--- |
75
- | **Initial Run** | O(N) calls | O(N) calls |
76
- | **Modify Item at `i`** | O(N) calls (entire reduction) | O(N-i) calls |
77
- | **Append Item** | O(N+1) calls | 1 call |
78
- | **Move Item** | O(N) calls | O(affected chain) |
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