mutts 1.0.10 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/browser.cjs +244 -3199
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +2 -2
- package/dist/browser.dev.cjs +51 -50
- package/dist/browser.dev.cjs.map +1 -1
- package/dist/browser.dev.d.ts +2 -2
- package/dist/browser.dev.esm.js +2 -2
- package/dist/browser.esm.js +15 -13
- package/dist/browser.esm.js.map +1 -1
- package/dist/chunks/{index-CaaQQlPJ.esm.js → index-BUop6B2U.esm.js} +384 -981
- package/dist/chunks/index-BUop6B2U.esm.js.map +1 -0
- package/dist/chunks/index-yK0HVxHv.cjs +2612 -0
- package/dist/chunks/index-yK0HVxHv.cjs.map +1 -0
- package/dist/chunks/{node-nKJBk8iJ.esm.js → node-Bo7WU5S2.esm.js} +2 -2
- package/dist/chunks/{node-nKJBk8iJ.esm.js.map → node-Bo7WU5S2.esm.js.map} +1 -1
- package/dist/chunks/{async-node-3PrbVAbB.cjs → node-Dd0esp5F.cjs} +4 -4
- package/dist/chunks/node-Dd0esp5F.cjs.map +1 -0
- package/dist/chunks/{proxy-Dtg-bJ3T.cjs → proxy-BvM4yewA.cjs} +441 -342
- package/dist/chunks/proxy-BvM4yewA.cjs.map +1 -0
- package/dist/chunks/{proxy-r7lARftl.esm.js → proxy-D2C49sXH.esm.js} +401 -330
- package/dist/chunks/proxy-D2C49sXH.esm.js.map +1 -0
- package/dist/debug.cjs +17 -4
- package/dist/debug.cjs.map +1 -1
- package/dist/debug.d.ts +2 -2
- package/dist/debug.esm.js +17 -3
- package/dist/debug.esm.js.map +1 -1
- package/dist/index.d.ts +84 -209
- package/dist/mutts.umd.js +842 -1362
- package/dist/mutts.umd.js.map +1 -1
- package/dist/mutts.umd.min.js +1 -1
- package/dist/mutts.umd.min.js.map +1 -1
- package/dist/node.cjs +51 -49
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.dev.cjs +51 -49
- package/dist/node.dev.cjs.map +1 -1
- package/dist/node.dev.d.ts +2 -2
- package/dist/node.dev.esm.js +3 -3
- package/dist/node.esm.js +3 -3
- package/dist/{types-W5vD6m2n.d.ts → types-Bx2PhORg.d.ts} +38 -47
- package/docs/ai/api-reference.md +0 -14
- package/docs/ai/manual.md +2 -22
- package/docs/reactive/advanced.md +13 -14
- package/docs/reactive/attend.md +1 -2
- package/docs/reactive/collections.md +2 -149
- package/docs/reactive/core.md +218 -96
- package/docs/reactive/debugging.md +2 -2
- package/docs/reactive/resource.md +1 -1
- package/docs/reactive.md +1 -3
- package/docs/zone.md +1 -1
- package/package.json +18 -9
- package/dist/chunks/async-browser-BU_IfxYD.cjs +0 -216
- package/dist/chunks/async-browser-BU_IfxYD.cjs.map +0 -1
- package/dist/chunks/async-core-CRLKP3l-.cjs +0 -29
- package/dist/chunks/async-core-CRLKP3l-.cjs.map +0 -1
- package/dist/chunks/async-node-3PrbVAbB.cjs.map +0 -1
- package/dist/chunks/index-CaaQQlPJ.esm.js.map +0 -1
- package/dist/chunks/proxy-Dtg-bJ3T.cjs.map +0 -1
- package/dist/chunks/proxy-r7lARftl.esm.js.map +0 -1
- package/docs/reactive/scan.md +0 -324
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
4
|
+
// Queue for hooks registered before the environment is ready (circular dependency fix)
|
|
5
|
+
const hooks = new Set();
|
|
6
|
+
const asyncHooks = {
|
|
7
|
+
addHook(hook) {
|
|
8
|
+
hooks.add(hook);
|
|
9
|
+
return () => hooks.delete(hook);
|
|
10
|
+
},
|
|
11
|
+
/**
|
|
12
|
+
* [Hack] Sanitize a promise (or value) to prevent context leaks.
|
|
13
|
+
* Default: Identity function.
|
|
14
|
+
* Browser: Uses Macrotask wrapping to break microtask chains.
|
|
15
|
+
*/
|
|
16
|
+
sanitizePromise(p) {
|
|
17
|
+
return p;
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Register a hook that will be called whenever an asynchronous operation is initiated.
|
|
22
|
+
* The hook should return a restorer function which will be called just before the async callback runs.
|
|
23
|
+
* That restorer should in turn return an undoer function which will be called just after the async callback finishes.
|
|
24
|
+
*/
|
|
25
|
+
const asyncHook = (hook) => asyncHooks.addHook(hook);
|
|
4
26
|
|
|
5
27
|
/**
|
|
6
28
|
* Yields tuples containing elements from each input array, stopping at the longest array length
|
|
@@ -99,16 +121,16 @@ const FoolProof = {
|
|
|
99
121
|
if (hasNode && obj instanceof Node) {
|
|
100
122
|
obj[prop] = value;
|
|
101
123
|
return true;
|
|
102
|
-
}
|
|
103
|
-
if (!(obj instanceof Object) && !
|
|
124
|
+
} /*
|
|
125
|
+
if (!(obj instanceof Object) && !Object.hasOwn(obj, prop)) {
|
|
104
126
|
Object.defineProperty(obj, prop, {
|
|
105
127
|
value,
|
|
106
128
|
configurable: true,
|
|
107
129
|
writable: true,
|
|
108
130
|
enumerable: true,
|
|
109
|
-
})
|
|
110
|
-
return true
|
|
111
|
-
}
|
|
131
|
+
})
|
|
132
|
+
return true
|
|
133
|
+
}*/
|
|
112
134
|
return Reflect.set(obj, prop, value, receiver);
|
|
113
135
|
},
|
|
114
136
|
};
|
|
@@ -132,15 +154,8 @@ function deepCompare(a, b, cache = new Map()) {
|
|
|
132
154
|
return a === b;
|
|
133
155
|
}
|
|
134
156
|
// Prototype check
|
|
135
|
-
|
|
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
|
-
});
|
|
157
|
+
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b))
|
|
142
158
|
return false;
|
|
143
|
-
}
|
|
144
159
|
// Circular reference protection
|
|
145
160
|
let compared = cache.get(a);
|
|
146
161
|
if (compared?.has(b))
|
|
@@ -152,39 +167,21 @@ function deepCompare(a, b, cache = new Map()) {
|
|
|
152
167
|
compared.add(b);
|
|
153
168
|
// Handle specific object types
|
|
154
169
|
if (Array.isArray(a)) {
|
|
155
|
-
if (!Array.isArray(b))
|
|
156
|
-
console.warn(`[deepCompare] B is not an array`);
|
|
170
|
+
if (!Array.isArray(b) || a.length !== b.length)
|
|
157
171
|
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
172
|
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}`);
|
|
173
|
+
if (!deepCompare(a[i], b[i], cache))
|
|
166
174
|
return false;
|
|
167
|
-
}
|
|
168
175
|
}
|
|
169
176
|
return true;
|
|
170
177
|
}
|
|
171
|
-
if (a instanceof Date)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
}
|
|
178
|
+
if (a instanceof Date)
|
|
179
|
+
return b instanceof Date && a.getTime() === b.getTime();
|
|
180
|
+
if (a instanceof RegExp)
|
|
181
|
+
return b instanceof RegExp && a.toString() === b.toString();
|
|
183
182
|
if (a instanceof Set) {
|
|
184
|
-
if (!(b instanceof Set) || a.size !== b.size)
|
|
185
|
-
console.warn(`[deepCompare] Set size mismatch`);
|
|
183
|
+
if (!(b instanceof Set) || a.size !== b.size)
|
|
186
184
|
return false;
|
|
187
|
-
}
|
|
188
185
|
for (const val of a) {
|
|
189
186
|
let found = false;
|
|
190
187
|
for (const bVal of b) {
|
|
@@ -193,18 +190,14 @@ function deepCompare(a, b, cache = new Map()) {
|
|
|
193
190
|
break;
|
|
194
191
|
}
|
|
195
192
|
}
|
|
196
|
-
if (!found)
|
|
197
|
-
console.warn(`[deepCompare] missing Set element`);
|
|
193
|
+
if (!found)
|
|
198
194
|
return false;
|
|
199
|
-
}
|
|
200
195
|
}
|
|
201
196
|
return true;
|
|
202
197
|
}
|
|
203
198
|
if (a instanceof Map) {
|
|
204
|
-
if (!(b instanceof Map) || a.size !== b.size)
|
|
205
|
-
console.warn(`[deepCompare] Map size mismatch`);
|
|
199
|
+
if (!(b instanceof Map) || a.size !== b.size)
|
|
206
200
|
return false;
|
|
207
|
-
}
|
|
208
201
|
for (const [key, val] of a) {
|
|
209
202
|
if (!b.has(key)) {
|
|
210
203
|
let foundMatch = false;
|
|
@@ -214,16 +207,11 @@ function deepCompare(a, b, cache = new Map()) {
|
|
|
214
207
|
break;
|
|
215
208
|
}
|
|
216
209
|
}
|
|
217
|
-
if (!foundMatch)
|
|
218
|
-
console.warn(`[deepCompare] missing Map key`);
|
|
210
|
+
if (!foundMatch)
|
|
219
211
|
return false;
|
|
220
|
-
}
|
|
221
212
|
}
|
|
222
|
-
else {
|
|
223
|
-
|
|
224
|
-
console.warn(`[deepCompare] Map value mismatch for key`);
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
213
|
+
else if (!deepCompare(val, b.get(key), cache)) {
|
|
214
|
+
return false;
|
|
227
215
|
}
|
|
228
216
|
}
|
|
229
217
|
return true;
|
|
@@ -231,29 +219,11 @@ function deepCompare(a, b, cache = new Map()) {
|
|
|
231
219
|
// Compare own properties
|
|
232
220
|
const keysA = Object.keys(a);
|
|
233
221
|
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
|
-
});
|
|
222
|
+
if (keysA.length !== keysB.length)
|
|
243
223
|
return false;
|
|
244
|
-
}
|
|
245
224
|
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
|
-
});
|
|
225
|
+
if (!Object.hasOwn(b, key) || !deepCompare(a[key], b[key], cache))
|
|
255
226
|
return false;
|
|
256
|
-
}
|
|
257
227
|
}
|
|
258
228
|
return true;
|
|
259
229
|
}
|
|
@@ -301,6 +271,12 @@ function named(name, fn) {
|
|
|
301
271
|
});
|
|
302
272
|
return fn;
|
|
303
273
|
}
|
|
274
|
+
const _mode = (typeof process !== 'undefined' && process.env?.NODE_ENV) ||
|
|
275
|
+
(typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunks/proxy-BvM4yewA.cjs', document.baseURI).href)) }) !== 'undefined' && undefined?.MODE) ||
|
|
276
|
+
'production';
|
|
277
|
+
const isDev = _mode === 'development';
|
|
278
|
+
const isProd = _mode === 'production';
|
|
279
|
+
const isTest = _mode === 'test';
|
|
304
280
|
|
|
305
281
|
// biome-ignore-all lint/suspicious/noConfusingVoidType: We *love* voids
|
|
306
282
|
// Standardized decorator system that works with both Legacy and Modern decorators
|
|
@@ -528,9 +504,7 @@ function flavorOptions(fn, defaultOptions, opts = {}) {
|
|
|
528
504
|
const isObject = currentOptions !== null &&
|
|
529
505
|
typeof currentOptions === 'object' &&
|
|
530
506
|
!Array.isArray(currentOptions);
|
|
531
|
-
newArgs[targetIndex] = isObject
|
|
532
|
-
? { ...defaultOptions, ...currentOptions }
|
|
533
|
-
: defaultOptions;
|
|
507
|
+
newArgs[targetIndex] = isObject ? { ...defaultOptions, ...currentOptions } : defaultOptions;
|
|
534
508
|
return fn.apply(this, newArgs);
|
|
535
509
|
};
|
|
536
510
|
if (opts.name)
|
|
@@ -871,6 +845,20 @@ function mixin(mixinFunction, unwrapFunction) {
|
|
|
871
845
|
});
|
|
872
846
|
}
|
|
873
847
|
|
|
848
|
+
const debugHooks = {
|
|
849
|
+
isDevtoolsEnabled: () => false,
|
|
850
|
+
registerEffect: () => { },
|
|
851
|
+
getTriggerChain: () => [],
|
|
852
|
+
captureStack: () => [],
|
|
853
|
+
captureLineage: () => new Error().stack,
|
|
854
|
+
formatStack: (stack) => [stack],
|
|
855
|
+
recordTriggerLink: () => { },
|
|
856
|
+
decorateError: () => { },
|
|
857
|
+
};
|
|
858
|
+
function setDebugHooks(hooks) {
|
|
859
|
+
Object.assign(debugHooks, hooks);
|
|
860
|
+
}
|
|
861
|
+
|
|
874
862
|
/******************************************************************************
|
|
875
863
|
Copyright (c) Microsoft Corporation.
|
|
876
864
|
|
|
@@ -922,41 +910,17 @@ function __runInitializers(thisArg, initializers, value) {
|
|
|
922
910
|
}
|
|
923
911
|
return useValue ? value : void 0;
|
|
924
912
|
}
|
|
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
913
|
function __classPrivateFieldGet(receiver, state, kind, f) {
|
|
930
914
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
931
915
|
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
916
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
933
917
|
}
|
|
934
918
|
|
|
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
919
|
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
943
920
|
var e = new Error(message);
|
|
944
921
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
945
922
|
};
|
|
946
923
|
|
|
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
924
|
var _ZoneAggregator_zones;
|
|
961
925
|
function isu(z) {
|
|
962
926
|
return z;
|
|
@@ -981,7 +945,7 @@ class AZone {
|
|
|
981
945
|
}
|
|
982
946
|
// [HACK]: Sanitization
|
|
983
947
|
// See BROWSER_ASYNC_POLYFILL.md
|
|
984
|
-
return
|
|
948
|
+
return asyncHooks.sanitizePromise(res);
|
|
985
949
|
}
|
|
986
950
|
root(fn) {
|
|
987
951
|
const prev = this.enter();
|
|
@@ -1084,13 +1048,27 @@ class ZoneAggregator extends AZone {
|
|
|
1084
1048
|
}
|
|
1085
1049
|
}
|
|
1086
1050
|
_ZoneAggregator_zones = new WeakMap();
|
|
1051
|
+
/**
|
|
1052
|
+
* Aggregator of zones that should be preserved across async boundaries.
|
|
1053
|
+
* If you add a zone here, it will be preserved across async boundaries.
|
|
1054
|
+
*
|
|
1055
|
+
* @example
|
|
1056
|
+
* ```ts
|
|
1057
|
+
* import { Zone, asyncZone } from 'mutts'
|
|
1058
|
+
* const userZone = new Zone<User>()
|
|
1059
|
+
* asyncZone.add(userZone)
|
|
1060
|
+
* ```
|
|
1061
|
+
*/
|
|
1087
1062
|
const asyncZone = tag('async', new ZoneAggregator());
|
|
1088
|
-
|
|
1063
|
+
asyncHooks.addHook(() => {
|
|
1064
|
+
// capture state before async boundary
|
|
1089
1065
|
const zone = asyncZone.active;
|
|
1090
1066
|
return () => {
|
|
1067
|
+
// restore state after async boundary, temporarily
|
|
1091
1068
|
const prev = asyncZone.active;
|
|
1092
1069
|
asyncZone.active = zone;
|
|
1093
1070
|
return () => {
|
|
1071
|
+
// restore previous state from before our restore
|
|
1094
1072
|
asyncZone.active = prev;
|
|
1095
1073
|
};
|
|
1096
1074
|
};
|
|
@@ -1160,6 +1138,75 @@ function getRoot(fn) {
|
|
|
1160
1138
|
return fn;
|
|
1161
1139
|
}
|
|
1162
1140
|
|
|
1141
|
+
const effectHistory = tag('effectHistory', new ZoneHistory());
|
|
1142
|
+
tag('effectHistory.present', effectHistory.present);
|
|
1143
|
+
asyncZone.add(effectHistory);
|
|
1144
|
+
/**
|
|
1145
|
+
* Aggregator for zones that need to be tracked along effects.
|
|
1146
|
+
* ie. in each effect, the active zone of the given zoning will be the one active at effect's definition
|
|
1147
|
+
*/
|
|
1148
|
+
const effectAggregator = tag('effectAggregator', new ZoneAggregator(effectHistory.present));
|
|
1149
|
+
function isRunning(effect) {
|
|
1150
|
+
const root = getRoot(effect);
|
|
1151
|
+
return effectHistory.some((e) => getRoot(e) === root);
|
|
1152
|
+
}
|
|
1153
|
+
function getActiveEffect() {
|
|
1154
|
+
return effectHistory.present.active;
|
|
1155
|
+
}
|
|
1156
|
+
const cleanups = new WeakMap();
|
|
1157
|
+
/**
|
|
1158
|
+
* Attach cleanup dependencies to an object. When `unlink(obj)` is called,
|
|
1159
|
+
* each dependency is disposed: functions are invoked with the cleanup reason,
|
|
1160
|
+
* objects are recursively `unlink`ed. This forms a cleanup tree.
|
|
1161
|
+
*
|
|
1162
|
+
* @param obj - The owner object
|
|
1163
|
+
* @param cleanupFns - Cleanup callbacks and/or child objects to unlink recursively
|
|
1164
|
+
* @returns The owner object (for chaining)
|
|
1165
|
+
*
|
|
1166
|
+
* @example
|
|
1167
|
+
* ```ts
|
|
1168
|
+
* // Functions are called with CleanupReason
|
|
1169
|
+
* link(parent, () => console.log('disposed'))
|
|
1170
|
+
*
|
|
1171
|
+
* // Objects are recursively unlinked
|
|
1172
|
+
* link(parent, childA, childB)
|
|
1173
|
+
*
|
|
1174
|
+
* // Mixed
|
|
1175
|
+
* link(parent, childObj, () => timer.clear())
|
|
1176
|
+
*
|
|
1177
|
+
* unlink(parent) // disposes childA, childB, calls the function
|
|
1178
|
+
* ```
|
|
1179
|
+
*/
|
|
1180
|
+
function link(obj, ...cleanupFns) {
|
|
1181
|
+
const set = cleanups.get(obj);
|
|
1182
|
+
if (!set)
|
|
1183
|
+
cleanups.set(obj, new Set(cleanupFns.filter(Boolean)));
|
|
1184
|
+
else
|
|
1185
|
+
for (const fn of cleanupFns)
|
|
1186
|
+
if (fn)
|
|
1187
|
+
set.add(fn);
|
|
1188
|
+
return obj;
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Dispose an object's cleanup dependencies. Functions are called with the
|
|
1192
|
+
* reason; linked objects are recursively unlinked. The cleanup set is removed
|
|
1193
|
+
* so calling `unlink` twice is safe (second call is a no-op).
|
|
1194
|
+
*
|
|
1195
|
+
* @param obj - The object to dispose
|
|
1196
|
+
* @param reason - Optional cleanup reason propagated to callbacks
|
|
1197
|
+
*/
|
|
1198
|
+
function unlink(obj, reason) {
|
|
1199
|
+
const set = cleanups.get(obj);
|
|
1200
|
+
if (set) {
|
|
1201
|
+
cleanups.delete(obj);
|
|
1202
|
+
for (const fn of set)
|
|
1203
|
+
if (typeof fn === 'function')
|
|
1204
|
+
fn(reason);
|
|
1205
|
+
else
|
|
1206
|
+
unlink(fn, reason);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1163
1210
|
const effectMarker = {
|
|
1164
1211
|
enter: 'effect:enter',
|
|
1165
1212
|
leave: 'effect:leave',
|
|
@@ -1222,6 +1269,11 @@ function formatCleanupReason(reason, depth = 0) {
|
|
|
1222
1269
|
}
|
|
1223
1270
|
}
|
|
1224
1271
|
}
|
|
1272
|
+
// Track native reactivity
|
|
1273
|
+
/**
|
|
1274
|
+
* Symbol to mark class properties as non-reactive
|
|
1275
|
+
*/
|
|
1276
|
+
const unreactiveProperties = Symbol('unreactive-properties');
|
|
1225
1277
|
/**
|
|
1226
1278
|
* Symbol representing all properties in reactive tracking
|
|
1227
1279
|
*/
|
|
@@ -1231,14 +1283,6 @@ const allProps = Symbol('all-props');
|
|
|
1231
1283
|
* Used by ownKeys proxy trap — Object.keys(), for..in, Map.keys() depend on this.
|
|
1232
1284
|
*/
|
|
1233
1285
|
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
1286
|
/**
|
|
1243
1287
|
* Structured error codes for machine-readable diagnosis
|
|
1244
1288
|
*/
|
|
@@ -1312,6 +1356,12 @@ const options = {
|
|
|
1312
1356
|
* @param runningChain - The array of effects from the detected one to the currently running one
|
|
1313
1357
|
*/
|
|
1314
1358
|
skipRunningEffect: (_effect) => { },
|
|
1359
|
+
/**
|
|
1360
|
+
* Debug purpose: called when an effect starts executing.
|
|
1361
|
+
* @param effect - The effect being executed (root function)
|
|
1362
|
+
* @param reaction - false for initial creation, true/CleanupReason for subsequent runs
|
|
1363
|
+
*/
|
|
1364
|
+
effectRun: (_effect, _reaction) => { },
|
|
1315
1365
|
/**
|
|
1316
1366
|
* Debug purpose: maximum effect chain (like call stack max depth)
|
|
1317
1367
|
* Used to prevent infinite loops
|
|
@@ -1438,34 +1488,6 @@ const options = {
|
|
|
1438
1488
|
enableHistory: true,
|
|
1439
1489
|
historySize: 50,
|
|
1440
1490
|
},
|
|
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
1491
|
};
|
|
1470
1492
|
function optionCall(name, ...args) {
|
|
1471
1493
|
const fn = options[name];
|
|
@@ -1479,6 +1501,36 @@ function optionCall(name, ...args) {
|
|
|
1479
1501
|
options.warn(`options.${name} threw`, error);
|
|
1480
1502
|
}
|
|
1481
1503
|
}
|
|
1504
|
+
/** Production preset: no introspection, heuristic cycle detection, minimal overhead */
|
|
1505
|
+
const prodPreset = {
|
|
1506
|
+
maxEffectReaction: 'throw',
|
|
1507
|
+
cycleHandling: 'production',
|
|
1508
|
+
introspection: null,
|
|
1509
|
+
onMemoizationDiscrepancy: undefined,
|
|
1510
|
+
};
|
|
1511
|
+
/** Development preset (default): introspection on, early cycle detection, warnings */
|
|
1512
|
+
const devPreset = {
|
|
1513
|
+
maxEffectReaction: 'warn',
|
|
1514
|
+
cycleHandling: 'development',
|
|
1515
|
+
introspection: {
|
|
1516
|
+
gatherReasons: { lineages: 'touch' },
|
|
1517
|
+
logErrors: true,
|
|
1518
|
+
enableHistory: true,
|
|
1519
|
+
historySize: 50,
|
|
1520
|
+
},
|
|
1521
|
+
onMemoizationDiscrepancy: undefined,
|
|
1522
|
+
};
|
|
1523
|
+
/** Debug preset: full diagnostics, throws on violations, rich lineage capture */
|
|
1524
|
+
const debugPreset = {
|
|
1525
|
+
maxEffectReaction: 'debug',
|
|
1526
|
+
cycleHandling: 'debug',
|
|
1527
|
+
introspection: {
|
|
1528
|
+
gatherReasons: { lineages: 'both' },
|
|
1529
|
+
logErrors: true,
|
|
1530
|
+
enableHistory: true,
|
|
1531
|
+
historySize: 200,
|
|
1532
|
+
},
|
|
1533
|
+
};
|
|
1482
1534
|
// --- Proxy State (Merged from proxy-state.ts) ---
|
|
1483
1535
|
const objectToProxy = new WeakMap();
|
|
1484
1536
|
const proxyToObject = new WeakMap();
|
|
@@ -1498,54 +1550,28 @@ function isReactive(obj) {
|
|
|
1498
1550
|
return proxyToObject.has(obj);
|
|
1499
1551
|
}
|
|
1500
1552
|
|
|
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
1553
|
// Track dependency stacks per (obj, prop, effect)
|
|
1545
1554
|
let dependencyStacks = new WeakMap();
|
|
1555
|
+
let assertUntrackedFlag = false;
|
|
1546
1556
|
function resetTracking() {
|
|
1547
1557
|
dependencyStacks = new WeakMap();
|
|
1548
1558
|
}
|
|
1559
|
+
/**
|
|
1560
|
+
* Executes a function and throws if any reactive dependencies are tracked during execution.
|
|
1561
|
+
* Used to assert that code runs in an untracked context.
|
|
1562
|
+
*/
|
|
1563
|
+
function assertUntracked(fn) {
|
|
1564
|
+
if (assertUntrackedFlag) {
|
|
1565
|
+
throw new Error('assertUntracked: nested calls are not supported');
|
|
1566
|
+
}
|
|
1567
|
+
assertUntrackedFlag = true;
|
|
1568
|
+
try {
|
|
1569
|
+
return fn();
|
|
1570
|
+
}
|
|
1571
|
+
finally {
|
|
1572
|
+
assertUntrackedFlag = false;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1549
1575
|
function getDependencyStack(effect, obj, prop) {
|
|
1550
1576
|
const objStacks = dependencyStacks.get(obj);
|
|
1551
1577
|
if (!objStacks)
|
|
@@ -1558,14 +1584,17 @@ function getDependencyStack(effect, obj, prop) {
|
|
|
1558
1584
|
* @param prop - The property name (defaults to allProps)
|
|
1559
1585
|
*/
|
|
1560
1586
|
function dependant(obj, prop = allProps) {
|
|
1587
|
+
if (assertUntrackedFlag) {
|
|
1588
|
+
throw new Error(`Reactive dependency tracking detected in assertUntracked context: ${String(prop)} on ${obj}`);
|
|
1589
|
+
}
|
|
1561
1590
|
obj = unwrap(obj);
|
|
1562
1591
|
const currentActiveEffect = getActiveEffect();
|
|
1563
1592
|
// Early return if no active effect, tracking disabled, or invalid prop
|
|
1564
1593
|
if (!currentActiveEffect || (typeof prop === 'symbol' && prop !== allProps && prop !== keysOf))
|
|
1565
1594
|
return;
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1595
|
+
const node = getEffectNode(currentActiveEffect);
|
|
1596
|
+
if ('dependencyHook' in node) {
|
|
1597
|
+
node.dependencyHook(obj, prop);
|
|
1569
1598
|
}
|
|
1570
1599
|
let objectWatchers = exports.watchers.get(obj);
|
|
1571
1600
|
if (!objectWatchers) {
|
|
@@ -1702,6 +1731,7 @@ let effectTriggeredBy = new WeakMap();
|
|
|
1702
1731
|
// consequencesClosure: for each effect, all effects that it triggers (directly or indirectly)
|
|
1703
1732
|
let causesClosure = new WeakMap();
|
|
1704
1733
|
let consequencesClosure = new WeakMap();
|
|
1734
|
+
// Batch re-entrance depth and broken state
|
|
1705
1735
|
let broken = false;
|
|
1706
1736
|
/**
|
|
1707
1737
|
* Gets or creates an IterableWeakSet for a closure map
|
|
@@ -2355,6 +2385,7 @@ function batch(effect, immediate) {
|
|
|
2355
2385
|
throw new Error('Activation registry already exists');
|
|
2356
2386
|
optionCall('beginChain', roots);
|
|
2357
2387
|
}
|
|
2388
|
+
// TODO: Consider this has been produced but was useless - it might be more correct ?const caller = executingStack.length > 0 ? getActiveEffect() : undefined
|
|
2358
2389
|
const caller = getActiveEffect();
|
|
2359
2390
|
// Optimization: If nested and NOT immediate, just join the existing batch
|
|
2360
2391
|
if (!isNewBatch && !immediate) {
|
|
@@ -2508,6 +2539,22 @@ const atomic = decorator({
|
|
|
2508
2539
|
};
|
|
2509
2540
|
},
|
|
2510
2541
|
});
|
|
2542
|
+
/**
|
|
2543
|
+
* Wraps `fn` so it runs within `effect`'s zone context when invoked later.
|
|
2544
|
+
*
|
|
2545
|
+
* Useful for deferred callbacks (event listeners, `DOMContentLoaded`, etc.)
|
|
2546
|
+
* that need sub-effects parented to the original effect.
|
|
2547
|
+
*
|
|
2548
|
+
* @param prev - The effect whose context should be restored, or `undefined` for root context
|
|
2549
|
+
* @param fn - The function to wrap
|
|
2550
|
+
* @returns A function with the same signature that restores the effect context before calling `fn`
|
|
2551
|
+
*/
|
|
2552
|
+
function captured(prev, fn) {
|
|
2553
|
+
prev ?? (prev = effectHistory.active);
|
|
2554
|
+
return named(effectMarker.leave, (...args) => {
|
|
2555
|
+
return effectHistory.with(prev, () => fn(...args));
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2511
2558
|
/**
|
|
2512
2559
|
* Runs `fn` atomically and **always immediately**, batching all reactive effects
|
|
2513
2560
|
* triggered inside it so they fire only once after `fn` completes.
|
|
@@ -2530,19 +2577,13 @@ function atom(fn) {
|
|
|
2530
2577
|
return batch(fn, 'immediate');
|
|
2531
2578
|
}
|
|
2532
2579
|
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
2580
|
/**
|
|
2538
2581
|
* Creates a reactive effect that automatically re-runs when dependencies change
|
|
2539
2582
|
* @param fn - The effect function that provides dependencies and may return a cleanup function or Promise
|
|
2540
2583
|
* @param options - Options for effect execution
|
|
2541
2584
|
* @returns A cleanup function to stop the effect
|
|
2542
2585
|
*/
|
|
2543
|
-
const effect = named(effectMarker.leave, flavored(function effect(
|
|
2544
|
-
// biome-ignore lint/suspicious/noConfusingVoidType: Effect callbacks commonly return void
|
|
2545
|
-
fn, effectOptions = {}) {
|
|
2586
|
+
const effect = named(effectMarker.leave, flavored(function effect(fn, effectOptions = {}) {
|
|
2546
2587
|
if (effectOptions?.name)
|
|
2547
2588
|
Object.defineProperty(fn, 'name', { value: effectOptions.name });
|
|
2548
2589
|
// Use per-effect asyncMode or fall back to global option
|
|
@@ -2566,6 +2607,7 @@ fn, effectOptions = {}) {
|
|
|
2566
2607
|
if (runningPromise) {
|
|
2567
2608
|
if (asyncMode === 'cancel' && cancelPrevious) {
|
|
2568
2609
|
// Cancel previous execution
|
|
2610
|
+
abort();
|
|
2569
2611
|
cancelPrevious();
|
|
2570
2612
|
cancelPrevious = null;
|
|
2571
2613
|
runningPromise = null;
|
|
@@ -2589,11 +2631,33 @@ fn, effectOptions = {}) {
|
|
|
2589
2631
|
access.reaction = node.nextReason || access.reaction;
|
|
2590
2632
|
node.nextReason = undefined;
|
|
2591
2633
|
optionCall('enter', getRoot(fn));
|
|
2634
|
+
optionCall('effectRun', getRoot(fn), access.reaction);
|
|
2592
2635
|
let result;
|
|
2593
2636
|
let caught = 0;
|
|
2594
|
-
//
|
|
2595
|
-
|
|
2596
|
-
|
|
2637
|
+
// Define bubbling thrower
|
|
2638
|
+
const thrower = (error) => {
|
|
2639
|
+
const catches = node.catchers;
|
|
2640
|
+
const reason = { type: 'error', error };
|
|
2641
|
+
if (catches)
|
|
2642
|
+
while (caught < catches.length) {
|
|
2643
|
+
cleanupReaction(reason);
|
|
2644
|
+
try {
|
|
2645
|
+
reactionCleanup = catches[caught](error);
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2648
|
+
catch (_e) {
|
|
2649
|
+
caught++;
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
if (parent) {
|
|
2653
|
+
const parentNode = getEffectNode(parent);
|
|
2654
|
+
if (parentNode.forwardThrow)
|
|
2655
|
+
parentNode.forwardThrow(error);
|
|
2656
|
+
else
|
|
2657
|
+
throw error;
|
|
2658
|
+
}
|
|
2659
|
+
else
|
|
2660
|
+
throw error;
|
|
2597
2661
|
};
|
|
2598
2662
|
node.forwardThrow = thrower;
|
|
2599
2663
|
let errorToThrow;
|
|
@@ -2643,12 +2707,14 @@ fn, effectOptions = {}) {
|
|
|
2643
2707
|
}
|
|
2644
2708
|
}
|
|
2645
2709
|
catch (error) {
|
|
2710
|
+
debugHooks.decorateError(error, runEffect);
|
|
2646
2711
|
// catcher:self`
|
|
2647
2712
|
errorToThrow = error;
|
|
2648
2713
|
}
|
|
2649
2714
|
// Create cleanup function for next run
|
|
2650
2715
|
node.cleanup = (reason) => {
|
|
2651
2716
|
node.cleanup = undefined;
|
|
2717
|
+
abort();
|
|
2652
2718
|
cleanupReaction(reason);
|
|
2653
2719
|
delete node.catchers;
|
|
2654
2720
|
// Remove this effect from all reactive objects it's watching
|
|
@@ -2659,13 +2725,11 @@ fn, effectOptions = {}) {
|
|
|
2659
2725
|
if (objectWatchers) {
|
|
2660
2726
|
for (const [prop, deps] of objectWatchers.entries()) {
|
|
2661
2727
|
deps.delete(runEffect);
|
|
2662
|
-
if (deps.size === 0)
|
|
2728
|
+
if (deps.size === 0)
|
|
2663
2729
|
objectWatchers.delete(prop);
|
|
2664
|
-
}
|
|
2665
2730
|
}
|
|
2666
|
-
if (objectWatchers.size === 0)
|
|
2731
|
+
if (objectWatchers.size === 0)
|
|
2667
2732
|
exports.watchers.delete(reactiveObj);
|
|
2668
|
-
}
|
|
2669
2733
|
}
|
|
2670
2734
|
}
|
|
2671
2735
|
exports.effectToReactiveObjects.delete(runEffect);
|
|
@@ -2683,33 +2747,6 @@ fn, effectOptions = {}) {
|
|
|
2683
2747
|
delete node.children;
|
|
2684
2748
|
}
|
|
2685
2749
|
};
|
|
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
2750
|
if (errorToThrow)
|
|
2714
2751
|
thrower(errorToThrow);
|
|
2715
2752
|
};
|
|
@@ -2728,26 +2765,25 @@ fn, effectOptions = {}) {
|
|
|
2728
2765
|
node.parent = parent;
|
|
2729
2766
|
// let thrower: CatchFunction | undefined // Moved inside runEffect
|
|
2730
2767
|
let effectStopped = false;
|
|
2768
|
+
let abortController;
|
|
2731
2769
|
const access = {
|
|
2732
2770
|
tracked,
|
|
2733
2771
|
ascend: named(effectMarker.leave, (fn) => ascended(named(effectMarker.enter, () => fn.call(null)))),
|
|
2734
2772
|
//named(effectMarker.enter, (fn) => ascended(fn)),
|
|
2735
2773
|
reaction: false,
|
|
2774
|
+
get signal() {
|
|
2775
|
+
if (!abortController) {
|
|
2776
|
+
abortController = new AbortController();
|
|
2777
|
+
}
|
|
2778
|
+
return abortController.signal;
|
|
2779
|
+
},
|
|
2736
2780
|
};
|
|
2737
2781
|
let runningPromise = null;
|
|
2738
2782
|
let cancelPrevious = null;
|
|
2739
|
-
if (effectOptions?.dependencyHook)
|
|
2783
|
+
if (effectOptions?.dependencyHook)
|
|
2740
2784
|
node.dependencyHook = effectOptions.dependencyHook;
|
|
2741
|
-
}
|
|
2742
2785
|
// Mark the runEffect callback with the original function as its root
|
|
2743
2786
|
markWithRoot(runEffect, fn);
|
|
2744
|
-
function augmentedRv(rv) {
|
|
2745
|
-
return Object.defineProperties(rv, {
|
|
2746
|
-
[stopped]: {
|
|
2747
|
-
get: () => effectStopped,
|
|
2748
|
-
},
|
|
2749
|
-
});
|
|
2750
|
-
}
|
|
2751
2787
|
// Register strict mode if enabled
|
|
2752
2788
|
if (effectOptions?.opaque) {
|
|
2753
2789
|
node.isOpaque = true;
|
|
@@ -2756,6 +2792,12 @@ fn, effectOptions = {}) {
|
|
|
2756
2792
|
debugHooks.registerEffect(runEffect);
|
|
2757
2793
|
}
|
|
2758
2794
|
// Store parent relationship for hierarchy traversal - ALREADY DONE ABOVE via getEffectNode
|
|
2795
|
+
const abort = () => {
|
|
2796
|
+
if (abortController) {
|
|
2797
|
+
abortController.abort(new ReactiveError('[reactive] Effect aborted due to dependency change or stop'));
|
|
2798
|
+
abortController = undefined;
|
|
2799
|
+
}
|
|
2800
|
+
};
|
|
2759
2801
|
batch(runEffect, 'immediate');
|
|
2760
2802
|
// Only ROOT effects are registered for GC cleanup and zone tracking
|
|
2761
2803
|
const isRootEffect = !parent;
|
|
@@ -2765,6 +2807,7 @@ fn, effectOptions = {}) {
|
|
|
2765
2807
|
effectStopped = true;
|
|
2766
2808
|
node.stopped = true;
|
|
2767
2809
|
// Cancel any running async work
|
|
2810
|
+
abort();
|
|
2768
2811
|
if (cancelPrevious) {
|
|
2769
2812
|
cancelPrevious();
|
|
2770
2813
|
cancelPrevious = null;
|
|
@@ -2783,7 +2826,7 @@ fn, effectOptions = {}) {
|
|
|
2783
2826
|
fr.unregister(stopEffect);
|
|
2784
2827
|
};
|
|
2785
2828
|
if (isRootEffect) {
|
|
2786
|
-
const callIfCollected =
|
|
2829
|
+
const callIfCollected = (reason) => stopEffect(reason);
|
|
2787
2830
|
fr.register(callIfCollected, () => {
|
|
2788
2831
|
stopEffect({ type: 'gc' });
|
|
2789
2832
|
optionCall('garbageCollected', fn);
|
|
@@ -2797,16 +2840,16 @@ fn, effectOptions = {}) {
|
|
|
2797
2840
|
parentNode.children = new Set();
|
|
2798
2841
|
}
|
|
2799
2842
|
const children = parentNode.children;
|
|
2800
|
-
const subEffectCleanup =
|
|
2843
|
+
const subEffectCleanup = (reason) => {
|
|
2801
2844
|
children.delete(subEffectCleanup);
|
|
2802
2845
|
// Execute this child effect cleanup (which triggers its own mainCleanup)
|
|
2803
2846
|
stopEffect(reason);
|
|
2804
|
-
}
|
|
2847
|
+
};
|
|
2805
2848
|
children.add(subEffectCleanup);
|
|
2806
2849
|
return subEffectCleanup;
|
|
2807
2850
|
}
|
|
2808
2851
|
// Should not be reachable given isRootEffect check, but for type safety
|
|
2809
|
-
return
|
|
2852
|
+
return (reason) => stopEffect(reason);
|
|
2810
2853
|
}, {
|
|
2811
2854
|
get opaque() {
|
|
2812
2855
|
return flavorOptions(this, { opaque: true }, { name: 'opaque' });
|
|
@@ -2837,9 +2880,11 @@ function biDi(received, get, set) {
|
|
|
2837
2880
|
get = get.get;
|
|
2838
2881
|
}
|
|
2839
2882
|
let programmaticallySetValue = Symbol();
|
|
2840
|
-
effect(markWithRoot(() => {
|
|
2883
|
+
effect.named('biDi')(markWithRoot(() => {
|
|
2841
2884
|
const newValue = get();
|
|
2842
|
-
|
|
2885
|
+
const pValue = programmaticallySetValue;
|
|
2886
|
+
programmaticallySetValue = Symbol();
|
|
2887
|
+
if (unwrap(newValue) !== pValue)
|
|
2843
2888
|
received(newValue);
|
|
2844
2889
|
}, received));
|
|
2845
2890
|
return set
|
|
@@ -2910,16 +2955,16 @@ function bubbleUpChange(changedObject, evolution) {
|
|
|
2910
2955
|
const parents = objectParents.get(changedObject);
|
|
2911
2956
|
if (!parents)
|
|
2912
2957
|
return;
|
|
2913
|
-
for (const { parent
|
|
2958
|
+
for (const { parent } of parents) {
|
|
2914
2959
|
// Trigger deep watchers on parent
|
|
2915
2960
|
const parentDeepWatchers = deepWatchers.get(parent);
|
|
2916
2961
|
if (parentDeepWatchers) {
|
|
2917
2962
|
if (options.introspection?.gatherReasons) {
|
|
2918
2963
|
const gatherReasons = options.introspection.gatherReasons;
|
|
2919
2964
|
const lineageConfig = gatherReasons.lineages;
|
|
2920
|
-
let
|
|
2965
|
+
let touchLineage;
|
|
2921
2966
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
2922
|
-
|
|
2967
|
+
touchLineage = debugHooks.captureLineage();
|
|
2923
2968
|
}
|
|
2924
2969
|
for (const watcher of parentDeepWatchers) {
|
|
2925
2970
|
const dependencyStack = lineageConfig === 'dependency' || lineageConfig === 'both'
|
|
@@ -2932,7 +2977,7 @@ function bubbleUpChange(changedObject, evolution) {
|
|
|
2932
2977
|
obj: parent,
|
|
2933
2978
|
evolution,
|
|
2934
2979
|
dependency: dependencyStack,
|
|
2935
|
-
touch:
|
|
2980
|
+
touch: touchLineage,
|
|
2936
2981
|
});
|
|
2937
2982
|
}
|
|
2938
2983
|
}
|
|
@@ -2985,6 +3030,8 @@ function collectEffects(obj, evolution, effects, objectWatchers, ...keyChains) {
|
|
|
2985
3030
|
for (const key of keys) {
|
|
2986
3031
|
const deps = objectWatchers.get(key);
|
|
2987
3032
|
if (deps) {
|
|
3033
|
+
// Make sure `some.prop++` does not keep a dependency to `some.props`
|
|
3034
|
+
deps.delete(sourceEffect);
|
|
2988
3035
|
for (const effect of deps) {
|
|
2989
3036
|
const runningChain = isRunning(effect);
|
|
2990
3037
|
if (runningChain) {
|
|
@@ -3035,9 +3082,9 @@ function touched(obj, evolution, props) {
|
|
|
3035
3082
|
if (options.introspection?.gatherReasons) {
|
|
3036
3083
|
const gatherReasons = options.introspection.gatherReasons;
|
|
3037
3084
|
const lineageConfig = gatherReasons.lineages;
|
|
3038
|
-
let
|
|
3085
|
+
let touchLineage;
|
|
3039
3086
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
3040
|
-
|
|
3087
|
+
touchLineage = debugHooks.captureLineage();
|
|
3041
3088
|
}
|
|
3042
3089
|
for (const [effect, dependencyStack] of effects) {
|
|
3043
3090
|
const node = getEffectNode(effect);
|
|
@@ -3047,7 +3094,7 @@ function touched(obj, evolution, props) {
|
|
|
3047
3094
|
obj,
|
|
3048
3095
|
evolution,
|
|
3049
3096
|
dependency: dependencyStack,
|
|
3050
|
-
touch:
|
|
3097
|
+
touch: touchLineage,
|
|
3051
3098
|
});
|
|
3052
3099
|
}
|
|
3053
3100
|
}
|
|
@@ -3086,10 +3133,10 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
3086
3133
|
}
|
|
3087
3134
|
effects.add(effect);
|
|
3088
3135
|
if (gather) {
|
|
3089
|
-
let
|
|
3136
|
+
let touchLineage;
|
|
3090
3137
|
let dependencyStack;
|
|
3091
3138
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
3092
|
-
|
|
3139
|
+
touchLineage = debugHooks.captureLineage();
|
|
3093
3140
|
}
|
|
3094
3141
|
if (lineageConfig === 'dependency' || lineageConfig === 'both') {
|
|
3095
3142
|
dependencyStack = getDependencyStack(effect, obj, prop);
|
|
@@ -3100,7 +3147,7 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
3100
3147
|
obj,
|
|
3101
3148
|
evolution,
|
|
3102
3149
|
dependency: dependencyStack,
|
|
3103
|
-
touch:
|
|
3150
|
+
touch: touchLineage,
|
|
3104
3151
|
});
|
|
3105
3152
|
}
|
|
3106
3153
|
recordActivation(effect, obj, evolution, prop);
|
|
@@ -3129,59 +3176,60 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
3129
3176
|
}
|
|
3130
3177
|
}
|
|
3131
3178
|
|
|
3132
|
-
const
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3179
|
+
const absent = Symbol('absent');
|
|
3180
|
+
/**
|
|
3181
|
+
* Add unreactive properties to a prototype.
|
|
3182
|
+
* If no set is provided, marks the entire object/prototype as non-reactive (sets [unreactiveProperties] = true).
|
|
3183
|
+
* If a set is provided, merges with existing unreactive properties (never overrides true).
|
|
3184
|
+
*/
|
|
3136
3185
|
function addUnreactiveProps(proto, set) {
|
|
3137
|
-
|
|
3138
|
-
|
|
3186
|
+
if (unreactiveProperties in proto) {
|
|
3187
|
+
const existing = proto[unreactiveProperties];
|
|
3188
|
+
// If already fully unreactive, don't change
|
|
3189
|
+
if (existing === true)
|
|
3190
|
+
return proto;
|
|
3191
|
+
// If no set provided, upgrade to fully unreactive
|
|
3192
|
+
if (!set) {
|
|
3193
|
+
proto[unreactiveProperties] = true;
|
|
3194
|
+
return proto;
|
|
3195
|
+
}
|
|
3196
|
+
// Merge sets
|
|
3197
|
+
set = proto[unreactiveProperties] = new Set(proto[unreactiveProperties]);
|
|
3198
|
+
for (const p of set)
|
|
3199
|
+
existing.add(p);
|
|
3200
|
+
}
|
|
3201
|
+
// If no set, mark as fully unreactive, otherwise create set
|
|
3202
|
+
else
|
|
3203
|
+
proto[unreactiveProperties] = set ? new Set(set) : true;
|
|
3204
|
+
return proto;
|
|
3139
3205
|
}
|
|
3140
3206
|
/** Check if a property is marked unreactive on obj or any of its prototypes (trap-free) */
|
|
3141
3207
|
function isUnreactiveProp(obj, prop) {
|
|
3142
|
-
if (
|
|
3143
|
-
return
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
target = Object.getPrototypeOf(target);
|
|
3149
|
-
}
|
|
3150
|
-
return false;
|
|
3208
|
+
if (typeof prop === 'symbol' || prop === 'constructor')
|
|
3209
|
+
return true;
|
|
3210
|
+
const marker = obj[unreactiveProperties];
|
|
3211
|
+
return (marker === true || // Fully unreactive
|
|
3212
|
+
marker?.has?.(prop) || // Property is unreactive
|
|
3213
|
+
false);
|
|
3151
3214
|
}
|
|
3152
|
-
|
|
3153
|
-
const
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
nonReactiveObjects.add(o);
|
|
3215
|
+
function nonReactive(...obj) {
|
|
3216
|
+
for (const o of obj) {
|
|
3217
|
+
o[unreactiveProperties] = true;
|
|
3218
|
+
}
|
|
3157
3219
|
return obj[0];
|
|
3158
3220
|
}
|
|
3159
3221
|
function nonReactiveClass(...cls) {
|
|
3160
3222
|
for (const c of cls)
|
|
3161
3223
|
if (c)
|
|
3162
|
-
|
|
3224
|
+
c.prototype[unreactiveProperties] = true;
|
|
3163
3225
|
return cls[0];
|
|
3164
3226
|
}
|
|
3165
3227
|
function isNonReactive(obj) {
|
|
3166
|
-
|
|
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;
|
|
3228
|
+
return !obj || obj[unreactiveProperties] === true;
|
|
3181
3229
|
}
|
|
3182
3230
|
nonReactiveClass(Date, RegExp, Error, Promise, Function);
|
|
3183
3231
|
if (typeof window !== 'undefined') {
|
|
3184
|
-
|
|
3232
|
+
nonReactive(window, document);
|
|
3185
3233
|
nonReactiveClass(Node, Element, HTMLElement, EventTarget, HTMLCollection, NodeList);
|
|
3186
3234
|
}
|
|
3187
3235
|
|
|
@@ -3203,10 +3251,33 @@ function shouldRecurseTouch(oldValue, newValue) {
|
|
|
3203
3251
|
if ((typeof oldValue !== 'object' && !Array.isArray(oldValue)) ||
|
|
3204
3252
|
(typeof newValue !== 'object' && !Array.isArray(newValue)))
|
|
3205
3253
|
return false;
|
|
3206
|
-
if (isNonReactive(oldValue)
|
|
3254
|
+
if (isNonReactive(oldValue) /*|| isNonReactive(newValue)*/)
|
|
3207
3255
|
return false;
|
|
3208
3256
|
return getPrototypeToken(oldValue) === getPrototypeToken(newValue);
|
|
3209
3257
|
}
|
|
3258
|
+
/**
|
|
3259
|
+
* Migrate all watcher registrations from oldRef to newRef.
|
|
3260
|
+
* Called when deep touch replaces an object identity without any child value differences,
|
|
3261
|
+
* to prevent watcher orphaning (effects still pointing at the discarded old object).
|
|
3262
|
+
*/
|
|
3263
|
+
function migrateWatchers(oldRef, newRef) {
|
|
3264
|
+
const oldMap = exports.watchers.get(oldRef);
|
|
3265
|
+
if (!oldMap)
|
|
3266
|
+
return;
|
|
3267
|
+
// Move the entire watcher map
|
|
3268
|
+
exports.watchers.set(newRef, oldMap);
|
|
3269
|
+
exports.watchers.delete(oldRef);
|
|
3270
|
+
// Update the reverse map (effect → objects it watches)
|
|
3271
|
+
for (const deps of oldMap.values()) {
|
|
3272
|
+
for (const effect of deps) {
|
|
3273
|
+
const objects = exports.effectToReactiveObjects.get(effect);
|
|
3274
|
+
if (objects) {
|
|
3275
|
+
objects.delete(oldRef);
|
|
3276
|
+
objects.add(newRef);
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3210
3281
|
/**
|
|
3211
3282
|
* Centralized function to handle property change notifications with optional recursive touch
|
|
3212
3283
|
* @param targetObj - The object whose property changed
|
|
@@ -3224,7 +3295,15 @@ function notifyPropertyChange(targetObj, prop, oldValue, newValue, hadProperty)
|
|
|
3224
3295
|
const origin = { obj: unwrappedObj, prop };
|
|
3225
3296
|
// Deep touch: only notify nested property changes with origin filtering
|
|
3226
3297
|
// Don't notify direct property change - the whole point is to avoid parent effects re-running
|
|
3227
|
-
|
|
3298
|
+
const changes = untracked(() => recursiveTouch(oldValue, newValue, new WeakMap(), [], origin));
|
|
3299
|
+
// When deep touch found no child differences, the object identity still changed.
|
|
3300
|
+
// Migrate watchers from old → new so the dependency chain is preserved.
|
|
3301
|
+
if (changes.length === 0) {
|
|
3302
|
+
migrateWatchers(unwrap(oldValue), unwrap(newValue));
|
|
3303
|
+
}
|
|
3304
|
+
else {
|
|
3305
|
+
dispatchNotifications(changes);
|
|
3306
|
+
}
|
|
3228
3307
|
// Notify opaque listeners (like memoize) that always want to know about identity changes
|
|
3229
3308
|
touchedOpaque(targetObj, evolution, prop);
|
|
3230
3309
|
}
|
|
@@ -3311,12 +3390,11 @@ function diffObjectProperties(oldObj, newObj, visited, notifications, origin) {
|
|
|
3311
3390
|
for (const key of oldKeys)
|
|
3312
3391
|
if (!newKeys.has(key))
|
|
3313
3392
|
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
3393
|
for (const key of newKeys) {
|
|
3318
|
-
if (!oldKeys.has(key))
|
|
3394
|
+
if (!oldKeys.has(key)) {
|
|
3395
|
+
local.push({ target: oldObj, evolution: { type: 'add', prop: key }, prop: key, origin });
|
|
3319
3396
|
continue;
|
|
3397
|
+
}
|
|
3320
3398
|
const oldEntry = unwrap(oldObj[key]);
|
|
3321
3399
|
const newEntry = unwrap(newObj[key]);
|
|
3322
3400
|
if (shouldRecurseTouch(oldEntry, newEntry)) {
|
|
@@ -3358,11 +3436,10 @@ function dispatchNotifications(notifications) {
|
|
|
3358
3436
|
if (originWatchers) {
|
|
3359
3437
|
const originEffects = new Map();
|
|
3360
3438
|
collectEffects(origin.obj, { type: 'set', prop: origin.prop }, originEffects, originWatchers, [allProps], [origin.prop]);
|
|
3361
|
-
|
|
3362
|
-
allowedEffects.add(effect);
|
|
3439
|
+
allowedEffects = new Set(originEffects.keys());
|
|
3363
3440
|
}
|
|
3364
3441
|
// If no allowed effects, skip all notifications (no one should be notified)
|
|
3365
|
-
if (allowedEffects
|
|
3442
|
+
if (!allowedEffects?.size)
|
|
3366
3443
|
return;
|
|
3367
3444
|
}
|
|
3368
3445
|
for (const notification of notifications) {
|
|
@@ -3375,7 +3452,6 @@ function dispatchNotifications(notifications) {
|
|
|
3375
3452
|
let currentEffects;
|
|
3376
3453
|
const propsArray = [prop];
|
|
3377
3454
|
if (objectWatchers) {
|
|
3378
|
-
// console.log(`[DEBUG] dispatchNotifications: processing ${obj.constructor.name} (has watchers)`)
|
|
3379
3455
|
currentEffects = new Map();
|
|
3380
3456
|
const broad = evolution.type !== 'set' ? [allProps, keysOf] : [allProps];
|
|
3381
3457
|
collectEffects(obj, evolution, currentEffects, objectWatchers, broad, propsArray);
|
|
@@ -3411,9 +3487,9 @@ function dispatchNotifications(notifications) {
|
|
|
3411
3487
|
if (options.introspection?.gatherReasons) {
|
|
3412
3488
|
const gatherReasons = options.introspection.gatherReasons;
|
|
3413
3489
|
const lineageConfig = gatherReasons.lineages;
|
|
3414
|
-
let
|
|
3490
|
+
let touchLineage;
|
|
3415
3491
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
3416
|
-
|
|
3492
|
+
touchLineage = debugHooks.captureLineage();
|
|
3417
3493
|
}
|
|
3418
3494
|
for (const effect of combinedEffects) {
|
|
3419
3495
|
const node = getEffectNode(effect);
|
|
@@ -3427,7 +3503,7 @@ function dispatchNotifications(notifications) {
|
|
|
3427
3503
|
obj: unwrap(target),
|
|
3428
3504
|
evolution,
|
|
3429
3505
|
dependency: dependencyStack,
|
|
3430
|
-
touch:
|
|
3506
|
+
touch: touchLineage,
|
|
3431
3507
|
});
|
|
3432
3508
|
}
|
|
3433
3509
|
}
|
|
@@ -3440,11 +3516,16 @@ const metaProtos = new WeakMap();
|
|
|
3440
3516
|
const wrapProtos = new WeakMap();
|
|
3441
3517
|
const arrayLengths = new WeakMap();
|
|
3442
3518
|
const hasReentry = new Set();
|
|
3443
|
-
const subsRegister = new WeakMap();
|
|
3444
3519
|
// Sub-proxy registration for custom reactive behaviors
|
|
3520
|
+
const subsRegister = new WeakMap();
|
|
3521
|
+
// Internal untracked flag for setter/getter operations - only used when testing oldValue while setting a value
|
|
3522
|
+
// TODO: `touched` trigger also compares to old value and should use the internalUntracked flag
|
|
3523
|
+
let internalUntracked = false;
|
|
3445
3524
|
const reactiveHandlers = {
|
|
3446
3525
|
[Symbol.toStringTag]: 'MutTs Reactive',
|
|
3447
3526
|
get(obj, prop, receiver) {
|
|
3527
|
+
if (internalUntracked)
|
|
3528
|
+
return FoolProof.get(obj, prop, receiver);
|
|
3448
3529
|
if (obj && typeof obj === 'object' && prop !== Symbol.toStringTag) {
|
|
3449
3530
|
const metaProto = metaProtos.get(obj.constructor);
|
|
3450
3531
|
if (metaProto && Object.hasOwn(metaProto, prop)) {
|
|
@@ -3464,15 +3545,21 @@ const reactiveHandlers = {
|
|
|
3464
3545
|
if (wrapProto && Object.hasOwn(wrapProto, prop))
|
|
3465
3546
|
return wrapProto[prop];
|
|
3466
3547
|
}
|
|
3467
|
-
// Symbols: fast-path — no reactivity tracking
|
|
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))
|
|
3548
|
+
// Symbols: fast-path — no reactivity tracking
|
|
3549
|
+
if (typeof prop === 'symbol' || prop === 'constructor' || isUnreactiveProp(obj, prop))
|
|
3472
3550
|
return FoolProof.get(obj, prop, receiver);
|
|
3473
3551
|
// Check if property exists using a trap-free walk to avoid triggering
|
|
3474
3552
|
// the has-trap cascade on prototype chains of reactive proxies.
|
|
3475
3553
|
const isOwnProp = Object.hasOwn(obj, prop);
|
|
3554
|
+
// For accessor properties, check the unwrapped object to see if it's an accessor
|
|
3555
|
+
// This ensures ignoreAccessors works correctly even after operations like Object.setPrototypeOf
|
|
3556
|
+
// Skip for null-proto objects (pounce scopes) — they never have accessors
|
|
3557
|
+
const shouldIgnoreAccessor = options.ignoreAccessors &&
|
|
3558
|
+
isOwnProp &&
|
|
3559
|
+
Object.getPrototypeOf(obj) !== null &&
|
|
3560
|
+
(isOwnAccessor(receiver, prop) || isOwnAccessor(obj, prop));
|
|
3561
|
+
// Check if property exists using a trap-free walk to avoid triggering
|
|
3562
|
+
// the has-trap cascade on prototype chains of reactive proxies.
|
|
3476
3563
|
let hasProp = isOwnProp;
|
|
3477
3564
|
let owner = isOwnProp ? obj : undefined;
|
|
3478
3565
|
if (!isOwnProp) {
|
|
@@ -3487,29 +3574,20 @@ const reactiveHandlers = {
|
|
|
3487
3574
|
}
|
|
3488
3575
|
}
|
|
3489
3576
|
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
3577
|
// Depend if...
|
|
3498
3578
|
if (!hasProp ||
|
|
3499
3579
|
(!(options.instanceMembers && isInheritedAccess && obj instanceof Object) &&
|
|
3500
3580
|
!shouldIgnoreAccessor))
|
|
3501
3581
|
dependant(obj, prop);
|
|
3502
|
-
// Two-Point Tracking: for inherited access on null-proto chains,
|
|
3503
|
-
// the owning ancestor
|
|
3504
|
-
// "structural stability" contract: key presence in the chain is fixed at
|
|
3505
|
-
// creation time, so intermediate levels never gain/lose shadowing properties.
|
|
3582
|
+
// Two-Point Tracking: for inherited access on null-proto chains, also track
|
|
3583
|
+
// the owning ancestor so that writing directly to it triggers dependent effects.
|
|
3506
3584
|
if (isInheritedAccess && owner && (!options.instanceMembers || !(obj instanceof Object))) {
|
|
3507
3585
|
dependant(owner, prop);
|
|
3508
3586
|
}
|
|
3509
3587
|
// For arrays, use FoolProof.get (Indexer path) for numeric index reactivity.
|
|
3510
3588
|
// For all other objects, inline Reflect.get directly (skips 3 function calls).
|
|
3511
3589
|
const value = (subsRegister.get(obj)?.get || FoolProof.get)(obj, prop, receiver);
|
|
3512
|
-
if (typeof value === 'object' && value !== null) {
|
|
3590
|
+
if (!isReactive(value) && typeof value === 'object' && value !== null) {
|
|
3513
3591
|
const reactiveValue = reactiveObject(value);
|
|
3514
3592
|
// Only create back-references if this object needs them
|
|
3515
3593
|
if (needsBackReferences(obj)) {
|
|
@@ -3520,9 +3598,19 @@ const reactiveHandlers = {
|
|
|
3520
3598
|
return value;
|
|
3521
3599
|
},
|
|
3522
3600
|
set(obj, prop, value, receiver) {
|
|
3523
|
-
const
|
|
3601
|
+
const unwrapped = unwrap(receiver);
|
|
3602
|
+
if (obj !== unwrapped)
|
|
3603
|
+
return Object.defineProperty(unwrapped, prop, {
|
|
3604
|
+
value,
|
|
3605
|
+
configurable: true,
|
|
3606
|
+
writable: true,
|
|
3607
|
+
enumerable: true,
|
|
3608
|
+
});
|
|
3609
|
+
if (internalUntracked)
|
|
3610
|
+
throw new Error('Internal untracked: setting a value in an getter in a set operation');
|
|
3611
|
+
//return FoolProof.set(obj, prop, value, receiver)
|
|
3524
3612
|
// Check if this property is marked as unreactive
|
|
3525
|
-
if (isUnreactiveProp(obj, prop)
|
|
3613
|
+
if (isUnreactiveProp(obj, prop))
|
|
3526
3614
|
return FoolProof.set(obj, prop, value, receiver);
|
|
3527
3615
|
const newValue = unwrap(value);
|
|
3528
3616
|
// metaProto setter dispatch (e.g., reactive array length)
|
|
@@ -3539,16 +3627,19 @@ const reactiveHandlers = {
|
|
|
3539
3627
|
// Read old value, using withEffect(undefined, ...) for getter-only accessors to avoid
|
|
3540
3628
|
// breaking memoization dependency tracking during SET operations
|
|
3541
3629
|
let oldVal = absent;
|
|
3542
|
-
// TODO: Pffft... Find a way to "generalize" this case?
|
|
3543
3630
|
const isArrayLength = prop === 'length' && Array.isArray(obj);
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3631
|
+
internalUntracked = true;
|
|
3632
|
+
try {
|
|
3633
|
+
if (Reflect.has(obj, prop)) {
|
|
3634
|
+
oldVal = isArrayLength
|
|
3635
|
+
? arrayLengths.get(obj) === newValue
|
|
3636
|
+
? newValue
|
|
3637
|
+
: absent
|
|
3638
|
+
: Reflect.get(obj, prop, receiver);
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
finally {
|
|
3642
|
+
internalUntracked = false;
|
|
3552
3643
|
}
|
|
3553
3644
|
if (objectsWithDeepWatchers.has(obj)) {
|
|
3554
3645
|
if (typeof oldVal === 'object' && oldVal !== null) {
|
|
@@ -3577,7 +3668,8 @@ const reactiveHandlers = {
|
|
|
3577
3668
|
cycle: [], // We don't have the full cycle here, but we know it involves obj
|
|
3578
3669
|
});
|
|
3579
3670
|
hasReentry.add(obj);
|
|
3580
|
-
|
|
3671
|
+
if (!internalUntracked && !isUnreactiveProp(obj, prop))
|
|
3672
|
+
dependant(obj, prop);
|
|
3581
3673
|
const rv = (subsRegister.get(obj)?.has || Reflect.has)(obj, prop);
|
|
3582
3674
|
hasReentry.delete(obj);
|
|
3583
3675
|
return rv;
|
|
@@ -3603,7 +3695,8 @@ const reactiveHandlers = {
|
|
|
3603
3695
|
return subsRegister.get(obj)?.ownKeys?.(obj) || Reflect.ownKeys(obj);
|
|
3604
3696
|
},
|
|
3605
3697
|
getOwnPropertyDescriptor(obj, prop) {
|
|
3606
|
-
return subsRegister.get(obj)?.getOwnPropertyDescriptor?.(obj, prop) ||
|
|
3698
|
+
return (subsRegister.get(obj)?.getOwnPropertyDescriptor?.(obj, prop) ||
|
|
3699
|
+
Reflect.getOwnPropertyDescriptor(obj, prop));
|
|
3607
3700
|
},
|
|
3608
3701
|
};
|
|
3609
3702
|
const reactiveClasses = new WeakSet();
|
|
@@ -3688,31 +3781,34 @@ exports.ReactiveError = ReactiveError;
|
|
|
3688
3781
|
exports.Zone = Zone;
|
|
3689
3782
|
exports.ZoneAggregator = ZoneAggregator;
|
|
3690
3783
|
exports.ZoneHistory = ZoneHistory;
|
|
3691
|
-
exports.__classPrivateFieldGet = __classPrivateFieldGet;
|
|
3692
|
-
exports.__classPrivateFieldSet = __classPrivateFieldSet;
|
|
3693
3784
|
exports.__esDecorate = __esDecorate;
|
|
3694
3785
|
exports.__runInitializers = __runInitializers;
|
|
3695
|
-
exports.__setFunctionName = __setFunctionName;
|
|
3696
3786
|
exports.addBatchCleanup = addBatchCleanup;
|
|
3697
3787
|
exports.addUnreactiveProps = addUnreactiveProps;
|
|
3698
3788
|
exports.allProps = allProps;
|
|
3699
3789
|
exports.arrayEquals = arrayEquals;
|
|
3790
|
+
exports.assertUntracked = assertUntracked;
|
|
3791
|
+
exports.asyncHook = asyncHook;
|
|
3792
|
+
exports.asyncHooks = asyncHooks;
|
|
3700
3793
|
exports.asyncZone = asyncZone;
|
|
3701
3794
|
exports.atom = atom;
|
|
3702
3795
|
exports.atomic = atomic;
|
|
3796
|
+
exports.batch = batch;
|
|
3703
3797
|
exports.biDi = biDi;
|
|
3798
|
+
exports.captured = captured;
|
|
3704
3799
|
exports.caught = caught;
|
|
3705
|
-
exports.cleanedBy = cleanedBy;
|
|
3706
|
-
exports.cleanup = cleanup;
|
|
3707
3800
|
exports.contentRef = contentRef;
|
|
3708
3801
|
exports.createFlavor = createFlavor;
|
|
3802
|
+
exports.debugPreset = debugPreset;
|
|
3709
3803
|
exports.decorator = decorator;
|
|
3710
3804
|
exports.deepCompare = deepCompare;
|
|
3711
3805
|
exports.deepWatchers = deepWatchers;
|
|
3712
3806
|
exports.defer = defer;
|
|
3713
3807
|
exports.dependant = dependant;
|
|
3808
|
+
exports.devPreset = devPreset;
|
|
3714
3809
|
exports.effect = effect;
|
|
3715
3810
|
exports.effectAggregator = effectAggregator;
|
|
3811
|
+
exports.effectHistory = effectHistory;
|
|
3716
3812
|
exports.effectMarker = effectMarker;
|
|
3717
3813
|
exports.effectToDeepWatchedObjects = effectToDeepWatchedObjects;
|
|
3718
3814
|
exports.flavorOptions = flavorOptions;
|
|
@@ -3723,20 +3819,22 @@ exports.getActiveEffect = getActiveEffect;
|
|
|
3723
3819
|
exports.getEffectNode = getEffectNode;
|
|
3724
3820
|
exports.getRoot = getRoot;
|
|
3725
3821
|
exports.getState = getState;
|
|
3726
|
-
exports.
|
|
3822
|
+
exports.hooks = hooks;
|
|
3727
3823
|
exports.isConstructor = isConstructor;
|
|
3824
|
+
exports.isDev = isDev;
|
|
3728
3825
|
exports.isNonReactive = isNonReactive;
|
|
3729
3826
|
exports.isObject = isObject;
|
|
3827
|
+
exports.isProd = isProd;
|
|
3730
3828
|
exports.isReactive = isReactive;
|
|
3829
|
+
exports.isTest = isTest;
|
|
3731
3830
|
exports.keysOf = keysOf;
|
|
3732
3831
|
exports.legacyDecorator = legacyDecorator;
|
|
3832
|
+
exports.link = link;
|
|
3733
3833
|
exports.markWithRoot = markWithRoot;
|
|
3734
3834
|
exports.metaProtos = metaProtos;
|
|
3735
3835
|
exports.mixin = mixin;
|
|
3736
3836
|
exports.modernDecorator = modernDecorator;
|
|
3737
3837
|
exports.named = named;
|
|
3738
|
-
exports.nonReactiveClass = nonReactiveClass;
|
|
3739
|
-
exports.nonReactiveObjects = nonReactiveObjects;
|
|
3740
3838
|
exports.notifyPropertyChange = notifyPropertyChange;
|
|
3741
3839
|
exports.objectParents = objectParents;
|
|
3742
3840
|
exports.objectToProxy = objectToProxy;
|
|
@@ -3744,6 +3842,7 @@ exports.objectsWithDeepWatchers = objectsWithDeepWatchers;
|
|
|
3744
3842
|
exports.onEffectThrow = onEffectThrow;
|
|
3745
3843
|
exports.optionCall = optionCall;
|
|
3746
3844
|
exports.options = options;
|
|
3845
|
+
exports.prodPreset = prodPreset;
|
|
3747
3846
|
exports.proxyToObject = proxyToObject;
|
|
3748
3847
|
exports.reactive = reactive;
|
|
3749
3848
|
exports.registerDeepWatcher = registerDeepWatcher;
|
|
@@ -3751,13 +3850,13 @@ exports.reset = reset;
|
|
|
3751
3850
|
exports.root = root;
|
|
3752
3851
|
exports.rootFunctionSymbol = rootFunctionSymbol;
|
|
3753
3852
|
exports.setDebugHooks = setDebugHooks;
|
|
3754
|
-
exports.stopped = stopped;
|
|
3755
3853
|
exports.tag = tag;
|
|
3756
3854
|
exports.touched = touched;
|
|
3757
3855
|
exports.touched1 = touched1;
|
|
3758
|
-
exports.
|
|
3856
|
+
exports.unlink = unlink;
|
|
3857
|
+
exports.unreactiveProperties = unreactiveProperties;
|
|
3759
3858
|
exports.untracked = untracked;
|
|
3760
3859
|
exports.unwrap = unwrap;
|
|
3761
3860
|
exports.wrapProtos = wrapProtos;
|
|
3762
3861
|
exports.zip = zip;
|
|
3763
|
-
//# sourceMappingURL=proxy-
|
|
3862
|
+
//# sourceMappingURL=proxy-BvM4yewA.cjs.map
|