mutts 1.0.5 → 1.0.7
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 +2 -1
- package/dist/browser.d.ts +2 -0
- package/dist/browser.esm.js +70 -0
- package/dist/browser.esm.js.map +1 -0
- package/dist/browser.js +161 -0
- package/dist/browser.js.map +1 -0
- package/dist/chunks/{index-Cvxdw6Ax.js → index-BFYK02LG.js} +5377 -4059
- package/dist/chunks/index-BFYK02LG.js.map +1 -0
- package/dist/chunks/{index-qiWwozOc.esm.js → index-CNR6QRUl.esm.js} +5247 -3963
- package/dist/chunks/index-CNR6QRUl.esm.js.map +1 -0
- package/dist/mutts.umd.js +1 -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.d.ts +2 -0
- package/dist/node.esm.js +45 -0
- package/dist/node.esm.js.map +1 -0
- package/dist/node.js +136 -0
- package/dist/node.js.map +1 -0
- package/docs/ai/api-reference.md +0 -2
- package/docs/ai/manual.md +14 -95
- package/docs/reactive/advanced.md +7 -111
- package/docs/reactive/collections.md +0 -125
- package/docs/reactive/core.md +27 -24
- package/docs/reactive/debugging.md +168 -0
- package/docs/reactive/project.md +1 -1
- package/docs/reactive/scan.md +78 -0
- package/docs/reactive.md +8 -6
- package/docs/std-decorators.md +1 -0
- package/docs/zone.md +88 -0
- package/package.json +47 -65
- package/src/async/browser.ts +87 -0
- package/src/async/index.ts +8 -0
- package/src/async/node.ts +46 -0
- package/src/decorator.ts +15 -9
- package/src/destroyable.ts +4 -4
- package/src/index.ts +54 -0
- package/src/indexable.ts +42 -0
- package/src/mixins.ts +2 -2
- package/src/reactive/array.ts +149 -141
- package/src/reactive/buffer.ts +168 -0
- package/src/reactive/change.ts +3 -3
- package/src/reactive/debug.ts +1 -1
- package/src/reactive/deep-touch.ts +1 -1
- package/src/reactive/deep-watch.ts +1 -1
- package/src/reactive/effect-context.ts +15 -91
- package/src/reactive/effects.ts +138 -170
- package/src/reactive/index.ts +10 -13
- package/src/reactive/interface.ts +20 -33
- package/src/reactive/map.ts +48 -61
- package/src/reactive/memoize.ts +87 -31
- package/src/reactive/project.ts +43 -22
- package/src/reactive/proxy.ts +18 -43
- package/src/reactive/record.ts +3 -3
- package/src/reactive/register.ts +5 -7
- package/src/reactive/registry.ts +59 -0
- package/src/reactive/set.ts +42 -56
- package/src/reactive/tracking.ts +5 -62
- package/src/reactive/types.ts +79 -19
- package/src/std-decorators.ts +9 -9
- package/src/utils.ts +203 -19
- package/src/zone.ts +127 -0
- package/dist/chunks/_tslib-BgjropY9.js +0 -81
- package/dist/chunks/_tslib-BgjropY9.js.map +0 -1
- package/dist/chunks/_tslib-Mzh1rNsX.esm.js +0 -75
- package/dist/chunks/_tslib-Mzh1rNsX.esm.js.map +0 -1
- package/dist/chunks/decorator-DLvrD0UF.js +0 -265
- package/dist/chunks/decorator-DLvrD0UF.js.map +0 -1
- package/dist/chunks/decorator-DqiszP7i.esm.js +0 -253
- package/dist/chunks/decorator-DqiszP7i.esm.js.map +0 -1
- package/dist/chunks/index-Cvxdw6Ax.js.map +0 -1
- package/dist/chunks/index-qiWwozOc.esm.js.map +0 -1
- package/dist/decorator.d.ts +0 -107
- package/dist/decorator.esm.js +0 -2
- package/dist/decorator.esm.js.map +0 -1
- package/dist/decorator.js +0 -11
- package/dist/decorator.js.map +0 -1
- package/dist/destroyable.d.ts +0 -90
- package/dist/destroyable.esm.js +0 -109
- package/dist/destroyable.esm.js.map +0 -1
- package/dist/destroyable.js +0 -116
- package/dist/destroyable.js.map +0 -1
- package/dist/eventful.d.ts +0 -20
- package/dist/eventful.esm.js +0 -66
- package/dist/eventful.esm.js.map +0 -1
- package/dist/eventful.js +0 -68
- package/dist/eventful.js.map +0 -1
- package/dist/index.d.ts +0 -19
- package/dist/index.esm.js +0 -8
- package/dist/index.esm.js.map +0 -1
- package/dist/index.js +0 -95
- package/dist/index.js.map +0 -1
- package/dist/indexable.d.ts +0 -243
- package/dist/indexable.esm.js +0 -285
- package/dist/indexable.esm.js.map +0 -1
- package/dist/indexable.js +0 -291
- package/dist/indexable.js.map +0 -1
- package/dist/promiseChain.d.ts +0 -21
- package/dist/promiseChain.esm.js +0 -78
- package/dist/promiseChain.esm.js.map +0 -1
- package/dist/promiseChain.js +0 -80
- package/dist/promiseChain.js.map +0 -1
- package/dist/reactive.d.ts +0 -885
- package/dist/reactive.esm.js +0 -5
- package/dist/reactive.esm.js.map +0 -1
- package/dist/reactive.js +0 -59
- package/dist/reactive.js.map +0 -1
- package/dist/std-decorators.d.ts +0 -52
- package/dist/std-decorators.esm.js +0 -196
- package/dist/std-decorators.esm.js.map +0 -1
- package/dist/std-decorators.js +0 -204
- package/dist/std-decorators.js.map +0 -1
- package/src/reactive/mapped.ts +0 -129
- package/src/reactive/zone.ts +0 -208
package/src/reactive/map.ts
CHANGED
|
@@ -1,56 +1,47 @@
|
|
|
1
|
+
import { contentRef } from '../utils'
|
|
1
2
|
import { touched, touched1 } from './change'
|
|
2
3
|
import { notifyPropertyChange } from './deep-touch'
|
|
3
4
|
import { makeReactiveEntriesIterator, makeReactiveIterator } from './non-reactive'
|
|
4
5
|
import { reactive } from './proxy'
|
|
5
6
|
import { dependant } from './tracking'
|
|
6
|
-
import { prototypeForwarding } from './types'
|
|
7
|
-
|
|
8
|
-
const native = Symbol('native')
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
9
|
* Reactive wrapper around JavaScript's WeakMap class
|
|
12
10
|
* Only tracks individual key operations, no size tracking (WeakMap limitation)
|
|
13
11
|
*/
|
|
14
|
-
export class ReactiveWeakMap<K extends object, V> {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
constructor(original: WeakMap<K, V>) {
|
|
18
|
-
Object.defineProperties(this, {
|
|
19
|
-
[native]: { value: original },
|
|
20
|
-
[prototypeForwarding]: { value: original },
|
|
21
|
-
content: { value: Symbol('WeakMapContent') },
|
|
22
|
-
[Symbol.toStringTag]: { value: 'ReactiveWeakMap' },
|
|
23
|
-
})
|
|
12
|
+
export abstract class ReactiveWeakMap<K extends object, V> extends WeakMap<K, V> {
|
|
13
|
+
get [Symbol.toStringTag]() {
|
|
14
|
+
return 'ReactiveWeakMap'
|
|
24
15
|
}
|
|
25
16
|
|
|
26
17
|
// Implement WeakMap interface methods with reactivity
|
|
27
18
|
delete(key: K): boolean {
|
|
28
|
-
const hadKey = this
|
|
29
|
-
const result = this
|
|
19
|
+
const hadKey = this.has(key)
|
|
20
|
+
const result = this.delete(key)
|
|
30
21
|
|
|
31
|
-
if (hadKey) touched1(this
|
|
22
|
+
if (hadKey) touched1(contentRef(this), { type: 'del', prop: key }, key)
|
|
32
23
|
|
|
33
24
|
return result
|
|
34
25
|
}
|
|
35
26
|
|
|
36
27
|
get(key: K): V | undefined {
|
|
37
|
-
dependant(this
|
|
38
|
-
return reactive(this
|
|
28
|
+
dependant(contentRef(this), key)
|
|
29
|
+
return reactive(this.get(key))
|
|
39
30
|
}
|
|
40
31
|
|
|
41
32
|
has(key: K): boolean {
|
|
42
|
-
dependant(this
|
|
43
|
-
return this
|
|
33
|
+
dependant(contentRef(this), key)
|
|
34
|
+
return this.has(key)
|
|
44
35
|
}
|
|
45
36
|
|
|
46
37
|
set(key: K, value: V): this {
|
|
47
|
-
const hadKey = this
|
|
48
|
-
const oldValue = this
|
|
38
|
+
const hadKey = this.has(key)
|
|
39
|
+
const oldValue = this.get(key)
|
|
49
40
|
const reactiveValue = reactive(value)
|
|
50
|
-
this
|
|
41
|
+
this.set(key, reactiveValue)
|
|
51
42
|
|
|
52
43
|
if (!hadKey || oldValue !== reactiveValue) {
|
|
53
|
-
notifyPropertyChange(this
|
|
44
|
+
notifyPropertyChange(contentRef(this), key, oldValue, reactiveValue, hadKey)
|
|
54
45
|
}
|
|
55
46
|
|
|
56
47
|
return this
|
|
@@ -61,60 +52,52 @@ export class ReactiveWeakMap<K extends object, V> {
|
|
|
61
52
|
* Reactive wrapper around JavaScript's Map class
|
|
62
53
|
* Tracks size changes, individual key operations, and collection-wide operations
|
|
63
54
|
*/
|
|
64
|
-
export class ReactiveMap<K, V> {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
constructor(original: Map<K, V>) {
|
|
69
|
-
Object.defineProperties(this, {
|
|
70
|
-
[native]: { value: original },
|
|
71
|
-
[prototypeForwarding]: { value: original },
|
|
72
|
-
content: { value: Symbol('MapContent') },
|
|
73
|
-
[Symbol.toStringTag]: { value: 'ReactiveMap' },
|
|
74
|
-
})
|
|
55
|
+
export abstract class ReactiveMap<K, V> extends Map<K, V> {
|
|
56
|
+
get [Symbol.toStringTag]() {
|
|
57
|
+
return 'ReactiveMap'
|
|
75
58
|
}
|
|
76
59
|
|
|
77
60
|
// Implement Map interface methods with reactivity
|
|
78
61
|
get size(): number {
|
|
79
62
|
dependant(this, 'size') // The ReactiveMap instance still goes through proxy
|
|
80
|
-
return this
|
|
63
|
+
return this.size
|
|
81
64
|
}
|
|
82
65
|
|
|
83
66
|
clear(): void {
|
|
84
|
-
const hadEntries = this
|
|
85
|
-
this
|
|
67
|
+
const hadEntries = this.size > 0
|
|
68
|
+
this.clear()
|
|
86
69
|
|
|
87
70
|
if (hadEntries) {
|
|
88
71
|
const evolution = { type: 'bunch', method: 'clear' } as const
|
|
89
72
|
// Clear triggers all effects since all keys are affected
|
|
90
73
|
touched1(this, evolution, 'size')
|
|
91
|
-
touched(this
|
|
74
|
+
touched(contentRef(this), evolution)
|
|
92
75
|
}
|
|
93
76
|
}
|
|
94
77
|
|
|
95
78
|
entries(): Generator<[K, V]> {
|
|
96
|
-
dependant(this
|
|
97
|
-
return makeReactiveEntriesIterator(this
|
|
79
|
+
dependant(contentRef(this))
|
|
80
|
+
return makeReactiveEntriesIterator(this.entries())
|
|
98
81
|
}
|
|
99
82
|
|
|
100
83
|
forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void {
|
|
101
|
-
dependant(this
|
|
102
|
-
this
|
|
84
|
+
dependant(contentRef(this))
|
|
85
|
+
this.forEach(callbackfn, thisArg)
|
|
103
86
|
}
|
|
104
87
|
|
|
105
88
|
keys(): MapIterator<K> {
|
|
106
|
-
dependant(this
|
|
107
|
-
return this
|
|
89
|
+
dependant(contentRef(this))
|
|
90
|
+
return this.keys()
|
|
108
91
|
}
|
|
109
92
|
|
|
110
93
|
values(): Generator<V> {
|
|
111
|
-
dependant(this
|
|
112
|
-
return makeReactiveIterator(this
|
|
94
|
+
dependant(contentRef(this))
|
|
95
|
+
return makeReactiveIterator(this.values())
|
|
113
96
|
}
|
|
114
97
|
|
|
115
|
-
[Symbol.iterator]():
|
|
116
|
-
dependant(this
|
|
117
|
-
const nativeIterator = this[
|
|
98
|
+
[Symbol.iterator](): MapIterator<[K, V]> {
|
|
99
|
+
dependant(contentRef(this))
|
|
100
|
+
const nativeIterator = this[Symbol.iterator]()
|
|
118
101
|
return {
|
|
119
102
|
next() {
|
|
120
103
|
const result = nativeIterator.next()
|
|
@@ -126,17 +109,21 @@ export class ReactiveMap<K, V> {
|
|
|
126
109
|
done: false,
|
|
127
110
|
}
|
|
128
111
|
},
|
|
112
|
+
[Symbol.iterator]() {
|
|
113
|
+
return this
|
|
114
|
+
},
|
|
115
|
+
[Symbol.dispose]() {},
|
|
129
116
|
}
|
|
130
117
|
}
|
|
131
118
|
|
|
132
119
|
// Implement Map methods with reactivity
|
|
133
120
|
delete(key: K): boolean {
|
|
134
|
-
const hadKey = this
|
|
135
|
-
const result = this
|
|
121
|
+
const hadKey = this.has(key)
|
|
122
|
+
const result = this.delete(key)
|
|
136
123
|
|
|
137
124
|
if (hadKey) {
|
|
138
125
|
const evolution = { type: 'del', prop: key } as const
|
|
139
|
-
touched1(this
|
|
126
|
+
touched1(contentRef(this), evolution, key)
|
|
140
127
|
touched1(this, evolution, 'size')
|
|
141
128
|
}
|
|
142
129
|
|
|
@@ -144,23 +131,23 @@ export class ReactiveMap<K, V> {
|
|
|
144
131
|
}
|
|
145
132
|
|
|
146
133
|
get(key: K): V | undefined {
|
|
147
|
-
dependant(this
|
|
148
|
-
return reactive(this
|
|
134
|
+
dependant(contentRef(this), key)
|
|
135
|
+
return reactive(this.get(key))
|
|
149
136
|
}
|
|
150
137
|
|
|
151
138
|
has(key: K): boolean {
|
|
152
|
-
dependant(this
|
|
153
|
-
return this
|
|
139
|
+
dependant(contentRef(this), key)
|
|
140
|
+
return this.has(key)
|
|
154
141
|
}
|
|
155
142
|
|
|
156
143
|
set(key: K, value: V): this {
|
|
157
|
-
const hadKey = this
|
|
158
|
-
const oldValue = this
|
|
144
|
+
const hadKey = this.has(key)
|
|
145
|
+
const oldValue = this.get(key)
|
|
159
146
|
const reactiveValue = reactive(value)
|
|
160
|
-
this
|
|
147
|
+
this.set(key, reactiveValue)
|
|
161
148
|
|
|
162
149
|
if (!hadKey || oldValue !== reactiveValue) {
|
|
163
|
-
notifyPropertyChange(this
|
|
150
|
+
notifyPropertyChange(contentRef(this), key, oldValue, reactiveValue, hadKey)
|
|
164
151
|
// Also notify size change for Map (WeakMap doesn't track size)
|
|
165
152
|
const evolution = { type: hadKey ? 'set' : 'add', prop: key } as const
|
|
166
153
|
touched1(this, evolution, 'size')
|
package/src/reactive/memoize.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { decorator } from '../decorator'
|
|
2
|
-
import { renamed } from '../utils'
|
|
2
|
+
import { deepCompare, renamed } from '../utils'
|
|
3
3
|
import { touched1 } from './change'
|
|
4
|
-
import { effect, root } from './effects'
|
|
5
|
-
import {
|
|
4
|
+
import { effect, root, untracked } from './effects'
|
|
5
|
+
import { getRoot, markWithRoot } from './registry'
|
|
6
|
+
import { dependant } from './tracking'
|
|
7
|
+
import { options, rootFunction } from './types'
|
|
6
8
|
|
|
7
9
|
export type Memoizable = object | any[] | symbol | ((...args: any[]) => any)
|
|
8
10
|
|
|
@@ -12,7 +14,8 @@ type MemoCacheTree<Result> = {
|
|
|
12
14
|
branches?: WeakMap<Memoizable, MemoCacheTree<Result>>
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
const memoizedRegistry = new WeakMap<
|
|
17
|
+
const memoizedRegistry = new WeakMap<any, Function>()
|
|
18
|
+
const wrapperRegistry = new WeakMap<Function, Function>()
|
|
16
19
|
|
|
17
20
|
function getBranch<Result>(tree: MemoCacheTree<Result>, key: Memoizable): MemoCacheTree<Result> {
|
|
18
21
|
tree.branches ??= new WeakMap()
|
|
@@ -38,18 +41,33 @@ function memoizeFunction<Result, Args extends Memoizable[]>(
|
|
|
38
41
|
throw new Error('memoize expects non-null object arguments')
|
|
39
42
|
|
|
40
43
|
let node: MemoCacheTree<Result> = cacheRoot
|
|
44
|
+
// Note: decorators add `this` as first argument
|
|
41
45
|
for (const arg of localArgs) {
|
|
42
46
|
node = getBranch(node, arg)
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
dependant(node, 'memoize')
|
|
46
|
-
if ('result' in node)
|
|
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
|
+
options.onMemoizationDiscrepancy(node.result, fresh, fn, localArgs, 'calculation')
|
|
58
|
+
}
|
|
59
|
+
} finally {
|
|
60
|
+
options.isVerificationRun = wasVerification
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return node.result!
|
|
64
|
+
}
|
|
47
65
|
|
|
48
66
|
// Create memoize internal effect to track dependencies and invalidate cache
|
|
49
67
|
// Use untracked to prevent the effect creation from being affected by parent effects
|
|
50
68
|
node.cleanup = root(() =>
|
|
51
69
|
effect(
|
|
52
|
-
|
|
70
|
+
() => {
|
|
53
71
|
// Execute the function and track its dependencies
|
|
54
72
|
// The function execution will automatically track dependencies on reactive objects
|
|
55
73
|
node.result = fn(...localArgs)
|
|
@@ -57,11 +75,31 @@ function memoizeFunction<Result, Args extends Memoizable[]>(
|
|
|
57
75
|
// When dependencies change, clear the cache and notify consumers
|
|
58
76
|
delete node.result
|
|
59
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
|
+
}
|
|
60
84
|
}
|
|
61
|
-
},
|
|
85
|
+
},
|
|
62
86
|
{ opaque: true }
|
|
63
87
|
)
|
|
64
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
|
+
options.onMemoizationDiscrepancy(node.result, fresh, fn, localArgs, 'comparison')
|
|
97
|
+
}
|
|
98
|
+
} finally {
|
|
99
|
+
options.isVerificationRun = wasVerification
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
65
103
|
return node.result!
|
|
66
104
|
}, fn)
|
|
67
105
|
|
|
@@ -71,35 +109,53 @@ function memoizeFunction<Result, Args extends Memoizable[]>(
|
|
|
71
109
|
}
|
|
72
110
|
|
|
73
111
|
export const memoize = decorator({
|
|
74
|
-
getter(original, propertyKey) {
|
|
75
|
-
const memoized = memoizeFunction(
|
|
76
|
-
markWithRoot(
|
|
77
|
-
renamed(
|
|
78
|
-
(that: object) => {
|
|
79
|
-
return original.call(that)
|
|
80
|
-
},
|
|
81
|
-
`${String(this.constructor.name)}.${String(propertyKey)}`
|
|
82
|
-
),
|
|
83
|
-
original
|
|
84
|
-
)
|
|
85
|
-
)
|
|
112
|
+
getter(original, target, propertyKey) {
|
|
86
113
|
return function (this: any) {
|
|
114
|
+
let wrapper = wrapperRegistry.get(original)
|
|
115
|
+
if (!wrapper) {
|
|
116
|
+
wrapper = markWithRoot(
|
|
117
|
+
renamed(
|
|
118
|
+
(that: object) => {
|
|
119
|
+
return original.call(that)
|
|
120
|
+
},
|
|
121
|
+
`${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(propertyKey)}`
|
|
122
|
+
),
|
|
123
|
+
{
|
|
124
|
+
method: original,
|
|
125
|
+
propertyKey,
|
|
126
|
+
...((original as any)[rootFunction]
|
|
127
|
+
? { [rootFunction]: (original as any)[rootFunction] }
|
|
128
|
+
: {}),
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
wrapperRegistry.set(original, wrapper)
|
|
132
|
+
}
|
|
133
|
+
const memoized = memoizeFunction(wrapper as any)
|
|
87
134
|
return memoized(this)
|
|
88
135
|
}
|
|
89
136
|
},
|
|
90
|
-
method(original, name) {
|
|
91
|
-
const memoized = memoizeFunction(
|
|
92
|
-
markWithRoot(
|
|
93
|
-
renamed(
|
|
94
|
-
(that: object, ...args: object[]) => {
|
|
95
|
-
return original.call(that, ...args)
|
|
96
|
-
},
|
|
97
|
-
`${String(this.constructor.name)}.${String(name)}`
|
|
98
|
-
),
|
|
99
|
-
original
|
|
100
|
-
)
|
|
101
|
-
) as (...args: object[]) => unknown
|
|
137
|
+
method(original, target, name) {
|
|
102
138
|
return function (this: any, ...args: object[]) {
|
|
139
|
+
let wrapper = wrapperRegistry.get(original)
|
|
140
|
+
if (!wrapper) {
|
|
141
|
+
wrapper = markWithRoot(
|
|
142
|
+
renamed(
|
|
143
|
+
(that: object, ...args: object[]) => {
|
|
144
|
+
return original.call(that, ...args)
|
|
145
|
+
},
|
|
146
|
+
`${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(name)}`
|
|
147
|
+
),
|
|
148
|
+
{
|
|
149
|
+
method: original,
|
|
150
|
+
propertyKey: name,
|
|
151
|
+
...((original as any)[rootFunction]
|
|
152
|
+
? { [rootFunction]: (original as any)[rootFunction] }
|
|
153
|
+
: {}),
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
wrapperRegistry.set(original, wrapper)
|
|
157
|
+
}
|
|
158
|
+
const memoized = memoizeFunction(wrapper as any) as (...args: object[]) => unknown
|
|
103
159
|
return memoized(this, ...args)
|
|
104
160
|
}
|
|
105
161
|
},
|
package/src/reactive/project.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FoolProof } from '../utils'
|
|
2
2
|
import { setEffectName } from './debug'
|
|
3
3
|
import { getActiveEffect } from './effect-context'
|
|
4
4
|
import { effect, untracked } from './effects'
|
|
@@ -42,17 +42,10 @@ export type ProjectAccess<SourceValue, Key, SourceType, Target> = {
|
|
|
42
42
|
value: SourceValue
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
type
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
export type ProjectCallback<
|
|
50
|
-
SourceValue,
|
|
51
|
-
Key,
|
|
52
|
-
Target extends object,
|
|
53
|
-
SourceType,
|
|
54
|
-
Result,
|
|
55
|
-
> = BivariantProjectCallback<[ProjectAccess<SourceValue, Key, SourceType, Target>, Target], Result>
|
|
45
|
+
export type ProjectCallback<SourceValue, Key, Target extends object, SourceType, Result> = (
|
|
46
|
+
access: ProjectAccess<SourceValue, Key, SourceType, Target>,
|
|
47
|
+
target: Target
|
|
48
|
+
) => Result
|
|
56
49
|
|
|
57
50
|
export type ProjectResult<Target extends object> = Target & { [cleanup]: ScopedCallback }
|
|
58
51
|
|
|
@@ -92,12 +85,12 @@ function projectArray<SourceValue, ResultValue>(
|
|
|
92
85
|
source: readonly SourceValue[],
|
|
93
86
|
apply: ProjectCallback<SourceValue, number, ResultValue[], readonly SourceValue[], ResultValue>
|
|
94
87
|
): ProjectResult<ResultValue[]> {
|
|
95
|
-
|
|
88
|
+
source = reactive(source)
|
|
96
89
|
const target = reactive([] as ResultValue[])
|
|
97
90
|
const indexEffects = new Map<number, ScopedCallback>()
|
|
98
91
|
|
|
99
92
|
function normalizeTargetLength(length: number) {
|
|
100
|
-
|
|
93
|
+
FoolProof.set(target as unknown as object, 'length', length, target)
|
|
101
94
|
}
|
|
102
95
|
|
|
103
96
|
function disposeIndex(index: number) {
|
|
@@ -113,7 +106,7 @@ function projectArray<SourceValue, ResultValue>(
|
|
|
113
106
|
const depth = parent ? parent.depth + 1 : 0
|
|
114
107
|
|
|
115
108
|
const cleanupLength = effect(function projectArrayLengthEffect({ ascend }) {
|
|
116
|
-
const length =
|
|
109
|
+
const length = source.length
|
|
117
110
|
normalizeTargetLength(length)
|
|
118
111
|
const existing = Array.from(indexEffects.keys())
|
|
119
112
|
for (let i = 0; i < length; i++) {
|
|
@@ -124,10 +117,10 @@ function projectArray<SourceValue, ResultValue>(
|
|
|
124
117
|
const previous = untracked(() => target[index])
|
|
125
118
|
const accessBase = {
|
|
126
119
|
key: index,
|
|
127
|
-
source
|
|
128
|
-
get: () =>
|
|
120
|
+
source,
|
|
121
|
+
get: () => FoolProof.get(source as any, index, source),
|
|
129
122
|
set: (value: SourceValue) =>
|
|
130
|
-
|
|
123
|
+
FoolProof.set(source as any, index, value, source),
|
|
131
124
|
old: previous,
|
|
132
125
|
} as ProjectAccess<SourceValue, number, readonly SourceValue[], ResultValue[]>
|
|
133
126
|
defineAccessValue(accessBase)
|
|
@@ -136,7 +129,7 @@ function projectArray<SourceValue, ResultValue>(
|
|
|
136
129
|
})
|
|
137
130
|
setEffectName(stop, `project[${depth}]:${index}`)
|
|
138
131
|
effectProjectionMetadata.set(stop, {
|
|
139
|
-
source
|
|
132
|
+
source,
|
|
140
133
|
key: index,
|
|
141
134
|
target,
|
|
142
135
|
depth,
|
|
@@ -149,7 +142,7 @@ function projectArray<SourceValue, ResultValue>(
|
|
|
149
142
|
})
|
|
150
143
|
|
|
151
144
|
return makeCleanup(target, indexEffects, () => cleanupLength(), {
|
|
152
|
-
source
|
|
145
|
+
source,
|
|
153
146
|
target,
|
|
154
147
|
apply,
|
|
155
148
|
depth,
|
|
@@ -274,9 +267,9 @@ function projectRecord<Source extends Record<PropertyKey, any>, ResultValue>(
|
|
|
274
267
|
const accessBase = {
|
|
275
268
|
key: sourceKey,
|
|
276
269
|
source: observedSource,
|
|
277
|
-
get: () =>
|
|
270
|
+
get: () => FoolProof.get(observedSource, sourceKey, observedSource),
|
|
278
271
|
set: (value: Source[typeof sourceKey]) =>
|
|
279
|
-
|
|
272
|
+
FoolProof.set(observedSource, sourceKey, value, observedSource),
|
|
280
273
|
old: previous,
|
|
281
274
|
} as ProjectAccess<
|
|
282
275
|
Source[typeof sourceKey],
|
|
@@ -415,6 +408,34 @@ type ProjectOverload = {
|
|
|
415
408
|
map: typeof projectMap
|
|
416
409
|
}
|
|
417
410
|
|
|
411
|
+
function projectCore<SourceValue, ResultValue>(
|
|
412
|
+
source: readonly SourceValue[],
|
|
413
|
+
apply: ProjectCallback<SourceValue, number, ResultValue[], readonly SourceValue[], ResultValue>
|
|
414
|
+
): ProjectResult<ResultValue[]>
|
|
415
|
+
function projectCore<Key extends PropertyKey, SourceValue, ResultValue>(
|
|
416
|
+
source: Register<SourceValue, Key>,
|
|
417
|
+
apply: ProjectCallback<
|
|
418
|
+
SourceValue,
|
|
419
|
+
Key,
|
|
420
|
+
Map<Key, ResultValue>,
|
|
421
|
+
Register<SourceValue, Key>,
|
|
422
|
+
ResultValue
|
|
423
|
+
>
|
|
424
|
+
): ProjectResult<Map<Key, ResultValue>>
|
|
425
|
+
function projectCore<Source extends Record<PropertyKey, any>, ResultValue>(
|
|
426
|
+
source: Source,
|
|
427
|
+
apply: ProjectCallback<
|
|
428
|
+
Source[keyof Source],
|
|
429
|
+
keyof Source,
|
|
430
|
+
Record<keyof Source, ResultValue>,
|
|
431
|
+
Source,
|
|
432
|
+
ResultValue
|
|
433
|
+
>
|
|
434
|
+
): ProjectResult<Record<keyof Source, ResultValue>>
|
|
435
|
+
function projectCore<Key, Value, ResultValue>(
|
|
436
|
+
source: Map<Key, Value>,
|
|
437
|
+
apply: ProjectCallback<Value, Key, Map<Key, ResultValue>, Map<Key, Value>, ResultValue>
|
|
438
|
+
): ProjectResult<Map<Key, ResultValue>>
|
|
418
439
|
function projectCore(source: any, apply: any): ProjectResult<any> {
|
|
419
440
|
if (Array.isArray(source)) return projectArray(source, apply)
|
|
420
441
|
if (source instanceof Map) return projectMap(source, apply)
|
package/src/reactive/proxy.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { decorator } from '../decorator'
|
|
2
2
|
import { mixin } from '../mixins'
|
|
3
|
-
import {
|
|
3
|
+
import { FoolProof, isOwnAccessor } from '../utils'
|
|
4
4
|
import { touched1 } from './change'
|
|
5
5
|
import { notifyPropertyChange } from './deep-touch'
|
|
6
6
|
import {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
objectsWithDeepWatchers,
|
|
11
11
|
removeBackReference,
|
|
12
12
|
} from './deep-watch-state'
|
|
13
|
-
import {
|
|
13
|
+
import { untracked } from './effects'
|
|
14
14
|
import { absent, isNonReactive } from './non-reactive-state'
|
|
15
15
|
import {
|
|
16
16
|
getExistingProxy,
|
|
@@ -25,34 +25,29 @@ import {
|
|
|
25
25
|
nativeReactive,
|
|
26
26
|
nonReactiveMark,
|
|
27
27
|
options,
|
|
28
|
-
prototypeForwarding,
|
|
29
28
|
ReactiveError,
|
|
30
29
|
ReactiveErrorCode,
|
|
31
30
|
unreactiveProperties,
|
|
32
31
|
} from './types'
|
|
32
|
+
export const metaProtos = new WeakMap()
|
|
33
33
|
|
|
34
34
|
const hasReentry: any[] = []
|
|
35
35
|
const reactiveHandlers = {
|
|
36
36
|
[Symbol.toStringTag]: 'MutTs Reactive',
|
|
37
37
|
get(obj: any, prop: PropertyKey, receiver: any) {
|
|
38
|
+
if (obj && typeof obj === 'object' && !Object.hasOwn(obj, prop)) {
|
|
39
|
+
const metaProto = metaProtos.get(obj.constructor)
|
|
40
|
+
if (metaProto && prop in metaProto) {
|
|
41
|
+
const desc = Object.getOwnPropertyDescriptor(metaProto, prop)!
|
|
42
|
+
return desc.get ? desc.get.call(obj) : (...args) => desc.value.apply(obj, args)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
38
45
|
if (prop === nonReactiveMark) return false
|
|
39
46
|
const unwrappedObj = unwrap(obj)
|
|
40
47
|
// Check if this property is marked as unreactive
|
|
41
48
|
if (unwrappedObj[unreactiveProperties]?.has(prop) || typeof prop === 'symbol')
|
|
42
|
-
return
|
|
49
|
+
return FoolProof.get(obj, prop, receiver)
|
|
43
50
|
|
|
44
|
-
// Special-case: array wrappers use prototype forwarding + numeric accessors.
|
|
45
|
-
// With options.instanceMembers=true, inherited reads are normally not tracked, which breaks
|
|
46
|
-
// reactivity for array indices/length (they appear inherited on the proxy).
|
|
47
|
-
const isArrayCase =
|
|
48
|
-
prototypeForwarding in obj &&
|
|
49
|
-
// biome-ignore lint/suspicious/useIsArray: This is the whole point here
|
|
50
|
-
obj[prototypeForwarding] instanceof Array &&
|
|
51
|
-
typeof prop === 'string' &&
|
|
52
|
-
(prop === 'length' || !Number.isNaN(Number(prop)))
|
|
53
|
-
if (isArrayCase) {
|
|
54
|
-
dependant(obj, prop === 'length' ? 'length' : Number(prop))
|
|
55
|
-
}
|
|
56
51
|
// Check if property exists and if it's an own property (cached for later use)
|
|
57
52
|
const hasProp = Reflect.has(receiver, prop)
|
|
58
53
|
const isOwnProp = hasProp && Object.hasOwn(receiver, prop)
|
|
@@ -86,7 +81,7 @@ const reactiveHandlers = {
|
|
|
86
81
|
current = next
|
|
87
82
|
}
|
|
88
83
|
}
|
|
89
|
-
const value =
|
|
84
|
+
const value = FoolProof.get(obj, prop, receiver)
|
|
90
85
|
if (typeof value === 'object' && value !== null) {
|
|
91
86
|
const reactiveValue = reactiveObject(value)
|
|
92
87
|
|
|
@@ -106,19 +101,8 @@ const reactiveHandlers = {
|
|
|
106
101
|
|
|
107
102
|
// Check if this property is marked as unreactive
|
|
108
103
|
if (unwrappedObj[unreactiveProperties]?.has(prop) || unwrappedObj !== unwrappedReceiver)
|
|
109
|
-
return
|
|
110
|
-
// Really specific case for when Array is forwarder, in order to let it manage the reactivity
|
|
111
|
-
const isArrayCase =
|
|
112
|
-
prototypeForwarding in obj &&
|
|
113
|
-
// biome-ignore lint/suspicious/useIsArray: This is the whole point here
|
|
114
|
-
obj[prototypeForwarding] instanceof Array &&
|
|
115
|
-
(!Number.isNaN(Number(prop)) || prop === 'length')
|
|
104
|
+
return FoolProof.set(obj, prop, value, receiver)
|
|
116
105
|
const newValue = unwrap(value)
|
|
117
|
-
|
|
118
|
-
if (isArrayCase) {
|
|
119
|
-
;(obj as any)[prop] = newValue
|
|
120
|
-
return true
|
|
121
|
-
}
|
|
122
106
|
// Read old value, using withEffect(undefined, ...) for getter-only accessors to avoid
|
|
123
107
|
// breaking memoization dependency tracking during SET operations
|
|
124
108
|
let oldVal = absent
|
|
@@ -127,12 +111,12 @@ const reactiveHandlers = {
|
|
|
127
111
|
const receiverDesc = Object.getOwnPropertyDescriptor(unwrappedReceiver, prop)
|
|
128
112
|
const targetDesc = Object.getOwnPropertyDescriptor(unwrappedObj, prop)
|
|
129
113
|
const desc = receiverDesc || targetDesc
|
|
130
|
-
//
|
|
131
|
-
//
|
|
114
|
+
// We *need* to use `receiver` and not `unwrappedObj` here, otherwise we break
|
|
115
|
+
// the dependency tracking for memoized getters
|
|
132
116
|
if (desc?.get && !desc?.set) {
|
|
133
|
-
oldVal =
|
|
117
|
+
oldVal = untracked(() => Reflect.get(unwrappedObj, prop, receiver))
|
|
134
118
|
} else {
|
|
135
|
-
oldVal = Reflect.get(unwrappedObj, prop,
|
|
119
|
+
oldVal = untracked(() => Reflect.get(unwrappedObj, prop, receiver))
|
|
136
120
|
}
|
|
137
121
|
}
|
|
138
122
|
if (objectsWithDeepWatchers.has(obj)) {
|
|
@@ -148,7 +132,7 @@ const reactiveHandlers = {
|
|
|
148
132
|
if (oldVal !== newValue) {
|
|
149
133
|
// For getter-only accessors, Reflect.set() may fail, but we still return true
|
|
150
134
|
// to avoid throwing errors. Only proceed with change notifications if set succeeded.
|
|
151
|
-
if (
|
|
135
|
+
if (FoolProof.set(obj, prop, newValue, receiver)) {
|
|
152
136
|
notifyPropertyChange(obj, prop, oldVal, newValue, oldVal !== absent)
|
|
153
137
|
}
|
|
154
138
|
}
|
|
@@ -189,15 +173,6 @@ const reactiveHandlers = {
|
|
|
189
173
|
|
|
190
174
|
return true
|
|
191
175
|
},
|
|
192
|
-
getPrototypeOf(obj: any): object | null {
|
|
193
|
-
if (prototypeForwarding in obj) return obj[prototypeForwarding]
|
|
194
|
-
return Object.getPrototypeOf(obj)
|
|
195
|
-
},
|
|
196
|
-
setPrototypeOf(obj: any, proto: object | null): boolean {
|
|
197
|
-
if (prototypeForwarding in obj) return false
|
|
198
|
-
Object.setPrototypeOf(obj, proto)
|
|
199
|
-
return true
|
|
200
|
-
},
|
|
201
176
|
ownKeys(obj: any): (string | symbol)[] {
|
|
202
177
|
dependant(obj, allProps)
|
|
203
178
|
return Reflect.ownKeys(obj)
|
package/src/reactive/record.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FoolProof } from '../utils'
|
|
2
2
|
import { touched1 } from './change'
|
|
3
3
|
import { effect } from './effects'
|
|
4
4
|
import { cleanedBy, cleanup } from './interface'
|
|
@@ -147,9 +147,9 @@ export function organized<
|
|
|
147
147
|
const sourceKey = key as keyof Source
|
|
148
148
|
const accessBase = {
|
|
149
149
|
key: sourceKey,
|
|
150
|
-
get: () =>
|
|
150
|
+
get: () => FoolProof.get(observedSource, sourceKey, observedSource),
|
|
151
151
|
set: (value: Source[typeof sourceKey]) =>
|
|
152
|
-
|
|
152
|
+
FoolProof.set(observedSource, sourceKey, value, observedSource),
|
|
153
153
|
}
|
|
154
154
|
Object.defineProperty(accessBase, 'value', {
|
|
155
155
|
get: accessBase.get,
|