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/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
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { rootFunction, type ScopedCallback } from './types'
|
|
2
|
+
|
|
3
|
+
// Track which effects are watching which reactive objects for cleanup
|
|
4
|
+
export const effectToReactiveObjects = new WeakMap<ScopedCallback, Set<object>>()
|
|
5
|
+
|
|
6
|
+
// Track effects per reactive object and property
|
|
7
|
+
export const watchers = new WeakMap<object, Map<any, Set<ScopedCallback>>>()
|
|
8
|
+
|
|
9
|
+
// runEffect -> set<stop>
|
|
10
|
+
export const effectChildren = new WeakMap<ScopedCallback, Set<ScopedCallback>>()
|
|
11
|
+
|
|
12
|
+
// Track parent effect relationships for hierarchy traversal (used in deep touch filtering)
|
|
13
|
+
export const effectParent = new WeakMap<ScopedCallback, ScopedCallback | undefined>()
|
|
14
|
+
|
|
15
|
+
// Track reverse mapping to ensure unicity: One Root -> One Function
|
|
16
|
+
const reverseRoots = new WeakMap<any, WeakRef<Function>>()
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Marks a function with its root function for effect tracking
|
|
20
|
+
* Enforces strict unicity: A root function can only identify ONE function.
|
|
21
|
+
* @param fn - The function to mark
|
|
22
|
+
* @param root - The root function
|
|
23
|
+
* @returns The marked function
|
|
24
|
+
*/
|
|
25
|
+
export function markWithRoot<T extends Function>(fn: T, root: any): T {
|
|
26
|
+
// Check for collision
|
|
27
|
+
const existingRef = reverseRoots.get(root)
|
|
28
|
+
const existing = existingRef?.deref()
|
|
29
|
+
|
|
30
|
+
if (existing && existing !== fn) {
|
|
31
|
+
const rootName = root.name || 'anonymous'
|
|
32
|
+
const existingName = existing.name || 'anonymous'
|
|
33
|
+
const fnName = fn.name || 'anonymous'
|
|
34
|
+
throw new Error(
|
|
35
|
+
`[reactive] Abusive Shared Root detected: Root '${rootName}' is already identifying function '${existingName}'. ` +
|
|
36
|
+
`Cannot reuse it for '${fnName}'. Shared roots cause lost updates and broken identity logic.`
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Always update the map so subsequent checks find this one
|
|
41
|
+
// (Last writer wins for the check)
|
|
42
|
+
reverseRoots.set(root, new WeakRef(fn))
|
|
43
|
+
|
|
44
|
+
// Mark fn with the new root
|
|
45
|
+
return Object.defineProperty(fn, rootFunction, {
|
|
46
|
+
value: getRoot(root),
|
|
47
|
+
writable: false,
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Gets the root function of a function for effect tracking
|
|
53
|
+
* @param fn - The function to get the root of
|
|
54
|
+
* @returns The root function
|
|
55
|
+
*/
|
|
56
|
+
export function getRoot<T extends Function | undefined>(fn: T): T {
|
|
57
|
+
while (fn && rootFunction in fn) fn = fn[rootFunction] as T
|
|
58
|
+
return fn
|
|
59
|
+
}
|
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
|
},
|
|
124
|
+
[Symbol.iterator]() {
|
|
125
|
+
return this
|
|
126
|
+
},
|
|
127
|
+
[Symbol.dispose]() {},
|
|
142
128
|
}
|
|
143
129
|
}
|
|
144
130
|
}
|
package/src/reactive/tracking.ts
CHANGED
|
@@ -1,62 +1,10 @@
|
|
|
1
1
|
import { getActiveEffect } from './effect-context'
|
|
2
2
|
import { unwrap } from './proxy-state'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
// Track effects per reactive object and property
|
|
9
|
-
export const watchers = new WeakMap<object, Map<any, Set<ScopedCallback>>>()
|
|
10
|
-
|
|
11
|
-
// runEffect -> set<stop>
|
|
12
|
-
export const effectChildren = new WeakMap<ScopedCallback, Set<ScopedCallback>>()
|
|
13
|
-
|
|
14
|
-
// Track parent effect relationships for hierarchy traversal (used in deep touch filtering)
|
|
15
|
-
export const effectParent = new WeakMap<ScopedCallback, ScopedCallback | undefined>()
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Marks a function with its root function for effect tracking
|
|
19
|
-
* @param fn - The function to mark
|
|
20
|
-
* @param root - The root function
|
|
21
|
-
* @returns The marked function
|
|
22
|
-
*/
|
|
23
|
-
export function markWithRoot<T extends Function>(fn: T, root: Function): T {
|
|
24
|
-
// Mark fn with the new root
|
|
25
|
-
return Object.defineProperty(fn, rootFunction, {
|
|
26
|
-
value: getRoot(root),
|
|
27
|
-
writable: false,
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Gets the root function of a function for effect tracking
|
|
33
|
-
* @param fn - The function to get the root of
|
|
34
|
-
* @returns The root function
|
|
35
|
-
*/
|
|
36
|
-
export function getRoot<T extends Function | undefined>(fn: T): T {
|
|
37
|
-
return (fn as any)?.[rootFunction] || fn
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Flag to disable dependency tracking for the current active effect (not globally)
|
|
41
|
-
const trackingDisabledEffects = new WeakSet<ScopedCallback>()
|
|
42
|
-
let globalTrackingDisabled = false
|
|
43
|
-
|
|
44
|
-
export function getTrackingDisabled(): boolean {
|
|
45
|
-
const active = getActiveEffect()
|
|
46
|
-
if (!active) return globalTrackingDisabled
|
|
47
|
-
return trackingDisabledEffects.has(getRoot(active))
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function setTrackingDisabled(value: boolean): void {
|
|
51
|
-
const active = getActiveEffect()
|
|
52
|
-
if (!active) {
|
|
53
|
-
globalTrackingDisabled = value
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
const root = getRoot(active)
|
|
57
|
-
if (value) trackingDisabledEffects.add(root)
|
|
58
|
-
else trackingDisabledEffects.delete(root)
|
|
59
|
-
}
|
|
3
|
+
import {
|
|
4
|
+
effectToReactiveObjects,
|
|
5
|
+
watchers,
|
|
6
|
+
} from './registry'
|
|
7
|
+
import { allProps, type ScopedCallback } from './types'
|
|
60
8
|
|
|
61
9
|
/**
|
|
62
10
|
* Marks a property as a dependency of the current effect
|
|
@@ -70,15 +18,10 @@ export function dependant(obj: any, prop: any = allProps) {
|
|
|
70
18
|
// Early return if no active effect, tracking disabled, or invalid prop
|
|
71
19
|
if (
|
|
72
20
|
!currentActiveEffect ||
|
|
73
|
-
getTrackingDisabled() ||
|
|
74
21
|
(typeof prop === 'symbol' && prop !== allProps)
|
|
75
22
|
)
|
|
76
23
|
return
|
|
77
24
|
|
|
78
|
-
registerDependency(obj, prop, currentActiveEffect)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function registerDependency(obj: any, prop: any, currentActiveEffect: ScopedCallback) {
|
|
82
25
|
let objectWatchers = watchers.get(obj)
|
|
83
26
|
if (!objectWatchers) {
|
|
84
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
|
|
@@ -146,6 +139,16 @@ export const allProps = Symbol('all-props')
|
|
|
146
139
|
*/
|
|
147
140
|
export const projectionInfo = Symbol('projection-info')
|
|
148
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Symbol to check if an effect is stopped
|
|
144
|
+
*/
|
|
145
|
+
export const stopped = Symbol('stopped')
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Symbol to access effect cleanup function
|
|
149
|
+
*/
|
|
150
|
+
export const cleanup = Symbol('cleanup')
|
|
151
|
+
|
|
149
152
|
/**
|
|
150
153
|
* Context for a running projection item effect
|
|
151
154
|
*/
|
|
@@ -169,6 +172,7 @@ export enum ReactiveErrorCode {
|
|
|
169
172
|
MaxReactionExceeded = 'MAX_REACTION_EXCEEDED',
|
|
170
173
|
WriteInComputed = 'WRITE_IN_COMPUTED',
|
|
171
174
|
TrackingError = 'TRACKING_ERROR',
|
|
175
|
+
BrokenEffects = 'BROKEN_EFFECTS',
|
|
172
176
|
}
|
|
173
177
|
|
|
174
178
|
export type CycleDebugInfo = {
|
|
@@ -189,6 +193,11 @@ export type MaxReactionDebugInfo = {
|
|
|
189
193
|
effect: string
|
|
190
194
|
}
|
|
191
195
|
|
|
196
|
+
export type BrokenEffectsDebugInfo = {
|
|
197
|
+
code: ReactiveErrorCode.BrokenEffects
|
|
198
|
+
cause: any
|
|
199
|
+
}
|
|
200
|
+
|
|
192
201
|
export type GenericDebugInfo = {
|
|
193
202
|
code: ReactiveErrorCode
|
|
194
203
|
causalChain?: string[]
|
|
@@ -200,6 +209,7 @@ export type ReactiveDebugInfo =
|
|
|
200
209
|
| CycleDebugInfo
|
|
201
210
|
| MaxDepthDebugInfo
|
|
202
211
|
| MaxReactionDebugInfo
|
|
212
|
+
| BrokenEffectsDebugInfo
|
|
203
213
|
| GenericDebugInfo
|
|
204
214
|
|
|
205
215
|
/**
|
|
@@ -213,6 +223,14 @@ export class ReactiveError extends Error {
|
|
|
213
223
|
super(message)
|
|
214
224
|
this.name = 'ReactiveError'
|
|
215
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
|
+
}
|
|
216
234
|
}
|
|
217
235
|
|
|
218
236
|
// biome-ignore-start lint/correctness/noUnusedFunctionParameters: Interface declaration with empty defaults
|
|
@@ -259,7 +277,7 @@ export const options = {
|
|
|
259
277
|
* @param effect - The effect that is already running
|
|
260
278
|
* @param runningChain - The array of effects from the detected one to the currently running one
|
|
261
279
|
*/
|
|
262
|
-
skipRunningEffect: (_effect: ScopedCallback
|
|
280
|
+
skipRunningEffect: (_effect: ScopedCallback) => {},
|
|
263
281
|
/**
|
|
264
282
|
* Debug purpose: maximum effect chain (like call stack max depth)
|
|
265
283
|
* Used to prevent infinite loops
|
|
@@ -279,14 +297,55 @@ export const options = {
|
|
|
279
297
|
*/
|
|
280
298
|
maxEffectReaction: 'throw' as 'throw' | 'debug' | 'warn',
|
|
281
299
|
/**
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
300
|
+
* Callback called when a memoization discrepancy is detected (debug only)
|
|
301
|
+
* When defined, memoized functions will run a second time (untracked) to verify consistency.
|
|
302
|
+
* If the untracked run returns a different value than the cached one, this callback is triggered.
|
|
303
|
+
*
|
|
304
|
+
* This is the primary tool for detecting missing reactive dependencies in computed values.
|
|
305
|
+
*
|
|
306
|
+
* @param cached - The value currently in the memoization cache
|
|
307
|
+
* @param fresh - The value obtained by re-running the function untracked
|
|
308
|
+
* @param fn - The memoized function itself
|
|
309
|
+
* @param args - Arguments passed to the function
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* ```typescript
|
|
313
|
+
* reactiveOptions.onMemoizationDiscrepancy = (cached, fresh, fn, args) => {
|
|
314
|
+
* throw new Error(`Memoization discrepancy in ${fn.name}!`);
|
|
315
|
+
* };
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
318
|
+
onMemoizationDiscrepancy: undefined as
|
|
319
|
+
| ((
|
|
320
|
+
cached: any,
|
|
321
|
+
fresh: any,
|
|
322
|
+
fn: Function,
|
|
323
|
+
args: any[],
|
|
324
|
+
cause: 'calculation' | 'comparison'
|
|
325
|
+
) => void)
|
|
326
|
+
| undefined,
|
|
327
|
+
/**
|
|
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'
|
|
342
|
+
*/
|
|
343
|
+
cycleHandling: 'none' as 'none' | 'throw' | 'warn' | 'break' | 'strict',
|
|
344
|
+
/**
|
|
345
|
+
* Internal flag used by memoization discrepancy detector to avoid counting calls in tests
|
|
346
|
+
* @warning Do not modify this flag manually, this flag is given by the engine
|
|
288
347
|
*/
|
|
289
|
-
|
|
348
|
+
isVerificationRun: false,
|
|
290
349
|
/**
|
|
291
350
|
* Maximum depth for deep watching traversal
|
|
292
351
|
* Used to prevent infinite recursion in circular references
|
|
@@ -351,6 +410,7 @@ export const options = {
|
|
|
351
410
|
* Configuration for zone hooks - control which async APIs are hooked
|
|
352
411
|
* Each option controls whether the corresponding async API is wrapped to preserve effect context
|
|
353
412
|
* Only applies when asyncMode is enabled (truthy)
|
|
413
|
+
* @deprecated Should take all when we made sure PIXI.create, Game.create, ... are -> .root()
|
|
354
414
|
*/
|
|
355
415
|
zones: {
|
|
356
416
|
/**
|
package/src/std-decorators.ts
CHANGED
|
@@ -7,7 +7,7 @@ const syncCalculating: { object: object; prop: PropertyKey }[] = []
|
|
|
7
7
|
* Prevents circular dependencies and provides automatic cache invalidation
|
|
8
8
|
*/
|
|
9
9
|
export const cached = decorator({
|
|
10
|
-
getter(original, propertyKey) {
|
|
10
|
+
getter(original, _target, propertyKey) {
|
|
11
11
|
return function (this: any) {
|
|
12
12
|
const alreadyCalculating = syncCalculating.findIndex(
|
|
13
13
|
(c) => c.object === this && c.prop === propertyKey
|
|
@@ -83,19 +83,19 @@ export function describe(descriptor: {
|
|
|
83
83
|
*/
|
|
84
84
|
export const deprecated = Object.assign(
|
|
85
85
|
decorator({
|
|
86
|
-
method(original, propertyKey) {
|
|
86
|
+
method(original, _target, propertyKey) {
|
|
87
87
|
return function (this: any, ...args: any[]) {
|
|
88
88
|
deprecated.warn(this, propertyKey)
|
|
89
89
|
return original.apply(this, args)
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
|
-
getter(original, propertyKey) {
|
|
92
|
+
getter(original, _target, propertyKey) {
|
|
93
93
|
return function (this: any) {
|
|
94
94
|
deprecated.warn(this, propertyKey)
|
|
95
95
|
return original.call(this)
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
|
-
setter(original, propertyKey) {
|
|
98
|
+
setter(original, _target, propertyKey) {
|
|
99
99
|
return function (this: any, value: any) {
|
|
100
100
|
deprecated.warn(this, propertyKey)
|
|
101
101
|
return original.call(this, value)
|
|
@@ -111,19 +111,19 @@ export const deprecated = Object.assign(
|
|
|
111
111
|
},
|
|
112
112
|
default(message: string) {
|
|
113
113
|
return decorator({
|
|
114
|
-
method(original, propertyKey) {
|
|
114
|
+
method(original, _target, propertyKey) {
|
|
115
115
|
return function (this: any, ...args: any[]) {
|
|
116
116
|
deprecated.warn(this, propertyKey, message)
|
|
117
117
|
return original.apply(this, args)
|
|
118
118
|
}
|
|
119
119
|
},
|
|
120
|
-
getter(original, propertyKey) {
|
|
120
|
+
getter(original, _target, propertyKey) {
|
|
121
121
|
return function (this: any) {
|
|
122
122
|
deprecated.warn(this, propertyKey, message)
|
|
123
123
|
return original.call(this)
|
|
124
124
|
}
|
|
125
125
|
},
|
|
126
|
-
setter(original, propertyKey) {
|
|
126
|
+
setter(original, _target, propertyKey) {
|
|
127
127
|
return function (this: any, value: any) {
|
|
128
128
|
deprecated.warn(this, propertyKey, message)
|
|
129
129
|
return original.call(this, value)
|
|
@@ -157,7 +157,7 @@ export const deprecated = Object.assign(
|
|
|
157
157
|
*/
|
|
158
158
|
export function debounce(delay: number) {
|
|
159
159
|
return decorator({
|
|
160
|
-
method(original, _propertyKey) {
|
|
160
|
+
method(original, _target, _propertyKey) {
|
|
161
161
|
let timeoutId: ReturnType<typeof setTimeout> | null = null
|
|
162
162
|
|
|
163
163
|
return function (this: any, ...args: any[]) {
|
|
@@ -183,7 +183,7 @@ export function debounce(delay: number) {
|
|
|
183
183
|
*/
|
|
184
184
|
export function throttle(delay: number) {
|
|
185
185
|
return decorator({
|
|
186
|
-
method(original, _propertyKey) {
|
|
186
|
+
method(original, _target, _propertyKey) {
|
|
187
187
|
let lastCallTime = 0
|
|
188
188
|
let timeoutId: ReturnType<typeof setTimeout> | null = null
|
|
189
189
|
|