mutts 1.0.6 → 1.0.8
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 +61 -23
- package/dist/async/browser.d.ts +2 -0
- package/dist/async/browser.d.ts.map +1 -0
- package/dist/async/index.d.ts +18 -0
- package/dist/async/index.d.ts.map +1 -0
- package/dist/async/node.d.ts +2 -0
- package/dist/async/node.d.ts.map +1 -0
- package/dist/{chunks/index-CDCOjzTy.js → browser.cjs} +5913 -4382
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.ts +1655 -0
- package/dist/browser.esm.js +305 -0
- package/dist/browser.esm.js.map +1 -0
- package/dist/chunks/async-browser-CA0jPWIi.cjs +304 -0
- package/dist/chunks/async-browser-CA0jPWIi.cjs.map +1 -0
- package/dist/chunks/async-core-UqHzvJ-S.cjs +25 -0
- package/dist/chunks/async-core-UqHzvJ-S.cjs.map +1 -0
- package/dist/chunks/async-node-BYHuGTni.cjs +103 -0
- package/dist/chunks/async-node-BYHuGTni.cjs.map +1 -0
- package/dist/chunks/{index-DiP0RXoZ.esm.js → index-DhaOVusv.esm.js} +5851 -4345
- package/dist/chunks/index-DhaOVusv.esm.js.map +1 -0
- package/dist/decorator.d.ts +17 -18
- package/dist/decorator.d.ts.map +1 -0
- package/dist/destroyable.d.ts +12 -15
- package/dist/destroyable.d.ts.map +1 -0
- package/dist/devtools/devtool/devtools.d.ts +1 -0
- package/dist/devtools/devtool/devtools.d.ts.map +1 -0
- package/dist/devtools/devtool/panel.d.ts +2 -0
- package/dist/devtools/devtool/panel.d.ts.map +1 -0
- package/dist/devtools/panel.js.map +1 -1
- package/dist/entry-browser.d.ts +3 -0
- package/dist/entry-browser.d.ts.map +1 -0
- package/dist/entry-node.d.ts +3 -0
- package/dist/entry-node.d.ts.map +1 -0
- package/dist/eventful.d.ts +3 -5
- package/dist/eventful.d.ts.map +1 -0
- package/dist/index.d.ts +13 -19
- package/dist/index.d.ts.map +1 -0
- package/dist/indexable.d.ts +10 -10
- package/dist/indexable.d.ts.map +1 -0
- package/dist/introspection.d.ts +27 -0
- package/dist/introspection.d.ts.map +1 -0
- package/dist/iterableWeak.d.ts +53 -0
- package/dist/iterableWeak.d.ts.map +1 -0
- package/dist/mixins.d.ts +25 -0
- package/dist/mixins.d.ts.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.cjs +105 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.ts +1 -0
- package/dist/node.esm.js +104 -0
- package/dist/node.esm.js.map +1 -0
- package/dist/promiseChain.d.ts +4 -5
- package/dist/promiseChain.d.ts.map +1 -0
- package/dist/reactive/array.d.ts +49 -0
- package/dist/reactive/array.d.ts.map +1 -0
- package/dist/reactive/buffer.d.ts +44 -0
- package/dist/reactive/buffer.d.ts.map +1 -0
- package/dist/reactive/change.d.ts +29 -0
- package/dist/reactive/change.d.ts.map +1 -0
- package/dist/reactive/debug.d.ts +111 -0
- package/dist/reactive/debug.d.ts.map +1 -0
- package/dist/reactive/deep-touch.d.ts +28 -0
- package/dist/reactive/deep-touch.d.ts.map +1 -0
- package/dist/reactive/deep-watch-state.d.ts +25 -0
- package/dist/reactive/deep-watch-state.d.ts.map +1 -0
- package/dist/reactive/deep-watch.d.ts +19 -0
- package/dist/reactive/deep-watch.d.ts.map +1 -0
- package/dist/reactive/effect-context.d.ts +7 -0
- package/dist/reactive/effect-context.d.ts.map +1 -0
- package/dist/reactive/effects.d.ts +151 -0
- package/dist/reactive/effects.d.ts.map +1 -0
- package/dist/reactive/index.d.ts +20 -0
- package/dist/reactive/index.d.ts.map +1 -0
- package/dist/reactive/interface.d.ts +64 -0
- package/dist/reactive/interface.d.ts.map +1 -0
- package/dist/reactive/map.d.ts +30 -0
- package/dist/reactive/map.d.ts.map +1 -0
- package/dist/reactive/memoize.d.ts +5 -0
- package/dist/reactive/memoize.d.ts.map +1 -0
- package/dist/reactive/non-reactive-state.d.ts +9 -0
- package/dist/reactive/non-reactive-state.d.ts.map +1 -0
- package/dist/reactive/non-reactive.d.ts +11 -0
- package/dist/reactive/non-reactive.d.ts.map +1 -0
- package/dist/reactive/project.d.ts +41 -0
- package/dist/reactive/project.d.ts.map +1 -0
- package/dist/reactive/proxy-state.d.ts +8 -0
- package/dist/reactive/proxy-state.d.ts.map +1 -0
- package/dist/reactive/proxy.d.ts +23 -0
- package/dist/reactive/proxy.d.ts.map +1 -0
- package/dist/reactive/record.d.ts +116 -0
- package/dist/reactive/record.d.ts.map +1 -0
- package/dist/reactive/register.d.ts +64 -0
- package/dist/reactive/register.d.ts.map +1 -0
- package/dist/reactive/registry.d.ts +20 -0
- package/dist/reactive/registry.d.ts.map +1 -0
- package/dist/reactive/set.d.ts +28 -0
- package/dist/reactive/set.d.ts.map +1 -0
- package/dist/reactive/tracking.d.ts +7 -0
- package/dist/reactive/tracking.d.ts.map +1 -0
- package/dist/reactive/types.d.ts +376 -0
- package/dist/reactive/types.d.ts.map +1 -0
- package/dist/std-decorators.d.ts +9 -11
- package/dist/std-decorators.d.ts.map +1 -0
- package/dist/utils.d.ts +49 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/zone.d.ts +40 -0
- package/dist/zone.d.ts.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 +69 -0
- package/docs/zone.md +95 -0
- package/package.json +67 -23
- package/src/async/browser.ts +319 -0
- package/src/async/index.ts +23 -0
- package/src/async/node.ts +104 -0
- package/src/decorator.ts +5 -1
- package/src/destroyable.ts +1 -1
- package/src/entry-browser.ts +5 -0
- package/src/entry-node.ts +5 -0
- package/src/index.d.ts +12 -9
- package/src/index.ts +23 -14
- package/src/indexable.ts +42 -0
- package/src/mixins.ts +2 -2
- package/src/reactive/array.ts +274 -179
- 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 +11 -13
- package/src/reactive/interface.ts +19 -33
- package/src/reactive/map.ts +49 -62
- 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 +43 -57
- package/src/reactive/tracking.ts +1 -29
- package/src/reactive/types.ts +46 -23
- package/src/utils.ts +80 -37
- package/src/zone.ts +138 -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.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.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.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.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.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.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.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/set.ts
CHANGED
|
@@ -1,49 +1,38 @@
|
|
|
1
|
+
import { contentRef } from '../utils'
|
|
1
2
|
import { touched, touched1 } from './change'
|
|
2
3
|
import { makeReactiveEntriesIterator, makeReactiveIterator } from './non-reactive'
|
|
3
4
|
import { reactive } from './proxy'
|
|
4
5
|
import { dependant } from './tracking'
|
|
5
|
-
import { prototypeForwarding } from './types'
|
|
6
|
-
|
|
7
|
-
const native = Symbol('native')
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
8
|
* Reactive wrapper around JavaScript's WeakSet class
|
|
11
9
|
* Only tracks individual value operations, no size tracking (WeakSet limitation)
|
|
12
10
|
*/
|
|
13
|
-
export class ReactiveWeakSet<T extends object> {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
constructor(original: WeakSet<T>) {
|
|
18
|
-
Object.defineProperties(this, {
|
|
19
|
-
[native]: { value: original },
|
|
20
|
-
[prototypeForwarding]: { value: original },
|
|
21
|
-
content: { value: Symbol('WeakSetContent') },
|
|
22
|
-
[Symbol.toStringTag]: { value: 'ReactiveWeakSet' },
|
|
23
|
-
})
|
|
11
|
+
export abstract class ReactiveWeakSet<T extends object> extends WeakSet<T> {
|
|
12
|
+
get [Symbol.toStringTag]() {
|
|
13
|
+
return 'ReactiveWeakSet'
|
|
24
14
|
}
|
|
25
|
-
|
|
26
15
|
add(value: T): this {
|
|
27
|
-
const had = this
|
|
28
|
-
this
|
|
16
|
+
const had = this.has(value)
|
|
17
|
+
this.add(value)
|
|
29
18
|
if (!had) {
|
|
30
19
|
// touch the specific value and the collection view
|
|
31
|
-
touched1(this
|
|
20
|
+
touched1(contentRef(this), { type: 'add', prop: value }, value)
|
|
32
21
|
// no size/allProps for WeakSet
|
|
33
22
|
}
|
|
34
23
|
return this
|
|
35
24
|
}
|
|
36
25
|
|
|
37
26
|
delete(value: T): boolean {
|
|
38
|
-
const had = this
|
|
39
|
-
const res = this
|
|
40
|
-
if (had) touched1(this
|
|
27
|
+
const had = this.has(value)
|
|
28
|
+
const res = this.delete(value)
|
|
29
|
+
if (had) touched1(contentRef(this), { type: 'del', prop: value }, value)
|
|
41
30
|
return res
|
|
42
31
|
}
|
|
43
32
|
|
|
44
33
|
has(value: T): boolean {
|
|
45
|
-
dependant(this
|
|
46
|
-
return this
|
|
34
|
+
dependant(contentRef(this), value)
|
|
35
|
+
return this.has(value)
|
|
47
36
|
}
|
|
48
37
|
}
|
|
49
38
|
|
|
@@ -51,86 +40,79 @@ export class ReactiveWeakSet<T extends object> {
|
|
|
51
40
|
* Reactive wrapper around JavaScript's Set class
|
|
52
41
|
* Tracks size changes, individual value operations, and collection-wide operations
|
|
53
42
|
*/
|
|
54
|
-
export class ReactiveSet<T> {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
constructor(original: Set<T>) {
|
|
58
|
-
Object.defineProperties(this, {
|
|
59
|
-
[native]: { value: original },
|
|
60
|
-
[prototypeForwarding]: { value: original },
|
|
61
|
-
content: { value: Symbol('SetContent') },
|
|
62
|
-
[Symbol.toStringTag]: { value: 'ReactiveSet' },
|
|
63
|
-
})
|
|
43
|
+
export abstract class ReactiveSet<T> extends Set<T> {
|
|
44
|
+
get [Symbol.toStringTag]() {
|
|
45
|
+
return 'ReactiveSet'
|
|
64
46
|
}
|
|
65
47
|
|
|
66
48
|
get size(): number {
|
|
67
49
|
// size depends on the wrapper instance, like Map counterpart
|
|
68
50
|
dependant(this, 'size')
|
|
69
|
-
return this
|
|
51
|
+
return this.size
|
|
70
52
|
}
|
|
71
53
|
|
|
72
54
|
add(value: T): this {
|
|
73
|
-
const had = this
|
|
55
|
+
const had = this.has(value)
|
|
74
56
|
const reactiveValue = reactive(value)
|
|
75
|
-
this
|
|
57
|
+
this.add(reactiveValue)
|
|
76
58
|
if (!had) {
|
|
77
59
|
const evolution = { type: 'add', prop: reactiveValue } as const
|
|
78
60
|
// touch for value-specific and aggregate dependencies
|
|
79
|
-
touched1(this
|
|
61
|
+
touched1(contentRef(this), evolution, reactiveValue)
|
|
80
62
|
touched1(this, evolution, 'size')
|
|
81
63
|
}
|
|
82
64
|
return this
|
|
83
65
|
}
|
|
84
66
|
|
|
85
67
|
clear(): void {
|
|
86
|
-
const hadEntries = this
|
|
87
|
-
this
|
|
68
|
+
const hadEntries = this.size > 0
|
|
69
|
+
this.clear()
|
|
88
70
|
if (hadEntries) {
|
|
89
71
|
const evolution = { type: 'bunch', method: 'clear' } as const
|
|
90
72
|
touched1(this, evolution, 'size')
|
|
91
|
-
touched(this
|
|
73
|
+
touched(contentRef(this), evolution)
|
|
92
74
|
}
|
|
93
75
|
}
|
|
94
76
|
|
|
95
77
|
delete(value: T): boolean {
|
|
96
|
-
const had = this
|
|
97
|
-
const res = this
|
|
78
|
+
const had = this.has(value)
|
|
79
|
+
const res = this.delete(value)
|
|
98
80
|
if (had) {
|
|
99
81
|
const evolution = { type: 'del', prop: value } as const
|
|
100
|
-
touched1(this
|
|
82
|
+
touched1(contentRef(this), evolution, value)
|
|
101
83
|
touched1(this, evolution, 'size')
|
|
102
84
|
}
|
|
103
85
|
return res
|
|
104
86
|
}
|
|
105
87
|
|
|
106
88
|
has(value: T): boolean {
|
|
107
|
-
dependant(this
|
|
108
|
-
return this
|
|
89
|
+
dependant(contentRef(this), value)
|
|
90
|
+
return this.has(value)
|
|
109
91
|
}
|
|
110
92
|
|
|
111
93
|
entries(): Generator<[T, T]> {
|
|
112
|
-
dependant(this
|
|
113
|
-
return makeReactiveEntriesIterator(this
|
|
94
|
+
dependant(contentRef(this))
|
|
95
|
+
return makeReactiveEntriesIterator(this.entries())
|
|
114
96
|
}
|
|
115
97
|
|
|
116
98
|
forEach(callbackfn: (value: T, value2: T, set: Set<T>) => void, thisArg?: any): void {
|
|
117
|
-
dependant(this
|
|
118
|
-
this
|
|
99
|
+
dependant(contentRef(this))
|
|
100
|
+
this.forEach(callbackfn, thisArg)
|
|
119
101
|
}
|
|
120
102
|
|
|
121
103
|
keys(): Generator<T> {
|
|
122
|
-
dependant(this
|
|
123
|
-
return makeReactiveIterator(this
|
|
104
|
+
dependant(contentRef(this))
|
|
105
|
+
return makeReactiveIterator(this.keys())
|
|
124
106
|
}
|
|
125
107
|
|
|
126
108
|
values(): Generator<T> {
|
|
127
|
-
dependant(this
|
|
128
|
-
return makeReactiveIterator(this
|
|
109
|
+
dependant(contentRef(this))
|
|
110
|
+
return makeReactiveIterator(this.values())
|
|
129
111
|
}
|
|
130
112
|
|
|
131
|
-
[Symbol.iterator]():
|
|
132
|
-
dependant(this
|
|
133
|
-
const nativeIterator = this[
|
|
113
|
+
[Symbol.iterator](): SetIterator<T> {
|
|
114
|
+
dependant(contentRef(this))
|
|
115
|
+
const nativeIterator = this[Symbol.iterator]()
|
|
134
116
|
return {
|
|
135
117
|
next() {
|
|
136
118
|
const result = nativeIterator.next()
|
|
@@ -139,6 +121,10 @@ export class ReactiveSet<T> {
|
|
|
139
121
|
}
|
|
140
122
|
return { value: reactive(result.value), done: false }
|
|
141
123
|
},
|
|
142
|
-
|
|
124
|
+
[Symbol.iterator]() {
|
|
125
|
+
return this
|
|
126
|
+
},
|
|
127
|
+
[Symbol.dispose]() {},
|
|
128
|
+
} as any //TODO? (something easy)
|
|
143
129
|
}
|
|
144
130
|
}
|
package/src/reactive/tracking.ts
CHANGED
|
@@ -1,34 +1,11 @@
|
|
|
1
1
|
import { getActiveEffect } from './effect-context'
|
|
2
|
+
import { unwrap } from './proxy-state'
|
|
2
3
|
import {
|
|
3
4
|
effectToReactiveObjects,
|
|
4
|
-
getRoot,
|
|
5
|
-
globalTrackingDisabled,
|
|
6
|
-
setGlobalTrackingDisabled,
|
|
7
|
-
trackingDisabledEffects,
|
|
8
5
|
watchers,
|
|
9
6
|
} from './registry'
|
|
10
|
-
import { unwrap } from './proxy-state'
|
|
11
7
|
import { allProps, type ScopedCallback } from './types'
|
|
12
8
|
|
|
13
|
-
export function getTrackingDisabled(): boolean {
|
|
14
|
-
const active = getActiveEffect()
|
|
15
|
-
if (!active) return globalTrackingDisabled
|
|
16
|
-
return trackingDisabledEffects.has(getRoot(active))
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function setTrackingDisabled(value: boolean): void {
|
|
20
|
-
const active = getActiveEffect()
|
|
21
|
-
if (!active) {
|
|
22
|
-
setGlobalTrackingDisabled(value)
|
|
23
|
-
return
|
|
24
|
-
}
|
|
25
|
-
const root = getRoot(active)
|
|
26
|
-
if (value) trackingDisabledEffects.add(root)
|
|
27
|
-
else trackingDisabledEffects.delete(root)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
9
|
/**
|
|
33
10
|
* Marks a property as a dependency of the current effect
|
|
34
11
|
* @param obj - The object containing the property
|
|
@@ -41,15 +18,10 @@ export function dependant(obj: any, prop: any = allProps) {
|
|
|
41
18
|
// Early return if no active effect, tracking disabled, or invalid prop
|
|
42
19
|
if (
|
|
43
20
|
!currentActiveEffect ||
|
|
44
|
-
getTrackingDisabled() ||
|
|
45
21
|
(typeof prop === 'symbol' && prop !== allProps)
|
|
46
22
|
)
|
|
47
23
|
return
|
|
48
24
|
|
|
49
|
-
registerDependency(obj, prop, currentActiveEffect)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function registerDependency(obj: any, prop: any, currentActiveEffect: ScopedCallback) {
|
|
53
25
|
let objectWatchers = watchers.get(obj)
|
|
54
26
|
if (!objectWatchers) {
|
|
55
27
|
objectWatchers = new Map<PropertyKey, Set<ScopedCallback>>()
|
package/src/reactive/types.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
// biome-ignore-all lint/suspicious/noConfusingVoidType: Type 'void' is not assignable to type 'ScopedCallback | undefined'.
|
|
2
2
|
// Argument of type '() => void' is not assignable to parameter of type '(dep: DependencyFunction) => ScopedCallback | undefined'.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* Restores the active effect context for dependency tracking
|
|
7
|
-
*/
|
|
8
|
-
export type DependencyFunction = <T>(cb: () => T) => T
|
|
4
|
+
import { FunctionWrapper } from "../zone"
|
|
5
|
+
|
|
9
6
|
/**
|
|
10
7
|
* Dependency access passed to user callbacks within effects/watch
|
|
11
8
|
* Provides functions to track dependencies and information about the effect execution
|
|
@@ -25,7 +22,7 @@ export interface DependencyAccess {
|
|
|
25
22
|
* })
|
|
26
23
|
* ```
|
|
27
24
|
*/
|
|
28
|
-
tracked:
|
|
25
|
+
tracked: FunctionWrapper
|
|
29
26
|
/**
|
|
30
27
|
* Tracks dependencies in the parent effect context
|
|
31
28
|
* Use this when child effects should track dependencies in the parent,
|
|
@@ -43,7 +40,7 @@ export interface DependencyAccess {
|
|
|
43
40
|
* })
|
|
44
41
|
* ```
|
|
45
42
|
*/
|
|
46
|
-
ascend:
|
|
43
|
+
ascend: FunctionWrapper
|
|
47
44
|
/**
|
|
48
45
|
* Indicates whether the effect is running as a reaction (i.e. not the first call)
|
|
49
46
|
* - `false`: First execution when the effect is created
|
|
@@ -131,10 +128,6 @@ export const nonReactiveMark = Symbol('non-reactive')
|
|
|
131
128
|
* Symbol to mark class properties as non-reactive
|
|
132
129
|
*/
|
|
133
130
|
export const unreactiveProperties = Symbol('unreactive-properties')
|
|
134
|
-
/**
|
|
135
|
-
* Symbol for prototype forwarding in reactive objects
|
|
136
|
-
*/
|
|
137
|
-
export const prototypeForwarding: unique symbol = Symbol('prototype-forwarding')
|
|
138
131
|
|
|
139
132
|
/**
|
|
140
133
|
* Symbol representing all properties in reactive tracking
|
|
@@ -179,6 +172,7 @@ export enum ReactiveErrorCode {
|
|
|
179
172
|
MaxReactionExceeded = 'MAX_REACTION_EXCEEDED',
|
|
180
173
|
WriteInComputed = 'WRITE_IN_COMPUTED',
|
|
181
174
|
TrackingError = 'TRACKING_ERROR',
|
|
175
|
+
BrokenEffects = 'BROKEN_EFFECTS',
|
|
182
176
|
}
|
|
183
177
|
|
|
184
178
|
export type CycleDebugInfo = {
|
|
@@ -199,6 +193,11 @@ export type MaxReactionDebugInfo = {
|
|
|
199
193
|
effect: string
|
|
200
194
|
}
|
|
201
195
|
|
|
196
|
+
export type BrokenEffectsDebugInfo = {
|
|
197
|
+
code: ReactiveErrorCode.BrokenEffects
|
|
198
|
+
cause: any
|
|
199
|
+
}
|
|
200
|
+
|
|
202
201
|
export type GenericDebugInfo = {
|
|
203
202
|
code: ReactiveErrorCode
|
|
204
203
|
causalChain?: string[]
|
|
@@ -210,6 +209,7 @@ export type ReactiveDebugInfo =
|
|
|
210
209
|
| CycleDebugInfo
|
|
211
210
|
| MaxDepthDebugInfo
|
|
212
211
|
| MaxReactionDebugInfo
|
|
212
|
+
| BrokenEffectsDebugInfo
|
|
213
213
|
| GenericDebugInfo
|
|
214
214
|
|
|
215
215
|
/**
|
|
@@ -223,6 +223,14 @@ export class ReactiveError extends Error {
|
|
|
223
223
|
super(message)
|
|
224
224
|
this.name = 'ReactiveError'
|
|
225
225
|
}
|
|
226
|
+
|
|
227
|
+
get code(): ReactiveErrorCode | undefined {
|
|
228
|
+
return this.debugInfo?.code
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
get cause(): any {
|
|
232
|
+
return (this.debugInfo as any)?.cause
|
|
233
|
+
}
|
|
226
234
|
}
|
|
227
235
|
|
|
228
236
|
// biome-ignore-start lint/correctness/noUnusedFunctionParameters: Interface declaration with empty defaults
|
|
@@ -269,7 +277,7 @@ export const options = {
|
|
|
269
277
|
* @param effect - The effect that is already running
|
|
270
278
|
* @param runningChain - The array of effects from the detected one to the currently running one
|
|
271
279
|
*/
|
|
272
|
-
skipRunningEffect: (_effect: ScopedCallback
|
|
280
|
+
skipRunningEffect: (_effect: ScopedCallback) => {},
|
|
273
281
|
/**
|
|
274
282
|
* Debug purpose: maximum effect chain (like call stack max depth)
|
|
275
283
|
* Used to prevent infinite loops
|
|
@@ -292,14 +300,14 @@ export const options = {
|
|
|
292
300
|
* Callback called when a memoization discrepancy is detected (debug only)
|
|
293
301
|
* When defined, memoized functions will run a second time (untracked) to verify consistency.
|
|
294
302
|
* If the untracked run returns a different value than the cached one, this callback is triggered.
|
|
295
|
-
*
|
|
303
|
+
*
|
|
296
304
|
* This is the primary tool for detecting missing reactive dependencies in computed values.
|
|
297
|
-
*
|
|
305
|
+
*
|
|
298
306
|
* @param cached - The value currently in the memoization cache
|
|
299
307
|
* @param fresh - The value obtained by re-running the function untracked
|
|
300
308
|
* @param fn - The memoized function itself
|
|
301
309
|
* @param args - Arguments passed to the function
|
|
302
|
-
*
|
|
310
|
+
*
|
|
303
311
|
* @example
|
|
304
312
|
* ```typescript
|
|
305
313
|
* reactiveOptions.onMemoizationDiscrepancy = (cached, fresh, fn, args) => {
|
|
@@ -308,17 +316,31 @@ export const options = {
|
|
|
308
316
|
* ```
|
|
309
317
|
*/
|
|
310
318
|
onMemoizationDiscrepancy: undefined as
|
|
311
|
-
| ((
|
|
319
|
+
| ((
|
|
320
|
+
cached: any,
|
|
321
|
+
fresh: any,
|
|
322
|
+
fn: Function,
|
|
323
|
+
args: any[],
|
|
324
|
+
cause: 'calculation' | 'comparison'
|
|
325
|
+
) => void)
|
|
312
326
|
| undefined,
|
|
313
327
|
/**
|
|
314
|
-
* How to handle cycles detected in effect batches
|
|
315
|
-
*
|
|
316
|
-
* - '
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
328
|
+
* How to handle cycles detected in effect batches.
|
|
329
|
+
*
|
|
330
|
+
* - `'none'` (Default): High-performance mode. Disables dependency graph maintenance and
|
|
331
|
+
* Topological Sorting in favor of a simple FIFO queue. Use this for trustworthy, acyclic UI code.
|
|
332
|
+
* Cycle detection is heuristic (uses execution counts).
|
|
333
|
+
*
|
|
334
|
+
* - `'throw'`: Traditional Topological Sorting. Guarantees dependency order and catches
|
|
335
|
+
* circular dependencies mathematically before execution.
|
|
336
|
+
*
|
|
337
|
+
* - `'warn'`: Topological sorting, but logs a warning instead of throwing on cycles.
|
|
338
|
+
* - `'break'`: Topological sorting, but silently breaks cycles.
|
|
339
|
+
* - `'strict'`: Prevents cycle creation by checking the graph *during* dependency discovery.
|
|
340
|
+
*
|
|
341
|
+
* @default 'none'
|
|
320
342
|
*/
|
|
321
|
-
cycleHandling: '
|
|
343
|
+
cycleHandling: 'none' as 'none' | 'throw' | 'warn' | 'break' | 'strict',
|
|
322
344
|
/**
|
|
323
345
|
* Internal flag used by memoization discrepancy detector to avoid counting calls in tests
|
|
324
346
|
* @warning Do not modify this flag manually, this flag is given by the engine
|
|
@@ -388,6 +410,7 @@ export const options = {
|
|
|
388
410
|
* Configuration for zone hooks - control which async APIs are hooked
|
|
389
411
|
* Each option controls whether the corresponding async API is wrapped to preserve effect context
|
|
390
412
|
* Only applies when asyncMode is enabled (truthy)
|
|
413
|
+
* @deprecated Should take all when we made sure PIXI.create, Game.create, ... are -> .root()
|
|
391
414
|
*/
|
|
392
415
|
zones: {
|
|
393
416
|
/**
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { prototypeForwarding } from './reactive/types'
|
|
2
|
-
|
|
3
1
|
type ElementTypes<T extends readonly unknown[]> = {
|
|
4
2
|
[K in keyof T]: T[K] extends readonly (infer U)[] ? U : T[K]
|
|
5
3
|
}
|
|
@@ -88,29 +86,29 @@ export function renamed<F extends Function>(fct: F, name: string): F {
|
|
|
88
86
|
},
|
|
89
87
|
})
|
|
90
88
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
89
|
+
export const FoolProof = {
|
|
90
|
+
get(obj: any, prop: any, receiver: any) {
|
|
91
|
+
// Check if Node is available and obj is an instance of Node
|
|
92
|
+
if (typeof Node !== 'undefined' && obj instanceof Node) return (obj as any)[prop]
|
|
93
|
+
return Reflect.get(obj, prop, receiver)
|
|
94
|
+
},
|
|
95
|
+
set(obj: any, prop: any, value: any, receiver: any) {
|
|
96
|
+
// Check if Node is available and obj is an instance of Node
|
|
97
|
+
if (typeof Node !== 'undefined' && obj instanceof Node) {
|
|
98
|
+
;(obj as any)[prop] = value
|
|
99
|
+
return true
|
|
100
|
+
}
|
|
101
|
+
if (!(obj instanceof Object) && !Reflect.has(obj, prop)) {
|
|
102
|
+
Object.defineProperty(obj, prop, {
|
|
103
|
+
value,
|
|
104
|
+
configurable: true,
|
|
105
|
+
writable: true,
|
|
106
|
+
enumerable: true,
|
|
107
|
+
})
|
|
108
|
+
return true
|
|
109
|
+
}
|
|
110
|
+
return Reflect.set(obj, prop, value, receiver)
|
|
111
|
+
},
|
|
114
112
|
}
|
|
115
113
|
|
|
116
114
|
export function isOwnAccessor(obj: any, prop: any) {
|
|
@@ -128,14 +126,6 @@ export function isOwnAccessor(obj: any, prop: any) {
|
|
|
128
126
|
* @returns True if values are deeply equal
|
|
129
127
|
*/
|
|
130
128
|
export function deepCompare(a: any, b: any, cache = new Map<object, Set<object>>()): boolean {
|
|
131
|
-
// Unwrap mutts proxies if present
|
|
132
|
-
while (a && typeof a === 'object' && prototypeForwarding in a) {
|
|
133
|
-
a = (a as any)[prototypeForwarding]
|
|
134
|
-
}
|
|
135
|
-
while (b && typeof b === 'object' && prototypeForwarding in b) {
|
|
136
|
-
b = (b as any)[prototypeForwarding]
|
|
137
|
-
}
|
|
138
|
-
|
|
139
129
|
if (a === b) return true
|
|
140
130
|
|
|
141
131
|
if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {
|
|
@@ -146,7 +136,10 @@ export function deepCompare(a: any, b: any, cache = new Map<object, Set<object>>
|
|
|
146
136
|
const protoA = Object.getPrototypeOf(a)
|
|
147
137
|
const protoB = Object.getPrototypeOf(b)
|
|
148
138
|
if (protoA !== protoB) {
|
|
149
|
-
console.warn(`[deepCompare] prototype mismatch:`, {
|
|
139
|
+
console.warn(`[deepCompare] prototype mismatch:`, {
|
|
140
|
+
nameA: a?.constructor?.name,
|
|
141
|
+
nameB: b?.constructor?.name,
|
|
142
|
+
})
|
|
150
143
|
return false
|
|
151
144
|
}
|
|
152
145
|
// Circular reference protection
|
|
@@ -239,20 +232,70 @@ export function deepCompare(a: any, b: any, cache = new Map<object, Set<object>>
|
|
|
239
232
|
const keysA = Object.keys(a)
|
|
240
233
|
const keysB = Object.keys(b)
|
|
241
234
|
if (keysA.length !== keysB.length) {
|
|
242
|
-
console.warn(`[deepCompare] keys length mismatch:`, {
|
|
235
|
+
console.warn(`[deepCompare] keys length mismatch:`, {
|
|
236
|
+
lenA: keysA.length,
|
|
237
|
+
lenB: keysB.length,
|
|
238
|
+
keysA,
|
|
239
|
+
keysB,
|
|
240
|
+
a,
|
|
241
|
+
b,
|
|
242
|
+
})
|
|
243
243
|
return false
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
for (const key of keysA) {
|
|
247
|
-
if (!Object.
|
|
247
|
+
if (!Object.hasOwn(b, key)) {
|
|
248
248
|
console.warn(`[deepCompare] missing key ${String(key)} in B`)
|
|
249
249
|
return false
|
|
250
250
|
}
|
|
251
251
|
if (!deepCompare(a[key], b[key], cache)) {
|
|
252
|
-
console.warn(`[deepCompare] value mismatch for key ${String(key)}:`, {
|
|
252
|
+
console.warn(`[deepCompare] value mismatch for key ${String(key)}:`, {
|
|
253
|
+
valA: a[key],
|
|
254
|
+
valB: b[key],
|
|
255
|
+
})
|
|
253
256
|
return false
|
|
254
257
|
}
|
|
255
258
|
}
|
|
256
259
|
|
|
257
260
|
return true
|
|
258
261
|
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
const contentRefs = new WeakMap<object, any>()
|
|
265
|
+
export function contentRef(container: object) {
|
|
266
|
+
if (!contentRefs.has(container))
|
|
267
|
+
contentRefs.set(
|
|
268
|
+
container,
|
|
269
|
+
Object.seal(
|
|
270
|
+
Object.create(null, {
|
|
271
|
+
contentOf: { value: container, writable: false, configurable: false },
|
|
272
|
+
})
|
|
273
|
+
)
|
|
274
|
+
)
|
|
275
|
+
return contentRefs.get(container)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function tag<T extends object>(name: string, obj: T): T {
|
|
279
|
+
Object.defineProperties(obj, {
|
|
280
|
+
[Symbol.toStringTag]: {
|
|
281
|
+
value: name,
|
|
282
|
+
writable: false,
|
|
283
|
+
configurable: false,
|
|
284
|
+
},
|
|
285
|
+
toString: {
|
|
286
|
+
value: () => name,
|
|
287
|
+
writable: false,
|
|
288
|
+
configurable: false,
|
|
289
|
+
},
|
|
290
|
+
})
|
|
291
|
+
return obj
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function named<T extends Function>(name: string, fn: T): T {
|
|
295
|
+
Object.defineProperty(fn, 'name', {
|
|
296
|
+
value: fn.name ? `${fn.name}::${name}` : name,
|
|
297
|
+
writable: false,
|
|
298
|
+
configurable: false,
|
|
299
|
+
})
|
|
300
|
+
return fn
|
|
301
|
+
}
|
package/src/zone.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { asyncHooks } from "./async"
|
|
2
|
+
import { named, tag } from "./utils"
|
|
3
|
+
|
|
4
|
+
interface InternalZoneUse<T> {
|
|
5
|
+
enter(value?: T): unknown
|
|
6
|
+
leave(entered: unknown): void
|
|
7
|
+
}
|
|
8
|
+
function isu<T>(z: AZone<T> | InternalZoneUse<T>): InternalZoneUse<T> {
|
|
9
|
+
return z as InternalZoneUse<T>
|
|
10
|
+
}
|
|
11
|
+
export abstract class AZone<T> {
|
|
12
|
+
abstract active?: T
|
|
13
|
+
protected enter(value?: T): unknown {
|
|
14
|
+
const prev = this.active
|
|
15
|
+
this.active = value
|
|
16
|
+
return prev
|
|
17
|
+
}
|
|
18
|
+
protected leave(entered: unknown): void {
|
|
19
|
+
this.active = entered as T | undefined
|
|
20
|
+
}
|
|
21
|
+
with<R>(value: T, fn: () => R): R {
|
|
22
|
+
const entered = this.enter(value)
|
|
23
|
+
let res: R;
|
|
24
|
+
try {
|
|
25
|
+
res = fn()
|
|
26
|
+
} finally {
|
|
27
|
+
this.leave(entered)
|
|
28
|
+
}
|
|
29
|
+
// [HACK]: Sanitization
|
|
30
|
+
// See BROWSER_ASYNC_POLYFILL.md
|
|
31
|
+
return asyncHooks.sanitizePromise(res) as R
|
|
32
|
+
}
|
|
33
|
+
root<R>(fn: () => R): R {
|
|
34
|
+
let prev = this.enter()
|
|
35
|
+
try {
|
|
36
|
+
return fn()
|
|
37
|
+
} finally {
|
|
38
|
+
this.leave(prev)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
get zoned(): FunctionWrapper {
|
|
42
|
+
const active = this.active
|
|
43
|
+
return named(`${this}@${active}`, (fn) => this.with(active, fn))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type FunctionWrapper = <R>(fn?: () => R) => R
|
|
48
|
+
|
|
49
|
+
export class Zone<T> extends AZone<T> {
|
|
50
|
+
active: T | undefined
|
|
51
|
+
}
|
|
52
|
+
type HistoryValue<T> = {present: T | undefined, history: Set<T>}
|
|
53
|
+
export class ZoneHistory<T> extends AZone<HistoryValue<T>> {
|
|
54
|
+
private history = new Set<T>()
|
|
55
|
+
public readonly present: AZone<T>
|
|
56
|
+
public has(value: T): boolean {
|
|
57
|
+
return this.history.has(value)
|
|
58
|
+
}
|
|
59
|
+
public some(predicate: (value: T) => boolean): boolean {
|
|
60
|
+
for(const value of this.history) if(predicate(value)) return true
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
constructor(private controlled: AZone<T> = new Zone<T>()) {
|
|
64
|
+
super()
|
|
65
|
+
const self = this
|
|
66
|
+
this.present = Object.create(controlled,
|
|
67
|
+
Object.getOwnPropertyDescriptors({
|
|
68
|
+
get active() { return controlled.active },
|
|
69
|
+
set active(value: T | undefined) {
|
|
70
|
+
controlled.active = value
|
|
71
|
+
},
|
|
72
|
+
enter(value?: T) {
|
|
73
|
+
if(value && self.history.has(value)) throw new Error('ZoneHistory: re-entering historical zone')
|
|
74
|
+
if(value !== undefined) self.history.add(value)
|
|
75
|
+
return { added: value, entered: isu(controlled).enter(value) }
|
|
76
|
+
},
|
|
77
|
+
leave(entered: { added: T | undefined, entered: unknown }) {
|
|
78
|
+
if(entered.added !== undefined) self.history.delete(entered.added)
|
|
79
|
+
return isu(controlled).leave(entered.entered)
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
get active() {
|
|
85
|
+
return {present: this.controlled.active, history: new Set(this.history)}
|
|
86
|
+
}
|
|
87
|
+
set active(value: HistoryValue<T> | undefined) {
|
|
88
|
+
this.history = this.history && new Set(this.history)
|
|
89
|
+
this.controlled.active = value?.present
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export class ZoneAggregator extends AZone<Map<AZone<unknown>, unknown>> {
|
|
94
|
+
#zones = new Set<AZone<unknown>>()
|
|
95
|
+
constructor(...zones: AZone<unknown>[]) {
|
|
96
|
+
super()
|
|
97
|
+
for (const z of zones) this.#zones.add(z)
|
|
98
|
+
}
|
|
99
|
+
get active(): Map<AZone<unknown>, unknown> | undefined {
|
|
100
|
+
const rv = new Map<AZone<unknown>, unknown>()
|
|
101
|
+
for (const z of this.#zones)
|
|
102
|
+
if (z.active !== undefined) rv.set(z, z.active)
|
|
103
|
+
return rv
|
|
104
|
+
}
|
|
105
|
+
set active(value: Map<AZone<unknown>, unknown> | undefined) {
|
|
106
|
+
for (const z of this.#zones) z.active = value?.get(z)
|
|
107
|
+
}
|
|
108
|
+
enter(value?: Map<AZone<unknown>, unknown> | undefined) {
|
|
109
|
+
const entered = new Map<AZone<unknown>, unknown>()
|
|
110
|
+
for (const z of this.#zones) {
|
|
111
|
+
const v = value?.get(z)
|
|
112
|
+
entered.set(z, isu(z).enter(v))
|
|
113
|
+
}
|
|
114
|
+
return entered
|
|
115
|
+
}
|
|
116
|
+
leave(entered: Map<AZone<unknown>, unknown>): void {
|
|
117
|
+
for (const z of this.#zones) isu(z).leave(entered.get(z))
|
|
118
|
+
}
|
|
119
|
+
add(z: AZone<unknown>) {
|
|
120
|
+
this.#zones.add(z)
|
|
121
|
+
}
|
|
122
|
+
delete(z: AZone<unknown>) {
|
|
123
|
+
this.#zones.delete(z)
|
|
124
|
+
}
|
|
125
|
+
clear() {
|
|
126
|
+
this.#zones.clear()
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const asyncZone = tag('async', new ZoneAggregator())
|
|
131
|
+
asyncHooks.addHook(() => {
|
|
132
|
+
const zone = asyncZone.active
|
|
133
|
+
return () => {
|
|
134
|
+
const prev = asyncZone.active
|
|
135
|
+
asyncZone.active = zone
|
|
136
|
+
return () => asyncZone.active = prev
|
|
137
|
+
}
|
|
138
|
+
})
|