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,328 +0,0 @@
1
- import { FoolProof } from '../utils'
2
- import { cleanedBy } from '.'
3
- import { effect, untracked } from './effects'
4
- import { memoize } from './memoize'
5
- import { reactive } from './proxy'
6
- import type { cleanup, EffectAccess, EffectCleanup, EffectCloser, ScopedCallback } from './types'
7
-
8
- /**
9
- * Reactively attends to each entry of a collection or each key yielded by an
10
- * enumeration callback. For each key, an inner effect runs the callback. When a
11
- * key disappears, its inner effect is disposed. The callback may return a cleanup
12
- * (like a regular effect closer).
13
- *
14
- * Accepts arrays, records, Maps, Sets, or a raw `() => Iterable<Key>` callback.
15
- *
16
- * @example
17
- * ```typescript
18
- * // Record shorthand
19
- * attend(record, (key) => { console.log(key, record[key]) })
20
- *
21
- * // Array shorthand
22
- * attend(array, (index) => { console.log(index, array[index]) })
23
- *
24
- * // Raw enumeration callback
25
- * attend(() => Object.keys(record), (key) => { ... })
26
- * ```
27
- */
28
- export function attend<T>(
29
- source: readonly T[],
30
- callback: (index: number) => EffectCloser | void
31
- ): ScopedCallback
32
- export function attend<K, V>(
33
- source: Map<K, V>,
34
- callback: (key: K) => EffectCloser | void
35
- ): ScopedCallback
36
- export function attend<T>(
37
- source: Set<T>,
38
- callback: (value: T) => EffectCloser | void
39
- ): ScopedCallback
40
- export function attend<S extends Record<PropertyKey, any>>(
41
- source: S,
42
- callback: (key: keyof S & string) => EffectCloser | void
43
- ): ScopedCallback
44
- export function attend<Key>(
45
- enumerate: () => Iterable<Key>,
46
- callback: (key: Key) => EffectCloser | void
47
- ): ScopedCallback
48
- export function attend(
49
- source: any,
50
- callback: (key: any) => EffectCloser | void
51
- ): ScopedCallback {
52
- const enumerate: () => Iterable<any> =
53
- typeof source === 'function'
54
- ? source
55
- : Array.isArray(source)
56
- ? () => Array.from({ length: source.length }, (_, i) => i)
57
- : source instanceof Map
58
- ? () => source.keys()
59
- : source instanceof Set
60
- ? () => source.values()
61
- : () => Object.keys(source)
62
-
63
- const keyEffects = new Map<any, ScopedCallback>()
64
-
65
- const outer = effect(({ ascend }) => {
66
- const keys = new Set<any>()
67
- for (const key of enumerate()) keys.add(key)
68
-
69
- for (const key of keys) {
70
- if (keyEffects.has(key)) continue
71
- keyEffects.set(key, ascend(() => effect(() => callback(key))))
72
- }
73
-
74
- for (const key of Array.from(keyEffects.keys())) {
75
- if (!keys.has(key)) {
76
- keyEffects.get(key)!()
77
- keyEffects.delete(key)
78
- }
79
- }
80
- })
81
-
82
- return () => {
83
- outer()
84
- for (const stop of keyEffects.values()) stop()
85
- keyEffects.clear()
86
- }
87
- }
88
-
89
- /**
90
- * Result of a reactive scan, which is a reactive array of accumulated values
91
- * with an attached cleanup function.
92
- */
93
- export type ScanResult<Output> = readonly Output[] & { [cleanup]: ScopedCallback }
94
-
95
- /**
96
- * Perform a reactive scan over an array of items.
97
- *
98
- * This implementation is highly optimized for performance and fine-grained reactivity:
99
- * - **Incremental Updates**: Changes to an item only trigger re-computation from that
100
- * point onwards in the result chain.
101
- * - **Move Optimization**: If items are moved within the array, their accumulated
102
- * values are reused as long as their predecessor remains the same.
103
- * - **Duplicate Support**: Correctly handles multiple occurrences of the same object
104
- * instance using an internal occurrence tracking mechanism.
105
- * - **Memory Efficient**: Uses `WeakMap` for caching intermediates, which are
106
- * automatically cleared when source items are garbage collected.
107
- *
108
- * @example
109
- * ```typescript
110
- * const source = reactive([{ val: 1 }, { val: 2 }, { val: 3 }])
111
- * const sum = scan(source, (acc, item) => acc + item.val, 0)
112
- *
113
- * expect([...sum]).toEqual([1, 3, 6])
114
- *
115
- * // Modifying an item only re-computes subsequent sums
116
- * source[1].val = 10
117
- * expect([...sum]).toEqual([1, 11, 14])
118
- * ```
119
- *
120
- * @param source The source array of objects (will be made reactive)
121
- * @param callback The accumulator function called with (accumulator, currentItem)
122
- * @param initialValue The starting value for the accumulation
123
- * @returns A reactive array of accumulated values, with a [cleanup] property to stop the tracking
124
- */
125
- export function scan<Input extends object, Output>(
126
- source: readonly Input[],
127
- callback: (acc: Output, val: Input) => Output,
128
- initialValue: Output
129
- ): ScanResult<Output> {
130
- const observedSource = reactive(source)
131
- const result = reactive([] as Output[])
132
-
133
- // Track effects for each index to dispose them when the array shrinks
134
- const indexEffects = new Map<number, EffectCleanup>()
135
- // Mapping from index to its current intermediate object
136
- const indexToIntermediate = reactive([] as Intermediate[])
137
- const intermediaries = new WeakMap<Input, Intermediate[]>()
138
-
139
- class Intermediate {
140
- public prev: Intermediate | undefined
141
- constructor(
142
- public val: Input,
143
- prev: Intermediate | undefined
144
- ) {
145
- this.prev = prev
146
- }
147
-
148
- @memoize
149
- get acc(): Output {
150
- const prevAcc = this.prev ? this.prev.acc : initialValue
151
- return callback(prevAcc, this.val)
152
- }
153
- }
154
-
155
- function disposeIndex(index: number) {
156
- const stop = indexEffects.get(index)
157
- if (stop) {
158
- stop()
159
- indexEffects.delete(index)
160
- untracked(() => {
161
- Reflect.deleteProperty(indexToIntermediate as any, index)
162
- Reflect.deleteProperty(result as any, index)
163
- })
164
- }
165
- }
166
-
167
- const mainEffect = effect(function scanMainEffect({ ascend }) {
168
- const length = observedSource.length
169
- const occurrenceCount = new Map<Input, number>()
170
- let prev: Intermediate | undefined
171
-
172
- for (let i = 0; i < length; i++) {
173
- const val = FoolProof.get(observedSource as any, i, observedSource) as Input
174
-
175
- if (
176
- !(val && (typeof val === 'object' || typeof val === 'function' || typeof val === 'symbol'))
177
- ) {
178
- throw new Error('scan: items must be objects (WeakKey) for intermediate caching')
179
- }
180
-
181
- const count = occurrenceCount.get(val) ?? 0
182
- occurrenceCount.set(val, count + 1)
183
-
184
- let list = intermediaries.get(val)
185
- if (!list) {
186
- list = []
187
- intermediaries.set(val, list)
188
- }
189
-
190
- let intermediate = list[count]
191
- if (!intermediate) {
192
- intermediate = reactive(new Intermediate(val, prev))
193
- list[count] = intermediate
194
- } else {
195
- // Update the link.
196
- if (untracked(() => intermediate.prev) !== prev) {
197
- intermediate.prev = prev
198
- }
199
- }
200
-
201
- // Update the reactive mapping for this index
202
- if (indexToIntermediate[i] !== intermediate) {
203
- indexToIntermediate[i] = intermediate
204
- }
205
-
206
- // If we don't have an effect for this index yet, create one
207
- if (!indexEffects.has(i)) {
208
- ascend(() => {
209
- const index = i
210
- const stop = effect(function scanIndexSyncEffect() {
211
- const inter = indexToIntermediate[index]
212
- if (inter) {
213
- const accValue = inter.acc
214
- untracked(() => {
215
- result[index] = accValue
216
- })
217
- }
218
- })
219
- indexEffects.set(index, stop)
220
- })
221
- }
222
-
223
- prev = intermediate
224
- }
225
-
226
- // Cleanup trailing indices
227
- for (const index of Array.from(indexEffects.keys())) {
228
- if (index >= length) disposeIndex(index)
229
- }
230
-
231
- // Ensure result length matches source length
232
- untracked(() => {
233
- if (result.length !== length) {
234
- FoolProof.set(result as any, 'length', length, result)
235
- }
236
- })
237
- })
238
-
239
- return cleanedBy(result, (() => {
240
- mainEffect()
241
- for (const stop of indexEffects.values()) stop()
242
- indexEffects.clear()
243
- }) as EffectCleanup) as ScanResult<Output>
244
- }
245
-
246
- /**
247
- * Lifts a callback that returns an array into a reactive array that automatically
248
- * synchronizes with the source array returned by the callback.
249
- *
250
- * The returned reactive array will update whenever the callback's dependencies change,
251
- * efficiently syncing only the elements that differ from the previous result.
252
- *
253
- * @example
254
- * ```typescript
255
- * const items = reactive([1, 2, 3])
256
- * const doubled = lift(() => items.map(x => x * 2))
257
- *
258
- * console.log([...doubled]) // [2, 4, 6]
259
- *
260
- * items.push(4)
261
- * console.log([...doubled]) // [2, 4, 6, 8]
262
- * ```
263
- *
264
- * @param cb Callback function that returns an array
265
- * @returns A reactive array synchronized with the callback's result, with a [cleanup] property to stop tracking
266
- */
267
- export function lift<Output extends any[]>(
268
- cb: (access: EffectAccess) => Output
269
- ): Output & { [cleanup]: ScopedCallback }
270
-
271
- /**
272
- * Lifts a callback that returns an object into a reactive object that automatically
273
- * synchronizes with the source object returned by the callback.
274
- *
275
- * The returned reactive object will update whenever the callback's dependencies change,
276
- * efficiently syncing only the properties that differ from the previous result using
277
- * Object.assign(). Properties that no longer exist in the source are automatically removed.
278
- *
279
- * @example
280
- * ```typescript
281
- * const user = reactive({ name: 'John', age: 30 })
282
- * const profile = lift(() => ({
283
- * displayName: user.name.toUpperCase(),
284
- * isAdult: user.age >= 18,
285
- * description: `${user.name} is ${user.age} years old`
286
- * }))
287
- *
288
- * console.log(profile.displayName) // JOHN
289
- * console.log(profile.isAdult) // true
290
- *
291
- * user.name = 'Jane'
292
- * console.log(profile.displayName) // JANE
293
- * console.log(profile.description) // Jane is 30 years old
294
- * ```
295
- *
296
- * @param cb Callback function that returns an object
297
- * @returns A reactive object synchronized with the callback's result, with a [cleanup] property to stop tracking
298
- */
299
- export function lift<Output extends object>(
300
- cb: (access: EffectAccess) => Output
301
- ): Output & { [cleanup]: ScopedCallback }
302
- export function lift<Output extends any[] | object>(
303
- cb: (access: EffectAccess) => Output
304
- ): Output & { [cleanup]: ScopedCallback } {
305
- let result: Output
306
- const liftCleanup = effect((access) => {
307
- const source = cb(access)
308
- if (!source || typeof source !== 'object')
309
- throw new Error('lift callback must return an array or object')
310
- if (!result) {
311
- if (Array.isArray(source)) {
312
- result = reactive([]) as Output
313
- } else {
314
- result = reactive({}) as Output
315
- }
316
- }
317
- if (Array.isArray(source) && Array.isArray(result)) {
318
- if (result.length !== source.length) result.length = source.length
319
- for (let i = 0; i < source.length; i++) if (result[i] !== source[i]) result[i] = source[i]
320
- } else if (!Array.isArray(source) && !Array.isArray(result)) {
321
- Object.assign(result, source)
322
- for (const key of Object.keys(result)) if (!(key in source)) delete result[key]
323
- } else {
324
- throw new Error('lift callback must return the same type as the previous result')
325
- }
326
- })
327
- return cleanedBy(result as Output, liftCleanup)
328
- }
@@ -1,131 +0,0 @@
1
- import { recordTriggerLink } from '../../debug/debug'
2
- import { bubbleUpChange, objectsWithDeepWatchers } from './deep-watch-state'
3
- import { getActiveEffect, isRunning } from './effect-context'
4
- import { batch, hasBatched, recordActivation } from './effects'
5
- import { unwrap } from './proxy-state'
6
- import { getEffectNode, watchers } from './registry'
7
- import { allProps, type EffectTrigger, type Evolution, optionCall, type State } from './types'
8
-
9
- const states = new WeakMap<object, State>()
10
-
11
- export function addState(obj: any, evolution: Evolution) {
12
- obj = unwrap(obj)
13
- const next = {}
14
- const state = getState(obj)
15
- if (state) Object.assign(state, { evolution, next })
16
- states.set(obj, next)
17
- }
18
-
19
- /**
20
- * Gets the current state of a reactive object for evolution tracking
21
- * @param obj - The reactive object
22
- * @returns The current state object
23
- */
24
- export function getState(obj: any) {
25
- obj = unwrap(obj)
26
- let state = states.get(obj)
27
- if (!state) {
28
- state = {}
29
- states.set(obj, state)
30
- }
31
- return state
32
- }
33
-
34
- export function collectEffects(
35
- obj: any,
36
- evolution: Evolution,
37
- effects: Set<EffectTrigger>,
38
- objectWatchers: Map<any, Set<EffectTrigger>>,
39
- ...keyChains: Iterable<any>[]
40
- ) {
41
- const sourceEffect = getActiveEffect()
42
- for (const keys of keyChains)
43
- for (const key of keys) {
44
- const deps = objectWatchers.get(key)
45
- // console.log(`[DEBUG] collectEffects: checking ${String(key)}. Found deps: ${deps ? deps.size : 'none'}`)
46
- if (deps)
47
- for (const effect of deps) {
48
- // console.log(`[DEBUG] collectEffects: found dependency ${effect.name || 'anonymous'} for ${String(key)}`)
49
- const runningChain = isRunning(effect)
50
- if (runningChain) {
51
- optionCall('skipRunningEffect', effect)
52
- continue
53
- }
54
- if (!effects.has(effect)) {
55
- effects.add(effect)
56
- if (!hasBatched(effect)) recordActivation(effect, obj, evolution, key)
57
- }
58
- recordTriggerLink(sourceEffect, effect, obj, key, evolution)
59
- }
60
- }
61
- }
62
-
63
- /**
64
- * Triggers effects for a single property change
65
- * @param obj - The object that changed
66
- * @param evolution - The type of change
67
- * @param prop - The property that changed
68
- */
69
- export function touched1(obj: any, evolution: Evolution, prop: any) {
70
- touched(obj, evolution, [prop])
71
- }
72
-
73
- /**
74
- * Triggers effects for property changes
75
- * @param obj - The object that changed
76
- * @param evolution - The type of change
77
- * @param props - The properties that changed
78
- */
79
- export function touched(obj: any, evolution: Evolution, props?: Iterable<any>) {
80
- obj = unwrap(obj)
81
- addState(obj, evolution)
82
- const objectWatchers = watchers.get(obj)
83
- if (objectWatchers) {
84
- // Note: we have to collect effects to remove duplicates in the specific case when no batch is running
85
- const effects = new Set<EffectTrigger>()
86
- if (props) collectEffects(obj, evolution, effects, objectWatchers, [allProps], props)
87
- else collectEffects(obj, evolution, effects, objectWatchers, objectWatchers.keys())
88
- optionCall('touched', obj, evolution, props as any[] | undefined, effects)
89
- batch(Array.from(effects))
90
- }
91
-
92
- // Bubble up changes if this object has deep watchers
93
- if (objectsWithDeepWatchers.has(obj)) {
94
- bubbleUpChange(obj, evolution)
95
- }
96
- }
97
-
98
- /**
99
- * Triggers only opaque effects for property changes
100
- * Used by deep-touch to ensure opaque listeners are notified even when deep optimization is active
101
- */
102
- export function touchedOpaque(obj: any, evolution: Evolution, prop: any) {
103
- obj = unwrap(obj)
104
- const objectWatchers = watchers.get(obj)
105
- if (!objectWatchers) return
106
-
107
- const deps = objectWatchers.get(prop)
108
- if (!deps) return
109
-
110
- const effects = new Set<EffectTrigger>()
111
- const sourceEffect = getActiveEffect()
112
-
113
- for (const effect of deps) {
114
- const node = getEffectNode(effect)
115
- if (!node.isOpaque) continue
116
-
117
- const runningChain = isRunning(effect)
118
- if (runningChain) {
119
- optionCall('skipRunningEffect', effect)
120
- continue
121
- }
122
- effects.add(effect)
123
- recordActivation(effect, obj, evolution, prop)
124
- recordTriggerLink(sourceEffect, effect, obj, prop, evolution)
125
- }
126
-
127
- if (effects.size > 0) {
128
- optionCall('touched', obj, evolution, [prop], effects)
129
- batch(Array.from(effects))
130
- }
131
- }