mutts 1.0.8 → 1.0.10

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 (195) hide show
  1. package/README.md +77 -35
  2. package/dist/browser.cjs +2144 -5355
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.ts +2 -1655
  5. package/dist/browser.dev.cjs +110 -0
  6. package/dist/browser.dev.cjs.map +1 -0
  7. package/dist/browser.dev.d.ts +2 -0
  8. package/dist/browser.dev.esm.js +5 -0
  9. package/dist/browser.dev.esm.js.map +1 -0
  10. package/dist/browser.esm.js +20 -107
  11. package/dist/browser.esm.js.map +1 -1
  12. package/dist/chunks/{async-browser-CA0jPWIi.cjs → async-browser-BU_IfxYD.cjs} +19 -107
  13. package/dist/chunks/async-browser-BU_IfxYD.cjs.map +1 -0
  14. package/dist/chunks/{async-core-UqHzvJ-S.cjs → async-core-CRLKP3l-.cjs} +8 -4
  15. package/dist/chunks/async-core-CRLKP3l-.cjs.map +1 -0
  16. package/dist/chunks/{async-node-BYHuGTni.cjs → async-node-3PrbVAbB.cjs} +7 -12
  17. package/dist/chunks/async-node-3PrbVAbB.cjs.map +1 -0
  18. package/dist/chunks/index-CaaQQlPJ.esm.js +3174 -0
  19. package/dist/chunks/index-CaaQQlPJ.esm.js.map +1 -0
  20. package/dist/chunks/node-nKJBk8iJ.esm.js +96 -0
  21. package/dist/chunks/node-nKJBk8iJ.esm.js.map +1 -0
  22. package/dist/chunks/proxy-Dtg-bJ3T.cjs +3763 -0
  23. package/dist/chunks/proxy-Dtg-bJ3T.cjs.map +1 -0
  24. package/dist/chunks/proxy-r7lARftl.esm.js +3701 -0
  25. package/dist/chunks/proxy-r7lARftl.esm.js.map +1 -0
  26. package/dist/debug.cjs +980 -0
  27. package/dist/debug.cjs.map +1 -0
  28. package/dist/debug.d.ts +208 -0
  29. package/dist/debug.esm.js +950 -0
  30. package/dist/debug.esm.js.map +1 -0
  31. package/dist/devtools/manifest.json +1 -1
  32. package/dist/devtools/panel.html +1 -1
  33. package/dist/devtools/panel.js +107 -94
  34. package/dist/devtools/panel.js.map +1 -1
  35. package/dist/index.d.ts +1447 -13
  36. package/dist/mutts.umd.js +7167 -1
  37. package/dist/mutts.umd.js.map +1 -1
  38. package/dist/mutts.umd.min.js +1 -1
  39. package/dist/mutts.umd.min.js.map +1 -1
  40. package/dist/node.cjs +70 -66
  41. package/dist/node.cjs.map +1 -1
  42. package/dist/node.d.ts +2 -1
  43. package/dist/node.dev.cjs +110 -0
  44. package/dist/node.dev.cjs.map +1 -0
  45. package/dist/node.dev.d.ts +2 -0
  46. package/dist/node.dev.esm.js +6 -0
  47. package/dist/node.dev.esm.js.map +1 -0
  48. package/dist/node.esm.js +4 -103
  49. package/dist/node.esm.js.map +1 -1
  50. package/dist/{reactive/types.d.ts → types-W5vD6m2n.d.ts} +186 -93
  51. package/docs/ai/api-reference.md +12 -19
  52. package/docs/ai/manual.md +521 -17
  53. package/docs/events.md +57 -2
  54. package/docs/flavored.md +210 -0
  55. package/docs/reactive/advanced.md +314 -5
  56. package/docs/reactive/attend.md +142 -0
  57. package/docs/reactive/collections.md +76 -39
  58. package/docs/reactive/core.md +100 -14
  59. package/docs/reactive/debugging.md +123 -18
  60. package/docs/reactive/error-handling.md +121 -0
  61. package/docs/reactive/resource.md +125 -0
  62. package/docs/reactive/scan.md +247 -1
  63. package/docs/reactive.md +4 -1
  64. package/docs/std-decorators.md +102 -51
  65. package/docs/utils.md +70 -0
  66. package/docs/zone.md +3 -7
  67. package/package.json +64 -30
  68. package/dist/async/browser.d.ts +0 -2
  69. package/dist/async/browser.d.ts.map +0 -1
  70. package/dist/async/index.d.ts +0 -18
  71. package/dist/async/index.d.ts.map +0 -1
  72. package/dist/async/node.d.ts +0 -2
  73. package/dist/async/node.d.ts.map +0 -1
  74. package/dist/chunks/async-browser-CA0jPWIi.cjs.map +0 -1
  75. package/dist/chunks/async-core-UqHzvJ-S.cjs.map +0 -1
  76. package/dist/chunks/async-node-BYHuGTni.cjs.map +0 -1
  77. package/dist/chunks/index-DhaOVusv.esm.js +0 -6411
  78. package/dist/chunks/index-DhaOVusv.esm.js.map +0 -1
  79. package/dist/decorator.d.ts +0 -106
  80. package/dist/decorator.d.ts.map +0 -1
  81. package/dist/destroyable.d.ts +0 -87
  82. package/dist/destroyable.d.ts.map +0 -1
  83. package/dist/entry-browser.d.ts +0 -3
  84. package/dist/entry-browser.d.ts.map +0 -1
  85. package/dist/entry-node.d.ts +0 -3
  86. package/dist/entry-node.d.ts.map +0 -1
  87. package/dist/eventful.d.ts +0 -18
  88. package/dist/eventful.d.ts.map +0 -1
  89. package/dist/index.d.ts.map +0 -1
  90. package/dist/indexable.d.ts +0 -243
  91. package/dist/indexable.d.ts.map +0 -1
  92. package/dist/introspection.d.ts +0 -27
  93. package/dist/introspection.d.ts.map +0 -1
  94. package/dist/iterableWeak.d.ts +0 -53
  95. package/dist/iterableWeak.d.ts.map +0 -1
  96. package/dist/mixins.d.ts +0 -25
  97. package/dist/mixins.d.ts.map +0 -1
  98. package/dist/promiseChain.d.ts +0 -20
  99. package/dist/promiseChain.d.ts.map +0 -1
  100. package/dist/reactive/array.d.ts +0 -49
  101. package/dist/reactive/array.d.ts.map +0 -1
  102. package/dist/reactive/buffer.d.ts +0 -44
  103. package/dist/reactive/buffer.d.ts.map +0 -1
  104. package/dist/reactive/change.d.ts +0 -29
  105. package/dist/reactive/change.d.ts.map +0 -1
  106. package/dist/reactive/debug.d.ts +0 -111
  107. package/dist/reactive/debug.d.ts.map +0 -1
  108. package/dist/reactive/deep-touch.d.ts +0 -28
  109. package/dist/reactive/deep-touch.d.ts.map +0 -1
  110. package/dist/reactive/deep-watch-state.d.ts +0 -25
  111. package/dist/reactive/deep-watch-state.d.ts.map +0 -1
  112. package/dist/reactive/deep-watch.d.ts +0 -19
  113. package/dist/reactive/deep-watch.d.ts.map +0 -1
  114. package/dist/reactive/effect-context.d.ts +0 -7
  115. package/dist/reactive/effect-context.d.ts.map +0 -1
  116. package/dist/reactive/effects.d.ts +0 -151
  117. package/dist/reactive/effects.d.ts.map +0 -1
  118. package/dist/reactive/index.d.ts +0 -20
  119. package/dist/reactive/index.d.ts.map +0 -1
  120. package/dist/reactive/interface.d.ts +0 -64
  121. package/dist/reactive/interface.d.ts.map +0 -1
  122. package/dist/reactive/map.d.ts +0 -30
  123. package/dist/reactive/map.d.ts.map +0 -1
  124. package/dist/reactive/memoize.d.ts +0 -5
  125. package/dist/reactive/memoize.d.ts.map +0 -1
  126. package/dist/reactive/non-reactive-state.d.ts +0 -9
  127. package/dist/reactive/non-reactive-state.d.ts.map +0 -1
  128. package/dist/reactive/non-reactive.d.ts +0 -11
  129. package/dist/reactive/non-reactive.d.ts.map +0 -1
  130. package/dist/reactive/project.d.ts +0 -41
  131. package/dist/reactive/project.d.ts.map +0 -1
  132. package/dist/reactive/proxy-state.d.ts +0 -8
  133. package/dist/reactive/proxy-state.d.ts.map +0 -1
  134. package/dist/reactive/proxy.d.ts +0 -23
  135. package/dist/reactive/proxy.d.ts.map +0 -1
  136. package/dist/reactive/record.d.ts +0 -116
  137. package/dist/reactive/record.d.ts.map +0 -1
  138. package/dist/reactive/register.d.ts +0 -64
  139. package/dist/reactive/register.d.ts.map +0 -1
  140. package/dist/reactive/registry.d.ts +0 -20
  141. package/dist/reactive/registry.d.ts.map +0 -1
  142. package/dist/reactive/set.d.ts +0 -28
  143. package/dist/reactive/set.d.ts.map +0 -1
  144. package/dist/reactive/tracking.d.ts +0 -7
  145. package/dist/reactive/tracking.d.ts.map +0 -1
  146. package/dist/reactive/types.d.ts.map +0 -1
  147. package/dist/std-decorators.d.ts +0 -50
  148. package/dist/std-decorators.d.ts.map +0 -1
  149. package/dist/utils.d.ts +0 -49
  150. package/dist/utils.d.ts.map +0 -1
  151. package/dist/zone.d.ts +0 -40
  152. package/dist/zone.d.ts.map +0 -1
  153. package/docs/reactive/project.md +0 -93
  154. package/src/async/browser.ts +0 -319
  155. package/src/async/index.ts +0 -23
  156. package/src/async/node.ts +0 -104
  157. package/src/decorator.ts +0 -272
  158. package/src/destroyable.ts +0 -199
  159. package/src/entry-browser.ts +0 -5
  160. package/src/entry-node.ts +0 -5
  161. package/src/eventful.ts +0 -77
  162. package/src/index.d.ts +0 -12
  163. package/src/index.ts +0 -64
  164. package/src/indexable.ts +0 -526
  165. package/src/introspection.ts +0 -59
  166. package/src/iterableWeak.ts +0 -233
  167. package/src/mixins.ts +0 -123
  168. package/src/promiseChain.ts +0 -110
  169. package/src/reactive/array.ts +0 -507
  170. package/src/reactive/buffer.ts +0 -168
  171. package/src/reactive/change.ts +0 -138
  172. package/src/reactive/debug.ts +0 -517
  173. package/src/reactive/deep-touch.ts +0 -268
  174. package/src/reactive/deep-watch-state.ts +0 -82
  175. package/src/reactive/deep-watch.ts +0 -168
  176. package/src/reactive/effect-context.ts +0 -18
  177. package/src/reactive/effects.ts +0 -1382
  178. package/src/reactive/index.ts +0 -75
  179. package/src/reactive/interface.ts +0 -210
  180. package/src/reactive/map.ts +0 -158
  181. package/src/reactive/memoize.ts +0 -163
  182. package/src/reactive/non-reactive-state.ts +0 -49
  183. package/src/reactive/non-reactive.ts +0 -43
  184. package/src/reactive/project.ts +0 -453
  185. package/src/reactive/proxy-state.ts +0 -27
  186. package/src/reactive/proxy.ts +0 -264
  187. package/src/reactive/record.ts +0 -196
  188. package/src/reactive/register.ts +0 -419
  189. package/src/reactive/registry.ts +0 -59
  190. package/src/reactive/set.ts +0 -130
  191. package/src/reactive/tracking.ts +0 -44
  192. package/src/reactive/types.ts +0 -440
  193. package/src/std-decorators.ts +0 -217
  194. package/src/utils.ts +0 -301
  195. package/src/zone.ts +0 -138
@@ -0,0 +1,3763 @@
1
+ 'use strict';
2
+
3
+ var asyncCore = require('./async-core-CRLKP3l-.cjs');
4
+
5
+ /**
6
+ * Yields tuples containing elements from each input array, stopping at the longest array length
7
+ * @param args - Arrays to zip together
8
+ * @returns Generator yielding tuples containing elements from each input array
9
+ */
10
+ function* zip(...args) {
11
+ if (!args.length)
12
+ return [];
13
+ const maxLength = Math.max(...args.map((arr) => arr.length));
14
+ for (let i = 0; i < maxLength; i++) {
15
+ const tuple = args.map((arr) => arr[i]);
16
+ yield tuple;
17
+ }
18
+ }
19
+ /**
20
+ * Checks if two arrays are strictly equal (shallow comparison)
21
+ * @param a - First value
22
+ * @param b - Second value
23
+ * @returns True if arrays are equal or values are strictly equal
24
+ */
25
+ function arrayEquals(a, b) {
26
+ if (!Array.isArray(a) || !Array.isArray(b))
27
+ return false;
28
+ if (a === b)
29
+ return true;
30
+ if (a.length !== b.length)
31
+ return false;
32
+ for (let i = 0; i < a.length; i++) {
33
+ if (a[i] !== b[i])
34
+ return false;
35
+ }
36
+ return true;
37
+ }
38
+ const nativeConstructors = new Set([
39
+ Object,
40
+ Array,
41
+ Date,
42
+ Function,
43
+ Set,
44
+ Map,
45
+ WeakMap,
46
+ WeakSet,
47
+ Promise,
48
+ Error,
49
+ TypeError,
50
+ ReferenceError,
51
+ SyntaxError,
52
+ RangeError,
53
+ URIError,
54
+ EvalError,
55
+ Reflect,
56
+ Proxy,
57
+ RegExp,
58
+ String,
59
+ Number,
60
+ Boolean,
61
+ ]);
62
+ /**
63
+ * Checks if a function is a constructor (class or constructor function)
64
+ * @param fn - The function to check
65
+ * @returns True if the function is a constructor
66
+ */
67
+ function isConstructor(fn) {
68
+ return (fn &&
69
+ typeof fn === 'function' &&
70
+ (nativeConstructors.has(fn) || fn.toString?.().startsWith('class ')));
71
+ }
72
+ /**
73
+ * Checks if a value is an object
74
+ * @param value - The value to check
75
+ * @returns True if the value is an object
76
+ */
77
+ function isObject(value) {
78
+ return (typeof value === 'object' &&
79
+ value !== null &&
80
+ !Array.isArray(value) &&
81
+ !(value instanceof Date ||
82
+ value instanceof RegExp ||
83
+ value instanceof Error ||
84
+ value instanceof Set ||
85
+ value instanceof Map ||
86
+ value instanceof WeakSet ||
87
+ value instanceof WeakMap ||
88
+ value instanceof Promise ||
89
+ value instanceof Function));
90
+ }
91
+ const hasNode = typeof Node !== 'undefined';
92
+ const FoolProof = {
93
+ get(obj, prop, receiver) {
94
+ if (hasNode && obj instanceof Node)
95
+ return obj[prop];
96
+ return Reflect.get(obj, prop, receiver);
97
+ },
98
+ set(obj, prop, value, receiver) {
99
+ if (hasNode && obj instanceof Node) {
100
+ obj[prop] = value;
101
+ return true;
102
+ }
103
+ if (!(obj instanceof Object) && !Reflect.has(obj, prop)) {
104
+ Object.defineProperty(obj, prop, {
105
+ value,
106
+ configurable: true,
107
+ writable: true,
108
+ enumerable: true,
109
+ });
110
+ return true;
111
+ }
112
+ return Reflect.set(obj, prop, value, receiver);
113
+ },
114
+ };
115
+ function isOwnAccessor(obj, prop) {
116
+ const opd = Object.getOwnPropertyDescriptor(obj, prop);
117
+ return !!(opd?.get || opd?.set);
118
+ }
119
+ /**
120
+ * Deeply compares two values.
121
+ * For objects, compares prototypes with === and then own properties recursively.
122
+ * Uses a cache to handle circular references.
123
+ * @param a - First value
124
+ * @param b - Second value
125
+ * @param cache - Map for circular reference protection (internal use)
126
+ * @returns True if values are deeply equal
127
+ */
128
+ function deepCompare(a, b, cache = new Map()) {
129
+ if (a === b)
130
+ return true;
131
+ if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {
132
+ return a === b;
133
+ }
134
+ // Prototype check
135
+ const protoA = Object.getPrototypeOf(a);
136
+ const protoB = Object.getPrototypeOf(b);
137
+ if (protoA !== protoB) {
138
+ console.warn(`[deepCompare] prototype mismatch:`, {
139
+ nameA: a?.constructor?.name,
140
+ nameB: b?.constructor?.name,
141
+ });
142
+ return false;
143
+ }
144
+ // Circular reference protection
145
+ let compared = cache.get(a);
146
+ if (compared?.has(b))
147
+ return true;
148
+ if (!compared) {
149
+ compared = new Set();
150
+ cache.set(a, compared);
151
+ }
152
+ compared.add(b);
153
+ // Handle specific object types
154
+ if (Array.isArray(a)) {
155
+ if (!Array.isArray(b)) {
156
+ console.warn(`[deepCompare] B is not an array`);
157
+ return false;
158
+ }
159
+ if (a.length !== b.length) {
160
+ console.warn(`[deepCompare] array length mismatch:`, { lenA: a.length, lenB: b.length });
161
+ return false;
162
+ }
163
+ for (let i = 0; i < a.length; i++) {
164
+ if (!deepCompare(a[i], b[i], cache)) {
165
+ console.warn(`[deepCompare] array element mismatch at index ${i}`);
166
+ return false;
167
+ }
168
+ }
169
+ return true;
170
+ }
171
+ if (a instanceof Date) {
172
+ const match = b instanceof Date && a.getTime() === b.getTime();
173
+ if (!match)
174
+ console.warn(`[deepCompare] Date mismatch`);
175
+ return match;
176
+ }
177
+ if (a instanceof RegExp) {
178
+ const match = b instanceof RegExp && a.toString() === b.toString();
179
+ if (!match)
180
+ console.warn(`[deepCompare] RegExp mismatch`);
181
+ return match;
182
+ }
183
+ if (a instanceof Set) {
184
+ if (!(b instanceof Set) || a.size !== b.size) {
185
+ console.warn(`[deepCompare] Set size mismatch`);
186
+ return false;
187
+ }
188
+ for (const val of a) {
189
+ let found = false;
190
+ for (const bVal of b) {
191
+ if (deepCompare(val, bVal, cache)) {
192
+ found = true;
193
+ break;
194
+ }
195
+ }
196
+ if (!found) {
197
+ console.warn(`[deepCompare] missing Set element`);
198
+ return false;
199
+ }
200
+ }
201
+ return true;
202
+ }
203
+ if (a instanceof Map) {
204
+ if (!(b instanceof Map) || a.size !== b.size) {
205
+ console.warn(`[deepCompare] Map size mismatch`);
206
+ return false;
207
+ }
208
+ for (const [key, val] of a) {
209
+ if (!b.has(key)) {
210
+ let foundMatch = false;
211
+ for (const [bKey, bVal] of b) {
212
+ if (deepCompare(key, bKey, cache) && deepCompare(val, bVal, cache)) {
213
+ foundMatch = true;
214
+ break;
215
+ }
216
+ }
217
+ if (!foundMatch) {
218
+ console.warn(`[deepCompare] missing Map key`);
219
+ return false;
220
+ }
221
+ }
222
+ else {
223
+ if (!deepCompare(val, b.get(key), cache)) {
224
+ console.warn(`[deepCompare] Map value mismatch for key`);
225
+ return false;
226
+ }
227
+ }
228
+ }
229
+ return true;
230
+ }
231
+ // Compare own properties
232
+ const keysA = Object.keys(a);
233
+ const keysB = Object.keys(b);
234
+ if (keysA.length !== keysB.length) {
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
+ return false;
244
+ }
245
+ for (const key of keysA) {
246
+ if (!Object.hasOwn(b, key)) {
247
+ console.warn(`[deepCompare] missing key ${String(key)} in B`);
248
+ return false;
249
+ }
250
+ if (!deepCompare(a[key], b[key], cache)) {
251
+ console.warn(`[deepCompare] value mismatch for key ${String(key)}:`, {
252
+ valA: a[key],
253
+ valB: b[key],
254
+ });
255
+ return false;
256
+ }
257
+ }
258
+ return true;
259
+ }
260
+ // Internal use: Used for reactive sets/maps to differentiate between different reactive containers: `x.get('aKey')` vs. `x['aKey']`
261
+ const contentRefs = new WeakMap();
262
+ function contentRef(container) {
263
+ if (!contentRefs.has(container))
264
+ contentRefs.set(container, Object.seal(Object.create(null, {
265
+ contentOf: { value: container, writable: false, configurable: false },
266
+ })));
267
+ return contentRefs.get(container);
268
+ }
269
+ /**
270
+ * Tags an object with a name
271
+ * @param name - The name to tag the object with
272
+ * @param obj - The object to tag
273
+ * @returns The object with the tag
274
+ */
275
+ function tag(name, obj) {
276
+ Object.defineProperties(obj, {
277
+ [Symbol.toStringTag]: {
278
+ value: name,
279
+ writable: false,
280
+ configurable: true,
281
+ },
282
+ toString: {
283
+ value: () => name,
284
+ writable: false,
285
+ configurable: true,
286
+ },
287
+ });
288
+ return obj;
289
+ }
290
+ /**
291
+ * Renames a function with a new name
292
+ * @param name - The new name for the function
293
+ * @param fn - The function to rename
294
+ * @returns The function with the new name
295
+ */
296
+ function named(name, fn) {
297
+ Object.defineProperty(fn, 'name', {
298
+ value: fn.name ? `${fn.name}::${name}` : name,
299
+ writable: false,
300
+ configurable: true,
301
+ });
302
+ return fn;
303
+ }
304
+
305
+ // biome-ignore-all lint/suspicious/noConfusingVoidType: We *love* voids
306
+ // Standardized decorator system that works with both Legacy and Modern decorators
307
+ /**
308
+ * Error thrown when decorator operations fail
309
+ */
310
+ class DecoratorError extends Error {
311
+ constructor(message) {
312
+ super(message);
313
+ this.name = 'DecoratorException';
314
+ }
315
+ }
316
+ /**
317
+ * Creates a decorator that works with Legacy decorator proposals
318
+ * @param description - The decorator description object
319
+ * @returns A decorator function compatible with Legacy decorators
320
+ */
321
+ function legacyDecorator(description) {
322
+ return function (target, propertyKey, descriptor, ...args) {
323
+ if (propertyKey === undefined) {
324
+ if (isConstructor(target)) {
325
+ if (!('class' in description))
326
+ throw new Error('Decorator cannot be applied to a class');
327
+ return description.class(target);
328
+ }
329
+ }
330
+ else if (typeof target === 'object' && ['string', 'symbol'].includes(typeof propertyKey)) {
331
+ if (!descriptor)
332
+ throw new Error('Decorator cannot be applied to a field');
333
+ else if (typeof descriptor === 'object' && 'configurable' in descriptor) {
334
+ if ('get' in descriptor || 'set' in descriptor) {
335
+ if (!('getter' in description || 'setter' in description))
336
+ throw new Error('Decorator cannot be applied to a getter or setter');
337
+ if ('getter' in description) {
338
+ const newGetter = description.getter(descriptor.get, target, propertyKey);
339
+ if (newGetter)
340
+ descriptor.get = newGetter;
341
+ }
342
+ if ('setter' in description) {
343
+ const newSetter = description.setter(descriptor.set, target, propertyKey);
344
+ if (newSetter)
345
+ descriptor.set = newSetter;
346
+ }
347
+ return descriptor;
348
+ }
349
+ else if (typeof descriptor.value === 'function') {
350
+ if (!('method' in description))
351
+ throw new Error('Decorator cannot be applied to a method');
352
+ const newMethod = description.method(descriptor.value, target, propertyKey);
353
+ if (newMethod)
354
+ descriptor.value = newMethod;
355
+ return descriptor;
356
+ }
357
+ }
358
+ }
359
+ if (!('default' in description))
360
+ throw new Error('Decorator do not have a default implementation');
361
+ return description.default.call(this, target, propertyKey, descriptor, ...args);
362
+ };
363
+ }
364
+ /**
365
+ * Creates a decorator that works with Modern decorator proposals
366
+ * @param description - The decorator description object
367
+ * @returns A decorator function compatible with Modern decorators
368
+ */
369
+ function modernDecorator(description) {
370
+ /*return function (target: any, context?: DecoratorContext, ...args: any[]) {*/
371
+ return function (target, context, ...args) {
372
+ if (!context?.kind || typeof context.kind !== 'string') {
373
+ if (!('default' in description))
374
+ throw new Error('Decorator do not have a default implementation');
375
+ return description.default.call(this, target, context, ...args);
376
+ }
377
+ switch (context.kind) {
378
+ case 'class':
379
+ if (!('class' in description))
380
+ throw new Error('Decorator cannot be applied to a class');
381
+ return description.class(target);
382
+ case 'field':
383
+ throw new Error('Decorator cannot be applied to a field');
384
+ case 'getter':
385
+ if (!('getter' in description))
386
+ throw new Error('Decorator cannot be applied to a getter');
387
+ return description.getter(target, target, context.name);
388
+ case 'setter':
389
+ if (!('setter' in description))
390
+ throw new Error('Decorator cannot be applied to a setter');
391
+ return description.setter(target, target, context.name);
392
+ case 'method':
393
+ if (!('method' in description))
394
+ throw new Error('Decorator cannot be applied to a method');
395
+ return description.method(target, target, context.name);
396
+ case 'accessor': {
397
+ if (!('getter' in description || 'setter' in description))
398
+ throw new Error('Decorator cannot be applied to a getter or setter');
399
+ const rv = {};
400
+ if ('getter' in description) {
401
+ const newGetter = description.getter(target.get, target, context.name);
402
+ if (newGetter)
403
+ rv.get = newGetter;
404
+ }
405
+ if ('setter' in description) {
406
+ const newSetter = description.setter(target.set, target, context.name);
407
+ if (newSetter)
408
+ rv.set = newSetter;
409
+ }
410
+ return rv;
411
+ }
412
+ //return description.accessor?.(target, context.name, target)
413
+ }
414
+ };
415
+ }
416
+ /**
417
+ * Detects if the decorator is being called in modern (Modern) or legacy (Legacy) mode
418
+ * based on the arguments passed to the decorator function
419
+ */
420
+ function detectDecoratorMode(_target, contextOrKey, _descriptor) {
421
+ // Modern decorators have a context object as the second parameter
422
+ // Legacy decorators have a string/symbol key as the second parameter
423
+ if (typeof contextOrKey === 'object' &&
424
+ contextOrKey !== null &&
425
+ typeof contextOrKey.kind === 'string') {
426
+ return 'modern';
427
+ }
428
+ return 'legacy';
429
+ }
430
+ /**
431
+ * Main decorator factory that automatically detects and works with both Legacy and Modern decorator proposals
432
+ * @param description - The decorator description object
433
+ * @returns A decorator that works in both Legacy and Modern environments
434
+ */
435
+ const decorator = (description) => {
436
+ const modern = modernDecorator(description);
437
+ const legacy = legacyDecorator(description);
438
+ return ((target, contextOrKey, ...args) => {
439
+ const mode = detectDecoratorMode(target, contextOrKey, args[0]);
440
+ return mode === 'modern'
441
+ ? modern(target, contextOrKey, ...args)
442
+ : legacy(target, contextOrKey, ...args);
443
+ });
444
+ };
445
+
446
+ /**
447
+ * Creates a flavored (extensible) version of a function with chainable property modifiers.
448
+ *
449
+ * Each property defined in `flavors` returns a new flavored function that transforms
450
+ * how the original function is called. This enables a fluent API where properties
451
+ * create specialized variants of the base function.
452
+ *
453
+ * @param fn - The base function to flavor
454
+ * @param flavors - Object defining the flavor properties (getters or methods)
455
+ * @returns A proxy of the function with the flavor properties attached
456
+ *
457
+ * @example
458
+ * ```typescript
459
+ * function greet(name: string, options?: { loud?: boolean }) {
460
+ * const greeting = `Hello, ${name}!`
461
+ * return options?.loud ? greeting.toUpperCase() : greeting
462
+ * }
463
+ *
464
+ * const flavoredGreet = flavored(greet, {
465
+ * get loud() {
466
+ * return createFlavor(this, (name, opts) => [name, { ...opts, loud: true }])
467
+ * }
468
+ * })
469
+ *
470
+ * flavoredGreet('World') // "Hello, World!"
471
+ * flavoredGreet.loud('World') // "HELLO, WORLD!"
472
+ * ```
473
+ */
474
+ /**
475
+ * Creates a flavored (extensible) version of a function with chainable property modifiers.
476
+ */
477
+ function flavored(fn, flavors) {
478
+ fn.flavors = flavors;
479
+ return new Proxy(fn, {
480
+ get(target, prop, receiver) {
481
+ if (prop in flavors) {
482
+ return Reflect.get(flavors, prop, receiver);
483
+ }
484
+ return target[prop];
485
+ },
486
+ });
487
+ }
488
+ /**
489
+ * Creates a new flavored function that transforms arguments before calling the base.
490
+ *
491
+ * @param fn - The base flavored function
492
+ * @param transform - Function that receives the original arguments and returns transformed arguments
493
+ * @returns A new flavored function with the transformation applied
494
+ *
495
+ * @example
496
+ * ```typescript
497
+ * const loudGreet = createFlavor(greet, (name, opts) => [name, { ...opts, loud: true }])
498
+ * ```
499
+ */
500
+ function createFlavor(fn, transform, name) {
501
+ const fct = function flavorWrapper(...args) {
502
+ return fn.apply(this, transform(...args));
503
+ };
504
+ if (name)
505
+ named(name, fct);
506
+ return flavored(fct, fn.flavors || {});
507
+ }
508
+ /**
509
+ * Creates a new flavored function that merges options objects at a specific index.
510
+ * By default, uses the function's arity (length) as the index for options.
511
+ *
512
+ * @param fn - The base flavored function
513
+ * @param defaultOptions - Options to merge
514
+ * @param optionsIndex - Optional explicit index for options (defaults to fn.length)
515
+ * @param name - Optional name for the wrapper
516
+ * @returns A new flavored function
517
+ */
518
+ function flavorOptions(fn, defaultOptions, opts = {}) {
519
+ // If the function is already flavorOptions-wrapped, it might have an index stored
520
+ const targetIndex = opts.optionsIndex ?? fn.optionsIndex ?? fn.length;
521
+ const fct = function flavorOptionsWrapper(...args) {
522
+ const newArgs = [...args];
523
+ // Ensure we have enough arguments to reach the options index
524
+ while (newArgs.length <= targetIndex) {
525
+ newArgs.push(undefined);
526
+ }
527
+ const currentOptions = newArgs[targetIndex];
528
+ const isObject = currentOptions !== null &&
529
+ typeof currentOptions === 'object' &&
530
+ !Array.isArray(currentOptions);
531
+ newArgs[targetIndex] = isObject
532
+ ? { ...defaultOptions, ...currentOptions }
533
+ : defaultOptions;
534
+ return fn.apply(this, newArgs);
535
+ };
536
+ if (opts.name)
537
+ named(`${fn.name}.${opts.name}`, fct);
538
+ // Preserve arity and options track
539
+ Object.defineProperty(fct, 'length', { value: fn.length });
540
+ fct.optionsIndex = targetIndex;
541
+ return flavored(fct, fn.flavors || {});
542
+ }
543
+
544
+ /// <reference lib="esnext.collection" />
545
+ var _a, _b;
546
+ /**
547
+ * Uses weak references but still may iterate through them
548
+ * Note: The behavior is highly dependant on the garbage collector - some entries are perhaps deemed to be collected: don't resuscitate them
549
+ */
550
+ class IterableWeakMap {
551
+ constructor(entries) {
552
+ this.uuids = new WeakMap();
553
+ this.refs = {};
554
+ this[_a] = 'IterableWeakMap';
555
+ // Create a FinalizationRegistry to clean up refs when keys are garbage collected
556
+ this.registry = new FinalizationRegistry((uuid) => {
557
+ delete this.refs[uuid];
558
+ });
559
+ if (entries)
560
+ for (const [k, v] of entries)
561
+ this.set(k, v);
562
+ }
563
+ createIterator(cb) {
564
+ const { refs } = this;
565
+ return (function* () {
566
+ for (const uuid of Object.keys(refs)) {
567
+ const [keyRef, value] = refs[uuid];
568
+ const key = keyRef.deref();
569
+ if (key)
570
+ yield cb(key, value);
571
+ else
572
+ delete refs[uuid];
573
+ }
574
+ return undefined;
575
+ })();
576
+ }
577
+ clear() {
578
+ // Unregister all keys from the FinalizationRegistry
579
+ for (const uuid of Object.keys(this.refs)) {
580
+ const key = this.refs[uuid][0].deref();
581
+ if (key)
582
+ this.registry.unregister(key);
583
+ }
584
+ this.uuids = new WeakMap();
585
+ this.refs = {};
586
+ }
587
+ delete(key) {
588
+ const uuid = this.uuids.get(key);
589
+ if (!uuid)
590
+ return false;
591
+ delete this.refs[uuid];
592
+ this.uuids.delete(key);
593
+ this.registry.unregister(key);
594
+ return true;
595
+ }
596
+ forEach(callbackfn, thisArg) {
597
+ for (const [k, v] of this)
598
+ callbackfn.call(thisArg ?? this, v, k, thisArg ?? this);
599
+ }
600
+ get(key) {
601
+ const uuid = this.uuids.get(key);
602
+ if (!uuid)
603
+ return undefined;
604
+ return this.refs[uuid][1];
605
+ }
606
+ has(key) {
607
+ return this.uuids.has(key);
608
+ }
609
+ set(key, value) {
610
+ let uuid = this.uuids.get(key);
611
+ if (uuid) {
612
+ this.refs[uuid][1] = value;
613
+ }
614
+ else {
615
+ uuid = crypto.randomUUID();
616
+ this.uuids.set(key, uuid);
617
+ this.refs[uuid] = [new WeakRef(key), value];
618
+ // Register key for cleanup when garbage collected
619
+ this.registry.register(key, uuid, key);
620
+ }
621
+ return this;
622
+ }
623
+ get size() {
624
+ return [...this].length;
625
+ }
626
+ entries() {
627
+ return this.createIterator((key, value) => [key, value]);
628
+ }
629
+ keys() {
630
+ return this.createIterator((key, _value) => key);
631
+ }
632
+ values() {
633
+ return this.createIterator((_key, value) => value);
634
+ }
635
+ [Symbol.iterator]() {
636
+ return this.entries();
637
+ }
638
+ }
639
+ _a = Symbol.toStringTag;
640
+ /**
641
+ * Uses weak references but still may iterate through them
642
+ * Note: The behavior is highly dependant on the garbage collector - some entries are perhaps deemed to be collected: don't resuscitate them
643
+ */
644
+ class IterableWeakSet {
645
+ constructor(entries) {
646
+ this.uuids = new WeakMap();
647
+ this.refs = {};
648
+ this[_b] = 'IterableWeakSet';
649
+ // Create a FinalizationRegistry to clean up refs when values are garbage collected
650
+ this.registry = new FinalizationRegistry((uuid) => {
651
+ delete this.refs[uuid];
652
+ });
653
+ if (entries)
654
+ for (const k of entries)
655
+ this.add(k);
656
+ }
657
+ createIterator(cb) {
658
+ const { refs } = this;
659
+ return (function* () {
660
+ for (const uuid of Object.keys(refs)) {
661
+ const key = refs[uuid].deref();
662
+ if (key)
663
+ yield cb(key);
664
+ else
665
+ delete refs[uuid];
666
+ }
667
+ return undefined;
668
+ })();
669
+ }
670
+ clear() {
671
+ // Unregister all values from the FinalizationRegistry
672
+ for (const uuid of Object.keys(this.refs)) {
673
+ const value = this.refs[uuid].deref();
674
+ if (value)
675
+ this.registry.unregister(value);
676
+ }
677
+ this.uuids = new WeakMap();
678
+ this.refs = {};
679
+ }
680
+ add(value) {
681
+ let uuid = this.uuids.get(value);
682
+ if (!uuid) {
683
+ uuid = crypto.randomUUID();
684
+ this.uuids.set(value, uuid);
685
+ this.refs[uuid] = new WeakRef(value);
686
+ // Register value for cleanup when garbage collected
687
+ this.registry.register(value, uuid, value);
688
+ }
689
+ return this;
690
+ }
691
+ delete(value) {
692
+ const uuid = this.uuids.get(value);
693
+ if (!uuid)
694
+ return false;
695
+ delete this.refs[uuid];
696
+ this.uuids.delete(value);
697
+ this.registry.unregister(value);
698
+ return true;
699
+ }
700
+ forEach(callbackfn, thisArg) {
701
+ for (const value of this)
702
+ callbackfn.call(thisArg ?? this, value, value, thisArg ?? this);
703
+ }
704
+ has(value) {
705
+ return this.uuids.has(value);
706
+ }
707
+ get size() {
708
+ return [...this].length;
709
+ }
710
+ entries() {
711
+ return this.createIterator((key) => [key, key]);
712
+ }
713
+ keys() {
714
+ return this.createIterator((key) => key);
715
+ }
716
+ values() {
717
+ return this.createIterator((key) => key);
718
+ }
719
+ [Symbol.iterator]() {
720
+ return this.keys();
721
+ }
722
+ union(other) {
723
+ const others = {
724
+ [Symbol.iterator]() {
725
+ return other.keys();
726
+ },
727
+ };
728
+ const that = this;
729
+ return new Set((function* () {
730
+ yield* that;
731
+ for (const value of others)
732
+ if (!that.has(value))
733
+ yield value;
734
+ })());
735
+ }
736
+ intersection(other) {
737
+ const that = this;
738
+ return new Set((function* () {
739
+ for (const value of that)
740
+ if (other.has(value))
741
+ yield value;
742
+ })());
743
+ }
744
+ difference(other) {
745
+ const that = this;
746
+ return new Set((function* () {
747
+ for (const value of that)
748
+ if (!other.has(value))
749
+ yield value;
750
+ })());
751
+ }
752
+ symmetricDifference(other) {
753
+ const others = {
754
+ [Symbol.iterator]() {
755
+ return other.keys();
756
+ },
757
+ };
758
+ const that = this;
759
+ return new Set((function* () {
760
+ for (const value of that)
761
+ if (!other.has(value))
762
+ yield value;
763
+ for (const value of others)
764
+ if (!that.has(value))
765
+ yield value;
766
+ })());
767
+ }
768
+ isSubsetOf(other) {
769
+ for (const value of this)
770
+ if (!other.has(value))
771
+ return false;
772
+ return true;
773
+ }
774
+ isSupersetOf(other) {
775
+ const others = {
776
+ [Symbol.iterator]() {
777
+ return other.keys();
778
+ },
779
+ };
780
+ for (const value of others)
781
+ if (!this.has(value))
782
+ return false;
783
+ return true;
784
+ }
785
+ isDisjointFrom(other) {
786
+ for (const value of this)
787
+ if (other.has(value))
788
+ return false;
789
+ return true;
790
+ }
791
+ }
792
+ _b = Symbol.toStringTag;
793
+
794
+ /**
795
+ * Creates a mixin that can be used both as a class (extends) and as a function (mixin)
796
+ *
797
+ * This function supports:
798
+ * - Using mixins as base classes: `class MyClass extends MyMixin`
799
+ * - Using mixins as functions: `class MyClass extends MyMixin(SomeBase)`
800
+ * - Composing mixins: `const Composed = MixinA(MixinB)`
801
+ * - Type-safe property inference for all patterns
802
+ *
803
+ * @param mixinFunction - The function that creates the mixin
804
+ * @param unwrapFunction - Optional function to unwrap reactive objects for method calls
805
+ * @returns A mixin that can be used both as a class and as a function
806
+ */
807
+ function mixin(mixinFunction, unwrapFunction) {
808
+ /**
809
+ * Cache for mixin results to ensure the same base class always returns the same mixed class
810
+ */
811
+ const mixinCache = new WeakMap();
812
+ // Apply the mixin to Object as the base class
813
+ const MixedBase = mixinFunction(Object);
814
+ mixinCache.set(Object, MixedBase);
815
+ // Create the proxy that handles both constructor and function calls
816
+ return new Proxy(MixedBase, {
817
+ // Handle `MixinClass(SomeBase)` - use as mixin function
818
+ apply(_target, _thisArg, args) {
819
+ if (args.length === 0) {
820
+ throw new Error('Mixin requires a base class');
821
+ }
822
+ const baseClass = args[0];
823
+ if (typeof baseClass !== 'function') {
824
+ throw new Error('Mixin requires a constructor function');
825
+ }
826
+ // Check if it's a valid constructor or a mixin
827
+ if (!isConstructor(baseClass) &&
828
+ !(baseClass && typeof baseClass === 'function' && baseClass.prototype)) {
829
+ throw new Error('Mixin requires a valid constructor');
830
+ }
831
+ // Check cache first
832
+ const cached = mixinCache.get(baseClass);
833
+ if (cached) {
834
+ return cached;
835
+ }
836
+ let usedBase = baseClass;
837
+ if (unwrapFunction) {
838
+ // Create a proxied base class that handles method unwrapping
839
+ const ProxiedBaseClass = class extends baseClass {
840
+ };
841
+ // Proxy the prototype methods to handle unwrapping
842
+ const originalPrototype = baseClass.prototype;
843
+ const proxiedPrototype = new Proxy(originalPrototype, {
844
+ get(target, prop, receiver) {
845
+ const value = FoolProof.get(target, prop, receiver);
846
+ // Only wrap methods that are likely to access private fields
847
+ // Skip symbols and special properties that the reactive system needs
848
+ if (typeof value === 'function' &&
849
+ typeof prop === 'string' &&
850
+ !['constructor', 'toString', 'valueOf'].includes(prop)) {
851
+ // Return a wrapped version that uses unwrapped context
852
+ return function (...args) {
853
+ // Use the unwrapping function if provided, otherwise use this
854
+ const context = unwrapFunction(this);
855
+ return value.apply(context, args);
856
+ };
857
+ }
858
+ return value;
859
+ },
860
+ });
861
+ // Set the proxied prototype
862
+ Object.setPrototypeOf(ProxiedBaseClass.prototype, proxiedPrototype);
863
+ usedBase = ProxiedBaseClass;
864
+ }
865
+ // Create the mixed class using the proxied base class
866
+ const mixedClass = mixinFunction(usedBase);
867
+ // Cache the result
868
+ mixinCache.set(baseClass, mixedClass);
869
+ return mixedClass;
870
+ },
871
+ });
872
+ }
873
+
874
+ /******************************************************************************
875
+ Copyright (c) Microsoft Corporation.
876
+
877
+ Permission to use, copy, modify, and/or distribute this software for any
878
+ purpose with or without fee is hereby granted.
879
+
880
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
881
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
882
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
883
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
884
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
885
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
886
+ PERFORMANCE OF THIS SOFTWARE.
887
+ ***************************************************************************** */
888
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
889
+
890
+
891
+ function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
892
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
893
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
894
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
895
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
896
+ var _, done = false;
897
+ for (var i = decorators.length - 1; i >= 0; i--) {
898
+ var context = {};
899
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
900
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
901
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
902
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
903
+ if (kind === "accessor") {
904
+ if (result === void 0) continue;
905
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
906
+ if (_ = accept(result.get)) descriptor.get = _;
907
+ if (_ = accept(result.set)) descriptor.set = _;
908
+ if (_ = accept(result.init)) initializers.unshift(_);
909
+ }
910
+ else if (_ = accept(result)) {
911
+ if (kind === "field") initializers.unshift(_);
912
+ else descriptor[key] = _;
913
+ }
914
+ }
915
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
916
+ done = true;
917
+ }
918
+ function __runInitializers(thisArg, initializers, value) {
919
+ var useValue = arguments.length > 2;
920
+ for (var i = 0; i < initializers.length; i++) {
921
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
922
+ }
923
+ return useValue ? value : void 0;
924
+ }
925
+ function __setFunctionName(f, name, prefix) {
926
+ if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
927
+ return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
928
+ }
929
+ function __classPrivateFieldGet(receiver, state, kind, f) {
930
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
931
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
932
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
933
+ }
934
+
935
+ function __classPrivateFieldSet(receiver, state, value, kind, f) {
936
+ if (kind === "m") throw new TypeError("Private method is not writable");
937
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
938
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
939
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
940
+ }
941
+
942
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
943
+ var e = new Error(message);
944
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
945
+ };
946
+
947
+ const debugHooks = {
948
+ isDevtoolsEnabled: () => false,
949
+ registerEffect: () => { },
950
+ getTriggerChain: () => [],
951
+ captureStack: () => [],
952
+ captureLineage: () => new Error().stack,
953
+ formatStack: (stack) => [stack],
954
+ recordTriggerLink: () => { },
955
+ };
956
+ function setDebugHooks(hooks) {
957
+ Object.assign(debugHooks, hooks);
958
+ }
959
+
960
+ var _ZoneAggregator_zones;
961
+ function isu(z) {
962
+ return z;
963
+ }
964
+ class AZone {
965
+ enter(value) {
966
+ const prev = this.active;
967
+ this.active = value;
968
+ return prev;
969
+ }
970
+ leave(entered) {
971
+ this.active = entered;
972
+ }
973
+ with(value, fn) {
974
+ const entered = this.enter(value);
975
+ let res;
976
+ try {
977
+ res = fn();
978
+ }
979
+ finally {
980
+ this.leave(entered);
981
+ }
982
+ // [HACK]: Sanitization
983
+ // See BROWSER_ASYNC_POLYFILL.md
984
+ return asyncCore.asyncHooks.sanitizePromise(res);
985
+ }
986
+ root(fn) {
987
+ const prev = this.enter();
988
+ try {
989
+ return fn();
990
+ }
991
+ finally {
992
+ this.leave(prev);
993
+ }
994
+ }
995
+ get zoned() {
996
+ const active = this.active;
997
+ return named(`${this}@${active}`, (fn) => this.with(active, fn));
998
+ }
999
+ }
1000
+ class Zone extends AZone {
1001
+ }
1002
+ class ZoneHistory extends AZone {
1003
+ has(value) {
1004
+ return this.history.has(value);
1005
+ }
1006
+ some(predicate) {
1007
+ for (const value of this.history)
1008
+ if (predicate(value))
1009
+ return true;
1010
+ return false;
1011
+ }
1012
+ constructor(controlled = new Zone()) {
1013
+ super();
1014
+ this.controlled = controlled;
1015
+ this.history = new Set();
1016
+ const self = this;
1017
+ this.present = Object.create(controlled, Object.getOwnPropertyDescriptors({
1018
+ get active() {
1019
+ return controlled.active;
1020
+ },
1021
+ set active(value) {
1022
+ controlled.active = value;
1023
+ },
1024
+ enter(value) {
1025
+ if (value && self.history.has(value))
1026
+ throw new Error('ZoneHistory: re-entering historical zone');
1027
+ if (value !== undefined)
1028
+ self.history.add(value);
1029
+ return { added: value, entered: isu(controlled).enter(value) };
1030
+ },
1031
+ leave(entered) {
1032
+ if (entered.added !== undefined)
1033
+ self.history.delete(entered.added);
1034
+ return isu(controlled).leave(entered.entered);
1035
+ },
1036
+ }));
1037
+ }
1038
+ get active() {
1039
+ return { present: this.controlled.active, history: new Set(this.history) };
1040
+ }
1041
+ set active(value) {
1042
+ this.history = value?.history ? new Set(value.history) : new Set();
1043
+ this.controlled.active = value?.present;
1044
+ }
1045
+ }
1046
+ class ZoneAggregator extends AZone {
1047
+ constructor(...zones) {
1048
+ super();
1049
+ _ZoneAggregator_zones.set(this, new Set());
1050
+ for (const z of zones)
1051
+ __classPrivateFieldGet(this, _ZoneAggregator_zones, "f").add(z);
1052
+ }
1053
+ get active() {
1054
+ const rv = new Map();
1055
+ for (const z of __classPrivateFieldGet(this, _ZoneAggregator_zones, "f"))
1056
+ if (z.active !== undefined)
1057
+ rv.set(z, z.active);
1058
+ return rv;
1059
+ }
1060
+ set active(value) {
1061
+ for (const z of __classPrivateFieldGet(this, _ZoneAggregator_zones, "f"))
1062
+ z.active = value?.get(z);
1063
+ }
1064
+ enter(value) {
1065
+ const entered = new Map();
1066
+ for (const z of __classPrivateFieldGet(this, _ZoneAggregator_zones, "f")) {
1067
+ const v = value?.get(z);
1068
+ entered.set(z, isu(z).enter(v));
1069
+ }
1070
+ return entered;
1071
+ }
1072
+ leave(entered) {
1073
+ for (const z of __classPrivateFieldGet(this, _ZoneAggregator_zones, "f"))
1074
+ isu(z).leave(entered.get(z));
1075
+ }
1076
+ add(z) {
1077
+ __classPrivateFieldGet(this, _ZoneAggregator_zones, "f").add(z);
1078
+ }
1079
+ delete(z) {
1080
+ __classPrivateFieldGet(this, _ZoneAggregator_zones, "f").delete(z);
1081
+ }
1082
+ clear() {
1083
+ __classPrivateFieldGet(this, _ZoneAggregator_zones, "f").clear();
1084
+ }
1085
+ }
1086
+ _ZoneAggregator_zones = new WeakMap();
1087
+ const asyncZone = tag('async', new ZoneAggregator());
1088
+ asyncCore.asyncHooks.addHook(() => {
1089
+ const zone = asyncZone.active;
1090
+ return () => {
1091
+ const prev = asyncZone.active;
1092
+ asyncZone.active = zone;
1093
+ return () => {
1094
+ asyncZone.active = prev;
1095
+ };
1096
+ };
1097
+ });
1098
+
1099
+ // Symbol for storing root function on the function itself
1100
+ const rootFunctionSymbol = Symbol('root-function');
1101
+ // Track which effects are watching which reactive objects for cleanup
1102
+ exports.effectToReactiveObjects = new WeakMap();
1103
+ // Track effects per reactive object and property
1104
+ exports.watchers = new WeakMap();
1105
+ // Track effect metadata and relationships
1106
+ let effectNodes = new WeakMap();
1107
+ function getEffectNode(effect) {
1108
+ let node = effectNodes.get(effect);
1109
+ if (!node) {
1110
+ node = {};
1111
+ effectNodes.set(effect, node);
1112
+ }
1113
+ return node;
1114
+ }
1115
+ // Track reverse mapping to ensure unicity: One Root -> One Function
1116
+ let reverseRoots = new WeakMap();
1117
+ function resetRegistry() {
1118
+ exports.effectToReactiveObjects = new WeakMap();
1119
+ exports.watchers = new WeakMap();
1120
+ effectNodes = new WeakMap();
1121
+ reverseRoots = new WeakMap();
1122
+ }
1123
+ /**
1124
+ * Marks a function with its root function for effect tracking
1125
+ * Enforces strict unicity: A root function can only identify ONE function.
1126
+ * @param fn - The function to mark
1127
+ * @param root - The root function
1128
+ * @returns The marked function
1129
+ */
1130
+ function markWithRoot(fn, root) {
1131
+ // Check for collision
1132
+ const existingRef = reverseRoots.get(root);
1133
+ const existing = existingRef?.deref();
1134
+ if (existing && existing !== fn) {
1135
+ const rootName = root.name || 'anonymous';
1136
+ const existingName = existing.name || 'anonymous';
1137
+ const fnName = fn.name || 'anonymous';
1138
+ throw new Error(`[reactive] Abusive Shared Root detected: Root '${rootName}' is already identifying function '${existingName}'. ` +
1139
+ `Cannot reuse it for '${fnName}'. Shared roots cause lost updates and broken identity logic.`);
1140
+ }
1141
+ // Always update the map so subsequent checks find this one
1142
+ // (Last writer wins for the check)
1143
+ reverseRoots.set(root, new WeakRef(fn));
1144
+ // Store root mapping as symbol property on the function
1145
+ fn[rootFunctionSymbol] = getRoot(root);
1146
+ return fn;
1147
+ }
1148
+ /**
1149
+ * Gets the root function of a function for effect tracking
1150
+ * @param fn - The function to get the root of
1151
+ * @returns The root function
1152
+ */
1153
+ function getRoot(fn) {
1154
+ while (fn) {
1155
+ const r = fn[rootFunctionSymbol];
1156
+ if (!r)
1157
+ break;
1158
+ fn = r;
1159
+ }
1160
+ return fn;
1161
+ }
1162
+
1163
+ const effectMarker = {
1164
+ enter: 'effect:enter',
1165
+ leave: 'effect:leave',
1166
+ };
1167
+ function formatTrigger({ obj, evolution, dependency, touch }) {
1168
+ const detail = evolution.type === 'bunch' ? evolution.method : String(evolution.prop);
1169
+ const parts = [`${evolution.type} ${detail} on`, obj];
1170
+ if (dependency) {
1171
+ parts.push('\n Dependency created at:');
1172
+ parts.push(...debugHooks.formatStack(dependency));
1173
+ }
1174
+ if (touch) {
1175
+ parts.push('\n Touched from:');
1176
+ parts.push(...debugHooks.formatStack(touch));
1177
+ }
1178
+ return parts;
1179
+ }
1180
+ /**
1181
+ * Console-friendly description of a `CleanupReason`.
1182
+ * Returns an array of arguments to spread into `console.log` / `console.warn`,
1183
+ * mixing strings and raw object references so the console can render them as inspectable values.
1184
+ *
1185
+ * @example
1186
+ * ```typescript
1187
+ * effect(({ reaction }) => {
1188
+ * if (reaction !== true) console.log(...formatCleanupReason(reaction))
1189
+ * })
1190
+ * ```
1191
+ */
1192
+ function formatCleanupReason(reason, depth = 0) {
1193
+ const indent = depth ? ' '.repeat(depth) : '';
1194
+ switch (reason.type) {
1195
+ case 'propChange': {
1196
+ const parts = [`${indent}propChange:`];
1197
+ for (let i = 0; i < reason.triggers.length; i++) {
1198
+ if (i > 0)
1199
+ parts.push(',');
1200
+ parts.push(...formatTrigger(reason.triggers[i]));
1201
+ }
1202
+ return parts;
1203
+ }
1204
+ case 'stopped':
1205
+ return [`${indent}stopped`];
1206
+ case 'gc':
1207
+ return [`${indent}gc`];
1208
+ case 'error':
1209
+ return [`${indent}error:`, reason.error];
1210
+ case 'lineage':
1211
+ return [`${indent}lineage ←\n`, ...formatCleanupReason(reason.parent, depth + 1)];
1212
+ case 'invalidate':
1213
+ return [`${indent}invalidate ←\n`, ...formatCleanupReason(reason.cause, depth + 1)];
1214
+ case 'multiple': {
1215
+ const parts = [];
1216
+ for (let i = 0; i < reason.reasons.length; i++) {
1217
+ if (i > 0)
1218
+ parts.push('\n');
1219
+ parts.push(...formatCleanupReason(reason.reasons[i], depth));
1220
+ }
1221
+ return parts;
1222
+ }
1223
+ }
1224
+ }
1225
+ /**
1226
+ * Symbol representing all properties in reactive tracking
1227
+ */
1228
+ const allProps = Symbol('all-props');
1229
+ /**
1230
+ * Symbol for structure-only tracking (triggered on key add/delete, not value changes).
1231
+ * Used by ownKeys proxy trap — Object.keys(), for..in, Map.keys() depend on this.
1232
+ */
1233
+ const keysOf = Symbol('keys-of');
1234
+ /**
1235
+ * Symbol to check if an effect is stopped
1236
+ */
1237
+ const stopped = Symbol('stopped');
1238
+ /**
1239
+ * Symbol to access effect cleanup function
1240
+ */
1241
+ const cleanup = Symbol('cleanup');
1242
+ /**
1243
+ * Structured error codes for machine-readable diagnosis
1244
+ */
1245
+ exports.ReactiveErrorCode = void 0;
1246
+ (function (ReactiveErrorCode) {
1247
+ ReactiveErrorCode["CycleDetected"] = "Cycle detected";
1248
+ ReactiveErrorCode["MaxDepthExceeded"] = "Max depth exceeded";
1249
+ ReactiveErrorCode["MaxReactionExceeded"] = "Max reaction exceeded";
1250
+ ReactiveErrorCode["WriteInComputed"] = "Write in computed";
1251
+ ReactiveErrorCode["TrackingError"] = "Tracking error";
1252
+ ReactiveErrorCode["BrokenEffects"] = "Broken effects";
1253
+ })(exports.ReactiveErrorCode || (exports.ReactiveErrorCode = {}));
1254
+ /**
1255
+ * Error class for reactive system errors
1256
+ */
1257
+ class ReactiveError extends Error {
1258
+ constructor(message, debugInfo) {
1259
+ super(message);
1260
+ this.debugInfo = debugInfo;
1261
+ this.name = 'ReactiveError';
1262
+ }
1263
+ get code() {
1264
+ return this.debugInfo?.code;
1265
+ }
1266
+ get cause() {
1267
+ return this.debugInfo?.cause;
1268
+ }
1269
+ }
1270
+ // biome-ignore-start lint/correctness/noUnusedFunctionParameters: Interface declaration with empty defaults
1271
+ /**
1272
+ * Global options for the reactive system
1273
+ */
1274
+ const options = {
1275
+ /**
1276
+ * Debug purpose: called when an effect is entered
1277
+ * @param effect - The effect that is entered
1278
+ */
1279
+ enter: (_effect) => { },
1280
+ /**
1281
+ * Debug purpose: called when an effect is left
1282
+ * @param effect - The effect that is left
1283
+ */
1284
+ leave: (_effect) => { },
1285
+ /**
1286
+ * Debug purpose: called when an effect is chained
1287
+ * @param target - The effect that is being triggered
1288
+ * @param caller - The effect that is calling the target
1289
+ */
1290
+ chain: (_targets, _caller) => { },
1291
+ /**
1292
+ * Debug purpose: called when an effect chain is started
1293
+ * @param target - The effect that is being triggered
1294
+ */
1295
+ beginChain: (_targets) => { },
1296
+ /**
1297
+ * Debug purpose: called when an effect chain is ended
1298
+ */
1299
+ endChain: () => { },
1300
+ garbageCollected: (_fn) => { },
1301
+ /**
1302
+ * Debug purpose: called when an object is touched
1303
+ * @param obj - The object that is touched
1304
+ * @param evolution - The type of change
1305
+ * @param props - The properties that changed
1306
+ * @param deps - The dependencies that changed
1307
+ */
1308
+ touched: (_obj, _evolution, _props, _deps) => { },
1309
+ /**
1310
+ * Debug purpose: called when an effect is skipped because it's already running
1311
+ * @param effect - The effect that is already running
1312
+ * @param runningChain - The array of effects from the detected one to the currently running one
1313
+ */
1314
+ skipRunningEffect: (_effect) => { },
1315
+ /**
1316
+ * Debug purpose: maximum effect chain (like call stack max depth)
1317
+ * Used to prevent infinite loops
1318
+ * @default 100
1319
+ */
1320
+ maxEffectChain: 100,
1321
+ /**
1322
+ * Maximum number of times an effect can be triggered by the same cause in a single batch
1323
+ * Used to detect aggressive re-computation or infinite loops
1324
+ * @default 10
1325
+ */
1326
+ maxTriggerPerBatch: 10,
1327
+ /**
1328
+ * Debug purpose: maximum effect reaction (like call stack max depth)
1329
+ * Used to prevent infinite loops
1330
+ * @default 'throw'
1331
+ */
1332
+ maxEffectReaction: 'throw',
1333
+ /**
1334
+ * Callback called when a memoization discrepancy is detected (debug only)
1335
+ * When defined, memoized functions will run a second time (untracked) to verify consistency.
1336
+ * If the untracked run returns a different value than the cached one, this callback is triggered.
1337
+ *
1338
+ * This is the primary tool for detecting missing reactive dependencies in computed values.
1339
+ *
1340
+ * @param cached - The value currently in the memoization cache
1341
+ * @param fresh - The value obtained by re-running the function untracked
1342
+ * @param fn - The memoized function itself
1343
+ * @param args - Arguments passed to the function
1344
+ *
1345
+ * @example
1346
+ * ```typescript
1347
+ * reactiveOptions.onMemoizationDiscrepancy = (cached, fresh, fn, args) => {
1348
+ * throw new Error(`Memoization discrepancy in ${fn.name}!`);
1349
+ * };
1350
+ * ```
1351
+ */
1352
+ onMemoizationDiscrepancy: undefined,
1353
+ /**
1354
+ * How to handle cycles detected in effect batches.
1355
+ *
1356
+ * - `'production'` (Default): High-performance mode. Disables dependency graph maintenance and
1357
+ * Topological Sorting in favor of a simple FIFO queue. Use this for trustworthy, acyclic UI code.
1358
+ * Cycle detection is heuristic (uses maxEffectChain execution counts).
1359
+ *
1360
+ * - `'development'`: Maintains direct dependency graph for early cycle detection during edge creation.
1361
+ * Catches cycles before effects execute via DFS check when adding edges. Throws immediately with
1362
+ * basic path information. Good balance of debugging help with moderate overhead.
1363
+ *
1364
+ * - `'debug'`: Full diagnostic mode with transitive closures and topological sorting.
1365
+ * Provides detailed cycle path reporting. Highest overhead but most informative for bug hunting.
1366
+ *
1367
+ * @default 'production'
1368
+ */
1369
+ cycleHandling: 'development',
1370
+ /**
1371
+ * Internal flag used by memoization discrepancy detector to avoid counting calls in tests
1372
+ * @warning Do not modify this flag manually, this flag is given by the engine
1373
+ */
1374
+ isVerificationRun: false,
1375
+ /**
1376
+ * Maximum depth for deep watching traversal
1377
+ * Used to prevent infinite recursion in circular references
1378
+ * @default 100
1379
+ */
1380
+ maxDeepWatchDepth: 100,
1381
+ /**
1382
+ * Only react on instance members modification (not inherited properties)
1383
+ * For instance, do not track class methods
1384
+ * @default true
1385
+ */
1386
+ instanceMembers: true,
1387
+ /**
1388
+ * Ignore accessors (getters and setters) and only track direct properties
1389
+ * @default true
1390
+ */
1391
+ ignoreAccessors: true,
1392
+ /**
1393
+ * Enable recursive touching when objects with the same prototype are replaced
1394
+ * When enabled, replacing an object with another of the same prototype triggers
1395
+ * recursive diffing instead of notifying parent effects
1396
+ * @default true
1397
+ */
1398
+ recursiveTouching: true,
1399
+ /**
1400
+ * Default async execution mode for effects that return Promises
1401
+ * - 'cancel': Cancel previous async execution when dependencies change (default, enables async zone)
1402
+ * - 'queue': Queue next execution to run after current completes (enables async zone)
1403
+ * - 'ignore': Ignore new executions while async work is running (enables async zone)
1404
+ * - false: Disable async zone and async mode handling (effects run concurrently)
1405
+ *
1406
+ * **When truthy:** Enables async zone (Promise.prototype wrapping) for automatic context
1407
+ * preservation in Promise callbacks. Warning: This modifies Promise.prototype globally.
1408
+ * Only enable if no other library modifies Promise.prototype.
1409
+ *
1410
+ * **When false:** Async zone is disabled. Use `tracked()` manually in Promise callbacks.
1411
+ *
1412
+ * Can be overridden per-effect via EffectOptions
1413
+ * @default 'cancel'
1414
+ */
1415
+ asyncMode: 'cancel',
1416
+ // biome-ignore lint/suspicious/noConsole: This is the whole point here
1417
+ warn: (...args) => console.warn(...args),
1418
+ /**
1419
+ * Introspection and debug aids. Set to `null` to disable all debug overhead in production.
1420
+ *
1421
+ * - `gatherReasons`: collect `PropTrigger[]` for `CleanupReason` on effect re-runs (default `true`)
1422
+ * - `lineages`: what lineages to capture in PropTrigger (default `'touch'`)
1423
+ * - `logErrors`: log errors with detailed context (default `true`)
1424
+ * - `enableHistory`: keep a history of mutations (default `true`)
1425
+ * - `historySize`: number of mutations to keep in history (default `50`)
1426
+ *
1427
+ * `enableDevTools()` sets `logErrors` to `true` automatically.
1428
+ *
1429
+ * @example
1430
+ * ```typescript
1431
+ * // Production: disable all introspection
1432
+ * reactiveOptions.introspection = null
1433
+ * ```
1434
+ */
1435
+ introspection: {
1436
+ gatherReasons: { lineages: 'touch' },
1437
+ logErrors: true,
1438
+ enableHistory: true,
1439
+ historySize: 50,
1440
+ },
1441
+ /**
1442
+ * Configuration for zone hooks - control which async APIs are hooked
1443
+ * Each option controls whether the corresponding async API is wrapped to preserve effect context
1444
+ * Only applies when asyncMode is enabled (truthy)
1445
+ * @deprecated Should take all when we made sure PIXI.create, Game.create, ... are -> .root()
1446
+ */
1447
+ zones: {
1448
+ /**
1449
+ * Hook setTimeout to preserve effect context
1450
+ * @default true
1451
+ */
1452
+ setTimeout: true,
1453
+ /**
1454
+ * Hook setInterval to preserve effect context
1455
+ * @default true
1456
+ */
1457
+ setInterval: true,
1458
+ /**
1459
+ * Hook requestAnimationFrame (runs in untracked context when hooked)
1460
+ * @default true
1461
+ */
1462
+ requestAnimationFrame: true,
1463
+ /**
1464
+ * Hook queueMicrotask to preserve effect context
1465
+ * @default true
1466
+ */
1467
+ queueMicrotask: true,
1468
+ },
1469
+ };
1470
+ function optionCall(name, ...args) {
1471
+ const fn = options[name];
1472
+ if (typeof fn !== 'function')
1473
+ return;
1474
+ try {
1475
+ ;
1476
+ fn(...args);
1477
+ }
1478
+ catch (error) {
1479
+ options.warn(`options.${name} threw`, error);
1480
+ }
1481
+ }
1482
+ // --- Proxy State (Merged from proxy-state.ts) ---
1483
+ const objectToProxy = new WeakMap();
1484
+ const proxyToObject = new WeakMap();
1485
+ function storeProxyRelationship(target, proxy) {
1486
+ objectToProxy.set(target, proxy);
1487
+ proxyToObject.set(proxy, target);
1488
+ }
1489
+ function getExistingProxy(target) {
1490
+ return objectToProxy.get(target);
1491
+ }
1492
+ function unwrap(obj) {
1493
+ if (!obj || typeof obj !== 'object')
1494
+ return obj;
1495
+ return proxyToObject.get(obj) || obj;
1496
+ }
1497
+ function isReactive(obj) {
1498
+ return proxyToObject.has(obj);
1499
+ }
1500
+
1501
+ const effectHistory = tag('effectHistory', new ZoneHistory());
1502
+ tag('effectHistory.present', effectHistory.present);
1503
+ asyncZone.add(effectHistory);
1504
+ /**
1505
+ * Aggregator for zones that need to be tracked along effects.
1506
+ * ie. in each effect, the active zone of the given zoning will be the one active at effect's definition
1507
+ */
1508
+ const effectAggregator = tag('effectAggregator', new ZoneAggregator(effectHistory.present));
1509
+ function isRunning(effect) {
1510
+ const root = getRoot(effect);
1511
+ return effectHistory.some((e) => getRoot(e) === root);
1512
+ }
1513
+ function getActiveEffect() {
1514
+ return effectHistory.present.active;
1515
+ }
1516
+ /**
1517
+ * ADD a cleanup function to an object using the cleanup symbol.
1518
+ * The cleanup function will be called when the object needs to be disposed.
1519
+ *
1520
+ * Note: most of the time, you don't need to use this function directly.
1521
+ * The main use if for the cleanup function to be stored with the object, as GC calls the cleanup function when the *function* is garbage collected.
1522
+ *
1523
+ * @param obj - The object to attach the cleanup function to
1524
+ * @param cleanupFn - The cleanup function to attach
1525
+ * @returns The object with the cleanup function attached
1526
+ */
1527
+ function cleanedBy(obj, cleanupFn) {
1528
+ const oldCleanup = obj[cleanup];
1529
+ return Object.defineProperty(obj, cleanup, {
1530
+ value: oldCleanup
1531
+ ? Object.defineProperties((reason) => {
1532
+ oldCleanup(reason);
1533
+ cleanupFn(reason);
1534
+ }, {
1535
+ [stopped]: { get: () => oldCleanup[stopped] || cleanupFn[stopped] },
1536
+ })
1537
+ : cleanupFn,
1538
+ writable: false,
1539
+ enumerable: false,
1540
+ configurable: true,
1541
+ });
1542
+ }
1543
+
1544
+ // Track dependency stacks per (obj, prop, effect)
1545
+ let dependencyStacks = new WeakMap();
1546
+ function resetTracking() {
1547
+ dependencyStacks = new WeakMap();
1548
+ }
1549
+ function getDependencyStack(effect, obj, prop) {
1550
+ const objStacks = dependencyStacks.get(obj);
1551
+ if (!objStacks)
1552
+ return undefined;
1553
+ return objStacks.get(prop)?.get(effect) ?? objStacks.get(allProps)?.get(effect);
1554
+ }
1555
+ /**
1556
+ * Marks a property as a dependency of the current effect
1557
+ * @param obj - The object containing the property
1558
+ * @param prop - The property name (defaults to allProps)
1559
+ */
1560
+ function dependant(obj, prop = allProps) {
1561
+ obj = unwrap(obj);
1562
+ const currentActiveEffect = getActiveEffect();
1563
+ // Early return if no active effect, tracking disabled, or invalid prop
1564
+ if (!currentActiveEffect || (typeof prop === 'symbol' && prop !== allProps && prop !== keysOf))
1565
+ return;
1566
+ if ('dependencyHook' in currentActiveEffect) {
1567
+ // @ts-expect-error We declared it nowhere - it's okay as it's really internal and for edge-case debug purpose only
1568
+ currentActiveEffect.dependencyHook(obj, prop);
1569
+ }
1570
+ let objectWatchers = exports.watchers.get(obj);
1571
+ if (!objectWatchers) {
1572
+ objectWatchers = new Map();
1573
+ exports.watchers.set(obj, objectWatchers);
1574
+ }
1575
+ let deps = objectWatchers.get(prop);
1576
+ if (!deps) {
1577
+ deps = new Set();
1578
+ objectWatchers.set(prop, deps);
1579
+ }
1580
+ deps.add(currentActiveEffect);
1581
+ // Track which reactive objects this effect is watching
1582
+ const effectObjects = exports.effectToReactiveObjects.get(currentActiveEffect);
1583
+ if (effectObjects) {
1584
+ effectObjects.add(obj);
1585
+ }
1586
+ else {
1587
+ exports.effectToReactiveObjects.set(currentActiveEffect, new Set([obj]));
1588
+ }
1589
+ // Store dependency stack if introspection is enabled
1590
+ const gatherReasons = options.introspection?.gatherReasons;
1591
+ if (gatherReasons) {
1592
+ const lineageConfig = gatherReasons.lineages;
1593
+ if (lineageConfig === 'dependency' || lineageConfig === 'both') {
1594
+ let objStacks = dependencyStacks.get(obj);
1595
+ if (!objStacks) {
1596
+ objStacks = new Map();
1597
+ dependencyStacks.set(obj, objStacks);
1598
+ }
1599
+ let propStacks = objStacks.get(prop);
1600
+ if (!propStacks) {
1601
+ propStacks = new Map();
1602
+ objStacks.set(prop, propStacks);
1603
+ }
1604
+ propStacks.set(currentActiveEffect, debugHooks.captureLineage());
1605
+ }
1606
+ }
1607
+ }
1608
+
1609
+ /**
1610
+ * Finds a cycle in a sequence of functions by looking for the first repetition
1611
+ */
1612
+ function findCycleInChain(roots) {
1613
+ const seen = new Map();
1614
+ for (let i = 0; i < roots.length; i++) {
1615
+ const root = roots[i];
1616
+ if (seen.has(root)) {
1617
+ return roots.slice(seen.get(root));
1618
+ }
1619
+ seen.set(root, i);
1620
+ }
1621
+ return null;
1622
+ }
1623
+ /**
1624
+ * Formats a list of function roots into a readable trace
1625
+ */
1626
+ function formatRoots(roots, limit = 20) {
1627
+ const names = roots.map((r) => r.name || '<anonymous>');
1628
+ if (names.length <= limit)
1629
+ return names.join(' → ');
1630
+ const start = names.slice(0, 5);
1631
+ const end = names.slice(-10);
1632
+ return `${start.join(' → ')} ... (${names.length - 15} more) ... ${end.join(' → ')}`;
1633
+ }
1634
+ // Nested map structure for efficient counting and batch cleanup
1635
+ // batchId -> effect root -> obj -> prop -> count
1636
+ let activationRegistry;
1637
+ const activationLog = new Array(100);
1638
+ /**
1639
+ * Returns the activation log containing recent effect activations for debugging.
1640
+ * The log is a circular buffer of the last 100 activations.
1641
+ *
1642
+ * @returns Array of activation records
1643
+ */
1644
+ function getActivationLog() {
1645
+ return activationLog;
1646
+ }
1647
+ function recordActivation(effect, obj, evolution, prop) {
1648
+ const root = getRoot(effect);
1649
+ if (!activationRegistry)
1650
+ return;
1651
+ let effectData = activationRegistry.get(root);
1652
+ if (!effectData) {
1653
+ effectData = new Map();
1654
+ activationRegistry.set(root, effectData);
1655
+ }
1656
+ let objData = effectData.get(obj);
1657
+ if (!objData) {
1658
+ objData = new Map();
1659
+ effectData.set(obj, objData);
1660
+ }
1661
+ const count = (objData.get(prop) ?? 0) + 1;
1662
+ objData.set(prop, count);
1663
+ // Keep a limited history for diagnostics
1664
+ activationLog.unshift({
1665
+ effect,
1666
+ obj,
1667
+ evolution,
1668
+ prop,
1669
+ });
1670
+ activationLog.pop();
1671
+ if (count >= options.maxTriggerPerBatch) {
1672
+ const effectName = root.name;
1673
+ const message = `Aggressive trigger detected: effect "${effectName}" triggered ${count} times in the batch by the same cause.`;
1674
+ if (options.maxEffectReaction === 'throw') {
1675
+ throw new ReactiveError(message, {
1676
+ code: exports.ReactiveErrorCode.MaxReactionExceeded,
1677
+ count,
1678
+ effect: root,
1679
+ });
1680
+ }
1681
+ options.warn(`[reactive] ${message}`);
1682
+ }
1683
+ }
1684
+ function caught(onThrow, effect) {
1685
+ effect ?? (effect = getActiveEffect());
1686
+ if (!effect)
1687
+ throw new Error('Tracking an effect throw while not in an effect');
1688
+ const node = getEffectNode(effect);
1689
+ if (!node.catchers)
1690
+ node.catchers = [onThrow];
1691
+ else
1692
+ node.catchers.push(onThrow);
1693
+ }
1694
+ /** @deprecated Use `caught` instead */
1695
+ const onEffectThrow = caught;
1696
+ // Dependency graph: tracks which effects trigger which other effects
1697
+ // Uses roots (Function) as keys for consistency
1698
+ let effectTriggers = new WeakMap();
1699
+ let effectTriggeredBy = new WeakMap();
1700
+ // Transitive closures: track all indirect relationships
1701
+ // causesClosure: for each effect, all effects that trigger it (directly or indirectly)
1702
+ // consequencesClosure: for each effect, all effects that it triggers (directly or indirectly)
1703
+ let causesClosure = new WeakMap();
1704
+ let consequencesClosure = new WeakMap();
1705
+ let broken = false;
1706
+ /**
1707
+ * Gets or creates an IterableWeakSet for a closure map
1708
+ */
1709
+ function getOrCreateClosure(closure, root) {
1710
+ let set = closure.get(root);
1711
+ if (!set) {
1712
+ set = new IterableWeakSet();
1713
+ closure.set(root, set);
1714
+ }
1715
+ return set;
1716
+ }
1717
+ /**
1718
+ * Adds an edge to the dependency graph: callerRoot → targetRoot
1719
+ * Also maintains transitive closures
1720
+ * @param callerRoot - Root function of the effect that triggers
1721
+ * @param targetRoot - Root function of the effect being triggered
1722
+ */
1723
+ function addGraphEdge(callerRoot, targetRoot) {
1724
+ if (options.cycleHandling === 'production')
1725
+ return;
1726
+ // Add to forward graph: callerRoot → targetRoot
1727
+ const triggers = effectTriggers.get(callerRoot);
1728
+ if (!triggers) {
1729
+ const newTriggers = new IterableWeakSet();
1730
+ newTriggers.add(targetRoot);
1731
+ effectTriggers.set(callerRoot, newTriggers);
1732
+ }
1733
+ else {
1734
+ triggers.add(targetRoot);
1735
+ }
1736
+ // Add to reverse graph: targetRoot ← callerRoot
1737
+ let triggeredBy = effectTriggeredBy.get(targetRoot);
1738
+ if (!triggeredBy) {
1739
+ triggeredBy = new IterableWeakSet();
1740
+ effectTriggeredBy.set(targetRoot, triggeredBy);
1741
+ }
1742
+ triggeredBy.add(callerRoot);
1743
+ // Update transitive closures
1744
+ // When U→V is added, we need to propagate the relationship:
1745
+ // 1. Add U to causesClosure(V) and V to consequencesClosure(U) (direct relationship)
1746
+ // 2. For each X in causesClosure(U): add V to consequencesClosure(X) and X to causesClosure(V)
1747
+ // 3. For each Y in consequencesClosure(V): add U to causesClosure(Y) and Y to consequencesClosure(U)
1748
+ // Note: Self-loops (U→U) are not added to closures - if an effect appears in its own closure,
1749
+ // it means there's an indirect cycle that should be detected
1750
+ // Self-loops are explicitly ignored - an effect reading and writing the same property
1751
+ // (e.g., obj.prop++) should not create a dependency relationship or appear in closures
1752
+ if (callerRoot === targetRoot) {
1753
+ return;
1754
+ }
1755
+ const uConsequences = getOrCreateClosure(consequencesClosure, callerRoot);
1756
+ const vCauses = getOrCreateClosure(causesClosure, targetRoot);
1757
+ // 1. Add direct relationship
1758
+ uConsequences.add(targetRoot);
1759
+ vCauses.add(callerRoot);
1760
+ // 2. For each X in causesClosure(U): X→U→V means X→V
1761
+ const uCausesSet = causesClosure.get(callerRoot);
1762
+ if (uCausesSet) {
1763
+ for (const x of uCausesSet) {
1764
+ // Skip if this would create a self-loop
1765
+ if (x === targetRoot)
1766
+ continue;
1767
+ const xConsequences = getOrCreateClosure(consequencesClosure, x);
1768
+ xConsequences.add(targetRoot);
1769
+ vCauses.add(x);
1770
+ }
1771
+ }
1772
+ // 3. For each Y in consequencesClosure(V): U→V→Y means U→Y
1773
+ const vConsequencesSet = consequencesClosure.get(targetRoot);
1774
+ if (vConsequencesSet) {
1775
+ for (const y of vConsequencesSet) {
1776
+ // Skip if this would create a self-loop
1777
+ if (y === callerRoot)
1778
+ continue;
1779
+ const yCauses = getOrCreateClosure(causesClosure, y);
1780
+ yCauses.add(callerRoot);
1781
+ uConsequences.add(y);
1782
+ }
1783
+ }
1784
+ // 4. Cross-product: for each X in causesClosure(U) and Y in consequencesClosure(V): X→Y
1785
+ if (uCausesSet?.size && vConsequencesSet?.size) {
1786
+ for (const x of uCausesSet) {
1787
+ const xConsequences = getOrCreateClosure(consequencesClosure, x);
1788
+ for (const y of vConsequencesSet) {
1789
+ // Skip if this would create a self-loop
1790
+ if (x === y)
1791
+ continue;
1792
+ xConsequences.add(y);
1793
+ const yCauses = getOrCreateClosure(causesClosure, y);
1794
+ yCauses.add(x);
1795
+ }
1796
+ }
1797
+ }
1798
+ }
1799
+ /**
1800
+ * Checks if there's a path from start to end in the dependency graph, excluding a specific node
1801
+ * Uses BFS to find any path that doesn't go through the excluded node
1802
+ * @param start - Starting node
1803
+ * @param end - Target node
1804
+ * @param exclude - Node to exclude from the path
1805
+ * @returns true if a path exists without going through the excluded node
1806
+ */
1807
+ function hasPathExcluding(start, end, exclude) {
1808
+ if (start === end)
1809
+ return true;
1810
+ if (start === exclude)
1811
+ return false;
1812
+ const visited = new Set();
1813
+ const queue = [start];
1814
+ visited.add(start);
1815
+ visited.add(exclude); // Pre-mark excluded node as visited to skip it
1816
+ while (queue.length > 0) {
1817
+ const current = queue.shift();
1818
+ const triggers = effectTriggers.get(current);
1819
+ if (!triggers)
1820
+ continue;
1821
+ for (const next of triggers) {
1822
+ if (next === end)
1823
+ return true;
1824
+ if (!visited.has(next)) {
1825
+ visited.add(next);
1826
+ queue.push(next);
1827
+ }
1828
+ }
1829
+ }
1830
+ return false;
1831
+ }
1832
+ /**
1833
+ * Removes all edges involving the given effect from the dependency graph
1834
+ * Also cleans up transitive closures by propagating cleanup to all affected effects
1835
+ * Called when an effect is stopped/cleaned up
1836
+ * @param effect - The effect being cleaned up
1837
+ */
1838
+ function cleanupEffectFromGraph(effect) {
1839
+ if (options.cycleHandling === 'production')
1840
+ return;
1841
+ const root = getRoot(effect);
1842
+ // Get closures before removing direct edges (needed for propagation)
1843
+ const rootCauses = causesClosure.get(root);
1844
+ const rootConsequences = consequencesClosure.get(root);
1845
+ // Remove from effectTriggers (outgoing edges)
1846
+ const triggers = effectTriggers.get(root);
1847
+ if (triggers) {
1848
+ // Remove this root from all targets' effectTriggeredBy sets
1849
+ for (const targetRoot of triggers) {
1850
+ const triggeredBy = effectTriggeredBy.get(targetRoot);
1851
+ triggeredBy?.delete(root);
1852
+ }
1853
+ effectTriggers.delete(root);
1854
+ }
1855
+ // Remove from effectTriggeredBy (incoming edges)
1856
+ const triggeredBy = effectTriggeredBy.get(root);
1857
+ if (triggeredBy) {
1858
+ // Remove this root from all sources' effectTriggers sets
1859
+ for (const sourceRoot of triggeredBy) {
1860
+ const triggers = effectTriggers.get(sourceRoot);
1861
+ triggers?.delete(root);
1862
+ }
1863
+ effectTriggeredBy.delete(root);
1864
+ }
1865
+ // Propagate closure cleanup to all affected effects
1866
+ // When removing B from A → B → C:
1867
+ // - Remove B from causesClosure(C) and consequencesClosure(A)
1868
+ // - For each X in causesClosure(B): remove C from consequencesClosure(X) if B was the only path
1869
+ // - For each Y in consequencesClosure(B): remove A from causesClosure(Y) if B was the only path
1870
+ // - Remove transitive relationships that depended on B
1871
+ if (rootCauses) {
1872
+ // For each X that triggers root: remove root from X's consequences
1873
+ // Only remove root's consequences if no alternate path exists
1874
+ for (const causeRoot of rootCauses) {
1875
+ const causeConsequences = consequencesClosure.get(causeRoot);
1876
+ if (causeConsequences) {
1877
+ // Remove root itself (it's being cleaned up)
1878
+ causeConsequences.delete(root);
1879
+ // Only remove consequences of root if there's no alternate path from causeRoot to them
1880
+ if (rootConsequences) {
1881
+ for (const consequence of rootConsequences) {
1882
+ // Check if causeRoot can still reach consequence without going through root
1883
+ if (!hasPathExcluding(causeRoot, consequence, root)) {
1884
+ causeConsequences.delete(consequence);
1885
+ }
1886
+ }
1887
+ }
1888
+ }
1889
+ }
1890
+ }
1891
+ if (rootConsequences) {
1892
+ // For each Y that root triggers: remove root from Y's causes
1893
+ // Only remove root's causes if no alternate path exists
1894
+ for (const consequenceRoot of rootConsequences) {
1895
+ const consequenceCauses = causesClosure.get(consequenceRoot);
1896
+ if (consequenceCauses) {
1897
+ // Remove root itself (it's being cleaned up)
1898
+ consequenceCauses.delete(root);
1899
+ // Only remove causes of root if there's no alternate path from them to consequenceRoot
1900
+ if (rootCauses) {
1901
+ for (const cause of rootCauses) {
1902
+ // Check if cause can still reach consequenceRoot without going through root
1903
+ if (!hasPathExcluding(cause, consequenceRoot, root)) {
1904
+ consequenceCauses.delete(cause);
1905
+ }
1906
+ }
1907
+ }
1908
+ }
1909
+ }
1910
+ }
1911
+ // Cross-product cleanup: for each X in causesClosure(B) and Y in consequencesClosure(B),
1912
+ // remove X→Y if B was the only path connecting them
1913
+ if (rootCauses && rootConsequences) {
1914
+ for (const x of rootCauses) {
1915
+ const xConsequences = consequencesClosure.get(x);
1916
+ if (xConsequences) {
1917
+ for (const y of rootConsequences) {
1918
+ // Check if there's still a path from X to Y without going through root
1919
+ // Use BFS to find any path that doesn't include root
1920
+ if (!hasPathExcluding(x, y, root)) {
1921
+ xConsequences.delete(y);
1922
+ const yCauses = causesClosure.get(y);
1923
+ yCauses?.delete(x);
1924
+ }
1925
+ }
1926
+ }
1927
+ }
1928
+ }
1929
+ // Finally, delete the closures for this effect
1930
+ causesClosure.delete(root);
1931
+ consequencesClosure.delete(root);
1932
+ }
1933
+ // Track currently executing effects to prevent re-execution
1934
+ // These are all the effects triggered under `activeEffect`
1935
+ // Batch stack - handles nested batches by giving each its own queue
1936
+ const batchStack = [];
1937
+ function hasBatched(effect) {
1938
+ const root = getRoot(effect);
1939
+ return batchStack.some((bs) => bs.all.has(root));
1940
+ }
1941
+ // DEV: stack of currently-executing effects (push on enter, pop on leave)
1942
+ const executingStack = [];
1943
+ /**
1944
+ * Computes and caches in-degrees for all effects in the batch
1945
+ * Called once when batch starts or when new effects are added
1946
+ */
1947
+ function computeAllInDegrees(batch) {
1948
+ if (options.cycleHandling === 'production')
1949
+ return;
1950
+ const activeEffect = getActiveEffect();
1951
+ const activeRoot = activeEffect ? getRoot(activeEffect) : null;
1952
+ // Reset all in-degrees
1953
+ batch.inDegrees.clear();
1954
+ for (const [root] of batch.all) {
1955
+ let inDegree = 0;
1956
+ const causes = causesClosure.get(root);
1957
+ if (causes) {
1958
+ for (const causeRoot of causes) {
1959
+ // Only count if it's in the batch and not the active/self effect
1960
+ if (batch.all.has(causeRoot) && causeRoot !== activeRoot && causeRoot !== root) {
1961
+ inDegree++;
1962
+ }
1963
+ }
1964
+ }
1965
+ batch.inDegrees.set(root, inDegree);
1966
+ }
1967
+ }
1968
+ /**
1969
+ * Decrements in-degrees of all effects that depend on the executed effect
1970
+ * Called after an effect is executed to update the cached in-degrees
1971
+ */
1972
+ function decrementInDegreesForExecuted(batch, executedRoot) {
1973
+ // Get all effects that this executed effect triggers
1974
+ const consequences = consequencesClosure.get(executedRoot);
1975
+ if (!consequences)
1976
+ return;
1977
+ for (const consequenceRoot of consequences) {
1978
+ // Only update if it's still in the batch
1979
+ if (batch.all.has(consequenceRoot)) {
1980
+ const currentDegree = batch.inDegrees.get(consequenceRoot) ?? 0;
1981
+ if (currentDegree > 0) {
1982
+ batch.inDegrees.set(consequenceRoot, currentDegree - 1);
1983
+ }
1984
+ }
1985
+ }
1986
+ }
1987
+ /**
1988
+ * Finds a path from startRoot to endRoot in the dependency graph
1989
+ * Uses DFS to find the path through direct edges
1990
+ * @param startRoot - Starting effect root
1991
+ * @param endRoot - Target effect root
1992
+ * @param visited - Set of visited nodes (for recursion)
1993
+ * @param path - Current path being explored
1994
+ * @returns Path from startRoot to endRoot, or empty array if no path exists
1995
+ */
1996
+ function findPath(startRoot, endRoot, visited = new Set(), path = []) {
1997
+ if (startRoot === endRoot) {
1998
+ return [...path, endRoot];
1999
+ }
2000
+ if (visited.has(startRoot)) {
2001
+ return [];
2002
+ }
2003
+ visited.add(startRoot);
2004
+ const newPath = [...path, startRoot];
2005
+ const triggers = effectTriggers.get(startRoot);
2006
+ if (triggers) {
2007
+ for (const targetRoot of triggers) {
2008
+ const result = findPath(targetRoot, endRoot, visited, newPath);
2009
+ if (result.length > 0) {
2010
+ return result;
2011
+ }
2012
+ }
2013
+ }
2014
+ return [];
2015
+ }
2016
+ /**
2017
+ * Gets the cycle path when adding an edge would create a cycle
2018
+ * @param callerRoot - Root of the effect that triggers
2019
+ * @param targetRoot - Root of the effect being triggered
2020
+ * @returns Array of effect roots forming the cycle, or empty array if no cycle
2021
+ */
2022
+ function getCyclePathForEdge(callerRoot, targetRoot) {
2023
+ // Find path from targetRoot back to callerRoot (this is the existing path)
2024
+ // Then adding callerRoot -> targetRoot completes the cycle
2025
+ const path = findPath(targetRoot, callerRoot);
2026
+ if (path.length > 0) {
2027
+ // The cycle is: callerRoot -> targetRoot -> ... -> callerRoot
2028
+ return [callerRoot, ...path];
2029
+ }
2030
+ return [];
2031
+ }
2032
+ /**
2033
+ * Checks if adding an edge would create a cycle
2034
+ * Uses causesClosure to check if callerRoot is already a cause of targetRoot
2035
+ * Self-loops (callerRoot === targetRoot) are explicitly ignored and return false
2036
+ *
2037
+ * **Note**: This is the primary optimization benefit of the transitive closure system.
2038
+ * It allows detecting cycles in O(1) time before they are executed.
2039
+ *
2040
+ * @param callerRoot - Root of the effect that triggers
2041
+ * @param targetRoot - Root of the effect being triggered
2042
+ * @returns true if adding this edge would create a cycle
2043
+ */
2044
+ function wouldCreateCycle(callerRoot, targetRoot) {
2045
+ // Self-loops are explicitly ignored - an effect reading and writing the same property
2046
+ // (e.g., obj.prop++) should not create a dependency relationship
2047
+ if (callerRoot === targetRoot) {
2048
+ return false;
2049
+ }
2050
+ // Check if targetRoot already triggers callerRoot (directly or indirectly)
2051
+ // This would create a cycle: callerRoot -> targetRoot -> ... -> callerRoot
2052
+ // Using consequencesClosure: if targetRoot triggers callerRoot, then callerRoot is in consequencesClosure(targetRoot)
2053
+ const targetConsequences = consequencesClosure.get(targetRoot);
2054
+ if (targetConsequences?.has(callerRoot)) {
2055
+ return true; // Cycle detected: targetRoot -> ... -> callerRoot, and we're adding callerRoot -> targetRoot
2056
+ }
2057
+ return false;
2058
+ }
2059
+ /**
2060
+ * Adds an effect to the batch queue
2061
+ * @param effect - The effect to add
2062
+ * @param caller - The active effect that triggered this one (optional)
2063
+ * @param immediate - If true, don't create edges in the dependency graph
2064
+ */
2065
+ function addToBatch(effect, caller, immediate, reason) {
2066
+ const node = getEffectNode(effect);
2067
+ const currentBatch = batchStack[batchStack.length - 1];
2068
+ if (!currentBatch) {
2069
+ return;
2070
+ }
2071
+ const root = getRoot(effect);
2072
+ // Build reason from pending triggers if not provided
2073
+ if (!reason && node.pendingTriggers) {
2074
+ reason = { type: 'propChange', triggers: node.pendingTriggers };
2075
+ }
2076
+ node.pendingTriggers = undefined;
2077
+ if (reason) {
2078
+ const existing = node.nextReason;
2079
+ if (!existing) {
2080
+ node.nextReason = reason;
2081
+ }
2082
+ else {
2083
+ const mergePropChange = (into, from) => {
2084
+ if (into.type === 'propChange') {
2085
+ into.triggers.push(...from.triggers);
2086
+ return true;
2087
+ }
2088
+ if (into.type === 'multiple') {
2089
+ const target = into.reasons.find((r) => r.type === 'propChange');
2090
+ if (target) {
2091
+ target.triggers.push(...from.triggers);
2092
+ return true;
2093
+ }
2094
+ }
2095
+ return false;
2096
+ };
2097
+ if (reason.type === 'propChange') {
2098
+ if (!mergePropChange(existing, reason)) {
2099
+ if (existing.type === 'multiple') {
2100
+ existing.reasons.push(reason);
2101
+ }
2102
+ else {
2103
+ node.nextReason = { type: 'multiple', reasons: [existing, reason] };
2104
+ }
2105
+ }
2106
+ }
2107
+ else if (existing.type === 'multiple') {
2108
+ existing.reasons.push(reason);
2109
+ }
2110
+ else {
2111
+ node.nextReason = { type: 'multiple', reasons: [existing, reason] };
2112
+ }
2113
+ }
2114
+ }
2115
+ // 1. Add to batch first (needed for cycle detection)
2116
+ // TODO: Check if it's the correct way to do (these different behavior in function of dev/production)
2117
+ if (options.cycleHandling === 'production') {
2118
+ // Production mode: FIFO (delete and re-add to move to end)
2119
+ if (currentBatch.all.has(root)) {
2120
+ currentBatch.all.delete(root);
2121
+ }
2122
+ }
2123
+ else {
2124
+ // Dev mode: skip if already queued — the existing entry will re-run
2125
+ if (currentBatch.all.has(root)) {
2126
+ return;
2127
+ }
2128
+ }
2129
+ // If the effect was stopped during cleanup (e.g. lazy memoization), don't add it to the batch
2130
+ if (node.stopped)
2131
+ return;
2132
+ currentBatch.all.set(root, effect);
2133
+ if (caller && true && options.cycleHandling !== 'production') {
2134
+ const callerRoot = getRoot(caller);
2135
+ // const root = getRoot(effect) // Already have root
2136
+ // Check for cycle BEFORE adding edge
2137
+ if (wouldCreateCycle(callerRoot, root)) {
2138
+ const cyclePath = getCyclePathForEdge(callerRoot, root);
2139
+ const cycleMessage = cyclePath.length > 0
2140
+ ? `Cycle detected: ${cyclePath.map((r) => r.name || r.toString()).join(' → ')}`
2141
+ : `Cycle detected: ${callerRoot.name || callerRoot.toString()} → ${root.name || root.toString()} (and back)`;
2142
+ currentBatch.all.delete(root);
2143
+ const causalChain = debugHooks.getTriggerChain(effect);
2144
+ const lineage = getEffectNode(effect).creationStack;
2145
+ throw new ReactiveError(`[reactive] ${cycleMessage}`, {
2146
+ code: exports.ReactiveErrorCode.CycleDetected,
2147
+ cycle: cyclePath.map((r) => r.name || r.toString()),
2148
+ details: cycleMessage,
2149
+ causalChain,
2150
+ lineage,
2151
+ });
2152
+ }
2153
+ addGraphEdge(callerRoot, root);
2154
+ }
2155
+ }
2156
+ /**
2157
+ * Adds a cleanup function to be called when the current batch of effects completes
2158
+ * @param cleanup - The cleanup function to add
2159
+ */
2160
+ function addBatchCleanup(cleanup) {
2161
+ const currentBatch = batchStack[batchStack.length - 1];
2162
+ if (!currentBatch)
2163
+ cleanup();
2164
+ else
2165
+ currentBatch.deferreds.add(cleanup);
2166
+ }
2167
+ /**
2168
+ * Semantic alias for `addBatchCleanup` - defers work to the end of the current reactive batch.
2169
+ *
2170
+ * Use this when an effect needs to perform an action that would modify state the effect depends on,
2171
+ * which would create a reactive cycle. The deferred callback runs after all effects complete.
2172
+ *
2173
+ * @param callback - The callback to defer until after the current batch completes
2174
+ *
2175
+ * @example
2176
+ * ```typescript
2177
+ * effect(() => {
2178
+ * processData()
2179
+ *
2180
+ * // Defer to avoid cycle (createMovement modifies state this effect reads)
2181
+ * defer(() => {
2182
+ * createMovement(data)
2183
+ * })
2184
+ * })
2185
+ * ```
2186
+ */
2187
+ const defer = addBatchCleanup;
2188
+ /**
2189
+ * Gets a cycle path for debugging
2190
+ * Uses DFS to find cycles in the batch
2191
+ * @param batch - The batch queue
2192
+ * @returns Array of effect roots forming a cycle
2193
+ */
2194
+ function getCyclePath(batch) {
2195
+ // If all effects have in-degree > 0, there must be a cycle
2196
+ // Use DFS to find it
2197
+ const visited = new Set();
2198
+ const recursionStack = new Set();
2199
+ const path = [];
2200
+ for (const [root] of batch.all) {
2201
+ if (visited.has(root))
2202
+ continue;
2203
+ const cycle = findCycle(root, visited, recursionStack, path, batch);
2204
+ if (cycle.length > 0) {
2205
+ return cycle;
2206
+ }
2207
+ }
2208
+ return [];
2209
+ }
2210
+ function findCycle(root, visited, recursionStack, path, batch) {
2211
+ if (recursionStack.has(root)) {
2212
+ // Found a cycle! Return the path from the cycle start to root
2213
+ const cycleStart = path.indexOf(root);
2214
+ return path.slice(cycleStart).concat([root]);
2215
+ }
2216
+ if (visited.has(root)) {
2217
+ return [];
2218
+ }
2219
+ visited.add(root);
2220
+ recursionStack.add(root);
2221
+ path.push(root);
2222
+ // Follow edges to effects in the batch
2223
+ // Use direct edges (effectTriggers) for cycle detection
2224
+ const triggers = effectTriggers.get(root);
2225
+ if (triggers) {
2226
+ for (const targetRoot of triggers) {
2227
+ if (batch.all.has(targetRoot)) {
2228
+ const cycle = findCycle(targetRoot, visited, recursionStack, path, batch);
2229
+ if (cycle.length > 0) {
2230
+ return cycle;
2231
+ }
2232
+ }
2233
+ }
2234
+ }
2235
+ path.pop();
2236
+ recursionStack.delete(root);
2237
+ return [];
2238
+ }
2239
+ /**
2240
+ * Executes the next effect in dependency order (using cached in-degrees)
2241
+ * Finds an effect with in-degree 0 and executes it
2242
+ * @returns The return value of the executed effect, or null if batch is complete
2243
+ */
2244
+ function executeNext(effectuatedRoots) {
2245
+ const currentBatch = batchStack[batchStack.length - 1];
2246
+ if (!currentBatch)
2247
+ return null;
2248
+ // Find an effect with in-degree 0 using cached values
2249
+ let nextEffect = null;
2250
+ let nextRoot = null;
2251
+ if (options.cycleHandling === 'production') {
2252
+ // In flat mode, we just take the first effect in the queue (FIFO)
2253
+ const first = currentBatch.all.entries().next().value;
2254
+ if (first) {
2255
+ [nextRoot, nextEffect] = first;
2256
+ }
2257
+ }
2258
+ else {
2259
+ // Find an effect with in-degree 0 (no dependencies in batch that still need execution)
2260
+ // Using cached in-degrees for O(n) lookup instead of O(n²)
2261
+ for (const [root, effect] of currentBatch.all) {
2262
+ const inDegree = currentBatch.inDegrees.get(root) ?? 0;
2263
+ if (inDegree === 0) {
2264
+ nextEffect = effect;
2265
+ nextRoot = root;
2266
+ break;
2267
+ }
2268
+ }
2269
+ }
2270
+ if (!nextEffect) {
2271
+ // No effect with in-degree 0 - there must be a cycle
2272
+ // If all effects have dependencies, it means there's a circular dependency
2273
+ if (currentBatch.all.size > 0) {
2274
+ let cycle = getCyclePath(currentBatch);
2275
+ // If we couldn't find a cycle path using direct edges, try using closures
2276
+ // (transitive relationships) - if all effects have in-degree > 0, there must be a cycle
2277
+ if (cycle.length === 0) {
2278
+ // Try to find a cycle using consequencesClosure (transitive relationships)
2279
+ // Note: Self-loops are ignored - we only look for cycles between different effects
2280
+ for (const [root] of currentBatch.all) {
2281
+ const consequences = consequencesClosure.get(root);
2282
+ if (consequences) {
2283
+ // Check if any consequence in the batch also has root as a consequence
2284
+ for (const consequence of consequences) {
2285
+ // Skip self-loops - they are ignored
2286
+ if (consequence === root)
2287
+ continue;
2288
+ if (currentBatch.all.has(consequence)) {
2289
+ const consequenceConsequences = consequencesClosure.get(consequence);
2290
+ if (consequenceConsequences?.has(root)) {
2291
+ // Found cycle: root -> consequence -> root
2292
+ cycle = [root, consequence, root];
2293
+ break;
2294
+ }
2295
+ }
2296
+ }
2297
+ if (cycle.length > 0)
2298
+ break;
2299
+ }
2300
+ }
2301
+ }
2302
+ const cycleMessage = cycle.length > 0
2303
+ ? `Cycle detected: ${cycle.map((r) => r.name || '<anonymous>').join(' → ')}`
2304
+ : 'Cycle detected in effect batch - all effects have dependencies that prevent execution';
2305
+ throw new ReactiveError(`[reactive] ${cycleMessage}`, {
2306
+ code: exports.ReactiveErrorCode.CycleDetected,
2307
+ cycle: cycle.map((r) => r.name || r.toString()),
2308
+ details: cycleMessage,
2309
+ });
2310
+ }
2311
+ return null; // Batch complete
2312
+ }
2313
+ effectuatedRoots.push(getRoot(nextEffect));
2314
+ // Execute the effect
2315
+ executingStack.push(nextEffect);
2316
+ let result;
2317
+ try {
2318
+ const node = getEffectNode(nextEffect);
2319
+ const reason = node.nextReason;
2320
+ if (node.cleanup) {
2321
+ const cleanup = node.cleanup;
2322
+ node.cleanup = undefined;
2323
+ cleanup(reason);
2324
+ }
2325
+ result = nextEffect();
2326
+ }
2327
+ finally {
2328
+ executingStack.pop();
2329
+ }
2330
+ // Remove from ALL batches in the stack and update in-degrees of dependents
2331
+ for (let i = batchStack.length - 1; i >= 0; i--) {
2332
+ const batch = batchStack[i];
2333
+ if (batch.all.has(nextRoot)) {
2334
+ batch.all.delete(nextRoot);
2335
+ batch.inDegrees.delete(nextRoot);
2336
+ decrementInDegreesForExecuted(batch, nextRoot);
2337
+ }
2338
+ }
2339
+ return result;
2340
+ }
2341
+ // Track which sub-effects have been executed to prevent infinite loops
2342
+ // These are all the effects triggered under `activeEffect` and all their sub-effects
2343
+ function batch(effect, immediate) {
2344
+ if (broken) {
2345
+ throw new ReactiveError('[reactive] Reactive system is broken after an unrecoverable error. Call reset() to recover.', { code: exports.ReactiveErrorCode.BrokenEffects });
2346
+ }
2347
+ if (!Array.isArray(effect))
2348
+ effect = [effect];
2349
+ const roots = effect.map(getRoot);
2350
+ const isNewBatch = batchStack.length === 0;
2351
+ if (isNewBatch) {
2352
+ if (!activationRegistry)
2353
+ activationRegistry = new Map();
2354
+ else
2355
+ throw new Error('Activation registry already exists');
2356
+ optionCall('beginChain', roots);
2357
+ }
2358
+ const caller = getActiveEffect();
2359
+ // Optimization: If nested and NOT immediate, just join the existing batch
2360
+ if (!isNewBatch && !immediate) {
2361
+ for (let i = 0; i < effect.length; i++) {
2362
+ addToBatch(effect[i], caller);
2363
+ }
2364
+ return;
2365
+ }
2366
+ const currentBatch = {
2367
+ all: new Map(),
2368
+ inDegrees: new Map(),
2369
+ deferreds: new Set(),
2370
+ };
2371
+ batchStack.push(currentBatch);
2372
+ let success = false;
2373
+ try {
2374
+ const effectuatedRoots = [];
2375
+ const firstReturn = {};
2376
+ if (immediate) {
2377
+ // Execute initial effects in providing order
2378
+ for (let i = 0; i < effect.length; i++) {
2379
+ executingStack.push(effect[i]);
2380
+ try {
2381
+ const node = getEffectNode(effect[i]);
2382
+ const reason = node.nextReason;
2383
+ if (node.cleanup) {
2384
+ const cleanup = node.cleanup;
2385
+ node.cleanup = undefined;
2386
+ cleanup(reason);
2387
+ }
2388
+ const rv = effect[i]();
2389
+ if (rv !== undefined && !('value' in firstReturn))
2390
+ firstReturn.value = rv;
2391
+ }
2392
+ finally {
2393
+ executingStack.pop();
2394
+ currentBatch.all.delete(getRoot(effect[i]));
2395
+ }
2396
+ }
2397
+ }
2398
+ else {
2399
+ // Add initial effects to batch and compute dependencies
2400
+ for (let i = 0; i < effect.length; i++) {
2401
+ addToBatch(effect[i], caller, false);
2402
+ }
2403
+ computeAllInDegrees(currentBatch);
2404
+ }
2405
+ // Process the current batch queue
2406
+ while (currentBatch.all.size > 0 || currentBatch.deferreds.size > 0) {
2407
+ if (currentBatch.all.size > 0) {
2408
+ if (effectuatedRoots.length > options.maxEffectChain) {
2409
+ const cycle = findCycleInChain(effectuatedRoots);
2410
+ const trace = formatRoots(effectuatedRoots);
2411
+ const message = cycle
2412
+ ? `Max effect chain reached (cycle detected: ${formatRoots(cycle)})`
2413
+ : `Max effect chain reached (trace: ${trace})`;
2414
+ const queuedRoots = Array.from(currentBatch.all.keys());
2415
+ const queued = queuedRoots.map((r) => r.name || '<anonymous>');
2416
+ const debugInfo = {
2417
+ code: exports.ReactiveErrorCode.MaxDepthExceeded,
2418
+ effectuatedRoots,
2419
+ cycle,
2420
+ trace,
2421
+ maxEffectChain: options.maxEffectChain,
2422
+ queued: queued.slice(0, 50),
2423
+ queuedCount: queued.length,
2424
+ causalChain: effectuatedRoots.length > 0
2425
+ ? debugHooks.getTriggerChain(currentBatch.all.get(effectuatedRoots[effectuatedRoots.length - 1]))
2426
+ : [],
2427
+ };
2428
+ switch (options.maxEffectReaction) {
2429
+ case 'throw':
2430
+ throw new ReactiveError(`[reactive] ${message}`, debugInfo);
2431
+ case 'debug':
2432
+ // biome-ignore lint/suspicious/noDebugger: This is the whole point here
2433
+ debugger;
2434
+ throw new ReactiveError(`[reactive] ${message}`, debugInfo);
2435
+ case 'warn':
2436
+ options.warn(`[reactive] ${message} (queued: ${queued.slice(0, 10).join(', ')}${queued.length > 10 ? ', …' : ''})`);
2437
+ break;
2438
+ }
2439
+ }
2440
+ const rv = executeNext(effectuatedRoots);
2441
+ if (rv !== undefined && !('value' in firstReturn))
2442
+ firstReturn.value = rv;
2443
+ }
2444
+ else {
2445
+ // Process deferreds for this batch.
2446
+ const deferreds = Array.from(currentBatch.deferreds);
2447
+ currentBatch.deferreds.clear();
2448
+ for (const deferred of deferreds)
2449
+ deferred();
2450
+ }
2451
+ }
2452
+ success = true;
2453
+ return firstReturn.value;
2454
+ }
2455
+ finally {
2456
+ if (!success && batchStack.length === 1) {
2457
+ broken = true;
2458
+ }
2459
+ batchStack.pop();
2460
+ if (batchStack.length === 0) {
2461
+ activationRegistry = undefined;
2462
+ optionCall('endChain');
2463
+ }
2464
+ }
2465
+ }
2466
+ /**
2467
+ * Resets the reactive system to a consistent state.
2468
+ * Call this after an unrecoverable error has set the system to "broken".
2469
+ * This clears all batch state, effect dependency graphs, and watcher registrations.
2470
+ * All existing effects become orphaned and must be recreated.
2471
+ */
2472
+ function reset() {
2473
+ broken = false;
2474
+ activationRegistry = undefined;
2475
+ batchStack.length = 0;
2476
+ effectTriggers = new WeakMap();
2477
+ effectTriggeredBy = new WeakMap();
2478
+ causesClosure = new WeakMap();
2479
+ consequencesClosure = new WeakMap();
2480
+ resetRegistry();
2481
+ resetTracking();
2482
+ effectHistory.present.active = undefined;
2483
+ }
2484
+ // Inject batch function to allow atomic game loops in requestAnimationFrame/setTimeout/...
2485
+ // Note: Automatic batching of async callbacks (setTimeout, Promise.then, etc.) is NOT implemented.
2486
+ // Rationale: (1) asyncHooks.addHook API doesn't support knowing when callbacks complete (needed for batching),
2487
+ // (2) hooking all callback-creating functions adds overhead without guaranteed benefit,
2488
+ // (3) incomplete coverage in Node (async_hooks misses user-land patterns).
2489
+ // Solution: Use explicit @atomic decorator or manual batch() calls where optimization is needed.
2490
+ /**
2491
+ * Decorator that makes methods atomic - batches all effects triggered within the method
2492
+ */
2493
+ const atomic = decorator({
2494
+ method(original) {
2495
+ return function (...args) {
2496
+ const atomicEffect = () => original.apply(this, args);
2497
+ // Debug: helpful to have a name
2498
+ Object.defineProperty(atomicEffect, 'name', { value: `atomic(${original.name})` });
2499
+ return batch(atomicEffect, 'immediate');
2500
+ };
2501
+ },
2502
+ default(original) {
2503
+ return function (...args) {
2504
+ const atomicEffect = () => original.apply(this, args);
2505
+ // Debug: helpful to have a name
2506
+ Object.defineProperty(atomicEffect, 'name', { value: `atomic(${original.name})` });
2507
+ return batch(atomicEffect, 'immediate');
2508
+ };
2509
+ },
2510
+ });
2511
+ /**
2512
+ * Runs `fn` atomically and **always immediately**, batching all reactive effects
2513
+ * triggered inside it so they fire only once after `fn` completes.
2514
+ *
2515
+ * Unlike `atomic(fn)` which **wraps** a function for later invocation,
2516
+ * `atom(fn)` **executes** the function right away.
2517
+ *
2518
+ * @example
2519
+ * ```ts
2520
+ * const state = reactive({ a: 0, b: 0 })
2521
+ * effect(() => console.log(state.a, state.b)) // logs once after atom completes
2522
+ *
2523
+ * atom(() => {
2524
+ * state.a = 1
2525
+ * state.b = 2
2526
+ * })
2527
+ * ```
2528
+ */
2529
+ function atom(fn) {
2530
+ return batch(fn, 'immediate');
2531
+ }
2532
+ const fr = new FinalizationRegistry((f) => f());
2533
+ /**
2534
+ * @param fn - The effect function to run - provides the cleaner
2535
+ * @returns The cleanup function
2536
+ */
2537
+ /**
2538
+ * Creates a reactive effect that automatically re-runs when dependencies change
2539
+ * @param fn - The effect function that provides dependencies and may return a cleanup function or Promise
2540
+ * @param options - Options for effect execution
2541
+ * @returns A cleanup function to stop the effect
2542
+ */
2543
+ const effect = named(effectMarker.leave, flavored(function effect(
2544
+ // biome-ignore lint/suspicious/noConfusingVoidType: Effect callbacks commonly return void
2545
+ fn, effectOptions = {}) {
2546
+ if (effectOptions?.name)
2547
+ Object.defineProperty(fn, 'name', { value: effectOptions.name });
2548
+ // Use per-effect asyncMode or fall back to global option
2549
+ const asyncMode = effectOptions?.asyncMode ?? options.asyncMode ?? 'cancel';
2550
+ // Create the effect function - naming it for debug
2551
+ const runEffect = () => {
2552
+ const node = getEffectNode(runEffect);
2553
+ // Clear previous dependencies
2554
+ if (node.cleanup) {
2555
+ const prevCleanup = node.cleanup;
2556
+ node.cleanup = undefined;
2557
+ try {
2558
+ untracked(() => prevCleanup(node.nextReason || { type: 'stopped' }));
2559
+ }
2560
+ catch (error) {
2561
+ // If we want to report them, we could use options.warn or similar
2562
+ options.warn('Error during effect cleanup', error);
2563
+ }
2564
+ }
2565
+ // Handle async modes when effect is retriggered
2566
+ if (runningPromise) {
2567
+ if (asyncMode === 'cancel' && cancelPrevious) {
2568
+ // Cancel previous execution
2569
+ cancelPrevious();
2570
+ cancelPrevious = null;
2571
+ runningPromise = null;
2572
+ }
2573
+ else if (asyncMode === 'ignore') {
2574
+ // Ignore new execution while async work is running
2575
+ return;
2576
+ }
2577
+ // Note: 'queue' mode not yet implemented
2578
+ }
2579
+ // The effect has been stopped after having been planned
2580
+ if (effectStopped)
2581
+ return;
2582
+ let reactionCleanup;
2583
+ function cleanupReaction(reason) {
2584
+ const toCleanup = reactionCleanup;
2585
+ reactionCleanup = undefined;
2586
+ toCleanup?.(reason);
2587
+ }
2588
+ // Set reaction reason for the upcoming run
2589
+ access.reaction = node.nextReason || access.reaction;
2590
+ node.nextReason = undefined;
2591
+ optionCall('enter', getRoot(fn));
2592
+ let result;
2593
+ let caught = 0;
2594
+ // Default thrower (self)
2595
+ let thrower = (error) => {
2596
+ throw error;
2597
+ };
2598
+ node.forwardThrow = thrower;
2599
+ let errorToThrow;
2600
+ try {
2601
+ result = tracked(named(effectMarker.enter, () => fn.call(null, access)));
2602
+ access.reaction = true;
2603
+ optionCall('leave', fn);
2604
+ if (result &&
2605
+ typeof result !== 'function' &&
2606
+ (typeof result !== 'object' || !('then' in result)))
2607
+ throw new ReactiveError(`[reactive] Effect returned a non-function value: ${result}`);
2608
+ // Check if result is a Promise (async effect)
2609
+ if (result && typeof result === 'object' && typeof result.then === 'function') {
2610
+ const originalPromise = result;
2611
+ // Create a cancellation promise that we can reject
2612
+ let cancelReject = null;
2613
+ const cancelPromise = new Promise((_, reject) => {
2614
+ cancelReject = reject;
2615
+ });
2616
+ const cancelError = new ReactiveError('[reactive] Effect canceled due to dependency change');
2617
+ // Race between the actual promise and cancellation
2618
+ // If canceled, the race rejects, which will propagate through any promise chain
2619
+ runningPromise = Promise.race([originalPromise, cancelPromise]);
2620
+ // Store the cancellation function
2621
+ cancelPrevious = () => {
2622
+ if (cancelReject) {
2623
+ cancelReject(cancelError);
2624
+ }
2625
+ };
2626
+ // Wrap the original promise chain so cancellation propagates
2627
+ // This ensures that when we cancel, the original promise's .catch() handlers are triggered
2628
+ // We do this by rejecting the race promise, which makes the original promise chain see the rejection
2629
+ // through the zone-wrapped .then()/.catch() handlers
2630
+ runningPromise = runningPromise.catch((error) => {
2631
+ // Propagate async errors to the effect's error handler
2632
+ // This ensures onEffectThrow handlers are triggered for async errors
2633
+ if (error !== cancelError) {
2634
+ thrower(error);
2635
+ }
2636
+ // If thrower didn't throw (handled), we absorb the error.
2637
+ // If thrower threw (unhandled), it propagates as a new unhandled rejection, which is correct.
2638
+ });
2639
+ }
2640
+ else {
2641
+ // Synchronous result - treat as cleanup function
2642
+ reactionCleanup = result;
2643
+ }
2644
+ }
2645
+ catch (error) {
2646
+ // catcher:self`
2647
+ errorToThrow = error;
2648
+ }
2649
+ // Create cleanup function for next run
2650
+ node.cleanup = (reason) => {
2651
+ node.cleanup = undefined;
2652
+ cleanupReaction(reason);
2653
+ delete node.catchers;
2654
+ // Remove this effect from all reactive objects it's watching
2655
+ const effectObjects = exports.effectToReactiveObjects.get(runEffect);
2656
+ if (effectObjects) {
2657
+ for (const reactiveObj of effectObjects) {
2658
+ const objectWatchers = exports.watchers.get(reactiveObj);
2659
+ if (objectWatchers) {
2660
+ for (const [prop, deps] of objectWatchers.entries()) {
2661
+ deps.delete(runEffect);
2662
+ if (deps.size === 0) {
2663
+ objectWatchers.delete(prop);
2664
+ }
2665
+ }
2666
+ if (objectWatchers.size === 0) {
2667
+ exports.watchers.delete(reactiveObj);
2668
+ }
2669
+ }
2670
+ }
2671
+ exports.effectToReactiveObjects.delete(runEffect);
2672
+ }
2673
+ // Invoke all child stops (recursive via subEffectCleanup calling its own mainCleanup)
2674
+ const children = node.children;
2675
+ if (children) {
2676
+ const childReason = reason
2677
+ ? reason.type === 'lineage'
2678
+ ? reason
2679
+ : { type: 'lineage', parent: reason }
2680
+ : { type: 'stopped' };
2681
+ for (const childCleanup of children)
2682
+ childCleanup(childReason);
2683
+ delete node.children;
2684
+ }
2685
+ };
2686
+ // Define bubbling thrower
2687
+ thrower = (error) => {
2688
+ const catches = node.catchers;
2689
+ const reason = { type: 'error', error };
2690
+ if (catches)
2691
+ while (caught < catches.length) {
2692
+ cleanupReaction(reason);
2693
+ try {
2694
+ reactionCleanup = catches[caught](error);
2695
+ return;
2696
+ }
2697
+ catch (e) {
2698
+ caught++;
2699
+ }
2700
+ }
2701
+ if (parent) {
2702
+ const parentNode = getEffectNode(parent);
2703
+ if (parentNode.forwardThrow)
2704
+ parentNode.forwardThrow(error);
2705
+ else
2706
+ throw error;
2707
+ }
2708
+ else
2709
+ throw error;
2710
+ };
2711
+ // Update the node's forwardThrow to the bubbling one
2712
+ node.forwardThrow = thrower;
2713
+ if (errorToThrow)
2714
+ thrower(errorToThrow);
2715
+ };
2716
+ // Initialize metadata node
2717
+ const node = getEffectNode(runEffect);
2718
+ if (debugHooks.isDevtoolsEnabled()) {
2719
+ const stack = debugHooks.captureStack(); // Robustly skips internal mutts frames
2720
+ if (Array.isArray(stack) && stack.length > 0) {
2721
+ node.creationStack = stack;
2722
+ }
2723
+ }
2724
+ const tracked = effectHistory.present.with(runEffect, () => named(effectMarker.leave, effectAggregator.zoned));
2725
+ const ascended = named(effectMarker.leave, effectHistory.zoned);
2726
+ const parent = effectHistory.present.active;
2727
+ // Set parent relationship in node
2728
+ node.parent = parent;
2729
+ // let thrower: CatchFunction | undefined // Moved inside runEffect
2730
+ let effectStopped = false;
2731
+ const access = {
2732
+ tracked,
2733
+ ascend: named(effectMarker.leave, (fn) => ascended(named(effectMarker.enter, () => fn.call(null)))),
2734
+ //named(effectMarker.enter, (fn) => ascended(fn)),
2735
+ reaction: false,
2736
+ };
2737
+ let runningPromise = null;
2738
+ let cancelPrevious = null;
2739
+ if (effectOptions?.dependencyHook) {
2740
+ node.dependencyHook = effectOptions.dependencyHook;
2741
+ }
2742
+ // Mark the runEffect callback with the original function as its root
2743
+ markWithRoot(runEffect, fn);
2744
+ function augmentedRv(rv) {
2745
+ return Object.defineProperties(rv, {
2746
+ [stopped]: {
2747
+ get: () => effectStopped,
2748
+ },
2749
+ });
2750
+ }
2751
+ // Register strict mode if enabled
2752
+ if (effectOptions?.opaque) {
2753
+ node.isOpaque = true;
2754
+ }
2755
+ if (debugHooks.isDevtoolsEnabled()) {
2756
+ debugHooks.registerEffect(runEffect);
2757
+ }
2758
+ // Store parent relationship for hierarchy traversal - ALREADY DONE ABOVE via getEffectNode
2759
+ batch(runEffect, 'immediate');
2760
+ // Only ROOT effects are registered for GC cleanup and zone tracking
2761
+ const isRootEffect = !parent;
2762
+ const stopEffect = (reason) => {
2763
+ if (effectStopped)
2764
+ return;
2765
+ effectStopped = true;
2766
+ node.stopped = true;
2767
+ // Cancel any running async work
2768
+ if (cancelPrevious) {
2769
+ cancelPrevious();
2770
+ cancelPrevious = null;
2771
+ runningPromise = null;
2772
+ }
2773
+ try {
2774
+ node.cleanup?.(reason || { type: 'stopped' });
2775
+ }
2776
+ catch (error) {
2777
+ // Cleanup errors should basically be ignored or at least not stop the world
2778
+ // If we want to report them, we could use options.warn or similar
2779
+ options.warn('Error during effect cleanup', error);
2780
+ }
2781
+ // Clean up dependency graph edges
2782
+ cleanupEffectFromGraph(runEffect);
2783
+ fr.unregister(stopEffect);
2784
+ };
2785
+ if (isRootEffect) {
2786
+ const callIfCollected = augmentedRv((reason) => stopEffect(reason));
2787
+ fr.register(callIfCollected, () => {
2788
+ stopEffect({ type: 'gc' });
2789
+ optionCall('garbageCollected', fn);
2790
+ }, stopEffect);
2791
+ return callIfCollected;
2792
+ }
2793
+ // Register this effect to be stopped when the parent effect is cleaned up
2794
+ if (parent) {
2795
+ const parentNode = getEffectNode(parent);
2796
+ if (!parentNode.children) {
2797
+ parentNode.children = new Set();
2798
+ }
2799
+ const children = parentNode.children;
2800
+ const subEffectCleanup = augmentedRv((reason) => {
2801
+ children.delete(subEffectCleanup);
2802
+ // Execute this child effect cleanup (which triggers its own mainCleanup)
2803
+ stopEffect(reason);
2804
+ });
2805
+ children.add(subEffectCleanup);
2806
+ return subEffectCleanup;
2807
+ }
2808
+ // Should not be reachable given isRootEffect check, but for type safety
2809
+ return augmentedRv((reason) => stopEffect(reason));
2810
+ }, {
2811
+ get opaque() {
2812
+ return flavorOptions(this, { opaque: true }, { name: 'opaque' });
2813
+ },
2814
+ named(name) {
2815
+ return flavorOptions(this, { name }, { name: 'named' });
2816
+ },
2817
+ }));
2818
+ /**
2819
+ * Executes a function without tracking dependencies but maintains parent cleanup relationship
2820
+ * Effects created inside will still be cleaned up when the parent effect is destroyed
2821
+ * @param fn - The function to execute
2822
+ */
2823
+ function untracked(fn) {
2824
+ return effectHistory.present.root(fn);
2825
+ }
2826
+ /**
2827
+ * Executes a function from a virgin/root context - no parent effect, no tracking
2828
+ * Creates completely independent effects that won't be cleaned up by any parent
2829
+ * @param fn - The function to execute
2830
+ */
2831
+ function root(fn) {
2832
+ return effectHistory.root(fn);
2833
+ }
2834
+ function biDi(received, get, set) {
2835
+ if (typeof get !== 'function') {
2836
+ set = get.set;
2837
+ get = get.get;
2838
+ }
2839
+ let programmaticallySetValue = Symbol();
2840
+ effect(markWithRoot(() => {
2841
+ const newValue = get();
2842
+ if (unwrap(newValue) !== programmaticallySetValue)
2843
+ received(newValue);
2844
+ }, received));
2845
+ return set
2846
+ ? atomic((value) => {
2847
+ programmaticallySetValue = unwrap(value);
2848
+ set(value);
2849
+ })
2850
+ : () => { };
2851
+ }
2852
+
2853
+ // Track which objects contain which other objects (back-references)
2854
+ const objectParents = new WeakMap();
2855
+ // Track which objects have deep watchers
2856
+ const objectsWithDeepWatchers = new WeakSet();
2857
+ let deepWatcherCount = 0;
2858
+ function registerDeepWatcher() {
2859
+ deepWatcherCount++;
2860
+ }
2861
+ // Track deep watchers per object
2862
+ const deepWatchers = new WeakMap();
2863
+ // Track which effects are doing deep watching
2864
+ const effectToDeepWatchedObjects = new WeakMap();
2865
+ /**
2866
+ * Add a back-reference from child to parent
2867
+ */
2868
+ function addBackReference(child, parent, prop) {
2869
+ let parents = objectParents.get(child);
2870
+ if (!parents) {
2871
+ parents = new Set();
2872
+ objectParents.set(child, parents);
2873
+ }
2874
+ parents.add({ parent, prop });
2875
+ }
2876
+ /**
2877
+ * Remove a back-reference from child to parent
2878
+ */
2879
+ function removeBackReference(child, parent, prop) {
2880
+ const parents = objectParents.get(child);
2881
+ if (parents) {
2882
+ for (const entry of parents) {
2883
+ if (entry.parent === parent && entry.prop === prop) {
2884
+ parents.delete(entry);
2885
+ break;
2886
+ }
2887
+ }
2888
+ if (parents.size === 0) {
2889
+ objectParents.delete(child);
2890
+ }
2891
+ }
2892
+ }
2893
+ /**
2894
+ * Check if an object needs back-references (has deep watchers or parents with deep watchers)
2895
+ */
2896
+ function needsBackReferences(obj) {
2897
+ // Fast path: if no deep watchers exist anywhere, skip entirely
2898
+ if (!deepWatcherCount)
2899
+ return false; // fast path: no deep watchers anywhere
2900
+ // Check if object itself has deep watchers
2901
+ if (objectsWithDeepWatchers.has(obj))
2902
+ return true;
2903
+ // Slow path: check if any parent has deep watchers (recursive)
2904
+ return hasParentWithDeepWatchers(obj);
2905
+ }
2906
+ /**
2907
+ * Bubble up changes through the back-reference chain
2908
+ */
2909
+ function bubbleUpChange(changedObject, evolution) {
2910
+ const parents = objectParents.get(changedObject);
2911
+ if (!parents)
2912
+ return;
2913
+ for (const { parent, prop } of parents) {
2914
+ // Trigger deep watchers on parent
2915
+ const parentDeepWatchers = deepWatchers.get(parent);
2916
+ if (parentDeepWatchers) {
2917
+ if (options.introspection?.gatherReasons) {
2918
+ const gatherReasons = options.introspection.gatherReasons;
2919
+ const lineageConfig = gatherReasons.lineages;
2920
+ let touchStack;
2921
+ if (lineageConfig === 'touch' || lineageConfig === 'both') {
2922
+ touchStack = debugHooks.captureLineage();
2923
+ }
2924
+ for (const watcher of parentDeepWatchers) {
2925
+ const dependencyStack = lineageConfig === 'dependency' || lineageConfig === 'both'
2926
+ ? getDependencyStack(watcher, parent, allProps)
2927
+ : undefined;
2928
+ const node = getEffectNode(watcher);
2929
+ if (!node.pendingTriggers)
2930
+ node.pendingTriggers = [];
2931
+ node.pendingTriggers.push({
2932
+ obj: parent,
2933
+ evolution,
2934
+ dependency: dependencyStack,
2935
+ touch: touchStack,
2936
+ });
2937
+ }
2938
+ }
2939
+ for (const watcher of parentDeepWatchers)
2940
+ batch(watcher);
2941
+ }
2942
+ // Continue bubbling up
2943
+ bubbleUpChange(parent, evolution);
2944
+ }
2945
+ }
2946
+ function hasParentWithDeepWatchers(obj) {
2947
+ const parents = objectParents.get(obj);
2948
+ if (!parents)
2949
+ return false;
2950
+ for (const { parent } of parents) {
2951
+ if (objectsWithDeepWatchers.has(parent))
2952
+ return true;
2953
+ if (hasParentWithDeepWatchers(parent))
2954
+ return true;
2955
+ }
2956
+ return false;
2957
+ }
2958
+
2959
+ const states = new WeakMap();
2960
+ function addState(obj, evolution) {
2961
+ obj = unwrap(obj);
2962
+ const next = {};
2963
+ const state = getState(obj);
2964
+ if (state)
2965
+ Object.assign(state, { evolution, next });
2966
+ states.set(obj, next);
2967
+ }
2968
+ /**
2969
+ * Gets the current state of a reactive object for evolution tracking
2970
+ * @param obj - The reactive object
2971
+ * @returns The current state object
2972
+ */
2973
+ function getState(obj) {
2974
+ obj = unwrap(obj);
2975
+ let state = states.get(obj);
2976
+ if (!state) {
2977
+ state = {};
2978
+ states.set(obj, state);
2979
+ }
2980
+ return state;
2981
+ }
2982
+ function collectEffects(obj, evolution, effects, objectWatchers, ...keyChains) {
2983
+ const sourceEffect = getActiveEffect();
2984
+ for (const keys of keyChains)
2985
+ for (const key of keys) {
2986
+ const deps = objectWatchers.get(key);
2987
+ if (deps) {
2988
+ for (const effect of deps) {
2989
+ const runningChain = isRunning(effect);
2990
+ if (runningChain) {
2991
+ optionCall('skipRunningEffect', effect);
2992
+ continue;
2993
+ }
2994
+ if (!effects.has(effect)) {
2995
+ effects.set(effect, getDependencyStack(effect, obj, key));
2996
+ if (!hasBatched(effect))
2997
+ recordActivation(effect, obj, evolution, key);
2998
+ }
2999
+ debugHooks.recordTriggerLink(sourceEffect, effect, obj, key, evolution);
3000
+ }
3001
+ }
3002
+ }
3003
+ }
3004
+ /**
3005
+ * Triggers effects for a single property change
3006
+ * @param obj - The object that changed
3007
+ * @param evolution - The type of change
3008
+ * @param prop - The property that changed
3009
+ */
3010
+ function touched1(obj, evolution, prop) {
3011
+ touched(obj, evolution, [prop]);
3012
+ }
3013
+ /**
3014
+ * Triggers effects for property changes
3015
+ * @param obj - The object that changed
3016
+ * @param evolution - The type of change
3017
+ * @param props - The properties that changed
3018
+ */
3019
+ function touched(obj, evolution, props) {
3020
+ obj = unwrap(obj);
3021
+ addState(obj, evolution);
3022
+ const objectWatchers = exports.watchers.get(obj);
3023
+ if (objectWatchers) {
3024
+ // Note: we have to collect effects to remove duplicates in the specific case when no batch is running
3025
+ const effects = new Map();
3026
+ const structural = !['set', 'invalidate'].includes(evolution.type);
3027
+ const broad = structural ? [allProps, keysOf] : [allProps];
3028
+ if (props)
3029
+ collectEffects(obj, evolution, effects, objectWatchers, broad, props);
3030
+ else
3031
+ collectEffects(obj, evolution, effects, objectWatchers, objectWatchers.keys());
3032
+ const triggers = Array.from(effects.keys());
3033
+ optionCall('touched', obj, evolution, props, triggers);
3034
+ // Store pending triggers for CleanupReason before batching
3035
+ if (options.introspection?.gatherReasons) {
3036
+ const gatherReasons = options.introspection.gatherReasons;
3037
+ const lineageConfig = gatherReasons.lineages;
3038
+ let touchStack;
3039
+ if (lineageConfig === 'touch' || lineageConfig === 'both') {
3040
+ touchStack = debugHooks.captureLineage();
3041
+ }
3042
+ for (const [effect, dependencyStack] of effects) {
3043
+ const node = getEffectNode(effect);
3044
+ if (!node.pendingTriggers)
3045
+ node.pendingTriggers = [];
3046
+ node.pendingTriggers.push({
3047
+ obj,
3048
+ evolution,
3049
+ dependency: dependencyStack,
3050
+ touch: touchStack,
3051
+ });
3052
+ }
3053
+ }
3054
+ batch(triggers);
3055
+ }
3056
+ // Bubble up changes if this object has deep watchers
3057
+ if (objectsWithDeepWatchers.has(obj)) {
3058
+ bubbleUpChange(obj, evolution);
3059
+ }
3060
+ }
3061
+ /**
3062
+ * Triggers only opaque effects for property changes
3063
+ * Used by deep-touch to ensure opaque listeners are notified even when deep optimization is active
3064
+ */
3065
+ function touchedOpaque(obj, evolution, prop) {
3066
+ obj = unwrap(obj);
3067
+ const objectWatchers = exports.watchers.get(obj);
3068
+ if (!objectWatchers)
3069
+ return;
3070
+ const deps = objectWatchers.get(prop);
3071
+ if (!deps)
3072
+ return;
3073
+ const effects = new Set();
3074
+ const sourceEffect = getActiveEffect();
3075
+ const gather = options.introspection?.gatherReasons;
3076
+ if (gather) {
3077
+ const lineageConfig = gather.lineages;
3078
+ for (const effect of deps) {
3079
+ const node = getEffectNode(effect);
3080
+ if (!node.isOpaque)
3081
+ continue;
3082
+ const runningChain = isRunning(effect);
3083
+ if (runningChain) {
3084
+ optionCall('skipRunningEffect', effect);
3085
+ continue;
3086
+ }
3087
+ effects.add(effect);
3088
+ if (gather) {
3089
+ let touchStack;
3090
+ let dependencyStack;
3091
+ if (lineageConfig === 'touch' || lineageConfig === 'both') {
3092
+ touchStack = debugHooks.captureLineage();
3093
+ }
3094
+ if (lineageConfig === 'dependency' || lineageConfig === 'both') {
3095
+ dependencyStack = getDependencyStack(effect, obj, prop);
3096
+ }
3097
+ if (!node.pendingTriggers)
3098
+ node.pendingTriggers = [];
3099
+ node.pendingTriggers.push({
3100
+ obj,
3101
+ evolution,
3102
+ dependency: dependencyStack,
3103
+ touch: touchStack,
3104
+ });
3105
+ }
3106
+ recordActivation(effect, obj, evolution, prop);
3107
+ debugHooks.recordTriggerLink(sourceEffect, effect, obj, prop, evolution);
3108
+ }
3109
+ }
3110
+ else {
3111
+ // When not gathering reasons, process effects normally
3112
+ for (const effect of deps) {
3113
+ const node = getEffectNode(effect);
3114
+ if (!node.isOpaque)
3115
+ continue;
3116
+ const runningChain = isRunning(effect);
3117
+ if (runningChain) {
3118
+ optionCall('skipRunningEffect', effect);
3119
+ continue;
3120
+ }
3121
+ effects.add(effect);
3122
+ recordActivation(effect, obj, evolution, prop);
3123
+ debugHooks.recordTriggerLink(sourceEffect, effect, obj, prop, evolution);
3124
+ }
3125
+ }
3126
+ if (effects.size > 0) {
3127
+ optionCall('touched', obj, evolution, [prop], Array.from(effects));
3128
+ batch(Array.from(effects));
3129
+ }
3130
+ }
3131
+
3132
+ const nonReactiveObjects = new WeakSet();
3133
+ const nonReactiveClasses = new WeakSet();
3134
+ const unreactiveProps = new WeakMap();
3135
+ let unreactivePropsCount = 0;
3136
+ function addUnreactiveProps(proto, set) {
3137
+ unreactiveProps.set(proto, set);
3138
+ unreactivePropsCount++;
3139
+ }
3140
+ /** Check if a property is marked unreactive on obj or any of its prototypes (trap-free) */
3141
+ function isUnreactiveProp(obj, prop) {
3142
+ if (!unreactivePropsCount)
3143
+ return false;
3144
+ let target = obj;
3145
+ while (target) {
3146
+ if (unreactiveProps.get(target)?.has(prop))
3147
+ return true;
3148
+ target = Object.getPrototypeOf(target);
3149
+ }
3150
+ return false;
3151
+ }
3152
+ const immutables = new Set();
3153
+ const absent = Symbol('absent');
3154
+ function markNonReactive(...obj) {
3155
+ for (const o of obj)
3156
+ nonReactiveObjects.add(o);
3157
+ return obj[0];
3158
+ }
3159
+ function nonReactiveClass(...cls) {
3160
+ for (const c of cls)
3161
+ if (c)
3162
+ nonReactiveClasses.add(c.prototype);
3163
+ return cls[0];
3164
+ }
3165
+ function isNonReactive(obj) {
3166
+ if (obj === null || typeof obj !== 'object')
3167
+ return true;
3168
+ if (nonReactiveObjects.has(obj))
3169
+ return true;
3170
+ // Walk the prototype chain on the raw object to check for non-reactive classes
3171
+ let proto = Object.getPrototypeOf(obj);
3172
+ while (proto) {
3173
+ if (nonReactiveClasses.has(proto))
3174
+ return true;
3175
+ proto = Object.getPrototypeOf(proto);
3176
+ }
3177
+ for (const fn of immutables)
3178
+ if (fn(obj))
3179
+ return true;
3180
+ return false;
3181
+ }
3182
+ nonReactiveClass(Date, RegExp, Error, Promise, Function);
3183
+ if (typeof window !== 'undefined') {
3184
+ markNonReactive(window, document);
3185
+ nonReactiveClass(Node, Element, HTMLElement, EventTarget, HTMLCollection, NodeList);
3186
+ }
3187
+
3188
+ function getPrototypeToken(value) {
3189
+ if (Array.isArray(value))
3190
+ return Array.prototype;
3191
+ if (typeof value !== 'object')
3192
+ return undefined;
3193
+ try {
3194
+ return value.constructor;
3195
+ }
3196
+ catch {
3197
+ return undefined;
3198
+ }
3199
+ }
3200
+ function shouldRecurseTouch(oldValue, newValue) {
3201
+ if (oldValue === newValue)
3202
+ return false;
3203
+ if ((typeof oldValue !== 'object' && !Array.isArray(oldValue)) ||
3204
+ (typeof newValue !== 'object' && !Array.isArray(newValue)))
3205
+ return false;
3206
+ if (isNonReactive(oldValue) || isNonReactive(newValue))
3207
+ return false;
3208
+ return getPrototypeToken(oldValue) === getPrototypeToken(newValue);
3209
+ }
3210
+ /**
3211
+ * Centralized function to handle property change notifications with optional recursive touch
3212
+ * @param targetObj - The object whose property changed
3213
+ * @param prop - The property that changed
3214
+ * @param oldValue - The old value (before change)
3215
+ * @param newValue - The new value (after change)
3216
+ * @param hadProperty - Whether the property existed before (for add vs set)
3217
+ */
3218
+ function notifyPropertyChange(targetObj, prop, oldValue, newValue, hadProperty) {
3219
+ const evolution = { type: hadProperty ? 'set' : 'add', prop };
3220
+ if (options.recursiveTouching &&
3221
+ oldValue !== undefined &&
3222
+ shouldRecurseTouch(oldValue, newValue)) {
3223
+ const unwrappedObj = unwrap(targetObj);
3224
+ const origin = { obj: unwrappedObj, prop };
3225
+ // Deep touch: only notify nested property changes with origin filtering
3226
+ // Don't notify direct property change - the whole point is to avoid parent effects re-running
3227
+ dispatchNotifications(untracked(() => recursiveTouch(oldValue, newValue, new WeakMap(), [], origin)));
3228
+ // Notify opaque listeners (like memoize) that always want to know about identity changes
3229
+ touchedOpaque(targetObj, evolution, prop);
3230
+ }
3231
+ else {
3232
+ touched1(targetObj, evolution, prop);
3233
+ }
3234
+ }
3235
+ function hasVisitedPair(visited, oldObj, newObj) {
3236
+ let mapped = visited.get(oldObj);
3237
+ if (!mapped) {
3238
+ mapped = new WeakSet();
3239
+ visited.set(oldObj, mapped);
3240
+ }
3241
+ if (mapped.has(newObj))
3242
+ return true;
3243
+ mapped.add(newObj);
3244
+ return false;
3245
+ }
3246
+ function collectObjectKeys(obj) {
3247
+ const keys = new Set(Reflect.ownKeys(obj));
3248
+ let proto = Object.getPrototypeOf(obj);
3249
+ // Continue walking while prototype exists and doesn't have its own constructor
3250
+ // This stops at Object.prototype (has own constructor) and class prototypes (have own constructor)
3251
+ // but continues for data prototypes (Object.create({}), Object.create(instance), etc.)
3252
+ while (proto && !Object.hasOwn(proto, 'constructor')) {
3253
+ for (const key of Reflect.ownKeys(proto))
3254
+ keys.add(key);
3255
+ proto = Object.getPrototypeOf(proto);
3256
+ }
3257
+ return keys;
3258
+ }
3259
+ function recursiveTouch(oldValue, newValue, visited = new WeakMap(), notifications = [], origin) {
3260
+ if (!shouldRecurseTouch(oldValue, newValue))
3261
+ return notifications;
3262
+ if ((typeof oldValue !== 'object' && !Array.isArray(oldValue)) ||
3263
+ (typeof newValue !== 'object' && !Array.isArray(newValue)))
3264
+ return notifications;
3265
+ if (hasVisitedPair(visited, oldValue, newValue))
3266
+ return notifications;
3267
+ if (Array.isArray(oldValue) && Array.isArray(newValue)) {
3268
+ diffArrayElements(oldValue, newValue, visited, notifications, origin);
3269
+ return notifications;
3270
+ }
3271
+ diffObjectProperties(oldValue, newValue, visited, notifications, origin);
3272
+ return notifications;
3273
+ }
3274
+ function diffArrayElements(oldArray, newArray, _visited, notifications, origin) {
3275
+ const local = [];
3276
+ const oldLength = oldArray.length;
3277
+ const newLength = newArray.length;
3278
+ const max = Math.max(oldLength, newLength);
3279
+ for (let index = 0; index < max; index++) {
3280
+ const hasOld = index < oldLength;
3281
+ const hasNew = index < newLength;
3282
+ if (hasOld && !hasNew) {
3283
+ local.push({ target: oldArray, evolution: { type: 'del', prop: index }, prop: index, origin });
3284
+ continue;
3285
+ }
3286
+ if (!hasOld && hasNew) {
3287
+ local.push({ target: oldArray, evolution: { type: 'add', prop: index }, prop: index, origin });
3288
+ continue;
3289
+ }
3290
+ if (!hasOld || !hasNew)
3291
+ continue;
3292
+ const oldEntry = unwrap(oldArray[index]);
3293
+ const newEntry = unwrap(newArray[index]);
3294
+ if (!Object.is(oldEntry, newEntry)) {
3295
+ local.push({ target: oldArray, evolution: { type: 'set', prop: index }, prop: index, origin });
3296
+ }
3297
+ }
3298
+ if (oldLength !== newLength)
3299
+ local.push({
3300
+ target: oldArray,
3301
+ evolution: { type: 'set', prop: 'length' },
3302
+ prop: 'length',
3303
+ origin,
3304
+ });
3305
+ notifications.push(...local);
3306
+ }
3307
+ function diffObjectProperties(oldObj, newObj, visited, notifications, origin) {
3308
+ const oldKeys = collectObjectKeys(oldObj);
3309
+ const newKeys = collectObjectKeys(newObj);
3310
+ const local = [];
3311
+ for (const key of oldKeys)
3312
+ if (!newKeys.has(key))
3313
+ local.push({ target: oldObj, evolution: { type: 'del', prop: key }, prop: key, origin });
3314
+ for (const key of newKeys)
3315
+ if (!oldKeys.has(key))
3316
+ local.push({ target: oldObj, evolution: { type: 'add', prop: key }, prop: key, origin });
3317
+ for (const key of newKeys) {
3318
+ if (!oldKeys.has(key))
3319
+ continue;
3320
+ const oldEntry = unwrap(oldObj[key]);
3321
+ const newEntry = unwrap(newObj[key]);
3322
+ if (shouldRecurseTouch(oldEntry, newEntry)) {
3323
+ recursiveTouch(oldEntry, newEntry, visited, notifications, origin);
3324
+ }
3325
+ else if (!Object.is(oldEntry, newEntry)) {
3326
+ local.push({ target: oldObj, evolution: { type: 'set', prop: key }, prop: key, origin });
3327
+ }
3328
+ }
3329
+ notifications.push(...local);
3330
+ }
3331
+ /**
3332
+ * Checks if an effect or any of its ancestors is in the allowed set
3333
+ */
3334
+ function hasAncestorInSet(effect, allowedSet) {
3335
+ let current = effect;
3336
+ const visited = new WeakSet();
3337
+ while (current && !visited.has(current)) {
3338
+ visited.add(current);
3339
+ if (allowedSet.has(current))
3340
+ return true;
3341
+ const node = getEffectNode(current);
3342
+ current = node.parent;
3343
+ }
3344
+ return false;
3345
+ }
3346
+ function dispatchNotifications(notifications) {
3347
+ if (!notifications.length)
3348
+ return;
3349
+ const combinedEffects = new Set();
3350
+ const effectCauses = new Map();
3351
+ // Extract origin from first notification (all should have the same origin from a single deep touch)
3352
+ const origin = notifications[0]?.origin;
3353
+ let allowedEffects;
3354
+ // If origin exists, compute allowed effects (those that depend on origin.obj[origin.prop])
3355
+ if (origin) {
3356
+ allowedEffects = new Set();
3357
+ const originWatchers = exports.watchers.get(origin.obj);
3358
+ if (originWatchers) {
3359
+ const originEffects = new Map();
3360
+ collectEffects(origin.obj, { type: 'set', prop: origin.prop }, originEffects, originWatchers, [allProps], [origin.prop]);
3361
+ for (const effect of originEffects.keys())
3362
+ allowedEffects.add(effect);
3363
+ }
3364
+ // If no allowed effects, skip all notifications (no one should be notified)
3365
+ if (allowedEffects.size === 0)
3366
+ return;
3367
+ }
3368
+ for (const notification of notifications) {
3369
+ const { target, evolution, prop } = notification;
3370
+ if (typeof target !== 'object' && !Array.isArray(target))
3371
+ continue;
3372
+ const obj = unwrap(target);
3373
+ addState(obj, evolution);
3374
+ const objectWatchers = exports.watchers.get(obj);
3375
+ let currentEffects;
3376
+ const propsArray = [prop];
3377
+ if (objectWatchers) {
3378
+ // console.log(`[DEBUG] dispatchNotifications: processing ${obj.constructor.name} (has watchers)`)
3379
+ currentEffects = new Map();
3380
+ const broad = evolution.type !== 'set' ? [allProps, keysOf] : [allProps];
3381
+ collectEffects(obj, evolution, currentEffects, objectWatchers, broad, propsArray);
3382
+ // Filter effects by ancestor chain if origin exists
3383
+ // Include effects that either directly depend on origin or have an ancestor that does
3384
+ if (origin && allowedEffects) {
3385
+ const filteredEffects = new Map();
3386
+ for (const [effect, associated] of currentEffects) {
3387
+ // Check if effect itself is allowed OR has an ancestor that is allowed
3388
+ if (allowedEffects.has(effect) || hasAncestorInSet(effect, allowedEffects)) {
3389
+ filteredEffects.set(effect, associated);
3390
+ }
3391
+ }
3392
+ currentEffects = filteredEffects;
3393
+ }
3394
+ for (const effect of currentEffects.keys()) {
3395
+ combinedEffects.add(effect);
3396
+ let causes = effectCauses.get(effect);
3397
+ if (!causes) {
3398
+ causes = [];
3399
+ effectCauses.set(effect, causes);
3400
+ }
3401
+ causes.push(notification);
3402
+ }
3403
+ }
3404
+ if (currentEffects) {
3405
+ optionCall('touched', obj, evolution, propsArray, Array.from(currentEffects.keys()));
3406
+ }
3407
+ if (objectsWithDeepWatchers.has(obj))
3408
+ bubbleUpChange(obj, evolution);
3409
+ }
3410
+ if (combinedEffects.size) {
3411
+ if (options.introspection?.gatherReasons) {
3412
+ const gatherReasons = options.introspection.gatherReasons;
3413
+ const lineageConfig = gatherReasons.lineages;
3414
+ let touchStack;
3415
+ if (lineageConfig === 'touch' || lineageConfig === 'both') {
3416
+ touchStack = debugHooks.captureLineage();
3417
+ }
3418
+ for (const effect of combinedEffects) {
3419
+ const node = getEffectNode(effect);
3420
+ if (!node.pendingTriggers)
3421
+ node.pendingTriggers = [];
3422
+ for (const { target, evolution, prop } of effectCauses.get(effect)) {
3423
+ const dependencyStack = lineageConfig === 'dependency' || lineageConfig === 'both'
3424
+ ? getDependencyStack(effect, unwrap(target), prop ?? allProps)
3425
+ : undefined;
3426
+ node.pendingTriggers.push({
3427
+ obj: unwrap(target),
3428
+ evolution,
3429
+ dependency: dependencyStack,
3430
+ touch: touchStack,
3431
+ });
3432
+ }
3433
+ }
3434
+ }
3435
+ batch([...combinedEffects]);
3436
+ }
3437
+ }
3438
+
3439
+ const metaProtos = new WeakMap();
3440
+ const wrapProtos = new WeakMap();
3441
+ const arrayLengths = new WeakMap();
3442
+ const hasReentry = new Set();
3443
+ const subsRegister = new WeakMap();
3444
+ // Sub-proxy registration for custom reactive behaviors
3445
+ const reactiveHandlers = {
3446
+ [Symbol.toStringTag]: 'MutTs Reactive',
3447
+ get(obj, prop, receiver) {
3448
+ if (obj && typeof obj === 'object' && prop !== Symbol.toStringTag) {
3449
+ const metaProto = metaProtos.get(obj.constructor);
3450
+ if (metaProto && Object.hasOwn(metaProto, prop)) {
3451
+ const desc = Object.getOwnPropertyDescriptor(metaProto, prop);
3452
+ if (desc.get) {
3453
+ if (!Object.hasOwn(obj, prop))
3454
+ return desc.get.call(obj);
3455
+ // For own properties (e.g., array length): only override if writable/configurable
3456
+ const ownDesc = Object.getOwnPropertyDescriptor(obj, prop);
3457
+ if (ownDesc.configurable || ownDesc.writable || ownDesc.get)
3458
+ return desc.get.call(obj);
3459
+ }
3460
+ else if (!Object.hasOwn(obj, prop))
3461
+ return (...args) => desc.value.apply(obj, args);
3462
+ }
3463
+ const wrapProto = wrapProtos.get(obj.constructor);
3464
+ if (wrapProto && Object.hasOwn(wrapProto, prop))
3465
+ return wrapProto[prop];
3466
+ }
3467
+ // Symbols: fast-path — no reactivity tracking, no unreactive check needed
3468
+ if (typeof prop === 'symbol')
3469
+ return FoolProof.get(obj, prop, receiver);
3470
+ // Check if this property is marked as unreactive (WeakMap lookup — no proxy traps)
3471
+ if (isUnreactiveProp(obj, prop))
3472
+ return FoolProof.get(obj, prop, receiver);
3473
+ // Check if property exists using a trap-free walk to avoid triggering
3474
+ // the has-trap cascade on prototype chains of reactive proxies.
3475
+ const isOwnProp = Object.hasOwn(obj, prop);
3476
+ let hasProp = isOwnProp;
3477
+ let owner = isOwnProp ? obj : undefined;
3478
+ if (!isOwnProp) {
3479
+ let raw = Object.getPrototypeOf(obj);
3480
+ while (raw && raw !== Object.prototype) {
3481
+ if (Object.hasOwn(raw, prop)) {
3482
+ hasProp = true;
3483
+ owner = raw;
3484
+ break;
3485
+ }
3486
+ raw = Object.getPrototypeOf(raw);
3487
+ }
3488
+ }
3489
+ const isInheritedAccess = hasProp && !isOwnProp;
3490
+ // For accessor properties, check the unwrapped object to see if it's an accessor
3491
+ // This ensures ignoreAccessors works correctly even after operations like Object.setPrototypeOf
3492
+ // Skip for null-proto objects (pounce scopes) — they never have accessors
3493
+ const shouldIgnoreAccessor = options.ignoreAccessors &&
3494
+ isOwnProp &&
3495
+ Object.getPrototypeOf(obj) !== null &&
3496
+ (isOwnAccessor(receiver, prop) || isOwnAccessor(obj, prop));
3497
+ // Depend if...
3498
+ if (!hasProp ||
3499
+ (!(options.instanceMembers && isInheritedAccess && obj instanceof Object) &&
3500
+ !shouldIgnoreAccessor))
3501
+ dependant(obj, prop);
3502
+ // Two-Point Tracking: for inherited access on null-proto chains, only track
3503
+ // the owning ancestor — not every intermediate level. This relies on the
3504
+ // "structural stability" contract: key presence in the chain is fixed at
3505
+ // creation time, so intermediate levels never gain/lose shadowing properties.
3506
+ if (isInheritedAccess && owner && (!options.instanceMembers || !(obj instanceof Object))) {
3507
+ dependant(owner, prop);
3508
+ }
3509
+ // For arrays, use FoolProof.get (Indexer path) for numeric index reactivity.
3510
+ // For all other objects, inline Reflect.get directly (skips 3 function calls).
3511
+ const value = (subsRegister.get(obj)?.get || FoolProof.get)(obj, prop, receiver);
3512
+ if (typeof value === 'object' && value !== null) {
3513
+ const reactiveValue = reactiveObject(value);
3514
+ // Only create back-references if this object needs them
3515
+ if (needsBackReferences(obj)) {
3516
+ addBackReference(reactiveValue, obj, prop);
3517
+ }
3518
+ return reactiveValue;
3519
+ }
3520
+ return value;
3521
+ },
3522
+ set(obj, prop, value, receiver) {
3523
+ const unwrappedReceiver = unwrap(receiver);
3524
+ // Check if this property is marked as unreactive
3525
+ if (isUnreactiveProp(obj, prop) || obj !== unwrappedReceiver)
3526
+ return FoolProof.set(obj, prop, value, receiver);
3527
+ const newValue = unwrap(value);
3528
+ // metaProto setter dispatch (e.g., reactive array length)
3529
+ if (obj && typeof obj === 'object' && prop !== Symbol.toStringTag) {
3530
+ const metaProto = obj.constructor && metaProtos.get(obj.constructor);
3531
+ if (metaProto && Object.hasOwn(metaProto, prop)) {
3532
+ const desc = Object.getOwnPropertyDescriptor(metaProto, prop);
3533
+ if (desc.set) {
3534
+ desc.set.call(obj, newValue);
3535
+ return true;
3536
+ }
3537
+ }
3538
+ }
3539
+ // Read old value, using withEffect(undefined, ...) for getter-only accessors to avoid
3540
+ // breaking memoization dependency tracking during SET operations
3541
+ let oldVal = absent;
3542
+ // TODO: Pffft... Find a way to "generalize" this case?
3543
+ const isArrayLength = prop === 'length' && Array.isArray(obj);
3544
+ if (Reflect.has(unwrappedReceiver, prop)) {
3545
+ // We *need* to use `receiver` and not `obj` here, otherwise we break
3546
+ // the dependency tracking for memoized getters
3547
+ oldVal = isArrayLength
3548
+ ? arrayLengths.get(obj) === newValue
3549
+ ? newValue
3550
+ : absent
3551
+ : untracked(() => Reflect.get(obj, prop, receiver));
3552
+ }
3553
+ if (objectsWithDeepWatchers.has(obj)) {
3554
+ if (typeof oldVal === 'object' && oldVal !== null) {
3555
+ removeBackReference(oldVal, obj, prop);
3556
+ }
3557
+ if (typeof newValue === 'object' && newValue !== null) {
3558
+ const reactiveValue = reactiveObject(newValue);
3559
+ addBackReference(reactiveValue, obj, prop);
3560
+ }
3561
+ }
3562
+ if (oldVal !== newValue) {
3563
+ // For getter-only accessors, Reflect.set() may fail, but we still return true
3564
+ // to avoid throwing errors. Only proceed with change notifications if set succeeded.
3565
+ if (FoolProof.set(obj, prop, newValue, receiver)) {
3566
+ if (isArrayLength)
3567
+ arrayLengths.set(obj, newValue);
3568
+ notifyPropertyChange(obj, prop, oldVal, newValue, oldVal !== absent);
3569
+ }
3570
+ }
3571
+ return true;
3572
+ },
3573
+ has(obj, prop) {
3574
+ if (hasReentry.has(obj))
3575
+ throw new ReactiveError(`[reactive] Circular dependency detected in 'has' check for property '${String(prop)}'`, {
3576
+ code: exports.ReactiveErrorCode.CycleDetected,
3577
+ cycle: [], // We don't have the full cycle here, but we know it involves obj
3578
+ });
3579
+ hasReentry.add(obj);
3580
+ dependant(obj, prop);
3581
+ const rv = (subsRegister.get(obj)?.has || Reflect.has)(obj, prop);
3582
+ hasReentry.delete(obj);
3583
+ return rv;
3584
+ },
3585
+ deleteProperty(obj, prop) {
3586
+ if (!Object.hasOwn(obj, prop))
3587
+ return false;
3588
+ const oldVal = obj[prop];
3589
+ // Remove back-references if this object has deep watchers
3590
+ if (objectsWithDeepWatchers.has(obj) && typeof oldVal === 'object' && oldVal !== null) {
3591
+ removeBackReference(oldVal, obj, prop);
3592
+ }
3593
+ delete obj[prop];
3594
+ touched1(obj, { type: 'del', prop }, prop);
3595
+ // Bubble up changes if this object has deep watchers
3596
+ if (objectsWithDeepWatchers.has(obj)) {
3597
+ bubbleUpChange(obj, { type: 'del', prop });
3598
+ }
3599
+ return true;
3600
+ },
3601
+ ownKeys(obj) {
3602
+ dependant(obj, keysOf);
3603
+ return subsRegister.get(obj)?.ownKeys?.(obj) || Reflect.ownKeys(obj);
3604
+ },
3605
+ getOwnPropertyDescriptor(obj, prop) {
3606
+ return subsRegister.get(obj)?.getOwnPropertyDescriptor?.(obj, prop) || Reflect.getOwnPropertyDescriptor(obj, prop);
3607
+ },
3608
+ };
3609
+ const reactiveClasses = new WeakSet();
3610
+ // Create the ReactiveBase mixin
3611
+ /**
3612
+ * Base mixin for reactive classes that provides proper constructor reactivity
3613
+ * Solves constructor reactivity issues in complex inheritance trees
3614
+ */
3615
+ const ReactiveBase = mixin((base) => {
3616
+ class ReactiveMixin extends base {
3617
+ constructor(...args) {
3618
+ super(...args);
3619
+ // Only apply reactive transformation if the class is marked with @reactive
3620
+ // This allows the mixin to work properly with method inheritance
3621
+ // biome-ignore lint/correctness/noConstructorReturn: This is the whole point here
3622
+ return reactiveClasses.has(new.target) ? reactive(this) : this;
3623
+ }
3624
+ }
3625
+ return ReactiveMixin;
3626
+ });
3627
+ function reactiveObject(anyTarget, subProxy) {
3628
+ if (!anyTarget || typeof anyTarget !== 'object')
3629
+ return anyTarget;
3630
+ const target = anyTarget;
3631
+ // If target is already a proxy, return it
3632
+ if (isNonReactive(target))
3633
+ return target;
3634
+ const isProxy = proxyToObject.has(target);
3635
+ if (isProxy)
3636
+ return target;
3637
+ // If we already have a proxy for this object, return it (optimized: get returns undefined if not found)
3638
+ const existing = getExistingProxy(target);
3639
+ if (existing !== undefined)
3640
+ return existing;
3641
+ if (subProxy)
3642
+ subsRegister.set(target, subProxy);
3643
+ const proxy = new Proxy(target, reactiveHandlers);
3644
+ if (Array.isArray(target))
3645
+ arrayLengths.set(target, target.length);
3646
+ // Store the relationships
3647
+ storeProxyRelationship(target, proxy);
3648
+ return proxy;
3649
+ }
3650
+ /**
3651
+ * Main decorator for making classes reactive
3652
+ * Automatically makes class instances reactive when created
3653
+ */
3654
+ const reactive = decorator({
3655
+ class(original) {
3656
+ if (original.prototype instanceof ReactiveBase) {
3657
+ reactiveClasses.add(original);
3658
+ return original;
3659
+ }
3660
+ class Reactive extends original {
3661
+ constructor(...args) {
3662
+ super(...args);
3663
+ if (new.target !== Reactive && !reactiveClasses.has(new.target))
3664
+ options.warn(`${original.name} has been inherited by ${this.constructor.name} that is not reactive.
3665
+ @reactive decorator must be applied to the leaf class OR classes have to extend ReactiveBase.`);
3666
+ // biome-ignore lint/correctness/noConstructorReturn: This is the whole point here
3667
+ return reactive(this);
3668
+ }
3669
+ }
3670
+ Object.defineProperty(Reactive, 'name', {
3671
+ value: `Reactive<${original.name}>`,
3672
+ });
3673
+ return Reactive;
3674
+ },
3675
+ get(original) {
3676
+ return reactiveObject(original);
3677
+ },
3678
+ default: reactiveObject,
3679
+ });
3680
+
3681
+ exports.AZone = AZone;
3682
+ exports.DecoratorError = DecoratorError;
3683
+ exports.FoolProof = FoolProof;
3684
+ exports.IterableWeakMap = IterableWeakMap;
3685
+ exports.IterableWeakSet = IterableWeakSet;
3686
+ exports.ReactiveBase = ReactiveBase;
3687
+ exports.ReactiveError = ReactiveError;
3688
+ exports.Zone = Zone;
3689
+ exports.ZoneAggregator = ZoneAggregator;
3690
+ exports.ZoneHistory = ZoneHistory;
3691
+ exports.__classPrivateFieldGet = __classPrivateFieldGet;
3692
+ exports.__classPrivateFieldSet = __classPrivateFieldSet;
3693
+ exports.__esDecorate = __esDecorate;
3694
+ exports.__runInitializers = __runInitializers;
3695
+ exports.__setFunctionName = __setFunctionName;
3696
+ exports.addBatchCleanup = addBatchCleanup;
3697
+ exports.addUnreactiveProps = addUnreactiveProps;
3698
+ exports.allProps = allProps;
3699
+ exports.arrayEquals = arrayEquals;
3700
+ exports.asyncZone = asyncZone;
3701
+ exports.atom = atom;
3702
+ exports.atomic = atomic;
3703
+ exports.biDi = biDi;
3704
+ exports.caught = caught;
3705
+ exports.cleanedBy = cleanedBy;
3706
+ exports.cleanup = cleanup;
3707
+ exports.contentRef = contentRef;
3708
+ exports.createFlavor = createFlavor;
3709
+ exports.decorator = decorator;
3710
+ exports.deepCompare = deepCompare;
3711
+ exports.deepWatchers = deepWatchers;
3712
+ exports.defer = defer;
3713
+ exports.dependant = dependant;
3714
+ exports.effect = effect;
3715
+ exports.effectAggregator = effectAggregator;
3716
+ exports.effectMarker = effectMarker;
3717
+ exports.effectToDeepWatchedObjects = effectToDeepWatchedObjects;
3718
+ exports.flavorOptions = flavorOptions;
3719
+ exports.flavored = flavored;
3720
+ exports.formatCleanupReason = formatCleanupReason;
3721
+ exports.getActivationLog = getActivationLog;
3722
+ exports.getActiveEffect = getActiveEffect;
3723
+ exports.getEffectNode = getEffectNode;
3724
+ exports.getRoot = getRoot;
3725
+ exports.getState = getState;
3726
+ exports.immutables = immutables;
3727
+ exports.isConstructor = isConstructor;
3728
+ exports.isNonReactive = isNonReactive;
3729
+ exports.isObject = isObject;
3730
+ exports.isReactive = isReactive;
3731
+ exports.keysOf = keysOf;
3732
+ exports.legacyDecorator = legacyDecorator;
3733
+ exports.markWithRoot = markWithRoot;
3734
+ exports.metaProtos = metaProtos;
3735
+ exports.mixin = mixin;
3736
+ exports.modernDecorator = modernDecorator;
3737
+ exports.named = named;
3738
+ exports.nonReactiveClass = nonReactiveClass;
3739
+ exports.nonReactiveObjects = nonReactiveObjects;
3740
+ exports.notifyPropertyChange = notifyPropertyChange;
3741
+ exports.objectParents = objectParents;
3742
+ exports.objectToProxy = objectToProxy;
3743
+ exports.objectsWithDeepWatchers = objectsWithDeepWatchers;
3744
+ exports.onEffectThrow = onEffectThrow;
3745
+ exports.optionCall = optionCall;
3746
+ exports.options = options;
3747
+ exports.proxyToObject = proxyToObject;
3748
+ exports.reactive = reactive;
3749
+ exports.registerDeepWatcher = registerDeepWatcher;
3750
+ exports.reset = reset;
3751
+ exports.root = root;
3752
+ exports.rootFunctionSymbol = rootFunctionSymbol;
3753
+ exports.setDebugHooks = setDebugHooks;
3754
+ exports.stopped = stopped;
3755
+ exports.tag = tag;
3756
+ exports.touched = touched;
3757
+ exports.touched1 = touched1;
3758
+ exports.unreactiveProps = unreactiveProps;
3759
+ exports.untracked = untracked;
3760
+ exports.unwrap = unwrap;
3761
+ exports.wrapProtos = wrapProtos;
3762
+ exports.zip = zip;
3763
+ //# sourceMappingURL=proxy-Dtg-bJ3T.cjs.map