mutts 1.0.9 → 1.0.10

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