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.
Files changed (196) hide show
  1. package/README.md +61 -23
  2. package/dist/async/browser.d.ts +2 -0
  3. package/dist/async/browser.d.ts.map +1 -0
  4. package/dist/async/index.d.ts +18 -0
  5. package/dist/async/index.d.ts.map +1 -0
  6. package/dist/async/node.d.ts +2 -0
  7. package/dist/async/node.d.ts.map +1 -0
  8. package/dist/{chunks/index-CDCOjzTy.js → browser.cjs} +5913 -4382
  9. package/dist/browser.cjs.map +1 -0
  10. package/dist/browser.d.ts +1655 -0
  11. package/dist/browser.esm.js +305 -0
  12. package/dist/browser.esm.js.map +1 -0
  13. package/dist/chunks/async-browser-CA0jPWIi.cjs +304 -0
  14. package/dist/chunks/async-browser-CA0jPWIi.cjs.map +1 -0
  15. package/dist/chunks/async-core-UqHzvJ-S.cjs +25 -0
  16. package/dist/chunks/async-core-UqHzvJ-S.cjs.map +1 -0
  17. package/dist/chunks/async-node-BYHuGTni.cjs +103 -0
  18. package/dist/chunks/async-node-BYHuGTni.cjs.map +1 -0
  19. package/dist/chunks/{index-DiP0RXoZ.esm.js → index-DhaOVusv.esm.js} +5851 -4345
  20. package/dist/chunks/index-DhaOVusv.esm.js.map +1 -0
  21. package/dist/decorator.d.ts +17 -18
  22. package/dist/decorator.d.ts.map +1 -0
  23. package/dist/destroyable.d.ts +12 -15
  24. package/dist/destroyable.d.ts.map +1 -0
  25. package/dist/devtools/devtool/devtools.d.ts +1 -0
  26. package/dist/devtools/devtool/devtools.d.ts.map +1 -0
  27. package/dist/devtools/devtool/panel.d.ts +2 -0
  28. package/dist/devtools/devtool/panel.d.ts.map +1 -0
  29. package/dist/devtools/panel.js.map +1 -1
  30. package/dist/entry-browser.d.ts +3 -0
  31. package/dist/entry-browser.d.ts.map +1 -0
  32. package/dist/entry-node.d.ts +3 -0
  33. package/dist/entry-node.d.ts.map +1 -0
  34. package/dist/eventful.d.ts +3 -5
  35. package/dist/eventful.d.ts.map +1 -0
  36. package/dist/index.d.ts +13 -19
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/indexable.d.ts +10 -10
  39. package/dist/indexable.d.ts.map +1 -0
  40. package/dist/introspection.d.ts +27 -0
  41. package/dist/introspection.d.ts.map +1 -0
  42. package/dist/iterableWeak.d.ts +53 -0
  43. package/dist/iterableWeak.d.ts.map +1 -0
  44. package/dist/mixins.d.ts +25 -0
  45. package/dist/mixins.d.ts.map +1 -0
  46. package/dist/mutts.umd.js +1 -1
  47. package/dist/mutts.umd.js.map +1 -1
  48. package/dist/mutts.umd.min.js +1 -1
  49. package/dist/mutts.umd.min.js.map +1 -1
  50. package/dist/node.cjs +105 -0
  51. package/dist/node.cjs.map +1 -0
  52. package/dist/node.d.ts +1 -0
  53. package/dist/node.esm.js +104 -0
  54. package/dist/node.esm.js.map +1 -0
  55. package/dist/promiseChain.d.ts +4 -5
  56. package/dist/promiseChain.d.ts.map +1 -0
  57. package/dist/reactive/array.d.ts +49 -0
  58. package/dist/reactive/array.d.ts.map +1 -0
  59. package/dist/reactive/buffer.d.ts +44 -0
  60. package/dist/reactive/buffer.d.ts.map +1 -0
  61. package/dist/reactive/change.d.ts +29 -0
  62. package/dist/reactive/change.d.ts.map +1 -0
  63. package/dist/reactive/debug.d.ts +111 -0
  64. package/dist/reactive/debug.d.ts.map +1 -0
  65. package/dist/reactive/deep-touch.d.ts +28 -0
  66. package/dist/reactive/deep-touch.d.ts.map +1 -0
  67. package/dist/reactive/deep-watch-state.d.ts +25 -0
  68. package/dist/reactive/deep-watch-state.d.ts.map +1 -0
  69. package/dist/reactive/deep-watch.d.ts +19 -0
  70. package/dist/reactive/deep-watch.d.ts.map +1 -0
  71. package/dist/reactive/effect-context.d.ts +7 -0
  72. package/dist/reactive/effect-context.d.ts.map +1 -0
  73. package/dist/reactive/effects.d.ts +151 -0
  74. package/dist/reactive/effects.d.ts.map +1 -0
  75. package/dist/reactive/index.d.ts +20 -0
  76. package/dist/reactive/index.d.ts.map +1 -0
  77. package/dist/reactive/interface.d.ts +64 -0
  78. package/dist/reactive/interface.d.ts.map +1 -0
  79. package/dist/reactive/map.d.ts +30 -0
  80. package/dist/reactive/map.d.ts.map +1 -0
  81. package/dist/reactive/memoize.d.ts +5 -0
  82. package/dist/reactive/memoize.d.ts.map +1 -0
  83. package/dist/reactive/non-reactive-state.d.ts +9 -0
  84. package/dist/reactive/non-reactive-state.d.ts.map +1 -0
  85. package/dist/reactive/non-reactive.d.ts +11 -0
  86. package/dist/reactive/non-reactive.d.ts.map +1 -0
  87. package/dist/reactive/project.d.ts +41 -0
  88. package/dist/reactive/project.d.ts.map +1 -0
  89. package/dist/reactive/proxy-state.d.ts +8 -0
  90. package/dist/reactive/proxy-state.d.ts.map +1 -0
  91. package/dist/reactive/proxy.d.ts +23 -0
  92. package/dist/reactive/proxy.d.ts.map +1 -0
  93. package/dist/reactive/record.d.ts +116 -0
  94. package/dist/reactive/record.d.ts.map +1 -0
  95. package/dist/reactive/register.d.ts +64 -0
  96. package/dist/reactive/register.d.ts.map +1 -0
  97. package/dist/reactive/registry.d.ts +20 -0
  98. package/dist/reactive/registry.d.ts.map +1 -0
  99. package/dist/reactive/set.d.ts +28 -0
  100. package/dist/reactive/set.d.ts.map +1 -0
  101. package/dist/reactive/tracking.d.ts +7 -0
  102. package/dist/reactive/tracking.d.ts.map +1 -0
  103. package/dist/reactive/types.d.ts +376 -0
  104. package/dist/reactive/types.d.ts.map +1 -0
  105. package/dist/std-decorators.d.ts +9 -11
  106. package/dist/std-decorators.d.ts.map +1 -0
  107. package/dist/utils.d.ts +49 -0
  108. package/dist/utils.d.ts.map +1 -0
  109. package/dist/zone.d.ts +40 -0
  110. package/dist/zone.d.ts.map +1 -0
  111. package/docs/ai/api-reference.md +0 -2
  112. package/docs/reactive/advanced.md +2 -5
  113. package/docs/reactive/collections.md +0 -125
  114. package/docs/reactive/core.md +27 -24
  115. package/docs/reactive/debugging.md +12 -2
  116. package/docs/reactive/project.md +1 -1
  117. package/docs/reactive/scan.md +78 -0
  118. package/docs/reactive.md +2 -1
  119. package/docs/std-decorators.md +69 -0
  120. package/docs/zone.md +95 -0
  121. package/package.json +67 -23
  122. package/src/async/browser.ts +319 -0
  123. package/src/async/index.ts +23 -0
  124. package/src/async/node.ts +104 -0
  125. package/src/decorator.ts +5 -1
  126. package/src/destroyable.ts +1 -1
  127. package/src/entry-browser.ts +5 -0
  128. package/src/entry-node.ts +5 -0
  129. package/src/index.d.ts +12 -9
  130. package/src/index.ts +23 -14
  131. package/src/indexable.ts +42 -0
  132. package/src/mixins.ts +2 -2
  133. package/src/reactive/array.ts +274 -179
  134. package/src/reactive/buffer.ts +168 -0
  135. package/src/reactive/change.ts +2 -2
  136. package/src/reactive/effect-context.ts +15 -91
  137. package/src/reactive/effects.ts +119 -179
  138. package/src/reactive/index.ts +11 -13
  139. package/src/reactive/interface.ts +19 -33
  140. package/src/reactive/map.ts +49 -62
  141. package/src/reactive/memoize.ts +19 -9
  142. package/src/reactive/project.ts +43 -22
  143. package/src/reactive/proxy.ts +16 -41
  144. package/src/reactive/record.ts +3 -3
  145. package/src/reactive/register.ts +5 -7
  146. package/src/reactive/registry.ts +9 -17
  147. package/src/reactive/set.ts +43 -57
  148. package/src/reactive/tracking.ts +1 -29
  149. package/src/reactive/types.ts +46 -23
  150. package/src/utils.ts +80 -37
  151. package/src/zone.ts +138 -0
  152. package/dist/chunks/_tslib-BgjropY9.js +0 -81
  153. package/dist/chunks/_tslib-BgjropY9.js.map +0 -1
  154. package/dist/chunks/_tslib-MCKDzsSq.esm.js +0 -75
  155. package/dist/chunks/_tslib-MCKDzsSq.esm.js.map +0 -1
  156. package/dist/chunks/decorator-BGILvPtN.esm.js +0 -627
  157. package/dist/chunks/decorator-BGILvPtN.esm.js.map +0 -1
  158. package/dist/chunks/decorator-BQ2eBTCj.js +0 -651
  159. package/dist/chunks/decorator-BQ2eBTCj.js.map +0 -1
  160. package/dist/chunks/index-CDCOjzTy.js.map +0 -1
  161. package/dist/chunks/index-DiP0RXoZ.esm.js.map +0 -1
  162. package/dist/decorator.esm.js +0 -2
  163. package/dist/decorator.esm.js.map +0 -1
  164. package/dist/decorator.js +0 -11
  165. package/dist/decorator.js.map +0 -1
  166. package/dist/destroyable.esm.js +0 -109
  167. package/dist/destroyable.esm.js.map +0 -1
  168. package/dist/destroyable.js +0 -116
  169. package/dist/destroyable.js.map +0 -1
  170. package/dist/eventful.esm.js +0 -66
  171. package/dist/eventful.esm.js.map +0 -1
  172. package/dist/eventful.js +0 -68
  173. package/dist/eventful.js.map +0 -1
  174. package/dist/index.esm.js +0 -53
  175. package/dist/index.esm.js.map +0 -1
  176. package/dist/index.js +0 -139
  177. package/dist/index.js.map +0 -1
  178. package/dist/indexable.esm.js +0 -285
  179. package/dist/indexable.esm.js.map +0 -1
  180. package/dist/indexable.js +0 -291
  181. package/dist/indexable.js.map +0 -1
  182. package/dist/promiseChain.esm.js +0 -78
  183. package/dist/promiseChain.esm.js.map +0 -1
  184. package/dist/promiseChain.js +0 -80
  185. package/dist/promiseChain.js.map +0 -1
  186. package/dist/reactive.d.ts +0 -910
  187. package/dist/reactive.esm.js +0 -5
  188. package/dist/reactive.esm.js.map +0 -1
  189. package/dist/reactive.js +0 -59
  190. package/dist/reactive.js.map +0 -1
  191. package/dist/std-decorators.esm.js +0 -196
  192. package/dist/std-decorators.esm.js.map +0 -1
  193. package/dist/std-decorators.js +0 -204
  194. package/dist/std-decorators.js.map +0 -1
  195. package/src/reactive/mapped.ts +0 -129
  196. package/src/reactive/zone.ts +0 -208
@@ -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
- readonly [native]!: WeakSet<T>
15
- readonly content!: symbol
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[native].has(value)
28
- this[native].add(value)
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.content, { type: 'add', prop: value }, value)
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[native].has(value)
39
- const res = this[native].delete(value)
40
- if (had) touched1(this.content, { type: 'del', prop: value }, value)
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.content, value)
46
- return this[native].has(value)
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
- readonly [native]!: Set<T>
56
- readonly content!: symbol
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[native].size
51
+ return this.size
70
52
  }
71
53
 
72
54
  add(value: T): this {
73
- const had = this[native].has(value)
55
+ const had = this.has(value)
74
56
  const reactiveValue = reactive(value)
75
- this[native].add(reactiveValue)
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.content, evolution, reactiveValue)
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[native].size > 0
87
- this[native].clear()
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.content, evolution)
73
+ touched(contentRef(this), evolution)
92
74
  }
93
75
  }
94
76
 
95
77
  delete(value: T): boolean {
96
- const had = this[native].has(value)
97
- const res = this[native].delete(value)
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.content, evolution, value)
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.content, value)
108
- return this[native].has(value)
89
+ dependant(contentRef(this), value)
90
+ return this.has(value)
109
91
  }
110
92
 
111
93
  entries(): Generator<[T, T]> {
112
- dependant(this.content)
113
- return makeReactiveEntriesIterator(this[native].entries())
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.content)
118
- this[native].forEach(callbackfn, thisArg)
99
+ dependant(contentRef(this))
100
+ this.forEach(callbackfn, thisArg)
119
101
  }
120
102
 
121
103
  keys(): Generator<T> {
122
- dependant(this.content)
123
- return makeReactiveIterator(this[native].keys())
104
+ dependant(contentRef(this))
105
+ return makeReactiveIterator(this.keys())
124
106
  }
125
107
 
126
108
  values(): Generator<T> {
127
- dependant(this.content)
128
- return makeReactiveIterator(this[native].values())
109
+ dependant(contentRef(this))
110
+ return makeReactiveIterator(this.values())
129
111
  }
130
112
 
131
- [Symbol.iterator](): Iterator<T> {
132
- dependant(this.content)
133
- const nativeIterator = this[native][Symbol.iterator]()
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
  }
@@ -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>>()
@@ -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
- * Function type for dependency tracking in effects
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: DependencyFunction
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: DependencyFunction
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, _runningChain: 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
- | ((cached: any, fresh: any, fn: Function, args: any[], cause: "calculation" | "comparison") => void)
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
- * - 'throw': Throw an error with cycle information (default, recommended for development)
316
- * - 'warn': Log a warning and break the cycle by executing one effect
317
- * - 'break': Silently break the cycle by executing one effect (recommended for production)
318
- * - 'strict': Prevent cycle creation by checking graph before execution (throws error)
319
- * @default 'throw'
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: 'throw' as 'throw' | 'warn' | 'break' | 'strict',
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
- export function ReflectGet(obj: any, prop: any, receiver: any) {
93
- // Check if Node is available and obj is an instance of Node
94
- if (typeof Node !== 'undefined' && obj instanceof Node) return (obj as any)[prop]
95
- return Reflect.get(obj, prop, receiver)
96
- }
97
-
98
- export function ReflectSet(obj: any, prop: any, value: any, receiver: any) {
99
- // Check if Node is available and obj is an instance of Node
100
- if (typeof Node !== 'undefined' && obj instanceof Node) {
101
- ;(obj as any)[prop] = value
102
- return true
103
- }
104
- if (!(obj instanceof Object) && !Reflect.has(obj, prop)) {
105
- Object.defineProperty(obj, prop, {
106
- value,
107
- configurable: true,
108
- writable: true,
109
- enumerable: true,
110
- })
111
- return true
112
- }
113
- return Reflect.set(obj, prop, value, receiver)
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:`, { nameA: a?.constructor?.name, nameB: b?.constructor?.name })
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:`, { lenA: keysA.length, lenB: keysB.length, keysA, keysB, a, b })
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.prototype.hasOwnProperty.call(b, key)) {
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)}:`, { valA: a[key], valB: b[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
+ })