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.
Files changed (191) hide show
  1. package/README.md +72 -17
  2. package/dist/browser.cjs +3111 -0
  3. package/dist/browser.cjs.map +1 -0
  4. package/dist/browser.d.ts +1391 -1
  5. package/dist/browser.esm.js +264 -35
  6. package/dist/browser.esm.js.map +1 -1
  7. package/dist/chunks/async-browser-lvzLOCgk.cjs +297 -0
  8. package/dist/chunks/async-browser-lvzLOCgk.cjs.map +1 -0
  9. package/dist/chunks/async-core-CRLKP3l-.cjs +29 -0
  10. package/dist/chunks/async-core-CRLKP3l-.cjs.map +1 -0
  11. package/dist/chunks/async-node-C3DeIb0y.cjs +96 -0
  12. package/dist/chunks/async-node-C3DeIb0y.cjs.map +1 -0
  13. package/dist/chunks/index-2vea86wD.esm.js +3011 -0
  14. package/dist/chunks/index-2vea86wD.esm.js.map +1 -0
  15. package/dist/chunks/index-CtA2AWl3.esm.js +4376 -0
  16. package/dist/chunks/index-CtA2AWl3.esm.js.map +1 -0
  17. package/dist/chunks/index-VTO-b2vR.cjs +4461 -0
  18. package/dist/chunks/index-VTO-b2vR.cjs.map +1 -0
  19. package/dist/debug/debug.d.ts +122 -0
  20. package/dist/debug/debug.d.ts.map +1 -0
  21. package/dist/debug/index.d.ts +4 -0
  22. package/dist/debug/index.d.ts.map +1 -0
  23. package/dist/debug/lineage-panel.d.ts +5 -0
  24. package/dist/debug/lineage-panel.d.ts.map +1 -0
  25. package/dist/debug/lineage.d.ts +79 -0
  26. package/dist/debug/lineage.d.ts.map +1 -0
  27. package/dist/debug.cjs +35 -0
  28. package/dist/debug.cjs.map +1 -0
  29. package/dist/debug.d.ts +209 -0
  30. package/dist/debug.esm.js +2 -0
  31. package/dist/debug.esm.js.map +1 -0
  32. package/dist/devtools/devtool/devtools.d.ts +1 -0
  33. package/dist/devtools/devtool/devtools.d.ts.map +1 -0
  34. package/dist/devtools/devtool/panel.d.ts +2 -0
  35. package/dist/devtools/devtool/panel.d.ts.map +1 -0
  36. package/dist/index.d.ts +1 -0
  37. package/dist/mutts.umd.js +1 -1
  38. package/dist/mutts.umd.js.map +1 -1
  39. package/dist/mutts.umd.min.js +1 -1
  40. package/dist/mutts.umd.min.js.map +1 -1
  41. package/dist/node.cjs +107 -0
  42. package/dist/node.cjs.map +1 -0
  43. package/dist/node.d.ts +2 -2
  44. package/dist/node.esm.js +86 -33
  45. package/dist/node.esm.js.map +1 -1
  46. package/dist/src/async/browser.d.ts +2 -0
  47. package/dist/src/async/browser.d.ts.map +1 -0
  48. package/dist/src/async/index.d.ts +19 -0
  49. package/dist/src/async/index.d.ts.map +1 -0
  50. package/dist/src/async/node.d.ts +2 -0
  51. package/dist/src/async/node.d.ts.map +1 -0
  52. package/dist/src/decorator.d.ts +106 -0
  53. package/dist/src/decorator.d.ts.map +1 -0
  54. package/dist/src/destroyable.d.ts +87 -0
  55. package/dist/src/destroyable.d.ts.map +1 -0
  56. package/dist/src/entry-browser.d.ts +3 -0
  57. package/dist/src/entry-browser.d.ts.map +1 -0
  58. package/dist/src/entry-node.d.ts +3 -0
  59. package/dist/src/entry-node.d.ts.map +1 -0
  60. package/dist/src/eventful.d.ts +20 -0
  61. package/dist/src/eventful.d.ts.map +1 -0
  62. package/dist/src/flavored.d.ts +33 -0
  63. package/dist/src/flavored.d.ts.map +1 -0
  64. package/dist/src/index.d.ts +14 -0
  65. package/dist/src/index.d.ts.map +1 -0
  66. package/dist/src/indexable.d.ts +243 -0
  67. package/dist/src/indexable.d.ts.map +1 -0
  68. package/dist/src/introspection.d.ts +27 -0
  69. package/dist/src/introspection.d.ts.map +1 -0
  70. package/dist/src/iterableWeak.d.ts +53 -0
  71. package/dist/src/iterableWeak.d.ts.map +1 -0
  72. package/dist/src/mixins.d.ts +25 -0
  73. package/dist/src/mixins.d.ts.map +1 -0
  74. package/dist/src/promiseChain.d.ts +20 -0
  75. package/dist/src/promiseChain.d.ts.map +1 -0
  76. package/dist/src/reactive/array.d.ts +48 -0
  77. package/dist/src/reactive/array.d.ts.map +1 -0
  78. package/dist/src/reactive/buffer.d.ts +120 -0
  79. package/dist/src/reactive/buffer.d.ts.map +1 -0
  80. package/dist/src/reactive/change.d.ts +29 -0
  81. package/dist/src/reactive/change.d.ts.map +1 -0
  82. package/dist/src/reactive/deep-touch.d.ts +28 -0
  83. package/dist/src/reactive/deep-touch.d.ts.map +1 -0
  84. package/dist/src/reactive/deep-watch-state.d.ts +25 -0
  85. package/dist/src/reactive/deep-watch-state.d.ts.map +1 -0
  86. package/dist/src/reactive/deep-watch.d.ts +20 -0
  87. package/dist/src/reactive/deep-watch.d.ts.map +1 -0
  88. package/dist/src/reactive/describe.d.ts +12 -0
  89. package/dist/src/reactive/describe.d.ts.map +1 -0
  90. package/dist/src/reactive/effect-context.d.ts +34 -0
  91. package/dist/src/reactive/effect-context.d.ts.map +1 -0
  92. package/dist/src/reactive/effects.d.ts +164 -0
  93. package/dist/src/reactive/effects.d.ts.map +1 -0
  94. package/dist/src/reactive/index.d.ts +19 -0
  95. package/dist/src/reactive/index.d.ts.map +1 -0
  96. package/dist/src/reactive/map.d.ts +28 -0
  97. package/dist/src/reactive/map.d.ts.map +1 -0
  98. package/dist/src/reactive/memoize.d.ts +28 -0
  99. package/dist/src/reactive/memoize.d.ts.map +1 -0
  100. package/dist/src/reactive/non-reactive-state.d.ts +9 -0
  101. package/dist/src/reactive/non-reactive-state.d.ts.map +1 -0
  102. package/dist/src/reactive/non-reactive.d.ts +11 -0
  103. package/dist/src/reactive/non-reactive.d.ts.map +1 -0
  104. package/dist/src/reactive/project.d.ts +40 -0
  105. package/dist/src/reactive/project.d.ts.map +1 -0
  106. package/dist/src/reactive/proxy-state.d.ts +8 -0
  107. package/dist/src/reactive/proxy-state.d.ts.map +1 -0
  108. package/dist/src/reactive/proxy.d.ts +23 -0
  109. package/dist/src/reactive/proxy.d.ts.map +1 -0
  110. package/dist/src/reactive/record.d.ts +115 -0
  111. package/dist/src/reactive/record.d.ts.map +1 -0
  112. package/dist/src/reactive/register.d.ts +125 -0
  113. package/dist/src/reactive/register.d.ts.map +1 -0
  114. package/dist/src/reactive/registry.d.ts +21 -0
  115. package/dist/src/reactive/registry.d.ts.map +1 -0
  116. package/dist/src/reactive/set.d.ts +26 -0
  117. package/dist/src/reactive/set.d.ts.map +1 -0
  118. package/dist/src/reactive/tracking.d.ts +7 -0
  119. package/dist/src/reactive/tracking.d.ts.map +1 -0
  120. package/dist/src/reactive/types.d.ts +424 -0
  121. package/dist/src/reactive/types.d.ts.map +1 -0
  122. package/dist/src/reactive/watch.d.ts +48 -0
  123. package/dist/src/reactive/watch.d.ts.map +1 -0
  124. package/dist/src/std-decorators.d.ts +45 -0
  125. package/dist/src/std-decorators.d.ts.map +1 -0
  126. package/dist/src/utils.d.ts +49 -0
  127. package/dist/src/utils.d.ts.map +1 -0
  128. package/dist/src/zone.d.ts +40 -0
  129. package/dist/src/zone.d.ts.map +1 -0
  130. package/dist/types-DaHFfhlN.d.ts +414 -0
  131. package/docs/ai/api-reference.md +1 -1
  132. package/docs/ai/manual.md +443 -17
  133. package/docs/events.md +57 -2
  134. package/docs/flavored.md +210 -0
  135. package/docs/reactive/advanced.md +2 -3
  136. package/docs/reactive/attend.md +143 -0
  137. package/docs/reactive/collections.md +55 -0
  138. package/docs/reactive/core.md +120 -4
  139. package/docs/reactive/debugging.md +8 -10
  140. package/docs/reactive/describe.md +85 -0
  141. package/docs/reactive/error-handling.md +121 -0
  142. package/docs/reactive/project.md +1 -1
  143. package/docs/reactive/scan.md +215 -0
  144. package/docs/reactive.md +2 -0
  145. package/docs/std-decorators.md +171 -52
  146. package/docs/zone.md +9 -6
  147. package/package.json +44 -27
  148. package/src/async/browser.ts +277 -41
  149. package/src/async/index.ts +23 -4
  150. package/src/async/node.ts +78 -32
  151. package/src/entry-browser.ts +5 -0
  152. package/src/entry-node.ts +5 -0
  153. package/src/eventful.ts +82 -49
  154. package/src/flavored.ts +106 -0
  155. package/src/index.d.ts +12 -9
  156. package/src/index.ts +2 -1
  157. package/src/introspection.ts +1 -1
  158. package/src/reactive/array.ts +160 -80
  159. package/src/reactive/buffer.ts +187 -27
  160. package/src/reactive/change.ts +16 -23
  161. package/src/reactive/deep-touch.ts +19 -14
  162. package/src/reactive/deep-watch-state.ts +3 -3
  163. package/src/reactive/deep-watch.ts +7 -4
  164. package/src/reactive/describe.ts +39 -0
  165. package/src/reactive/effect-context.ts +71 -6
  166. package/src/reactive/effects.ts +445 -393
  167. package/src/reactive/index.ts +12 -14
  168. package/src/reactive/map.ts +8 -24
  169. package/src/reactive/memoize.ts +26 -3
  170. package/src/reactive/project.md +107 -0
  171. package/src/reactive/project.ts +48 -71
  172. package/src/reactive/proxy.ts +21 -3
  173. package/src/reactive/record.ts +30 -45
  174. package/src/reactive/register.ts +138 -19
  175. package/src/reactive/registry.ts +22 -9
  176. package/src/reactive/set.ts +7 -20
  177. package/src/reactive/tracking.ts +9 -12
  178. package/src/reactive/types.ts +104 -24
  179. package/src/reactive/{interface.ts → watch.ts} +29 -59
  180. package/src/std-decorators.ts +56 -17
  181. package/src/utils.ts +3 -4
  182. package/src/zone.ts +43 -28
  183. package/dist/browser.js +0 -161
  184. package/dist/browser.js.map +0 -1
  185. package/dist/chunks/index-BFYK02LG.js +0 -6378
  186. package/dist/chunks/index-BFYK02LG.js.map +0 -1
  187. package/dist/chunks/index-CNR6QRUl.esm.js +0 -6291
  188. package/dist/chunks/index-CNR6QRUl.esm.js.map +0 -1
  189. package/dist/node.js +0 -136
  190. package/dist/node.js.map +0 -1
  191. 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
@@ -87,7 +87,7 @@ project.array(
87
87
 
88
88
  **Related Files:**
89
89
  - `src/reactive/project.ts` - Main implementation
90
- - `src/reactive/mapped.ts` - Reference for `ReactiveReadOnlyArray` pattern
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
 
@@ -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)