mutts 1.0.4 → 1.0.6

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 (75) hide show
  1. package/README.md +2 -1
  2. package/dist/chunks/{_tslib-Mzh1rNsX.esm.js → _tslib-MCKDzsSq.esm.js} +2 -2
  3. package/dist/chunks/_tslib-MCKDzsSq.esm.js.map +1 -0
  4. package/dist/chunks/decorator-BGILvPtN.esm.js +627 -0
  5. package/dist/chunks/decorator-BGILvPtN.esm.js.map +1 -0
  6. package/dist/chunks/decorator-BQ2eBTCj.js +651 -0
  7. package/dist/chunks/decorator-BQ2eBTCj.js.map +1 -0
  8. package/dist/chunks/{index-GRBSx0mB.js → index-CDCOjzTy.js} +543 -495
  9. package/dist/chunks/index-CDCOjzTy.js.map +1 -0
  10. package/dist/chunks/{index-79Kk8D6e.esm.js → index-DiP0RXoZ.esm.js} +452 -404
  11. package/dist/chunks/index-DiP0RXoZ.esm.js.map +1 -0
  12. package/dist/decorator.d.ts +3 -3
  13. package/dist/decorator.esm.js +1 -1
  14. package/dist/decorator.js +1 -1
  15. package/dist/destroyable.esm.js +4 -4
  16. package/dist/destroyable.esm.js.map +1 -1
  17. package/dist/destroyable.js +4 -4
  18. package/dist/destroyable.js.map +1 -1
  19. package/dist/devtools/panel.js.map +1 -1
  20. package/dist/eventful.esm.js +1 -1
  21. package/dist/index.esm.js +48 -3
  22. package/dist/index.esm.js.map +1 -1
  23. package/dist/index.js +50 -4
  24. package/dist/index.js.map +1 -1
  25. package/dist/mutts.umd.js +1 -1
  26. package/dist/mutts.umd.js.map +1 -1
  27. package/dist/mutts.umd.min.js +1 -1
  28. package/dist/mutts.umd.min.js.map +1 -1
  29. package/dist/reactive.d.ts +54 -1
  30. package/dist/reactive.esm.js +3 -3
  31. package/dist/reactive.js +6 -4
  32. package/dist/reactive.js.map +1 -1
  33. package/dist/std-decorators.d.ts +1 -1
  34. package/dist/std-decorators.esm.js +10 -10
  35. package/dist/std-decorators.esm.js.map +1 -1
  36. package/dist/std-decorators.js +10 -10
  37. package/dist/std-decorators.js.map +1 -1
  38. package/docs/ai/manual.md +14 -95
  39. package/docs/reactive/advanced.md +6 -107
  40. package/docs/reactive/core.md +16 -16
  41. package/docs/reactive/debugging.md +158 -0
  42. package/docs/reactive.md +8 -0
  43. package/package.json +16 -66
  44. package/src/decorator.ts +11 -9
  45. package/src/destroyable.ts +5 -5
  46. package/src/index.ts +46 -0
  47. package/src/reactive/array.ts +3 -5
  48. package/src/reactive/change.ts +7 -3
  49. package/src/reactive/debug.ts +1 -1
  50. package/src/reactive/deep-touch.ts +1 -1
  51. package/src/reactive/deep-watch.ts +1 -1
  52. package/src/reactive/effect-context.ts +2 -2
  53. package/src/reactive/effects.ts +114 -17
  54. package/src/reactive/index.ts +3 -2
  55. package/src/reactive/interface.ts +10 -9
  56. package/src/reactive/map.ts +6 -6
  57. package/src/reactive/mapped.ts +2 -3
  58. package/src/reactive/memoize.ts +77 -31
  59. package/src/reactive/project.ts +103 -6
  60. package/src/reactive/proxy.ts +4 -4
  61. package/src/reactive/registry.ts +67 -0
  62. package/src/reactive/set.ts +6 -6
  63. package/src/reactive/tracking.ts +12 -41
  64. package/src/reactive/types.ts +59 -0
  65. package/src/reactive/zone.ts +1 -1
  66. package/src/std-decorators.ts +10 -10
  67. package/src/utils.ts +141 -0
  68. package/dist/chunks/_tslib-Mzh1rNsX.esm.js.map +0 -1
  69. package/dist/chunks/decorator-DLvrD0UF.js +0 -265
  70. package/dist/chunks/decorator-DLvrD0UF.js.map +0 -1
  71. package/dist/chunks/decorator-DqiszP7i.esm.js +0 -253
  72. package/dist/chunks/decorator-DqiszP7i.esm.js.map +0 -1
  73. package/dist/chunks/index-79Kk8D6e.esm.js.map +0 -1
  74. package/dist/chunks/index-GRBSx0mB.js.map +0 -1
  75. /package/{src/reactive/project.project.md → docs/reactive/project.md} +0 -0
@@ -1,9 +1,29 @@
1
1
  import { ReflectGet, ReflectSet } from '../utils'
2
+ import { setEffectName } from './debug'
3
+ import { getActiveEffect } from './effect-context'
2
4
  import { effect, untracked } from './effects'
3
5
  import { cleanedBy, cleanup } from './interface'
4
6
  import { reactive } from './proxy'
5
7
  import { Register } from './register'
6
- import { type ScopedCallback } from './types'
8
+ import { type ProjectionContext, projectionInfo, type ScopedCallback } from './types'
9
+
10
+ /**
11
+ * Maps projection effects (item effects) to their projection context
12
+ */
13
+ export const effectProjectionMetadata = new WeakMap<ScopedCallback, ProjectionContext>()
14
+
15
+ /**
16
+ * Returns the projection context of the currently running effect, if any.
17
+ */
18
+ export function getActiveProjection(): ProjectionContext | undefined {
19
+ const active = getActiveEffect()
20
+ return active ? effectProjectionMetadata.get(active) : undefined
21
+ }
22
+
23
+ /* TODO
24
+ It seems to work and I feel like it's correct but I couldn't validate theoretically that `ascend`
25
+ is the correct way to deal with nested effects.
26
+ */
7
27
 
8
28
  type ProjectOldValue<Target> = Target extends readonly (infer Item)[]
9
29
  ? Item
@@ -50,8 +70,17 @@ function defineAccessValue<Access extends { get(): unknown; set(value: unknown):
50
70
  function makeCleanup<Result extends object>(
51
71
  target: Result,
52
72
  effectMap: Map<unknown, ScopedCallback>,
53
- onDispose: () => void
73
+ onDispose: () => void,
74
+ metadata?: any
54
75
  ): ProjectResult<Result> {
76
+ if (metadata) {
77
+ Object.defineProperty(target, projectionInfo, {
78
+ value: metadata,
79
+ writable: false,
80
+ enumerable: false,
81
+ configurable: true,
82
+ })
83
+ }
55
84
  return cleanedBy(target, () => {
56
85
  onDispose()
57
86
  for (const stop of effectMap.values()) stop?.()
@@ -80,6 +109,9 @@ function projectArray<SourceValue, ResultValue>(
80
109
  }
81
110
  }
82
111
 
112
+ const parent = getActiveProjection()
113
+ const depth = parent ? parent.depth + 1 : 0
114
+
83
115
  const cleanupLength = effect(function projectArrayLengthEffect({ ascend }) {
84
116
  const length = observedSource.length
85
117
  normalizeTargetLength(length)
@@ -102,13 +134,27 @@ function projectArray<SourceValue, ResultValue>(
102
134
  const produced = apply(accessBase, target)
103
135
  target[index] = produced
104
136
  })
137
+ setEffectName(stop, `project[${depth}]:${index}`)
138
+ effectProjectionMetadata.set(stop, {
139
+ source: observedSource,
140
+ key: index,
141
+ target,
142
+ depth,
143
+ parent,
144
+ })
105
145
  indexEffects.set(i, stop)
106
146
  })
107
147
  }
108
148
  for (const index of existing) if (index >= length) disposeIndex(index)
109
149
  })
110
150
 
111
- return makeCleanup(target, indexEffects, () => cleanupLength())
151
+ return makeCleanup(target, indexEffects, () => cleanupLength(), {
152
+ source: observedSource,
153
+ target,
154
+ apply,
155
+ depth,
156
+ parent,
157
+ } as ProjectionContext)
112
158
  }
113
159
 
114
160
  function projectRegister<Key extends PropertyKey, SourceValue, ResultValue>(
@@ -135,6 +181,9 @@ function projectRegister<Key extends PropertyKey, SourceValue, ResultValue>(
135
181
  }
136
182
  }
137
183
 
184
+ const parent = getActiveProjection()
185
+ const depth = parent ? parent.depth + 1 : 0
186
+
138
187
  const cleanupKeys = effect(function projectRegisterEffect({ ascend }) {
139
188
  const keys = new Set<Key>()
140
189
  for (const key of observedSource.mapKeys()) keys.add(key)
@@ -158,6 +207,14 @@ function projectRegister<Key extends PropertyKey, SourceValue, ResultValue>(
158
207
  const produced = apply(accessBase, target)
159
208
  target.set(key, produced)
160
209
  })
210
+ setEffectName(stop, `project[${depth}]:${String(key)}`)
211
+ effectProjectionMetadata.set(stop, {
212
+ source: observedSource,
213
+ key,
214
+ target,
215
+ depth,
216
+ parent,
217
+ })
161
218
  keyEffects.set(key, stop)
162
219
  })
163
220
  }
@@ -165,7 +222,13 @@ function projectRegister<Key extends PropertyKey, SourceValue, ResultValue>(
165
222
  for (const key of Array.from(keyEffects.keys())) if (!keys.has(key)) disposeKey(key)
166
223
  })
167
224
 
168
- return makeCleanup(target, keyEffects, () => cleanupKeys())
225
+ return makeCleanup(target, keyEffects, () => cleanupKeys(), {
226
+ source: observedSource,
227
+ target,
228
+ apply,
229
+ depth,
230
+ parent,
231
+ } as ProjectionContext)
169
232
  }
170
233
 
171
234
  function projectRecord<Source extends Record<PropertyKey, any>, ResultValue>(
@@ -191,6 +254,9 @@ function projectRecord<Source extends Record<PropertyKey, any>, ResultValue>(
191
254
  }
192
255
  }
193
256
 
257
+ const parent = getActiveProjection()
258
+ const depth = parent ? parent.depth + 1 : 0
259
+
194
260
  const cleanupKeys = effect(function projectRecordEffect({ ascend }) {
195
261
  const keys = new Set<PropertyKey>()
196
262
  for (const key in observedSource) keys.add(key)
@@ -222,6 +288,14 @@ function projectRecord<Source extends Record<PropertyKey, any>, ResultValue>(
222
288
  const produced = apply(accessBase, target)
223
289
  ;(target as any)[sourceKey] = produced
224
290
  })
291
+ setEffectName(stop, `project[${depth}]:${String(key)}`)
292
+ effectProjectionMetadata.set(stop, {
293
+ source: observedSource,
294
+ key,
295
+ target,
296
+ depth,
297
+ parent,
298
+ })
225
299
  keyEffects.set(key, stop)
226
300
  })
227
301
  }
@@ -229,7 +303,13 @@ function projectRecord<Source extends Record<PropertyKey, any>, ResultValue>(
229
303
  for (const key of Array.from(keyEffects.keys())) if (!keys.has(key)) disposeKey(key)
230
304
  })
231
305
 
232
- return makeCleanup(target, keyEffects, () => cleanupKeys())
306
+ return makeCleanup(target, keyEffects, () => cleanupKeys(), {
307
+ source: observedSource,
308
+ target,
309
+ apply,
310
+ depth,
311
+ parent,
312
+ } as ProjectionContext)
233
313
  }
234
314
 
235
315
  function projectMap<Key, Value, ResultValue>(
@@ -250,6 +330,9 @@ function projectMap<Key, Value, ResultValue>(
250
330
  }
251
331
  }
252
332
 
333
+ const parent = getActiveProjection()
334
+ const depth = parent ? parent.depth + 1 : 0
335
+
253
336
  const cleanupKeys = effect(function projectMapEffect({ ascend }) {
254
337
  const keys = new Set<Key>()
255
338
  for (const key of observedSource.keys()) keys.add(key)
@@ -273,6 +356,14 @@ function projectMap<Key, Value, ResultValue>(
273
356
  const produced = apply(accessBase, target)
274
357
  target.set(key, produced)
275
358
  })
359
+ setEffectName(stop, `project[${depth}]:${String(key)}`)
360
+ effectProjectionMetadata.set(stop, {
361
+ source: observedSource,
362
+ key,
363
+ target,
364
+ depth,
365
+ parent,
366
+ })
276
367
  keyEffects.set(key, stop)
277
368
  })
278
369
  }
@@ -280,7 +371,13 @@ function projectMap<Key, Value, ResultValue>(
280
371
  for (const key of Array.from(keyEffects.keys())) if (!keys.has(key)) disposeKey(key)
281
372
  })
282
373
 
283
- return makeCleanup(target, keyEffects, () => cleanupKeys())
374
+ return makeCleanup(target, keyEffects, () => cleanupKeys(), {
375
+ source: observedSource,
376
+ target,
377
+ apply,
378
+ depth,
379
+ parent,
380
+ } as ProjectionContext)
284
381
  }
285
382
 
286
383
  type ProjectOverload = {
@@ -127,12 +127,12 @@ const reactiveHandlers = {
127
127
  const receiverDesc = Object.getOwnPropertyDescriptor(unwrappedReceiver, prop)
128
128
  const targetDesc = Object.getOwnPropertyDescriptor(unwrappedObj, prop)
129
129
  const desc = receiverDesc || targetDesc
130
- // If it's a getter-only accessor (has getter but no setter), read without tracking
131
- // to avoid breaking memoization invalidation when the getter calls memoized functions
130
+ // We *need* to use `receiver` and not `unwrappedObj` here, otherwise we break
131
+ // the dependency tracking for memoized getters
132
132
  if (desc?.get && !desc?.set) {
133
- oldVal = withEffect(undefined, () => Reflect.get(unwrappedObj, prop, unwrappedReceiver))
133
+ oldVal = withEffect(undefined, () => Reflect.get(unwrappedObj, prop, receiver))
134
134
  } else {
135
- oldVal = Reflect.get(unwrappedObj, prop, unwrappedReceiver)
135
+ oldVal = withEffect(undefined, () => Reflect.get(unwrappedObj, prop, receiver))
136
136
  }
137
137
  }
138
138
  if (objectsWithDeepWatchers.has(obj)) {
@@ -0,0 +1,67 @@
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
+ }
60
+
61
+ // Flag to disable dependency tracking for the current active effect (not globally)
62
+ export const trackingDisabledEffects = new WeakSet<ScopedCallback>()
63
+ export let globalTrackingDisabled = false
64
+
65
+ export function setGlobalTrackingDisabled(value: boolean): void {
66
+ globalTrackingDisabled = value
67
+ }
@@ -11,14 +11,14 @@ const native = Symbol('native')
11
11
  * Only tracks individual value operations, no size tracking (WeakSet limitation)
12
12
  */
13
13
  export class ReactiveWeakSet<T extends object> {
14
- declare readonly [native]: WeakSet<T>
15
- declare readonly content: symbol
14
+ readonly [native]!: WeakSet<T>
15
+ readonly content!: symbol
16
16
 
17
17
  constructor(original: WeakSet<T>) {
18
18
  Object.defineProperties(this, {
19
19
  [native]: { value: original },
20
20
  [prototypeForwarding]: { value: original },
21
- content: { value: Symbol('content') },
21
+ content: { value: Symbol('WeakSetContent') },
22
22
  [Symbol.toStringTag]: { value: 'ReactiveWeakSet' },
23
23
  })
24
24
  }
@@ -52,13 +52,13 @@ export class ReactiveWeakSet<T extends object> {
52
52
  * Tracks size changes, individual value operations, and collection-wide operations
53
53
  */
54
54
  export class ReactiveSet<T> {
55
- declare readonly [native]: Set<T>
56
- declare readonly content: symbol
55
+ readonly [native]!: Set<T>
56
+ readonly content!: symbol
57
57
  constructor(original: Set<T>) {
58
58
  Object.defineProperties(this, {
59
59
  [native]: { value: original },
60
60
  [prototypeForwarding]: { value: original },
61
- content: { value: Symbol('content') },
61
+ content: { value: Symbol('SetContent') },
62
62
  [Symbol.toStringTag]: { value: 'ReactiveSet' },
63
63
  })
64
64
  }
@@ -1,45 +1,14 @@
1
1
  import { getActiveEffect } from './effect-context'
2
+ import {
3
+ effectToReactiveObjects,
4
+ getRoot,
5
+ globalTrackingDisabled,
6
+ setGlobalTrackingDisabled,
7
+ trackingDisabledEffects,
8
+ watchers,
9
+ } from './registry'
2
10
  import { unwrap } from './proxy-state'
3
- import { allProps, rootFunction, type ScopedCallback } from './types'
4
-
5
- // Track which effects are watching which reactive objects for cleanup
6
- export const effectToReactiveObjects = new WeakMap<ScopedCallback, Set<object>>()
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
11
+ import { allProps, type ScopedCallback } from './types'
43
12
 
44
13
  export function getTrackingDisabled(): boolean {
45
14
  const active = getActiveEffect()
@@ -50,7 +19,7 @@ export function getTrackingDisabled(): boolean {
50
19
  export function setTrackingDisabled(value: boolean): void {
51
20
  const active = getActiveEffect()
52
21
  if (!active) {
53
- globalTrackingDisabled = value
22
+ setGlobalTrackingDisabled(value)
54
23
  return
55
24
  }
56
25
  const root = getRoot(active)
@@ -58,6 +27,8 @@ export function setTrackingDisabled(value: boolean): void {
58
27
  else trackingDisabledEffects.delete(root)
59
28
  }
60
29
 
30
+
31
+
61
32
  /**
62
33
  * Marks a property as a dependency of the current effect
63
34
  * @param obj - The object containing the property
@@ -141,6 +141,32 @@ export const prototypeForwarding: unique symbol = Symbol('prototype-forwarding')
141
141
  */
142
142
  export const allProps = Symbol('all-props')
143
143
 
144
+ /**
145
+ * Symbol for accessing projection information on reactive objects
146
+ */
147
+ export const projectionInfo = Symbol('projection-info')
148
+
149
+ /**
150
+ * Symbol to check if an effect is stopped
151
+ */
152
+ export const stopped = Symbol('stopped')
153
+
154
+ /**
155
+ * Symbol to access effect cleanup function
156
+ */
157
+ export const cleanup = Symbol('cleanup')
158
+
159
+ /**
160
+ * Context for a running projection item effect
161
+ */
162
+ export interface ProjectionContext {
163
+ source: any
164
+ key?: any
165
+ target: any
166
+ depth: number
167
+ parent?: ProjectionContext
168
+ }
169
+
144
170
  // Symbol to mark functions with their root function
145
171
  const rootFunction = Symbol('root-function')
146
172
 
@@ -250,12 +276,40 @@ export const options = {
250
276
  * @default 100
251
277
  */
252
278
  maxEffectChain: 100,
279
+ /**
280
+ * Maximum number of times an effect can be triggered by the same cause in a single batch
281
+ * Used to detect aggressive re-computation or infinite loops
282
+ * @default 10
283
+ */
284
+ maxTriggerPerBatch: 10,
253
285
  /**
254
286
  * Debug purpose: maximum effect reaction (like call stack max depth)
255
287
  * Used to prevent infinite loops
256
288
  * @default 'throw'
257
289
  */
258
290
  maxEffectReaction: 'throw' as 'throw' | 'debug' | 'warn',
291
+ /**
292
+ * Callback called when a memoization discrepancy is detected (debug only)
293
+ * When defined, memoized functions will run a second time (untracked) to verify consistency.
294
+ * If the untracked run returns a different value than the cached one, this callback is triggered.
295
+ *
296
+ * This is the primary tool for detecting missing reactive dependencies in computed values.
297
+ *
298
+ * @param cached - The value currently in the memoization cache
299
+ * @param fresh - The value obtained by re-running the function untracked
300
+ * @param fn - The memoized function itself
301
+ * @param args - Arguments passed to the function
302
+ *
303
+ * @example
304
+ * ```typescript
305
+ * reactiveOptions.onMemoizationDiscrepancy = (cached, fresh, fn, args) => {
306
+ * throw new Error(`Memoization discrepancy in ${fn.name}!`);
307
+ * };
308
+ * ```
309
+ */
310
+ onMemoizationDiscrepancy: undefined as
311
+ | ((cached: any, fresh: any, fn: Function, args: any[], cause: "calculation" | "comparison") => void)
312
+ | undefined,
259
313
  /**
260
314
  * How to handle cycles detected in effect batches
261
315
  * - 'throw': Throw an error with cycle information (default, recommended for development)
@@ -265,6 +319,11 @@ export const options = {
265
319
  * @default 'throw'
266
320
  */
267
321
  cycleHandling: 'throw' as 'throw' | 'warn' | 'break' | 'strict',
322
+ /**
323
+ * Internal flag used by memoization discrepancy detector to avoid counting calls in tests
324
+ * @warning Do not modify this flag manually, this flag is given by the engine
325
+ */
326
+ isVerificationRun: false,
268
327
  /**
269
328
  * Maximum depth for deep watching traversal
270
329
  * Used to prevent infinite recursion in circular references
@@ -15,7 +15,7 @@
15
15
  */
16
16
 
17
17
  import { captureEffectStack, withEffectStack } from './effect-context'
18
- import { options, ScopedCallback } from './types'
18
+ import { options, type ScopedCallback } from './types'
19
19
 
20
20
  let zoneHooked = false
21
21
 
@@ -1,4 +1,4 @@
1
- import { decorator, GenericClassDecorator } from './decorator'
1
+ import { decorator, type GenericClassDecorator } from './decorator'
2
2
 
3
3
  // In order to avoid async re-entrance, we could use zone.js or something like that.
4
4
  const syncCalculating: { object: object; prop: PropertyKey }[] = []
@@ -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