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.
Files changed (206) hide show
  1. package/README.md +60 -35
  2. package/dist/browser.cjs +1333 -1760
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.ts +2 -1392
  5. package/dist/browser.dev.cjs +114 -0
  6. package/dist/browser.dev.cjs.map +1 -0
  7. package/dist/browser.dev.d.ts +2 -0
  8. package/dist/browser.dev.esm.js +5 -0
  9. package/dist/browser.dev.esm.js.map +1 -0
  10. package/dist/browser.esm.js +18 -97
  11. package/dist/browser.esm.js.map +1 -1
  12. package/dist/chunks/{async-browser-lvzLOCgk.cjs → async-browser-Dgr5CreQ.cjs} +16 -95
  13. package/dist/chunks/async-browser-Dgr5CreQ.cjs.map +1 -0
  14. package/dist/chunks/{async-node-C3DeIb0y.cjs → async-node-3PrbVAbB.cjs} +3 -1
  15. package/dist/chunks/async-node-3PrbVAbB.cjs.map +1 -0
  16. package/dist/chunks/index-Sf74wXTV.esm.js +2577 -0
  17. package/dist/chunks/index-Sf74wXTV.esm.js.map +1 -0
  18. package/dist/chunks/node-Bo7WU5S2.esm.js +96 -0
  19. package/dist/chunks/node-Bo7WU5S2.esm.js.map +1 -0
  20. package/dist/chunks/{index-VTO-b2vR.cjs → proxy-Cc79Lrzj.cjs} +2593 -3216
  21. package/dist/chunks/proxy-Cc79Lrzj.cjs.map +1 -0
  22. package/dist/chunks/{index-CtA2AWl3.esm.js → proxy-D2C49sXH.esm.js} +2570 -3174
  23. package/dist/chunks/proxy-D2C49sXH.esm.js.map +1 -0
  24. package/dist/debug.cjs +987 -28
  25. package/dist/debug.cjs.map +1 -1
  26. package/dist/debug.d.ts +37 -38
  27. package/dist/debug.esm.js +963 -1
  28. package/dist/debug.esm.js.map +1 -1
  29. package/dist/devtools/manifest.json +1 -1
  30. package/dist/devtools/panel.html +1 -1
  31. package/dist/devtools/panel.js +107 -94
  32. package/dist/devtools/panel.js.map +1 -1
  33. package/dist/index.d.ts +1322 -1
  34. package/dist/mutts.umd.js +6647 -1
  35. package/dist/mutts.umd.js.map +1 -1
  36. package/dist/mutts.umd.min.js +1 -1
  37. package/dist/mutts.umd.min.js.map +1 -1
  38. package/dist/node.cjs +69 -63
  39. package/dist/node.cjs.map +1 -1
  40. package/dist/node.d.ts +2 -2
  41. package/dist/node.dev.cjs +114 -0
  42. package/dist/node.dev.cjs.map +1 -0
  43. package/dist/node.dev.d.ts +2 -0
  44. package/dist/node.dev.esm.js +6 -0
  45. package/dist/node.dev.esm.js.map +1 -0
  46. package/dist/node.esm.js +4 -97
  47. package/dist/node.esm.js.map +1 -1
  48. package/dist/{types-DaHFfhlN.d.ts → types-Bx2PhORg.d.ts} +134 -88
  49. package/docs/ai/api-reference.md +11 -32
  50. package/docs/ai/manual.md +297 -239
  51. package/docs/reactive/advanced.md +318 -9
  52. package/docs/reactive/attend.md +2 -4
  53. package/docs/reactive/collections.md +22 -187
  54. package/docs/reactive/core.md +223 -131
  55. package/docs/reactive/debugging.md +119 -12
  56. package/docs/reactive/error-handling.md +10 -10
  57. package/docs/reactive/resource.md +125 -0
  58. package/docs/reactive.md +3 -4
  59. package/docs/utils.md +70 -0
  60. package/docs/zone.md +1 -1
  61. package/package.json +76 -38
  62. package/dist/chunks/async-browser-lvzLOCgk.cjs.map +0 -1
  63. package/dist/chunks/async-node-C3DeIb0y.cjs.map +0 -1
  64. package/dist/chunks/index-2vea86wD.esm.js +0 -3011
  65. package/dist/chunks/index-2vea86wD.esm.js.map +0 -1
  66. package/dist/chunks/index-CtA2AWl3.esm.js.map +0 -1
  67. package/dist/chunks/index-VTO-b2vR.cjs.map +0 -1
  68. package/dist/debug/debug.d.ts +0 -122
  69. package/dist/debug/debug.d.ts.map +0 -1
  70. package/dist/debug/index.d.ts +0 -4
  71. package/dist/debug/index.d.ts.map +0 -1
  72. package/dist/debug/lineage-panel.d.ts +0 -5
  73. package/dist/debug/lineage-panel.d.ts.map +0 -1
  74. package/dist/debug/lineage.d.ts +0 -79
  75. package/dist/debug/lineage.d.ts.map +0 -1
  76. package/dist/src/async/browser.d.ts +0 -2
  77. package/dist/src/async/browser.d.ts.map +0 -1
  78. package/dist/src/async/index.d.ts +0 -19
  79. package/dist/src/async/index.d.ts.map +0 -1
  80. package/dist/src/async/node.d.ts +0 -2
  81. package/dist/src/async/node.d.ts.map +0 -1
  82. package/dist/src/decorator.d.ts +0 -106
  83. package/dist/src/decorator.d.ts.map +0 -1
  84. package/dist/src/destroyable.d.ts +0 -87
  85. package/dist/src/destroyable.d.ts.map +0 -1
  86. package/dist/src/entry-browser.d.ts +0 -3
  87. package/dist/src/entry-browser.d.ts.map +0 -1
  88. package/dist/src/entry-node.d.ts +0 -3
  89. package/dist/src/entry-node.d.ts.map +0 -1
  90. package/dist/src/eventful.d.ts +0 -20
  91. package/dist/src/eventful.d.ts.map +0 -1
  92. package/dist/src/flavored.d.ts +0 -33
  93. package/dist/src/flavored.d.ts.map +0 -1
  94. package/dist/src/index.d.ts +0 -14
  95. package/dist/src/index.d.ts.map +0 -1
  96. package/dist/src/indexable.d.ts +0 -243
  97. package/dist/src/indexable.d.ts.map +0 -1
  98. package/dist/src/introspection.d.ts +0 -27
  99. package/dist/src/introspection.d.ts.map +0 -1
  100. package/dist/src/iterableWeak.d.ts +0 -53
  101. package/dist/src/iterableWeak.d.ts.map +0 -1
  102. package/dist/src/mixins.d.ts +0 -25
  103. package/dist/src/mixins.d.ts.map +0 -1
  104. package/dist/src/promiseChain.d.ts +0 -20
  105. package/dist/src/promiseChain.d.ts.map +0 -1
  106. package/dist/src/reactive/array.d.ts +0 -48
  107. package/dist/src/reactive/array.d.ts.map +0 -1
  108. package/dist/src/reactive/buffer.d.ts +0 -120
  109. package/dist/src/reactive/buffer.d.ts.map +0 -1
  110. package/dist/src/reactive/change.d.ts +0 -29
  111. package/dist/src/reactive/change.d.ts.map +0 -1
  112. package/dist/src/reactive/deep-touch.d.ts +0 -28
  113. package/dist/src/reactive/deep-touch.d.ts.map +0 -1
  114. package/dist/src/reactive/deep-watch-state.d.ts +0 -25
  115. package/dist/src/reactive/deep-watch-state.d.ts.map +0 -1
  116. package/dist/src/reactive/deep-watch.d.ts +0 -20
  117. package/dist/src/reactive/deep-watch.d.ts.map +0 -1
  118. package/dist/src/reactive/describe.d.ts +0 -12
  119. package/dist/src/reactive/describe.d.ts.map +0 -1
  120. package/dist/src/reactive/effect-context.d.ts +0 -34
  121. package/dist/src/reactive/effect-context.d.ts.map +0 -1
  122. package/dist/src/reactive/effects.d.ts +0 -164
  123. package/dist/src/reactive/effects.d.ts.map +0 -1
  124. package/dist/src/reactive/index.d.ts +0 -19
  125. package/dist/src/reactive/index.d.ts.map +0 -1
  126. package/dist/src/reactive/map.d.ts +0 -28
  127. package/dist/src/reactive/map.d.ts.map +0 -1
  128. package/dist/src/reactive/memoize.d.ts +0 -28
  129. package/dist/src/reactive/memoize.d.ts.map +0 -1
  130. package/dist/src/reactive/non-reactive-state.d.ts +0 -9
  131. package/dist/src/reactive/non-reactive-state.d.ts.map +0 -1
  132. package/dist/src/reactive/non-reactive.d.ts +0 -11
  133. package/dist/src/reactive/non-reactive.d.ts.map +0 -1
  134. package/dist/src/reactive/project.d.ts +0 -40
  135. package/dist/src/reactive/project.d.ts.map +0 -1
  136. package/dist/src/reactive/proxy-state.d.ts +0 -8
  137. package/dist/src/reactive/proxy-state.d.ts.map +0 -1
  138. package/dist/src/reactive/proxy.d.ts +0 -23
  139. package/dist/src/reactive/proxy.d.ts.map +0 -1
  140. package/dist/src/reactive/record.d.ts +0 -115
  141. package/dist/src/reactive/record.d.ts.map +0 -1
  142. package/dist/src/reactive/register.d.ts +0 -125
  143. package/dist/src/reactive/register.d.ts.map +0 -1
  144. package/dist/src/reactive/registry.d.ts +0 -21
  145. package/dist/src/reactive/registry.d.ts.map +0 -1
  146. package/dist/src/reactive/set.d.ts +0 -26
  147. package/dist/src/reactive/set.d.ts.map +0 -1
  148. package/dist/src/reactive/tracking.d.ts +0 -7
  149. package/dist/src/reactive/tracking.d.ts.map +0 -1
  150. package/dist/src/reactive/types.d.ts +0 -424
  151. package/dist/src/reactive/types.d.ts.map +0 -1
  152. package/dist/src/reactive/watch.d.ts +0 -48
  153. package/dist/src/reactive/watch.d.ts.map +0 -1
  154. package/dist/src/std-decorators.d.ts +0 -45
  155. package/dist/src/std-decorators.d.ts.map +0 -1
  156. package/dist/src/utils.d.ts +0 -49
  157. package/dist/src/utils.d.ts.map +0 -1
  158. package/dist/src/zone.d.ts +0 -40
  159. package/dist/src/zone.d.ts.map +0 -1
  160. package/docs/reactive/describe.md +0 -85
  161. package/docs/reactive/project.md +0 -93
  162. package/docs/reactive/scan.md +0 -293
  163. package/src/async/browser.ts +0 -323
  164. package/src/async/index.ts +0 -27
  165. package/src/async/node.ts +0 -92
  166. package/src/decorator.ts +0 -272
  167. package/src/destroyable.ts +0 -199
  168. package/src/entry-browser.ts +0 -5
  169. package/src/entry-node.ts +0 -5
  170. package/src/eventful.ts +0 -110
  171. package/src/flavored.ts +0 -106
  172. package/src/index.d.ts +0 -12
  173. package/src/index.ts +0 -64
  174. package/src/indexable.ts +0 -526
  175. package/src/introspection.ts +0 -59
  176. package/src/iterableWeak.ts +0 -233
  177. package/src/mixins.ts +0 -123
  178. package/src/promiseChain.ts +0 -110
  179. package/src/reactive/array.ts +0 -500
  180. package/src/reactive/buffer.ts +0 -328
  181. package/src/reactive/change.ts +0 -131
  182. package/src/reactive/deep-touch.ts +0 -273
  183. package/src/reactive/deep-watch-state.ts +0 -82
  184. package/src/reactive/deep-watch.ts +0 -171
  185. package/src/reactive/describe.ts +0 -39
  186. package/src/reactive/effect-context.ts +0 -83
  187. package/src/reactive/effects.ts +0 -1434
  188. package/src/reactive/index.ts +0 -72
  189. package/src/reactive/map.ts +0 -142
  190. package/src/reactive/memoize.ts +0 -186
  191. package/src/reactive/non-reactive-state.ts +0 -49
  192. package/src/reactive/non-reactive.ts +0 -43
  193. package/src/reactive/project.md +0 -107
  194. package/src/reactive/project.ts +0 -430
  195. package/src/reactive/proxy-state.ts +0 -27
  196. package/src/reactive/proxy.ts +0 -282
  197. package/src/reactive/record.ts +0 -181
  198. package/src/reactive/register.ts +0 -538
  199. package/src/reactive/registry.ts +0 -72
  200. package/src/reactive/set.ts +0 -117
  201. package/src/reactive/tracking.ts +0 -41
  202. package/src/reactive/types.ts +0 -520
  203. package/src/reactive/watch.ts +0 -180
  204. package/src/std-decorators.ts +0 -256
  205. package/src/utils.ts +0 -300
  206. 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
- ```
@@ -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
-
@@ -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