mutts 1.0.6 → 1.0.8

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 (196) hide show
  1. package/README.md +61 -23
  2. package/dist/async/browser.d.ts +2 -0
  3. package/dist/async/browser.d.ts.map +1 -0
  4. package/dist/async/index.d.ts +18 -0
  5. package/dist/async/index.d.ts.map +1 -0
  6. package/dist/async/node.d.ts +2 -0
  7. package/dist/async/node.d.ts.map +1 -0
  8. package/dist/{chunks/index-CDCOjzTy.js → browser.cjs} +5913 -4382
  9. package/dist/browser.cjs.map +1 -0
  10. package/dist/browser.d.ts +1655 -0
  11. package/dist/browser.esm.js +305 -0
  12. package/dist/browser.esm.js.map +1 -0
  13. package/dist/chunks/async-browser-CA0jPWIi.cjs +304 -0
  14. package/dist/chunks/async-browser-CA0jPWIi.cjs.map +1 -0
  15. package/dist/chunks/async-core-UqHzvJ-S.cjs +25 -0
  16. package/dist/chunks/async-core-UqHzvJ-S.cjs.map +1 -0
  17. package/dist/chunks/async-node-BYHuGTni.cjs +103 -0
  18. package/dist/chunks/async-node-BYHuGTni.cjs.map +1 -0
  19. package/dist/chunks/{index-DiP0RXoZ.esm.js → index-DhaOVusv.esm.js} +5851 -4345
  20. package/dist/chunks/index-DhaOVusv.esm.js.map +1 -0
  21. package/dist/decorator.d.ts +17 -18
  22. package/dist/decorator.d.ts.map +1 -0
  23. package/dist/destroyable.d.ts +12 -15
  24. package/dist/destroyable.d.ts.map +1 -0
  25. package/dist/devtools/devtool/devtools.d.ts +1 -0
  26. package/dist/devtools/devtool/devtools.d.ts.map +1 -0
  27. package/dist/devtools/devtool/panel.d.ts +2 -0
  28. package/dist/devtools/devtool/panel.d.ts.map +1 -0
  29. package/dist/devtools/panel.js.map +1 -1
  30. package/dist/entry-browser.d.ts +3 -0
  31. package/dist/entry-browser.d.ts.map +1 -0
  32. package/dist/entry-node.d.ts +3 -0
  33. package/dist/entry-node.d.ts.map +1 -0
  34. package/dist/eventful.d.ts +3 -5
  35. package/dist/eventful.d.ts.map +1 -0
  36. package/dist/index.d.ts +13 -19
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/indexable.d.ts +10 -10
  39. package/dist/indexable.d.ts.map +1 -0
  40. package/dist/introspection.d.ts +27 -0
  41. package/dist/introspection.d.ts.map +1 -0
  42. package/dist/iterableWeak.d.ts +53 -0
  43. package/dist/iterableWeak.d.ts.map +1 -0
  44. package/dist/mixins.d.ts +25 -0
  45. package/dist/mixins.d.ts.map +1 -0
  46. package/dist/mutts.umd.js +1 -1
  47. package/dist/mutts.umd.js.map +1 -1
  48. package/dist/mutts.umd.min.js +1 -1
  49. package/dist/mutts.umd.min.js.map +1 -1
  50. package/dist/node.cjs +105 -0
  51. package/dist/node.cjs.map +1 -0
  52. package/dist/node.d.ts +1 -0
  53. package/dist/node.esm.js +104 -0
  54. package/dist/node.esm.js.map +1 -0
  55. package/dist/promiseChain.d.ts +4 -5
  56. package/dist/promiseChain.d.ts.map +1 -0
  57. package/dist/reactive/array.d.ts +49 -0
  58. package/dist/reactive/array.d.ts.map +1 -0
  59. package/dist/reactive/buffer.d.ts +44 -0
  60. package/dist/reactive/buffer.d.ts.map +1 -0
  61. package/dist/reactive/change.d.ts +29 -0
  62. package/dist/reactive/change.d.ts.map +1 -0
  63. package/dist/reactive/debug.d.ts +111 -0
  64. package/dist/reactive/debug.d.ts.map +1 -0
  65. package/dist/reactive/deep-touch.d.ts +28 -0
  66. package/dist/reactive/deep-touch.d.ts.map +1 -0
  67. package/dist/reactive/deep-watch-state.d.ts +25 -0
  68. package/dist/reactive/deep-watch-state.d.ts.map +1 -0
  69. package/dist/reactive/deep-watch.d.ts +19 -0
  70. package/dist/reactive/deep-watch.d.ts.map +1 -0
  71. package/dist/reactive/effect-context.d.ts +7 -0
  72. package/dist/reactive/effect-context.d.ts.map +1 -0
  73. package/dist/reactive/effects.d.ts +151 -0
  74. package/dist/reactive/effects.d.ts.map +1 -0
  75. package/dist/reactive/index.d.ts +20 -0
  76. package/dist/reactive/index.d.ts.map +1 -0
  77. package/dist/reactive/interface.d.ts +64 -0
  78. package/dist/reactive/interface.d.ts.map +1 -0
  79. package/dist/reactive/map.d.ts +30 -0
  80. package/dist/reactive/map.d.ts.map +1 -0
  81. package/dist/reactive/memoize.d.ts +5 -0
  82. package/dist/reactive/memoize.d.ts.map +1 -0
  83. package/dist/reactive/non-reactive-state.d.ts +9 -0
  84. package/dist/reactive/non-reactive-state.d.ts.map +1 -0
  85. package/dist/reactive/non-reactive.d.ts +11 -0
  86. package/dist/reactive/non-reactive.d.ts.map +1 -0
  87. package/dist/reactive/project.d.ts +41 -0
  88. package/dist/reactive/project.d.ts.map +1 -0
  89. package/dist/reactive/proxy-state.d.ts +8 -0
  90. package/dist/reactive/proxy-state.d.ts.map +1 -0
  91. package/dist/reactive/proxy.d.ts +23 -0
  92. package/dist/reactive/proxy.d.ts.map +1 -0
  93. package/dist/reactive/record.d.ts +116 -0
  94. package/dist/reactive/record.d.ts.map +1 -0
  95. package/dist/reactive/register.d.ts +64 -0
  96. package/dist/reactive/register.d.ts.map +1 -0
  97. package/dist/reactive/registry.d.ts +20 -0
  98. package/dist/reactive/registry.d.ts.map +1 -0
  99. package/dist/reactive/set.d.ts +28 -0
  100. package/dist/reactive/set.d.ts.map +1 -0
  101. package/dist/reactive/tracking.d.ts +7 -0
  102. package/dist/reactive/tracking.d.ts.map +1 -0
  103. package/dist/reactive/types.d.ts +376 -0
  104. package/dist/reactive/types.d.ts.map +1 -0
  105. package/dist/std-decorators.d.ts +9 -11
  106. package/dist/std-decorators.d.ts.map +1 -0
  107. package/dist/utils.d.ts +49 -0
  108. package/dist/utils.d.ts.map +1 -0
  109. package/dist/zone.d.ts +40 -0
  110. package/dist/zone.d.ts.map +1 -0
  111. package/docs/ai/api-reference.md +0 -2
  112. package/docs/reactive/advanced.md +2 -5
  113. package/docs/reactive/collections.md +0 -125
  114. package/docs/reactive/core.md +27 -24
  115. package/docs/reactive/debugging.md +12 -2
  116. package/docs/reactive/project.md +1 -1
  117. package/docs/reactive/scan.md +78 -0
  118. package/docs/reactive.md +2 -1
  119. package/docs/std-decorators.md +69 -0
  120. package/docs/zone.md +95 -0
  121. package/package.json +67 -23
  122. package/src/async/browser.ts +319 -0
  123. package/src/async/index.ts +23 -0
  124. package/src/async/node.ts +104 -0
  125. package/src/decorator.ts +5 -1
  126. package/src/destroyable.ts +1 -1
  127. package/src/entry-browser.ts +5 -0
  128. package/src/entry-node.ts +5 -0
  129. package/src/index.d.ts +12 -9
  130. package/src/index.ts +23 -14
  131. package/src/indexable.ts +42 -0
  132. package/src/mixins.ts +2 -2
  133. package/src/reactive/array.ts +274 -179
  134. package/src/reactive/buffer.ts +168 -0
  135. package/src/reactive/change.ts +2 -2
  136. package/src/reactive/effect-context.ts +15 -91
  137. package/src/reactive/effects.ts +119 -179
  138. package/src/reactive/index.ts +11 -13
  139. package/src/reactive/interface.ts +19 -33
  140. package/src/reactive/map.ts +49 -62
  141. package/src/reactive/memoize.ts +19 -9
  142. package/src/reactive/project.ts +43 -22
  143. package/src/reactive/proxy.ts +16 -41
  144. package/src/reactive/record.ts +3 -3
  145. package/src/reactive/register.ts +5 -7
  146. package/src/reactive/registry.ts +9 -17
  147. package/src/reactive/set.ts +43 -57
  148. package/src/reactive/tracking.ts +1 -29
  149. package/src/reactive/types.ts +46 -23
  150. package/src/utils.ts +80 -37
  151. package/src/zone.ts +138 -0
  152. package/dist/chunks/_tslib-BgjropY9.js +0 -81
  153. package/dist/chunks/_tslib-BgjropY9.js.map +0 -1
  154. package/dist/chunks/_tslib-MCKDzsSq.esm.js +0 -75
  155. package/dist/chunks/_tslib-MCKDzsSq.esm.js.map +0 -1
  156. package/dist/chunks/decorator-BGILvPtN.esm.js +0 -627
  157. package/dist/chunks/decorator-BGILvPtN.esm.js.map +0 -1
  158. package/dist/chunks/decorator-BQ2eBTCj.js +0 -651
  159. package/dist/chunks/decorator-BQ2eBTCj.js.map +0 -1
  160. package/dist/chunks/index-CDCOjzTy.js.map +0 -1
  161. package/dist/chunks/index-DiP0RXoZ.esm.js.map +0 -1
  162. package/dist/decorator.esm.js +0 -2
  163. package/dist/decorator.esm.js.map +0 -1
  164. package/dist/decorator.js +0 -11
  165. package/dist/decorator.js.map +0 -1
  166. package/dist/destroyable.esm.js +0 -109
  167. package/dist/destroyable.esm.js.map +0 -1
  168. package/dist/destroyable.js +0 -116
  169. package/dist/destroyable.js.map +0 -1
  170. package/dist/eventful.esm.js +0 -66
  171. package/dist/eventful.esm.js.map +0 -1
  172. package/dist/eventful.js +0 -68
  173. package/dist/eventful.js.map +0 -1
  174. package/dist/index.esm.js +0 -53
  175. package/dist/index.esm.js.map +0 -1
  176. package/dist/index.js +0 -139
  177. package/dist/index.js.map +0 -1
  178. package/dist/indexable.esm.js +0 -285
  179. package/dist/indexable.esm.js.map +0 -1
  180. package/dist/indexable.js +0 -291
  181. package/dist/indexable.js.map +0 -1
  182. package/dist/promiseChain.esm.js +0 -78
  183. package/dist/promiseChain.esm.js.map +0 -1
  184. package/dist/promiseChain.js +0 -80
  185. package/dist/promiseChain.js.map +0 -1
  186. package/dist/reactive.d.ts +0 -910
  187. package/dist/reactive.esm.js +0 -5
  188. package/dist/reactive.esm.js.map +0 -1
  189. package/dist/reactive.js +0 -59
  190. package/dist/reactive.js.map +0 -1
  191. package/dist/std-decorators.esm.js +0 -196
  192. package/dist/std-decorators.esm.js.map +0 -1
  193. package/dist/std-decorators.js +0 -204
  194. package/dist/std-decorators.js.map +0 -1
  195. package/src/reactive/mapped.ts +0 -129
  196. package/src/reactive/zone.ts +0 -208
@@ -0,0 +1,168 @@
1
+ import { FoolProof } from '../utils'
2
+ import { effect, untracked } from './effects'
3
+ import { cleanedBy, cleanup } from './interface'
4
+ import { memoize } from './memoize'
5
+ import { reactive } from './proxy'
6
+ import type { ScopedCallback } from './types'
7
+
8
+ /**
9
+ * Result of a reactive scan, which is a reactive array of accumulated values
10
+ * with an attached cleanup function.
11
+ */
12
+ export type ScanResult<Output> = readonly Output[] & { [cleanup]: ScopedCallback }
13
+
14
+ /**
15
+ * Perform a reactive scan over an array of items.
16
+ *
17
+ * This implementation is highly optimized for performance and fine-grained reactivity:
18
+ * - **Incremental Updates**: Changes to an item only trigger re-computation from that
19
+ * point onwards in the result chain.
20
+ * - **Move Optimization**: If items are moved within the array, their accumulated
21
+ * values are reused as long as their predecessor remains the same.
22
+ * - **Duplicate Support**: Correctly handles multiple occurrences of the same object
23
+ * instance using an internal occurrence tracking mechanism.
24
+ * - **Memory Efficient**: Uses `WeakMap` for caching intermediates, which are
25
+ * automatically cleared when source items are garbage collected.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const source = reactive([{ val: 1 }, { val: 2 }, { val: 3 }])
30
+ * const sum = scan(source, (acc, item) => acc + item.val, 0)
31
+ *
32
+ * expect([...sum]).toEqual([1, 3, 6])
33
+ *
34
+ * // Modifying an item only re-computes subsequent sums
35
+ * source[1].val = 10
36
+ * expect([...sum]).toEqual([1, 11, 14])
37
+ * ```
38
+ *
39
+ * @param source The source array of objects (will be made reactive)
40
+ * @param callback The accumulator function called with (accumulator, currentItem)
41
+ * @param initialValue The starting value for the accumulation
42
+ * @returns A reactive array of accumulated values, with a [cleanup] property to stop the tracking
43
+ */
44
+ export function scan<Input extends object, Output>(
45
+ source: readonly Input[],
46
+ callback: (acc: Output, val: Input) => Output,
47
+ initialValue: Output
48
+ ): ScanResult<Output> {
49
+ const observedSource = reactive(source)
50
+ const result = reactive([] as Output[])
51
+
52
+ // Track effects for each index to dispose them when the array shrinks
53
+ const indexEffects = new Map<number, ScopedCallback>()
54
+ // Mapping from index to its current intermediate object
55
+ const indexToIntermediate = reactive([] as Intermediate[])
56
+ const intermediaries = new WeakMap<Input, Intermediate[]>()
57
+
58
+ class Intermediate {
59
+ public prev: Intermediate | undefined
60
+ constructor(public val: Input, prev: Intermediate | undefined) {
61
+ this.prev = prev
62
+ }
63
+
64
+ @memoize
65
+ get acc(): Output {
66
+ const prevAcc = this.prev ? this.prev.acc : initialValue
67
+ return callback(prevAcc, this.val)
68
+ }
69
+ }
70
+
71
+ function disposeIndex(index: number) {
72
+ const stop = indexEffects.get(index)
73
+ if (stop) {
74
+ stop()
75
+ indexEffects.delete(index)
76
+ untracked(() => {
77
+ Reflect.deleteProperty(indexToIntermediate as any, index)
78
+ Reflect.deleteProperty(result as any, index)
79
+ })
80
+ }
81
+ }
82
+
83
+ const mainEffect = effect(function scanMainEffect({ ascend }) {
84
+ const length = observedSource.length
85
+ const occurrenceCount = new Map<Input, number>()
86
+ let prev: Intermediate | undefined = undefined
87
+
88
+ for (let i = 0; i < length; i++) {
89
+ const val = FoolProof.get(observedSource as any, i, observedSource) as Input
90
+
91
+ if (!(val && (typeof val === 'object' || typeof val === 'function' || typeof val === 'symbol'))) {
92
+ throw new Error('scan: items must be objects (WeakKey) for intermediate caching')
93
+ }
94
+
95
+ const count = occurrenceCount.get(val) ?? 0
96
+ occurrenceCount.set(val, count + 1)
97
+
98
+ let list = intermediaries.get(val)
99
+ if (!list) {
100
+ list = []
101
+ intermediaries.set(val, list)
102
+ }
103
+
104
+ let intermediate = list[count]
105
+ if (!intermediate) {
106
+ intermediate = reactive(new Intermediate(val, prev))
107
+ list[count] = intermediate
108
+ } else {
109
+ // Update the link.
110
+ if (untracked(() => intermediate.prev) !== prev) {
111
+ intermediate.prev = prev
112
+ }
113
+ }
114
+
115
+ // Update the reactive mapping for this index
116
+ if (indexToIntermediate[i] !== intermediate) {
117
+ indexToIntermediate[i] = intermediate
118
+ }
119
+
120
+ // If we don't have an effect for this index yet, create one
121
+ if (!indexEffects.has(i)) {
122
+ ascend(() => {
123
+ const index = i
124
+ const stop = effect(function scanIndexSyncEffect() {
125
+ const inter = indexToIntermediate[index]
126
+ if (inter) {
127
+ const accValue = inter.acc
128
+ untracked(() => {
129
+ result[index] = accValue
130
+ })
131
+ }
132
+ })
133
+ indexEffects.set(index, stop)
134
+ })
135
+ }
136
+
137
+ prev = intermediate
138
+ }
139
+
140
+ // Cleanup trailing indices
141
+ for (const index of Array.from(indexEffects.keys())) {
142
+ if (index >= length) disposeIndex(index)
143
+ }
144
+
145
+ // Ensure result length matches source length
146
+ untracked(() => {
147
+ if (result.length !== length) {
148
+ FoolProof.set(result as any, 'length', length, result)
149
+ }
150
+ })
151
+ })
152
+
153
+ return cleanedBy(result, () => {
154
+ mainEffect()
155
+ for (const stop of indexEffects.values()) stop()
156
+ indexEffects.clear()
157
+ }) as ScanResult<Output>
158
+ }
159
+
160
+ export function resolve<Output>(cb: () => Output[]): Output[] & { [cleanup]: ScopedCallback } {
161
+ const result = reactive([] as Output[])
162
+ return cleanedBy(result, effect(() => {
163
+ const source = cb()
164
+ if (result.length !== source.length) result.length = source.length
165
+ for (let i = 0; i < source.length; i++)
166
+ if (result[i] !== source[i]) result[i] = source[i]
167
+ }))
168
+ }
@@ -46,7 +46,7 @@ export function collectEffects(
46
46
  for (const effect of deps) {
47
47
  const runningChain = isRunning(effect)
48
48
  if (runningChain) {
49
- options.skipRunningEffect(effect, runningChain as any)
49
+ options.skipRunningEffect(effect)
50
50
  continue
51
51
  }
52
52
  if (!effects.has(effect)) {
@@ -118,7 +118,7 @@ export function touchedOpaque(obj: any, evolution: Evolution, prop: any) {
118
118
 
119
119
  const runningChain = isRunning(effect)
120
120
  if (runningChain) {
121
- options.skipRunningEffect(effect, runningChain as any)
121
+ options.skipRunningEffect(effect)
122
122
  continue
123
123
  }
124
124
  effects.add(effect)
@@ -1,94 +1,18 @@
1
- import { effectParent, getRoot } from './registry'
2
- import { ReactiveError, type ScopedCallback } from './types'
3
-
4
- /**
5
- * Effect context stack for nested tracking (front = active, next = parent)
6
- */
7
- const stack: (ScopedCallback | undefined)[] = []
8
- export const effectStack = stack
9
-
10
- export function captureEffectStack() {
11
- return stack.slice()
12
- }
13
- export function isRunning(effect: ScopedCallback): (ScopedCallback | undefined)[] | false {
14
- const rootEffect = getRoot(effect)
15
-
16
- // Check if the effect is directly in the stack
17
- const rootIndex = stack.indexOf(rootEffect)
18
- if (rootIndex !== -1) {
19
- return stack.slice(0, rootIndex + 1).reverse()
20
- }
21
-
22
- // Check if any effect in the stack is a descendant of this effect
23
- // (i.e., walk up the parent chain from each stack effect to see if we reach this effect)
24
- for (let i = 0; i < stack.length; i++) {
25
- const stackEffect = stack[i]
26
- let current: ScopedCallback | undefined = stackEffect
27
- const visited = new WeakSet<ScopedCallback>()
28
- const ancestorChain: ScopedCallback[] = []
29
- // TODO: That's perhaps a lot of computations for an `assert`
30
- // Walk up the parent chain to find if this effect is an ancestor
31
- while (current && !visited.has(current)) {
32
- visited.add(current)
33
- const currentRoot = getRoot(current)
34
- ancestorChain.push(currentRoot)
35
- if (currentRoot === rootEffect) {
36
- // Found a descendant - build the full chain from ancestor to active
37
- // The ancestorChain contains [descendant, parent, ..., ancestor] (walking up)
38
- // We need [ancestor (effect), ..., parent, descendant, ...stack from descendant to active]
39
- const chainFromAncestor = ancestorChain.reverse() // [ancestor, ..., descendant]
40
- // Prepend the actual effect we're checking (in case current is a wrapper)
41
- if (chainFromAncestor[0] !== rootEffect) {
42
- chainFromAncestor.unshift(rootEffect)
43
- }
44
- // Append the rest of the stack from the descendant to the active effect
45
- const stackFromDescendant = stack.slice(0, i + 1).reverse() // [descendant, ..., active]
46
- // Remove duplicate descendant (it's both at end of chainFromAncestor and start of stackFromDescendant)
47
- if (chainFromAncestor.length > 0 && stackFromDescendant.length > 0) {
48
- stackFromDescendant.shift() // Remove duplicate descendant
49
- }
50
- return [...chainFromAncestor, ...stackFromDescendant]
51
- }
52
- current = effectParent.get(current)
53
- }
54
- }
55
-
56
- return false
57
- }
58
- export function withEffectStack<T>(snapshot: (ScopedCallback | undefined)[], fn: () => T): T {
59
- const previousStack = stack.slice()
60
- assignStack(snapshot)
61
- try {
62
- return fn()
63
- } finally {
64
- assignStack(previousStack)
65
- }
1
+ import { tag } from '../utils'
2
+ import { asyncZone, ZoneAggregator, ZoneHistory } from '../zone'
3
+ import { getRoot } from './registry'
4
+ import { type ScopedCallback } from './types'
5
+
6
+ export const effectHistory = tag('effectHistory', new ZoneHistory<ScopedCallback>())
7
+ tag('effectHistory.present', effectHistory.present)
8
+ asyncZone.add(effectHistory)
9
+ export const effectAggregator = tag('effectAggregator', new ZoneAggregator(effectHistory.present))
10
+
11
+ export function isRunning(effect: ScopedCallback): boolean {
12
+ const root = getRoot(effect)
13
+ return effectHistory.some((e) => getRoot(e) === root)
66
14
  }
67
15
 
68
16
  export function getActiveEffect() {
69
- return stack[0]
70
- }
71
-
72
- /**
73
- * Executes a function with a specific effect context
74
- * @param effect - The effect to use as context
75
- * @param fn - The function to execute
76
- * @param keepParent - Whether to keep the parent effect context
77
- * @returns The result of the function
78
- */
79
- export function withEffect<T>(effect: ScopedCallback | undefined, fn: () => T): T {
80
-
81
- if (getRoot(effect) === getRoot(getActiveEffect())) return fn()
82
- stack.unshift(effect)
83
- try {
84
- return fn()
85
- } finally {
86
- const recoveredEffect = stack.shift()
87
- if (recoveredEffect !== effect) throw new ReactiveError('[reactive] Effect stack mismatch')
88
- }
89
- }
90
-
91
- function assignStack(values: (ScopedCallback | undefined)[]) {
92
- stack.length = 0
93
- stack.push(...values)
94
- }
17
+ return effectHistory.present.active
18
+ }