mutts 1.0.6 → 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 +1 -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-CDCOjzTy.js → index-BFYK02LG.js} +5760 -4338
- package/dist/chunks/index-BFYK02LG.js.map +1 -0
- package/dist/chunks/{index-DiP0RXoZ.esm.js → index-CNR6QRUl.esm.js} +5440 -4054
- package/dist/chunks/index-CNR6QRUl.esm.js.map +1 -0
- package/dist/devtools/panel.js.map +1 -1
- 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/reactive/advanced.md +2 -5
- package/docs/reactive/collections.md +0 -125
- package/docs/reactive/core.md +27 -24
- package/docs/reactive/debugging.md +12 -2
- package/docs/reactive/project.md +1 -1
- package/docs/reactive/scan.md +78 -0
- package/docs/reactive.md +2 -1
- package/docs/std-decorators.md +1 -0
- package/docs/zone.md +88 -0
- package/package.json +42 -10
- 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 +5 -1
- package/src/destroyable.ts +1 -1
- package/src/index.ts +22 -14
- 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 +2 -2
- package/src/reactive/effect-context.ts +15 -91
- package/src/reactive/effects.ts +119 -179
- package/src/reactive/index.ts +10 -13
- package/src/reactive/interface.ts +19 -33
- package/src/reactive/map.ts +48 -61
- package/src/reactive/memoize.ts +19 -9
- package/src/reactive/project.ts +43 -22
- package/src/reactive/proxy.ts +16 -41
- package/src/reactive/record.ts +3 -3
- package/src/reactive/register.ts +5 -7
- package/src/reactive/registry.ts +9 -17
- package/src/reactive/set.ts +42 -56
- package/src/reactive/tracking.ts +1 -29
- package/src/reactive/types.ts +46 -23
- package/src/utils.ts +80 -37
- 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-MCKDzsSq.esm.js +0 -75
- package/dist/chunks/_tslib-MCKDzsSq.esm.js.map +0 -1
- package/dist/chunks/decorator-BGILvPtN.esm.js +0 -627
- package/dist/chunks/decorator-BGILvPtN.esm.js.map +0 -1
- package/dist/chunks/decorator-BQ2eBTCj.js +0 -651
- package/dist/chunks/decorator-BQ2eBTCj.js.map +0 -1
- package/dist/chunks/index-CDCOjzTy.js.map +0 -1
- package/dist/chunks/index-DiP0RXoZ.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 -53
- package/dist/index.esm.js.map +0 -1
- package/dist/index.js +0 -139
- 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 -910
- 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
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { decorator, type GenericClassDecorator } from '../decorator'
|
|
2
2
|
import { deepWatch } from './deep-watch'
|
|
3
|
-
import {
|
|
4
|
-
import { effect, getActiveEffect, untracked } from './effects'
|
|
3
|
+
import { effect, untracked } from './effects'
|
|
5
4
|
import { isNonReactive, nonReactiveClass, nonReactiveObjects } from './non-reactive-state'
|
|
6
5
|
import { unwrap } from './proxy-state'
|
|
7
|
-
import { dependant } from './tracking'
|
|
8
6
|
import { markWithRoot } from './registry'
|
|
7
|
+
import { dependant } from './tracking'
|
|
9
8
|
import {
|
|
10
9
|
type DependencyAccess,
|
|
11
10
|
nonReactiveMark,
|
|
@@ -87,15 +86,12 @@ function watchObject(
|
|
|
87
86
|
changed: (value: object) => void,
|
|
88
87
|
{ immediate = false, deep = false } = {}
|
|
89
88
|
): ScopedCallback {
|
|
90
|
-
const myParentEffect = getActiveEffect()
|
|
91
89
|
if (deep) return deepWatch(value, changed, { immediate })!
|
|
92
|
-
return effect(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
)
|
|
90
|
+
return effect(function watchObjectEffect() {
|
|
91
|
+
dependant(value)
|
|
92
|
+
if (immediate) changed(value)
|
|
93
|
+
immediate = true
|
|
94
|
+
})
|
|
99
95
|
}
|
|
100
96
|
|
|
101
97
|
function watchCallBack<T>(
|
|
@@ -103,29 +99,20 @@ function watchCallBack<T>(
|
|
|
103
99
|
changed: (value: T, oldValue?: T) => void,
|
|
104
100
|
{ immediate = false, deep = false } = {}
|
|
105
101
|
): ScopedCallback {
|
|
106
|
-
const myParentEffect = getActiveEffect()
|
|
107
102
|
let oldValue: T | typeof unsetYet = unsetYet
|
|
108
103
|
let deepCleanup: ScopedCallback | undefined
|
|
109
104
|
const cbCleanup = effect(
|
|
110
105
|
markWithRoot(function watchCallBackEffect(access) {
|
|
111
106
|
const newValue = value(access)
|
|
112
107
|
if (oldValue !== newValue)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (deepCleanup) deepCleanup()
|
|
122
|
-
deepCleanup = deepWatch(
|
|
123
|
-
newValue as object,
|
|
124
|
-
(value) => changed(value as T, value as T)
|
|
125
|
-
)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
)
|
|
108
|
+
if (oldValue === unsetYet) {
|
|
109
|
+
if (immediate) changed(newValue)
|
|
110
|
+
} else changed(newValue, oldValue)
|
|
111
|
+
oldValue = newValue
|
|
112
|
+
if (deep) {
|
|
113
|
+
if (deepCleanup) deepCleanup()
|
|
114
|
+
deepCleanup = deepWatch(newValue as object, (value) => changed(value as T, value as T))
|
|
115
|
+
}
|
|
129
116
|
}, value)
|
|
130
117
|
)
|
|
131
118
|
return () => {
|
|
@@ -154,6 +141,7 @@ function deepNonReactive<T>(obj: T): T {
|
|
|
154
141
|
})
|
|
155
142
|
} catch {}
|
|
156
143
|
if (!(nonReactiveMark in (obj as object))) nonReactiveObjects.add(obj as object)
|
|
144
|
+
// Finally, not deep
|
|
157
145
|
//for (const key in obj) deepNonReactive(obj[key])
|
|
158
146
|
return obj
|
|
159
147
|
}
|
|
@@ -214,11 +202,9 @@ export function derived<T>(compute: (dep: DependencyAccess) => T): {
|
|
|
214
202
|
return cleanedBy(
|
|
215
203
|
rv,
|
|
216
204
|
untracked(() =>
|
|
217
|
-
effect(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
)
|
|
205
|
+
effect(function derivedEffect(access) {
|
|
206
|
+
rv.value = compute(access)
|
|
207
|
+
})
|
|
222
208
|
)
|
|
223
209
|
)
|
|
224
210
|
}
|
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
|
@@ -2,8 +2,8 @@ import { decorator } from '../decorator'
|
|
|
2
2
|
import { deepCompare, renamed } from '../utils'
|
|
3
3
|
import { touched1 } from './change'
|
|
4
4
|
import { effect, root, untracked } from './effects'
|
|
5
|
-
import { dependant } from './tracking'
|
|
6
5
|
import { getRoot, markWithRoot } from './registry'
|
|
6
|
+
import { dependant } from './tracking'
|
|
7
7
|
import { options, rootFunction } from './types'
|
|
8
8
|
|
|
9
9
|
export type Memoizable = object | any[] | symbol | ((...args: any[]) => any)
|
|
@@ -114,13 +114,18 @@ export const memoize = decorator({
|
|
|
114
114
|
let wrapper = wrapperRegistry.get(original)
|
|
115
115
|
if (!wrapper) {
|
|
116
116
|
wrapper = markWithRoot(
|
|
117
|
-
renamed(
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
renamed(
|
|
118
|
+
(that: object) => {
|
|
119
|
+
return original.call(that)
|
|
120
|
+
},
|
|
121
|
+
`${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(propertyKey)}`
|
|
122
|
+
),
|
|
120
123
|
{
|
|
121
124
|
method: original,
|
|
122
125
|
propertyKey,
|
|
123
|
-
...((original as any)[rootFunction]
|
|
126
|
+
...((original as any)[rootFunction]
|
|
127
|
+
? { [rootFunction]: (original as any)[rootFunction] }
|
|
128
|
+
: {}),
|
|
124
129
|
}
|
|
125
130
|
)
|
|
126
131
|
wrapperRegistry.set(original, wrapper)
|
|
@@ -134,13 +139,18 @@ export const memoize = decorator({
|
|
|
134
139
|
let wrapper = wrapperRegistry.get(original)
|
|
135
140
|
if (!wrapper) {
|
|
136
141
|
wrapper = markWithRoot(
|
|
137
|
-
renamed(
|
|
138
|
-
|
|
139
|
-
|
|
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
|
+
),
|
|
140
148
|
{
|
|
141
149
|
method: original,
|
|
142
150
|
propertyKey: name,
|
|
143
|
-
...((original as any)[rootFunction]
|
|
151
|
+
...((original as any)[rootFunction]
|
|
152
|
+
? { [rootFunction]: (original as any)[rootFunction] }
|
|
153
|
+
: {}),
|
|
144
154
|
}
|
|
145
155
|
)
|
|
146
156
|
wrapperRegistry.set(original, wrapper)
|
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
|
|
@@ -130,9 +114,9 @@ const reactiveHandlers = {
|
|
|
130
114
|
// We *need* to use `receiver` and not `unwrappedObj` here, otherwise we break
|
|
131
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 =
|
|
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,
|
package/src/reactive/register.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { FunctionWrapper } from '../zone'
|
|
1
2
|
import { ArrayReadForward, forwardArray, getAt, Indexable, setAt } from '../indexable'
|
|
2
3
|
import { effect } from './effects'
|
|
3
4
|
import { unreactive } from './interface'
|
|
4
5
|
import { reactive } from './proxy'
|
|
5
|
-
import { type
|
|
6
|
+
import { type ScopedCallback } from './types'
|
|
6
7
|
|
|
7
8
|
// TODO: use register in a real-world crud situation, have "events" for add, delete, update
|
|
8
9
|
|
|
@@ -36,7 +37,7 @@ function getRegisterBase<T>() {
|
|
|
36
37
|
interface RegisterInstance<T> extends ArrayReadForward<T> {
|
|
37
38
|
[index: number]: T
|
|
38
39
|
}
|
|
39
|
-
|
|
40
|
+
// TODO: What to do with prototype forwarding ?
|
|
40
41
|
@unreactive
|
|
41
42
|
class RegisterClass<T, K extends PropertyKey = PropertyKey>
|
|
42
43
|
extends getRegisterBase<any>()
|
|
@@ -51,12 +52,12 @@ class RegisterClass<T, K extends PropertyKey = PropertyKey>
|
|
|
51
52
|
readonly #usage = new Map<K, number>()
|
|
52
53
|
readonly #valueInfo = new Map<T, { key: K; stop?: ScopedCallback }>()
|
|
53
54
|
readonly #keyEffects = new Set<ScopedCallback>()
|
|
54
|
-
readonly #ascend:
|
|
55
|
+
readonly #ascend: FunctionWrapper
|
|
55
56
|
|
|
56
57
|
constructor(keyFn: KeyFunction<T, K>, initial?: Iterable<T>) {
|
|
57
58
|
super()
|
|
58
59
|
/* Moved below initialization */
|
|
59
|
-
let ascendGet:
|
|
60
|
+
let ascendGet: FunctionWrapper | undefined
|
|
60
61
|
effect(({ ascend }) => {
|
|
61
62
|
ascendGet = ascend
|
|
62
63
|
})
|
|
@@ -65,9 +66,6 @@ class RegisterClass<T, K extends PropertyKey = PropertyKey>
|
|
|
65
66
|
this.#keyFn = keyFn
|
|
66
67
|
this.#keys = reactive([] as K[])
|
|
67
68
|
this.#values = reactive(new Map<K, T>())
|
|
68
|
-
Object.defineProperties(this, {
|
|
69
|
-
[prototypeForwarding]: { value: this.#keys },
|
|
70
|
-
})
|
|
71
69
|
if (initial) this.push(...initial)
|
|
72
70
|
}
|
|
73
71
|
|