mutts 1.0.10 → 1.0.11
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 +395 -987
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +2 -2
- package/dist/browser.dev.cjs +13 -9
- 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/{async-browser-BU_IfxYD.cjs → async-browser-Dgr5CreQ.cjs} +13 -11
- package/dist/chunks/async-browser-Dgr5CreQ.cjs.map +1 -0
- package/dist/chunks/{index-CaaQQlPJ.esm.js → index-Sf74wXTV.esm.js} +384 -981
- package/dist/chunks/index-Sf74wXTV.esm.js.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/{proxy-Dtg-bJ3T.cjs → proxy-Cc79Lrzj.cjs} +414 -339
- package/dist/chunks/proxy-Cc79Lrzj.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 -3
- 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 +13 -9
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.dev.cjs +13 -9
- 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.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
|
@@ -118,16 +118,16 @@ const FoolProof = {
|
|
|
118
118
|
if (hasNode && obj instanceof Node) {
|
|
119
119
|
obj[prop] = value;
|
|
120
120
|
return true;
|
|
121
|
-
}
|
|
122
|
-
if (!(obj instanceof Object) && !
|
|
121
|
+
} /*
|
|
122
|
+
if (!(obj instanceof Object) && !Object.hasOwn(obj, prop)) {
|
|
123
123
|
Object.defineProperty(obj, prop, {
|
|
124
124
|
value,
|
|
125
125
|
configurable: true,
|
|
126
126
|
writable: true,
|
|
127
127
|
enumerable: true,
|
|
128
|
-
})
|
|
129
|
-
return true
|
|
130
|
-
}
|
|
128
|
+
})
|
|
129
|
+
return true
|
|
130
|
+
}*/
|
|
131
131
|
return Reflect.set(obj, prop, value, receiver);
|
|
132
132
|
},
|
|
133
133
|
};
|
|
@@ -151,15 +151,8 @@ function deepCompare(a, b, cache = new Map()) {
|
|
|
151
151
|
return a === b;
|
|
152
152
|
}
|
|
153
153
|
// Prototype check
|
|
154
|
-
|
|
155
|
-
const protoB = Object.getPrototypeOf(b);
|
|
156
|
-
if (protoA !== protoB) {
|
|
157
|
-
console.warn(`[deepCompare] prototype mismatch:`, {
|
|
158
|
-
nameA: a?.constructor?.name,
|
|
159
|
-
nameB: b?.constructor?.name,
|
|
160
|
-
});
|
|
154
|
+
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b))
|
|
161
155
|
return false;
|
|
162
|
-
}
|
|
163
156
|
// Circular reference protection
|
|
164
157
|
let compared = cache.get(a);
|
|
165
158
|
if (compared?.has(b))
|
|
@@ -171,39 +164,21 @@ function deepCompare(a, b, cache = new Map()) {
|
|
|
171
164
|
compared.add(b);
|
|
172
165
|
// Handle specific object types
|
|
173
166
|
if (Array.isArray(a)) {
|
|
174
|
-
if (!Array.isArray(b))
|
|
175
|
-
console.warn(`[deepCompare] B is not an array`);
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
if (a.length !== b.length) {
|
|
179
|
-
console.warn(`[deepCompare] array length mismatch:`, { lenA: a.length, lenB: b.length });
|
|
167
|
+
if (!Array.isArray(b) || a.length !== b.length)
|
|
180
168
|
return false;
|
|
181
|
-
}
|
|
182
169
|
for (let i = 0; i < a.length; i++) {
|
|
183
|
-
if (!deepCompare(a[i], b[i], cache))
|
|
184
|
-
console.warn(`[deepCompare] array element mismatch at index ${i}`);
|
|
170
|
+
if (!deepCompare(a[i], b[i], cache))
|
|
185
171
|
return false;
|
|
186
|
-
}
|
|
187
172
|
}
|
|
188
173
|
return true;
|
|
189
174
|
}
|
|
190
|
-
if (a instanceof Date)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return match;
|
|
195
|
-
}
|
|
196
|
-
if (a instanceof RegExp) {
|
|
197
|
-
const match = b instanceof RegExp && a.toString() === b.toString();
|
|
198
|
-
if (!match)
|
|
199
|
-
console.warn(`[deepCompare] RegExp mismatch`);
|
|
200
|
-
return match;
|
|
201
|
-
}
|
|
175
|
+
if (a instanceof Date)
|
|
176
|
+
return b instanceof Date && a.getTime() === b.getTime();
|
|
177
|
+
if (a instanceof RegExp)
|
|
178
|
+
return b instanceof RegExp && a.toString() === b.toString();
|
|
202
179
|
if (a instanceof Set) {
|
|
203
|
-
if (!(b instanceof Set) || a.size !== b.size)
|
|
204
|
-
console.warn(`[deepCompare] Set size mismatch`);
|
|
180
|
+
if (!(b instanceof Set) || a.size !== b.size)
|
|
205
181
|
return false;
|
|
206
|
-
}
|
|
207
182
|
for (const val of a) {
|
|
208
183
|
let found = false;
|
|
209
184
|
for (const bVal of b) {
|
|
@@ -212,18 +187,14 @@ function deepCompare(a, b, cache = new Map()) {
|
|
|
212
187
|
break;
|
|
213
188
|
}
|
|
214
189
|
}
|
|
215
|
-
if (!found)
|
|
216
|
-
console.warn(`[deepCompare] missing Set element`);
|
|
190
|
+
if (!found)
|
|
217
191
|
return false;
|
|
218
|
-
}
|
|
219
192
|
}
|
|
220
193
|
return true;
|
|
221
194
|
}
|
|
222
195
|
if (a instanceof Map) {
|
|
223
|
-
if (!(b instanceof Map) || a.size !== b.size)
|
|
224
|
-
console.warn(`[deepCompare] Map size mismatch`);
|
|
196
|
+
if (!(b instanceof Map) || a.size !== b.size)
|
|
225
197
|
return false;
|
|
226
|
-
}
|
|
227
198
|
for (const [key, val] of a) {
|
|
228
199
|
if (!b.has(key)) {
|
|
229
200
|
let foundMatch = false;
|
|
@@ -233,16 +204,11 @@ function deepCompare(a, b, cache = new Map()) {
|
|
|
233
204
|
break;
|
|
234
205
|
}
|
|
235
206
|
}
|
|
236
|
-
if (!foundMatch)
|
|
237
|
-
console.warn(`[deepCompare] missing Map key`);
|
|
207
|
+
if (!foundMatch)
|
|
238
208
|
return false;
|
|
239
|
-
}
|
|
240
209
|
}
|
|
241
|
-
else {
|
|
242
|
-
|
|
243
|
-
console.warn(`[deepCompare] Map value mismatch for key`);
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
210
|
+
else if (!deepCompare(val, b.get(key), cache)) {
|
|
211
|
+
return false;
|
|
246
212
|
}
|
|
247
213
|
}
|
|
248
214
|
return true;
|
|
@@ -250,29 +216,11 @@ function deepCompare(a, b, cache = new Map()) {
|
|
|
250
216
|
// Compare own properties
|
|
251
217
|
const keysA = Object.keys(a);
|
|
252
218
|
const keysB = Object.keys(b);
|
|
253
|
-
if (keysA.length !== keysB.length)
|
|
254
|
-
console.warn(`[deepCompare] keys length mismatch:`, {
|
|
255
|
-
lenA: keysA.length,
|
|
256
|
-
lenB: keysB.length,
|
|
257
|
-
keysA,
|
|
258
|
-
keysB,
|
|
259
|
-
a,
|
|
260
|
-
b,
|
|
261
|
-
});
|
|
219
|
+
if (keysA.length !== keysB.length)
|
|
262
220
|
return false;
|
|
263
|
-
}
|
|
264
221
|
for (const key of keysA) {
|
|
265
|
-
if (!Object.hasOwn(b, key))
|
|
266
|
-
console.warn(`[deepCompare] missing key ${String(key)} in B`);
|
|
222
|
+
if (!Object.hasOwn(b, key) || !deepCompare(a[key], b[key], cache))
|
|
267
223
|
return false;
|
|
268
|
-
}
|
|
269
|
-
if (!deepCompare(a[key], b[key], cache)) {
|
|
270
|
-
console.warn(`[deepCompare] value mismatch for key ${String(key)}:`, {
|
|
271
|
-
valA: a[key],
|
|
272
|
-
valB: b[key],
|
|
273
|
-
});
|
|
274
|
-
return false;
|
|
275
|
-
}
|
|
276
224
|
}
|
|
277
225
|
return true;
|
|
278
226
|
}
|
|
@@ -320,6 +268,12 @@ function named(name, fn) {
|
|
|
320
268
|
});
|
|
321
269
|
return fn;
|
|
322
270
|
}
|
|
271
|
+
const _mode = (typeof process !== 'undefined' && process.env?.NODE_ENV) ||
|
|
272
|
+
(typeof import.meta !== 'undefined' && import.meta.env?.MODE) ||
|
|
273
|
+
'production';
|
|
274
|
+
const isDev = _mode === 'development';
|
|
275
|
+
const isProd = _mode === 'production';
|
|
276
|
+
const isTest = _mode === 'test';
|
|
323
277
|
|
|
324
278
|
// biome-ignore-all lint/suspicious/noConfusingVoidType: We *love* voids
|
|
325
279
|
// Standardized decorator system that works with both Legacy and Modern decorators
|
|
@@ -547,9 +501,7 @@ function flavorOptions(fn, defaultOptions, opts = {}) {
|
|
|
547
501
|
const isObject = currentOptions !== null &&
|
|
548
502
|
typeof currentOptions === 'object' &&
|
|
549
503
|
!Array.isArray(currentOptions);
|
|
550
|
-
newArgs[targetIndex] = isObject
|
|
551
|
-
? { ...defaultOptions, ...currentOptions }
|
|
552
|
-
: defaultOptions;
|
|
504
|
+
newArgs[targetIndex] = isObject ? { ...defaultOptions, ...currentOptions } : defaultOptions;
|
|
553
505
|
return fn.apply(this, newArgs);
|
|
554
506
|
};
|
|
555
507
|
if (opts.name)
|
|
@@ -890,6 +842,20 @@ function mixin(mixinFunction, unwrapFunction) {
|
|
|
890
842
|
});
|
|
891
843
|
}
|
|
892
844
|
|
|
845
|
+
const debugHooks = {
|
|
846
|
+
isDevtoolsEnabled: () => false,
|
|
847
|
+
registerEffect: () => { },
|
|
848
|
+
getTriggerChain: () => [],
|
|
849
|
+
captureStack: () => [],
|
|
850
|
+
captureLineage: () => new Error().stack,
|
|
851
|
+
formatStack: (stack) => [stack],
|
|
852
|
+
recordTriggerLink: () => { },
|
|
853
|
+
decorateError: () => { },
|
|
854
|
+
};
|
|
855
|
+
function setDebugHooks(hooks) {
|
|
856
|
+
Object.assign(debugHooks, hooks);
|
|
857
|
+
}
|
|
858
|
+
|
|
893
859
|
/******************************************************************************
|
|
894
860
|
Copyright (c) Microsoft Corporation.
|
|
895
861
|
|
|
@@ -941,41 +907,17 @@ function __runInitializers(thisArg, initializers, value) {
|
|
|
941
907
|
}
|
|
942
908
|
return useValue ? value : void 0;
|
|
943
909
|
}
|
|
944
|
-
function __setFunctionName(f, name, prefix) {
|
|
945
|
-
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
|
946
|
-
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
|
947
|
-
}
|
|
948
910
|
function __classPrivateFieldGet(receiver, state, kind, f) {
|
|
949
911
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
950
912
|
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");
|
|
951
913
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
952
914
|
}
|
|
953
915
|
|
|
954
|
-
function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
|
955
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
956
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
957
|
-
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");
|
|
958
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
959
|
-
}
|
|
960
|
-
|
|
961
916
|
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
962
917
|
var e = new Error(message);
|
|
963
918
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
964
919
|
};
|
|
965
920
|
|
|
966
|
-
const debugHooks = {
|
|
967
|
-
isDevtoolsEnabled: () => false,
|
|
968
|
-
registerEffect: () => { },
|
|
969
|
-
getTriggerChain: () => [],
|
|
970
|
-
captureStack: () => [],
|
|
971
|
-
captureLineage: () => new Error().stack,
|
|
972
|
-
formatStack: (stack) => [stack],
|
|
973
|
-
recordTriggerLink: () => { },
|
|
974
|
-
};
|
|
975
|
-
function setDebugHooks(hooks) {
|
|
976
|
-
Object.assign(debugHooks, hooks);
|
|
977
|
-
}
|
|
978
|
-
|
|
979
921
|
var _ZoneAggregator_zones;
|
|
980
922
|
function isu(z) {
|
|
981
923
|
return z;
|
|
@@ -1103,13 +1045,27 @@ class ZoneAggregator extends AZone {
|
|
|
1103
1045
|
}
|
|
1104
1046
|
}
|
|
1105
1047
|
_ZoneAggregator_zones = new WeakMap();
|
|
1048
|
+
/**
|
|
1049
|
+
* Aggregator of zones that should be preserved across async boundaries.
|
|
1050
|
+
* If you add a zone here, it will be preserved across async boundaries.
|
|
1051
|
+
*
|
|
1052
|
+
* @example
|
|
1053
|
+
* ```ts
|
|
1054
|
+
* import { Zone, asyncZone } from 'mutts'
|
|
1055
|
+
* const userZone = new Zone<User>()
|
|
1056
|
+
* asyncZone.add(userZone)
|
|
1057
|
+
* ```
|
|
1058
|
+
*/
|
|
1106
1059
|
const asyncZone = tag('async', new ZoneAggregator());
|
|
1107
1060
|
asyncHooks.addHook(() => {
|
|
1061
|
+
// capture state before async boundary
|
|
1108
1062
|
const zone = asyncZone.active;
|
|
1109
1063
|
return () => {
|
|
1064
|
+
// restore state after async boundary, temporarily
|
|
1110
1065
|
const prev = asyncZone.active;
|
|
1111
1066
|
asyncZone.active = zone;
|
|
1112
1067
|
return () => {
|
|
1068
|
+
// restore previous state from before our restore
|
|
1113
1069
|
asyncZone.active = prev;
|
|
1114
1070
|
};
|
|
1115
1071
|
};
|
|
@@ -1179,6 +1135,75 @@ function getRoot(fn) {
|
|
|
1179
1135
|
return fn;
|
|
1180
1136
|
}
|
|
1181
1137
|
|
|
1138
|
+
const effectHistory = tag('effectHistory', new ZoneHistory());
|
|
1139
|
+
tag('effectHistory.present', effectHistory.present);
|
|
1140
|
+
asyncZone.add(effectHistory);
|
|
1141
|
+
/**
|
|
1142
|
+
* Aggregator for zones that need to be tracked along effects.
|
|
1143
|
+
* ie. in each effect, the active zone of the given zoning will be the one active at effect's definition
|
|
1144
|
+
*/
|
|
1145
|
+
const effectAggregator = tag('effectAggregator', new ZoneAggregator(effectHistory.present));
|
|
1146
|
+
function isRunning(effect) {
|
|
1147
|
+
const root = getRoot(effect);
|
|
1148
|
+
return effectHistory.some((e) => getRoot(e) === root);
|
|
1149
|
+
}
|
|
1150
|
+
function getActiveEffect() {
|
|
1151
|
+
return effectHistory.present.active;
|
|
1152
|
+
}
|
|
1153
|
+
const cleanups = new WeakMap();
|
|
1154
|
+
/**
|
|
1155
|
+
* Attach cleanup dependencies to an object. When `unlink(obj)` is called,
|
|
1156
|
+
* each dependency is disposed: functions are invoked with the cleanup reason,
|
|
1157
|
+
* objects are recursively `unlink`ed. This forms a cleanup tree.
|
|
1158
|
+
*
|
|
1159
|
+
* @param obj - The owner object
|
|
1160
|
+
* @param cleanupFns - Cleanup callbacks and/or child objects to unlink recursively
|
|
1161
|
+
* @returns The owner object (for chaining)
|
|
1162
|
+
*
|
|
1163
|
+
* @example
|
|
1164
|
+
* ```ts
|
|
1165
|
+
* // Functions are called with CleanupReason
|
|
1166
|
+
* link(parent, () => console.log('disposed'))
|
|
1167
|
+
*
|
|
1168
|
+
* // Objects are recursively unlinked
|
|
1169
|
+
* link(parent, childA, childB)
|
|
1170
|
+
*
|
|
1171
|
+
* // Mixed
|
|
1172
|
+
* link(parent, childObj, () => timer.clear())
|
|
1173
|
+
*
|
|
1174
|
+
* unlink(parent) // disposes childA, childB, calls the function
|
|
1175
|
+
* ```
|
|
1176
|
+
*/
|
|
1177
|
+
function link(obj, ...cleanupFns) {
|
|
1178
|
+
const set = cleanups.get(obj);
|
|
1179
|
+
if (!set)
|
|
1180
|
+
cleanups.set(obj, new Set(cleanupFns.filter(Boolean)));
|
|
1181
|
+
else
|
|
1182
|
+
for (const fn of cleanupFns)
|
|
1183
|
+
if (fn)
|
|
1184
|
+
set.add(fn);
|
|
1185
|
+
return obj;
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Dispose an object's cleanup dependencies. Functions are called with the
|
|
1189
|
+
* reason; linked objects are recursively unlinked. The cleanup set is removed
|
|
1190
|
+
* so calling `unlink` twice is safe (second call is a no-op).
|
|
1191
|
+
*
|
|
1192
|
+
* @param obj - The object to dispose
|
|
1193
|
+
* @param reason - Optional cleanup reason propagated to callbacks
|
|
1194
|
+
*/
|
|
1195
|
+
function unlink(obj, reason) {
|
|
1196
|
+
const set = cleanups.get(obj);
|
|
1197
|
+
if (set) {
|
|
1198
|
+
cleanups.delete(obj);
|
|
1199
|
+
for (const fn of set)
|
|
1200
|
+
if (typeof fn === 'function')
|
|
1201
|
+
fn(reason);
|
|
1202
|
+
else
|
|
1203
|
+
unlink(fn, reason);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1182
1207
|
const effectMarker = {
|
|
1183
1208
|
enter: 'effect:enter',
|
|
1184
1209
|
leave: 'effect:leave',
|
|
@@ -1241,6 +1266,11 @@ function formatCleanupReason(reason, depth = 0) {
|
|
|
1241
1266
|
}
|
|
1242
1267
|
}
|
|
1243
1268
|
}
|
|
1269
|
+
// Track native reactivity
|
|
1270
|
+
/**
|
|
1271
|
+
* Symbol to mark class properties as non-reactive
|
|
1272
|
+
*/
|
|
1273
|
+
const unreactiveProperties = Symbol('unreactive-properties');
|
|
1244
1274
|
/**
|
|
1245
1275
|
* Symbol representing all properties in reactive tracking
|
|
1246
1276
|
*/
|
|
@@ -1250,14 +1280,6 @@ const allProps = Symbol('all-props');
|
|
|
1250
1280
|
* Used by ownKeys proxy trap — Object.keys(), for..in, Map.keys() depend on this.
|
|
1251
1281
|
*/
|
|
1252
1282
|
const keysOf = Symbol('keys-of');
|
|
1253
|
-
/**
|
|
1254
|
-
* Symbol to check if an effect is stopped
|
|
1255
|
-
*/
|
|
1256
|
-
const stopped = Symbol('stopped');
|
|
1257
|
-
/**
|
|
1258
|
-
* Symbol to access effect cleanup function
|
|
1259
|
-
*/
|
|
1260
|
-
const cleanup = Symbol('cleanup');
|
|
1261
1283
|
/**
|
|
1262
1284
|
* Structured error codes for machine-readable diagnosis
|
|
1263
1285
|
*/
|
|
@@ -1331,6 +1353,12 @@ const options = {
|
|
|
1331
1353
|
* @param runningChain - The array of effects from the detected one to the currently running one
|
|
1332
1354
|
*/
|
|
1333
1355
|
skipRunningEffect: (_effect) => { },
|
|
1356
|
+
/**
|
|
1357
|
+
* Debug purpose: called when an effect starts executing.
|
|
1358
|
+
* @param effect - The effect being executed (root function)
|
|
1359
|
+
* @param reaction - false for initial creation, true/CleanupReason for subsequent runs
|
|
1360
|
+
*/
|
|
1361
|
+
effectRun: (_effect, _reaction) => { },
|
|
1334
1362
|
/**
|
|
1335
1363
|
* Debug purpose: maximum effect chain (like call stack max depth)
|
|
1336
1364
|
* Used to prevent infinite loops
|
|
@@ -1457,34 +1485,6 @@ const options = {
|
|
|
1457
1485
|
enableHistory: true,
|
|
1458
1486
|
historySize: 50,
|
|
1459
1487
|
},
|
|
1460
|
-
/**
|
|
1461
|
-
* Configuration for zone hooks - control which async APIs are hooked
|
|
1462
|
-
* Each option controls whether the corresponding async API is wrapped to preserve effect context
|
|
1463
|
-
* Only applies when asyncMode is enabled (truthy)
|
|
1464
|
-
* @deprecated Should take all when we made sure PIXI.create, Game.create, ... are -> .root()
|
|
1465
|
-
*/
|
|
1466
|
-
zones: {
|
|
1467
|
-
/**
|
|
1468
|
-
* Hook setTimeout to preserve effect context
|
|
1469
|
-
* @default true
|
|
1470
|
-
*/
|
|
1471
|
-
setTimeout: true,
|
|
1472
|
-
/**
|
|
1473
|
-
* Hook setInterval to preserve effect context
|
|
1474
|
-
* @default true
|
|
1475
|
-
*/
|
|
1476
|
-
setInterval: true,
|
|
1477
|
-
/**
|
|
1478
|
-
* Hook requestAnimationFrame (runs in untracked context when hooked)
|
|
1479
|
-
* @default true
|
|
1480
|
-
*/
|
|
1481
|
-
requestAnimationFrame: true,
|
|
1482
|
-
/**
|
|
1483
|
-
* Hook queueMicrotask to preserve effect context
|
|
1484
|
-
* @default true
|
|
1485
|
-
*/
|
|
1486
|
-
queueMicrotask: true,
|
|
1487
|
-
},
|
|
1488
1488
|
};
|
|
1489
1489
|
function optionCall(name, ...args) {
|
|
1490
1490
|
const fn = options[name];
|
|
@@ -1498,6 +1498,36 @@ function optionCall(name, ...args) {
|
|
|
1498
1498
|
options.warn(`options.${name} threw`, error);
|
|
1499
1499
|
}
|
|
1500
1500
|
}
|
|
1501
|
+
/** Production preset: no introspection, heuristic cycle detection, minimal overhead */
|
|
1502
|
+
const prodPreset = {
|
|
1503
|
+
maxEffectReaction: 'throw',
|
|
1504
|
+
cycleHandling: 'production',
|
|
1505
|
+
introspection: null,
|
|
1506
|
+
onMemoizationDiscrepancy: undefined,
|
|
1507
|
+
};
|
|
1508
|
+
/** Development preset (default): introspection on, early cycle detection, warnings */
|
|
1509
|
+
const devPreset = {
|
|
1510
|
+
maxEffectReaction: 'warn',
|
|
1511
|
+
cycleHandling: 'development',
|
|
1512
|
+
introspection: {
|
|
1513
|
+
gatherReasons: { lineages: 'touch' },
|
|
1514
|
+
logErrors: true,
|
|
1515
|
+
enableHistory: true,
|
|
1516
|
+
historySize: 50,
|
|
1517
|
+
},
|
|
1518
|
+
onMemoizationDiscrepancy: undefined,
|
|
1519
|
+
};
|
|
1520
|
+
/** Debug preset: full diagnostics, throws on violations, rich lineage capture */
|
|
1521
|
+
const debugPreset = {
|
|
1522
|
+
maxEffectReaction: 'debug',
|
|
1523
|
+
cycleHandling: 'debug',
|
|
1524
|
+
introspection: {
|
|
1525
|
+
gatherReasons: { lineages: 'both' },
|
|
1526
|
+
logErrors: true,
|
|
1527
|
+
enableHistory: true,
|
|
1528
|
+
historySize: 200,
|
|
1529
|
+
},
|
|
1530
|
+
};
|
|
1501
1531
|
// --- Proxy State (Merged from proxy-state.ts) ---
|
|
1502
1532
|
const objectToProxy = new WeakMap();
|
|
1503
1533
|
const proxyToObject = new WeakMap();
|
|
@@ -1517,54 +1547,28 @@ function isReactive(obj) {
|
|
|
1517
1547
|
return proxyToObject.has(obj);
|
|
1518
1548
|
}
|
|
1519
1549
|
|
|
1520
|
-
const effectHistory = tag('effectHistory', new ZoneHistory());
|
|
1521
|
-
tag('effectHistory.present', effectHistory.present);
|
|
1522
|
-
asyncZone.add(effectHistory);
|
|
1523
|
-
/**
|
|
1524
|
-
* Aggregator for zones that need to be tracked along effects.
|
|
1525
|
-
* ie. in each effect, the active zone of the given zoning will be the one active at effect's definition
|
|
1526
|
-
*/
|
|
1527
|
-
const effectAggregator = tag('effectAggregator', new ZoneAggregator(effectHistory.present));
|
|
1528
|
-
function isRunning(effect) {
|
|
1529
|
-
const root = getRoot(effect);
|
|
1530
|
-
return effectHistory.some((e) => getRoot(e) === root);
|
|
1531
|
-
}
|
|
1532
|
-
function getActiveEffect() {
|
|
1533
|
-
return effectHistory.present.active;
|
|
1534
|
-
}
|
|
1535
|
-
/**
|
|
1536
|
-
* ADD a cleanup function to an object using the cleanup symbol.
|
|
1537
|
-
* The cleanup function will be called when the object needs to be disposed.
|
|
1538
|
-
*
|
|
1539
|
-
* Note: most of the time, you don't need to use this function directly.
|
|
1540
|
-
* 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.
|
|
1541
|
-
*
|
|
1542
|
-
* @param obj - The object to attach the cleanup function to
|
|
1543
|
-
* @param cleanupFn - The cleanup function to attach
|
|
1544
|
-
* @returns The object with the cleanup function attached
|
|
1545
|
-
*/
|
|
1546
|
-
function cleanedBy(obj, cleanupFn) {
|
|
1547
|
-
const oldCleanup = obj[cleanup];
|
|
1548
|
-
return Object.defineProperty(obj, cleanup, {
|
|
1549
|
-
value: oldCleanup
|
|
1550
|
-
? Object.defineProperties((reason) => {
|
|
1551
|
-
oldCleanup(reason);
|
|
1552
|
-
cleanupFn(reason);
|
|
1553
|
-
}, {
|
|
1554
|
-
[stopped]: { get: () => oldCleanup[stopped] || cleanupFn[stopped] },
|
|
1555
|
-
})
|
|
1556
|
-
: cleanupFn,
|
|
1557
|
-
writable: false,
|
|
1558
|
-
enumerable: false,
|
|
1559
|
-
configurable: true,
|
|
1560
|
-
});
|
|
1561
|
-
}
|
|
1562
|
-
|
|
1563
1550
|
// Track dependency stacks per (obj, prop, effect)
|
|
1564
1551
|
let dependencyStacks = new WeakMap();
|
|
1552
|
+
let assertUntrackedFlag = false;
|
|
1565
1553
|
function resetTracking() {
|
|
1566
1554
|
dependencyStacks = new WeakMap();
|
|
1567
1555
|
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Executes a function and throws if any reactive dependencies are tracked during execution.
|
|
1558
|
+
* Used to assert that code runs in an untracked context.
|
|
1559
|
+
*/
|
|
1560
|
+
function assertUntracked(fn) {
|
|
1561
|
+
if (assertUntrackedFlag) {
|
|
1562
|
+
throw new Error('assertUntracked: nested calls are not supported');
|
|
1563
|
+
}
|
|
1564
|
+
assertUntrackedFlag = true;
|
|
1565
|
+
try {
|
|
1566
|
+
return fn();
|
|
1567
|
+
}
|
|
1568
|
+
finally {
|
|
1569
|
+
assertUntrackedFlag = false;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1568
1572
|
function getDependencyStack(effect, obj, prop) {
|
|
1569
1573
|
const objStacks = dependencyStacks.get(obj);
|
|
1570
1574
|
if (!objStacks)
|
|
@@ -1577,14 +1581,17 @@ function getDependencyStack(effect, obj, prop) {
|
|
|
1577
1581
|
* @param prop - The property name (defaults to allProps)
|
|
1578
1582
|
*/
|
|
1579
1583
|
function dependant(obj, prop = allProps) {
|
|
1584
|
+
if (assertUntrackedFlag) {
|
|
1585
|
+
throw new Error(`Reactive dependency tracking detected in assertUntracked context: ${String(prop)} on ${obj}`);
|
|
1586
|
+
}
|
|
1580
1587
|
obj = unwrap(obj);
|
|
1581
1588
|
const currentActiveEffect = getActiveEffect();
|
|
1582
1589
|
// Early return if no active effect, tracking disabled, or invalid prop
|
|
1583
1590
|
if (!currentActiveEffect || (typeof prop === 'symbol' && prop !== allProps && prop !== keysOf))
|
|
1584
1591
|
return;
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1592
|
+
const node = getEffectNode(currentActiveEffect);
|
|
1593
|
+
if ('dependencyHook' in node) {
|
|
1594
|
+
node.dependencyHook(obj, prop);
|
|
1588
1595
|
}
|
|
1589
1596
|
let objectWatchers = watchers.get(obj);
|
|
1590
1597
|
if (!objectWatchers) {
|
|
@@ -1721,6 +1728,7 @@ let effectTriggeredBy = new WeakMap();
|
|
|
1721
1728
|
// consequencesClosure: for each effect, all effects that it triggers (directly or indirectly)
|
|
1722
1729
|
let causesClosure = new WeakMap();
|
|
1723
1730
|
let consequencesClosure = new WeakMap();
|
|
1731
|
+
// Batch re-entrance depth and broken state
|
|
1724
1732
|
let broken = false;
|
|
1725
1733
|
/**
|
|
1726
1734
|
* Gets or creates an IterableWeakSet for a closure map
|
|
@@ -2374,6 +2382,7 @@ function batch(effect, immediate) {
|
|
|
2374
2382
|
throw new Error('Activation registry already exists');
|
|
2375
2383
|
optionCall('beginChain', roots);
|
|
2376
2384
|
}
|
|
2385
|
+
// TODO: Consider this has been produced but was useless - it might be more correct ?const caller = executingStack.length > 0 ? getActiveEffect() : undefined
|
|
2377
2386
|
const caller = getActiveEffect();
|
|
2378
2387
|
// Optimization: If nested and NOT immediate, just join the existing batch
|
|
2379
2388
|
if (!isNewBatch && !immediate) {
|
|
@@ -2527,6 +2536,22 @@ const atomic = decorator({
|
|
|
2527
2536
|
};
|
|
2528
2537
|
},
|
|
2529
2538
|
});
|
|
2539
|
+
/**
|
|
2540
|
+
* Wraps `fn` so it runs within `effect`'s zone context when invoked later.
|
|
2541
|
+
*
|
|
2542
|
+
* Useful for deferred callbacks (event listeners, `DOMContentLoaded`, etc.)
|
|
2543
|
+
* that need sub-effects parented to the original effect.
|
|
2544
|
+
*
|
|
2545
|
+
* @param prev - The effect whose context should be restored, or `undefined` for root context
|
|
2546
|
+
* @param fn - The function to wrap
|
|
2547
|
+
* @returns A function with the same signature that restores the effect context before calling `fn`
|
|
2548
|
+
*/
|
|
2549
|
+
function captured(prev, fn) {
|
|
2550
|
+
prev ?? (prev = effectHistory.active);
|
|
2551
|
+
return named(effectMarker.leave, (...args) => {
|
|
2552
|
+
return effectHistory.with(prev, () => fn(...args));
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2530
2555
|
/**
|
|
2531
2556
|
* Runs `fn` atomically and **always immediately**, batching all reactive effects
|
|
2532
2557
|
* triggered inside it so they fire only once after `fn` completes.
|
|
@@ -2549,19 +2574,13 @@ function atom(fn) {
|
|
|
2549
2574
|
return batch(fn, 'immediate');
|
|
2550
2575
|
}
|
|
2551
2576
|
const fr = new FinalizationRegistry((f) => f());
|
|
2552
|
-
/**
|
|
2553
|
-
* @param fn - The effect function to run - provides the cleaner
|
|
2554
|
-
* @returns The cleanup function
|
|
2555
|
-
*/
|
|
2556
2577
|
/**
|
|
2557
2578
|
* Creates a reactive effect that automatically re-runs when dependencies change
|
|
2558
2579
|
* @param fn - The effect function that provides dependencies and may return a cleanup function or Promise
|
|
2559
2580
|
* @param options - Options for effect execution
|
|
2560
2581
|
* @returns A cleanup function to stop the effect
|
|
2561
2582
|
*/
|
|
2562
|
-
const effect = named(effectMarker.leave, flavored(function effect(
|
|
2563
|
-
// biome-ignore lint/suspicious/noConfusingVoidType: Effect callbacks commonly return void
|
|
2564
|
-
fn, effectOptions = {}) {
|
|
2583
|
+
const effect = named(effectMarker.leave, flavored(function effect(fn, effectOptions = {}) {
|
|
2565
2584
|
if (effectOptions?.name)
|
|
2566
2585
|
Object.defineProperty(fn, 'name', { value: effectOptions.name });
|
|
2567
2586
|
// Use per-effect asyncMode or fall back to global option
|
|
@@ -2585,6 +2604,7 @@ fn, effectOptions = {}) {
|
|
|
2585
2604
|
if (runningPromise) {
|
|
2586
2605
|
if (asyncMode === 'cancel' && cancelPrevious) {
|
|
2587
2606
|
// Cancel previous execution
|
|
2607
|
+
abort();
|
|
2588
2608
|
cancelPrevious();
|
|
2589
2609
|
cancelPrevious = null;
|
|
2590
2610
|
runningPromise = null;
|
|
@@ -2608,11 +2628,33 @@ fn, effectOptions = {}) {
|
|
|
2608
2628
|
access.reaction = node.nextReason || access.reaction;
|
|
2609
2629
|
node.nextReason = undefined;
|
|
2610
2630
|
optionCall('enter', getRoot(fn));
|
|
2631
|
+
optionCall('effectRun', getRoot(fn), access.reaction);
|
|
2611
2632
|
let result;
|
|
2612
2633
|
let caught = 0;
|
|
2613
|
-
//
|
|
2614
|
-
|
|
2615
|
-
|
|
2634
|
+
// Define bubbling thrower
|
|
2635
|
+
const thrower = (error) => {
|
|
2636
|
+
const catches = node.catchers;
|
|
2637
|
+
const reason = { type: 'error', error };
|
|
2638
|
+
if (catches)
|
|
2639
|
+
while (caught < catches.length) {
|
|
2640
|
+
cleanupReaction(reason);
|
|
2641
|
+
try {
|
|
2642
|
+
reactionCleanup = catches[caught](error);
|
|
2643
|
+
return;
|
|
2644
|
+
}
|
|
2645
|
+
catch (_e) {
|
|
2646
|
+
caught++;
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
if (parent) {
|
|
2650
|
+
const parentNode = getEffectNode(parent);
|
|
2651
|
+
if (parentNode.forwardThrow)
|
|
2652
|
+
parentNode.forwardThrow(error);
|
|
2653
|
+
else
|
|
2654
|
+
throw error;
|
|
2655
|
+
}
|
|
2656
|
+
else
|
|
2657
|
+
throw error;
|
|
2616
2658
|
};
|
|
2617
2659
|
node.forwardThrow = thrower;
|
|
2618
2660
|
let errorToThrow;
|
|
@@ -2662,12 +2704,14 @@ fn, effectOptions = {}) {
|
|
|
2662
2704
|
}
|
|
2663
2705
|
}
|
|
2664
2706
|
catch (error) {
|
|
2707
|
+
debugHooks.decorateError(error, runEffect);
|
|
2665
2708
|
// catcher:self`
|
|
2666
2709
|
errorToThrow = error;
|
|
2667
2710
|
}
|
|
2668
2711
|
// Create cleanup function for next run
|
|
2669
2712
|
node.cleanup = (reason) => {
|
|
2670
2713
|
node.cleanup = undefined;
|
|
2714
|
+
abort();
|
|
2671
2715
|
cleanupReaction(reason);
|
|
2672
2716
|
delete node.catchers;
|
|
2673
2717
|
// Remove this effect from all reactive objects it's watching
|
|
@@ -2678,13 +2722,11 @@ fn, effectOptions = {}) {
|
|
|
2678
2722
|
if (objectWatchers) {
|
|
2679
2723
|
for (const [prop, deps] of objectWatchers.entries()) {
|
|
2680
2724
|
deps.delete(runEffect);
|
|
2681
|
-
if (deps.size === 0)
|
|
2725
|
+
if (deps.size === 0)
|
|
2682
2726
|
objectWatchers.delete(prop);
|
|
2683
|
-
}
|
|
2684
2727
|
}
|
|
2685
|
-
if (objectWatchers.size === 0)
|
|
2728
|
+
if (objectWatchers.size === 0)
|
|
2686
2729
|
watchers.delete(reactiveObj);
|
|
2687
|
-
}
|
|
2688
2730
|
}
|
|
2689
2731
|
}
|
|
2690
2732
|
effectToReactiveObjects.delete(runEffect);
|
|
@@ -2702,33 +2744,6 @@ fn, effectOptions = {}) {
|
|
|
2702
2744
|
delete node.children;
|
|
2703
2745
|
}
|
|
2704
2746
|
};
|
|
2705
|
-
// Define bubbling thrower
|
|
2706
|
-
thrower = (error) => {
|
|
2707
|
-
const catches = node.catchers;
|
|
2708
|
-
const reason = { type: 'error', error };
|
|
2709
|
-
if (catches)
|
|
2710
|
-
while (caught < catches.length) {
|
|
2711
|
-
cleanupReaction(reason);
|
|
2712
|
-
try {
|
|
2713
|
-
reactionCleanup = catches[caught](error);
|
|
2714
|
-
return;
|
|
2715
|
-
}
|
|
2716
|
-
catch (e) {
|
|
2717
|
-
caught++;
|
|
2718
|
-
}
|
|
2719
|
-
}
|
|
2720
|
-
if (parent) {
|
|
2721
|
-
const parentNode = getEffectNode(parent);
|
|
2722
|
-
if (parentNode.forwardThrow)
|
|
2723
|
-
parentNode.forwardThrow(error);
|
|
2724
|
-
else
|
|
2725
|
-
throw error;
|
|
2726
|
-
}
|
|
2727
|
-
else
|
|
2728
|
-
throw error;
|
|
2729
|
-
};
|
|
2730
|
-
// Update the node's forwardThrow to the bubbling one
|
|
2731
|
-
node.forwardThrow = thrower;
|
|
2732
2747
|
if (errorToThrow)
|
|
2733
2748
|
thrower(errorToThrow);
|
|
2734
2749
|
};
|
|
@@ -2747,26 +2762,25 @@ fn, effectOptions = {}) {
|
|
|
2747
2762
|
node.parent = parent;
|
|
2748
2763
|
// let thrower: CatchFunction | undefined // Moved inside runEffect
|
|
2749
2764
|
let effectStopped = false;
|
|
2765
|
+
let abortController;
|
|
2750
2766
|
const access = {
|
|
2751
2767
|
tracked,
|
|
2752
2768
|
ascend: named(effectMarker.leave, (fn) => ascended(named(effectMarker.enter, () => fn.call(null)))),
|
|
2753
2769
|
//named(effectMarker.enter, (fn) => ascended(fn)),
|
|
2754
2770
|
reaction: false,
|
|
2771
|
+
get signal() {
|
|
2772
|
+
if (!abortController) {
|
|
2773
|
+
abortController = new AbortController();
|
|
2774
|
+
}
|
|
2775
|
+
return abortController.signal;
|
|
2776
|
+
},
|
|
2755
2777
|
};
|
|
2756
2778
|
let runningPromise = null;
|
|
2757
2779
|
let cancelPrevious = null;
|
|
2758
|
-
if (effectOptions?.dependencyHook)
|
|
2780
|
+
if (effectOptions?.dependencyHook)
|
|
2759
2781
|
node.dependencyHook = effectOptions.dependencyHook;
|
|
2760
|
-
}
|
|
2761
2782
|
// Mark the runEffect callback with the original function as its root
|
|
2762
2783
|
markWithRoot(runEffect, fn);
|
|
2763
|
-
function augmentedRv(rv) {
|
|
2764
|
-
return Object.defineProperties(rv, {
|
|
2765
|
-
[stopped]: {
|
|
2766
|
-
get: () => effectStopped,
|
|
2767
|
-
},
|
|
2768
|
-
});
|
|
2769
|
-
}
|
|
2770
2784
|
// Register strict mode if enabled
|
|
2771
2785
|
if (effectOptions?.opaque) {
|
|
2772
2786
|
node.isOpaque = true;
|
|
@@ -2775,6 +2789,12 @@ fn, effectOptions = {}) {
|
|
|
2775
2789
|
debugHooks.registerEffect(runEffect);
|
|
2776
2790
|
}
|
|
2777
2791
|
// Store parent relationship for hierarchy traversal - ALREADY DONE ABOVE via getEffectNode
|
|
2792
|
+
const abort = () => {
|
|
2793
|
+
if (abortController) {
|
|
2794
|
+
abortController.abort(new ReactiveError('[reactive] Effect aborted due to dependency change or stop'));
|
|
2795
|
+
abortController = undefined;
|
|
2796
|
+
}
|
|
2797
|
+
};
|
|
2778
2798
|
batch(runEffect, 'immediate');
|
|
2779
2799
|
// Only ROOT effects are registered for GC cleanup and zone tracking
|
|
2780
2800
|
const isRootEffect = !parent;
|
|
@@ -2784,6 +2804,7 @@ fn, effectOptions = {}) {
|
|
|
2784
2804
|
effectStopped = true;
|
|
2785
2805
|
node.stopped = true;
|
|
2786
2806
|
// Cancel any running async work
|
|
2807
|
+
abort();
|
|
2787
2808
|
if (cancelPrevious) {
|
|
2788
2809
|
cancelPrevious();
|
|
2789
2810
|
cancelPrevious = null;
|
|
@@ -2802,7 +2823,7 @@ fn, effectOptions = {}) {
|
|
|
2802
2823
|
fr.unregister(stopEffect);
|
|
2803
2824
|
};
|
|
2804
2825
|
if (isRootEffect) {
|
|
2805
|
-
const callIfCollected =
|
|
2826
|
+
const callIfCollected = (reason) => stopEffect(reason);
|
|
2806
2827
|
fr.register(callIfCollected, () => {
|
|
2807
2828
|
stopEffect({ type: 'gc' });
|
|
2808
2829
|
optionCall('garbageCollected', fn);
|
|
@@ -2816,16 +2837,16 @@ fn, effectOptions = {}) {
|
|
|
2816
2837
|
parentNode.children = new Set();
|
|
2817
2838
|
}
|
|
2818
2839
|
const children = parentNode.children;
|
|
2819
|
-
const subEffectCleanup =
|
|
2840
|
+
const subEffectCleanup = (reason) => {
|
|
2820
2841
|
children.delete(subEffectCleanup);
|
|
2821
2842
|
// Execute this child effect cleanup (which triggers its own mainCleanup)
|
|
2822
2843
|
stopEffect(reason);
|
|
2823
|
-
}
|
|
2844
|
+
};
|
|
2824
2845
|
children.add(subEffectCleanup);
|
|
2825
2846
|
return subEffectCleanup;
|
|
2826
2847
|
}
|
|
2827
2848
|
// Should not be reachable given isRootEffect check, but for type safety
|
|
2828
|
-
return
|
|
2849
|
+
return (reason) => stopEffect(reason);
|
|
2829
2850
|
}, {
|
|
2830
2851
|
get opaque() {
|
|
2831
2852
|
return flavorOptions(this, { opaque: true }, { name: 'opaque' });
|
|
@@ -2856,9 +2877,11 @@ function biDi(received, get, set) {
|
|
|
2856
2877
|
get = get.get;
|
|
2857
2878
|
}
|
|
2858
2879
|
let programmaticallySetValue = Symbol();
|
|
2859
|
-
effect(markWithRoot(() => {
|
|
2880
|
+
effect.named('biDi')(markWithRoot(() => {
|
|
2860
2881
|
const newValue = get();
|
|
2861
|
-
|
|
2882
|
+
const pValue = programmaticallySetValue;
|
|
2883
|
+
programmaticallySetValue = Symbol();
|
|
2884
|
+
if (unwrap(newValue) !== pValue)
|
|
2862
2885
|
received(newValue);
|
|
2863
2886
|
}, received));
|
|
2864
2887
|
return set
|
|
@@ -2929,16 +2952,16 @@ function bubbleUpChange(changedObject, evolution) {
|
|
|
2929
2952
|
const parents = objectParents.get(changedObject);
|
|
2930
2953
|
if (!parents)
|
|
2931
2954
|
return;
|
|
2932
|
-
for (const { parent
|
|
2955
|
+
for (const { parent } of parents) {
|
|
2933
2956
|
// Trigger deep watchers on parent
|
|
2934
2957
|
const parentDeepWatchers = deepWatchers.get(parent);
|
|
2935
2958
|
if (parentDeepWatchers) {
|
|
2936
2959
|
if (options.introspection?.gatherReasons) {
|
|
2937
2960
|
const gatherReasons = options.introspection.gatherReasons;
|
|
2938
2961
|
const lineageConfig = gatherReasons.lineages;
|
|
2939
|
-
let
|
|
2962
|
+
let touchLineage;
|
|
2940
2963
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
2941
|
-
|
|
2964
|
+
touchLineage = debugHooks.captureLineage();
|
|
2942
2965
|
}
|
|
2943
2966
|
for (const watcher of parentDeepWatchers) {
|
|
2944
2967
|
const dependencyStack = lineageConfig === 'dependency' || lineageConfig === 'both'
|
|
@@ -2951,7 +2974,7 @@ function bubbleUpChange(changedObject, evolution) {
|
|
|
2951
2974
|
obj: parent,
|
|
2952
2975
|
evolution,
|
|
2953
2976
|
dependency: dependencyStack,
|
|
2954
|
-
touch:
|
|
2977
|
+
touch: touchLineage,
|
|
2955
2978
|
});
|
|
2956
2979
|
}
|
|
2957
2980
|
}
|
|
@@ -3004,6 +3027,8 @@ function collectEffects(obj, evolution, effects, objectWatchers, ...keyChains) {
|
|
|
3004
3027
|
for (const key of keys) {
|
|
3005
3028
|
const deps = objectWatchers.get(key);
|
|
3006
3029
|
if (deps) {
|
|
3030
|
+
// Make sure `some.prop++` does not keep a dependency to `some.props`
|
|
3031
|
+
deps.delete(sourceEffect);
|
|
3007
3032
|
for (const effect of deps) {
|
|
3008
3033
|
const runningChain = isRunning(effect);
|
|
3009
3034
|
if (runningChain) {
|
|
@@ -3054,9 +3079,9 @@ function touched(obj, evolution, props) {
|
|
|
3054
3079
|
if (options.introspection?.gatherReasons) {
|
|
3055
3080
|
const gatherReasons = options.introspection.gatherReasons;
|
|
3056
3081
|
const lineageConfig = gatherReasons.lineages;
|
|
3057
|
-
let
|
|
3082
|
+
let touchLineage;
|
|
3058
3083
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
3059
|
-
|
|
3084
|
+
touchLineage = debugHooks.captureLineage();
|
|
3060
3085
|
}
|
|
3061
3086
|
for (const [effect, dependencyStack] of effects) {
|
|
3062
3087
|
const node = getEffectNode(effect);
|
|
@@ -3066,7 +3091,7 @@ function touched(obj, evolution, props) {
|
|
|
3066
3091
|
obj,
|
|
3067
3092
|
evolution,
|
|
3068
3093
|
dependency: dependencyStack,
|
|
3069
|
-
touch:
|
|
3094
|
+
touch: touchLineage,
|
|
3070
3095
|
});
|
|
3071
3096
|
}
|
|
3072
3097
|
}
|
|
@@ -3105,10 +3130,10 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
3105
3130
|
}
|
|
3106
3131
|
effects.add(effect);
|
|
3107
3132
|
if (gather) {
|
|
3108
|
-
let
|
|
3133
|
+
let touchLineage;
|
|
3109
3134
|
let dependencyStack;
|
|
3110
3135
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
3111
|
-
|
|
3136
|
+
touchLineage = debugHooks.captureLineage();
|
|
3112
3137
|
}
|
|
3113
3138
|
if (lineageConfig === 'dependency' || lineageConfig === 'both') {
|
|
3114
3139
|
dependencyStack = getDependencyStack(effect, obj, prop);
|
|
@@ -3119,7 +3144,7 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
3119
3144
|
obj,
|
|
3120
3145
|
evolution,
|
|
3121
3146
|
dependency: dependencyStack,
|
|
3122
|
-
touch:
|
|
3147
|
+
touch: touchLineage,
|
|
3123
3148
|
});
|
|
3124
3149
|
}
|
|
3125
3150
|
recordActivation(effect, obj, evolution, prop);
|
|
@@ -3148,59 +3173,60 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
3148
3173
|
}
|
|
3149
3174
|
}
|
|
3150
3175
|
|
|
3151
|
-
const
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3176
|
+
const absent = Symbol('absent');
|
|
3177
|
+
/**
|
|
3178
|
+
* Add unreactive properties to a prototype.
|
|
3179
|
+
* If no set is provided, marks the entire object/prototype as non-reactive (sets [unreactiveProperties] = true).
|
|
3180
|
+
* If a set is provided, merges with existing unreactive properties (never overrides true).
|
|
3181
|
+
*/
|
|
3155
3182
|
function addUnreactiveProps(proto, set) {
|
|
3156
|
-
|
|
3157
|
-
|
|
3183
|
+
if (unreactiveProperties in proto) {
|
|
3184
|
+
const existing = proto[unreactiveProperties];
|
|
3185
|
+
// If already fully unreactive, don't change
|
|
3186
|
+
if (existing === true)
|
|
3187
|
+
return proto;
|
|
3188
|
+
// If no set provided, upgrade to fully unreactive
|
|
3189
|
+
if (!set) {
|
|
3190
|
+
proto[unreactiveProperties] = true;
|
|
3191
|
+
return proto;
|
|
3192
|
+
}
|
|
3193
|
+
// Merge sets
|
|
3194
|
+
set = proto[unreactiveProperties] = new Set(proto[unreactiveProperties]);
|
|
3195
|
+
for (const p of set)
|
|
3196
|
+
existing.add(p);
|
|
3197
|
+
}
|
|
3198
|
+
// If no set, mark as fully unreactive, otherwise create set
|
|
3199
|
+
else
|
|
3200
|
+
proto[unreactiveProperties] = set ? new Set(set) : true;
|
|
3201
|
+
return proto;
|
|
3158
3202
|
}
|
|
3159
3203
|
/** Check if a property is marked unreactive on obj or any of its prototypes (trap-free) */
|
|
3160
3204
|
function isUnreactiveProp(obj, prop) {
|
|
3161
|
-
if (
|
|
3162
|
-
return
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
target = Object.getPrototypeOf(target);
|
|
3168
|
-
}
|
|
3169
|
-
return false;
|
|
3205
|
+
if (typeof prop === 'symbol' || prop === 'constructor')
|
|
3206
|
+
return true;
|
|
3207
|
+
const marker = obj[unreactiveProperties];
|
|
3208
|
+
return (marker === true || // Fully unreactive
|
|
3209
|
+
marker?.has?.(prop) || // Property is unreactive
|
|
3210
|
+
false);
|
|
3170
3211
|
}
|
|
3171
|
-
|
|
3172
|
-
const
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
nonReactiveObjects.add(o);
|
|
3212
|
+
function nonReactive(...obj) {
|
|
3213
|
+
for (const o of obj) {
|
|
3214
|
+
o[unreactiveProperties] = true;
|
|
3215
|
+
}
|
|
3176
3216
|
return obj[0];
|
|
3177
3217
|
}
|
|
3178
3218
|
function nonReactiveClass(...cls) {
|
|
3179
3219
|
for (const c of cls)
|
|
3180
3220
|
if (c)
|
|
3181
|
-
|
|
3221
|
+
c.prototype[unreactiveProperties] = true;
|
|
3182
3222
|
return cls[0];
|
|
3183
3223
|
}
|
|
3184
3224
|
function isNonReactive(obj) {
|
|
3185
|
-
|
|
3186
|
-
return true;
|
|
3187
|
-
if (nonReactiveObjects.has(obj))
|
|
3188
|
-
return true;
|
|
3189
|
-
// Walk the prototype chain on the raw object to check for non-reactive classes
|
|
3190
|
-
let proto = Object.getPrototypeOf(obj);
|
|
3191
|
-
while (proto) {
|
|
3192
|
-
if (nonReactiveClasses.has(proto))
|
|
3193
|
-
return true;
|
|
3194
|
-
proto = Object.getPrototypeOf(proto);
|
|
3195
|
-
}
|
|
3196
|
-
for (const fn of immutables)
|
|
3197
|
-
if (fn(obj))
|
|
3198
|
-
return true;
|
|
3199
|
-
return false;
|
|
3225
|
+
return !obj || obj[unreactiveProperties] === true;
|
|
3200
3226
|
}
|
|
3201
3227
|
nonReactiveClass(Date, RegExp, Error, Promise, Function);
|
|
3202
3228
|
if (typeof window !== 'undefined') {
|
|
3203
|
-
|
|
3229
|
+
nonReactive(window, document);
|
|
3204
3230
|
nonReactiveClass(Node, Element, HTMLElement, EventTarget, HTMLCollection, NodeList);
|
|
3205
3231
|
}
|
|
3206
3232
|
|
|
@@ -3222,10 +3248,33 @@ function shouldRecurseTouch(oldValue, newValue) {
|
|
|
3222
3248
|
if ((typeof oldValue !== 'object' && !Array.isArray(oldValue)) ||
|
|
3223
3249
|
(typeof newValue !== 'object' && !Array.isArray(newValue)))
|
|
3224
3250
|
return false;
|
|
3225
|
-
if (isNonReactive(oldValue)
|
|
3251
|
+
if (isNonReactive(oldValue) /*|| isNonReactive(newValue)*/)
|
|
3226
3252
|
return false;
|
|
3227
3253
|
return getPrototypeToken(oldValue) === getPrototypeToken(newValue);
|
|
3228
3254
|
}
|
|
3255
|
+
/**
|
|
3256
|
+
* Migrate all watcher registrations from oldRef to newRef.
|
|
3257
|
+
* Called when deep touch replaces an object identity without any child value differences,
|
|
3258
|
+
* to prevent watcher orphaning (effects still pointing at the discarded old object).
|
|
3259
|
+
*/
|
|
3260
|
+
function migrateWatchers(oldRef, newRef) {
|
|
3261
|
+
const oldMap = watchers.get(oldRef);
|
|
3262
|
+
if (!oldMap)
|
|
3263
|
+
return;
|
|
3264
|
+
// Move the entire watcher map
|
|
3265
|
+
watchers.set(newRef, oldMap);
|
|
3266
|
+
watchers.delete(oldRef);
|
|
3267
|
+
// Update the reverse map (effect → objects it watches)
|
|
3268
|
+
for (const deps of oldMap.values()) {
|
|
3269
|
+
for (const effect of deps) {
|
|
3270
|
+
const objects = effectToReactiveObjects.get(effect);
|
|
3271
|
+
if (objects) {
|
|
3272
|
+
objects.delete(oldRef);
|
|
3273
|
+
objects.add(newRef);
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3229
3278
|
/**
|
|
3230
3279
|
* Centralized function to handle property change notifications with optional recursive touch
|
|
3231
3280
|
* @param targetObj - The object whose property changed
|
|
@@ -3243,7 +3292,15 @@ function notifyPropertyChange(targetObj, prop, oldValue, newValue, hadProperty)
|
|
|
3243
3292
|
const origin = { obj: unwrappedObj, prop };
|
|
3244
3293
|
// Deep touch: only notify nested property changes with origin filtering
|
|
3245
3294
|
// Don't notify direct property change - the whole point is to avoid parent effects re-running
|
|
3246
|
-
|
|
3295
|
+
const changes = untracked(() => recursiveTouch(oldValue, newValue, new WeakMap(), [], origin));
|
|
3296
|
+
// When deep touch found no child differences, the object identity still changed.
|
|
3297
|
+
// Migrate watchers from old → new so the dependency chain is preserved.
|
|
3298
|
+
if (changes.length === 0) {
|
|
3299
|
+
migrateWatchers(unwrap(oldValue), unwrap(newValue));
|
|
3300
|
+
}
|
|
3301
|
+
else {
|
|
3302
|
+
dispatchNotifications(changes);
|
|
3303
|
+
}
|
|
3247
3304
|
// Notify opaque listeners (like memoize) that always want to know about identity changes
|
|
3248
3305
|
touchedOpaque(targetObj, evolution, prop);
|
|
3249
3306
|
}
|
|
@@ -3330,12 +3387,11 @@ function diffObjectProperties(oldObj, newObj, visited, notifications, origin) {
|
|
|
3330
3387
|
for (const key of oldKeys)
|
|
3331
3388
|
if (!newKeys.has(key))
|
|
3332
3389
|
local.push({ target: oldObj, evolution: { type: 'del', prop: key }, prop: key, origin });
|
|
3333
|
-
for (const key of newKeys)
|
|
3334
|
-
if (!oldKeys.has(key))
|
|
3335
|
-
local.push({ target: oldObj, evolution: { type: 'add', prop: key }, prop: key, origin });
|
|
3336
3390
|
for (const key of newKeys) {
|
|
3337
|
-
if (!oldKeys.has(key))
|
|
3391
|
+
if (!oldKeys.has(key)) {
|
|
3392
|
+
local.push({ target: oldObj, evolution: { type: 'add', prop: key }, prop: key, origin });
|
|
3338
3393
|
continue;
|
|
3394
|
+
}
|
|
3339
3395
|
const oldEntry = unwrap(oldObj[key]);
|
|
3340
3396
|
const newEntry = unwrap(newObj[key]);
|
|
3341
3397
|
if (shouldRecurseTouch(oldEntry, newEntry)) {
|
|
@@ -3377,11 +3433,10 @@ function dispatchNotifications(notifications) {
|
|
|
3377
3433
|
if (originWatchers) {
|
|
3378
3434
|
const originEffects = new Map();
|
|
3379
3435
|
collectEffects(origin.obj, { type: 'set', prop: origin.prop }, originEffects, originWatchers, [allProps], [origin.prop]);
|
|
3380
|
-
|
|
3381
|
-
allowedEffects.add(effect);
|
|
3436
|
+
allowedEffects = new Set(originEffects.keys());
|
|
3382
3437
|
}
|
|
3383
3438
|
// If no allowed effects, skip all notifications (no one should be notified)
|
|
3384
|
-
if (allowedEffects
|
|
3439
|
+
if (!allowedEffects?.size)
|
|
3385
3440
|
return;
|
|
3386
3441
|
}
|
|
3387
3442
|
for (const notification of notifications) {
|
|
@@ -3394,7 +3449,6 @@ function dispatchNotifications(notifications) {
|
|
|
3394
3449
|
let currentEffects;
|
|
3395
3450
|
const propsArray = [prop];
|
|
3396
3451
|
if (objectWatchers) {
|
|
3397
|
-
// console.log(`[DEBUG] dispatchNotifications: processing ${obj.constructor.name} (has watchers)`)
|
|
3398
3452
|
currentEffects = new Map();
|
|
3399
3453
|
const broad = evolution.type !== 'set' ? [allProps, keysOf] : [allProps];
|
|
3400
3454
|
collectEffects(obj, evolution, currentEffects, objectWatchers, broad, propsArray);
|
|
@@ -3430,9 +3484,9 @@ function dispatchNotifications(notifications) {
|
|
|
3430
3484
|
if (options.introspection?.gatherReasons) {
|
|
3431
3485
|
const gatherReasons = options.introspection.gatherReasons;
|
|
3432
3486
|
const lineageConfig = gatherReasons.lineages;
|
|
3433
|
-
let
|
|
3487
|
+
let touchLineage;
|
|
3434
3488
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
3435
|
-
|
|
3489
|
+
touchLineage = debugHooks.captureLineage();
|
|
3436
3490
|
}
|
|
3437
3491
|
for (const effect of combinedEffects) {
|
|
3438
3492
|
const node = getEffectNode(effect);
|
|
@@ -3446,7 +3500,7 @@ function dispatchNotifications(notifications) {
|
|
|
3446
3500
|
obj: unwrap(target),
|
|
3447
3501
|
evolution,
|
|
3448
3502
|
dependency: dependencyStack,
|
|
3449
|
-
touch:
|
|
3503
|
+
touch: touchLineage,
|
|
3450
3504
|
});
|
|
3451
3505
|
}
|
|
3452
3506
|
}
|
|
@@ -3459,11 +3513,16 @@ const metaProtos = new WeakMap();
|
|
|
3459
3513
|
const wrapProtos = new WeakMap();
|
|
3460
3514
|
const arrayLengths = new WeakMap();
|
|
3461
3515
|
const hasReentry = new Set();
|
|
3462
|
-
const subsRegister = new WeakMap();
|
|
3463
3516
|
// Sub-proxy registration for custom reactive behaviors
|
|
3517
|
+
const subsRegister = new WeakMap();
|
|
3518
|
+
// Internal untracked flag for setter/getter operations - only used when testing oldValue while setting a value
|
|
3519
|
+
// TODO: `touched` trigger also compares to old value and should use the internalUntracked flag
|
|
3520
|
+
let internalUntracked = false;
|
|
3464
3521
|
const reactiveHandlers = {
|
|
3465
3522
|
[Symbol.toStringTag]: 'MutTs Reactive',
|
|
3466
3523
|
get(obj, prop, receiver) {
|
|
3524
|
+
if (internalUntracked)
|
|
3525
|
+
return FoolProof.get(obj, prop, receiver);
|
|
3467
3526
|
if (obj && typeof obj === 'object' && prop !== Symbol.toStringTag) {
|
|
3468
3527
|
const metaProto = metaProtos.get(obj.constructor);
|
|
3469
3528
|
if (metaProto && Object.hasOwn(metaProto, prop)) {
|
|
@@ -3483,15 +3542,21 @@ const reactiveHandlers = {
|
|
|
3483
3542
|
if (wrapProto && Object.hasOwn(wrapProto, prop))
|
|
3484
3543
|
return wrapProto[prop];
|
|
3485
3544
|
}
|
|
3486
|
-
// Symbols: fast-path — no reactivity tracking
|
|
3487
|
-
if (typeof prop === 'symbol')
|
|
3488
|
-
return FoolProof.get(obj, prop, receiver);
|
|
3489
|
-
// Check if this property is marked as unreactive (WeakMap lookup — no proxy traps)
|
|
3490
|
-
if (isUnreactiveProp(obj, prop))
|
|
3545
|
+
// Symbols: fast-path — no reactivity tracking
|
|
3546
|
+
if (typeof prop === 'symbol' || prop === 'constructor' || isUnreactiveProp(obj, prop))
|
|
3491
3547
|
return FoolProof.get(obj, prop, receiver);
|
|
3492
3548
|
// Check if property exists using a trap-free walk to avoid triggering
|
|
3493
3549
|
// the has-trap cascade on prototype chains of reactive proxies.
|
|
3494
3550
|
const isOwnProp = Object.hasOwn(obj, prop);
|
|
3551
|
+
// For accessor properties, check the unwrapped object to see if it's an accessor
|
|
3552
|
+
// This ensures ignoreAccessors works correctly even after operations like Object.setPrototypeOf
|
|
3553
|
+
// Skip for null-proto objects (pounce scopes) — they never have accessors
|
|
3554
|
+
const shouldIgnoreAccessor = options.ignoreAccessors &&
|
|
3555
|
+
isOwnProp &&
|
|
3556
|
+
Object.getPrototypeOf(obj) !== null &&
|
|
3557
|
+
(isOwnAccessor(receiver, prop) || isOwnAccessor(obj, prop));
|
|
3558
|
+
// Check if property exists using a trap-free walk to avoid triggering
|
|
3559
|
+
// the has-trap cascade on prototype chains of reactive proxies.
|
|
3495
3560
|
let hasProp = isOwnProp;
|
|
3496
3561
|
let owner = isOwnProp ? obj : undefined;
|
|
3497
3562
|
if (!isOwnProp) {
|
|
@@ -3506,29 +3571,20 @@ const reactiveHandlers = {
|
|
|
3506
3571
|
}
|
|
3507
3572
|
}
|
|
3508
3573
|
const isInheritedAccess = hasProp && !isOwnProp;
|
|
3509
|
-
// For accessor properties, check the unwrapped object to see if it's an accessor
|
|
3510
|
-
// This ensures ignoreAccessors works correctly even after operations like Object.setPrototypeOf
|
|
3511
|
-
// Skip for null-proto objects (pounce scopes) — they never have accessors
|
|
3512
|
-
const shouldIgnoreAccessor = options.ignoreAccessors &&
|
|
3513
|
-
isOwnProp &&
|
|
3514
|
-
Object.getPrototypeOf(obj) !== null &&
|
|
3515
|
-
(isOwnAccessor(receiver, prop) || isOwnAccessor(obj, prop));
|
|
3516
3574
|
// Depend if...
|
|
3517
3575
|
if (!hasProp ||
|
|
3518
3576
|
(!(options.instanceMembers && isInheritedAccess && obj instanceof Object) &&
|
|
3519
3577
|
!shouldIgnoreAccessor))
|
|
3520
3578
|
dependant(obj, prop);
|
|
3521
|
-
// Two-Point Tracking: for inherited access on null-proto chains,
|
|
3522
|
-
// the owning ancestor
|
|
3523
|
-
// "structural stability" contract: key presence in the chain is fixed at
|
|
3524
|
-
// creation time, so intermediate levels never gain/lose shadowing properties.
|
|
3579
|
+
// Two-Point Tracking: for inherited access on null-proto chains, also track
|
|
3580
|
+
// the owning ancestor so that writing directly to it triggers dependent effects.
|
|
3525
3581
|
if (isInheritedAccess && owner && (!options.instanceMembers || !(obj instanceof Object))) {
|
|
3526
3582
|
dependant(owner, prop);
|
|
3527
3583
|
}
|
|
3528
3584
|
// For arrays, use FoolProof.get (Indexer path) for numeric index reactivity.
|
|
3529
3585
|
// For all other objects, inline Reflect.get directly (skips 3 function calls).
|
|
3530
3586
|
const value = (subsRegister.get(obj)?.get || FoolProof.get)(obj, prop, receiver);
|
|
3531
|
-
if (typeof value === 'object' && value !== null) {
|
|
3587
|
+
if (!isReactive(value) && typeof value === 'object' && value !== null) {
|
|
3532
3588
|
const reactiveValue = reactiveObject(value);
|
|
3533
3589
|
// Only create back-references if this object needs them
|
|
3534
3590
|
if (needsBackReferences(obj)) {
|
|
@@ -3539,9 +3595,19 @@ const reactiveHandlers = {
|
|
|
3539
3595
|
return value;
|
|
3540
3596
|
},
|
|
3541
3597
|
set(obj, prop, value, receiver) {
|
|
3542
|
-
const
|
|
3598
|
+
const unwrapped = unwrap(receiver);
|
|
3599
|
+
if (obj !== unwrapped)
|
|
3600
|
+
return Object.defineProperty(unwrapped, prop, {
|
|
3601
|
+
value,
|
|
3602
|
+
configurable: true,
|
|
3603
|
+
writable: true,
|
|
3604
|
+
enumerable: true,
|
|
3605
|
+
});
|
|
3606
|
+
if (internalUntracked)
|
|
3607
|
+
throw new Error('Internal untracked: setting a value in an getter in a set operation');
|
|
3608
|
+
//return FoolProof.set(obj, prop, value, receiver)
|
|
3543
3609
|
// Check if this property is marked as unreactive
|
|
3544
|
-
if (isUnreactiveProp(obj, prop)
|
|
3610
|
+
if (isUnreactiveProp(obj, prop))
|
|
3545
3611
|
return FoolProof.set(obj, prop, value, receiver);
|
|
3546
3612
|
const newValue = unwrap(value);
|
|
3547
3613
|
// metaProto setter dispatch (e.g., reactive array length)
|
|
@@ -3558,16 +3624,19 @@ const reactiveHandlers = {
|
|
|
3558
3624
|
// Read old value, using withEffect(undefined, ...) for getter-only accessors to avoid
|
|
3559
3625
|
// breaking memoization dependency tracking during SET operations
|
|
3560
3626
|
let oldVal = absent;
|
|
3561
|
-
// TODO: Pffft... Find a way to "generalize" this case?
|
|
3562
3627
|
const isArrayLength = prop === 'length' && Array.isArray(obj);
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3628
|
+
internalUntracked = true;
|
|
3629
|
+
try {
|
|
3630
|
+
if (Reflect.has(obj, prop)) {
|
|
3631
|
+
oldVal = isArrayLength
|
|
3632
|
+
? arrayLengths.get(obj) === newValue
|
|
3633
|
+
? newValue
|
|
3634
|
+
: absent
|
|
3635
|
+
: Reflect.get(obj, prop, receiver);
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
finally {
|
|
3639
|
+
internalUntracked = false;
|
|
3571
3640
|
}
|
|
3572
3641
|
if (objectsWithDeepWatchers.has(obj)) {
|
|
3573
3642
|
if (typeof oldVal === 'object' && oldVal !== null) {
|
|
@@ -3596,7 +3665,8 @@ const reactiveHandlers = {
|
|
|
3596
3665
|
cycle: [], // We don't have the full cycle here, but we know it involves obj
|
|
3597
3666
|
});
|
|
3598
3667
|
hasReentry.add(obj);
|
|
3599
|
-
|
|
3668
|
+
if (!internalUntracked && !isUnreactiveProp(obj, prop))
|
|
3669
|
+
dependant(obj, prop);
|
|
3600
3670
|
const rv = (subsRegister.get(obj)?.has || Reflect.has)(obj, prop);
|
|
3601
3671
|
hasReentry.delete(obj);
|
|
3602
3672
|
return rv;
|
|
@@ -3622,7 +3692,8 @@ const reactiveHandlers = {
|
|
|
3622
3692
|
return subsRegister.get(obj)?.ownKeys?.(obj) || Reflect.ownKeys(obj);
|
|
3623
3693
|
},
|
|
3624
3694
|
getOwnPropertyDescriptor(obj, prop) {
|
|
3625
|
-
return subsRegister.get(obj)?.getOwnPropertyDescriptor?.(obj, prop) ||
|
|
3695
|
+
return (subsRegister.get(obj)?.getOwnPropertyDescriptor?.(obj, prop) ||
|
|
3696
|
+
Reflect.getOwnPropertyDescriptor(obj, prop));
|
|
3626
3697
|
},
|
|
3627
3698
|
};
|
|
3628
3699
|
const reactiveClasses = new WeakSet();
|
|
@@ -3697,5 +3768,5 @@ const reactive = decorator({
|
|
|
3697
3768
|
default: reactiveObject,
|
|
3698
3769
|
});
|
|
3699
3770
|
|
|
3700
|
-
export {
|
|
3701
|
-
//# sourceMappingURL=proxy-
|
|
3771
|
+
export { reactive as $, AZone as A, flavored as B, formatCleanupReason as C, DecoratorError as D, getActivationLog as E, getActiveEffect as F, getState as G, hooks as H, IterableWeakMap as I, isConstructor as J, isDev as K, isNonReactive as L, isObject as M, isProd as N, isReactive as O, isTest as P, legacyDecorator as Q, ReactiveBase as R, link as S, mixin as T, modernDecorator as U, named as V, objectToProxy as W, onEffectThrow as X, prodPreset as Y, Zone as Z, proxyToObject as _, IterableWeakSet as a, options as a0, reset as a1, root as a2, tag as a3, touched as a4, touched1 as a5, unlink as a6, untracked as a7, unwrap as a8, zip as a9, allProps as aA, dependant as aa, keysOf as ab, markWithRoot as ac, objectsWithDeepWatchers as ad, effectToDeepWatchedObjects as ae, deepWatchers as af, registerDeepWatcher as ag, rootFunctionSymbol as ah, getRoot as ai, optionCall as aj, FoolProof as ak, effectHistory as al, unreactiveProperties as am, __runInitializers as an, __esDecorate as ao, batch as ap, contentRef as aq, notifyPropertyChange as ar, metaProtos as as, wrapProtos as at, objectParents as au, watchers as av, effectToReactiveObjects as aw, effectMarker as ax, getEffectNode as ay, setDebugHooks as az, ReactiveError as b, ReactiveErrorCode as c, ZoneAggregator as d, ZoneHistory as e, addBatchCleanup as f, addUnreactiveProps as g, arrayEquals as h, assertUntracked as i, asyncHook as j, asyncHooks as k, asyncZone as l, atom as m, atomic as n, biDi as o, captured as p, caught as q, createFlavor as r, debugPreset as s, decorator as t, deepCompare as u, defer as v, devPreset as w, effect as x, effectAggregator as y, flavorOptions as z };
|
|
3772
|
+
//# sourceMappingURL=proxy-D2C49sXH.esm.js.map
|