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,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