mutts 1.0.9 → 1.0.11
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 +60 -35
- package/dist/browser.cjs +1333 -1760
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +2 -1392
- package/dist/browser.dev.cjs +114 -0
- package/dist/browser.dev.cjs.map +1 -0
- package/dist/browser.dev.d.ts +2 -0
- package/dist/browser.dev.esm.js +5 -0
- package/dist/browser.dev.esm.js.map +1 -0
- package/dist/browser.esm.js +18 -97
- package/dist/browser.esm.js.map +1 -1
- package/dist/chunks/{async-browser-lvzLOCgk.cjs → async-browser-Dgr5CreQ.cjs} +16 -95
- package/dist/chunks/async-browser-Dgr5CreQ.cjs.map +1 -0
- package/dist/chunks/{async-node-C3DeIb0y.cjs → async-node-3PrbVAbB.cjs} +3 -1
- package/dist/chunks/async-node-3PrbVAbB.cjs.map +1 -0
- package/dist/chunks/index-Sf74wXTV.esm.js +2577 -0
- package/dist/chunks/index-Sf74wXTV.esm.js.map +1 -0
- package/dist/chunks/node-Bo7WU5S2.esm.js +96 -0
- package/dist/chunks/node-Bo7WU5S2.esm.js.map +1 -0
- package/dist/chunks/{index-VTO-b2vR.cjs → proxy-Cc79Lrzj.cjs} +2593 -3216
- package/dist/chunks/proxy-Cc79Lrzj.cjs.map +1 -0
- package/dist/chunks/{index-CtA2AWl3.esm.js → proxy-D2C49sXH.esm.js} +2570 -3174
- package/dist/chunks/proxy-D2C49sXH.esm.js.map +1 -0
- package/dist/debug.cjs +987 -28
- package/dist/debug.cjs.map +1 -1
- package/dist/debug.d.ts +37 -38
- package/dist/debug.esm.js +963 -1
- package/dist/debug.esm.js.map +1 -1
- package/dist/devtools/manifest.json +1 -1
- package/dist/devtools/panel.html +1 -1
- package/dist/devtools/panel.js +107 -94
- package/dist/devtools/panel.js.map +1 -1
- package/dist/index.d.ts +1322 -1
- package/dist/mutts.umd.js +6647 -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 +69 -63
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.dev.cjs +114 -0
- package/dist/node.dev.cjs.map +1 -0
- package/dist/node.dev.d.ts +2 -0
- package/dist/node.dev.esm.js +6 -0
- package/dist/node.dev.esm.js.map +1 -0
- package/dist/node.esm.js +4 -97
- package/dist/node.esm.js.map +1 -1
- package/dist/{types-DaHFfhlN.d.ts → types-Bx2PhORg.d.ts} +134 -88
- package/docs/ai/api-reference.md +11 -32
- package/docs/ai/manual.md +297 -239
- package/docs/reactive/advanced.md +318 -9
- package/docs/reactive/attend.md +2 -4
- package/docs/reactive/collections.md +22 -187
- package/docs/reactive/core.md +223 -131
- package/docs/reactive/debugging.md +119 -12
- package/docs/reactive/error-handling.md +10 -10
- package/docs/reactive/resource.md +125 -0
- package/docs/reactive.md +3 -4
- package/docs/utils.md +70 -0
- package/docs/zone.md +1 -1
- package/package.json +76 -38
- package/dist/chunks/async-browser-lvzLOCgk.cjs.map +0 -1
- package/dist/chunks/async-node-C3DeIb0y.cjs.map +0 -1
- package/dist/chunks/index-2vea86wD.esm.js +0 -3011
- package/dist/chunks/index-2vea86wD.esm.js.map +0 -1
- package/dist/chunks/index-CtA2AWl3.esm.js.map +0 -1
- package/dist/chunks/index-VTO-b2vR.cjs.map +0 -1
- package/dist/debug/debug.d.ts +0 -122
- package/dist/debug/debug.d.ts.map +0 -1
- package/dist/debug/index.d.ts +0 -4
- package/dist/debug/index.d.ts.map +0 -1
- package/dist/debug/lineage-panel.d.ts +0 -5
- package/dist/debug/lineage-panel.d.ts.map +0 -1
- package/dist/debug/lineage.d.ts +0 -79
- package/dist/debug/lineage.d.ts.map +0 -1
- package/dist/src/async/browser.d.ts +0 -2
- package/dist/src/async/browser.d.ts.map +0 -1
- package/dist/src/async/index.d.ts +0 -19
- package/dist/src/async/index.d.ts.map +0 -1
- package/dist/src/async/node.d.ts +0 -2
- package/dist/src/async/node.d.ts.map +0 -1
- package/dist/src/decorator.d.ts +0 -106
- package/dist/src/decorator.d.ts.map +0 -1
- package/dist/src/destroyable.d.ts +0 -87
- package/dist/src/destroyable.d.ts.map +0 -1
- package/dist/src/entry-browser.d.ts +0 -3
- package/dist/src/entry-browser.d.ts.map +0 -1
- package/dist/src/entry-node.d.ts +0 -3
- package/dist/src/entry-node.d.ts.map +0 -1
- package/dist/src/eventful.d.ts +0 -20
- package/dist/src/eventful.d.ts.map +0 -1
- package/dist/src/flavored.d.ts +0 -33
- package/dist/src/flavored.d.ts.map +0 -1
- package/dist/src/index.d.ts +0 -14
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/indexable.d.ts +0 -243
- package/dist/src/indexable.d.ts.map +0 -1
- package/dist/src/introspection.d.ts +0 -27
- package/dist/src/introspection.d.ts.map +0 -1
- package/dist/src/iterableWeak.d.ts +0 -53
- package/dist/src/iterableWeak.d.ts.map +0 -1
- package/dist/src/mixins.d.ts +0 -25
- package/dist/src/mixins.d.ts.map +0 -1
- package/dist/src/promiseChain.d.ts +0 -20
- package/dist/src/promiseChain.d.ts.map +0 -1
- package/dist/src/reactive/array.d.ts +0 -48
- package/dist/src/reactive/array.d.ts.map +0 -1
- package/dist/src/reactive/buffer.d.ts +0 -120
- package/dist/src/reactive/buffer.d.ts.map +0 -1
- package/dist/src/reactive/change.d.ts +0 -29
- package/dist/src/reactive/change.d.ts.map +0 -1
- package/dist/src/reactive/deep-touch.d.ts +0 -28
- package/dist/src/reactive/deep-touch.d.ts.map +0 -1
- package/dist/src/reactive/deep-watch-state.d.ts +0 -25
- package/dist/src/reactive/deep-watch-state.d.ts.map +0 -1
- package/dist/src/reactive/deep-watch.d.ts +0 -20
- package/dist/src/reactive/deep-watch.d.ts.map +0 -1
- package/dist/src/reactive/describe.d.ts +0 -12
- package/dist/src/reactive/describe.d.ts.map +0 -1
- package/dist/src/reactive/effect-context.d.ts +0 -34
- package/dist/src/reactive/effect-context.d.ts.map +0 -1
- package/dist/src/reactive/effects.d.ts +0 -164
- package/dist/src/reactive/effects.d.ts.map +0 -1
- package/dist/src/reactive/index.d.ts +0 -19
- package/dist/src/reactive/index.d.ts.map +0 -1
- package/dist/src/reactive/map.d.ts +0 -28
- package/dist/src/reactive/map.d.ts.map +0 -1
- package/dist/src/reactive/memoize.d.ts +0 -28
- package/dist/src/reactive/memoize.d.ts.map +0 -1
- package/dist/src/reactive/non-reactive-state.d.ts +0 -9
- package/dist/src/reactive/non-reactive-state.d.ts.map +0 -1
- package/dist/src/reactive/non-reactive.d.ts +0 -11
- package/dist/src/reactive/non-reactive.d.ts.map +0 -1
- package/dist/src/reactive/project.d.ts +0 -40
- package/dist/src/reactive/project.d.ts.map +0 -1
- package/dist/src/reactive/proxy-state.d.ts +0 -8
- package/dist/src/reactive/proxy-state.d.ts.map +0 -1
- package/dist/src/reactive/proxy.d.ts +0 -23
- package/dist/src/reactive/proxy.d.ts.map +0 -1
- package/dist/src/reactive/record.d.ts +0 -115
- package/dist/src/reactive/record.d.ts.map +0 -1
- package/dist/src/reactive/register.d.ts +0 -125
- package/dist/src/reactive/register.d.ts.map +0 -1
- package/dist/src/reactive/registry.d.ts +0 -21
- package/dist/src/reactive/registry.d.ts.map +0 -1
- package/dist/src/reactive/set.d.ts +0 -26
- package/dist/src/reactive/set.d.ts.map +0 -1
- package/dist/src/reactive/tracking.d.ts +0 -7
- package/dist/src/reactive/tracking.d.ts.map +0 -1
- package/dist/src/reactive/types.d.ts +0 -424
- package/dist/src/reactive/types.d.ts.map +0 -1
- package/dist/src/reactive/watch.d.ts +0 -48
- package/dist/src/reactive/watch.d.ts.map +0 -1
- package/dist/src/std-decorators.d.ts +0 -45
- package/dist/src/std-decorators.d.ts.map +0 -1
- package/dist/src/utils.d.ts +0 -49
- package/dist/src/utils.d.ts.map +0 -1
- package/dist/src/zone.d.ts +0 -40
- package/dist/src/zone.d.ts.map +0 -1
- package/docs/reactive/describe.md +0 -85
- package/docs/reactive/project.md +0 -93
- package/docs/reactive/scan.md +0 -293
- package/src/async/browser.ts +0 -323
- package/src/async/index.ts +0 -27
- package/src/async/node.ts +0 -92
- package/src/decorator.ts +0 -272
- package/src/destroyable.ts +0 -199
- package/src/entry-browser.ts +0 -5
- package/src/entry-node.ts +0 -5
- package/src/eventful.ts +0 -110
- package/src/flavored.ts +0 -106
- package/src/index.d.ts +0 -12
- package/src/index.ts +0 -64
- package/src/indexable.ts +0 -526
- package/src/introspection.ts +0 -59
- package/src/iterableWeak.ts +0 -233
- package/src/mixins.ts +0 -123
- package/src/promiseChain.ts +0 -110
- package/src/reactive/array.ts +0 -500
- package/src/reactive/buffer.ts +0 -328
- package/src/reactive/change.ts +0 -131
- package/src/reactive/deep-touch.ts +0 -273
- package/src/reactive/deep-watch-state.ts +0 -82
- package/src/reactive/deep-watch.ts +0 -171
- package/src/reactive/describe.ts +0 -39
- package/src/reactive/effect-context.ts +0 -83
- package/src/reactive/effects.ts +0 -1434
- package/src/reactive/index.ts +0 -72
- package/src/reactive/map.ts +0 -142
- package/src/reactive/memoize.ts +0 -186
- package/src/reactive/non-reactive-state.ts +0 -49
- package/src/reactive/non-reactive.ts +0 -43
- package/src/reactive/project.md +0 -107
- package/src/reactive/project.ts +0 -430
- package/src/reactive/proxy-state.ts +0 -27
- package/src/reactive/proxy.ts +0 -282
- package/src/reactive/record.ts +0 -181
- package/src/reactive/register.ts +0 -538
- package/src/reactive/registry.ts +0 -72
- package/src/reactive/set.ts +0 -117
- package/src/reactive/tracking.ts +0 -41
- package/src/reactive/types.ts +0 -520
- package/src/reactive/watch.ts +0 -180
- package/src/std-decorators.ts +0 -256
- package/src/utils.ts +0 -300
- package/src/zone.ts +0 -142
|
@@ -1,85 +0,0 @@
|
|
|
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
|
-
```
|
package/docs/reactive/project.md
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# Reactive Register Memoization Notes
|
|
2
|
-
|
|
3
|
-
## Background
|
|
4
|
-
|
|
5
|
-
- Register entries are stored in a reactive `Map`. Updating an entry via `Map.set` marks the entire value as changed.
|
|
6
|
-
- Memoized computations that read through `map.get(key)` re-execute fully when the entry changes, even if only a nested property is touched.
|
|
7
|
-
- The goal for rendering lists in a JSX/HTML engine is to avoid rebuilding DOM nodes; only the affected properties should update.
|
|
8
|
-
|
|
9
|
-
## Evolution: From `organized` to `project`
|
|
10
|
-
|
|
11
|
-
### Initial State
|
|
12
|
-
|
|
13
|
-
- `memoize` caches results but invalidates on `Map.set`, so large effects still re-run.
|
|
14
|
-
- `organized` (designed for `Record` sources) creates per-key effects so downstream work reruns only for the touched key; this matches the desired behaviour.
|
|
15
|
-
- **Gap:** `organized` operates on plain objects: key enumeration relies on property iteration and `FoolProof.get/set`. Registers and other keyed collections (`Map`, `Register`, custom stores) need the same per-entry orchestration without converting to records.
|
|
16
|
-
|
|
17
|
-
### Completed Evolution: `project` Implementation
|
|
18
|
-
|
|
19
|
-
We implemented `project` as a generalized transformation helper that works across arrays, records, and maps:
|
|
20
|
-
|
|
21
|
-
**Key Design Decisions:**
|
|
22
|
-
- **Unified API:** Single `project` function with runtime dispatch to `project.array`, `project.record`, or `project.map` based on source type.
|
|
23
|
-
- **Access Pattern:** Callback receives a `ProjectAccess` object with `get()`, `set()`, `key`, `source`, and `value` (computed property) - similar to `organized` but returning a value instead of an effect.
|
|
24
|
-
- **Automatic Target Creation:** The function always creates its own reactive target container (array, record, or map) - no `baseTarget` parameter needed.
|
|
25
|
-
- **Per-Key Effects:** Each source key/index gets its own reactive effect that recomputes only when that specific entry changes, enabling granular updates for rendering pipelines.
|
|
26
|
-
|
|
27
|
-
**Current API:**
|
|
28
|
-
```typescript
|
|
29
|
-
project.array(source: readonly T[], apply: (access, target) => U): ProjectResult<U[]>
|
|
30
|
-
project.record(source: Record<K, T>, apply: (access, target) => U): ProjectResult<Record<K, U>>
|
|
31
|
-
project.map(source: Map<K, T>, apply: (access, target) => U): ProjectResult<Map<K, U>>
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
**Current Behavior:**
|
|
35
|
-
- Eager computation: all entries are computed immediately when keys are present.
|
|
36
|
-
- One-way transformation: callback only handles "get" (read) operations; no write-back support.
|
|
37
|
-
- Mutable results: returned arrays/records/maps are fully mutable.
|
|
38
|
-
- `ProjectAccess.old` exposes the previously computed result for each entry, enabling incremental updates and state preservation.
|
|
39
|
-
|
|
40
|
-
## Future Evolutions
|
|
41
|
-
|
|
42
|
-
### Bidirectional Transformation (Set Callback)
|
|
43
|
-
|
|
44
|
-
**Goal:** Support writing back to the source through the projected object.
|
|
45
|
-
|
|
46
|
-
**Implementation:**
|
|
47
|
-
- Add optional second callback parameter: `set: (access, newValue, target) => void | boolean`
|
|
48
|
-
- When `set` is provided, mutations to the projected object trigger the set callback.
|
|
49
|
-
- The callback receives the same `ProjectAccess` object plus the new value, allowing it to update the source.
|
|
50
|
-
- If `set` returns `false` or throws, the mutation is rejected.
|
|
51
|
-
|
|
52
|
-
**API Impact:**
|
|
53
|
-
```typescript
|
|
54
|
-
project.array(
|
|
55
|
-
source: readonly T[],
|
|
56
|
-
apply: (access, target) => U,
|
|
57
|
-
set?: (access, newValue: U, target) => void | boolean
|
|
58
|
-
): ProjectResult<U[]>
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Readonly Results When No Set Callback
|
|
62
|
-
|
|
63
|
-
**Goal:** Make projected objects readonly when no write-back is supported.
|
|
64
|
-
|
|
65
|
-
**Implementation:**
|
|
66
|
-
- When `set` callback is not provided, wrap the result in a readonly proxy or use read-only array/record types.
|
|
67
|
-
- Prevents accidental mutations that would have no effect on the source.
|
|
68
|
-
- Type system should reflect readonly nature in return types.
|
|
69
|
-
|
|
70
|
-
**API Impact:**
|
|
71
|
-
- Return type becomes `ProjectResult<readonly U[]>` or similar when `set` is omitted.
|
|
72
|
-
- Arrays use `ReactiveReadOnlyArray` (already exists in `mapped.ts`).
|
|
73
|
-
- Records and maps need readonly wrappers or proxy-based protection.
|
|
74
|
-
|
|
75
|
-
## Implementation Notes for AI Agents
|
|
76
|
-
|
|
77
|
-
**Current State:**
|
|
78
|
-
- `project` is fully functional for arrays, records, maps, and registers with eager, one-way transformation.
|
|
79
|
-
- Tests cover per-key reactivity, key addition/removal, automatic helper selection, and `access.old` value propagation.
|
|
80
|
-
- The implementation follows the same per-key effect pattern as `organized` but returns computed values.
|
|
81
|
-
|
|
82
|
-
**Future Work:**
|
|
83
|
-
- Lazy computing requires careful effect lifecycle management to avoid memory leaks.
|
|
84
|
-
- Bidirectional support needs to handle edge cases (concurrent reads/writes, validation).
|
|
85
|
-
- Readonly enforcement should align with existing `ReactiveReadOnlyArray` patterns where possible.
|
|
86
|
-
- Consider whether these features should be opt-in via options object or separate function variants.
|
|
87
|
-
|
|
88
|
-
**Related Files:**
|
|
89
|
-
- `src/reactive/project.ts` - Main implementation
|
|
90
|
-
- `src/reactive/array.ts` - Reference for `ReactiveReadOnlyArray` pattern
|
|
91
|
-
- `src/reactive/record.ts` - Reference for `organized` pattern (different use case)
|
|
92
|
-
- `tests/reactive/project.test.ts` - Test coverage
|
|
93
|
-
|
package/docs/reactive/scan.md
DELETED
|
@@ -1,293 +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 "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.
|
|
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
|
-
## 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
|