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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var asyncCore = require('./async-core-CRLKP3l-.cjs');
|
|
4
4
|
|
|
5
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
5
6
|
/**
|
|
6
7
|
* Yields tuples containing elements from each input array, stopping at the longest array length
|
|
7
8
|
* @param args - Arrays to zip together
|
|
@@ -99,16 +100,16 @@ const FoolProof = {
|
|
|
99
100
|
if (hasNode && obj instanceof Node) {
|
|
100
101
|
obj[prop] = value;
|
|
101
102
|
return true;
|
|
102
|
-
}
|
|
103
|
-
if (!(obj instanceof Object) && !
|
|
103
|
+
} /*
|
|
104
|
+
if (!(obj instanceof Object) && !Object.hasOwn(obj, prop)) {
|
|
104
105
|
Object.defineProperty(obj, prop, {
|
|
105
106
|
value,
|
|
106
107
|
configurable: true,
|
|
107
108
|
writable: true,
|
|
108
109
|
enumerable: true,
|
|
109
|
-
})
|
|
110
|
-
return true
|
|
111
|
-
}
|
|
110
|
+
})
|
|
111
|
+
return true
|
|
112
|
+
}*/
|
|
112
113
|
return Reflect.set(obj, prop, value, receiver);
|
|
113
114
|
},
|
|
114
115
|
};
|
|
@@ -132,15 +133,8 @@ function deepCompare(a, b, cache = new Map()) {
|
|
|
132
133
|
return a === b;
|
|
133
134
|
}
|
|
134
135
|
// 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
|
-
});
|
|
136
|
+
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b))
|
|
142
137
|
return false;
|
|
143
|
-
}
|
|
144
138
|
// Circular reference protection
|
|
145
139
|
let compared = cache.get(a);
|
|
146
140
|
if (compared?.has(b))
|
|
@@ -152,39 +146,21 @@ function deepCompare(a, b, cache = new Map()) {
|
|
|
152
146
|
compared.add(b);
|
|
153
147
|
// Handle specific object types
|
|
154
148
|
if (Array.isArray(a)) {
|
|
155
|
-
if (!Array.isArray(b))
|
|
156
|
-
console.warn(`[deepCompare] B is not an array`);
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
if (a.length !== b.length) {
|
|
160
|
-
console.warn(`[deepCompare] array length mismatch:`, { lenA: a.length, lenB: b.length });
|
|
149
|
+
if (!Array.isArray(b) || a.length !== b.length)
|
|
161
150
|
return false;
|
|
162
|
-
}
|
|
163
151
|
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}`);
|
|
152
|
+
if (!deepCompare(a[i], b[i], cache))
|
|
166
153
|
return false;
|
|
167
|
-
}
|
|
168
154
|
}
|
|
169
155
|
return true;
|
|
170
156
|
}
|
|
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
|
-
}
|
|
157
|
+
if (a instanceof Date)
|
|
158
|
+
return b instanceof Date && a.getTime() === b.getTime();
|
|
159
|
+
if (a instanceof RegExp)
|
|
160
|
+
return b instanceof RegExp && a.toString() === b.toString();
|
|
183
161
|
if (a instanceof Set) {
|
|
184
|
-
if (!(b instanceof Set) || a.size !== b.size)
|
|
185
|
-
console.warn(`[deepCompare] Set size mismatch`);
|
|
162
|
+
if (!(b instanceof Set) || a.size !== b.size)
|
|
186
163
|
return false;
|
|
187
|
-
}
|
|
188
164
|
for (const val of a) {
|
|
189
165
|
let found = false;
|
|
190
166
|
for (const bVal of b) {
|
|
@@ -193,18 +169,14 @@ function deepCompare(a, b, cache = new Map()) {
|
|
|
193
169
|
break;
|
|
194
170
|
}
|
|
195
171
|
}
|
|
196
|
-
if (!found)
|
|
197
|
-
console.warn(`[deepCompare] missing Set element`);
|
|
172
|
+
if (!found)
|
|
198
173
|
return false;
|
|
199
|
-
}
|
|
200
174
|
}
|
|
201
175
|
return true;
|
|
202
176
|
}
|
|
203
177
|
if (a instanceof Map) {
|
|
204
|
-
if (!(b instanceof Map) || a.size !== b.size)
|
|
205
|
-
console.warn(`[deepCompare] Map size mismatch`);
|
|
178
|
+
if (!(b instanceof Map) || a.size !== b.size)
|
|
206
179
|
return false;
|
|
207
|
-
}
|
|
208
180
|
for (const [key, val] of a) {
|
|
209
181
|
if (!b.has(key)) {
|
|
210
182
|
let foundMatch = false;
|
|
@@ -214,16 +186,11 @@ function deepCompare(a, b, cache = new Map()) {
|
|
|
214
186
|
break;
|
|
215
187
|
}
|
|
216
188
|
}
|
|
217
|
-
if (!foundMatch)
|
|
218
|
-
console.warn(`[deepCompare] missing Map key`);
|
|
189
|
+
if (!foundMatch)
|
|
219
190
|
return false;
|
|
220
|
-
}
|
|
221
191
|
}
|
|
222
|
-
else {
|
|
223
|
-
|
|
224
|
-
console.warn(`[deepCompare] Map value mismatch for key`);
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
192
|
+
else if (!deepCompare(val, b.get(key), cache)) {
|
|
193
|
+
return false;
|
|
227
194
|
}
|
|
228
195
|
}
|
|
229
196
|
return true;
|
|
@@ -231,29 +198,11 @@ function deepCompare(a, b, cache = new Map()) {
|
|
|
231
198
|
// Compare own properties
|
|
232
199
|
const keysA = Object.keys(a);
|
|
233
200
|
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
|
-
});
|
|
201
|
+
if (keysA.length !== keysB.length)
|
|
243
202
|
return false;
|
|
244
|
-
}
|
|
245
203
|
for (const key of keysA) {
|
|
246
|
-
if (!Object.hasOwn(b, key))
|
|
247
|
-
console.warn(`[deepCompare] missing key ${String(key)} in B`);
|
|
204
|
+
if (!Object.hasOwn(b, key) || !deepCompare(a[key], b[key], cache))
|
|
248
205
|
return false;
|
|
249
|
-
}
|
|
250
|
-
if (!deepCompare(a[key], b[key], cache)) {
|
|
251
|
-
console.warn(`[deepCompare] value mismatch for key ${String(key)}:`, {
|
|
252
|
-
valA: a[key],
|
|
253
|
-
valB: b[key],
|
|
254
|
-
});
|
|
255
|
-
return false;
|
|
256
|
-
}
|
|
257
206
|
}
|
|
258
207
|
return true;
|
|
259
208
|
}
|
|
@@ -301,6 +250,12 @@ function named(name, fn) {
|
|
|
301
250
|
});
|
|
302
251
|
return fn;
|
|
303
252
|
}
|
|
253
|
+
const _mode = (typeof process !== 'undefined' && process.env?.NODE_ENV) ||
|
|
254
|
+
(typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunks/proxy-Cc79Lrzj.cjs', document.baseURI).href)) }) !== 'undefined' && undefined?.MODE) ||
|
|
255
|
+
'production';
|
|
256
|
+
const isDev = _mode === 'development';
|
|
257
|
+
const isProd = _mode === 'production';
|
|
258
|
+
const isTest = _mode === 'test';
|
|
304
259
|
|
|
305
260
|
// biome-ignore-all lint/suspicious/noConfusingVoidType: We *love* voids
|
|
306
261
|
// Standardized decorator system that works with both Legacy and Modern decorators
|
|
@@ -528,9 +483,7 @@ function flavorOptions(fn, defaultOptions, opts = {}) {
|
|
|
528
483
|
const isObject = currentOptions !== null &&
|
|
529
484
|
typeof currentOptions === 'object' &&
|
|
530
485
|
!Array.isArray(currentOptions);
|
|
531
|
-
newArgs[targetIndex] = isObject
|
|
532
|
-
? { ...defaultOptions, ...currentOptions }
|
|
533
|
-
: defaultOptions;
|
|
486
|
+
newArgs[targetIndex] = isObject ? { ...defaultOptions, ...currentOptions } : defaultOptions;
|
|
534
487
|
return fn.apply(this, newArgs);
|
|
535
488
|
};
|
|
536
489
|
if (opts.name)
|
|
@@ -871,6 +824,20 @@ function mixin(mixinFunction, unwrapFunction) {
|
|
|
871
824
|
});
|
|
872
825
|
}
|
|
873
826
|
|
|
827
|
+
const debugHooks = {
|
|
828
|
+
isDevtoolsEnabled: () => false,
|
|
829
|
+
registerEffect: () => { },
|
|
830
|
+
getTriggerChain: () => [],
|
|
831
|
+
captureStack: () => [],
|
|
832
|
+
captureLineage: () => new Error().stack,
|
|
833
|
+
formatStack: (stack) => [stack],
|
|
834
|
+
recordTriggerLink: () => { },
|
|
835
|
+
decorateError: () => { },
|
|
836
|
+
};
|
|
837
|
+
function setDebugHooks(hooks) {
|
|
838
|
+
Object.assign(debugHooks, hooks);
|
|
839
|
+
}
|
|
840
|
+
|
|
874
841
|
/******************************************************************************
|
|
875
842
|
Copyright (c) Microsoft Corporation.
|
|
876
843
|
|
|
@@ -922,41 +889,17 @@ function __runInitializers(thisArg, initializers, value) {
|
|
|
922
889
|
}
|
|
923
890
|
return useValue ? value : void 0;
|
|
924
891
|
}
|
|
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
892
|
function __classPrivateFieldGet(receiver, state, kind, f) {
|
|
930
893
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
931
894
|
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
895
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
933
896
|
}
|
|
934
897
|
|
|
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
898
|
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
943
899
|
var e = new Error(message);
|
|
944
900
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
945
901
|
};
|
|
946
902
|
|
|
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
903
|
var _ZoneAggregator_zones;
|
|
961
904
|
function isu(z) {
|
|
962
905
|
return z;
|
|
@@ -1084,13 +1027,27 @@ class ZoneAggregator extends AZone {
|
|
|
1084
1027
|
}
|
|
1085
1028
|
}
|
|
1086
1029
|
_ZoneAggregator_zones = new WeakMap();
|
|
1030
|
+
/**
|
|
1031
|
+
* Aggregator of zones that should be preserved across async boundaries.
|
|
1032
|
+
* If you add a zone here, it will be preserved across async boundaries.
|
|
1033
|
+
*
|
|
1034
|
+
* @example
|
|
1035
|
+
* ```ts
|
|
1036
|
+
* import { Zone, asyncZone } from 'mutts'
|
|
1037
|
+
* const userZone = new Zone<User>()
|
|
1038
|
+
* asyncZone.add(userZone)
|
|
1039
|
+
* ```
|
|
1040
|
+
*/
|
|
1087
1041
|
const asyncZone = tag('async', new ZoneAggregator());
|
|
1088
1042
|
asyncCore.asyncHooks.addHook(() => {
|
|
1043
|
+
// capture state before async boundary
|
|
1089
1044
|
const zone = asyncZone.active;
|
|
1090
1045
|
return () => {
|
|
1046
|
+
// restore state after async boundary, temporarily
|
|
1091
1047
|
const prev = asyncZone.active;
|
|
1092
1048
|
asyncZone.active = zone;
|
|
1093
1049
|
return () => {
|
|
1050
|
+
// restore previous state from before our restore
|
|
1094
1051
|
asyncZone.active = prev;
|
|
1095
1052
|
};
|
|
1096
1053
|
};
|
|
@@ -1160,6 +1117,75 @@ function getRoot(fn) {
|
|
|
1160
1117
|
return fn;
|
|
1161
1118
|
}
|
|
1162
1119
|
|
|
1120
|
+
const effectHistory = tag('effectHistory', new ZoneHistory());
|
|
1121
|
+
tag('effectHistory.present', effectHistory.present);
|
|
1122
|
+
asyncZone.add(effectHistory);
|
|
1123
|
+
/**
|
|
1124
|
+
* Aggregator for zones that need to be tracked along effects.
|
|
1125
|
+
* ie. in each effect, the active zone of the given zoning will be the one active at effect's definition
|
|
1126
|
+
*/
|
|
1127
|
+
const effectAggregator = tag('effectAggregator', new ZoneAggregator(effectHistory.present));
|
|
1128
|
+
function isRunning(effect) {
|
|
1129
|
+
const root = getRoot(effect);
|
|
1130
|
+
return effectHistory.some((e) => getRoot(e) === root);
|
|
1131
|
+
}
|
|
1132
|
+
function getActiveEffect() {
|
|
1133
|
+
return effectHistory.present.active;
|
|
1134
|
+
}
|
|
1135
|
+
const cleanups = new WeakMap();
|
|
1136
|
+
/**
|
|
1137
|
+
* Attach cleanup dependencies to an object. When `unlink(obj)` is called,
|
|
1138
|
+
* each dependency is disposed: functions are invoked with the cleanup reason,
|
|
1139
|
+
* objects are recursively `unlink`ed. This forms a cleanup tree.
|
|
1140
|
+
*
|
|
1141
|
+
* @param obj - The owner object
|
|
1142
|
+
* @param cleanupFns - Cleanup callbacks and/or child objects to unlink recursively
|
|
1143
|
+
* @returns The owner object (for chaining)
|
|
1144
|
+
*
|
|
1145
|
+
* @example
|
|
1146
|
+
* ```ts
|
|
1147
|
+
* // Functions are called with CleanupReason
|
|
1148
|
+
* link(parent, () => console.log('disposed'))
|
|
1149
|
+
*
|
|
1150
|
+
* // Objects are recursively unlinked
|
|
1151
|
+
* link(parent, childA, childB)
|
|
1152
|
+
*
|
|
1153
|
+
* // Mixed
|
|
1154
|
+
* link(parent, childObj, () => timer.clear())
|
|
1155
|
+
*
|
|
1156
|
+
* unlink(parent) // disposes childA, childB, calls the function
|
|
1157
|
+
* ```
|
|
1158
|
+
*/
|
|
1159
|
+
function link(obj, ...cleanupFns) {
|
|
1160
|
+
const set = cleanups.get(obj);
|
|
1161
|
+
if (!set)
|
|
1162
|
+
cleanups.set(obj, new Set(cleanupFns.filter(Boolean)));
|
|
1163
|
+
else
|
|
1164
|
+
for (const fn of cleanupFns)
|
|
1165
|
+
if (fn)
|
|
1166
|
+
set.add(fn);
|
|
1167
|
+
return obj;
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Dispose an object's cleanup dependencies. Functions are called with the
|
|
1171
|
+
* reason; linked objects are recursively unlinked. The cleanup set is removed
|
|
1172
|
+
* so calling `unlink` twice is safe (second call is a no-op).
|
|
1173
|
+
*
|
|
1174
|
+
* @param obj - The object to dispose
|
|
1175
|
+
* @param reason - Optional cleanup reason propagated to callbacks
|
|
1176
|
+
*/
|
|
1177
|
+
function unlink(obj, reason) {
|
|
1178
|
+
const set = cleanups.get(obj);
|
|
1179
|
+
if (set) {
|
|
1180
|
+
cleanups.delete(obj);
|
|
1181
|
+
for (const fn of set)
|
|
1182
|
+
if (typeof fn === 'function')
|
|
1183
|
+
fn(reason);
|
|
1184
|
+
else
|
|
1185
|
+
unlink(fn, reason);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1163
1189
|
const effectMarker = {
|
|
1164
1190
|
enter: 'effect:enter',
|
|
1165
1191
|
leave: 'effect:leave',
|
|
@@ -1222,6 +1248,11 @@ function formatCleanupReason(reason, depth = 0) {
|
|
|
1222
1248
|
}
|
|
1223
1249
|
}
|
|
1224
1250
|
}
|
|
1251
|
+
// Track native reactivity
|
|
1252
|
+
/**
|
|
1253
|
+
* Symbol to mark class properties as non-reactive
|
|
1254
|
+
*/
|
|
1255
|
+
const unreactiveProperties = Symbol('unreactive-properties');
|
|
1225
1256
|
/**
|
|
1226
1257
|
* Symbol representing all properties in reactive tracking
|
|
1227
1258
|
*/
|
|
@@ -1231,14 +1262,6 @@ const allProps = Symbol('all-props');
|
|
|
1231
1262
|
* Used by ownKeys proxy trap — Object.keys(), for..in, Map.keys() depend on this.
|
|
1232
1263
|
*/
|
|
1233
1264
|
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
1265
|
/**
|
|
1243
1266
|
* Structured error codes for machine-readable diagnosis
|
|
1244
1267
|
*/
|
|
@@ -1312,6 +1335,12 @@ const options = {
|
|
|
1312
1335
|
* @param runningChain - The array of effects from the detected one to the currently running one
|
|
1313
1336
|
*/
|
|
1314
1337
|
skipRunningEffect: (_effect) => { },
|
|
1338
|
+
/**
|
|
1339
|
+
* Debug purpose: called when an effect starts executing.
|
|
1340
|
+
* @param effect - The effect being executed (root function)
|
|
1341
|
+
* @param reaction - false for initial creation, true/CleanupReason for subsequent runs
|
|
1342
|
+
*/
|
|
1343
|
+
effectRun: (_effect, _reaction) => { },
|
|
1315
1344
|
/**
|
|
1316
1345
|
* Debug purpose: maximum effect chain (like call stack max depth)
|
|
1317
1346
|
* Used to prevent infinite loops
|
|
@@ -1438,34 +1467,6 @@ const options = {
|
|
|
1438
1467
|
enableHistory: true,
|
|
1439
1468
|
historySize: 50,
|
|
1440
1469
|
},
|
|
1441
|
-
/**
|
|
1442
|
-
* Configuration for zone hooks - control which async APIs are hooked
|
|
1443
|
-
* Each option controls whether the corresponding async API is wrapped to preserve effect context
|
|
1444
|
-
* Only applies when asyncMode is enabled (truthy)
|
|
1445
|
-
* @deprecated Should take all when we made sure PIXI.create, Game.create, ... are -> .root()
|
|
1446
|
-
*/
|
|
1447
|
-
zones: {
|
|
1448
|
-
/**
|
|
1449
|
-
* Hook setTimeout to preserve effect context
|
|
1450
|
-
* @default true
|
|
1451
|
-
*/
|
|
1452
|
-
setTimeout: true,
|
|
1453
|
-
/**
|
|
1454
|
-
* Hook setInterval to preserve effect context
|
|
1455
|
-
* @default true
|
|
1456
|
-
*/
|
|
1457
|
-
setInterval: true,
|
|
1458
|
-
/**
|
|
1459
|
-
* Hook requestAnimationFrame (runs in untracked context when hooked)
|
|
1460
|
-
* @default true
|
|
1461
|
-
*/
|
|
1462
|
-
requestAnimationFrame: true,
|
|
1463
|
-
/**
|
|
1464
|
-
* Hook queueMicrotask to preserve effect context
|
|
1465
|
-
* @default true
|
|
1466
|
-
*/
|
|
1467
|
-
queueMicrotask: true,
|
|
1468
|
-
},
|
|
1469
1470
|
};
|
|
1470
1471
|
function optionCall(name, ...args) {
|
|
1471
1472
|
const fn = options[name];
|
|
@@ -1479,6 +1480,36 @@ function optionCall(name, ...args) {
|
|
|
1479
1480
|
options.warn(`options.${name} threw`, error);
|
|
1480
1481
|
}
|
|
1481
1482
|
}
|
|
1483
|
+
/** Production preset: no introspection, heuristic cycle detection, minimal overhead */
|
|
1484
|
+
const prodPreset = {
|
|
1485
|
+
maxEffectReaction: 'throw',
|
|
1486
|
+
cycleHandling: 'production',
|
|
1487
|
+
introspection: null,
|
|
1488
|
+
onMemoizationDiscrepancy: undefined,
|
|
1489
|
+
};
|
|
1490
|
+
/** Development preset (default): introspection on, early cycle detection, warnings */
|
|
1491
|
+
const devPreset = {
|
|
1492
|
+
maxEffectReaction: 'warn',
|
|
1493
|
+
cycleHandling: 'development',
|
|
1494
|
+
introspection: {
|
|
1495
|
+
gatherReasons: { lineages: 'touch' },
|
|
1496
|
+
logErrors: true,
|
|
1497
|
+
enableHistory: true,
|
|
1498
|
+
historySize: 50,
|
|
1499
|
+
},
|
|
1500
|
+
onMemoizationDiscrepancy: undefined,
|
|
1501
|
+
};
|
|
1502
|
+
/** Debug preset: full diagnostics, throws on violations, rich lineage capture */
|
|
1503
|
+
const debugPreset = {
|
|
1504
|
+
maxEffectReaction: 'debug',
|
|
1505
|
+
cycleHandling: 'debug',
|
|
1506
|
+
introspection: {
|
|
1507
|
+
gatherReasons: { lineages: 'both' },
|
|
1508
|
+
logErrors: true,
|
|
1509
|
+
enableHistory: true,
|
|
1510
|
+
historySize: 200,
|
|
1511
|
+
},
|
|
1512
|
+
};
|
|
1482
1513
|
// --- Proxy State (Merged from proxy-state.ts) ---
|
|
1483
1514
|
const objectToProxy = new WeakMap();
|
|
1484
1515
|
const proxyToObject = new WeakMap();
|
|
@@ -1498,54 +1529,28 @@ function isReactive(obj) {
|
|
|
1498
1529
|
return proxyToObject.has(obj);
|
|
1499
1530
|
}
|
|
1500
1531
|
|
|
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
1532
|
// Track dependency stacks per (obj, prop, effect)
|
|
1545
1533
|
let dependencyStacks = new WeakMap();
|
|
1534
|
+
let assertUntrackedFlag = false;
|
|
1546
1535
|
function resetTracking() {
|
|
1547
1536
|
dependencyStacks = new WeakMap();
|
|
1548
1537
|
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Executes a function and throws if any reactive dependencies are tracked during execution.
|
|
1540
|
+
* Used to assert that code runs in an untracked context.
|
|
1541
|
+
*/
|
|
1542
|
+
function assertUntracked(fn) {
|
|
1543
|
+
if (assertUntrackedFlag) {
|
|
1544
|
+
throw new Error('assertUntracked: nested calls are not supported');
|
|
1545
|
+
}
|
|
1546
|
+
assertUntrackedFlag = true;
|
|
1547
|
+
try {
|
|
1548
|
+
return fn();
|
|
1549
|
+
}
|
|
1550
|
+
finally {
|
|
1551
|
+
assertUntrackedFlag = false;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1549
1554
|
function getDependencyStack(effect, obj, prop) {
|
|
1550
1555
|
const objStacks = dependencyStacks.get(obj);
|
|
1551
1556
|
if (!objStacks)
|
|
@@ -1558,14 +1563,17 @@ function getDependencyStack(effect, obj, prop) {
|
|
|
1558
1563
|
* @param prop - The property name (defaults to allProps)
|
|
1559
1564
|
*/
|
|
1560
1565
|
function dependant(obj, prop = allProps) {
|
|
1566
|
+
if (assertUntrackedFlag) {
|
|
1567
|
+
throw new Error(`Reactive dependency tracking detected in assertUntracked context: ${String(prop)} on ${obj}`);
|
|
1568
|
+
}
|
|
1561
1569
|
obj = unwrap(obj);
|
|
1562
1570
|
const currentActiveEffect = getActiveEffect();
|
|
1563
1571
|
// Early return if no active effect, tracking disabled, or invalid prop
|
|
1564
1572
|
if (!currentActiveEffect || (typeof prop === 'symbol' && prop !== allProps && prop !== keysOf))
|
|
1565
1573
|
return;
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1574
|
+
const node = getEffectNode(currentActiveEffect);
|
|
1575
|
+
if ('dependencyHook' in node) {
|
|
1576
|
+
node.dependencyHook(obj, prop);
|
|
1569
1577
|
}
|
|
1570
1578
|
let objectWatchers = exports.watchers.get(obj);
|
|
1571
1579
|
if (!objectWatchers) {
|
|
@@ -1702,6 +1710,7 @@ let effectTriggeredBy = new WeakMap();
|
|
|
1702
1710
|
// consequencesClosure: for each effect, all effects that it triggers (directly or indirectly)
|
|
1703
1711
|
let causesClosure = new WeakMap();
|
|
1704
1712
|
let consequencesClosure = new WeakMap();
|
|
1713
|
+
// Batch re-entrance depth and broken state
|
|
1705
1714
|
let broken = false;
|
|
1706
1715
|
/**
|
|
1707
1716
|
* Gets or creates an IterableWeakSet for a closure map
|
|
@@ -2355,6 +2364,7 @@ function batch(effect, immediate) {
|
|
|
2355
2364
|
throw new Error('Activation registry already exists');
|
|
2356
2365
|
optionCall('beginChain', roots);
|
|
2357
2366
|
}
|
|
2367
|
+
// TODO: Consider this has been produced but was useless - it might be more correct ?const caller = executingStack.length > 0 ? getActiveEffect() : undefined
|
|
2358
2368
|
const caller = getActiveEffect();
|
|
2359
2369
|
// Optimization: If nested and NOT immediate, just join the existing batch
|
|
2360
2370
|
if (!isNewBatch && !immediate) {
|
|
@@ -2508,6 +2518,22 @@ const atomic = decorator({
|
|
|
2508
2518
|
};
|
|
2509
2519
|
},
|
|
2510
2520
|
});
|
|
2521
|
+
/**
|
|
2522
|
+
* Wraps `fn` so it runs within `effect`'s zone context when invoked later.
|
|
2523
|
+
*
|
|
2524
|
+
* Useful for deferred callbacks (event listeners, `DOMContentLoaded`, etc.)
|
|
2525
|
+
* that need sub-effects parented to the original effect.
|
|
2526
|
+
*
|
|
2527
|
+
* @param prev - The effect whose context should be restored, or `undefined` for root context
|
|
2528
|
+
* @param fn - The function to wrap
|
|
2529
|
+
* @returns A function with the same signature that restores the effect context before calling `fn`
|
|
2530
|
+
*/
|
|
2531
|
+
function captured(prev, fn) {
|
|
2532
|
+
prev ?? (prev = effectHistory.active);
|
|
2533
|
+
return named(effectMarker.leave, (...args) => {
|
|
2534
|
+
return effectHistory.with(prev, () => fn(...args));
|
|
2535
|
+
});
|
|
2536
|
+
}
|
|
2511
2537
|
/**
|
|
2512
2538
|
* Runs `fn` atomically and **always immediately**, batching all reactive effects
|
|
2513
2539
|
* triggered inside it so they fire only once after `fn` completes.
|
|
@@ -2530,19 +2556,13 @@ function atom(fn) {
|
|
|
2530
2556
|
return batch(fn, 'immediate');
|
|
2531
2557
|
}
|
|
2532
2558
|
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
2559
|
/**
|
|
2538
2560
|
* Creates a reactive effect that automatically re-runs when dependencies change
|
|
2539
2561
|
* @param fn - The effect function that provides dependencies and may return a cleanup function or Promise
|
|
2540
2562
|
* @param options - Options for effect execution
|
|
2541
2563
|
* @returns A cleanup function to stop the effect
|
|
2542
2564
|
*/
|
|
2543
|
-
const effect = named(effectMarker.leave, flavored(function effect(
|
|
2544
|
-
// biome-ignore lint/suspicious/noConfusingVoidType: Effect callbacks commonly return void
|
|
2545
|
-
fn, effectOptions = {}) {
|
|
2565
|
+
const effect = named(effectMarker.leave, flavored(function effect(fn, effectOptions = {}) {
|
|
2546
2566
|
if (effectOptions?.name)
|
|
2547
2567
|
Object.defineProperty(fn, 'name', { value: effectOptions.name });
|
|
2548
2568
|
// Use per-effect asyncMode or fall back to global option
|
|
@@ -2566,6 +2586,7 @@ fn, effectOptions = {}) {
|
|
|
2566
2586
|
if (runningPromise) {
|
|
2567
2587
|
if (asyncMode === 'cancel' && cancelPrevious) {
|
|
2568
2588
|
// Cancel previous execution
|
|
2589
|
+
abort();
|
|
2569
2590
|
cancelPrevious();
|
|
2570
2591
|
cancelPrevious = null;
|
|
2571
2592
|
runningPromise = null;
|
|
@@ -2589,11 +2610,33 @@ fn, effectOptions = {}) {
|
|
|
2589
2610
|
access.reaction = node.nextReason || access.reaction;
|
|
2590
2611
|
node.nextReason = undefined;
|
|
2591
2612
|
optionCall('enter', getRoot(fn));
|
|
2613
|
+
optionCall('effectRun', getRoot(fn), access.reaction);
|
|
2592
2614
|
let result;
|
|
2593
2615
|
let caught = 0;
|
|
2594
|
-
//
|
|
2595
|
-
|
|
2596
|
-
|
|
2616
|
+
// Define bubbling thrower
|
|
2617
|
+
const thrower = (error) => {
|
|
2618
|
+
const catches = node.catchers;
|
|
2619
|
+
const reason = { type: 'error', error };
|
|
2620
|
+
if (catches)
|
|
2621
|
+
while (caught < catches.length) {
|
|
2622
|
+
cleanupReaction(reason);
|
|
2623
|
+
try {
|
|
2624
|
+
reactionCleanup = catches[caught](error);
|
|
2625
|
+
return;
|
|
2626
|
+
}
|
|
2627
|
+
catch (_e) {
|
|
2628
|
+
caught++;
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
if (parent) {
|
|
2632
|
+
const parentNode = getEffectNode(parent);
|
|
2633
|
+
if (parentNode.forwardThrow)
|
|
2634
|
+
parentNode.forwardThrow(error);
|
|
2635
|
+
else
|
|
2636
|
+
throw error;
|
|
2637
|
+
}
|
|
2638
|
+
else
|
|
2639
|
+
throw error;
|
|
2597
2640
|
};
|
|
2598
2641
|
node.forwardThrow = thrower;
|
|
2599
2642
|
let errorToThrow;
|
|
@@ -2643,12 +2686,14 @@ fn, effectOptions = {}) {
|
|
|
2643
2686
|
}
|
|
2644
2687
|
}
|
|
2645
2688
|
catch (error) {
|
|
2689
|
+
debugHooks.decorateError(error, runEffect);
|
|
2646
2690
|
// catcher:self`
|
|
2647
2691
|
errorToThrow = error;
|
|
2648
2692
|
}
|
|
2649
2693
|
// Create cleanup function for next run
|
|
2650
2694
|
node.cleanup = (reason) => {
|
|
2651
2695
|
node.cleanup = undefined;
|
|
2696
|
+
abort();
|
|
2652
2697
|
cleanupReaction(reason);
|
|
2653
2698
|
delete node.catchers;
|
|
2654
2699
|
// Remove this effect from all reactive objects it's watching
|
|
@@ -2659,13 +2704,11 @@ fn, effectOptions = {}) {
|
|
|
2659
2704
|
if (objectWatchers) {
|
|
2660
2705
|
for (const [prop, deps] of objectWatchers.entries()) {
|
|
2661
2706
|
deps.delete(runEffect);
|
|
2662
|
-
if (deps.size === 0)
|
|
2707
|
+
if (deps.size === 0)
|
|
2663
2708
|
objectWatchers.delete(prop);
|
|
2664
|
-
}
|
|
2665
2709
|
}
|
|
2666
|
-
if (objectWatchers.size === 0)
|
|
2710
|
+
if (objectWatchers.size === 0)
|
|
2667
2711
|
exports.watchers.delete(reactiveObj);
|
|
2668
|
-
}
|
|
2669
2712
|
}
|
|
2670
2713
|
}
|
|
2671
2714
|
exports.effectToReactiveObjects.delete(runEffect);
|
|
@@ -2683,33 +2726,6 @@ fn, effectOptions = {}) {
|
|
|
2683
2726
|
delete node.children;
|
|
2684
2727
|
}
|
|
2685
2728
|
};
|
|
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
2729
|
if (errorToThrow)
|
|
2714
2730
|
thrower(errorToThrow);
|
|
2715
2731
|
};
|
|
@@ -2728,26 +2744,25 @@ fn, effectOptions = {}) {
|
|
|
2728
2744
|
node.parent = parent;
|
|
2729
2745
|
// let thrower: CatchFunction | undefined // Moved inside runEffect
|
|
2730
2746
|
let effectStopped = false;
|
|
2747
|
+
let abortController;
|
|
2731
2748
|
const access = {
|
|
2732
2749
|
tracked,
|
|
2733
2750
|
ascend: named(effectMarker.leave, (fn) => ascended(named(effectMarker.enter, () => fn.call(null)))),
|
|
2734
2751
|
//named(effectMarker.enter, (fn) => ascended(fn)),
|
|
2735
2752
|
reaction: false,
|
|
2753
|
+
get signal() {
|
|
2754
|
+
if (!abortController) {
|
|
2755
|
+
abortController = new AbortController();
|
|
2756
|
+
}
|
|
2757
|
+
return abortController.signal;
|
|
2758
|
+
},
|
|
2736
2759
|
};
|
|
2737
2760
|
let runningPromise = null;
|
|
2738
2761
|
let cancelPrevious = null;
|
|
2739
|
-
if (effectOptions?.dependencyHook)
|
|
2762
|
+
if (effectOptions?.dependencyHook)
|
|
2740
2763
|
node.dependencyHook = effectOptions.dependencyHook;
|
|
2741
|
-
}
|
|
2742
2764
|
// Mark the runEffect callback with the original function as its root
|
|
2743
2765
|
markWithRoot(runEffect, fn);
|
|
2744
|
-
function augmentedRv(rv) {
|
|
2745
|
-
return Object.defineProperties(rv, {
|
|
2746
|
-
[stopped]: {
|
|
2747
|
-
get: () => effectStopped,
|
|
2748
|
-
},
|
|
2749
|
-
});
|
|
2750
|
-
}
|
|
2751
2766
|
// Register strict mode if enabled
|
|
2752
2767
|
if (effectOptions?.opaque) {
|
|
2753
2768
|
node.isOpaque = true;
|
|
@@ -2756,6 +2771,12 @@ fn, effectOptions = {}) {
|
|
|
2756
2771
|
debugHooks.registerEffect(runEffect);
|
|
2757
2772
|
}
|
|
2758
2773
|
// Store parent relationship for hierarchy traversal - ALREADY DONE ABOVE via getEffectNode
|
|
2774
|
+
const abort = () => {
|
|
2775
|
+
if (abortController) {
|
|
2776
|
+
abortController.abort(new ReactiveError('[reactive] Effect aborted due to dependency change or stop'));
|
|
2777
|
+
abortController = undefined;
|
|
2778
|
+
}
|
|
2779
|
+
};
|
|
2759
2780
|
batch(runEffect, 'immediate');
|
|
2760
2781
|
// Only ROOT effects are registered for GC cleanup and zone tracking
|
|
2761
2782
|
const isRootEffect = !parent;
|
|
@@ -2765,6 +2786,7 @@ fn, effectOptions = {}) {
|
|
|
2765
2786
|
effectStopped = true;
|
|
2766
2787
|
node.stopped = true;
|
|
2767
2788
|
// Cancel any running async work
|
|
2789
|
+
abort();
|
|
2768
2790
|
if (cancelPrevious) {
|
|
2769
2791
|
cancelPrevious();
|
|
2770
2792
|
cancelPrevious = null;
|
|
@@ -2783,7 +2805,7 @@ fn, effectOptions = {}) {
|
|
|
2783
2805
|
fr.unregister(stopEffect);
|
|
2784
2806
|
};
|
|
2785
2807
|
if (isRootEffect) {
|
|
2786
|
-
const callIfCollected =
|
|
2808
|
+
const callIfCollected = (reason) => stopEffect(reason);
|
|
2787
2809
|
fr.register(callIfCollected, () => {
|
|
2788
2810
|
stopEffect({ type: 'gc' });
|
|
2789
2811
|
optionCall('garbageCollected', fn);
|
|
@@ -2797,16 +2819,16 @@ fn, effectOptions = {}) {
|
|
|
2797
2819
|
parentNode.children = new Set();
|
|
2798
2820
|
}
|
|
2799
2821
|
const children = parentNode.children;
|
|
2800
|
-
const subEffectCleanup =
|
|
2822
|
+
const subEffectCleanup = (reason) => {
|
|
2801
2823
|
children.delete(subEffectCleanup);
|
|
2802
2824
|
// Execute this child effect cleanup (which triggers its own mainCleanup)
|
|
2803
2825
|
stopEffect(reason);
|
|
2804
|
-
}
|
|
2826
|
+
};
|
|
2805
2827
|
children.add(subEffectCleanup);
|
|
2806
2828
|
return subEffectCleanup;
|
|
2807
2829
|
}
|
|
2808
2830
|
// Should not be reachable given isRootEffect check, but for type safety
|
|
2809
|
-
return
|
|
2831
|
+
return (reason) => stopEffect(reason);
|
|
2810
2832
|
}, {
|
|
2811
2833
|
get opaque() {
|
|
2812
2834
|
return flavorOptions(this, { opaque: true }, { name: 'opaque' });
|
|
@@ -2837,9 +2859,11 @@ function biDi(received, get, set) {
|
|
|
2837
2859
|
get = get.get;
|
|
2838
2860
|
}
|
|
2839
2861
|
let programmaticallySetValue = Symbol();
|
|
2840
|
-
effect(markWithRoot(() => {
|
|
2862
|
+
effect.named('biDi')(markWithRoot(() => {
|
|
2841
2863
|
const newValue = get();
|
|
2842
|
-
|
|
2864
|
+
const pValue = programmaticallySetValue;
|
|
2865
|
+
programmaticallySetValue = Symbol();
|
|
2866
|
+
if (unwrap(newValue) !== pValue)
|
|
2843
2867
|
received(newValue);
|
|
2844
2868
|
}, received));
|
|
2845
2869
|
return set
|
|
@@ -2910,16 +2934,16 @@ function bubbleUpChange(changedObject, evolution) {
|
|
|
2910
2934
|
const parents = objectParents.get(changedObject);
|
|
2911
2935
|
if (!parents)
|
|
2912
2936
|
return;
|
|
2913
|
-
for (const { parent
|
|
2937
|
+
for (const { parent } of parents) {
|
|
2914
2938
|
// Trigger deep watchers on parent
|
|
2915
2939
|
const parentDeepWatchers = deepWatchers.get(parent);
|
|
2916
2940
|
if (parentDeepWatchers) {
|
|
2917
2941
|
if (options.introspection?.gatherReasons) {
|
|
2918
2942
|
const gatherReasons = options.introspection.gatherReasons;
|
|
2919
2943
|
const lineageConfig = gatherReasons.lineages;
|
|
2920
|
-
let
|
|
2944
|
+
let touchLineage;
|
|
2921
2945
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
2922
|
-
|
|
2946
|
+
touchLineage = debugHooks.captureLineage();
|
|
2923
2947
|
}
|
|
2924
2948
|
for (const watcher of parentDeepWatchers) {
|
|
2925
2949
|
const dependencyStack = lineageConfig === 'dependency' || lineageConfig === 'both'
|
|
@@ -2932,7 +2956,7 @@ function bubbleUpChange(changedObject, evolution) {
|
|
|
2932
2956
|
obj: parent,
|
|
2933
2957
|
evolution,
|
|
2934
2958
|
dependency: dependencyStack,
|
|
2935
|
-
touch:
|
|
2959
|
+
touch: touchLineage,
|
|
2936
2960
|
});
|
|
2937
2961
|
}
|
|
2938
2962
|
}
|
|
@@ -2985,6 +3009,8 @@ function collectEffects(obj, evolution, effects, objectWatchers, ...keyChains) {
|
|
|
2985
3009
|
for (const key of keys) {
|
|
2986
3010
|
const deps = objectWatchers.get(key);
|
|
2987
3011
|
if (deps) {
|
|
3012
|
+
// Make sure `some.prop++` does not keep a dependency to `some.props`
|
|
3013
|
+
deps.delete(sourceEffect);
|
|
2988
3014
|
for (const effect of deps) {
|
|
2989
3015
|
const runningChain = isRunning(effect);
|
|
2990
3016
|
if (runningChain) {
|
|
@@ -3035,9 +3061,9 @@ function touched(obj, evolution, props) {
|
|
|
3035
3061
|
if (options.introspection?.gatherReasons) {
|
|
3036
3062
|
const gatherReasons = options.introspection.gatherReasons;
|
|
3037
3063
|
const lineageConfig = gatherReasons.lineages;
|
|
3038
|
-
let
|
|
3064
|
+
let touchLineage;
|
|
3039
3065
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
3040
|
-
|
|
3066
|
+
touchLineage = debugHooks.captureLineage();
|
|
3041
3067
|
}
|
|
3042
3068
|
for (const [effect, dependencyStack] of effects) {
|
|
3043
3069
|
const node = getEffectNode(effect);
|
|
@@ -3047,7 +3073,7 @@ function touched(obj, evolution, props) {
|
|
|
3047
3073
|
obj,
|
|
3048
3074
|
evolution,
|
|
3049
3075
|
dependency: dependencyStack,
|
|
3050
|
-
touch:
|
|
3076
|
+
touch: touchLineage,
|
|
3051
3077
|
});
|
|
3052
3078
|
}
|
|
3053
3079
|
}
|
|
@@ -3086,10 +3112,10 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
3086
3112
|
}
|
|
3087
3113
|
effects.add(effect);
|
|
3088
3114
|
if (gather) {
|
|
3089
|
-
let
|
|
3115
|
+
let touchLineage;
|
|
3090
3116
|
let dependencyStack;
|
|
3091
3117
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
3092
|
-
|
|
3118
|
+
touchLineage = debugHooks.captureLineage();
|
|
3093
3119
|
}
|
|
3094
3120
|
if (lineageConfig === 'dependency' || lineageConfig === 'both') {
|
|
3095
3121
|
dependencyStack = getDependencyStack(effect, obj, prop);
|
|
@@ -3100,7 +3126,7 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
3100
3126
|
obj,
|
|
3101
3127
|
evolution,
|
|
3102
3128
|
dependency: dependencyStack,
|
|
3103
|
-
touch:
|
|
3129
|
+
touch: touchLineage,
|
|
3104
3130
|
});
|
|
3105
3131
|
}
|
|
3106
3132
|
recordActivation(effect, obj, evolution, prop);
|
|
@@ -3129,59 +3155,60 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
3129
3155
|
}
|
|
3130
3156
|
}
|
|
3131
3157
|
|
|
3132
|
-
const
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3158
|
+
const absent = Symbol('absent');
|
|
3159
|
+
/**
|
|
3160
|
+
* Add unreactive properties to a prototype.
|
|
3161
|
+
* If no set is provided, marks the entire object/prototype as non-reactive (sets [unreactiveProperties] = true).
|
|
3162
|
+
* If a set is provided, merges with existing unreactive properties (never overrides true).
|
|
3163
|
+
*/
|
|
3136
3164
|
function addUnreactiveProps(proto, set) {
|
|
3137
|
-
|
|
3138
|
-
|
|
3165
|
+
if (unreactiveProperties in proto) {
|
|
3166
|
+
const existing = proto[unreactiveProperties];
|
|
3167
|
+
// If already fully unreactive, don't change
|
|
3168
|
+
if (existing === true)
|
|
3169
|
+
return proto;
|
|
3170
|
+
// If no set provided, upgrade to fully unreactive
|
|
3171
|
+
if (!set) {
|
|
3172
|
+
proto[unreactiveProperties] = true;
|
|
3173
|
+
return proto;
|
|
3174
|
+
}
|
|
3175
|
+
// Merge sets
|
|
3176
|
+
set = proto[unreactiveProperties] = new Set(proto[unreactiveProperties]);
|
|
3177
|
+
for (const p of set)
|
|
3178
|
+
existing.add(p);
|
|
3179
|
+
}
|
|
3180
|
+
// If no set, mark as fully unreactive, otherwise create set
|
|
3181
|
+
else
|
|
3182
|
+
proto[unreactiveProperties] = set ? new Set(set) : true;
|
|
3183
|
+
return proto;
|
|
3139
3184
|
}
|
|
3140
3185
|
/** Check if a property is marked unreactive on obj or any of its prototypes (trap-free) */
|
|
3141
3186
|
function isUnreactiveProp(obj, prop) {
|
|
3142
|
-
if (
|
|
3143
|
-
return
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
target = Object.getPrototypeOf(target);
|
|
3149
|
-
}
|
|
3150
|
-
return false;
|
|
3187
|
+
if (typeof prop === 'symbol' || prop === 'constructor')
|
|
3188
|
+
return true;
|
|
3189
|
+
const marker = obj[unreactiveProperties];
|
|
3190
|
+
return (marker === true || // Fully unreactive
|
|
3191
|
+
marker?.has?.(prop) || // Property is unreactive
|
|
3192
|
+
false);
|
|
3151
3193
|
}
|
|
3152
|
-
|
|
3153
|
-
const
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
nonReactiveObjects.add(o);
|
|
3194
|
+
function nonReactive(...obj) {
|
|
3195
|
+
for (const o of obj) {
|
|
3196
|
+
o[unreactiveProperties] = true;
|
|
3197
|
+
}
|
|
3157
3198
|
return obj[0];
|
|
3158
3199
|
}
|
|
3159
3200
|
function nonReactiveClass(...cls) {
|
|
3160
3201
|
for (const c of cls)
|
|
3161
3202
|
if (c)
|
|
3162
|
-
|
|
3203
|
+
c.prototype[unreactiveProperties] = true;
|
|
3163
3204
|
return cls[0];
|
|
3164
3205
|
}
|
|
3165
3206
|
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;
|
|
3207
|
+
return !obj || obj[unreactiveProperties] === true;
|
|
3181
3208
|
}
|
|
3182
3209
|
nonReactiveClass(Date, RegExp, Error, Promise, Function);
|
|
3183
3210
|
if (typeof window !== 'undefined') {
|
|
3184
|
-
|
|
3211
|
+
nonReactive(window, document);
|
|
3185
3212
|
nonReactiveClass(Node, Element, HTMLElement, EventTarget, HTMLCollection, NodeList);
|
|
3186
3213
|
}
|
|
3187
3214
|
|
|
@@ -3203,10 +3230,33 @@ function shouldRecurseTouch(oldValue, newValue) {
|
|
|
3203
3230
|
if ((typeof oldValue !== 'object' && !Array.isArray(oldValue)) ||
|
|
3204
3231
|
(typeof newValue !== 'object' && !Array.isArray(newValue)))
|
|
3205
3232
|
return false;
|
|
3206
|
-
if (isNonReactive(oldValue)
|
|
3233
|
+
if (isNonReactive(oldValue) /*|| isNonReactive(newValue)*/)
|
|
3207
3234
|
return false;
|
|
3208
3235
|
return getPrototypeToken(oldValue) === getPrototypeToken(newValue);
|
|
3209
3236
|
}
|
|
3237
|
+
/**
|
|
3238
|
+
* Migrate all watcher registrations from oldRef to newRef.
|
|
3239
|
+
* Called when deep touch replaces an object identity without any child value differences,
|
|
3240
|
+
* to prevent watcher orphaning (effects still pointing at the discarded old object).
|
|
3241
|
+
*/
|
|
3242
|
+
function migrateWatchers(oldRef, newRef) {
|
|
3243
|
+
const oldMap = exports.watchers.get(oldRef);
|
|
3244
|
+
if (!oldMap)
|
|
3245
|
+
return;
|
|
3246
|
+
// Move the entire watcher map
|
|
3247
|
+
exports.watchers.set(newRef, oldMap);
|
|
3248
|
+
exports.watchers.delete(oldRef);
|
|
3249
|
+
// Update the reverse map (effect → objects it watches)
|
|
3250
|
+
for (const deps of oldMap.values()) {
|
|
3251
|
+
for (const effect of deps) {
|
|
3252
|
+
const objects = exports.effectToReactiveObjects.get(effect);
|
|
3253
|
+
if (objects) {
|
|
3254
|
+
objects.delete(oldRef);
|
|
3255
|
+
objects.add(newRef);
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3210
3260
|
/**
|
|
3211
3261
|
* Centralized function to handle property change notifications with optional recursive touch
|
|
3212
3262
|
* @param targetObj - The object whose property changed
|
|
@@ -3224,7 +3274,15 @@ function notifyPropertyChange(targetObj, prop, oldValue, newValue, hadProperty)
|
|
|
3224
3274
|
const origin = { obj: unwrappedObj, prop };
|
|
3225
3275
|
// Deep touch: only notify nested property changes with origin filtering
|
|
3226
3276
|
// Don't notify direct property change - the whole point is to avoid parent effects re-running
|
|
3227
|
-
|
|
3277
|
+
const changes = untracked(() => recursiveTouch(oldValue, newValue, new WeakMap(), [], origin));
|
|
3278
|
+
// When deep touch found no child differences, the object identity still changed.
|
|
3279
|
+
// Migrate watchers from old → new so the dependency chain is preserved.
|
|
3280
|
+
if (changes.length === 0) {
|
|
3281
|
+
migrateWatchers(unwrap(oldValue), unwrap(newValue));
|
|
3282
|
+
}
|
|
3283
|
+
else {
|
|
3284
|
+
dispatchNotifications(changes);
|
|
3285
|
+
}
|
|
3228
3286
|
// Notify opaque listeners (like memoize) that always want to know about identity changes
|
|
3229
3287
|
touchedOpaque(targetObj, evolution, prop);
|
|
3230
3288
|
}
|
|
@@ -3311,12 +3369,11 @@ function diffObjectProperties(oldObj, newObj, visited, notifications, origin) {
|
|
|
3311
3369
|
for (const key of oldKeys)
|
|
3312
3370
|
if (!newKeys.has(key))
|
|
3313
3371
|
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
3372
|
for (const key of newKeys) {
|
|
3318
|
-
if (!oldKeys.has(key))
|
|
3373
|
+
if (!oldKeys.has(key)) {
|
|
3374
|
+
local.push({ target: oldObj, evolution: { type: 'add', prop: key }, prop: key, origin });
|
|
3319
3375
|
continue;
|
|
3376
|
+
}
|
|
3320
3377
|
const oldEntry = unwrap(oldObj[key]);
|
|
3321
3378
|
const newEntry = unwrap(newObj[key]);
|
|
3322
3379
|
if (shouldRecurseTouch(oldEntry, newEntry)) {
|
|
@@ -3358,11 +3415,10 @@ function dispatchNotifications(notifications) {
|
|
|
3358
3415
|
if (originWatchers) {
|
|
3359
3416
|
const originEffects = new Map();
|
|
3360
3417
|
collectEffects(origin.obj, { type: 'set', prop: origin.prop }, originEffects, originWatchers, [allProps], [origin.prop]);
|
|
3361
|
-
|
|
3362
|
-
allowedEffects.add(effect);
|
|
3418
|
+
allowedEffects = new Set(originEffects.keys());
|
|
3363
3419
|
}
|
|
3364
3420
|
// If no allowed effects, skip all notifications (no one should be notified)
|
|
3365
|
-
if (allowedEffects
|
|
3421
|
+
if (!allowedEffects?.size)
|
|
3366
3422
|
return;
|
|
3367
3423
|
}
|
|
3368
3424
|
for (const notification of notifications) {
|
|
@@ -3375,7 +3431,6 @@ function dispatchNotifications(notifications) {
|
|
|
3375
3431
|
let currentEffects;
|
|
3376
3432
|
const propsArray = [prop];
|
|
3377
3433
|
if (objectWatchers) {
|
|
3378
|
-
// console.log(`[DEBUG] dispatchNotifications: processing ${obj.constructor.name} (has watchers)`)
|
|
3379
3434
|
currentEffects = new Map();
|
|
3380
3435
|
const broad = evolution.type !== 'set' ? [allProps, keysOf] : [allProps];
|
|
3381
3436
|
collectEffects(obj, evolution, currentEffects, objectWatchers, broad, propsArray);
|
|
@@ -3411,9 +3466,9 @@ function dispatchNotifications(notifications) {
|
|
|
3411
3466
|
if (options.introspection?.gatherReasons) {
|
|
3412
3467
|
const gatherReasons = options.introspection.gatherReasons;
|
|
3413
3468
|
const lineageConfig = gatherReasons.lineages;
|
|
3414
|
-
let
|
|
3469
|
+
let touchLineage;
|
|
3415
3470
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
3416
|
-
|
|
3471
|
+
touchLineage = debugHooks.captureLineage();
|
|
3417
3472
|
}
|
|
3418
3473
|
for (const effect of combinedEffects) {
|
|
3419
3474
|
const node = getEffectNode(effect);
|
|
@@ -3427,7 +3482,7 @@ function dispatchNotifications(notifications) {
|
|
|
3427
3482
|
obj: unwrap(target),
|
|
3428
3483
|
evolution,
|
|
3429
3484
|
dependency: dependencyStack,
|
|
3430
|
-
touch:
|
|
3485
|
+
touch: touchLineage,
|
|
3431
3486
|
});
|
|
3432
3487
|
}
|
|
3433
3488
|
}
|
|
@@ -3440,11 +3495,16 @@ const metaProtos = new WeakMap();
|
|
|
3440
3495
|
const wrapProtos = new WeakMap();
|
|
3441
3496
|
const arrayLengths = new WeakMap();
|
|
3442
3497
|
const hasReentry = new Set();
|
|
3443
|
-
const subsRegister = new WeakMap();
|
|
3444
3498
|
// Sub-proxy registration for custom reactive behaviors
|
|
3499
|
+
const subsRegister = new WeakMap();
|
|
3500
|
+
// Internal untracked flag for setter/getter operations - only used when testing oldValue while setting a value
|
|
3501
|
+
// TODO: `touched` trigger also compares to old value and should use the internalUntracked flag
|
|
3502
|
+
let internalUntracked = false;
|
|
3445
3503
|
const reactiveHandlers = {
|
|
3446
3504
|
[Symbol.toStringTag]: 'MutTs Reactive',
|
|
3447
3505
|
get(obj, prop, receiver) {
|
|
3506
|
+
if (internalUntracked)
|
|
3507
|
+
return FoolProof.get(obj, prop, receiver);
|
|
3448
3508
|
if (obj && typeof obj === 'object' && prop !== Symbol.toStringTag) {
|
|
3449
3509
|
const metaProto = metaProtos.get(obj.constructor);
|
|
3450
3510
|
if (metaProto && Object.hasOwn(metaProto, prop)) {
|
|
@@ -3464,15 +3524,21 @@ const reactiveHandlers = {
|
|
|
3464
3524
|
if (wrapProto && Object.hasOwn(wrapProto, prop))
|
|
3465
3525
|
return wrapProto[prop];
|
|
3466
3526
|
}
|
|
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))
|
|
3527
|
+
// Symbols: fast-path — no reactivity tracking
|
|
3528
|
+
if (typeof prop === 'symbol' || prop === 'constructor' || isUnreactiveProp(obj, prop))
|
|
3472
3529
|
return FoolProof.get(obj, prop, receiver);
|
|
3473
3530
|
// Check if property exists using a trap-free walk to avoid triggering
|
|
3474
3531
|
// the has-trap cascade on prototype chains of reactive proxies.
|
|
3475
3532
|
const isOwnProp = Object.hasOwn(obj, prop);
|
|
3533
|
+
// For accessor properties, check the unwrapped object to see if it's an accessor
|
|
3534
|
+
// This ensures ignoreAccessors works correctly even after operations like Object.setPrototypeOf
|
|
3535
|
+
// Skip for null-proto objects (pounce scopes) — they never have accessors
|
|
3536
|
+
const shouldIgnoreAccessor = options.ignoreAccessors &&
|
|
3537
|
+
isOwnProp &&
|
|
3538
|
+
Object.getPrototypeOf(obj) !== null &&
|
|
3539
|
+
(isOwnAccessor(receiver, prop) || isOwnAccessor(obj, prop));
|
|
3540
|
+
// Check if property exists using a trap-free walk to avoid triggering
|
|
3541
|
+
// the has-trap cascade on prototype chains of reactive proxies.
|
|
3476
3542
|
let hasProp = isOwnProp;
|
|
3477
3543
|
let owner = isOwnProp ? obj : undefined;
|
|
3478
3544
|
if (!isOwnProp) {
|
|
@@ -3487,29 +3553,20 @@ const reactiveHandlers = {
|
|
|
3487
3553
|
}
|
|
3488
3554
|
}
|
|
3489
3555
|
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
3556
|
// Depend if...
|
|
3498
3557
|
if (!hasProp ||
|
|
3499
3558
|
(!(options.instanceMembers && isInheritedAccess && obj instanceof Object) &&
|
|
3500
3559
|
!shouldIgnoreAccessor))
|
|
3501
3560
|
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.
|
|
3561
|
+
// Two-Point Tracking: for inherited access on null-proto chains, also track
|
|
3562
|
+
// the owning ancestor so that writing directly to it triggers dependent effects.
|
|
3506
3563
|
if (isInheritedAccess && owner && (!options.instanceMembers || !(obj instanceof Object))) {
|
|
3507
3564
|
dependant(owner, prop);
|
|
3508
3565
|
}
|
|
3509
3566
|
// For arrays, use FoolProof.get (Indexer path) for numeric index reactivity.
|
|
3510
3567
|
// For all other objects, inline Reflect.get directly (skips 3 function calls).
|
|
3511
3568
|
const value = (subsRegister.get(obj)?.get || FoolProof.get)(obj, prop, receiver);
|
|
3512
|
-
if (typeof value === 'object' && value !== null) {
|
|
3569
|
+
if (!isReactive(value) && typeof value === 'object' && value !== null) {
|
|
3513
3570
|
const reactiveValue = reactiveObject(value);
|
|
3514
3571
|
// Only create back-references if this object needs them
|
|
3515
3572
|
if (needsBackReferences(obj)) {
|
|
@@ -3520,9 +3577,19 @@ const reactiveHandlers = {
|
|
|
3520
3577
|
return value;
|
|
3521
3578
|
},
|
|
3522
3579
|
set(obj, prop, value, receiver) {
|
|
3523
|
-
const
|
|
3580
|
+
const unwrapped = unwrap(receiver);
|
|
3581
|
+
if (obj !== unwrapped)
|
|
3582
|
+
return Object.defineProperty(unwrapped, prop, {
|
|
3583
|
+
value,
|
|
3584
|
+
configurable: true,
|
|
3585
|
+
writable: true,
|
|
3586
|
+
enumerable: true,
|
|
3587
|
+
});
|
|
3588
|
+
if (internalUntracked)
|
|
3589
|
+
throw new Error('Internal untracked: setting a value in an getter in a set operation');
|
|
3590
|
+
//return FoolProof.set(obj, prop, value, receiver)
|
|
3524
3591
|
// Check if this property is marked as unreactive
|
|
3525
|
-
if (isUnreactiveProp(obj, prop)
|
|
3592
|
+
if (isUnreactiveProp(obj, prop))
|
|
3526
3593
|
return FoolProof.set(obj, prop, value, receiver);
|
|
3527
3594
|
const newValue = unwrap(value);
|
|
3528
3595
|
// metaProto setter dispatch (e.g., reactive array length)
|
|
@@ -3539,16 +3606,19 @@ const reactiveHandlers = {
|
|
|
3539
3606
|
// Read old value, using withEffect(undefined, ...) for getter-only accessors to avoid
|
|
3540
3607
|
// breaking memoization dependency tracking during SET operations
|
|
3541
3608
|
let oldVal = absent;
|
|
3542
|
-
// TODO: Pffft... Find a way to "generalize" this case?
|
|
3543
3609
|
const isArrayLength = prop === 'length' && Array.isArray(obj);
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3610
|
+
internalUntracked = true;
|
|
3611
|
+
try {
|
|
3612
|
+
if (Reflect.has(obj, prop)) {
|
|
3613
|
+
oldVal = isArrayLength
|
|
3614
|
+
? arrayLengths.get(obj) === newValue
|
|
3615
|
+
? newValue
|
|
3616
|
+
: absent
|
|
3617
|
+
: Reflect.get(obj, prop, receiver);
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
finally {
|
|
3621
|
+
internalUntracked = false;
|
|
3552
3622
|
}
|
|
3553
3623
|
if (objectsWithDeepWatchers.has(obj)) {
|
|
3554
3624
|
if (typeof oldVal === 'object' && oldVal !== null) {
|
|
@@ -3577,7 +3647,8 @@ const reactiveHandlers = {
|
|
|
3577
3647
|
cycle: [], // We don't have the full cycle here, but we know it involves obj
|
|
3578
3648
|
});
|
|
3579
3649
|
hasReentry.add(obj);
|
|
3580
|
-
|
|
3650
|
+
if (!internalUntracked && !isUnreactiveProp(obj, prop))
|
|
3651
|
+
dependant(obj, prop);
|
|
3581
3652
|
const rv = (subsRegister.get(obj)?.has || Reflect.has)(obj, prop);
|
|
3582
3653
|
hasReentry.delete(obj);
|
|
3583
3654
|
return rv;
|
|
@@ -3603,7 +3674,8 @@ const reactiveHandlers = {
|
|
|
3603
3674
|
return subsRegister.get(obj)?.ownKeys?.(obj) || Reflect.ownKeys(obj);
|
|
3604
3675
|
},
|
|
3605
3676
|
getOwnPropertyDescriptor(obj, prop) {
|
|
3606
|
-
return subsRegister.get(obj)?.getOwnPropertyDescriptor?.(obj, prop) ||
|
|
3677
|
+
return (subsRegister.get(obj)?.getOwnPropertyDescriptor?.(obj, prop) ||
|
|
3678
|
+
Reflect.getOwnPropertyDescriptor(obj, prop));
|
|
3607
3679
|
},
|
|
3608
3680
|
};
|
|
3609
3681
|
const reactiveClasses = new WeakSet();
|
|
@@ -3688,31 +3760,32 @@ exports.ReactiveError = ReactiveError;
|
|
|
3688
3760
|
exports.Zone = Zone;
|
|
3689
3761
|
exports.ZoneAggregator = ZoneAggregator;
|
|
3690
3762
|
exports.ZoneHistory = ZoneHistory;
|
|
3691
|
-
exports.__classPrivateFieldGet = __classPrivateFieldGet;
|
|
3692
|
-
exports.__classPrivateFieldSet = __classPrivateFieldSet;
|
|
3693
3763
|
exports.__esDecorate = __esDecorate;
|
|
3694
3764
|
exports.__runInitializers = __runInitializers;
|
|
3695
|
-
exports.__setFunctionName = __setFunctionName;
|
|
3696
3765
|
exports.addBatchCleanup = addBatchCleanup;
|
|
3697
3766
|
exports.addUnreactiveProps = addUnreactiveProps;
|
|
3698
3767
|
exports.allProps = allProps;
|
|
3699
3768
|
exports.arrayEquals = arrayEquals;
|
|
3769
|
+
exports.assertUntracked = assertUntracked;
|
|
3700
3770
|
exports.asyncZone = asyncZone;
|
|
3701
3771
|
exports.atom = atom;
|
|
3702
3772
|
exports.atomic = atomic;
|
|
3773
|
+
exports.batch = batch;
|
|
3703
3774
|
exports.biDi = biDi;
|
|
3775
|
+
exports.captured = captured;
|
|
3704
3776
|
exports.caught = caught;
|
|
3705
|
-
exports.cleanedBy = cleanedBy;
|
|
3706
|
-
exports.cleanup = cleanup;
|
|
3707
3777
|
exports.contentRef = contentRef;
|
|
3708
3778
|
exports.createFlavor = createFlavor;
|
|
3779
|
+
exports.debugPreset = debugPreset;
|
|
3709
3780
|
exports.decorator = decorator;
|
|
3710
3781
|
exports.deepCompare = deepCompare;
|
|
3711
3782
|
exports.deepWatchers = deepWatchers;
|
|
3712
3783
|
exports.defer = defer;
|
|
3713
3784
|
exports.dependant = dependant;
|
|
3785
|
+
exports.devPreset = devPreset;
|
|
3714
3786
|
exports.effect = effect;
|
|
3715
3787
|
exports.effectAggregator = effectAggregator;
|
|
3788
|
+
exports.effectHistory = effectHistory;
|
|
3716
3789
|
exports.effectMarker = effectMarker;
|
|
3717
3790
|
exports.effectToDeepWatchedObjects = effectToDeepWatchedObjects;
|
|
3718
3791
|
exports.flavorOptions = flavorOptions;
|
|
@@ -3723,20 +3796,21 @@ exports.getActiveEffect = getActiveEffect;
|
|
|
3723
3796
|
exports.getEffectNode = getEffectNode;
|
|
3724
3797
|
exports.getRoot = getRoot;
|
|
3725
3798
|
exports.getState = getState;
|
|
3726
|
-
exports.immutables = immutables;
|
|
3727
3799
|
exports.isConstructor = isConstructor;
|
|
3800
|
+
exports.isDev = isDev;
|
|
3728
3801
|
exports.isNonReactive = isNonReactive;
|
|
3729
3802
|
exports.isObject = isObject;
|
|
3803
|
+
exports.isProd = isProd;
|
|
3730
3804
|
exports.isReactive = isReactive;
|
|
3805
|
+
exports.isTest = isTest;
|
|
3731
3806
|
exports.keysOf = keysOf;
|
|
3732
3807
|
exports.legacyDecorator = legacyDecorator;
|
|
3808
|
+
exports.link = link;
|
|
3733
3809
|
exports.markWithRoot = markWithRoot;
|
|
3734
3810
|
exports.metaProtos = metaProtos;
|
|
3735
3811
|
exports.mixin = mixin;
|
|
3736
3812
|
exports.modernDecorator = modernDecorator;
|
|
3737
3813
|
exports.named = named;
|
|
3738
|
-
exports.nonReactiveClass = nonReactiveClass;
|
|
3739
|
-
exports.nonReactiveObjects = nonReactiveObjects;
|
|
3740
3814
|
exports.notifyPropertyChange = notifyPropertyChange;
|
|
3741
3815
|
exports.objectParents = objectParents;
|
|
3742
3816
|
exports.objectToProxy = objectToProxy;
|
|
@@ -3744,6 +3818,7 @@ exports.objectsWithDeepWatchers = objectsWithDeepWatchers;
|
|
|
3744
3818
|
exports.onEffectThrow = onEffectThrow;
|
|
3745
3819
|
exports.optionCall = optionCall;
|
|
3746
3820
|
exports.options = options;
|
|
3821
|
+
exports.prodPreset = prodPreset;
|
|
3747
3822
|
exports.proxyToObject = proxyToObject;
|
|
3748
3823
|
exports.reactive = reactive;
|
|
3749
3824
|
exports.registerDeepWatcher = registerDeepWatcher;
|
|
@@ -3751,13 +3826,13 @@ exports.reset = reset;
|
|
|
3751
3826
|
exports.root = root;
|
|
3752
3827
|
exports.rootFunctionSymbol = rootFunctionSymbol;
|
|
3753
3828
|
exports.setDebugHooks = setDebugHooks;
|
|
3754
|
-
exports.stopped = stopped;
|
|
3755
3829
|
exports.tag = tag;
|
|
3756
3830
|
exports.touched = touched;
|
|
3757
3831
|
exports.touched1 = touched1;
|
|
3758
|
-
exports.
|
|
3832
|
+
exports.unlink = unlink;
|
|
3833
|
+
exports.unreactiveProperties = unreactiveProperties;
|
|
3759
3834
|
exports.untracked = untracked;
|
|
3760
3835
|
exports.unwrap = unwrap;
|
|
3761
3836
|
exports.wrapProtos = wrapProtos;
|
|
3762
3837
|
exports.zip = zip;
|
|
3763
|
-
//# sourceMappingURL=proxy-
|
|
3838
|
+
//# sourceMappingURL=proxy-Cc79Lrzj.cjs.map
|