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.
- package/README.md +60 -35
- package/dist/browser.cjs +1333 -1760
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +2 -1392
- package/dist/browser.dev.cjs +114 -0
- package/dist/browser.dev.cjs.map +1 -0
- package/dist/browser.dev.d.ts +2 -0
- package/dist/browser.dev.esm.js +5 -0
- package/dist/browser.dev.esm.js.map +1 -0
- package/dist/browser.esm.js +18 -97
- package/dist/browser.esm.js.map +1 -1
- package/dist/chunks/{async-browser-lvzLOCgk.cjs → async-browser-Dgr5CreQ.cjs} +16 -95
- package/dist/chunks/async-browser-Dgr5CreQ.cjs.map +1 -0
- package/dist/chunks/{async-node-C3DeIb0y.cjs → async-node-3PrbVAbB.cjs} +3 -1
- package/dist/chunks/async-node-3PrbVAbB.cjs.map +1 -0
- package/dist/chunks/index-Sf74wXTV.esm.js +2577 -0
- package/dist/chunks/index-Sf74wXTV.esm.js.map +1 -0
- package/dist/chunks/node-Bo7WU5S2.esm.js +96 -0
- package/dist/chunks/node-Bo7WU5S2.esm.js.map +1 -0
- package/dist/chunks/{index-VTO-b2vR.cjs → proxy-Cc79Lrzj.cjs} +2593 -3216
- package/dist/chunks/proxy-Cc79Lrzj.cjs.map +1 -0
- package/dist/chunks/{index-CtA2AWl3.esm.js → proxy-D2C49sXH.esm.js} +2570 -3174
- package/dist/chunks/proxy-D2C49sXH.esm.js.map +1 -0
- package/dist/debug.cjs +987 -28
- package/dist/debug.cjs.map +1 -1
- package/dist/debug.d.ts +37 -38
- package/dist/debug.esm.js +963 -1
- package/dist/debug.esm.js.map +1 -1
- package/dist/devtools/manifest.json +1 -1
- package/dist/devtools/panel.html +1 -1
- package/dist/devtools/panel.js +107 -94
- package/dist/devtools/panel.js.map +1 -1
- package/dist/index.d.ts +1322 -1
- package/dist/mutts.umd.js +6647 -1
- package/dist/mutts.umd.js.map +1 -1
- package/dist/mutts.umd.min.js +1 -1
- package/dist/mutts.umd.min.js.map +1 -1
- package/dist/node.cjs +69 -63
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.dev.cjs +114 -0
- package/dist/node.dev.cjs.map +1 -0
- package/dist/node.dev.d.ts +2 -0
- package/dist/node.dev.esm.js +6 -0
- package/dist/node.dev.esm.js.map +1 -0
- package/dist/node.esm.js +4 -97
- package/dist/node.esm.js.map +1 -1
- package/dist/{types-DaHFfhlN.d.ts → types-Bx2PhORg.d.ts} +134 -88
- package/docs/ai/api-reference.md +11 -32
- package/docs/ai/manual.md +297 -239
- package/docs/reactive/advanced.md +318 -9
- package/docs/reactive/attend.md +2 -4
- package/docs/reactive/collections.md +22 -187
- package/docs/reactive/core.md +223 -131
- package/docs/reactive/debugging.md +119 -12
- package/docs/reactive/error-handling.md +10 -10
- package/docs/reactive/resource.md +125 -0
- package/docs/reactive.md +3 -4
- package/docs/utils.md +70 -0
- package/docs/zone.md +1 -1
- package/package.json +76 -38
- package/dist/chunks/async-browser-lvzLOCgk.cjs.map +0 -1
- package/dist/chunks/async-node-C3DeIb0y.cjs.map +0 -1
- package/dist/chunks/index-2vea86wD.esm.js +0 -3011
- package/dist/chunks/index-2vea86wD.esm.js.map +0 -1
- package/dist/chunks/index-CtA2AWl3.esm.js.map +0 -1
- package/dist/chunks/index-VTO-b2vR.cjs.map +0 -1
- package/dist/debug/debug.d.ts +0 -122
- package/dist/debug/debug.d.ts.map +0 -1
- package/dist/debug/index.d.ts +0 -4
- package/dist/debug/index.d.ts.map +0 -1
- package/dist/debug/lineage-panel.d.ts +0 -5
- package/dist/debug/lineage-panel.d.ts.map +0 -1
- package/dist/debug/lineage.d.ts +0 -79
- package/dist/debug/lineage.d.ts.map +0 -1
- package/dist/src/async/browser.d.ts +0 -2
- package/dist/src/async/browser.d.ts.map +0 -1
- package/dist/src/async/index.d.ts +0 -19
- package/dist/src/async/index.d.ts.map +0 -1
- package/dist/src/async/node.d.ts +0 -2
- package/dist/src/async/node.d.ts.map +0 -1
- package/dist/src/decorator.d.ts +0 -106
- package/dist/src/decorator.d.ts.map +0 -1
- package/dist/src/destroyable.d.ts +0 -87
- package/dist/src/destroyable.d.ts.map +0 -1
- package/dist/src/entry-browser.d.ts +0 -3
- package/dist/src/entry-browser.d.ts.map +0 -1
- package/dist/src/entry-node.d.ts +0 -3
- package/dist/src/entry-node.d.ts.map +0 -1
- package/dist/src/eventful.d.ts +0 -20
- package/dist/src/eventful.d.ts.map +0 -1
- package/dist/src/flavored.d.ts +0 -33
- package/dist/src/flavored.d.ts.map +0 -1
- package/dist/src/index.d.ts +0 -14
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/indexable.d.ts +0 -243
- package/dist/src/indexable.d.ts.map +0 -1
- package/dist/src/introspection.d.ts +0 -27
- package/dist/src/introspection.d.ts.map +0 -1
- package/dist/src/iterableWeak.d.ts +0 -53
- package/dist/src/iterableWeak.d.ts.map +0 -1
- package/dist/src/mixins.d.ts +0 -25
- package/dist/src/mixins.d.ts.map +0 -1
- package/dist/src/promiseChain.d.ts +0 -20
- package/dist/src/promiseChain.d.ts.map +0 -1
- package/dist/src/reactive/array.d.ts +0 -48
- package/dist/src/reactive/array.d.ts.map +0 -1
- package/dist/src/reactive/buffer.d.ts +0 -120
- package/dist/src/reactive/buffer.d.ts.map +0 -1
- package/dist/src/reactive/change.d.ts +0 -29
- package/dist/src/reactive/change.d.ts.map +0 -1
- package/dist/src/reactive/deep-touch.d.ts +0 -28
- package/dist/src/reactive/deep-touch.d.ts.map +0 -1
- package/dist/src/reactive/deep-watch-state.d.ts +0 -25
- package/dist/src/reactive/deep-watch-state.d.ts.map +0 -1
- package/dist/src/reactive/deep-watch.d.ts +0 -20
- package/dist/src/reactive/deep-watch.d.ts.map +0 -1
- package/dist/src/reactive/describe.d.ts +0 -12
- package/dist/src/reactive/describe.d.ts.map +0 -1
- package/dist/src/reactive/effect-context.d.ts +0 -34
- package/dist/src/reactive/effect-context.d.ts.map +0 -1
- package/dist/src/reactive/effects.d.ts +0 -164
- package/dist/src/reactive/effects.d.ts.map +0 -1
- package/dist/src/reactive/index.d.ts +0 -19
- package/dist/src/reactive/index.d.ts.map +0 -1
- package/dist/src/reactive/map.d.ts +0 -28
- package/dist/src/reactive/map.d.ts.map +0 -1
- package/dist/src/reactive/memoize.d.ts +0 -28
- package/dist/src/reactive/memoize.d.ts.map +0 -1
- package/dist/src/reactive/non-reactive-state.d.ts +0 -9
- package/dist/src/reactive/non-reactive-state.d.ts.map +0 -1
- package/dist/src/reactive/non-reactive.d.ts +0 -11
- package/dist/src/reactive/non-reactive.d.ts.map +0 -1
- package/dist/src/reactive/project.d.ts +0 -40
- package/dist/src/reactive/project.d.ts.map +0 -1
- package/dist/src/reactive/proxy-state.d.ts +0 -8
- package/dist/src/reactive/proxy-state.d.ts.map +0 -1
- package/dist/src/reactive/proxy.d.ts +0 -23
- package/dist/src/reactive/proxy.d.ts.map +0 -1
- package/dist/src/reactive/record.d.ts +0 -115
- package/dist/src/reactive/record.d.ts.map +0 -1
- package/dist/src/reactive/register.d.ts +0 -125
- package/dist/src/reactive/register.d.ts.map +0 -1
- package/dist/src/reactive/registry.d.ts +0 -21
- package/dist/src/reactive/registry.d.ts.map +0 -1
- package/dist/src/reactive/set.d.ts +0 -26
- package/dist/src/reactive/set.d.ts.map +0 -1
- package/dist/src/reactive/tracking.d.ts +0 -7
- package/dist/src/reactive/tracking.d.ts.map +0 -1
- package/dist/src/reactive/types.d.ts +0 -424
- package/dist/src/reactive/types.d.ts.map +0 -1
- package/dist/src/reactive/watch.d.ts +0 -48
- package/dist/src/reactive/watch.d.ts.map +0 -1
- package/dist/src/std-decorators.d.ts +0 -45
- package/dist/src/std-decorators.d.ts.map +0 -1
- package/dist/src/utils.d.ts +0 -49
- package/dist/src/utils.d.ts.map +0 -1
- package/dist/src/zone.d.ts +0 -40
- package/dist/src/zone.d.ts.map +0 -1
- package/docs/reactive/describe.md +0 -85
- package/docs/reactive/project.md +0 -93
- package/docs/reactive/scan.md +0 -293
- package/src/async/browser.ts +0 -323
- package/src/async/index.ts +0 -27
- package/src/async/node.ts +0 -92
- package/src/decorator.ts +0 -272
- package/src/destroyable.ts +0 -199
- package/src/entry-browser.ts +0 -5
- package/src/entry-node.ts +0 -5
- package/src/eventful.ts +0 -110
- package/src/flavored.ts +0 -106
- package/src/index.d.ts +0 -12
- package/src/index.ts +0 -64
- package/src/indexable.ts +0 -526
- package/src/introspection.ts +0 -59
- package/src/iterableWeak.ts +0 -233
- package/src/mixins.ts +0 -123
- package/src/promiseChain.ts +0 -110
- package/src/reactive/array.ts +0 -500
- package/src/reactive/buffer.ts +0 -328
- package/src/reactive/change.ts +0 -131
- package/src/reactive/deep-touch.ts +0 -273
- package/src/reactive/deep-watch-state.ts +0 -82
- package/src/reactive/deep-watch.ts +0 -171
- package/src/reactive/describe.ts +0 -39
- package/src/reactive/effect-context.ts +0 -83
- package/src/reactive/effects.ts +0 -1434
- package/src/reactive/index.ts +0 -72
- package/src/reactive/map.ts +0 -142
- package/src/reactive/memoize.ts +0 -186
- package/src/reactive/non-reactive-state.ts +0 -49
- package/src/reactive/non-reactive.ts +0 -43
- package/src/reactive/project.md +0 -107
- package/src/reactive/project.ts +0 -430
- package/src/reactive/proxy-state.ts +0 -27
- package/src/reactive/proxy.ts +0 -282
- package/src/reactive/record.ts +0 -181
- package/src/reactive/register.ts +0 -538
- package/src/reactive/registry.ts +0 -72
- package/src/reactive/set.ts +0 -117
- package/src/reactive/tracking.ts +0 -41
- package/src/reactive/types.ts +0 -520
- package/src/reactive/watch.ts +0 -180
- package/src/std-decorators.ts +0 -256
- package/src/utils.ts +0 -300
- package/src/zone.ts +0 -142
package/src/reactive/index.ts
DELETED
|
@@ -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
|
-
}
|
package/src/reactive/map.ts
DELETED
|
@@ -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
|
-
}
|
package/src/reactive/memoize.ts
DELETED
|
@@ -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
|
-
}
|
package/src/reactive/project.md
DELETED
|
@@ -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
|