mutts 1.0.7 → 1.0.9
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 +72 -17
- package/dist/browser.cjs +3111 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.ts +1391 -1
- package/dist/browser.esm.js +264 -35
- package/dist/browser.esm.js.map +1 -1
- package/dist/chunks/async-browser-lvzLOCgk.cjs +297 -0
- package/dist/chunks/async-browser-lvzLOCgk.cjs.map +1 -0
- package/dist/chunks/async-core-CRLKP3l-.cjs +29 -0
- package/dist/chunks/async-core-CRLKP3l-.cjs.map +1 -0
- package/dist/chunks/async-node-C3DeIb0y.cjs +96 -0
- package/dist/chunks/async-node-C3DeIb0y.cjs.map +1 -0
- package/dist/chunks/index-2vea86wD.esm.js +3011 -0
- package/dist/chunks/index-2vea86wD.esm.js.map +1 -0
- package/dist/chunks/index-CtA2AWl3.esm.js +4376 -0
- package/dist/chunks/index-CtA2AWl3.esm.js.map +1 -0
- package/dist/chunks/index-VTO-b2vR.cjs +4461 -0
- package/dist/chunks/index-VTO-b2vR.cjs.map +1 -0
- package/dist/debug/debug.d.ts +122 -0
- package/dist/debug/debug.d.ts.map +1 -0
- package/dist/debug/index.d.ts +4 -0
- package/dist/debug/index.d.ts.map +1 -0
- package/dist/debug/lineage-panel.d.ts +5 -0
- package/dist/debug/lineage-panel.d.ts.map +1 -0
- package/dist/debug/lineage.d.ts +79 -0
- package/dist/debug/lineage.d.ts.map +1 -0
- package/dist/debug.cjs +35 -0
- package/dist/debug.cjs.map +1 -0
- package/dist/debug.d.ts +209 -0
- package/dist/debug.esm.js +2 -0
- package/dist/debug.esm.js.map +1 -0
- package/dist/devtools/devtool/devtools.d.ts +1 -0
- package/dist/devtools/devtool/devtools.d.ts.map +1 -0
- package/dist/devtools/devtool/panel.d.ts +2 -0
- package/dist/devtools/devtool/panel.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/mutts.umd.js +1 -1
- 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 +107 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.ts +2 -2
- package/dist/node.esm.js +86 -33
- package/dist/node.esm.js.map +1 -1
- package/dist/src/async/browser.d.ts +2 -0
- package/dist/src/async/browser.d.ts.map +1 -0
- package/dist/src/async/index.d.ts +19 -0
- package/dist/src/async/index.d.ts.map +1 -0
- package/dist/src/async/node.d.ts +2 -0
- package/dist/src/async/node.d.ts.map +1 -0
- package/dist/src/decorator.d.ts +106 -0
- package/dist/src/decorator.d.ts.map +1 -0
- package/dist/src/destroyable.d.ts +87 -0
- package/dist/src/destroyable.d.ts.map +1 -0
- package/dist/src/entry-browser.d.ts +3 -0
- package/dist/src/entry-browser.d.ts.map +1 -0
- package/dist/src/entry-node.d.ts +3 -0
- package/dist/src/entry-node.d.ts.map +1 -0
- package/dist/src/eventful.d.ts +20 -0
- package/dist/src/eventful.d.ts.map +1 -0
- package/dist/src/flavored.d.ts +33 -0
- package/dist/src/flavored.d.ts.map +1 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/indexable.d.ts +243 -0
- package/dist/src/indexable.d.ts.map +1 -0
- package/dist/src/introspection.d.ts +27 -0
- package/dist/src/introspection.d.ts.map +1 -0
- package/dist/src/iterableWeak.d.ts +53 -0
- package/dist/src/iterableWeak.d.ts.map +1 -0
- package/dist/src/mixins.d.ts +25 -0
- package/dist/src/mixins.d.ts.map +1 -0
- package/dist/src/promiseChain.d.ts +20 -0
- package/dist/src/promiseChain.d.ts.map +1 -0
- package/dist/src/reactive/array.d.ts +48 -0
- package/dist/src/reactive/array.d.ts.map +1 -0
- package/dist/src/reactive/buffer.d.ts +120 -0
- package/dist/src/reactive/buffer.d.ts.map +1 -0
- package/dist/src/reactive/change.d.ts +29 -0
- package/dist/src/reactive/change.d.ts.map +1 -0
- package/dist/src/reactive/deep-touch.d.ts +28 -0
- package/dist/src/reactive/deep-touch.d.ts.map +1 -0
- package/dist/src/reactive/deep-watch-state.d.ts +25 -0
- package/dist/src/reactive/deep-watch-state.d.ts.map +1 -0
- package/dist/src/reactive/deep-watch.d.ts +20 -0
- package/dist/src/reactive/deep-watch.d.ts.map +1 -0
- package/dist/src/reactive/describe.d.ts +12 -0
- package/dist/src/reactive/describe.d.ts.map +1 -0
- package/dist/src/reactive/effect-context.d.ts +34 -0
- package/dist/src/reactive/effect-context.d.ts.map +1 -0
- package/dist/src/reactive/effects.d.ts +164 -0
- package/dist/src/reactive/effects.d.ts.map +1 -0
- package/dist/src/reactive/index.d.ts +19 -0
- package/dist/src/reactive/index.d.ts.map +1 -0
- package/dist/src/reactive/map.d.ts +28 -0
- package/dist/src/reactive/map.d.ts.map +1 -0
- package/dist/src/reactive/memoize.d.ts +28 -0
- package/dist/src/reactive/memoize.d.ts.map +1 -0
- package/dist/src/reactive/non-reactive-state.d.ts +9 -0
- package/dist/src/reactive/non-reactive-state.d.ts.map +1 -0
- package/dist/src/reactive/non-reactive.d.ts +11 -0
- package/dist/src/reactive/non-reactive.d.ts.map +1 -0
- package/dist/src/reactive/project.d.ts +40 -0
- package/dist/src/reactive/project.d.ts.map +1 -0
- package/dist/src/reactive/proxy-state.d.ts +8 -0
- package/dist/src/reactive/proxy-state.d.ts.map +1 -0
- package/dist/src/reactive/proxy.d.ts +23 -0
- package/dist/src/reactive/proxy.d.ts.map +1 -0
- package/dist/src/reactive/record.d.ts +115 -0
- package/dist/src/reactive/record.d.ts.map +1 -0
- package/dist/src/reactive/register.d.ts +125 -0
- package/dist/src/reactive/register.d.ts.map +1 -0
- package/dist/src/reactive/registry.d.ts +21 -0
- package/dist/src/reactive/registry.d.ts.map +1 -0
- package/dist/src/reactive/set.d.ts +26 -0
- package/dist/src/reactive/set.d.ts.map +1 -0
- package/dist/src/reactive/tracking.d.ts +7 -0
- package/dist/src/reactive/tracking.d.ts.map +1 -0
- package/dist/src/reactive/types.d.ts +424 -0
- package/dist/src/reactive/types.d.ts.map +1 -0
- package/dist/src/reactive/watch.d.ts +48 -0
- package/dist/src/reactive/watch.d.ts.map +1 -0
- package/dist/src/std-decorators.d.ts +45 -0
- package/dist/src/std-decorators.d.ts.map +1 -0
- package/dist/src/utils.d.ts +49 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/zone.d.ts +40 -0
- package/dist/src/zone.d.ts.map +1 -0
- package/dist/types-DaHFfhlN.d.ts +414 -0
- package/docs/ai/api-reference.md +1 -1
- package/docs/ai/manual.md +443 -17
- package/docs/events.md +57 -2
- package/docs/flavored.md +210 -0
- package/docs/reactive/advanced.md +2 -3
- package/docs/reactive/attend.md +143 -0
- package/docs/reactive/collections.md +55 -0
- package/docs/reactive/core.md +120 -4
- package/docs/reactive/debugging.md +8 -10
- package/docs/reactive/describe.md +85 -0
- package/docs/reactive/error-handling.md +121 -0
- package/docs/reactive/project.md +1 -1
- package/docs/reactive/scan.md +215 -0
- package/docs/reactive.md +2 -0
- package/docs/std-decorators.md +171 -52
- package/docs/zone.md +9 -6
- package/package.json +44 -27
- package/src/async/browser.ts +277 -41
- package/src/async/index.ts +23 -4
- package/src/async/node.ts +78 -32
- package/src/entry-browser.ts +5 -0
- package/src/entry-node.ts +5 -0
- package/src/eventful.ts +82 -49
- package/src/flavored.ts +106 -0
- package/src/index.d.ts +12 -9
- package/src/index.ts +2 -1
- package/src/introspection.ts +1 -1
- package/src/reactive/array.ts +160 -80
- package/src/reactive/buffer.ts +187 -27
- package/src/reactive/change.ts +16 -23
- package/src/reactive/deep-touch.ts +19 -14
- package/src/reactive/deep-watch-state.ts +3 -3
- package/src/reactive/deep-watch.ts +7 -4
- package/src/reactive/describe.ts +39 -0
- package/src/reactive/effect-context.ts +71 -6
- package/src/reactive/effects.ts +445 -393
- package/src/reactive/index.ts +12 -14
- package/src/reactive/map.ts +8 -24
- package/src/reactive/memoize.ts +26 -3
- package/src/reactive/project.md +107 -0
- package/src/reactive/project.ts +48 -71
- package/src/reactive/proxy.ts +21 -3
- package/src/reactive/record.ts +30 -45
- package/src/reactive/register.ts +138 -19
- package/src/reactive/registry.ts +22 -9
- package/src/reactive/set.ts +7 -20
- package/src/reactive/tracking.ts +9 -12
- package/src/reactive/types.ts +104 -24
- package/src/reactive/{interface.ts → watch.ts} +29 -59
- package/src/std-decorators.ts +56 -17
- package/src/utils.ts +3 -4
- package/src/zone.ts +43 -28
- package/dist/browser.js +0 -161
- package/dist/browser.js.map +0 -1
- package/dist/chunks/index-BFYK02LG.js +0 -6378
- package/dist/chunks/index-BFYK02LG.js.map +0 -1
- package/dist/chunks/index-CNR6QRUl.esm.js +0 -6291
- package/dist/chunks/index-CNR6QRUl.esm.js.map +0 -1
- package/dist/node.js +0 -136
- package/dist/node.js.map +0 -1
- package/src/reactive/debug.ts +0 -517
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Reactive Property Description (`describe`)
|
|
2
|
+
|
|
3
|
+
The `describe` utility provides a reactive way to define or update properties on an object using a record of property descriptors.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Unlike `Object.defineProperties`, the reactive `describe` utility is alive:
|
|
8
|
+
- It tracks the **keys** of the descriptors record.
|
|
9
|
+
- When a new key is added to the descriptors record, the property is defined on the target object.
|
|
10
|
+
- When a key is removed, the property is removed from the target object.
|
|
11
|
+
- When a descriptor itself changes (e.g. its `value` or `get` function), the property definition is updated.
|
|
12
|
+
|
|
13
|
+
This is particularly useful when projecting props or dynamically extending objects in a reactive way.
|
|
14
|
+
|
|
15
|
+
Internally, `describe` is built on [`attend`](./attend.md), which provides the reactive key lifecycle.
|
|
16
|
+
|
|
17
|
+
## API
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
function describe<T extends object>(
|
|
21
|
+
descriptors: Record<PropertyKey, PropertyDescriptor>,
|
|
22
|
+
target: T = {} as T
|
|
23
|
+
): T
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Parameters
|
|
27
|
+
|
|
28
|
+
- `descriptors`: A reactive record where each value is a standard `PropertyDescriptor`.
|
|
29
|
+
- `target` (optional): The object to define properties on. If omitted, a new plain object is created and returned.
|
|
30
|
+
|
|
31
|
+
### Returns
|
|
32
|
+
|
|
33
|
+
The `target` object with reactive property definitions.
|
|
34
|
+
|
|
35
|
+
## Basic Usage
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { reactive, describe, effect } from 'mutts/reactive'
|
|
39
|
+
|
|
40
|
+
const target = {}
|
|
41
|
+
const descriptors = reactive({
|
|
42
|
+
foo: { value: 1, enumerable: true }
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe(descriptors, target)
|
|
46
|
+
|
|
47
|
+
console.log(target.foo) // 1
|
|
48
|
+
|
|
49
|
+
// Adding a property reactively
|
|
50
|
+
descriptors.bar = { get: () => 42, enumerable: true }
|
|
51
|
+
console.log(target.bar) // 42
|
|
52
|
+
|
|
53
|
+
// Removing a property reactively
|
|
54
|
+
delete descriptors.foo
|
|
55
|
+
console.log('foo' in target) // false
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Integration with `project`
|
|
59
|
+
|
|
60
|
+
`describe` is often used in conjunction with `project` to create dynamic projections of property descriptors:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { project, describe, memoize } from 'mutts/reactive'
|
|
64
|
+
|
|
65
|
+
function propsInto(props, into) {
|
|
66
|
+
const descriptors = project(props, ({ key, value }) => ({
|
|
67
|
+
get: memoize(() => (typeof value === 'function' ? value() : value)),
|
|
68
|
+
enumerable: true,
|
|
69
|
+
configurable: true
|
|
70
|
+
}))
|
|
71
|
+
|
|
72
|
+
return describe(descriptors, into)
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Renaming Conflict
|
|
77
|
+
|
|
78
|
+
Previously, `mutts` had a decorator named `describe`. To avoid confusion with this utility, the decorator has been renamed to `@descriptor`.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
@descriptor({ enumerable: false })
|
|
82
|
+
class MyClass {
|
|
83
|
+
internal = 'secret'
|
|
84
|
+
}
|
|
85
|
+
```
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Effect Error Handling
|
|
2
|
+
|
|
3
|
+
The `onEffectThrow` function allows you to catch and handle errors within reactive effects.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
Register an error handler inside an effect using `onEffectThrow`:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { effect, onEffectThrow, reactive } from 'mutts'
|
|
11
|
+
|
|
12
|
+
const state = reactive({ value: 0 })
|
|
13
|
+
|
|
14
|
+
effect(() => {
|
|
15
|
+
onEffectThrow((error) => {
|
|
16
|
+
console.error('Effect failed:', error)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
// Your effect logic that might throw
|
|
20
|
+
if (state.value < 0) throw new Error('Negative value not allowed')
|
|
21
|
+
})
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Multiple Handlers
|
|
25
|
+
|
|
26
|
+
You can register multiple handlers. They are tried in order until one succeeds:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
effect(() => {
|
|
30
|
+
// First handler - try to recover
|
|
31
|
+
onEffectThrow((error) => {
|
|
32
|
+
if (error.message === 'Retryable') {
|
|
33
|
+
retryOperation()
|
|
34
|
+
return // Success - stops here
|
|
35
|
+
}
|
|
36
|
+
throw error // Re-throw to try next handler
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// Second handler - log and continue
|
|
40
|
+
onEffectThrow((error) => {
|
|
41
|
+
console.log('Operation failed:', error)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Parent-Child Error Propagation
|
|
47
|
+
|
|
48
|
+
Errors in child effects propagate to parent effects:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
effect(() => {
|
|
52
|
+
// Parent catches child's error
|
|
53
|
+
onEffectThrow((error) => {
|
|
54
|
+
console.log('Child failed:', error.message)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
effect(() => {
|
|
58
|
+
// This error propagates to parent, even if a reactive re-evaluation
|
|
59
|
+
throw new Error('Child error')
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If no handler catches the error, it propagates up the effect chain until caught or thrown at the root.
|
|
65
|
+
|
|
66
|
+
## Cleanup from Handler
|
|
67
|
+
|
|
68
|
+
Handlers can return cleanup functions:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
effect(() => {
|
|
72
|
+
onEffectThrow((error) => {
|
|
73
|
+
console.log('Handling error:', error)
|
|
74
|
+
|
|
75
|
+
return () => {
|
|
76
|
+
// Cleanup when effect is destroyed
|
|
77
|
+
cleanupResources()
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## API
|
|
84
|
+
|
|
85
|
+
### `onEffectThrow(handler)`
|
|
86
|
+
|
|
87
|
+
Registers an error handler for the current effect.
|
|
88
|
+
|
|
89
|
+
**Parameters:**
|
|
90
|
+
- `handler: (error: any) => (() => void) | undefined | void` - Function called when an error occurs in the effect
|
|
91
|
+
|
|
92
|
+
**Returns:** Nothing
|
|
93
|
+
|
|
94
|
+
**Throws:** Error if called outside of an effect
|
|
95
|
+
|
|
96
|
+
**Handler behavior:**
|
|
97
|
+
- Returns without throwing → error is considered handled
|
|
98
|
+
- Throws → next handler is tried
|
|
99
|
+
- Returns a function → used as cleanup when effect stops
|
|
100
|
+
|
|
101
|
+
## Error Propagation Flow
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
Effect throws
|
|
105
|
+
↓
|
|
106
|
+
Try handlers in order
|
|
107
|
+
↓
|
|
108
|
+
Handler succeeds → effect continues
|
|
109
|
+
↓
|
|
110
|
+
All handlers fail → propagate to parent
|
|
111
|
+
↓
|
|
112
|
+
Parent's handlers try
|
|
113
|
+
↓
|
|
114
|
+
...continue until caught or root reached
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Notes
|
|
118
|
+
|
|
119
|
+
- Handlers must be registered **before** the code that might throw
|
|
120
|
+
- Handlers are cleared on each effect re-run (re-register if needed)
|
|
121
|
+
- Errors in async effects (Promises) are not caught by `onEffectThrow` - use `.catch()` on the Promise
|
package/docs/reactive/project.md
CHANGED
|
@@ -87,7 +87,7 @@ project.array(
|
|
|
87
87
|
|
|
88
88
|
**Related Files:**
|
|
89
89
|
- `src/reactive/project.ts` - Main implementation
|
|
90
|
-
- `src/reactive/
|
|
90
|
+
- `src/reactive/array.ts` - Reference for `ReactiveReadOnlyArray` pattern
|
|
91
91
|
- `src/reactive/record.ts` - Reference for `organized` pattern (different use case)
|
|
92
92
|
- `tests/reactive/project.test.ts` - Test coverage
|
|
93
93
|
|
package/docs/reactive/scan.md
CHANGED
|
@@ -76,3 +76,218 @@ 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
|
+
## Performance Considerations
|
|
280
|
+
|
|
281
|
+
### Arrays
|
|
282
|
+
- **Efficient Updates**: Only changed elements are updated, not the entire array
|
|
283
|
+
- **Length Adjustments**: Array length changes are handled separately from element updates
|
|
284
|
+
- **Reference Stability**: Unchanged elements maintain their references
|
|
285
|
+
|
|
286
|
+
### Objects
|
|
287
|
+
- **Property-wise Updates**: Only changed properties are updated using `Object.assign()`
|
|
288
|
+
- **Property Addition/Removal**: Properties are added or removed as needed when the source object structure changes
|
|
289
|
+
- **Reference Stability**: The reactive object maintains its identity while properties are updated
|
|
290
|
+
|
|
291
|
+
### General
|
|
292
|
+
- **Cleanup**: Remember to call the cleanup function when the lifted array/object is no longer needed to prevent memory leaks
|
|
293
|
+
- **Type Consistency**: The callback must return the same type (array or object) on subsequent calls
|
package/docs/reactive.md
CHANGED
|
@@ -12,6 +12,8 @@ The Mutts Reactive System documentation has been split into focused sections for
|
|
|
12
12
|
* **[Reactive Arrays](./reactive/collections.md#reactivearray)**: Full array method support
|
|
13
13
|
* **[Register](./reactive/collections.md#register)**: ID-keyed ordered collections
|
|
14
14
|
* **[Projections](./reactive/collections.md#projection)**: `project`, `organized`
|
|
15
|
+
* **[Attend](./reactive/attend.md)**: Reactive enumeration (`attend`)
|
|
16
|
+
* **[Describe](./reactive/describe.md)**: Reactive property definition (`describe`)
|
|
15
17
|
* **[Scan](./reactive/scan.md)**: Reactive scan and accumulation
|
|
16
18
|
|
|
17
19
|
## [Advanced Topics](./reactive/advanced.md)
|