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.
- package/README.md +3 -3
- package/dist/browser.cjs +244 -3199
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +2 -2
- package/dist/browser.dev.cjs +51 -50
- package/dist/browser.dev.cjs.map +1 -1
- package/dist/browser.dev.d.ts +2 -2
- package/dist/browser.dev.esm.js +2 -2
- package/dist/browser.esm.js +15 -13
- package/dist/browser.esm.js.map +1 -1
- package/dist/chunks/{index-CaaQQlPJ.esm.js → index-BUop6B2U.esm.js} +384 -981
- package/dist/chunks/index-BUop6B2U.esm.js.map +1 -0
- package/dist/chunks/index-yK0HVxHv.cjs +2612 -0
- package/dist/chunks/index-yK0HVxHv.cjs.map +1 -0
- package/dist/chunks/{node-nKJBk8iJ.esm.js → node-Bo7WU5S2.esm.js} +2 -2
- package/dist/chunks/{node-nKJBk8iJ.esm.js.map → node-Bo7WU5S2.esm.js.map} +1 -1
- package/dist/chunks/{async-node-3PrbVAbB.cjs → node-Dd0esp5F.cjs} +4 -4
- package/dist/chunks/node-Dd0esp5F.cjs.map +1 -0
- package/dist/chunks/{proxy-Dtg-bJ3T.cjs → proxy-BvM4yewA.cjs} +441 -342
- package/dist/chunks/proxy-BvM4yewA.cjs.map +1 -0
- package/dist/chunks/{proxy-r7lARftl.esm.js → proxy-D2C49sXH.esm.js} +401 -330
- package/dist/chunks/proxy-D2C49sXH.esm.js.map +1 -0
- package/dist/debug.cjs +17 -4
- package/dist/debug.cjs.map +1 -1
- package/dist/debug.d.ts +2 -2
- package/dist/debug.esm.js +17 -3
- package/dist/debug.esm.js.map +1 -1
- package/dist/index.d.ts +84 -209
- package/dist/mutts.umd.js +842 -1362
- package/dist/mutts.umd.js.map +1 -1
- package/dist/mutts.umd.min.js +1 -1
- package/dist/mutts.umd.min.js.map +1 -1
- package/dist/node.cjs +51 -49
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.dev.cjs +51 -49
- package/dist/node.dev.cjs.map +1 -1
- package/dist/node.dev.d.ts +2 -2
- package/dist/node.dev.esm.js +3 -3
- package/dist/node.esm.js +3 -3
- package/dist/{types-W5vD6m2n.d.ts → types-Bx2PhORg.d.ts} +38 -47
- package/docs/ai/api-reference.md +0 -14
- package/docs/ai/manual.md +2 -22
- package/docs/reactive/advanced.md +13 -14
- package/docs/reactive/attend.md +1 -2
- package/docs/reactive/collections.md +2 -149
- package/docs/reactive/core.md +218 -96
- package/docs/reactive/debugging.md +2 -2
- package/docs/reactive/resource.md +1 -1
- package/docs/reactive.md +1 -3
- package/docs/zone.md +1 -1
- package/package.json +18 -9
- package/dist/chunks/async-browser-BU_IfxYD.cjs +0 -216
- package/dist/chunks/async-browser-BU_IfxYD.cjs.map +0 -1
- package/dist/chunks/async-core-CRLKP3l-.cjs +0 -29
- package/dist/chunks/async-core-CRLKP3l-.cjs.map +0 -1
- package/dist/chunks/async-node-3PrbVAbB.cjs.map +0 -1
- package/dist/chunks/index-CaaQQlPJ.esm.js.map +0 -1
- package/dist/chunks/proxy-Dtg-bJ3T.cjs.map +0 -1
- package/dist/chunks/proxy-r7lARftl.esm.js.map +0 -1
- package/docs/reactive/scan.md +0 -324
package/docs/reactive/scan.md
DELETED
|
@@ -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
|