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,72 +0,0 @@
1
- export { attend, lift, type ScanResult, scan } from './buffer'
2
- export { getState, touched, touched1 } from './change'
3
- export { deepWatch } from './deep-watch'
4
- export { cleanedBy, derived, effectAggregator, getActiveEffect } from './effect-context'
5
- export {
6
- addBatchCleanup,
7
- atomic,
8
- biDi,
9
- defer,
10
- effect,
11
- getActivationLog,
12
- onEffectThrow,
13
- onEffectTrigger,
14
- reset,
15
- root,
16
- untracked,
17
- } from './effects'
18
- export { type Memoizable, memoize } from './memoize'
19
- export { immutables, isNonReactive, registerNativeReactivity } from './non-reactive'
20
- export { getActiveProjection, project } from './project'
21
- export { isReactive, ReactiveBase, reactive, unwrap } from './proxy'
22
- export { organize, organized } from './record'
23
- export { Register, register } from './register'
24
- export {
25
- cleanup,
26
- type EffectAccess as DependencyAccess,
27
- type EffectCleanup,
28
- type EffectOptions,
29
- type EffectTrigger,
30
- type Evolution,
31
- options as reactiveOptions,
32
- ReactiveError,
33
- ReactiveErrorCode,
34
- type ScopedCallback,
35
- } from './types'
36
- export { unreactive, watch } from './watch'
37
- export { describe } from './describe'
38
-
39
- import { ReactiveArray } from './array'
40
- import {
41
- deepWatchers,
42
- effectToDeepWatchedObjects,
43
- objectParents,
44
- objectsWithDeepWatchers,
45
- } from './deep-watch'
46
- import { ReactiveMap, ReactiveWeakMap } from './map'
47
- import { nonReactiveObjects } from './non-reactive-state'
48
- import { metaProtos, objectToProxy, proxyToObject } from './proxy'
49
- import { effectToReactiveObjects, watchers } from './registry'
50
- import { ReactiveSet, ReactiveWeakSet } from './set'
51
-
52
- // Register native collection types to use specialized reactive wrappers
53
- metaProtos.set(Array, ReactiveArray.prototype)
54
- metaProtos.set(Set, ReactiveSet.prototype)
55
- metaProtos.set(WeakSet, ReactiveWeakSet.prototype)
56
- metaProtos.set(Map, ReactiveMap.prototype)
57
- metaProtos.set(WeakMap, ReactiveWeakMap.prototype)
58
-
59
- /**
60
- * Object containing internal reactive system state for debugging and profiling
61
- */
62
- export const profileInfo: any = {
63
- objectToProxy,
64
- proxyToObject,
65
- effectToReactiveObjects,
66
- watchers,
67
- objectParents,
68
- objectsWithDeepWatchers,
69
- deepWatchers,
70
- effectToDeepWatchedObjects,
71
- nonReactiveObjects,
72
- }
@@ -1,142 +0,0 @@
1
- import { contentRef } from '../utils'
2
- import { touched, touched1 } from './change'
3
- import { notifyPropertyChange } from './deep-touch'
4
- import { makeReactiveEntriesIterator, makeReactiveIterator } from './non-reactive'
5
- import { reactive } from './proxy'
6
- import { dependant } from './tracking'
7
-
8
- /**
9
- * Reactive wrapper around JavaScript's WeakMap class
10
- * Only tracks individual key operations, no size tracking (WeakMap limitation)
11
- */
12
- export abstract class ReactiveWeakMap<K extends object, V> extends WeakMap<K, V> {
13
- // Implement WeakMap interface methods with reactivity
14
- delete(key: K): boolean {
15
- const hadKey = this.has(key)
16
- const result = this.delete(key)
17
-
18
- if (hadKey) touched1(contentRef(this), { type: 'del', prop: key }, key)
19
-
20
- return result
21
- }
22
-
23
- get(key: K): V | undefined {
24
- dependant(contentRef(this), key)
25
- return reactive(this.get(key))
26
- }
27
-
28
- has(key: K): boolean {
29
- dependant(contentRef(this), key)
30
- return this.has(key)
31
- }
32
-
33
- set(key: K, value: V): this {
34
- const hadKey = this.has(key)
35
- const oldValue = this.get(key)
36
- const reactiveValue = reactive(value)
37
- this.set(key, reactiveValue)
38
-
39
- if (!hadKey || oldValue !== reactiveValue) {
40
- notifyPropertyChange(contentRef(this), key, oldValue, reactiveValue, hadKey)
41
- }
42
-
43
- return this
44
- }
45
- }
46
-
47
- /**
48
- * Reactive wrapper around JavaScript's Map class
49
- * Tracks size changes, individual key operations, and collection-wide operations
50
- */
51
- export abstract class ReactiveMap<K, V> extends Map<K, V> {
52
- // Implement Map interface methods with reactivity
53
- get size(): number {
54
- dependant(this, 'size') // The ReactiveMap instance still goes through proxy
55
- return this.size
56
- }
57
-
58
- clear(): void {
59
- const hadEntries = this.size > 0
60
- this.clear()
61
-
62
- if (hadEntries) {
63
- const evolution = { type: 'bunch', method: 'clear' } as const
64
- // Clear triggers all effects since all keys are affected
65
- touched1(this, evolution, 'size')
66
- touched(contentRef(this), evolution)
67
- }
68
- }
69
-
70
- entries(): Generator<[K, V]> {
71
- dependant(contentRef(this))
72
- return makeReactiveEntriesIterator(this.entries())
73
- }
74
-
75
- forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void {
76
- dependant(contentRef(this))
77
- this.forEach(callbackfn, thisArg)
78
- }
79
-
80
- keys(): MapIterator<K> {
81
- dependant(contentRef(this))
82
- return this.keys()
83
- }
84
-
85
- values(): Generator<V> {
86
- dependant(contentRef(this))
87
- return makeReactiveIterator(this.values())
88
- }
89
-
90
- [Symbol.iterator](): MapIterator<[K, V]> {
91
- dependant(contentRef(this))
92
- const it: MapIterator<[K, V]> = Map.prototype[Symbol.iterator].call(this)
93
- const nativeNext = it.next.bind(it)
94
- it.next = () => {
95
- const result = nativeNext()
96
- if (result.done) return result
97
- const [key, value] = result.value
98
- return { value: [reactive(key), reactive(value)], done: false }
99
- }
100
- return it
101
- }
102
-
103
- // Implement Map methods with reactivity
104
- delete(key: K): boolean {
105
- const hadKey = this.has(key)
106
- const result = this.delete(key)
107
-
108
- if (hadKey) {
109
- const evolution = { type: 'del', prop: key } as const
110
- touched1(contentRef(this), evolution, key)
111
- touched1(this, evolution, 'size')
112
- }
113
-
114
- return result
115
- }
116
-
117
- get(key: K): V | undefined {
118
- dependant(contentRef(this), key)
119
- return reactive(this.get(key))
120
- }
121
-
122
- has(key: K): boolean {
123
- dependant(contentRef(this), key)
124
- return this.has(key)
125
- }
126
-
127
- set(key: K, value: V): this {
128
- const hadKey = this.has(key)
129
- const oldValue = this.get(key)
130
- const reactiveValue = reactive(value)
131
- this.set(key, reactiveValue)
132
-
133
- if (!hadKey || oldValue !== reactiveValue) {
134
- notifyPropertyChange(contentRef(this), key, oldValue, reactiveValue, hadKey)
135
- // Also notify size change for Map (WeakMap doesn't track size)
136
- const evolution = { type: hadKey ? 'set' : 'add', prop: key } as const
137
- touched1(this, evolution, 'size')
138
- }
139
-
140
- return this
141
- }
142
- }
@@ -1,186 +0,0 @@
1
- import { decorator } from '../decorator'
2
- import { deepCompare, renamed } from '../utils'
3
- import { touched1 } from './change'
4
- import { effect, root, untracked } from './effects'
5
- import { getRoot, markWithRoot } from './registry'
6
- import { dependant } from './tracking'
7
- import { optionCall, options, rootFunction } from './types'
8
-
9
- export type Memoizable = object | any[] | symbol | ((...args: any[]) => any)
10
-
11
- type MemoCacheTree<Result> = {
12
- result?: Result
13
- cleanup?: () => void
14
- branches?: WeakMap<Memoizable, MemoCacheTree<Result>>
15
- }
16
-
17
- const memoizedRegistry = new WeakMap<any, Function>()
18
- const wrapperRegistry = new WeakMap<Function, Function>()
19
-
20
- function getBranch<Result>(tree: MemoCacheTree<Result>, key: Memoizable): MemoCacheTree<Result> {
21
- tree.branches ??= new WeakMap()
22
- let branch = tree.branches.get(key)
23
- if (!branch) {
24
- branch = {}
25
- tree.branches.set(key, branch)
26
- }
27
- return branch
28
- }
29
-
30
- function memoizeFunction<Result, Args extends Memoizable[]>(
31
- fn: (...args: Args) => Result
32
- ): (...args: Args) => Result {
33
- const fnRoot = getRoot(fn)
34
- const existing = memoizedRegistry.get(fnRoot)
35
- if (existing) return existing as (...args: Args) => Result
36
-
37
- const cacheRoot: MemoCacheTree<Result> = {}
38
- const memoized = markWithRoot((...args: Args): Result => {
39
- const localArgs = args //: Args = maxArgs !== undefined ? (args.slice(0, maxArgs) as Args) : args
40
- if (localArgs.some((arg) => !(arg && ['object', 'symbol', 'function'].includes(typeof arg))))
41
- throw new Error('memoize expects non-null object arguments')
42
-
43
- let node: MemoCacheTree<Result> = cacheRoot
44
- // Note: decorators add `this` as first argument
45
- for (const arg of localArgs) {
46
- node = getBranch(node, arg)
47
- }
48
-
49
- dependant(node, 'memoize')
50
- if ('result' in node) {
51
- if (options.onMemoizationDiscrepancy) {
52
- const wasVerification = options.isVerificationRun
53
- options.isVerificationRun = true
54
- try {
55
- const fresh = untracked(() => fn(...localArgs))
56
- if (!deepCompare(node.result, fresh)) {
57
- optionCall('onMemoizationDiscrepancy', node.result, fresh, fn, localArgs, 'calculation')
58
- }
59
- } finally {
60
- options.isVerificationRun = wasVerification
61
- }
62
- }
63
- return node.result!
64
- }
65
-
66
- // Create memoize internal effect to track dependencies and invalidate cache
67
- // Use untracked to prevent the effect creation from being affected by parent effects
68
- node.cleanup = root(() =>
69
- effect(
70
- () => {
71
- // Execute the function and track its dependencies
72
- // The function execution will automatically track dependencies on reactive objects
73
- node.result = fn(...localArgs)
74
- return () => {
75
- // When dependencies change, clear the cache and notify consumers
76
- delete node.result
77
- touched1(node, { type: 'invalidate', prop: localArgs }, 'memoize')
78
- // Lazy memoization: stop the effect so it doesn't re-run immediately.
79
- // It will be re-created on next access.
80
- if (node.cleanup) {
81
- node.cleanup()
82
- node.cleanup = undefined
83
- }
84
- }
85
- },
86
- { opaque: true }
87
- )
88
- )
89
-
90
- if (options.onMemoizationDiscrepancy) {
91
- const wasVerification = options.isVerificationRun
92
- options.isVerificationRun = true
93
- try {
94
- const fresh = untracked(() => fn(...localArgs))
95
- if (!deepCompare(node.result, fresh)) {
96
- optionCall('onMemoizationDiscrepancy', node.result, fresh, fn, localArgs, 'comparison')
97
- }
98
- } finally {
99
- options.isVerificationRun = wasVerification
100
- }
101
- }
102
-
103
- return node.result!
104
- }, fn)
105
-
106
- memoizedRegistry.set(fnRoot, memoized)
107
- memoizedRegistry.set(memoized, memoized)
108
- return memoized as (...args: Args) => Result
109
- }
110
-
111
- /**
112
- * Decorator and function wrapper for memoizing computed values based on reactive dependencies.
113
- *
114
- * When used as a decorator on getters or methods, it caches the result and automatically
115
- * invalidates the cache when reactive dependencies change.
116
- *
117
- * When used as a function wrapper, it memoizes based on object arguments (WeakMap-based cache).
118
- *
119
- * @example
120
- * ```typescript
121
- * class User {
122
- * @memoize
123
- * get fullName() {
124
- * return `${this.firstName} ${this.lastName}`
125
- * }
126
- * }
127
- *
128
- * // Or as a function wrapper
129
- * const expensive = memoize((obj: SomeObject) => {
130
- * return heavyComputation(obj)
131
- * })
132
- * ```
133
- */
134
- export const memoize = decorator({
135
- getter(original, target, propertyKey) {
136
- return function (this: any) {
137
- let wrapper = wrapperRegistry.get(original)
138
- if (!wrapper) {
139
- wrapper = markWithRoot(
140
- renamed(
141
- (that: object) => {
142
- return original.call(that)
143
- },
144
- `${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(propertyKey)}`
145
- ),
146
- {
147
- method: original,
148
- propertyKey,
149
- ...((original as any)[rootFunction]
150
- ? { [rootFunction]: (original as any)[rootFunction] }
151
- : {}),
152
- }
153
- )
154
- wrapperRegistry.set(original, wrapper)
155
- }
156
- const memoized = memoizeFunction(wrapper as any)
157
- return memoized(this)
158
- }
159
- },
160
- method(original, target, name) {
161
- return function (this: any, ...args: object[]) {
162
- let wrapper = wrapperRegistry.get(original)
163
- if (!wrapper) {
164
- wrapper = markWithRoot(
165
- renamed(
166
- (that: object, ...args: object[]) => {
167
- return original.call(that, ...args)
168
- },
169
- `${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(name)}`
170
- ),
171
- {
172
- method: original,
173
- propertyKey: name,
174
- ...((original as any)[rootFunction]
175
- ? { [rootFunction]: (original as any)[rootFunction] }
176
- : {}),
177
- }
178
- )
179
- wrapperRegistry.set(original, wrapper)
180
- }
181
- const memoized = memoizeFunction(wrapper as any) as (...args: object[]) => unknown
182
- return memoized(this, ...args)
183
- }
184
- },
185
- default: memoizeFunction,
186
- })
@@ -1,49 +0,0 @@
1
- import { nativeReactive, nonReactiveMark } from './types'
2
-
3
- export const nonReactiveObjects = new WeakSet<object>()
4
- export const immutables = new Set<(tested: any) => boolean>()
5
- export const absent = Symbol('absent')
6
-
7
- function markNonReactive<T extends object[]>(...obj: T): T[0] {
8
- for (const o of obj) {
9
- try {
10
- Object.defineProperty(o, nonReactiveMark, {
11
- value: true,
12
- writable: false,
13
- enumerable: false,
14
- configurable: false,
15
- })
16
- } catch {}
17
- if (!(nonReactiveMark in (o as object))) nonReactiveObjects.add(o as object)
18
- }
19
- return obj[0]
20
- }
21
-
22
- export function nonReactiveClass<T extends (new (...args: any[]) => any)[]>(...cls: T): T[0] {
23
- for (const c of cls) if (c) (c.prototype as any)[nonReactiveMark] = true
24
- return cls[0]
25
- }
26
-
27
- export function isNonReactive(obj: any): boolean {
28
- if (obj === null || typeof obj !== 'object') return true
29
- if (nonReactiveObjects.has(obj)) return true
30
- if ((obj as any)[nonReactiveMark]) return true
31
- for (const fn of immutables) if (fn(obj)) return true
32
- return false
33
- }
34
-
35
- export function registerNativeReactivity(
36
- originalClass: new (...args: any[]) => any,
37
- reactiveClass: new (...args: any[]) => any
38
- ) {
39
- originalClass.prototype[nativeReactive] = reactiveClass
40
- nonReactiveClass(reactiveClass)
41
- }
42
-
43
- nonReactiveClass(Date, RegExp, Error, Promise, Function)
44
- if (typeof window !== 'undefined') {
45
- markNonReactive(window, document)
46
- nonReactiveClass(Node, Element, HTMLElement, EventTarget)
47
- }
48
-
49
- export { markNonReactive as nonReactive }
@@ -1,43 +0,0 @@
1
- import {
2
- absent,
3
- immutables,
4
- isNonReactive,
5
- nonReactive,
6
- nonReactiveClass,
7
- nonReactiveObjects,
8
- registerNativeReactivity,
9
- } from './non-reactive-state'
10
- import { reactive } from './proxy'
11
-
12
- /**
13
- * Converts an iterator to a generator that yields reactive values
14
- */
15
- export function* makeReactiveIterator<T>(iterator: Iterator<T>): Generator<T> {
16
- let result = iterator.next()
17
- while (!result.done) {
18
- yield reactive(result.value)
19
- result = iterator.next()
20
- }
21
- }
22
-
23
- /**
24
- * Converts an iterator of key-value pairs to a generator that yields reactive key-value pairs
25
- */
26
- export function* makeReactiveEntriesIterator<K, V>(iterator: Iterator<[K, V]>): Generator<[K, V]> {
27
- let result = iterator.next()
28
- while (!result.done) {
29
- const [key, value] = result.value
30
- yield [reactive(key), reactive(value)]
31
- result = iterator.next()
32
- }
33
- }
34
-
35
- export {
36
- absent,
37
- immutables,
38
- isNonReactive,
39
- nonReactive,
40
- nonReactiveClass,
41
- nonReactiveObjects,
42
- registerNativeReactivity,
43
- }
@@ -1,107 +0,0 @@
1
- # project
2
-
3
- This document explains how `project(...)` composes reactive effects, and why it uses `ascend(...)` when creating per-item/per-key effects.
4
-
5
- ## What `project` builds
6
-
7
- `project` is a reactive “mapping” primitive.
8
-
9
- - A **source** collection (`Array`, `Map`, `Register`, plain record)
10
- - A **target** collection of the same shape
11
- - A set of effects that keep target in sync with source:
12
- - **structure effect** (length/keys watcher)
13
- - **item effect** per key/index (computes the projected value)
14
-
15
- The important property is **stability**: changing one source entry should only re-run the effect that owns that entry.
16
-
17
- ## Effects involved
18
-
19
- For `project.array(source, apply)` the implementation creates:
20
-
21
- - **Length effect**: watches the set of valid indexes by reading `source.length`.
22
- - **Index effects**: created lazily (when an index appears), one effect per index.
23
-
24
- The other variants (`project.map`, `project.record`, `project.register`) follow the same pattern:
25
-
26
- - a *keys effect* (watches which keys exist)
27
- - a *key effect* per key
28
-
29
- ## Cleanup and “garbage collection”
30
-
31
- `effect(...)` has two cleanup pathways:
32
-
33
- - **explicit**: you keep the returned stop function and call it.
34
- - **GC-driven** (Node with `--expose-gc`): for **root effects only**, a `FinalizationRegistry` calls `stopEffect()` when the stop function itself becomes unreachable.
35
-
36
- Parenting matters because only root effects are eligible for automatic GC cleanup.
37
-
38
- ### Parent-child relationship
39
-
40
- When an effect is created while another effect is active, it becomes a **child**:
41
-
42
- - the parent keeps a reference to a child-cleanup function
43
- - when the parent is cleaned up, it calls child-cleanups
44
-
45
- This guarantees that **unstored** children do not leak and do not rely on GC.
46
-
47
- ## Why `project` uses `ascend(...)`
48
-
49
- In `project.ts`, per-item effects are created inside the structure effect.
50
-
51
- If we created item effects “normally” inside the structure effect body, they would be parented to the structure effect.
52
-
53
- That is *not* what we want:
54
-
55
- - The structure effect re-runs whenever the set of keys/indexes changes.
56
- - On each re-run, `effect(...)` executes the previous cleanup first.
57
- - If item effects were children of the structure effect, that cleanup would stop all item effects on every structural update.
58
-
59
- That would break stability:
60
-
61
- - pushing a new element would unnecessarily dispose and recreate existing item effects
62
- - nested projections would be especially unstable
63
-
64
- ### What `ascend` does here
65
-
66
- `ascend` is a wrapper around `effectHistory.zoned`.
67
-
68
- It executes a function in the **parent effect context** (the effect that was active when the structure effect itself was created).
69
-
70
- So the parent chain becomes:
71
-
72
- - owning effect (the effect that called `project(...)`)
73
- - structure effect (length/keys)
74
- - item effects (per index/key)
75
-
76
- All of them share the same owner.
77
-
78
- This gives the intended semantics:
79
-
80
- - structure effect may re-run many times without killing item effects
81
- - when the owning effect stops (explicitly or by GC), it will stop:
82
- - the structure effect
83
- - all item effects
84
- - all nested projections under those item effects
85
-
86
- ## Projection context
87
-
88
- During an item effect’s first run, `project` calls:
89
-
90
- - `setActiveProjection({ source, key, target, depth, parent })`
91
-
92
- This attaches a `ProjectionContext` to the currently running effect via `effectProjectionMetadata`.
93
-
94
- `getActiveProjection()` is how nested projections discover their parent projection.
95
-
96
- The goal is to allow:
97
-
98
- - a nested projection to compute its own `depth`
99
- - a nested projection to know its parent projection (for debugging/introspection)
100
-
101
- ## Testing expectations
102
-
103
- The behavior that matters (and that tests should lock down) is:
104
-
105
- - item effects are not disposed on structural updates of the same projection
106
- - disposing an outer item stops all nested projection effects under that item
107
- - disposing the owning effect (or letting it be GC’ed) stops all projection effects created under it