mutts 1.0.10 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/browser.cjs +244 -3199
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +2 -2
- package/dist/browser.dev.cjs +51 -50
- package/dist/browser.dev.cjs.map +1 -1
- package/dist/browser.dev.d.ts +2 -2
- package/dist/browser.dev.esm.js +2 -2
- package/dist/browser.esm.js +15 -13
- package/dist/browser.esm.js.map +1 -1
- package/dist/chunks/{index-CaaQQlPJ.esm.js → index-BUop6B2U.esm.js} +384 -981
- package/dist/chunks/index-BUop6B2U.esm.js.map +1 -0
- package/dist/chunks/index-yK0HVxHv.cjs +2612 -0
- package/dist/chunks/index-yK0HVxHv.cjs.map +1 -0
- package/dist/chunks/{node-nKJBk8iJ.esm.js → node-Bo7WU5S2.esm.js} +2 -2
- package/dist/chunks/{node-nKJBk8iJ.esm.js.map → node-Bo7WU5S2.esm.js.map} +1 -1
- package/dist/chunks/{async-node-3PrbVAbB.cjs → node-Dd0esp5F.cjs} +4 -4
- package/dist/chunks/node-Dd0esp5F.cjs.map +1 -0
- package/dist/chunks/{proxy-Dtg-bJ3T.cjs → proxy-BvM4yewA.cjs} +441 -342
- package/dist/chunks/proxy-BvM4yewA.cjs.map +1 -0
- package/dist/chunks/{proxy-r7lARftl.esm.js → proxy-D2C49sXH.esm.js} +401 -330
- package/dist/chunks/proxy-D2C49sXH.esm.js.map +1 -0
- package/dist/debug.cjs +17 -4
- package/dist/debug.cjs.map +1 -1
- package/dist/debug.d.ts +2 -2
- package/dist/debug.esm.js +17 -3
- package/dist/debug.esm.js.map +1 -1
- package/dist/index.d.ts +84 -209
- package/dist/mutts.umd.js +842 -1362
- package/dist/mutts.umd.js.map +1 -1
- package/dist/mutts.umd.min.js +1 -1
- package/dist/mutts.umd.min.js.map +1 -1
- package/dist/node.cjs +51 -49
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.dev.cjs +51 -49
- package/dist/node.dev.cjs.map +1 -1
- package/dist/node.dev.d.ts +2 -2
- package/dist/node.dev.esm.js +3 -3
- package/dist/node.esm.js +3 -3
- package/dist/{types-W5vD6m2n.d.ts → types-Bx2PhORg.d.ts} +38 -47
- package/docs/ai/api-reference.md +0 -14
- package/docs/ai/manual.md +2 -22
- package/docs/reactive/advanced.md +13 -14
- package/docs/reactive/attend.md +1 -2
- package/docs/reactive/collections.md +2 -149
- package/docs/reactive/core.md +218 -96
- package/docs/reactive/debugging.md +2 -2
- package/docs/reactive/resource.md +1 -1
- package/docs/reactive.md +1 -3
- package/docs/zone.md +1 -1
- package/package.json +18 -9
- package/dist/chunks/async-browser-BU_IfxYD.cjs +0 -216
- package/dist/chunks/async-browser-BU_IfxYD.cjs.map +0 -1
- package/dist/chunks/async-core-CRLKP3l-.cjs +0 -29
- package/dist/chunks/async-core-CRLKP3l-.cjs.map +0 -1
- package/dist/chunks/async-node-3PrbVAbB.cjs.map +0 -1
- package/dist/chunks/index-CaaQQlPJ.esm.js.map +0 -1
- package/dist/chunks/proxy-Dtg-bJ3T.cjs.map +0 -1
- package/dist/chunks/proxy-r7lARftl.esm.js.map +0 -1
- package/docs/reactive/scan.md +0 -324
package/dist/mutts.umd.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Mutts = {}));
|
|
5
5
|
})(this, (function (exports) { 'use strict';
|
|
6
6
|
|
|
7
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
7
8
|
// Queue for hooks registered before the environment is ready (circular dependency fix)
|
|
8
9
|
const hooks$1 = new Set();
|
|
9
10
|
const asyncHooks = {
|
|
@@ -64,21 +65,23 @@
|
|
|
64
65
|
return fn.apply(this, args);
|
|
65
66
|
}
|
|
66
67
|
finally {
|
|
67
|
-
|
|
68
|
+
/* cf BROWSER_ASYNC_POLYFILL.md
|
|
69
|
+
// Note: my fear about this code: in between 2~3~4 microtask waits, some other microtasks might have started, stopped, ...
|
|
70
|
+
// We might be in the middle of another promise hook trying to setup the zone
|
|
71
|
+
// TODO We might wish to have a flag :asyncZone.acquired - like a semaphore - that we falsify here and set back when we setup the zone
|
|
72
|
+
// - but this might perhaps be an overkill creating more problems than it solves
|
|
73
|
+
if (originals.queueMicrotask) {
|
|
68
74
|
// Double microtask ensures we run after the first await resumption microtask
|
|
69
|
-
originals
|
|
70
|
-
originals
|
|
71
|
-
originals
|
|
72
|
-
for (let i = undoers.length - 1; i >= 0; i--)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
for (let i = undoers.length - 1; i >= 0; i--)
|
|
80
|
-
undoers[i]();
|
|
81
|
-
}
|
|
75
|
+
originals.queueMicrotask.call(globalThis, () => {
|
|
76
|
+
originals.queueMicrotask.call(globalThis, () => {
|
|
77
|
+
originals.queueMicrotask.call(globalThis, () => {
|
|
78
|
+
for (let i = undoers.length - 1; i >= 0; i--) undoers[i]()
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
} else {
|
|
83
|
+
for (let i = undoers.length - 1; i >= 0; i--) undoers[i]()
|
|
84
|
+
}*/
|
|
82
85
|
}
|
|
83
86
|
};
|
|
84
87
|
}
|
|
@@ -336,16 +339,16 @@
|
|
|
336
339
|
if (hasNode && obj instanceof Node) {
|
|
337
340
|
obj[prop] = value;
|
|
338
341
|
return true;
|
|
339
|
-
}
|
|
340
|
-
if (!(obj instanceof Object) && !
|
|
342
|
+
} /*
|
|
343
|
+
if (!(obj instanceof Object) && !Object.hasOwn(obj, prop)) {
|
|
341
344
|
Object.defineProperty(obj, prop, {
|
|
342
345
|
value,
|
|
343
346
|
configurable: true,
|
|
344
347
|
writable: true,
|
|
345
348
|
enumerable: true,
|
|
346
|
-
})
|
|
347
|
-
return true
|
|
348
|
-
}
|
|
349
|
+
})
|
|
350
|
+
return true
|
|
351
|
+
}*/
|
|
349
352
|
return Reflect.set(obj, prop, value, receiver);
|
|
350
353
|
},
|
|
351
354
|
};
|
|
@@ -369,15 +372,8 @@
|
|
|
369
372
|
return a === b;
|
|
370
373
|
}
|
|
371
374
|
// Prototype check
|
|
372
|
-
|
|
373
|
-
const protoB = Object.getPrototypeOf(b);
|
|
374
|
-
if (protoA !== protoB) {
|
|
375
|
-
console.warn(`[deepCompare] prototype mismatch:`, {
|
|
376
|
-
nameA: a?.constructor?.name,
|
|
377
|
-
nameB: b?.constructor?.name,
|
|
378
|
-
});
|
|
375
|
+
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b))
|
|
379
376
|
return false;
|
|
380
|
-
}
|
|
381
377
|
// Circular reference protection
|
|
382
378
|
let compared = cache.get(a);
|
|
383
379
|
if (compared?.has(b))
|
|
@@ -389,39 +385,21 @@
|
|
|
389
385
|
compared.add(b);
|
|
390
386
|
// Handle specific object types
|
|
391
387
|
if (Array.isArray(a)) {
|
|
392
|
-
if (!Array.isArray(b))
|
|
393
|
-
console.warn(`[deepCompare] B is not an array`);
|
|
388
|
+
if (!Array.isArray(b) || a.length !== b.length)
|
|
394
389
|
return false;
|
|
395
|
-
}
|
|
396
|
-
if (a.length !== b.length) {
|
|
397
|
-
console.warn(`[deepCompare] array length mismatch:`, { lenA: a.length, lenB: b.length });
|
|
398
|
-
return false;
|
|
399
|
-
}
|
|
400
390
|
for (let i = 0; i < a.length; i++) {
|
|
401
|
-
if (!deepCompare(a[i], b[i], cache))
|
|
402
|
-
console.warn(`[deepCompare] array element mismatch at index ${i}`);
|
|
391
|
+
if (!deepCompare(a[i], b[i], cache))
|
|
403
392
|
return false;
|
|
404
|
-
}
|
|
405
393
|
}
|
|
406
394
|
return true;
|
|
407
395
|
}
|
|
408
|
-
if (a instanceof Date)
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
return match;
|
|
413
|
-
}
|
|
414
|
-
if (a instanceof RegExp) {
|
|
415
|
-
const match = b instanceof RegExp && a.toString() === b.toString();
|
|
416
|
-
if (!match)
|
|
417
|
-
console.warn(`[deepCompare] RegExp mismatch`);
|
|
418
|
-
return match;
|
|
419
|
-
}
|
|
396
|
+
if (a instanceof Date)
|
|
397
|
+
return b instanceof Date && a.getTime() === b.getTime();
|
|
398
|
+
if (a instanceof RegExp)
|
|
399
|
+
return b instanceof RegExp && a.toString() === b.toString();
|
|
420
400
|
if (a instanceof Set) {
|
|
421
|
-
if (!(b instanceof Set) || a.size !== b.size)
|
|
422
|
-
console.warn(`[deepCompare] Set size mismatch`);
|
|
401
|
+
if (!(b instanceof Set) || a.size !== b.size)
|
|
423
402
|
return false;
|
|
424
|
-
}
|
|
425
403
|
for (const val of a) {
|
|
426
404
|
let found = false;
|
|
427
405
|
for (const bVal of b) {
|
|
@@ -430,18 +408,14 @@
|
|
|
430
408
|
break;
|
|
431
409
|
}
|
|
432
410
|
}
|
|
433
|
-
if (!found)
|
|
434
|
-
console.warn(`[deepCompare] missing Set element`);
|
|
411
|
+
if (!found)
|
|
435
412
|
return false;
|
|
436
|
-
}
|
|
437
413
|
}
|
|
438
414
|
return true;
|
|
439
415
|
}
|
|
440
416
|
if (a instanceof Map) {
|
|
441
|
-
if (!(b instanceof Map) || a.size !== b.size)
|
|
442
|
-
console.warn(`[deepCompare] Map size mismatch`);
|
|
417
|
+
if (!(b instanceof Map) || a.size !== b.size)
|
|
443
418
|
return false;
|
|
444
|
-
}
|
|
445
419
|
for (const [key, val] of a) {
|
|
446
420
|
if (!b.has(key)) {
|
|
447
421
|
let foundMatch = false;
|
|
@@ -451,16 +425,11 @@
|
|
|
451
425
|
break;
|
|
452
426
|
}
|
|
453
427
|
}
|
|
454
|
-
if (!foundMatch)
|
|
455
|
-
console.warn(`[deepCompare] missing Map key`);
|
|
428
|
+
if (!foundMatch)
|
|
456
429
|
return false;
|
|
457
|
-
}
|
|
458
430
|
}
|
|
459
|
-
else {
|
|
460
|
-
|
|
461
|
-
console.warn(`[deepCompare] Map value mismatch for key`);
|
|
462
|
-
return false;
|
|
463
|
-
}
|
|
431
|
+
else if (!deepCompare(val, b.get(key), cache)) {
|
|
432
|
+
return false;
|
|
464
433
|
}
|
|
465
434
|
}
|
|
466
435
|
return true;
|
|
@@ -468,29 +437,11 @@
|
|
|
468
437
|
// Compare own properties
|
|
469
438
|
const keysA = Object.keys(a);
|
|
470
439
|
const keysB = Object.keys(b);
|
|
471
|
-
if (keysA.length !== keysB.length)
|
|
472
|
-
console.warn(`[deepCompare] keys length mismatch:`, {
|
|
473
|
-
lenA: keysA.length,
|
|
474
|
-
lenB: keysB.length,
|
|
475
|
-
keysA,
|
|
476
|
-
keysB,
|
|
477
|
-
a,
|
|
478
|
-
b,
|
|
479
|
-
});
|
|
440
|
+
if (keysA.length !== keysB.length)
|
|
480
441
|
return false;
|
|
481
|
-
}
|
|
482
442
|
for (const key of keysA) {
|
|
483
|
-
if (!Object.hasOwn(b, key))
|
|
484
|
-
console.warn(`[deepCompare] missing key ${String(key)} in B`);
|
|
443
|
+
if (!Object.hasOwn(b, key) || !deepCompare(a[key], b[key], cache))
|
|
485
444
|
return false;
|
|
486
|
-
}
|
|
487
|
-
if (!deepCompare(a[key], b[key], cache)) {
|
|
488
|
-
console.warn(`[deepCompare] value mismatch for key ${String(key)}:`, {
|
|
489
|
-
valA: a[key],
|
|
490
|
-
valB: b[key],
|
|
491
|
-
});
|
|
492
|
-
return false;
|
|
493
|
-
}
|
|
494
445
|
}
|
|
495
446
|
return true;
|
|
496
447
|
}
|
|
@@ -538,6 +489,12 @@
|
|
|
538
489
|
});
|
|
539
490
|
return fn;
|
|
540
491
|
}
|
|
492
|
+
const _mode = (typeof process !== 'undefined' && process.env?.NODE_ENV) ||
|
|
493
|
+
(typeof ({ url: (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('mutts.umd.js', document.baseURI).href)) }) !== 'undefined' && undefined?.MODE) ||
|
|
494
|
+
'production';
|
|
495
|
+
const isDev = _mode === 'development';
|
|
496
|
+
const isProd = _mode === 'production';
|
|
497
|
+
const isTest = _mode === 'test';
|
|
541
498
|
|
|
542
499
|
// biome-ignore-all lint/suspicious/noConfusingVoidType: We *love* voids
|
|
543
500
|
// Standardized decorator system that works with both Legacy and Modern decorators
|
|
@@ -1103,9 +1060,7 @@
|
|
|
1103
1060
|
const isObject = currentOptions !== null &&
|
|
1104
1061
|
typeof currentOptions === 'object' &&
|
|
1105
1062
|
!Array.isArray(currentOptions);
|
|
1106
|
-
newArgs[targetIndex] = isObject
|
|
1107
|
-
? { ...defaultOptions, ...currentOptions }
|
|
1108
|
-
: defaultOptions;
|
|
1063
|
+
newArgs[targetIndex] = isObject ? { ...defaultOptions, ...currentOptions } : defaultOptions;
|
|
1109
1064
|
return fn.apply(this, newArgs);
|
|
1110
1065
|
};
|
|
1111
1066
|
if (opts.name)
|
|
@@ -1852,6 +1807,17 @@
|
|
|
1852
1807
|
return chained;
|
|
1853
1808
|
}
|
|
1854
1809
|
|
|
1810
|
+
const debugHooks = {
|
|
1811
|
+
isDevtoolsEnabled: () => false,
|
|
1812
|
+
registerEffect: () => { },
|
|
1813
|
+
getTriggerChain: () => [],
|
|
1814
|
+
captureStack: () => [],
|
|
1815
|
+
captureLineage: () => new Error().stack,
|
|
1816
|
+
formatStack: (stack) => [stack],
|
|
1817
|
+
recordTriggerLink: () => { },
|
|
1818
|
+
decorateError: () => { },
|
|
1819
|
+
};
|
|
1820
|
+
|
|
1855
1821
|
/******************************************************************************
|
|
1856
1822
|
Copyright (c) Microsoft Corporation.
|
|
1857
1823
|
|
|
@@ -1903,38 +1869,17 @@
|
|
|
1903
1869
|
}
|
|
1904
1870
|
return useValue ? value : void 0;
|
|
1905
1871
|
}
|
|
1906
|
-
function __setFunctionName(f, name, prefix) {
|
|
1907
|
-
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
|
1908
|
-
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
|
1909
|
-
}
|
|
1910
1872
|
function __classPrivateFieldGet(receiver, state, kind, f) {
|
|
1911
1873
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
1912
1874
|
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");
|
|
1913
1875
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
1914
1876
|
}
|
|
1915
1877
|
|
|
1916
|
-
function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
|
1917
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
1918
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
1919
|
-
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");
|
|
1920
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
1921
|
-
}
|
|
1922
|
-
|
|
1923
1878
|
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
1924
1879
|
var e = new Error(message);
|
|
1925
1880
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
1926
1881
|
};
|
|
1927
1882
|
|
|
1928
|
-
const debugHooks = {
|
|
1929
|
-
isDevtoolsEnabled: () => false,
|
|
1930
|
-
registerEffect: () => { },
|
|
1931
|
-
getTriggerChain: () => [],
|
|
1932
|
-
captureStack: () => [],
|
|
1933
|
-
captureLineage: () => new Error().stack,
|
|
1934
|
-
formatStack: (stack) => [stack],
|
|
1935
|
-
recordTriggerLink: () => { },
|
|
1936
|
-
};
|
|
1937
|
-
|
|
1938
1883
|
var _ZoneAggregator_zones;
|
|
1939
1884
|
function isu(z) {
|
|
1940
1885
|
return z;
|
|
@@ -2062,13 +2007,27 @@
|
|
|
2062
2007
|
}
|
|
2063
2008
|
}
|
|
2064
2009
|
_ZoneAggregator_zones = new WeakMap();
|
|
2010
|
+
/**
|
|
2011
|
+
* Aggregator of zones that should be preserved across async boundaries.
|
|
2012
|
+
* If you add a zone here, it will be preserved across async boundaries.
|
|
2013
|
+
*
|
|
2014
|
+
* @example
|
|
2015
|
+
* ```ts
|
|
2016
|
+
* import { Zone, asyncZone } from 'mutts'
|
|
2017
|
+
* const userZone = new Zone<User>()
|
|
2018
|
+
* asyncZone.add(userZone)
|
|
2019
|
+
* ```
|
|
2020
|
+
*/
|
|
2065
2021
|
const asyncZone = tag('async', new ZoneAggregator());
|
|
2066
2022
|
asyncHooks.addHook(() => {
|
|
2023
|
+
// capture state before async boundary
|
|
2067
2024
|
const zone = asyncZone.active;
|
|
2068
2025
|
return () => {
|
|
2026
|
+
// restore state after async boundary, temporarily
|
|
2069
2027
|
const prev = asyncZone.active;
|
|
2070
2028
|
asyncZone.active = zone;
|
|
2071
2029
|
return () => {
|
|
2030
|
+
// restore previous state from before our restore
|
|
2072
2031
|
asyncZone.active = prev;
|
|
2073
2032
|
};
|
|
2074
2033
|
};
|
|
@@ -2138,6 +2097,75 @@
|
|
|
2138
2097
|
return fn;
|
|
2139
2098
|
}
|
|
2140
2099
|
|
|
2100
|
+
const effectHistory = tag('effectHistory', new ZoneHistory());
|
|
2101
|
+
tag('effectHistory.present', effectHistory.present);
|
|
2102
|
+
asyncZone.add(effectHistory);
|
|
2103
|
+
/**
|
|
2104
|
+
* Aggregator for zones that need to be tracked along effects.
|
|
2105
|
+
* ie. in each effect, the active zone of the given zoning will be the one active at effect's definition
|
|
2106
|
+
*/
|
|
2107
|
+
const effectAggregator = tag('effectAggregator', new ZoneAggregator(effectHistory.present));
|
|
2108
|
+
function isRunning(effect) {
|
|
2109
|
+
const root = getRoot(effect);
|
|
2110
|
+
return effectHistory.some((e) => getRoot(e) === root);
|
|
2111
|
+
}
|
|
2112
|
+
function getActiveEffect() {
|
|
2113
|
+
return effectHistory.present.active;
|
|
2114
|
+
}
|
|
2115
|
+
const cleanups = new WeakMap();
|
|
2116
|
+
/**
|
|
2117
|
+
* Attach cleanup dependencies to an object. When `unlink(obj)` is called,
|
|
2118
|
+
* each dependency is disposed: functions are invoked with the cleanup reason,
|
|
2119
|
+
* objects are recursively `unlink`ed. This forms a cleanup tree.
|
|
2120
|
+
*
|
|
2121
|
+
* @param obj - The owner object
|
|
2122
|
+
* @param cleanupFns - Cleanup callbacks and/or child objects to unlink recursively
|
|
2123
|
+
* @returns The owner object (for chaining)
|
|
2124
|
+
*
|
|
2125
|
+
* @example
|
|
2126
|
+
* ```ts
|
|
2127
|
+
* // Functions are called with CleanupReason
|
|
2128
|
+
* link(parent, () => console.log('disposed'))
|
|
2129
|
+
*
|
|
2130
|
+
* // Objects are recursively unlinked
|
|
2131
|
+
* link(parent, childA, childB)
|
|
2132
|
+
*
|
|
2133
|
+
* // Mixed
|
|
2134
|
+
* link(parent, childObj, () => timer.clear())
|
|
2135
|
+
*
|
|
2136
|
+
* unlink(parent) // disposes childA, childB, calls the function
|
|
2137
|
+
* ```
|
|
2138
|
+
*/
|
|
2139
|
+
function link(obj, ...cleanupFns) {
|
|
2140
|
+
const set = cleanups.get(obj);
|
|
2141
|
+
if (!set)
|
|
2142
|
+
cleanups.set(obj, new Set(cleanupFns.filter(Boolean)));
|
|
2143
|
+
else
|
|
2144
|
+
for (const fn of cleanupFns)
|
|
2145
|
+
if (fn)
|
|
2146
|
+
set.add(fn);
|
|
2147
|
+
return obj;
|
|
2148
|
+
}
|
|
2149
|
+
/**
|
|
2150
|
+
* Dispose an object's cleanup dependencies. Functions are called with the
|
|
2151
|
+
* reason; linked objects are recursively unlinked. The cleanup set is removed
|
|
2152
|
+
* so calling `unlink` twice is safe (second call is a no-op).
|
|
2153
|
+
*
|
|
2154
|
+
* @param obj - The object to dispose
|
|
2155
|
+
* @param reason - Optional cleanup reason propagated to callbacks
|
|
2156
|
+
*/
|
|
2157
|
+
function unlink(obj, reason) {
|
|
2158
|
+
const set = cleanups.get(obj);
|
|
2159
|
+
if (set) {
|
|
2160
|
+
cleanups.delete(obj);
|
|
2161
|
+
for (const fn of set)
|
|
2162
|
+
if (typeof fn === 'function')
|
|
2163
|
+
fn(reason);
|
|
2164
|
+
else
|
|
2165
|
+
unlink(fn, reason);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2141
2169
|
const effectMarker = {
|
|
2142
2170
|
enter: 'effect:enter',
|
|
2143
2171
|
leave: 'effect:leave',
|
|
@@ -2200,6 +2228,11 @@
|
|
|
2200
2228
|
}
|
|
2201
2229
|
}
|
|
2202
2230
|
}
|
|
2231
|
+
// Track native reactivity
|
|
2232
|
+
/**
|
|
2233
|
+
* Symbol to mark class properties as non-reactive
|
|
2234
|
+
*/
|
|
2235
|
+
const unreactiveProperties = Symbol('unreactive-properties');
|
|
2203
2236
|
/**
|
|
2204
2237
|
* Symbol representing all properties in reactive tracking
|
|
2205
2238
|
*/
|
|
@@ -2209,14 +2242,6 @@
|
|
|
2209
2242
|
* Used by ownKeys proxy trap — Object.keys(), for..in, Map.keys() depend on this.
|
|
2210
2243
|
*/
|
|
2211
2244
|
const keysOf = Symbol('keys-of');
|
|
2212
|
-
/**
|
|
2213
|
-
* Symbol to check if an effect is stopped
|
|
2214
|
-
*/
|
|
2215
|
-
const stopped = Symbol('stopped');
|
|
2216
|
-
/**
|
|
2217
|
-
* Symbol to access effect cleanup function
|
|
2218
|
-
*/
|
|
2219
|
-
const cleanup = Symbol('cleanup');
|
|
2220
2245
|
/**
|
|
2221
2246
|
* Structured error codes for machine-readable diagnosis
|
|
2222
2247
|
*/
|
|
@@ -2290,6 +2315,12 @@
|
|
|
2290
2315
|
* @param runningChain - The array of effects from the detected one to the currently running one
|
|
2291
2316
|
*/
|
|
2292
2317
|
skipRunningEffect: (_effect) => { },
|
|
2318
|
+
/**
|
|
2319
|
+
* Debug purpose: called when an effect starts executing.
|
|
2320
|
+
* @param effect - The effect being executed (root function)
|
|
2321
|
+
* @param reaction - false for initial creation, true/CleanupReason for subsequent runs
|
|
2322
|
+
*/
|
|
2323
|
+
effectRun: (_effect, _reaction) => { },
|
|
2293
2324
|
/**
|
|
2294
2325
|
* Debug purpose: maximum effect chain (like call stack max depth)
|
|
2295
2326
|
* Used to prevent infinite loops
|
|
@@ -2416,34 +2447,6 @@
|
|
|
2416
2447
|
enableHistory: true,
|
|
2417
2448
|
historySize: 50,
|
|
2418
2449
|
},
|
|
2419
|
-
/**
|
|
2420
|
-
* Configuration for zone hooks - control which async APIs are hooked
|
|
2421
|
-
* Each option controls whether the corresponding async API is wrapped to preserve effect context
|
|
2422
|
-
* Only applies when asyncMode is enabled (truthy)
|
|
2423
|
-
* @deprecated Should take all when we made sure PIXI.create, Game.create, ... are -> .root()
|
|
2424
|
-
*/
|
|
2425
|
-
zones: {
|
|
2426
|
-
/**
|
|
2427
|
-
* Hook setTimeout to preserve effect context
|
|
2428
|
-
* @default true
|
|
2429
|
-
*/
|
|
2430
|
-
setTimeout: true,
|
|
2431
|
-
/**
|
|
2432
|
-
* Hook setInterval to preserve effect context
|
|
2433
|
-
* @default true
|
|
2434
|
-
*/
|
|
2435
|
-
setInterval: true,
|
|
2436
|
-
/**
|
|
2437
|
-
* Hook requestAnimationFrame (runs in untracked context when hooked)
|
|
2438
|
-
* @default true
|
|
2439
|
-
*/
|
|
2440
|
-
requestAnimationFrame: true,
|
|
2441
|
-
/**
|
|
2442
|
-
* Hook queueMicrotask to preserve effect context
|
|
2443
|
-
* @default true
|
|
2444
|
-
*/
|
|
2445
|
-
queueMicrotask: true,
|
|
2446
|
-
},
|
|
2447
2450
|
};
|
|
2448
2451
|
function optionCall(name, ...args) {
|
|
2449
2452
|
const fn = options[name];
|
|
@@ -2457,6 +2460,36 @@
|
|
|
2457
2460
|
options.warn(`options.${name} threw`, error);
|
|
2458
2461
|
}
|
|
2459
2462
|
}
|
|
2463
|
+
/** Production preset: no introspection, heuristic cycle detection, minimal overhead */
|
|
2464
|
+
const prodPreset = {
|
|
2465
|
+
maxEffectReaction: 'throw',
|
|
2466
|
+
cycleHandling: 'production',
|
|
2467
|
+
introspection: null,
|
|
2468
|
+
onMemoizationDiscrepancy: undefined,
|
|
2469
|
+
};
|
|
2470
|
+
/** Development preset (default): introspection on, early cycle detection, warnings */
|
|
2471
|
+
const devPreset = {
|
|
2472
|
+
maxEffectReaction: 'warn',
|
|
2473
|
+
cycleHandling: 'development',
|
|
2474
|
+
introspection: {
|
|
2475
|
+
gatherReasons: { lineages: 'touch' },
|
|
2476
|
+
logErrors: true,
|
|
2477
|
+
enableHistory: true,
|
|
2478
|
+
historySize: 50,
|
|
2479
|
+
},
|
|
2480
|
+
onMemoizationDiscrepancy: undefined,
|
|
2481
|
+
};
|
|
2482
|
+
/** Debug preset: full diagnostics, throws on violations, rich lineage capture */
|
|
2483
|
+
const debugPreset = {
|
|
2484
|
+
maxEffectReaction: 'debug',
|
|
2485
|
+
cycleHandling: 'debug',
|
|
2486
|
+
introspection: {
|
|
2487
|
+
gatherReasons: { lineages: 'both' },
|
|
2488
|
+
logErrors: true,
|
|
2489
|
+
enableHistory: true,
|
|
2490
|
+
historySize: 200,
|
|
2491
|
+
},
|
|
2492
|
+
};
|
|
2460
2493
|
// --- Proxy State (Merged from proxy-state.ts) ---
|
|
2461
2494
|
const objectToProxy = new WeakMap();
|
|
2462
2495
|
const proxyToObject = new WeakMap();
|
|
@@ -2476,54 +2509,28 @@
|
|
|
2476
2509
|
return proxyToObject.has(obj);
|
|
2477
2510
|
}
|
|
2478
2511
|
|
|
2479
|
-
const effectHistory = tag('effectHistory', new ZoneHistory());
|
|
2480
|
-
tag('effectHistory.present', effectHistory.present);
|
|
2481
|
-
asyncZone.add(effectHistory);
|
|
2482
|
-
/**
|
|
2483
|
-
* Aggregator for zones that need to be tracked along effects.
|
|
2484
|
-
* ie. in each effect, the active zone of the given zoning will be the one active at effect's definition
|
|
2485
|
-
*/
|
|
2486
|
-
const effectAggregator = tag('effectAggregator', new ZoneAggregator(effectHistory.present));
|
|
2487
|
-
function isRunning(effect) {
|
|
2488
|
-
const root = getRoot(effect);
|
|
2489
|
-
return effectHistory.some((e) => getRoot(e) === root);
|
|
2490
|
-
}
|
|
2491
|
-
function getActiveEffect() {
|
|
2492
|
-
return effectHistory.present.active;
|
|
2493
|
-
}
|
|
2494
|
-
/**
|
|
2495
|
-
* ADD a cleanup function to an object using the cleanup symbol.
|
|
2496
|
-
* The cleanup function will be called when the object needs to be disposed.
|
|
2497
|
-
*
|
|
2498
|
-
* Note: most of the time, you don't need to use this function directly.
|
|
2499
|
-
* 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.
|
|
2500
|
-
*
|
|
2501
|
-
* @param obj - The object to attach the cleanup function to
|
|
2502
|
-
* @param cleanupFn - The cleanup function to attach
|
|
2503
|
-
* @returns The object with the cleanup function attached
|
|
2504
|
-
*/
|
|
2505
|
-
function cleanedBy(obj, cleanupFn) {
|
|
2506
|
-
const oldCleanup = obj[cleanup];
|
|
2507
|
-
return Object.defineProperty(obj, cleanup, {
|
|
2508
|
-
value: oldCleanup
|
|
2509
|
-
? Object.defineProperties((reason) => {
|
|
2510
|
-
oldCleanup(reason);
|
|
2511
|
-
cleanupFn(reason);
|
|
2512
|
-
}, {
|
|
2513
|
-
[stopped]: { get: () => oldCleanup[stopped] || cleanupFn[stopped] },
|
|
2514
|
-
})
|
|
2515
|
-
: cleanupFn,
|
|
2516
|
-
writable: false,
|
|
2517
|
-
enumerable: false,
|
|
2518
|
-
configurable: true,
|
|
2519
|
-
});
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
2512
|
// Track dependency stacks per (obj, prop, effect)
|
|
2523
2513
|
let dependencyStacks = new WeakMap();
|
|
2514
|
+
let assertUntrackedFlag = false;
|
|
2524
2515
|
function resetTracking() {
|
|
2525
2516
|
dependencyStacks = new WeakMap();
|
|
2526
2517
|
}
|
|
2518
|
+
/**
|
|
2519
|
+
* Executes a function and throws if any reactive dependencies are tracked during execution.
|
|
2520
|
+
* Used to assert that code runs in an untracked context.
|
|
2521
|
+
*/
|
|
2522
|
+
function assertUntracked(fn) {
|
|
2523
|
+
if (assertUntrackedFlag) {
|
|
2524
|
+
throw new Error('assertUntracked: nested calls are not supported');
|
|
2525
|
+
}
|
|
2526
|
+
assertUntrackedFlag = true;
|
|
2527
|
+
try {
|
|
2528
|
+
return fn();
|
|
2529
|
+
}
|
|
2530
|
+
finally {
|
|
2531
|
+
assertUntrackedFlag = false;
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2527
2534
|
function getDependencyStack(effect, obj, prop) {
|
|
2528
2535
|
const objStacks = dependencyStacks.get(obj);
|
|
2529
2536
|
if (!objStacks)
|
|
@@ -2536,14 +2543,17 @@
|
|
|
2536
2543
|
* @param prop - The property name (defaults to allProps)
|
|
2537
2544
|
*/
|
|
2538
2545
|
function dependant(obj, prop = allProps) {
|
|
2546
|
+
if (assertUntrackedFlag) {
|
|
2547
|
+
throw new Error(`Reactive dependency tracking detected in assertUntracked context: ${String(prop)} on ${obj}`);
|
|
2548
|
+
}
|
|
2539
2549
|
obj = unwrap(obj);
|
|
2540
2550
|
const currentActiveEffect = getActiveEffect();
|
|
2541
2551
|
// Early return if no active effect, tracking disabled, or invalid prop
|
|
2542
2552
|
if (!currentActiveEffect || (typeof prop === 'symbol' && prop !== allProps && prop !== keysOf))
|
|
2543
2553
|
return;
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2554
|
+
const node = getEffectNode(currentActiveEffect);
|
|
2555
|
+
if ('dependencyHook' in node) {
|
|
2556
|
+
node.dependencyHook(obj, prop);
|
|
2547
2557
|
}
|
|
2548
2558
|
let objectWatchers = watchers.get(obj);
|
|
2549
2559
|
if (!objectWatchers) {
|
|
@@ -2680,6 +2690,7 @@
|
|
|
2680
2690
|
// consequencesClosure: for each effect, all effects that it triggers (directly or indirectly)
|
|
2681
2691
|
let causesClosure = new WeakMap();
|
|
2682
2692
|
let consequencesClosure = new WeakMap();
|
|
2693
|
+
// Batch re-entrance depth and broken state
|
|
2683
2694
|
let broken = false;
|
|
2684
2695
|
/**
|
|
2685
2696
|
* Gets or creates an IterableWeakSet for a closure map
|
|
@@ -3333,6 +3344,7 @@
|
|
|
3333
3344
|
throw new Error('Activation registry already exists');
|
|
3334
3345
|
optionCall('beginChain', roots);
|
|
3335
3346
|
}
|
|
3347
|
+
// TODO: Consider this has been produced but was useless - it might be more correct ?const caller = executingStack.length > 0 ? getActiveEffect() : undefined
|
|
3336
3348
|
const caller = getActiveEffect();
|
|
3337
3349
|
// Optimization: If nested and NOT immediate, just join the existing batch
|
|
3338
3350
|
if (!isNewBatch && !immediate) {
|
|
@@ -3486,6 +3498,22 @@
|
|
|
3486
3498
|
};
|
|
3487
3499
|
},
|
|
3488
3500
|
});
|
|
3501
|
+
/**
|
|
3502
|
+
* Wraps `fn` so it runs within `effect`'s zone context when invoked later.
|
|
3503
|
+
*
|
|
3504
|
+
* Useful for deferred callbacks (event listeners, `DOMContentLoaded`, etc.)
|
|
3505
|
+
* that need sub-effects parented to the original effect.
|
|
3506
|
+
*
|
|
3507
|
+
* @param prev - The effect whose context should be restored, or `undefined` for root context
|
|
3508
|
+
* @param fn - The function to wrap
|
|
3509
|
+
* @returns A function with the same signature that restores the effect context before calling `fn`
|
|
3510
|
+
*/
|
|
3511
|
+
function captured(prev, fn) {
|
|
3512
|
+
prev ?? (prev = effectHistory.active);
|
|
3513
|
+
return named(effectMarker.leave, (...args) => {
|
|
3514
|
+
return effectHistory.with(prev, () => fn(...args));
|
|
3515
|
+
});
|
|
3516
|
+
}
|
|
3489
3517
|
/**
|
|
3490
3518
|
* Runs `fn` atomically and **always immediately**, batching all reactive effects
|
|
3491
3519
|
* triggered inside it so they fire only once after `fn` completes.
|
|
@@ -3508,19 +3536,13 @@
|
|
|
3508
3536
|
return batch(fn, 'immediate');
|
|
3509
3537
|
}
|
|
3510
3538
|
const fr = new FinalizationRegistry((f) => f());
|
|
3511
|
-
/**
|
|
3512
|
-
* @param fn - The effect function to run - provides the cleaner
|
|
3513
|
-
* @returns The cleanup function
|
|
3514
|
-
*/
|
|
3515
3539
|
/**
|
|
3516
3540
|
* Creates a reactive effect that automatically re-runs when dependencies change
|
|
3517
3541
|
* @param fn - The effect function that provides dependencies and may return a cleanup function or Promise
|
|
3518
3542
|
* @param options - Options for effect execution
|
|
3519
3543
|
* @returns A cleanup function to stop the effect
|
|
3520
3544
|
*/
|
|
3521
|
-
const effect = named(effectMarker.leave, flavored(function effect(
|
|
3522
|
-
// biome-ignore lint/suspicious/noConfusingVoidType: Effect callbacks commonly return void
|
|
3523
|
-
fn, effectOptions = {}) {
|
|
3545
|
+
const effect = named(effectMarker.leave, flavored(function effect(fn, effectOptions = {}) {
|
|
3524
3546
|
if (effectOptions?.name)
|
|
3525
3547
|
Object.defineProperty(fn, 'name', { value: effectOptions.name });
|
|
3526
3548
|
// Use per-effect asyncMode or fall back to global option
|
|
@@ -3544,6 +3566,7 @@
|
|
|
3544
3566
|
if (runningPromise) {
|
|
3545
3567
|
if (asyncMode === 'cancel' && cancelPrevious) {
|
|
3546
3568
|
// Cancel previous execution
|
|
3569
|
+
abort();
|
|
3547
3570
|
cancelPrevious();
|
|
3548
3571
|
cancelPrevious = null;
|
|
3549
3572
|
runningPromise = null;
|
|
@@ -3567,11 +3590,33 @@
|
|
|
3567
3590
|
access.reaction = node.nextReason || access.reaction;
|
|
3568
3591
|
node.nextReason = undefined;
|
|
3569
3592
|
optionCall('enter', getRoot(fn));
|
|
3593
|
+
optionCall('effectRun', getRoot(fn), access.reaction);
|
|
3570
3594
|
let result;
|
|
3571
3595
|
let caught = 0;
|
|
3572
|
-
//
|
|
3573
|
-
|
|
3574
|
-
|
|
3596
|
+
// Define bubbling thrower
|
|
3597
|
+
const thrower = (error) => {
|
|
3598
|
+
const catches = node.catchers;
|
|
3599
|
+
const reason = { type: 'error', error };
|
|
3600
|
+
if (catches)
|
|
3601
|
+
while (caught < catches.length) {
|
|
3602
|
+
cleanupReaction(reason);
|
|
3603
|
+
try {
|
|
3604
|
+
reactionCleanup = catches[caught](error);
|
|
3605
|
+
return;
|
|
3606
|
+
}
|
|
3607
|
+
catch (_e) {
|
|
3608
|
+
caught++;
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3611
|
+
if (parent) {
|
|
3612
|
+
const parentNode = getEffectNode(parent);
|
|
3613
|
+
if (parentNode.forwardThrow)
|
|
3614
|
+
parentNode.forwardThrow(error);
|
|
3615
|
+
else
|
|
3616
|
+
throw error;
|
|
3617
|
+
}
|
|
3618
|
+
else
|
|
3619
|
+
throw error;
|
|
3575
3620
|
};
|
|
3576
3621
|
node.forwardThrow = thrower;
|
|
3577
3622
|
let errorToThrow;
|
|
@@ -3627,6 +3672,7 @@
|
|
|
3627
3672
|
// Create cleanup function for next run
|
|
3628
3673
|
node.cleanup = (reason) => {
|
|
3629
3674
|
node.cleanup = undefined;
|
|
3675
|
+
abort();
|
|
3630
3676
|
cleanupReaction(reason);
|
|
3631
3677
|
delete node.catchers;
|
|
3632
3678
|
// Remove this effect from all reactive objects it's watching
|
|
@@ -3637,13 +3683,11 @@
|
|
|
3637
3683
|
if (objectWatchers) {
|
|
3638
3684
|
for (const [prop, deps] of objectWatchers.entries()) {
|
|
3639
3685
|
deps.delete(runEffect);
|
|
3640
|
-
if (deps.size === 0)
|
|
3686
|
+
if (deps.size === 0)
|
|
3641
3687
|
objectWatchers.delete(prop);
|
|
3642
|
-
}
|
|
3643
3688
|
}
|
|
3644
|
-
if (objectWatchers.size === 0)
|
|
3689
|
+
if (objectWatchers.size === 0)
|
|
3645
3690
|
watchers.delete(reactiveObj);
|
|
3646
|
-
}
|
|
3647
3691
|
}
|
|
3648
3692
|
}
|
|
3649
3693
|
effectToReactiveObjects.delete(runEffect);
|
|
@@ -3661,33 +3705,6 @@
|
|
|
3661
3705
|
delete node.children;
|
|
3662
3706
|
}
|
|
3663
3707
|
};
|
|
3664
|
-
// Define bubbling thrower
|
|
3665
|
-
thrower = (error) => {
|
|
3666
|
-
const catches = node.catchers;
|
|
3667
|
-
const reason = { type: 'error', error };
|
|
3668
|
-
if (catches)
|
|
3669
|
-
while (caught < catches.length) {
|
|
3670
|
-
cleanupReaction(reason);
|
|
3671
|
-
try {
|
|
3672
|
-
reactionCleanup = catches[caught](error);
|
|
3673
|
-
return;
|
|
3674
|
-
}
|
|
3675
|
-
catch (e) {
|
|
3676
|
-
caught++;
|
|
3677
|
-
}
|
|
3678
|
-
}
|
|
3679
|
-
if (parent) {
|
|
3680
|
-
const parentNode = getEffectNode(parent);
|
|
3681
|
-
if (parentNode.forwardThrow)
|
|
3682
|
-
parentNode.forwardThrow(error);
|
|
3683
|
-
else
|
|
3684
|
-
throw error;
|
|
3685
|
-
}
|
|
3686
|
-
else
|
|
3687
|
-
throw error;
|
|
3688
|
-
};
|
|
3689
|
-
// Update the node's forwardThrow to the bubbling one
|
|
3690
|
-
node.forwardThrow = thrower;
|
|
3691
3708
|
if (errorToThrow)
|
|
3692
3709
|
thrower(errorToThrow);
|
|
3693
3710
|
};
|
|
@@ -3700,31 +3717,36 @@
|
|
|
3700
3717
|
node.parent = parent;
|
|
3701
3718
|
// let thrower: CatchFunction | undefined // Moved inside runEffect
|
|
3702
3719
|
let effectStopped = false;
|
|
3720
|
+
let abortController;
|
|
3703
3721
|
const access = {
|
|
3704
3722
|
tracked,
|
|
3705
3723
|
ascend: named(effectMarker.leave, (fn) => ascended(named(effectMarker.enter, () => fn.call(null)))),
|
|
3706
3724
|
//named(effectMarker.enter, (fn) => ascended(fn)),
|
|
3707
3725
|
reaction: false,
|
|
3726
|
+
get signal() {
|
|
3727
|
+
if (!abortController) {
|
|
3728
|
+
abortController = new AbortController();
|
|
3729
|
+
}
|
|
3730
|
+
return abortController.signal;
|
|
3731
|
+
},
|
|
3708
3732
|
};
|
|
3709
3733
|
let runningPromise = null;
|
|
3710
3734
|
let cancelPrevious = null;
|
|
3711
|
-
if (effectOptions?.dependencyHook)
|
|
3735
|
+
if (effectOptions?.dependencyHook)
|
|
3712
3736
|
node.dependencyHook = effectOptions.dependencyHook;
|
|
3713
|
-
}
|
|
3714
3737
|
// Mark the runEffect callback with the original function as its root
|
|
3715
3738
|
markWithRoot(runEffect, fn);
|
|
3716
|
-
function augmentedRv(rv) {
|
|
3717
|
-
return Object.defineProperties(rv, {
|
|
3718
|
-
[stopped]: {
|
|
3719
|
-
get: () => effectStopped,
|
|
3720
|
-
},
|
|
3721
|
-
});
|
|
3722
|
-
}
|
|
3723
3739
|
// Register strict mode if enabled
|
|
3724
3740
|
if (effectOptions?.opaque) {
|
|
3725
3741
|
node.isOpaque = true;
|
|
3726
3742
|
}
|
|
3727
3743
|
// Store parent relationship for hierarchy traversal - ALREADY DONE ABOVE via getEffectNode
|
|
3744
|
+
const abort = () => {
|
|
3745
|
+
if (abortController) {
|
|
3746
|
+
abortController.abort(new ReactiveError('[reactive] Effect aborted due to dependency change or stop'));
|
|
3747
|
+
abortController = undefined;
|
|
3748
|
+
}
|
|
3749
|
+
};
|
|
3728
3750
|
batch(runEffect, 'immediate');
|
|
3729
3751
|
// Only ROOT effects are registered for GC cleanup and zone tracking
|
|
3730
3752
|
const isRootEffect = !parent;
|
|
@@ -3734,6 +3756,7 @@
|
|
|
3734
3756
|
effectStopped = true;
|
|
3735
3757
|
node.stopped = true;
|
|
3736
3758
|
// Cancel any running async work
|
|
3759
|
+
abort();
|
|
3737
3760
|
if (cancelPrevious) {
|
|
3738
3761
|
cancelPrevious();
|
|
3739
3762
|
cancelPrevious = null;
|
|
@@ -3752,7 +3775,7 @@
|
|
|
3752
3775
|
fr.unregister(stopEffect);
|
|
3753
3776
|
};
|
|
3754
3777
|
if (isRootEffect) {
|
|
3755
|
-
const callIfCollected =
|
|
3778
|
+
const callIfCollected = (reason) => stopEffect(reason);
|
|
3756
3779
|
fr.register(callIfCollected, () => {
|
|
3757
3780
|
stopEffect({ type: 'gc' });
|
|
3758
3781
|
optionCall('garbageCollected', fn);
|
|
@@ -3766,16 +3789,16 @@
|
|
|
3766
3789
|
parentNode.children = new Set();
|
|
3767
3790
|
}
|
|
3768
3791
|
const children = parentNode.children;
|
|
3769
|
-
const subEffectCleanup =
|
|
3792
|
+
const subEffectCleanup = (reason) => {
|
|
3770
3793
|
children.delete(subEffectCleanup);
|
|
3771
3794
|
// Execute this child effect cleanup (which triggers its own mainCleanup)
|
|
3772
3795
|
stopEffect(reason);
|
|
3773
|
-
}
|
|
3796
|
+
};
|
|
3774
3797
|
children.add(subEffectCleanup);
|
|
3775
3798
|
return subEffectCleanup;
|
|
3776
3799
|
}
|
|
3777
3800
|
// Should not be reachable given isRootEffect check, but for type safety
|
|
3778
|
-
return
|
|
3801
|
+
return (reason) => stopEffect(reason);
|
|
3779
3802
|
}, {
|
|
3780
3803
|
get opaque() {
|
|
3781
3804
|
return flavorOptions(this, { opaque: true }, { name: 'opaque' });
|
|
@@ -3806,9 +3829,11 @@
|
|
|
3806
3829
|
get = get.get;
|
|
3807
3830
|
}
|
|
3808
3831
|
let programmaticallySetValue = Symbol();
|
|
3809
|
-
effect(markWithRoot(() => {
|
|
3832
|
+
effect.named('biDi')(markWithRoot(() => {
|
|
3810
3833
|
const newValue = get();
|
|
3811
|
-
|
|
3834
|
+
const pValue = programmaticallySetValue;
|
|
3835
|
+
programmaticallySetValue = Symbol();
|
|
3836
|
+
if (unwrap(newValue) !== pValue)
|
|
3812
3837
|
received(newValue);
|
|
3813
3838
|
}, received));
|
|
3814
3839
|
return set
|
|
@@ -3879,16 +3904,16 @@
|
|
|
3879
3904
|
const parents = objectParents.get(changedObject);
|
|
3880
3905
|
if (!parents)
|
|
3881
3906
|
return;
|
|
3882
|
-
for (const { parent
|
|
3907
|
+
for (const { parent } of parents) {
|
|
3883
3908
|
// Trigger deep watchers on parent
|
|
3884
3909
|
const parentDeepWatchers = deepWatchers.get(parent);
|
|
3885
3910
|
if (parentDeepWatchers) {
|
|
3886
3911
|
if (options.introspection?.gatherReasons) {
|
|
3887
3912
|
const gatherReasons = options.introspection.gatherReasons;
|
|
3888
3913
|
const lineageConfig = gatherReasons.lineages;
|
|
3889
|
-
let
|
|
3914
|
+
let touchLineage;
|
|
3890
3915
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
3891
|
-
|
|
3916
|
+
touchLineage = debugHooks.captureLineage();
|
|
3892
3917
|
}
|
|
3893
3918
|
for (const watcher of parentDeepWatchers) {
|
|
3894
3919
|
const dependencyStack = lineageConfig === 'dependency' || lineageConfig === 'both'
|
|
@@ -3901,7 +3926,7 @@
|
|
|
3901
3926
|
obj: parent,
|
|
3902
3927
|
evolution,
|
|
3903
3928
|
dependency: dependencyStack,
|
|
3904
|
-
touch:
|
|
3929
|
+
touch: touchLineage,
|
|
3905
3930
|
});
|
|
3906
3931
|
}
|
|
3907
3932
|
}
|
|
@@ -3949,11 +3974,13 @@
|
|
|
3949
3974
|
return state;
|
|
3950
3975
|
}
|
|
3951
3976
|
function collectEffects(obj, evolution, effects, objectWatchers, ...keyChains) {
|
|
3952
|
-
getActiveEffect();
|
|
3977
|
+
const sourceEffect = getActiveEffect();
|
|
3953
3978
|
for (const keys of keyChains)
|
|
3954
3979
|
for (const key of keys) {
|
|
3955
3980
|
const deps = objectWatchers.get(key);
|
|
3956
3981
|
if (deps) {
|
|
3982
|
+
// Make sure `some.prop++` does not keep a dependency to `some.props`
|
|
3983
|
+
deps.delete(sourceEffect);
|
|
3957
3984
|
for (const effect of deps) {
|
|
3958
3985
|
const runningChain = isRunning(effect);
|
|
3959
3986
|
if (runningChain) {
|
|
@@ -4003,9 +4030,9 @@
|
|
|
4003
4030
|
if (options.introspection?.gatherReasons) {
|
|
4004
4031
|
const gatherReasons = options.introspection.gatherReasons;
|
|
4005
4032
|
const lineageConfig = gatherReasons.lineages;
|
|
4006
|
-
let
|
|
4033
|
+
let touchLineage;
|
|
4007
4034
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
4008
|
-
|
|
4035
|
+
touchLineage = debugHooks.captureLineage();
|
|
4009
4036
|
}
|
|
4010
4037
|
for (const [effect, dependencyStack] of effects) {
|
|
4011
4038
|
const node = getEffectNode(effect);
|
|
@@ -4015,7 +4042,7 @@
|
|
|
4015
4042
|
obj,
|
|
4016
4043
|
evolution,
|
|
4017
4044
|
dependency: dependencyStack,
|
|
4018
|
-
touch:
|
|
4045
|
+
touch: touchLineage,
|
|
4019
4046
|
});
|
|
4020
4047
|
}
|
|
4021
4048
|
}
|
|
@@ -4054,10 +4081,10 @@
|
|
|
4054
4081
|
}
|
|
4055
4082
|
effects.add(effect);
|
|
4056
4083
|
if (gather) {
|
|
4057
|
-
let
|
|
4084
|
+
let touchLineage;
|
|
4058
4085
|
let dependencyStack;
|
|
4059
4086
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
4060
|
-
|
|
4087
|
+
touchLineage = debugHooks.captureLineage();
|
|
4061
4088
|
}
|
|
4062
4089
|
if (lineageConfig === 'dependency' || lineageConfig === 'both') {
|
|
4063
4090
|
dependencyStack = getDependencyStack(effect, obj, prop);
|
|
@@ -4068,7 +4095,7 @@
|
|
|
4068
4095
|
obj,
|
|
4069
4096
|
evolution,
|
|
4070
4097
|
dependency: dependencyStack,
|
|
4071
|
-
touch:
|
|
4098
|
+
touch: touchLineage,
|
|
4072
4099
|
});
|
|
4073
4100
|
}
|
|
4074
4101
|
recordActivation(effect, obj, evolution, prop);
|
|
@@ -4095,264 +4122,60 @@
|
|
|
4095
4122
|
}
|
|
4096
4123
|
}
|
|
4097
4124
|
|
|
4098
|
-
const
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4125
|
+
const absent = Symbol('absent');
|
|
4126
|
+
/**
|
|
4127
|
+
* Add unreactive properties to a prototype.
|
|
4128
|
+
* If no set is provided, marks the entire object/prototype as non-reactive (sets [unreactiveProperties] = true).
|
|
4129
|
+
* If a set is provided, merges with existing unreactive properties (never overrides true).
|
|
4130
|
+
*/
|
|
4131
|
+
function addUnreactiveProps(proto, set) {
|
|
4132
|
+
if (unreactiveProperties in proto) {
|
|
4133
|
+
const existing = proto[unreactiveProperties];
|
|
4134
|
+
// If already fully unreactive, don't change
|
|
4135
|
+
if (existing === true)
|
|
4136
|
+
return proto;
|
|
4137
|
+
// If no set provided, upgrade to fully unreactive
|
|
4138
|
+
if (!set) {
|
|
4139
|
+
proto[unreactiveProperties] = true;
|
|
4140
|
+
return proto;
|
|
4141
|
+
}
|
|
4142
|
+
// Merge sets
|
|
4143
|
+
set = proto[unreactiveProperties] = new Set(proto[unreactiveProperties]);
|
|
4144
|
+
for (const p of set)
|
|
4145
|
+
existing.add(p);
|
|
4146
|
+
}
|
|
4147
|
+
// If no set, mark as fully unreactive, otherwise create set
|
|
4148
|
+
else
|
|
4149
|
+
proto[unreactiveProperties] = set ? new Set(set) : true;
|
|
4150
|
+
return proto;
|
|
4151
|
+
}
|
|
4152
|
+
/** Check if a property is marked unreactive on obj or any of its prototypes (trap-free) */
|
|
4153
|
+
function isUnreactiveProp(obj, prop) {
|
|
4154
|
+
if (typeof prop === 'symbol' || prop === 'constructor')
|
|
4155
|
+
return true;
|
|
4156
|
+
const marker = obj[unreactiveProperties];
|
|
4157
|
+
return (marker === true || // Fully unreactive
|
|
4158
|
+
marker?.has?.(prop) || // Property is unreactive
|
|
4159
|
+
false);
|
|
4160
|
+
}
|
|
4161
|
+
function nonReactive(...obj) {
|
|
4162
|
+
for (const o of obj) {
|
|
4163
|
+
o[unreactiveProperties] = true;
|
|
4106
4164
|
}
|
|
4107
|
-
return
|
|
4108
|
-
}
|
|
4109
|
-
function memoizeFunction(fn, opts) {
|
|
4110
|
-
const fnRoot = getRoot(fn);
|
|
4111
|
-
const existing = memoizedRegistry.get(fnRoot);
|
|
4112
|
-
if (existing)
|
|
4113
|
-
return existing;
|
|
4114
|
-
const cacheRoot = {};
|
|
4115
|
-
const memoized = markWithRoot(function memoized(...args) {
|
|
4116
|
-
if (args.some((arg) => !(arg && ['object', 'symbol', 'function'].includes(typeof arg)))) {
|
|
4117
|
-
if (opts?.lenient)
|
|
4118
|
-
return fn.apply(this, args);
|
|
4119
|
-
throw new Error('memoize expects non-null object arguments');
|
|
4120
|
-
}
|
|
4121
|
-
let node = cacheRoot;
|
|
4122
|
-
// Note: decorators add `this` as first argument
|
|
4123
|
-
for (const arg of args) {
|
|
4124
|
-
node = getBranch(node, arg);
|
|
4125
|
-
}
|
|
4126
|
-
dependant(node, 'memoize');
|
|
4127
|
-
if ('result' in node) {
|
|
4128
|
-
if (options.onMemoizationDiscrepancy) {
|
|
4129
|
-
const wasVerification = options.isVerificationRun;
|
|
4130
|
-
options.isVerificationRun = true;
|
|
4131
|
-
try {
|
|
4132
|
-
const fresh = untracked(() => fn.apply(this, args));
|
|
4133
|
-
if (!deepCompare(node.result, fresh)) {
|
|
4134
|
-
optionCall('onMemoizationDiscrepancy', node.result, fresh, fn, args, 'calculation');
|
|
4135
|
-
}
|
|
4136
|
-
}
|
|
4137
|
-
finally {
|
|
4138
|
-
options.isVerificationRun = wasVerification;
|
|
4139
|
-
}
|
|
4140
|
-
}
|
|
4141
|
-
return node.result;
|
|
4142
|
-
}
|
|
4143
|
-
// Create memoize internal effect to track dependencies and invalidate cache
|
|
4144
|
-
// Use untracked to prevent the effect creation from being affected by parent effects
|
|
4145
|
-
node.cleanup = root(() => effect.named('memoize')(() => {
|
|
4146
|
-
// Execute the function and track its dependencies
|
|
4147
|
-
// The function execution will automatically track dependencies on reactive objects
|
|
4148
|
-
node.result = fn.apply(this, args);
|
|
4149
|
-
return (reason) => {
|
|
4150
|
-
// When dependencies change, clear the cache and notify consumers
|
|
4151
|
-
delete node.result;
|
|
4152
|
-
touched1(node, { type: 'invalidate', prop: args }, 'memoize');
|
|
4153
|
-
// Lazy memoization: stop the effect so it doesn't re-run immediately.
|
|
4154
|
-
// It will be re-created on next access.
|
|
4155
|
-
if (node.cleanup) {
|
|
4156
|
-
node.cleanup({ type: 'invalidate', cause: reason });
|
|
4157
|
-
node.cleanup = undefined;
|
|
4158
|
-
}
|
|
4159
|
-
};
|
|
4160
|
-
}, { opaque: true }));
|
|
4161
|
-
if (options.onMemoizationDiscrepancy) {
|
|
4162
|
-
const wasVerification = options.isVerificationRun;
|
|
4163
|
-
options.isVerificationRun = true;
|
|
4164
|
-
try {
|
|
4165
|
-
const fresh = untracked(() => fn.apply(this, args));
|
|
4166
|
-
if (!deepCompare(node.result, fresh)) {
|
|
4167
|
-
optionCall('onMemoizationDiscrepancy', node.result, fresh, fn, args, 'comparison');
|
|
4168
|
-
}
|
|
4169
|
-
}
|
|
4170
|
-
finally {
|
|
4171
|
-
options.isVerificationRun = wasVerification;
|
|
4172
|
-
}
|
|
4173
|
-
}
|
|
4174
|
-
return node.result;
|
|
4175
|
-
}, fn);
|
|
4176
|
-
memoizedRegistry.set(fnRoot, memoized);
|
|
4177
|
-
memoizedRegistry.set(memoized, memoized);
|
|
4178
|
-
return memoized;
|
|
4179
|
-
}
|
|
4180
|
-
function memoizeObject(target, opts) {
|
|
4181
|
-
const existing = memoizedRegistry.get(target);
|
|
4182
|
-
if (existing)
|
|
4183
|
-
return existing;
|
|
4184
|
-
const proxy = new Proxy(target, {
|
|
4185
|
-
get(source, prop, receiver) {
|
|
4186
|
-
// 1. Walk prototype chain to find descriptor
|
|
4187
|
-
let current = source;
|
|
4188
|
-
let desc;
|
|
4189
|
-
while (current) {
|
|
4190
|
-
desc = Object.getOwnPropertyDescriptor(current, prop);
|
|
4191
|
-
if (desc)
|
|
4192
|
-
break;
|
|
4193
|
-
current = Object.getPrototypeOf(current);
|
|
4194
|
-
}
|
|
4195
|
-
if (!desc)
|
|
4196
|
-
return Reflect.get(source, prop, receiver);
|
|
4197
|
-
// 2. If getter, memoize
|
|
4198
|
-
if (desc.get) {
|
|
4199
|
-
const originalGetter = desc.get;
|
|
4200
|
-
let wrapper = wrapperRegistry.get(originalGetter);
|
|
4201
|
-
if (!wrapper) {
|
|
4202
|
-
wrapper = markWithRoot(named(`${String(source?.constructor?.name ?? 'Object')}.${String(prop)}`, (that) => {
|
|
4203
|
-
return originalGetter.call(that);
|
|
4204
|
-
}), {
|
|
4205
|
-
propertyKey: prop,
|
|
4206
|
-
});
|
|
4207
|
-
const origRoot = originalGetter[rootFunctionSymbol];
|
|
4208
|
-
if (origRoot)
|
|
4209
|
-
wrapper[rootFunctionSymbol] = origRoot;
|
|
4210
|
-
wrapperRegistry.set(originalGetter, wrapper);
|
|
4211
|
-
}
|
|
4212
|
-
const memoized = memoizeFunction(wrapper, opts);
|
|
4213
|
-
return memoized(receiver);
|
|
4214
|
-
}
|
|
4215
|
-
// 3. Otherwise forward
|
|
4216
|
-
return Reflect.get(source, prop, receiver);
|
|
4217
|
-
},
|
|
4218
|
-
// Forward set to the target (source) to ensure it acts as the receiver for reactivity notifications
|
|
4219
|
-
set(source, prop, value, _receiver) {
|
|
4220
|
-
// By strictly passing `source` as receiver, we ensure that if `source` is a reactive proxy,
|
|
4221
|
-
// it recognizes itself and triggers change notifications.
|
|
4222
|
-
return Reflect.set(source, prop, value, source);
|
|
4223
|
-
},
|
|
4224
|
-
});
|
|
4225
|
-
proxyToObject.set(proxy, target);
|
|
4226
|
-
memoizedRegistry.set(target, proxy);
|
|
4227
|
-
return proxy;
|
|
4228
|
-
}
|
|
4229
|
-
/**
|
|
4230
|
-
* Decorator and function wrapper for memoizing computed values based on reactive dependencies.
|
|
4231
|
-
*
|
|
4232
|
-
* When used as a decorator on getters or methods, it caches the result and automatically
|
|
4233
|
-
* invalidates the cache when reactive dependencies change.
|
|
4234
|
-
*
|
|
4235
|
-
* When used as a function wrapper, it memoizes based on object arguments (WeakMap-based cache).
|
|
4236
|
-
*
|
|
4237
|
-
* @example
|
|
4238
|
-
* ```typescript
|
|
4239
|
-
* class User {
|
|
4240
|
-
* @memoize
|
|
4241
|
-
* get fullName() {
|
|
4242
|
-
* return `${this.firstName} ${this.lastName}`
|
|
4243
|
-
* }
|
|
4244
|
-
* }
|
|
4245
|
-
*
|
|
4246
|
-
* // Or as a function wrapper
|
|
4247
|
-
* const expensive = memoize((obj: SomeObject) => {
|
|
4248
|
-
* return heavyComputation(obj)
|
|
4249
|
-
* })
|
|
4250
|
-
* ```
|
|
4251
|
-
*/
|
|
4252
|
-
function makeMemoizeDecorator(memoizeOpts) {
|
|
4253
|
-
return decorator({
|
|
4254
|
-
getter(original, target, propertyKey) {
|
|
4255
|
-
return function () {
|
|
4256
|
-
let wrapper = wrapperRegistry.get(original);
|
|
4257
|
-
if (!wrapper) {
|
|
4258
|
-
wrapper = markWithRoot(named(`${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(propertyKey)}`, (that) => {
|
|
4259
|
-
return original.call(that);
|
|
4260
|
-
}), {
|
|
4261
|
-
method: original,
|
|
4262
|
-
propertyKey,
|
|
4263
|
-
});
|
|
4264
|
-
const origRoot = original[rootFunctionSymbol];
|
|
4265
|
-
if (origRoot)
|
|
4266
|
-
wrapper[rootFunctionSymbol] = origRoot;
|
|
4267
|
-
wrapperRegistry.set(original, wrapper);
|
|
4268
|
-
}
|
|
4269
|
-
const memoized = memoizeFunction(wrapper, memoizeOpts);
|
|
4270
|
-
return memoized(this);
|
|
4271
|
-
};
|
|
4272
|
-
},
|
|
4273
|
-
method(original, target, name) {
|
|
4274
|
-
return function (...args) {
|
|
4275
|
-
let wrapper = wrapperRegistry.get(original);
|
|
4276
|
-
if (!wrapper) {
|
|
4277
|
-
wrapper = markWithRoot(named(`${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(name)}`, (that, ...args) => {
|
|
4278
|
-
return original.call(that, ...args);
|
|
4279
|
-
}), {
|
|
4280
|
-
method: original,
|
|
4281
|
-
propertyKey: name,
|
|
4282
|
-
});
|
|
4283
|
-
const origRoot = original[rootFunctionSymbol];
|
|
4284
|
-
if (origRoot)
|
|
4285
|
-
wrapper[rootFunctionSymbol] = origRoot;
|
|
4286
|
-
wrapperRegistry.set(original, wrapper);
|
|
4287
|
-
}
|
|
4288
|
-
const memoized = memoizeFunction(wrapper, memoizeOpts);
|
|
4289
|
-
return memoized(this, ...args);
|
|
4290
|
-
};
|
|
4291
|
-
},
|
|
4292
|
-
default: (target) => typeof target === 'object'
|
|
4293
|
-
? memoizeObject(target, memoizeOpts)
|
|
4294
|
-
: memoizeFunction(target, memoizeOpts),
|
|
4295
|
-
});
|
|
4296
|
-
}
|
|
4297
|
-
const memoize = flavored(makeMemoizeDecorator(), {
|
|
4298
|
-
get lenient() {
|
|
4299
|
-
return makeMemoizeDecorator({ lenient: true });
|
|
4300
|
-
},
|
|
4301
|
-
});
|
|
4302
|
-
|
|
4303
|
-
const nonReactiveObjects = new WeakSet();
|
|
4304
|
-
const nonReactiveClasses = new WeakSet();
|
|
4305
|
-
const unreactiveProps = new WeakMap();
|
|
4306
|
-
let unreactivePropsCount = 0;
|
|
4307
|
-
function addUnreactiveProps(proto, set) {
|
|
4308
|
-
unreactiveProps.set(proto, set);
|
|
4309
|
-
unreactivePropsCount++;
|
|
4310
|
-
}
|
|
4311
|
-
/** Check if a property is marked unreactive on obj or any of its prototypes (trap-free) */
|
|
4312
|
-
function isUnreactiveProp(obj, prop) {
|
|
4313
|
-
if (!unreactivePropsCount)
|
|
4314
|
-
return false;
|
|
4315
|
-
let target = obj;
|
|
4316
|
-
while (target) {
|
|
4317
|
-
if (unreactiveProps.get(target)?.has(prop))
|
|
4318
|
-
return true;
|
|
4319
|
-
target = Object.getPrototypeOf(target);
|
|
4320
|
-
}
|
|
4321
|
-
return false;
|
|
4322
|
-
}
|
|
4323
|
-
const immutables = new Set();
|
|
4324
|
-
const absent = Symbol('absent');
|
|
4325
|
-
function markNonReactive(...obj) {
|
|
4326
|
-
for (const o of obj)
|
|
4327
|
-
nonReactiveObjects.add(o);
|
|
4328
|
-
return obj[0];
|
|
4165
|
+
return obj[0];
|
|
4329
4166
|
}
|
|
4330
4167
|
function nonReactiveClass(...cls) {
|
|
4331
4168
|
for (const c of cls)
|
|
4332
4169
|
if (c)
|
|
4333
|
-
|
|
4170
|
+
c.prototype[unreactiveProperties] = true;
|
|
4334
4171
|
return cls[0];
|
|
4335
4172
|
}
|
|
4336
4173
|
function isNonReactive(obj) {
|
|
4337
|
-
|
|
4338
|
-
return true;
|
|
4339
|
-
if (nonReactiveObjects.has(obj))
|
|
4340
|
-
return true;
|
|
4341
|
-
// Walk the prototype chain on the raw object to check for non-reactive classes
|
|
4342
|
-
let proto = Object.getPrototypeOf(obj);
|
|
4343
|
-
while (proto) {
|
|
4344
|
-
if (nonReactiveClasses.has(proto))
|
|
4345
|
-
return true;
|
|
4346
|
-
proto = Object.getPrototypeOf(proto);
|
|
4347
|
-
}
|
|
4348
|
-
for (const fn of immutables)
|
|
4349
|
-
if (fn(obj))
|
|
4350
|
-
return true;
|
|
4351
|
-
return false;
|
|
4174
|
+
return !obj || obj[unreactiveProperties] === true;
|
|
4352
4175
|
}
|
|
4353
4176
|
nonReactiveClass(Date, RegExp, Error, Promise, Function);
|
|
4354
4177
|
if (typeof window !== 'undefined') {
|
|
4355
|
-
|
|
4178
|
+
nonReactive(window, document);
|
|
4356
4179
|
nonReactiveClass(Node, Element, HTMLElement, EventTarget, HTMLCollection, NodeList);
|
|
4357
4180
|
}
|
|
4358
4181
|
|
|
@@ -4374,10 +4197,33 @@
|
|
|
4374
4197
|
if ((typeof oldValue !== 'object' && !Array.isArray(oldValue)) ||
|
|
4375
4198
|
(typeof newValue !== 'object' && !Array.isArray(newValue)))
|
|
4376
4199
|
return false;
|
|
4377
|
-
if (isNonReactive(oldValue)
|
|
4200
|
+
if (isNonReactive(oldValue) /*|| isNonReactive(newValue)*/)
|
|
4378
4201
|
return false;
|
|
4379
4202
|
return getPrototypeToken(oldValue) === getPrototypeToken(newValue);
|
|
4380
4203
|
}
|
|
4204
|
+
/**
|
|
4205
|
+
* Migrate all watcher registrations from oldRef to newRef.
|
|
4206
|
+
* Called when deep touch replaces an object identity without any child value differences,
|
|
4207
|
+
* to prevent watcher orphaning (effects still pointing at the discarded old object).
|
|
4208
|
+
*/
|
|
4209
|
+
function migrateWatchers(oldRef, newRef) {
|
|
4210
|
+
const oldMap = watchers.get(oldRef);
|
|
4211
|
+
if (!oldMap)
|
|
4212
|
+
return;
|
|
4213
|
+
// Move the entire watcher map
|
|
4214
|
+
watchers.set(newRef, oldMap);
|
|
4215
|
+
watchers.delete(oldRef);
|
|
4216
|
+
// Update the reverse map (effect → objects it watches)
|
|
4217
|
+
for (const deps of oldMap.values()) {
|
|
4218
|
+
for (const effect of deps) {
|
|
4219
|
+
const objects = effectToReactiveObjects.get(effect);
|
|
4220
|
+
if (objects) {
|
|
4221
|
+
objects.delete(oldRef);
|
|
4222
|
+
objects.add(newRef);
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4381
4227
|
/**
|
|
4382
4228
|
* Centralized function to handle property change notifications with optional recursive touch
|
|
4383
4229
|
* @param targetObj - The object whose property changed
|
|
@@ -4395,7 +4241,15 @@
|
|
|
4395
4241
|
const origin = { obj: unwrappedObj, prop };
|
|
4396
4242
|
// Deep touch: only notify nested property changes with origin filtering
|
|
4397
4243
|
// Don't notify direct property change - the whole point is to avoid parent effects re-running
|
|
4398
|
-
|
|
4244
|
+
const changes = untracked(() => recursiveTouch(oldValue, newValue, new WeakMap(), [], origin));
|
|
4245
|
+
// When deep touch found no child differences, the object identity still changed.
|
|
4246
|
+
// Migrate watchers from old → new so the dependency chain is preserved.
|
|
4247
|
+
if (changes.length === 0) {
|
|
4248
|
+
migrateWatchers(unwrap(oldValue), unwrap(newValue));
|
|
4249
|
+
}
|
|
4250
|
+
else {
|
|
4251
|
+
dispatchNotifications(changes);
|
|
4252
|
+
}
|
|
4399
4253
|
// Notify opaque listeners (like memoize) that always want to know about identity changes
|
|
4400
4254
|
touchedOpaque(targetObj, evolution, prop);
|
|
4401
4255
|
}
|
|
@@ -4482,12 +4336,11 @@
|
|
|
4482
4336
|
for (const key of oldKeys)
|
|
4483
4337
|
if (!newKeys.has(key))
|
|
4484
4338
|
local.push({ target: oldObj, evolution: { type: 'del', prop: key }, prop: key, origin });
|
|
4485
|
-
for (const key of newKeys)
|
|
4486
|
-
if (!oldKeys.has(key))
|
|
4487
|
-
local.push({ target: oldObj, evolution: { type: 'add', prop: key }, prop: key, origin });
|
|
4488
4339
|
for (const key of newKeys) {
|
|
4489
|
-
if (!oldKeys.has(key))
|
|
4340
|
+
if (!oldKeys.has(key)) {
|
|
4341
|
+
local.push({ target: oldObj, evolution: { type: 'add', prop: key }, prop: key, origin });
|
|
4490
4342
|
continue;
|
|
4343
|
+
}
|
|
4491
4344
|
const oldEntry = unwrap(oldObj[key]);
|
|
4492
4345
|
const newEntry = unwrap(newObj[key]);
|
|
4493
4346
|
if (shouldRecurseTouch(oldEntry, newEntry)) {
|
|
@@ -4529,11 +4382,10 @@
|
|
|
4529
4382
|
if (originWatchers) {
|
|
4530
4383
|
const originEffects = new Map();
|
|
4531
4384
|
collectEffects(origin.obj, { type: 'set', prop: origin.prop }, originEffects, originWatchers, [allProps], [origin.prop]);
|
|
4532
|
-
|
|
4533
|
-
allowedEffects.add(effect);
|
|
4385
|
+
allowedEffects = new Set(originEffects.keys());
|
|
4534
4386
|
}
|
|
4535
4387
|
// If no allowed effects, skip all notifications (no one should be notified)
|
|
4536
|
-
if (allowedEffects
|
|
4388
|
+
if (!allowedEffects?.size)
|
|
4537
4389
|
return;
|
|
4538
4390
|
}
|
|
4539
4391
|
for (const notification of notifications) {
|
|
@@ -4546,7 +4398,6 @@
|
|
|
4546
4398
|
let currentEffects;
|
|
4547
4399
|
const propsArray = [prop];
|
|
4548
4400
|
if (objectWatchers) {
|
|
4549
|
-
// console.log(`[DEBUG] dispatchNotifications: processing ${obj.constructor.name} (has watchers)`)
|
|
4550
4401
|
currentEffects = new Map();
|
|
4551
4402
|
const broad = evolution.type !== 'set' ? [allProps, keysOf] : [allProps];
|
|
4552
4403
|
collectEffects(obj, evolution, currentEffects, objectWatchers, broad, propsArray);
|
|
@@ -4582,9 +4433,9 @@
|
|
|
4582
4433
|
if (options.introspection?.gatherReasons) {
|
|
4583
4434
|
const gatherReasons = options.introspection.gatherReasons;
|
|
4584
4435
|
const lineageConfig = gatherReasons.lineages;
|
|
4585
|
-
let
|
|
4436
|
+
let touchLineage;
|
|
4586
4437
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
4587
|
-
|
|
4438
|
+
touchLineage = debugHooks.captureLineage();
|
|
4588
4439
|
}
|
|
4589
4440
|
for (const effect of combinedEffects) {
|
|
4590
4441
|
const node = getEffectNode(effect);
|
|
@@ -4598,7 +4449,7 @@
|
|
|
4598
4449
|
obj: unwrap(target),
|
|
4599
4450
|
evolution,
|
|
4600
4451
|
dependency: dependencyStack,
|
|
4601
|
-
touch:
|
|
4452
|
+
touch: touchLineage,
|
|
4602
4453
|
});
|
|
4603
4454
|
}
|
|
4604
4455
|
}
|
|
@@ -4611,11 +4462,16 @@
|
|
|
4611
4462
|
const wrapProtos = new WeakMap();
|
|
4612
4463
|
const arrayLengths = new WeakMap();
|
|
4613
4464
|
const hasReentry = new Set();
|
|
4614
|
-
const subsRegister = new WeakMap();
|
|
4615
4465
|
// Sub-proxy registration for custom reactive behaviors
|
|
4466
|
+
const subsRegister = new WeakMap();
|
|
4467
|
+
// Internal untracked flag for setter/getter operations - only used when testing oldValue while setting a value
|
|
4468
|
+
// TODO: `touched` trigger also compares to old value and should use the internalUntracked flag
|
|
4469
|
+
let internalUntracked = false;
|
|
4616
4470
|
const reactiveHandlers = {
|
|
4617
4471
|
[Symbol.toStringTag]: 'MutTs Reactive',
|
|
4618
4472
|
get(obj, prop, receiver) {
|
|
4473
|
+
if (internalUntracked)
|
|
4474
|
+
return FoolProof.get(obj, prop, receiver);
|
|
4619
4475
|
if (obj && typeof obj === 'object' && prop !== Symbol.toStringTag) {
|
|
4620
4476
|
const metaProto = metaProtos.get(obj.constructor);
|
|
4621
4477
|
if (metaProto && Object.hasOwn(metaProto, prop)) {
|
|
@@ -4635,15 +4491,21 @@
|
|
|
4635
4491
|
if (wrapProto && Object.hasOwn(wrapProto, prop))
|
|
4636
4492
|
return wrapProto[prop];
|
|
4637
4493
|
}
|
|
4638
|
-
// Symbols: fast-path — no reactivity tracking
|
|
4639
|
-
if (typeof prop === 'symbol')
|
|
4640
|
-
return FoolProof.get(obj, prop, receiver);
|
|
4641
|
-
// Check if this property is marked as unreactive (WeakMap lookup — no proxy traps)
|
|
4642
|
-
if (isUnreactiveProp(obj, prop))
|
|
4494
|
+
// Symbols: fast-path — no reactivity tracking
|
|
4495
|
+
if (typeof prop === 'symbol' || prop === 'constructor' || isUnreactiveProp(obj, prop))
|
|
4643
4496
|
return FoolProof.get(obj, prop, receiver);
|
|
4644
4497
|
// Check if property exists using a trap-free walk to avoid triggering
|
|
4645
4498
|
// the has-trap cascade on prototype chains of reactive proxies.
|
|
4646
4499
|
const isOwnProp = Object.hasOwn(obj, prop);
|
|
4500
|
+
// For accessor properties, check the unwrapped object to see if it's an accessor
|
|
4501
|
+
// This ensures ignoreAccessors works correctly even after operations like Object.setPrototypeOf
|
|
4502
|
+
// Skip for null-proto objects (pounce scopes) — they never have accessors
|
|
4503
|
+
const shouldIgnoreAccessor = options.ignoreAccessors &&
|
|
4504
|
+
isOwnProp &&
|
|
4505
|
+
Object.getPrototypeOf(obj) !== null &&
|
|
4506
|
+
(isOwnAccessor(receiver, prop) || isOwnAccessor(obj, prop));
|
|
4507
|
+
// Check if property exists using a trap-free walk to avoid triggering
|
|
4508
|
+
// the has-trap cascade on prototype chains of reactive proxies.
|
|
4647
4509
|
let hasProp = isOwnProp;
|
|
4648
4510
|
let owner = isOwnProp ? obj : undefined;
|
|
4649
4511
|
if (!isOwnProp) {
|
|
@@ -4658,29 +4520,20 @@
|
|
|
4658
4520
|
}
|
|
4659
4521
|
}
|
|
4660
4522
|
const isInheritedAccess = hasProp && !isOwnProp;
|
|
4661
|
-
// For accessor properties, check the unwrapped object to see if it's an accessor
|
|
4662
|
-
// This ensures ignoreAccessors works correctly even after operations like Object.setPrototypeOf
|
|
4663
|
-
// Skip for null-proto objects (pounce scopes) — they never have accessors
|
|
4664
|
-
const shouldIgnoreAccessor = options.ignoreAccessors &&
|
|
4665
|
-
isOwnProp &&
|
|
4666
|
-
Object.getPrototypeOf(obj) !== null &&
|
|
4667
|
-
(isOwnAccessor(receiver, prop) || isOwnAccessor(obj, prop));
|
|
4668
4523
|
// Depend if...
|
|
4669
4524
|
if (!hasProp ||
|
|
4670
4525
|
(!(options.instanceMembers && isInheritedAccess && obj instanceof Object) &&
|
|
4671
4526
|
!shouldIgnoreAccessor))
|
|
4672
4527
|
dependant(obj, prop);
|
|
4673
|
-
// Two-Point Tracking: for inherited access on null-proto chains,
|
|
4674
|
-
// the owning ancestor
|
|
4675
|
-
// "structural stability" contract: key presence in the chain is fixed at
|
|
4676
|
-
// creation time, so intermediate levels never gain/lose shadowing properties.
|
|
4528
|
+
// Two-Point Tracking: for inherited access on null-proto chains, also track
|
|
4529
|
+
// the owning ancestor so that writing directly to it triggers dependent effects.
|
|
4677
4530
|
if (isInheritedAccess && owner && (!options.instanceMembers || !(obj instanceof Object))) {
|
|
4678
4531
|
dependant(owner, prop);
|
|
4679
4532
|
}
|
|
4680
4533
|
// For arrays, use FoolProof.get (Indexer path) for numeric index reactivity.
|
|
4681
4534
|
// For all other objects, inline Reflect.get directly (skips 3 function calls).
|
|
4682
4535
|
const value = (subsRegister.get(obj)?.get || FoolProof.get)(obj, prop, receiver);
|
|
4683
|
-
if (typeof value === 'object' && value !== null) {
|
|
4536
|
+
if (!isReactive(value) && typeof value === 'object' && value !== null) {
|
|
4684
4537
|
const reactiveValue = reactiveObject(value);
|
|
4685
4538
|
// Only create back-references if this object needs them
|
|
4686
4539
|
if (needsBackReferences(obj)) {
|
|
@@ -4691,9 +4544,19 @@
|
|
|
4691
4544
|
return value;
|
|
4692
4545
|
},
|
|
4693
4546
|
set(obj, prop, value, receiver) {
|
|
4694
|
-
const
|
|
4547
|
+
const unwrapped = unwrap(receiver);
|
|
4548
|
+
if (obj !== unwrapped)
|
|
4549
|
+
return Object.defineProperty(unwrapped, prop, {
|
|
4550
|
+
value,
|
|
4551
|
+
configurable: true,
|
|
4552
|
+
writable: true,
|
|
4553
|
+
enumerable: true,
|
|
4554
|
+
});
|
|
4555
|
+
if (internalUntracked)
|
|
4556
|
+
throw new Error('Internal untracked: setting a value in an getter in a set operation');
|
|
4557
|
+
//return FoolProof.set(obj, prop, value, receiver)
|
|
4695
4558
|
// Check if this property is marked as unreactive
|
|
4696
|
-
if (isUnreactiveProp(obj, prop)
|
|
4559
|
+
if (isUnreactiveProp(obj, prop))
|
|
4697
4560
|
return FoolProof.set(obj, prop, value, receiver);
|
|
4698
4561
|
const newValue = unwrap(value);
|
|
4699
4562
|
// metaProto setter dispatch (e.g., reactive array length)
|
|
@@ -4710,16 +4573,19 @@
|
|
|
4710
4573
|
// Read old value, using withEffect(undefined, ...) for getter-only accessors to avoid
|
|
4711
4574
|
// breaking memoization dependency tracking during SET operations
|
|
4712
4575
|
let oldVal = absent;
|
|
4713
|
-
// TODO: Pffft... Find a way to "generalize" this case?
|
|
4714
4576
|
const isArrayLength = prop === 'length' && Array.isArray(obj);
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4577
|
+
internalUntracked = true;
|
|
4578
|
+
try {
|
|
4579
|
+
if (Reflect.has(obj, prop)) {
|
|
4580
|
+
oldVal = isArrayLength
|
|
4581
|
+
? arrayLengths.get(obj) === newValue
|
|
4582
|
+
? newValue
|
|
4583
|
+
: absent
|
|
4584
|
+
: Reflect.get(obj, prop, receiver);
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
finally {
|
|
4588
|
+
internalUntracked = false;
|
|
4723
4589
|
}
|
|
4724
4590
|
if (objectsWithDeepWatchers.has(obj)) {
|
|
4725
4591
|
if (typeof oldVal === 'object' && oldVal !== null) {
|
|
@@ -4748,7 +4614,8 @@
|
|
|
4748
4614
|
cycle: [], // We don't have the full cycle here, but we know it involves obj
|
|
4749
4615
|
});
|
|
4750
4616
|
hasReentry.add(obj);
|
|
4751
|
-
|
|
4617
|
+
if (!internalUntracked && !isUnreactiveProp(obj, prop))
|
|
4618
|
+
dependant(obj, prop);
|
|
4752
4619
|
const rv = (subsRegister.get(obj)?.has || Reflect.has)(obj, prop);
|
|
4753
4620
|
hasReentry.delete(obj);
|
|
4754
4621
|
return rv;
|
|
@@ -4774,7 +4641,8 @@
|
|
|
4774
4641
|
return subsRegister.get(obj)?.ownKeys?.(obj) || Reflect.ownKeys(obj);
|
|
4775
4642
|
},
|
|
4776
4643
|
getOwnPropertyDescriptor(obj, prop) {
|
|
4777
|
-
return subsRegister.get(obj)?.getOwnPropertyDescriptor?.(obj, prop) ||
|
|
4644
|
+
return (subsRegister.get(obj)?.getOwnPropertyDescriptor?.(obj, prop) ||
|
|
4645
|
+
Reflect.getOwnPropertyDescriptor(obj, prop));
|
|
4778
4646
|
},
|
|
4779
4647
|
};
|
|
4780
4648
|
const reactiveClasses = new WeakSet();
|
|
@@ -4860,14 +4728,14 @@
|
|
|
4860
4728
|
? () => source.values()
|
|
4861
4729
|
: () => Object.keys(source);
|
|
4862
4730
|
const keyEffects = new Map();
|
|
4863
|
-
const outer = effect(({ ascend }) => {
|
|
4731
|
+
const outer = effect.named('attend')(({ ascend }) => {
|
|
4864
4732
|
const keys = new Set();
|
|
4865
4733
|
for (const key of enumerate())
|
|
4866
4734
|
keys.add(key);
|
|
4867
4735
|
for (const key of keys) {
|
|
4868
4736
|
if (keyEffects.has(key))
|
|
4869
4737
|
continue;
|
|
4870
|
-
keyEffects.set(key, ascend(() => effect(() => callback(key))));
|
|
4738
|
+
keyEffects.set(key, ascend(() => effect.named(`attend:${key}`)((access) => callback(key, access))));
|
|
4871
4739
|
}
|
|
4872
4740
|
for (const key of Array.from(keyEffects.keys())) {
|
|
4873
4741
|
if (!keys.has(key)) {
|
|
@@ -4883,146 +4751,10 @@
|
|
|
4883
4751
|
keyEffects.clear();
|
|
4884
4752
|
};
|
|
4885
4753
|
}
|
|
4886
|
-
/**
|
|
4887
|
-
* Perform a reactive scan over an array of items.
|
|
4888
|
-
*
|
|
4889
|
-
* This implementation is highly optimized for performance and fine-grained reactivity:
|
|
4890
|
-
* - **Incremental Updates**: Changes to an item only trigger re-computation from that
|
|
4891
|
-
* point onwards in the result chain.
|
|
4892
|
-
* - **Move Optimization**: If items are moved within the array, their accumulated
|
|
4893
|
-
* values are reused as long as their predecessor remains the same.
|
|
4894
|
-
* - **Duplicate Support**: Correctly handles multiple occurrences of the same object
|
|
4895
|
-
* instance using an internal occurrence tracking mechanism.
|
|
4896
|
-
* - **Memory Efficient**: Uses `WeakMap` for caching intermediates, which are
|
|
4897
|
-
* automatically cleared when source items are garbage collected.
|
|
4898
|
-
*
|
|
4899
|
-
* @example
|
|
4900
|
-
* ```typescript
|
|
4901
|
-
* const source = reactive([{ val: 1 }, { val: 2 }, { val: 3 }])
|
|
4902
|
-
* const sum = scan(source, (acc, item) => acc + item.val, 0)
|
|
4903
|
-
*
|
|
4904
|
-
* expect([...sum]).toEqual([1, 3, 6])
|
|
4905
|
-
*
|
|
4906
|
-
* // Modifying an item only re-computes subsequent sums
|
|
4907
|
-
* source[1].val = 10
|
|
4908
|
-
* expect([...sum]).toEqual([1, 11, 14])
|
|
4909
|
-
* ```
|
|
4910
|
-
*
|
|
4911
|
-
* @param source The source array of objects (will be made reactive)
|
|
4912
|
-
* @param callback The accumulator function called with (accumulator, currentItem)
|
|
4913
|
-
* @param initialValue The starting value for the accumulation
|
|
4914
|
-
* @returns A reactive array of accumulated values, with a [cleanup] property to stop the tracking
|
|
4915
|
-
*/
|
|
4916
|
-
function scan(source, callback, initialValue) {
|
|
4917
|
-
const observedSource = reactive(source);
|
|
4918
|
-
const rawResult = [];
|
|
4919
|
-
const result = reactive(rawResult);
|
|
4920
|
-
// Track effects for each index to dispose them when the array shrinks
|
|
4921
|
-
const indexEffects = new Map();
|
|
4922
|
-
// Mapping from index to its current intermediate object
|
|
4923
|
-
const indexToIntermediate = reactive([]);
|
|
4924
|
-
const intermediaries = new WeakMap();
|
|
4925
|
-
let Intermediate = (() => {
|
|
4926
|
-
var _a;
|
|
4927
|
-
let _instanceExtraInitializers = [];
|
|
4928
|
-
let _get_acc_decorators;
|
|
4929
|
-
return _a = class Intermediate {
|
|
4930
|
-
constructor(val, prev) {
|
|
4931
|
-
this.val = (__runInitializers(this, _instanceExtraInitializers), val);
|
|
4932
|
-
this.prev = prev;
|
|
4933
|
-
}
|
|
4934
|
-
get acc() {
|
|
4935
|
-
const prevAcc = this.prev ? this.prev.acc : initialValue;
|
|
4936
|
-
return callback(prevAcc, this.val);
|
|
4937
|
-
}
|
|
4938
|
-
},
|
|
4939
|
-
(() => {
|
|
4940
|
-
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
4941
|
-
_get_acc_decorators = [memoize];
|
|
4942
|
-
__esDecorate(_a, null, _get_acc_decorators, { kind: "getter", name: "acc", static: false, private: false, access: { has: obj => "acc" in obj, get: obj => obj.acc }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
4943
|
-
if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
4944
|
-
})(),
|
|
4945
|
-
_a;
|
|
4946
|
-
})();
|
|
4947
|
-
function disposeIndex(index) {
|
|
4948
|
-
const stop = indexEffects.get(index);
|
|
4949
|
-
if (stop) {
|
|
4950
|
-
stop();
|
|
4951
|
-
indexEffects.delete(index);
|
|
4952
|
-
Reflect.deleteProperty(indexToIntermediate, index);
|
|
4953
|
-
Reflect.deleteProperty(result, index);
|
|
4954
|
-
}
|
|
4955
|
-
}
|
|
4956
|
-
const mainEffect = effect(function scanMainEffect({ ascend }) {
|
|
4957
|
-
const length = observedSource.length;
|
|
4958
|
-
const occurrenceCount = new Map();
|
|
4959
|
-
let prev;
|
|
4960
|
-
for (let i = 0; i < length; i++)
|
|
4961
|
-
if (i in observedSource) {
|
|
4962
|
-
const val = observedSource[i];
|
|
4963
|
-
if (!(val &&
|
|
4964
|
-
(typeof val === 'object' || typeof val === 'function' || typeof val === 'symbol'))) {
|
|
4965
|
-
throw new Error('scan: items must be objects (WeakKey) for intermediate caching');
|
|
4966
|
-
}
|
|
4967
|
-
const count = occurrenceCount.get(val) ?? 0;
|
|
4968
|
-
occurrenceCount.set(val, count + 1);
|
|
4969
|
-
let list = intermediaries.get(val);
|
|
4970
|
-
if (!list) {
|
|
4971
|
-
list = [];
|
|
4972
|
-
intermediaries.set(val, list);
|
|
4973
|
-
}
|
|
4974
|
-
let intermediate = list[count];
|
|
4975
|
-
if (!intermediate) {
|
|
4976
|
-
intermediate = reactive(new Intermediate(val, prev));
|
|
4977
|
-
list[count] = intermediate;
|
|
4978
|
-
}
|
|
4979
|
-
else {
|
|
4980
|
-
// Update the link.
|
|
4981
|
-
if (untracked(() => intermediate.prev) !== prev) {
|
|
4982
|
-
intermediate.prev = prev;
|
|
4983
|
-
}
|
|
4984
|
-
}
|
|
4985
|
-
// Update the reactive mapping for this index
|
|
4986
|
-
if (indexToIntermediate[i] !== intermediate) {
|
|
4987
|
-
indexToIntermediate[i] = intermediate;
|
|
4988
|
-
}
|
|
4989
|
-
// If we don't have an effect for this index yet, create one
|
|
4990
|
-
if (!indexEffects.has(i)) {
|
|
4991
|
-
ascend(() => {
|
|
4992
|
-
const index = i;
|
|
4993
|
-
const stop = effect(function scanIndexSyncEffect() {
|
|
4994
|
-
const inter = indexToIntermediate[index];
|
|
4995
|
-
if (inter) {
|
|
4996
|
-
const accValue = inter.acc;
|
|
4997
|
-
result[index] = accValue;
|
|
4998
|
-
}
|
|
4999
|
-
});
|
|
5000
|
-
indexEffects.set(index, stop);
|
|
5001
|
-
});
|
|
5002
|
-
}
|
|
5003
|
-
prev = intermediate;
|
|
5004
|
-
}
|
|
5005
|
-
// Cleanup trailing indices
|
|
5006
|
-
for (const index of Array.from(indexEffects.keys())) {
|
|
5007
|
-
if (index >= length)
|
|
5008
|
-
disposeIndex(index);
|
|
5009
|
-
}
|
|
5010
|
-
// Ensure result length matches source length
|
|
5011
|
-
if (rawResult.length !== length) {
|
|
5012
|
-
FoolProof.set(result, 'length', length, result);
|
|
5013
|
-
}
|
|
5014
|
-
});
|
|
5015
|
-
return cleanedBy(result, ((reason) => {
|
|
5016
|
-
mainEffect(reason);
|
|
5017
|
-
for (const stop of indexEffects.values())
|
|
5018
|
-
stop(reason);
|
|
5019
|
-
indexEffects.clear();
|
|
5020
|
-
}));
|
|
5021
|
-
}
|
|
5022
4754
|
function lift(cb) {
|
|
5023
4755
|
let result;
|
|
5024
4756
|
let rawResult;
|
|
5025
|
-
const liftCleanup = effect.named(
|
|
4757
|
+
const liftCleanup = effect.named(`lift:${cb.name}`)(markWithRoot((access) => {
|
|
5026
4758
|
const source = cb(access);
|
|
5027
4759
|
if (!source || typeof source !== 'object')
|
|
5028
4760
|
throw new Error('lift callback must return an array or object');
|
|
@@ -5035,11 +4767,8 @@
|
|
|
5035
4767
|
throw new Error('lift callback must return the same type as the previous result');
|
|
5036
4768
|
if (Array.isArray(source)) {
|
|
5037
4769
|
const res = result;
|
|
5038
|
-
const
|
|
5039
|
-
for (let i = patches.length - 1; i >= 0; i--) {
|
|
5040
|
-
const { indexA, sliceA, sliceB } = patches[i];
|
|
4770
|
+
for (const { indexA, sliceA, sliceB } of arrayDiff(res, source).sort((a, b) => a.indexA - b.indexA))
|
|
5041
4771
|
res.splice(indexA, sliceA.length, ...sliceB);
|
|
5042
|
-
}
|
|
5043
4772
|
}
|
|
5044
4773
|
else {
|
|
5045
4774
|
for (const key of Object.keys(source)) {
|
|
@@ -5066,7 +4795,7 @@
|
|
|
5066
4795
|
}
|
|
5067
4796
|
}
|
|
5068
4797
|
}, cb));
|
|
5069
|
-
return
|
|
4798
|
+
return link(result, liftCleanup);
|
|
5070
4799
|
}
|
|
5071
4800
|
/**
|
|
5072
4801
|
* Reactively maps an array source through `fn`, producing a lazy reactive output array.
|
|
@@ -5087,7 +4816,7 @@
|
|
|
5087
4816
|
*/
|
|
5088
4817
|
function morphArray(source, fn, options) {
|
|
5089
4818
|
if (typeof source !== 'function' && !isReactive(source) && options?.pure === true) {
|
|
5090
|
-
return source.map(fn);
|
|
4819
|
+
return source.map((i) => fn(i));
|
|
5091
4820
|
}
|
|
5092
4821
|
let track;
|
|
5093
4822
|
const itemEffects = new Map();
|
|
@@ -5109,17 +4838,14 @@
|
|
|
5109
4838
|
}
|
|
5110
4839
|
else {
|
|
5111
4840
|
const indexRef = { value: key };
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
cache[indexRef.value]
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
};
|
|
5121
|
-
});
|
|
5122
|
-
});
|
|
4841
|
+
const stop = track(() => effect.named(`morph:${fn.name}:${key}`).opaque((access) => {
|
|
4842
|
+
cache[indexRef.value] = fn(input, access);
|
|
4843
|
+
return (reason) => {
|
|
4844
|
+
delete cache[indexRef.value];
|
|
4845
|
+
touched1(cache, { type: 'invalidate', prop: 'morph' }, String(key));
|
|
4846
|
+
stop?.({ type: 'invalidate', cause: reason });
|
|
4847
|
+
};
|
|
4848
|
+
}));
|
|
5123
4849
|
itemEffects.set(key, { stop, index: indexRef });
|
|
5124
4850
|
}
|
|
5125
4851
|
}
|
|
@@ -5136,7 +4862,7 @@
|
|
|
5136
4862
|
return Reflect.has(input, prop);
|
|
5137
4863
|
},
|
|
5138
4864
|
});
|
|
5139
|
-
const stopMain = effect.named(
|
|
4865
|
+
const stopMain = effect.named(`morph:${fn.name}`)(({ ascend }) => {
|
|
5140
4866
|
track = ascend;
|
|
5141
4867
|
const newInput = [...(typeof source === 'function' ? source() : source)];
|
|
5142
4868
|
const diffs = arrayDiff(input, newInput).toSorted((a, b) => b.indexA - a.indexA);
|
|
@@ -5151,7 +4877,7 @@
|
|
|
5151
4877
|
// We need to move entries in the Map.
|
|
5152
4878
|
const entries = Array.from(itemEffects.entries()).sort((a, b) => a[0] - b[0]);
|
|
5153
4879
|
// Remove entries that will be shifted
|
|
5154
|
-
for (const [idx,
|
|
4880
|
+
for (const [idx, _entry] of entries) {
|
|
5155
4881
|
if (idx >= diff.indexA + diff.sliceA.length) {
|
|
5156
4882
|
itemEffects.delete(idx);
|
|
5157
4883
|
}
|
|
@@ -5183,7 +4909,7 @@
|
|
|
5183
4909
|
}
|
|
5184
4910
|
input = newInput;
|
|
5185
4911
|
});
|
|
5186
|
-
return
|
|
4912
|
+
return link(proxy, (reason) => {
|
|
5187
4913
|
stopMain(reason);
|
|
5188
4914
|
for (const entry of itemEffects.values())
|
|
5189
4915
|
entry.stop(reason);
|
|
@@ -5205,7 +4931,7 @@
|
|
|
5205
4931
|
if (!isReactive(source) && options?.pure === true) {
|
|
5206
4932
|
const res = new Map();
|
|
5207
4933
|
for (const [k, v] of source)
|
|
5208
|
-
res.set(k, fn(v));
|
|
4934
|
+
res.set(k, fn(v, k));
|
|
5209
4935
|
return res;
|
|
5210
4936
|
}
|
|
5211
4937
|
let track;
|
|
@@ -5222,15 +4948,15 @@
|
|
|
5222
4948
|
function computeItem(key, val) {
|
|
5223
4949
|
const isPure = options?.pure === true || (typeof options?.pure === 'function' && options.pure(val));
|
|
5224
4950
|
if (isPure) {
|
|
5225
|
-
cache.set(key, track(() => fn(val)));
|
|
4951
|
+
cache.set(key, track(() => fn(val, key)));
|
|
5226
4952
|
}
|
|
5227
4953
|
else {
|
|
5228
|
-
const stop = track(() => effect.opaque(() => {
|
|
5229
|
-
cache.set(key, fn(source.get(key)));
|
|
4954
|
+
const stop = track(() => effect.named(`morph:${fn.name}:${key}`).opaque((access) => {
|
|
4955
|
+
cache.set(key, fn(source.get(key), key, access));
|
|
5230
4956
|
return (reason) => {
|
|
5231
4957
|
cache.delete(key);
|
|
5232
4958
|
touched1(cache, { type: 'invalidate', prop: 'morph' }, String(key));
|
|
5233
|
-
stop({ type: 'invalidate', cause: reason });
|
|
4959
|
+
stop?.({ type: 'invalidate', cause: reason });
|
|
5234
4960
|
};
|
|
5235
4961
|
}));
|
|
5236
4962
|
itemEffects.set(key, stop);
|
|
@@ -5274,7 +5000,7 @@
|
|
|
5274
5000
|
},
|
|
5275
5001
|
});
|
|
5276
5002
|
let stateSnapshot = getState(source);
|
|
5277
|
-
const stopMain = effect.named(
|
|
5003
|
+
const stopMain = effect.named(`morph:${fn.name}`)(({ ascend }) => {
|
|
5278
5004
|
track = ascend;
|
|
5279
5005
|
dependant(source, keysOf);
|
|
5280
5006
|
while ('evolution' in stateSnapshot) {
|
|
@@ -5290,7 +5016,7 @@
|
|
|
5290
5016
|
}
|
|
5291
5017
|
}
|
|
5292
5018
|
});
|
|
5293
|
-
return
|
|
5019
|
+
return link(proxy, (reason) => {
|
|
5294
5020
|
stopMain(reason);
|
|
5295
5021
|
for (const stop of itemEffects.values())
|
|
5296
5022
|
stop(reason);
|
|
@@ -5312,7 +5038,7 @@
|
|
|
5312
5038
|
if (!isReactive(source) && options?.pure === true) {
|
|
5313
5039
|
const res = {};
|
|
5314
5040
|
for (const k of Object.keys(source))
|
|
5315
|
-
res[k] = fn(source[k]);
|
|
5041
|
+
res[k] = fn(source[k], k);
|
|
5316
5042
|
return res;
|
|
5317
5043
|
}
|
|
5318
5044
|
let track;
|
|
@@ -5328,15 +5054,15 @@
|
|
|
5328
5054
|
function computeItem(key, val) {
|
|
5329
5055
|
const isPure = options?.pure === true || (typeof options?.pure === 'function' && options.pure(val));
|
|
5330
5056
|
if (isPure) {
|
|
5331
|
-
cache[key] = track(() => fn(val));
|
|
5057
|
+
cache[key] = track(() => fn(val, key));
|
|
5332
5058
|
}
|
|
5333
5059
|
else {
|
|
5334
|
-
const stop = track(() => effect.opaque(() => {
|
|
5335
|
-
cache[key] = fn(source[key]);
|
|
5060
|
+
const stop = track(() => effect.named(`morph:${fn.name}:${key}`).opaque((access) => {
|
|
5061
|
+
cache[key] = fn(source[key], key, access);
|
|
5336
5062
|
return (reason) => {
|
|
5337
5063
|
delete cache[key];
|
|
5338
5064
|
touched1(cache, { type: 'invalidate', prop: 'morph' }, String(key));
|
|
5339
|
-
stop({ type: 'invalidate', cause: reason });
|
|
5065
|
+
stop?.({ type: 'invalidate', cause: reason });
|
|
5340
5066
|
};
|
|
5341
5067
|
}));
|
|
5342
5068
|
itemEffects.set(key, stop);
|
|
@@ -5363,7 +5089,7 @@
|
|
|
5363
5089
|
},
|
|
5364
5090
|
});
|
|
5365
5091
|
let stateSnapshot = getState(source);
|
|
5366
|
-
const stopMain = effect.named(
|
|
5092
|
+
const stopMain = effect.named(`morph:${fn.name}`)(({ ascend }) => {
|
|
5367
5093
|
track = ascend;
|
|
5368
5094
|
// Track only structural changes on source
|
|
5369
5095
|
dependant(source, keysOf);
|
|
@@ -5380,7 +5106,7 @@
|
|
|
5380
5106
|
}
|
|
5381
5107
|
}
|
|
5382
5108
|
});
|
|
5383
|
-
return
|
|
5109
|
+
return link(proxy, (reason) => {
|
|
5384
5110
|
stopMain(reason);
|
|
5385
5111
|
for (const stop of itemEffects.values())
|
|
5386
5112
|
stop(reason);
|
|
@@ -5439,7 +5165,7 @@
|
|
|
5439
5165
|
const wrappedCallback = markWithRoot((() => callback(target)), callback);
|
|
5440
5166
|
registerDeepWatcher();
|
|
5441
5167
|
// Use the existing effect system to register dependencies
|
|
5442
|
-
return effect(() => {
|
|
5168
|
+
return effect.named('deepWatch')(() => {
|
|
5443
5169
|
// Mark the target object as having deep watchers
|
|
5444
5170
|
objectsWithDeepWatchers.add(target);
|
|
5445
5171
|
// Track which objects this effect is watching for cleanup
|
|
@@ -5475,77 +5201,271 @@
|
|
|
5475
5201
|
}
|
|
5476
5202
|
}
|
|
5477
5203
|
// Also handle array indices and length
|
|
5478
|
-
//
|
|
5479
|
-
if (
|
|
5480
|
-
// Access
|
|
5481
|
-
const
|
|
5482
|
-
// Access all current array elements to register dependencies
|
|
5483
|
-
for (let i = 0; i < length; i++) {
|
|
5484
|
-
// Access the array element to register dependency
|
|
5485
|
-
const value = obj[i];
|
|
5204
|
+
// Handle arrays and collections using iterators to ensure proxy tracking is triggered
|
|
5205
|
+
if (typeof obj[Symbol.iterator] === 'function') {
|
|
5206
|
+
// Access the iterator to track additions/removals/collection changes
|
|
5207
|
+
for (const value of obj) {
|
|
5486
5208
|
// Make the value reactive if it's an object
|
|
5487
5209
|
const reactiveValue = typeof value === 'object' && value !== null ? reactive(value) : value;
|
|
5488
5210
|
traverseAndTrack(reactiveValue, depth + 1);
|
|
5489
5211
|
}
|
|
5212
|
+
// Explicitly depend on length so array mutations changing count trigger re-evaluation
|
|
5213
|
+
if ('length' in obj) {
|
|
5214
|
+
dependant(obj, 'length');
|
|
5215
|
+
}
|
|
5216
|
+
// For Maps, also ensure we track values explicitly if the iterator yields entries
|
|
5217
|
+
if (obj instanceof Map) {
|
|
5218
|
+
for (const value of obj.values()) {
|
|
5219
|
+
const reactiveValue = typeof value === 'object' && value !== null ? reactive(value) : value;
|
|
5220
|
+
traverseAndTrack(reactiveValue, depth + 1);
|
|
5221
|
+
}
|
|
5222
|
+
}
|
|
5490
5223
|
}
|
|
5491
|
-
//
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5224
|
+
// Note: WeakSet and WeakMap cannot be iterated, so we can't deep watch their contents
|
|
5225
|
+
// They will only trigger when the collection itself is replaced
|
|
5226
|
+
}
|
|
5227
|
+
// Traverse the target object to register all dependencies
|
|
5228
|
+
// This will register dependencies on all current properties and array elements
|
|
5229
|
+
traverseAndTrack(target);
|
|
5230
|
+
// Only call the callback if immediate is true or if it's not the first run
|
|
5231
|
+
if (immediate) {
|
|
5232
|
+
untracked(() => callback(target));
|
|
5233
|
+
}
|
|
5234
|
+
immediate = true;
|
|
5235
|
+
// Return a cleanup function that properly removes deep watcher tracking
|
|
5236
|
+
return () => {
|
|
5237
|
+
// Get the objects this effect was watching
|
|
5238
|
+
const effectObjects = effectToDeepWatchedObjects.get(wrappedCallback);
|
|
5239
|
+
if (effectObjects) {
|
|
5240
|
+
// Remove deep watcher tracking from all objects this effect was watching
|
|
5241
|
+
for (const obj of effectObjects) {
|
|
5242
|
+
// Check if this object still has other deep watchers
|
|
5243
|
+
const watchers = deepWatchers.get(obj);
|
|
5244
|
+
if (watchers) {
|
|
5245
|
+
// Remove this effect's callback from the watchers
|
|
5246
|
+
watchers.delete(wrappedCallback);
|
|
5247
|
+
// If no more watchers, remove the object from deep watchers tracking
|
|
5248
|
+
if (watchers.size === 0) {
|
|
5249
|
+
deepWatchers.delete(obj);
|
|
5250
|
+
objectsWithDeepWatchers.delete(obj);
|
|
5251
|
+
}
|
|
5252
|
+
}
|
|
5253
|
+
else {
|
|
5254
|
+
// No watchers found, remove from deep watchers tracking
|
|
5255
|
+
objectsWithDeepWatchers.delete(obj);
|
|
5256
|
+
}
|
|
5257
|
+
}
|
|
5258
|
+
// Clean up the tracking data
|
|
5259
|
+
effectToDeepWatchedObjects.delete(wrappedCallback);
|
|
5260
|
+
}
|
|
5261
|
+
};
|
|
5262
|
+
});
|
|
5263
|
+
}
|
|
5264
|
+
|
|
5265
|
+
const memoizedRegistry = new WeakMap();
|
|
5266
|
+
const wrapperRegistry = new WeakMap();
|
|
5267
|
+
function getBranch(tree, key) {
|
|
5268
|
+
tree.branches ?? (tree.branches = new WeakMap());
|
|
5269
|
+
let branch = tree.branches.get(key);
|
|
5270
|
+
if (!branch) {
|
|
5271
|
+
branch = {};
|
|
5272
|
+
tree.branches.set(key, branch);
|
|
5273
|
+
}
|
|
5274
|
+
return branch;
|
|
5275
|
+
}
|
|
5276
|
+
function memoizeFunction(fn, opts) {
|
|
5277
|
+
const fnRoot = getRoot(fn);
|
|
5278
|
+
const existing = memoizedRegistry.get(fnRoot);
|
|
5279
|
+
if (existing)
|
|
5280
|
+
return existing;
|
|
5281
|
+
const cacheRoot = {};
|
|
5282
|
+
const memoized = markWithRoot(function memoized(...args) {
|
|
5283
|
+
if (args.some((arg) => !(arg && ['object', 'symbol', 'function'].includes(typeof arg)))) {
|
|
5284
|
+
if (opts?.lenient)
|
|
5285
|
+
return fn.apply(this, args);
|
|
5286
|
+
throw new Error('memoize expects non-null object arguments');
|
|
5287
|
+
}
|
|
5288
|
+
let node = cacheRoot;
|
|
5289
|
+
// Note: decorators add `this` as first argument
|
|
5290
|
+
for (const arg of args) {
|
|
5291
|
+
node = getBranch(node, arg);
|
|
5292
|
+
}
|
|
5293
|
+
dependant(node, 'memoize');
|
|
5294
|
+
if ('result' in node) {
|
|
5295
|
+
if (options.onMemoizationDiscrepancy) {
|
|
5296
|
+
const wasVerification = options.isVerificationRun;
|
|
5297
|
+
options.isVerificationRun = true;
|
|
5298
|
+
try {
|
|
5299
|
+
const fresh = untracked(() => fn.apply(this, args));
|
|
5300
|
+
if (!deepCompare(node.result, fresh)) {
|
|
5301
|
+
optionCall('onMemoizationDiscrepancy', node.result, fresh, fn, args, 'calculation');
|
|
5302
|
+
}
|
|
5303
|
+
}
|
|
5304
|
+
finally {
|
|
5305
|
+
options.isVerificationRun = wasVerification;
|
|
5306
|
+
}
|
|
5307
|
+
}
|
|
5308
|
+
return node.result;
|
|
5309
|
+
}
|
|
5310
|
+
// Create memoize internal effect to track dependencies and invalidate cache
|
|
5311
|
+
// Use untracked to prevent the effect creation from being affected by parent effects
|
|
5312
|
+
node.cleanup = root(() => effect.named('memoize')(() => {
|
|
5313
|
+
// Execute the function and track its dependencies
|
|
5314
|
+
// The function execution will automatically track dependencies on reactive objects
|
|
5315
|
+
node.result = fn.apply(this, args);
|
|
5316
|
+
return (reason) => {
|
|
5317
|
+
// When dependencies change, clear the cache and notify consumers
|
|
5318
|
+
delete node.result;
|
|
5319
|
+
touched1(node, { type: 'invalidate', prop: args }, 'memoize');
|
|
5320
|
+
// Lazy memoization: stop the effect so it doesn't re-run immediately.
|
|
5321
|
+
// It will be re-created on next access.
|
|
5322
|
+
if (node.cleanup) {
|
|
5323
|
+
node.cleanup({ type: 'invalidate', cause: reason });
|
|
5324
|
+
node.cleanup = undefined;
|
|
5325
|
+
}
|
|
5326
|
+
};
|
|
5327
|
+
}, { opaque: true }));
|
|
5328
|
+
if (options.onMemoizationDiscrepancy) {
|
|
5329
|
+
const wasVerification = options.isVerificationRun;
|
|
5330
|
+
options.isVerificationRun = true;
|
|
5331
|
+
try {
|
|
5332
|
+
const fresh = untracked(() => fn.apply(this, args));
|
|
5333
|
+
if (!deepCompare(node.result, fresh)) {
|
|
5334
|
+
optionCall('onMemoizationDiscrepancy', node.result, fresh, fn, args, 'comparison');
|
|
5498
5335
|
}
|
|
5499
5336
|
}
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5337
|
+
finally {
|
|
5338
|
+
options.isVerificationRun = wasVerification;
|
|
5339
|
+
}
|
|
5340
|
+
}
|
|
5341
|
+
return node.result;
|
|
5342
|
+
}, fn);
|
|
5343
|
+
memoizedRegistry.set(fnRoot, memoized);
|
|
5344
|
+
memoizedRegistry.set(memoized, memoized);
|
|
5345
|
+
return memoized;
|
|
5346
|
+
}
|
|
5347
|
+
function memoizeObject(target, opts) {
|
|
5348
|
+
const existing = memoizedRegistry.get(target);
|
|
5349
|
+
if (existing)
|
|
5350
|
+
return existing;
|
|
5351
|
+
const proxy = new Proxy(target, {
|
|
5352
|
+
get(source, prop, receiver) {
|
|
5353
|
+
// 1. Walk prototype chain to find descriptor
|
|
5354
|
+
let current = source;
|
|
5355
|
+
let desc;
|
|
5356
|
+
while (current) {
|
|
5357
|
+
desc = Object.getOwnPropertyDescriptor(current, prop);
|
|
5358
|
+
if (desc)
|
|
5359
|
+
break;
|
|
5360
|
+
current = Object.getPrototypeOf(current);
|
|
5361
|
+
}
|
|
5362
|
+
if (!desc)
|
|
5363
|
+
return Reflect.get(source, prop, receiver);
|
|
5364
|
+
// 2. If getter, memoize
|
|
5365
|
+
if (desc.get) {
|
|
5366
|
+
const originalGetter = desc.get;
|
|
5367
|
+
let wrapper = wrapperRegistry.get(originalGetter);
|
|
5368
|
+
if (!wrapper) {
|
|
5369
|
+
wrapper = markWithRoot(named(`${String(source?.constructor?.name ?? 'Object')}.${String(prop)}`, (that) => {
|
|
5370
|
+
return originalGetter.call(that);
|
|
5371
|
+
}), {
|
|
5372
|
+
propertyKey: prop,
|
|
5373
|
+
});
|
|
5374
|
+
const origRoot = originalGetter[rootFunctionSymbol];
|
|
5375
|
+
if (origRoot)
|
|
5376
|
+
wrapper[rootFunctionSymbol] = origRoot;
|
|
5377
|
+
wrapperRegistry.set(originalGetter, wrapper);
|
|
5507
5378
|
}
|
|
5379
|
+
const memoized = memoizeFunction(wrapper, opts);
|
|
5380
|
+
return memoized(receiver);
|
|
5508
5381
|
}
|
|
5509
|
-
//
|
|
5510
|
-
|
|
5511
|
-
}
|
|
5512
|
-
//
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5382
|
+
// 3. Otherwise forward
|
|
5383
|
+
return Reflect.get(source, prop, receiver);
|
|
5384
|
+
},
|
|
5385
|
+
// Forward set to the target (source) to ensure it acts as the receiver for reactivity notifications
|
|
5386
|
+
set(source, prop, value, _receiver) {
|
|
5387
|
+
// By strictly passing `source` as receiver, we ensure that if `source` is a reactive proxy,
|
|
5388
|
+
// it recognizes itself and triggers change notifications.
|
|
5389
|
+
return Reflect.set(source, prop, value, source);
|
|
5390
|
+
},
|
|
5391
|
+
});
|
|
5392
|
+
proxyToObject.set(proxy, target);
|
|
5393
|
+
memoizedRegistry.set(target, proxy);
|
|
5394
|
+
return proxy;
|
|
5395
|
+
}
|
|
5396
|
+
/**
|
|
5397
|
+
* Decorator and function wrapper for memoizing computed values based on reactive dependencies.
|
|
5398
|
+
*
|
|
5399
|
+
* When used as a decorator on getters or methods, it caches the result and automatically
|
|
5400
|
+
* invalidates the cache when reactive dependencies change.
|
|
5401
|
+
*
|
|
5402
|
+
* When used as a function wrapper, it memoizes based on object arguments (WeakMap-based cache).
|
|
5403
|
+
*
|
|
5404
|
+
* @example
|
|
5405
|
+
* ```typescript
|
|
5406
|
+
* class User {
|
|
5407
|
+
* @memoize
|
|
5408
|
+
* get fullName() {
|
|
5409
|
+
* return `${this.firstName} ${this.lastName}`
|
|
5410
|
+
* }
|
|
5411
|
+
* }
|
|
5412
|
+
*
|
|
5413
|
+
* // Or as a function wrapper
|
|
5414
|
+
* const expensive = memoize((obj: SomeObject) => {
|
|
5415
|
+
* return heavyComputation(obj)
|
|
5416
|
+
* })
|
|
5417
|
+
* ```
|
|
5418
|
+
*/
|
|
5419
|
+
function makeMemoizeDecorator(memoizeOpts) {
|
|
5420
|
+
return decorator({
|
|
5421
|
+
getter(original, target, propertyKey) {
|
|
5422
|
+
return function () {
|
|
5423
|
+
let wrapper = wrapperRegistry.get(original);
|
|
5424
|
+
if (!wrapper) {
|
|
5425
|
+
wrapper = markWithRoot(named(`${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(propertyKey)}`, (that) => {
|
|
5426
|
+
return original.call(that);
|
|
5427
|
+
}), {
|
|
5428
|
+
method: original,
|
|
5429
|
+
propertyKey,
|
|
5430
|
+
});
|
|
5431
|
+
const origRoot = original[rootFunctionSymbol];
|
|
5432
|
+
if (origRoot)
|
|
5433
|
+
wrapper[rootFunctionSymbol] = origRoot;
|
|
5434
|
+
wrapperRegistry.set(original, wrapper);
|
|
5542
5435
|
}
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
}
|
|
5546
|
-
}
|
|
5436
|
+
const memoized = memoizeFunction(wrapper, memoizeOpts);
|
|
5437
|
+
return memoized(this);
|
|
5438
|
+
};
|
|
5439
|
+
},
|
|
5440
|
+
method(original, target, name) {
|
|
5441
|
+
return function (...args) {
|
|
5442
|
+
let wrapper = wrapperRegistry.get(original);
|
|
5443
|
+
if (!wrapper) {
|
|
5444
|
+
wrapper = markWithRoot(named(`${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(name)}`, (that, ...args) => {
|
|
5445
|
+
return original.call(that, ...args);
|
|
5446
|
+
}), {
|
|
5447
|
+
method: original,
|
|
5448
|
+
propertyKey: name,
|
|
5449
|
+
});
|
|
5450
|
+
const origRoot = original[rootFunctionSymbol];
|
|
5451
|
+
if (origRoot)
|
|
5452
|
+
wrapper[rootFunctionSymbol] = origRoot;
|
|
5453
|
+
wrapperRegistry.set(original, wrapper);
|
|
5454
|
+
}
|
|
5455
|
+
const memoized = memoizeFunction(wrapper, memoizeOpts);
|
|
5456
|
+
return memoized(this, ...args);
|
|
5457
|
+
};
|
|
5458
|
+
},
|
|
5459
|
+
default: (target) => typeof target === 'object'
|
|
5460
|
+
? memoizeObject(target, memoizeOpts)
|
|
5461
|
+
: memoizeFunction(target, memoizeOpts),
|
|
5547
5462
|
});
|
|
5548
5463
|
}
|
|
5464
|
+
const memoize = flavored(makeMemoizeDecorator(), {
|
|
5465
|
+
get lenient() {
|
|
5466
|
+
return makeMemoizeDecorator({ lenient: true });
|
|
5467
|
+
},
|
|
5468
|
+
});
|
|
5549
5469
|
|
|
5550
5470
|
/**
|
|
5551
5471
|
* Organizes a source object's properties into a target object using a callback function.
|
|
@@ -5621,7 +5541,7 @@
|
|
|
5621
5541
|
});
|
|
5622
5542
|
return apply(accessBase, target);
|
|
5623
5543
|
});
|
|
5624
|
-
return
|
|
5544
|
+
return link(target, (reason) => stop(reason));
|
|
5625
5545
|
}
|
|
5626
5546
|
/**
|
|
5627
5547
|
* Organizes a property on a target object
|
|
@@ -5664,7 +5584,7 @@
|
|
|
5664
5584
|
function watchObject(value, changed, { immediate = false, deep = false } = {}) {
|
|
5665
5585
|
if (deep)
|
|
5666
5586
|
return deepWatch(value, changed, { immediate });
|
|
5667
|
-
return effect(
|
|
5587
|
+
return effect.named('watch:object')(() => {
|
|
5668
5588
|
dependant(value);
|
|
5669
5589
|
if (immediate)
|
|
5670
5590
|
changed(value);
|
|
@@ -5674,7 +5594,7 @@
|
|
|
5674
5594
|
function watchCallBack(value, changed, { immediate = false, deep = false } = {}) {
|
|
5675
5595
|
let oldValue = unsetYet;
|
|
5676
5596
|
let deepCleanup;
|
|
5677
|
-
const cbCleanup = effect(markWithRoot(
|
|
5597
|
+
const cbCleanup = effect.named('watch:callback')(markWithRoot((access) => {
|
|
5678
5598
|
const newValue = value(access);
|
|
5679
5599
|
if (oldValue !== newValue) {
|
|
5680
5600
|
const old = oldValue;
|
|
@@ -5692,12 +5612,10 @@
|
|
|
5692
5612
|
deepCleanup = deepWatch(newValue, (value) => changed(value, value));
|
|
5693
5613
|
}
|
|
5694
5614
|
}, value));
|
|
5695
|
-
return
|
|
5615
|
+
return (() => {
|
|
5696
5616
|
cbCleanup();
|
|
5697
5617
|
if (deepCleanup)
|
|
5698
5618
|
deepCleanup();
|
|
5699
|
-
}, {
|
|
5700
|
-
[stopped]: { get: () => cbCleanup[stopped] },
|
|
5701
5619
|
});
|
|
5702
5620
|
}
|
|
5703
5621
|
//#endregion
|
|
@@ -5712,7 +5630,7 @@
|
|
|
5712
5630
|
function when(predicate, timeout) {
|
|
5713
5631
|
return new Promise((resolve, reject) => {
|
|
5714
5632
|
let timer;
|
|
5715
|
-
const stop = effect(
|
|
5633
|
+
const stop = effect.named('watch:when')((access) => {
|
|
5716
5634
|
try {
|
|
5717
5635
|
const value = predicate(access);
|
|
5718
5636
|
if (value) {
|
|
@@ -5749,7 +5667,7 @@
|
|
|
5749
5667
|
obj = unwrap(obj);
|
|
5750
5668
|
if (isNonReactive(obj))
|
|
5751
5669
|
return obj;
|
|
5752
|
-
|
|
5670
|
+
obj[unreactiveProperties] = true;
|
|
5753
5671
|
return obj;
|
|
5754
5672
|
}
|
|
5755
5673
|
function unreactiveApplication(arg1, ...args) {
|
|
@@ -5757,13 +5675,19 @@
|
|
|
5757
5675
|
? shallowNonReactive(arg1)
|
|
5758
5676
|
: ((original) => {
|
|
5759
5677
|
// Copy the parent's unreactive properties if they exist
|
|
5760
|
-
const
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5678
|
+
const parentMarker = original.prototype[unreactiveProperties];
|
|
5679
|
+
// If parent is fully unreactive, child is too
|
|
5680
|
+
if (parentMarker === true) {
|
|
5681
|
+
original.prototype[unreactiveProperties] = true;
|
|
5682
|
+
}
|
|
5683
|
+
else {
|
|
5684
|
+
const set = new Set(parentMarker || []);
|
|
5685
|
+
// Add all arguments (including the first one)
|
|
5686
|
+
set.add(arg1);
|
|
5687
|
+
for (const arg of args)
|
|
5688
|
+
set.add(arg);
|
|
5689
|
+
addUnreactiveProps(original.prototype, set);
|
|
5690
|
+
}
|
|
5767
5691
|
return original; // Return the class
|
|
5768
5692
|
});
|
|
5769
5693
|
}
|
|
@@ -5773,11 +5697,26 @@
|
|
|
5773
5697
|
*/
|
|
5774
5698
|
const unreactive = decorator({
|
|
5775
5699
|
class(original) {
|
|
5776
|
-
|
|
5777
|
-
nonReactiveClass(original);
|
|
5700
|
+
original.prototype[unreactiveProperties] = true;
|
|
5778
5701
|
},
|
|
5779
5702
|
default: unreactiveApplication,
|
|
5780
5703
|
});
|
|
5704
|
+
//#endregion
|
|
5705
|
+
//#region resource
|
|
5706
|
+
function lazyInit(resource, load) {
|
|
5707
|
+
const creation = effectHistory.active;
|
|
5708
|
+
let fresh = true;
|
|
5709
|
+
return new Proxy(resource, {
|
|
5710
|
+
[Symbol.toStringTag]: 'LazyInit',
|
|
5711
|
+
get(target, prop) {
|
|
5712
|
+
if (fresh) {
|
|
5713
|
+
captured(creation, load)();
|
|
5714
|
+
fresh = false;
|
|
5715
|
+
}
|
|
5716
|
+
return target[prop];
|
|
5717
|
+
},
|
|
5718
|
+
});
|
|
5719
|
+
}
|
|
5781
5720
|
/**
|
|
5782
5721
|
* Creates a reactive resource that automatically tracks async state.
|
|
5783
5722
|
* @param fetcher - Async function that returns the value. Reactive dependencies are tracked.
|
|
@@ -5785,521 +5724,59 @@
|
|
|
5785
5724
|
* @returns Reactive Resource object with value, loading, error, latest properties
|
|
5786
5725
|
*/
|
|
5787
5726
|
function resource(fetcher, options = {}) {
|
|
5788
|
-
const
|
|
5727
|
+
const resource = reactive({
|
|
5789
5728
|
value: options.initialValue,
|
|
5790
5729
|
loading: true,
|
|
5791
5730
|
error: undefined,
|
|
5792
5731
|
latest: options.initialValue,
|
|
5793
|
-
reload
|
|
5732
|
+
reload() {
|
|
5794
5733
|
reloadSignal.value++;
|
|
5795
5734
|
},
|
|
5796
5735
|
});
|
|
5797
5736
|
const reloadSignal = reactive({ value: 0 });
|
|
5737
|
+
// Solve race conditions: make sure a new fast request is not overloaded by a slow old one
|
|
5798
5738
|
let counter = 0;
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
result
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
state.latest = result;
|
|
5824
|
-
state.loading = false;
|
|
5825
|
-
}
|
|
5826
|
-
}
|
|
5827
|
-
catch (err) {
|
|
5828
|
-
state.error = err;
|
|
5829
|
-
state.loading = false;
|
|
5830
|
-
}
|
|
5831
|
-
});
|
|
5832
|
-
state.stop = stop;
|
|
5833
|
-
return state;
|
|
5834
|
-
}
|
|
5835
|
-
//#endregion
|
|
5836
|
-
|
|
5837
|
-
// Helper to work around TypeScript limitation: base class expressions cannot reference class type parameters
|
|
5838
|
-
function getRegisterBase() {
|
|
5839
|
-
class RegisterBase extends Indexable(ArrayReadForward, {
|
|
5840
|
-
get(index) {
|
|
5841
|
-
return this[getAt](index);
|
|
5842
|
-
},
|
|
5843
|
-
set(index, value) {
|
|
5844
|
-
this[setAt](index, value);
|
|
5845
|
-
},
|
|
5846
|
-
getLength() {
|
|
5847
|
-
return this.length;
|
|
5848
|
-
},
|
|
5849
|
-
setLength(value) {
|
|
5850
|
-
this.length = value;
|
|
5851
|
-
},
|
|
5852
|
-
}) {
|
|
5853
|
-
toArray() {
|
|
5854
|
-
return Array.from(this);
|
|
5855
|
-
}
|
|
5856
|
-
}
|
|
5857
|
-
return RegisterBase;
|
|
5858
|
-
}
|
|
5859
|
-
let RegisterClass = (() => {
|
|
5860
|
-
var _RegisterClass_instances, _RegisterClass_keyFn, _RegisterClass_keys, _RegisterClass_values, _RegisterClass_usage, _RegisterClass_valueInfo, _RegisterClass_keyEffects, _RegisterClass_ascend, _RegisterClass_events, _RegisterClass_rekeyValue;
|
|
5861
|
-
let _classDecorators = [unreactive];
|
|
5862
|
-
let _classDescriptor;
|
|
5863
|
-
let _classExtraInitializers = [];
|
|
5864
|
-
let _classThis;
|
|
5865
|
-
let _classSuper = getRegisterBase();
|
|
5866
|
-
_classThis = class extends _classSuper {
|
|
5867
|
-
get [(_RegisterClass_keyFn = new WeakMap(), _RegisterClass_keys = new WeakMap(), _RegisterClass_values = new WeakMap(), _RegisterClass_usage = new WeakMap(), _RegisterClass_valueInfo = new WeakMap(), _RegisterClass_keyEffects = new WeakMap(), _RegisterClass_ascend = new WeakMap(), _RegisterClass_events = new WeakMap(), _RegisterClass_instances = new WeakSet(), forwardArray)]() {
|
|
5868
|
-
return this.toArray();
|
|
5869
|
-
}
|
|
5870
|
-
on(eventOrEvents, cb) {
|
|
5871
|
-
// @ts-expect-error Delegate to Eventful
|
|
5872
|
-
return __classPrivateFieldGet(this, _RegisterClass_events, "f").on(eventOrEvents, cb);
|
|
5873
|
-
}
|
|
5874
|
-
off(eventOrEvents, cb) {
|
|
5875
|
-
// @ts-expect-error Delegate to Eventful
|
|
5876
|
-
__classPrivateFieldGet(this, _RegisterClass_events, "f").off(eventOrEvents, cb);
|
|
5877
|
-
}
|
|
5878
|
-
/**
|
|
5879
|
-
* Register a global hook that receives all events
|
|
5880
|
-
*/
|
|
5881
|
-
hook(cb) {
|
|
5882
|
-
return __classPrivateFieldGet(this, _RegisterClass_events, "f").hook(cb);
|
|
5883
|
-
}
|
|
5884
|
-
constructor(keyFn, initial) {
|
|
5885
|
-
super();
|
|
5886
|
-
_RegisterClass_instances.add(this);
|
|
5887
|
-
_RegisterClass_keyFn.set(this, void 0);
|
|
5888
|
-
_RegisterClass_keys.set(this, void 0);
|
|
5889
|
-
_RegisterClass_values.set(this, void 0);
|
|
5890
|
-
_RegisterClass_usage.set(this, new Map());
|
|
5891
|
-
_RegisterClass_valueInfo.set(this, new Map());
|
|
5892
|
-
_RegisterClass_keyEffects.set(this, new Set());
|
|
5893
|
-
_RegisterClass_ascend.set(this, void 0);
|
|
5894
|
-
_RegisterClass_events.set(this, new Eventful()
|
|
5895
|
-
/**
|
|
5896
|
-
* Register event listeners for CRUD operations
|
|
5897
|
-
*/
|
|
5898
|
-
);
|
|
5899
|
-
/* Moved below initialization */
|
|
5900
|
-
let ascendGet;
|
|
5901
|
-
effect(({ ascend }) => {
|
|
5902
|
-
ascendGet = ascend;
|
|
5903
|
-
});
|
|
5904
|
-
__classPrivateFieldSet(this, _RegisterClass_ascend, ascendGet, "f");
|
|
5905
|
-
if (typeof keyFn !== 'function')
|
|
5906
|
-
throw new Error('Register requires a key function');
|
|
5907
|
-
__classPrivateFieldSet(this, _RegisterClass_keyFn, keyFn, "f");
|
|
5908
|
-
__classPrivateFieldSet(this, _RegisterClass_keys, reactive([]), "f");
|
|
5909
|
-
__classPrivateFieldSet(this, _RegisterClass_values, reactive(new Map()), "f");
|
|
5910
|
-
if (initial)
|
|
5911
|
-
this.push(...initial);
|
|
5912
|
-
}
|
|
5913
|
-
ensureKey(value) {
|
|
5914
|
-
let info = __classPrivateFieldGet(this, _RegisterClass_valueInfo, "f").get(value);
|
|
5915
|
-
if (info)
|
|
5916
|
-
return info.key;
|
|
5917
|
-
info = { key: undefined };
|
|
5918
|
-
__classPrivateFieldGet(this, _RegisterClass_valueInfo, "f").set(value, info);
|
|
5919
|
-
__classPrivateFieldGet(this, _RegisterClass_ascend, "f").call(this, () => {
|
|
5920
|
-
const stop = effect(({ reaction }) => {
|
|
5921
|
-
const nextKey = __classPrivateFieldGet(this, _RegisterClass_keyFn, "f").call(this, value);
|
|
5922
|
-
this.assertValidKey(nextKey);
|
|
5923
|
-
const previousKey = info.key;
|
|
5924
|
-
if (reaction && previousKey !== undefined && !Object.is(nextKey, previousKey))
|
|
5925
|
-
__classPrivateFieldGet(this, _RegisterClass_instances, "m", _RegisterClass_rekeyValue).call(this, value, previousKey, nextKey);
|
|
5926
|
-
info.key = nextKey;
|
|
5927
|
-
});
|
|
5928
|
-
info.stop = stop;
|
|
5929
|
-
__classPrivateFieldGet(this, _RegisterClass_keyEffects, "f").add(stop);
|
|
5930
|
-
});
|
|
5931
|
-
if (info.key === undefined)
|
|
5932
|
-
throw new Error('Register key function must return a property key');
|
|
5933
|
-
return info.key;
|
|
5934
|
-
}
|
|
5935
|
-
assertValidKey(key) {
|
|
5936
|
-
const type = typeof key;
|
|
5937
|
-
if (type !== 'string' && type !== 'number' && type !== 'symbol')
|
|
5938
|
-
throw new Error('Register key function must return a property key');
|
|
5939
|
-
}
|
|
5940
|
-
setKeyValue(key, value, index) {
|
|
5941
|
-
const existing = __classPrivateFieldGet(this, _RegisterClass_values, "f").get(key);
|
|
5942
|
-
if (existing !== undefined && existing !== value) {
|
|
5943
|
-
this.cleanupValue(existing);
|
|
5944
|
-
__classPrivateFieldGet(this, _RegisterClass_values, "f").set(key, value);
|
|
5945
|
-
if (index !== undefined) {
|
|
5946
|
-
__classPrivateFieldGet(this, _RegisterClass_events, "f").emit('update', existing, value, key, index);
|
|
5947
|
-
}
|
|
5948
|
-
}
|
|
5949
|
-
else {
|
|
5950
|
-
__classPrivateFieldGet(this, _RegisterClass_values, "f").set(key, value);
|
|
5951
|
-
}
|
|
5952
|
-
}
|
|
5953
|
-
cleanupValue(value) {
|
|
5954
|
-
const info = __classPrivateFieldGet(this, _RegisterClass_valueInfo, "f").get(value);
|
|
5955
|
-
if (!info)
|
|
5956
|
-
return;
|
|
5957
|
-
const stop = info.stop;
|
|
5958
|
-
if (stop) {
|
|
5959
|
-
info.stop = undefined;
|
|
5960
|
-
__classPrivateFieldGet(this, _RegisterClass_keyEffects, "f").delete(stop);
|
|
5961
|
-
stop();
|
|
5962
|
-
}
|
|
5963
|
-
__classPrivateFieldGet(this, _RegisterClass_valueInfo, "f").delete(value);
|
|
5964
|
-
}
|
|
5965
|
-
disposeKeyEffects() {
|
|
5966
|
-
for (const value of Array.from(__classPrivateFieldGet(this, _RegisterClass_valueInfo, "f").keys()))
|
|
5967
|
-
this.cleanupValue(value);
|
|
5968
|
-
__classPrivateFieldGet(this, _RegisterClass_keyEffects, "f").clear();
|
|
5969
|
-
}
|
|
5970
|
-
incrementUsage(key) {
|
|
5971
|
-
const count = __classPrivateFieldGet(this, _RegisterClass_usage, "f").get(key) ?? 0;
|
|
5972
|
-
__classPrivateFieldGet(this, _RegisterClass_usage, "f").set(key, count + 1);
|
|
5973
|
-
}
|
|
5974
|
-
decrementUsage(key, index) {
|
|
5975
|
-
const count = __classPrivateFieldGet(this, _RegisterClass_usage, "f").get(key);
|
|
5976
|
-
if (!count)
|
|
5977
|
-
return;
|
|
5978
|
-
if (count <= 1) {
|
|
5979
|
-
const value = __classPrivateFieldGet(this, _RegisterClass_values, "f").get(key);
|
|
5980
|
-
__classPrivateFieldGet(this, _RegisterClass_usage, "f").delete(key);
|
|
5981
|
-
__classPrivateFieldGet(this, _RegisterClass_values, "f").delete(key);
|
|
5982
|
-
if (value !== undefined) {
|
|
5983
|
-
this.cleanupValue(value);
|
|
5984
|
-
const idx = index ?? __classPrivateFieldGet(this, _RegisterClass_keys, "f").indexOf(key);
|
|
5985
|
-
__classPrivateFieldGet(this, _RegisterClass_events, "f").emit('delete', value, key, idx);
|
|
5986
|
-
}
|
|
5987
|
-
}
|
|
5988
|
-
else {
|
|
5989
|
-
__classPrivateFieldGet(this, _RegisterClass_usage, "f").set(key, count - 1);
|
|
5990
|
-
}
|
|
5991
|
-
}
|
|
5992
|
-
normalizeIndex(index, allowEnd = false) {
|
|
5993
|
-
const length = this.length;
|
|
5994
|
-
let resolved = index;
|
|
5995
|
-
if (resolved < 0)
|
|
5996
|
-
resolved = Math.max(length + resolved, 0);
|
|
5997
|
-
if (resolved > length) {
|
|
5998
|
-
if (allowEnd)
|
|
5999
|
-
resolved = length;
|
|
6000
|
-
else
|
|
6001
|
-
throw new RangeError('Index out of bounds');
|
|
6002
|
-
}
|
|
6003
|
-
if (!allowEnd && resolved === length)
|
|
6004
|
-
throw new RangeError('Index out of bounds');
|
|
6005
|
-
return resolved;
|
|
6006
|
-
}
|
|
6007
|
-
assignAt(index, key, value) {
|
|
6008
|
-
const oldKey = __classPrivateFieldGet(this, _RegisterClass_keys, "f")[index];
|
|
6009
|
-
if (oldKey !== undefined && Object.is(oldKey, key)) {
|
|
6010
|
-
const oldValue = __classPrivateFieldGet(this, _RegisterClass_values, "f").get(key);
|
|
6011
|
-
this.setKeyValue(key, value);
|
|
6012
|
-
if (oldValue !== undefined && oldValue !== value) {
|
|
6013
|
-
__classPrivateFieldGet(this, _RegisterClass_events, "f").emit('update', oldValue, value, key, index);
|
|
6014
|
-
}
|
|
6015
|
-
return;
|
|
6016
|
-
}
|
|
6017
|
-
if (oldKey !== undefined)
|
|
6018
|
-
this.decrementUsage(oldKey);
|
|
6019
|
-
__classPrivateFieldGet(this, _RegisterClass_keys, "f")[index] = key;
|
|
6020
|
-
this.incrementUsage(key);
|
|
6021
|
-
this.setKeyValue(key, value);
|
|
6022
|
-
__classPrivateFieldGet(this, _RegisterClass_events, "f").emit('add', value, key, index);
|
|
6023
|
-
}
|
|
6024
|
-
insertKeyValue(index, key, value) {
|
|
6025
|
-
__classPrivateFieldGet(this, _RegisterClass_keys, "f").splice(index, 0, key);
|
|
6026
|
-
this.incrementUsage(key);
|
|
6027
|
-
this.setKeyValue(key, value);
|
|
6028
|
-
__classPrivateFieldGet(this, _RegisterClass_events, "f").emit('add', value, key, index);
|
|
6029
|
-
}
|
|
6030
|
-
rebuildFrom(values) {
|
|
6031
|
-
this.disposeKeyEffects();
|
|
6032
|
-
__classPrivateFieldGet(this, _RegisterClass_keys, "f").splice(0, __classPrivateFieldGet(this, _RegisterClass_keys, "f").length);
|
|
6033
|
-
__classPrivateFieldGet(this, _RegisterClass_usage, "f").clear();
|
|
6034
|
-
__classPrivateFieldGet(this, _RegisterClass_values, "f").clear();
|
|
6035
|
-
for (const value of values) {
|
|
6036
|
-
const key = this.ensureKey(value);
|
|
6037
|
-
__classPrivateFieldGet(this, _RegisterClass_keys, "f").push(key);
|
|
6038
|
-
this.incrementUsage(key);
|
|
6039
|
-
__classPrivateFieldGet(this, _RegisterClass_values, "f").set(key, value);
|
|
6040
|
-
}
|
|
6041
|
-
}
|
|
6042
|
-
get length() {
|
|
6043
|
-
return __classPrivateFieldGet(this, _RegisterClass_keys, "f").length;
|
|
6044
|
-
}
|
|
6045
|
-
[(_RegisterClass_rekeyValue = function _RegisterClass_rekeyValue(value, oldKey, newKey) {
|
|
6046
|
-
if (Object.is(oldKey, newKey))
|
|
6047
|
-
return;
|
|
6048
|
-
const existingValue = __classPrivateFieldGet(this, _RegisterClass_values, "f").get(newKey);
|
|
6049
|
-
if (existingValue !== undefined && existingValue !== value)
|
|
6050
|
-
throw new Error(`Register key collision for key ${String(newKey)}`);
|
|
6051
|
-
const count = __classPrivateFieldGet(this, _RegisterClass_usage, "f").get(oldKey);
|
|
6052
|
-
if (!count)
|
|
6053
|
-
return;
|
|
6054
|
-
const existingCount = __classPrivateFieldGet(this, _RegisterClass_usage, "f").get(newKey) ?? 0;
|
|
6055
|
-
this.setKeyValue(newKey, value);
|
|
6056
|
-
let index = -1;
|
|
6057
|
-
for (let i = 0; i < __classPrivateFieldGet(this, _RegisterClass_keys, "f").length; i++)
|
|
6058
|
-
if (Object.is(__classPrivateFieldGet(this, _RegisterClass_keys, "f")[i], oldKey)) {
|
|
6059
|
-
__classPrivateFieldGet(this, _RegisterClass_keys, "f")[i] = newKey;
|
|
6060
|
-
if (index === -1)
|
|
6061
|
-
index = i;
|
|
6062
|
-
}
|
|
6063
|
-
__classPrivateFieldGet(this, _RegisterClass_usage, "f").set(newKey, existingCount + count);
|
|
6064
|
-
__classPrivateFieldGet(this, _RegisterClass_usage, "f").delete(oldKey);
|
|
6065
|
-
__classPrivateFieldGet(this, _RegisterClass_values, "f").delete(oldKey);
|
|
6066
|
-
const updatedInfo = __classPrivateFieldGet(this, _RegisterClass_valueInfo, "f").get(value);
|
|
6067
|
-
if (updatedInfo)
|
|
6068
|
-
updatedInfo.key = newKey;
|
|
6069
|
-
if (index !== -1) {
|
|
6070
|
-
__classPrivateFieldGet(this, _RegisterClass_events, "f").emit('rekey', value, oldKey, newKey, index);
|
|
6071
|
-
}
|
|
6072
|
-
}, getAt)](index) {
|
|
6073
|
-
const key = __classPrivateFieldGet(this, _RegisterClass_keys, "f")[index];
|
|
6074
|
-
return key === undefined ? undefined : __classPrivateFieldGet(this, _RegisterClass_values, "f").get(key);
|
|
6075
|
-
}
|
|
6076
|
-
[setAt](index, value) {
|
|
6077
|
-
const key = this.ensureKey(value);
|
|
6078
|
-
if (index === this.length) {
|
|
6079
|
-
this.insertKeyValue(index, key, value);
|
|
6080
|
-
return;
|
|
6081
|
-
}
|
|
6082
|
-
const normalized = this.normalizeIndex(index);
|
|
6083
|
-
this.assignAt(normalized, key, value);
|
|
6084
|
-
}
|
|
6085
|
-
push(...items) {
|
|
6086
|
-
for (const item of items) {
|
|
6087
|
-
const key = this.ensureKey(item);
|
|
6088
|
-
this.insertKeyValue(this.length, key, item);
|
|
6089
|
-
}
|
|
6090
|
-
return this.length;
|
|
6091
|
-
}
|
|
6092
|
-
pop() {
|
|
6093
|
-
if (!this.length)
|
|
6094
|
-
return undefined;
|
|
6095
|
-
return this.removeAt(this.length - 1);
|
|
6096
|
-
}
|
|
6097
|
-
shift() {
|
|
6098
|
-
if (!this.length)
|
|
6099
|
-
return undefined;
|
|
6100
|
-
return this.removeAt(0);
|
|
6101
|
-
}
|
|
6102
|
-
unshift(...items) {
|
|
6103
|
-
let index = 0;
|
|
6104
|
-
for (const item of items) {
|
|
6105
|
-
const key = this.ensureKey(item);
|
|
6106
|
-
this.insertKeyValue(index++, key, item);
|
|
6107
|
-
}
|
|
6108
|
-
return this.length;
|
|
6109
|
-
}
|
|
6110
|
-
splice(start, deleteCount, ...items) {
|
|
6111
|
-
const normalizedStart = this.normalizeIndex(start, true);
|
|
6112
|
-
const maxDeletions = this.length - normalizedStart;
|
|
6113
|
-
const actualDelete = Math.min(deleteCount === undefined ? maxDeletions : Math.max(deleteCount, 0), maxDeletions);
|
|
6114
|
-
const keysToInsert = [];
|
|
6115
|
-
for (const item of items)
|
|
6116
|
-
keysToInsert.push(this.ensureKey(item));
|
|
6117
|
-
const removedKeys = __classPrivateFieldGet(this, _RegisterClass_keys, "f").splice(normalizedStart, actualDelete, ...keysToInsert);
|
|
6118
|
-
const removedValues = [];
|
|
6119
|
-
for (let i = 0; i < removedKeys.length; i++) {
|
|
6120
|
-
const key = removedKeys[i];
|
|
6121
|
-
if (key === undefined)
|
|
6122
|
-
continue;
|
|
6123
|
-
const value = __classPrivateFieldGet(this, _RegisterClass_values, "f").get(key);
|
|
6124
|
-
this.decrementUsage(key, normalizedStart + i);
|
|
6125
|
-
removedValues.push(value);
|
|
6126
|
-
}
|
|
6127
|
-
for (let i = 0; i < keysToInsert.length; i++) {
|
|
6128
|
-
const key = keysToInsert[i];
|
|
6129
|
-
const value = items[i];
|
|
6130
|
-
this.incrementUsage(key);
|
|
6131
|
-
this.setKeyValue(key, value);
|
|
6132
|
-
}
|
|
6133
|
-
return removedValues;
|
|
6134
|
-
}
|
|
6135
|
-
clear() {
|
|
6136
|
-
__classPrivateFieldGet(this, _RegisterClass_keys, "f").length = 0;
|
|
6137
|
-
__classPrivateFieldGet(this, _RegisterClass_usage, "f").clear();
|
|
6138
|
-
__classPrivateFieldGet(this, _RegisterClass_values, "f").clear();
|
|
6139
|
-
this.disposeKeyEffects();
|
|
6140
|
-
}
|
|
6141
|
-
get(key) {
|
|
6142
|
-
return __classPrivateFieldGet(this, _RegisterClass_values, "f").get(key);
|
|
6143
|
-
}
|
|
6144
|
-
set(key, value) {
|
|
6145
|
-
if (__classPrivateFieldGet(this, _RegisterClass_values, "f").has(key))
|
|
6146
|
-
this.setKeyValue(key, value);
|
|
6147
|
-
}
|
|
6148
|
-
remove(key) {
|
|
6149
|
-
let index = this.indexOfKey(key);
|
|
6150
|
-
while (index !== -1) {
|
|
6151
|
-
this.removeAt(index);
|
|
6152
|
-
index = this.indexOfKey(key);
|
|
6153
|
-
}
|
|
6154
|
-
}
|
|
6155
|
-
removeAt(index) {
|
|
6156
|
-
const [key] = __classPrivateFieldGet(this, _RegisterClass_keys, "f").splice(index, 1);
|
|
6157
|
-
if (key === undefined)
|
|
6158
|
-
return undefined;
|
|
6159
|
-
const value = __classPrivateFieldGet(this, _RegisterClass_values, "f").get(key);
|
|
6160
|
-
this.decrementUsage(key, index);
|
|
6161
|
-
return value;
|
|
6162
|
-
}
|
|
6163
|
-
/**
|
|
6164
|
-
* Keep only the items for which the predicate returns true.
|
|
6165
|
-
* Items for which the predicate returns false are removed.
|
|
6166
|
-
*
|
|
6167
|
-
* The predicate is evaluated once per distinct key; duplicate keys
|
|
6168
|
-
* will follow the same keep/remove decision.
|
|
6169
|
-
*/
|
|
6170
|
-
keep(predicate) {
|
|
6171
|
-
const decisions = new Map();
|
|
6172
|
-
for (const [index, key] of __classPrivateFieldGet(this, _RegisterClass_keys, "f").entries()) {
|
|
6173
|
-
if (decisions.has(key)) {
|
|
6174
|
-
if (!decisions.get(key))
|
|
6175
|
-
this.removeAt(index);
|
|
6176
|
-
continue;
|
|
6177
|
-
}
|
|
6178
|
-
const value = __classPrivateFieldGet(this, _RegisterClass_values, "f").get(key);
|
|
6179
|
-
const shouldKeep = predicate(value);
|
|
6180
|
-
decisions.set(key, shouldKeep);
|
|
6181
|
-
if (!shouldKeep)
|
|
6182
|
-
this.removeAt(index);
|
|
6183
|
-
}
|
|
6184
|
-
}
|
|
6185
|
-
hasKey(key) {
|
|
6186
|
-
return __classPrivateFieldGet(this, _RegisterClass_usage, "f").has(key);
|
|
6187
|
-
}
|
|
6188
|
-
indexOfKey(key) {
|
|
6189
|
-
return __classPrivateFieldGet(this, _RegisterClass_keys, "f").indexOf(key);
|
|
6190
|
-
}
|
|
6191
|
-
mapKeys() {
|
|
6192
|
-
return __classPrivateFieldGet(this, _RegisterClass_values, "f").keys();
|
|
6193
|
-
}
|
|
6194
|
-
update(...values) {
|
|
6195
|
-
for (const value of values) {
|
|
6196
|
-
const key = this.ensureKey(value);
|
|
6197
|
-
if (__classPrivateFieldGet(this, _RegisterClass_values, "f").has(key)) {
|
|
6198
|
-
const index = __classPrivateFieldGet(this, _RegisterClass_keys, "f").indexOf(key);
|
|
6199
|
-
this.setKeyValue(key, value, index);
|
|
5739
|
+
return lazyInit(resource, () => {
|
|
5740
|
+
link(resource, effect.named('watch:resource')((access) => {
|
|
5741
|
+
// Track reload signal to enable manual reloading
|
|
5742
|
+
void reloadSignal.value;
|
|
5743
|
+
const id = ++counter;
|
|
5744
|
+
resource.loading = true;
|
|
5745
|
+
resource.error = undefined;
|
|
5746
|
+
try {
|
|
5747
|
+
const result = fetcher(access);
|
|
5748
|
+
if (result instanceof Promise) {
|
|
5749
|
+
resource.promise = result
|
|
5750
|
+
.then((val) => {
|
|
5751
|
+
if (id === counter) {
|
|
5752
|
+
resource.value = val;
|
|
5753
|
+
resource.latest = val;
|
|
5754
|
+
resource.loading = false;
|
|
5755
|
+
}
|
|
5756
|
+
})
|
|
5757
|
+
.catch((err) => {
|
|
5758
|
+
if (id === counter) {
|
|
5759
|
+
resource.error = err;
|
|
5760
|
+
resource.loading = false;
|
|
5761
|
+
}
|
|
5762
|
+
});
|
|
6200
5763
|
}
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
if (__classPrivateFieldGet(this, _RegisterClass_values, "f").has(key))
|
|
6207
|
-
this.setKeyValue(key, value);
|
|
6208
|
-
else
|
|
6209
|
-
insert(value);
|
|
6210
|
-
}
|
|
6211
|
-
}
|
|
6212
|
-
entries() {
|
|
6213
|
-
const self = this;
|
|
6214
|
-
function* iterator() {
|
|
6215
|
-
for (let i = 0; i < __classPrivateFieldGet(self, _RegisterClass_keys, "f").length; i++) {
|
|
6216
|
-
const val = __classPrivateFieldGet(self, _RegisterClass_values, "f").get(__classPrivateFieldGet(self, _RegisterClass_keys, "f")[i]);
|
|
6217
|
-
if (val !== undefined)
|
|
6218
|
-
yield [i, val];
|
|
5764
|
+
else {
|
|
5765
|
+
resource.promise = Promise.resolve();
|
|
5766
|
+
resource.value = result;
|
|
5767
|
+
resource.latest = result;
|
|
5768
|
+
resource.loading = false;
|
|
6219
5769
|
}
|
|
6220
5770
|
}
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
function* iterator() {
|
|
6226
|
-
for (const key of __classPrivateFieldGet(self, _RegisterClass_keys, "f")) {
|
|
6227
|
-
const value = __classPrivateFieldGet(self, _RegisterClass_values, "f").get(key);
|
|
6228
|
-
if (value !== undefined)
|
|
6229
|
-
yield value;
|
|
6230
|
-
}
|
|
5771
|
+
catch (err) {
|
|
5772
|
+
resource.promise = Promise.reject(err);
|
|
5773
|
+
resource.error = err;
|
|
5774
|
+
resource.loading = false;
|
|
6231
5775
|
}
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
toString() {
|
|
6235
|
-
return `[Register length=${this.length}]`;
|
|
6236
|
-
}
|
|
6237
|
-
at(index) {
|
|
6238
|
-
const resolved = index < 0 ? this.length + index : index;
|
|
6239
|
-
if (resolved < 0 || resolved >= this.length)
|
|
6240
|
-
return undefined;
|
|
6241
|
-
return this[getAt](resolved);
|
|
6242
|
-
}
|
|
6243
|
-
reverse() {
|
|
6244
|
-
__classPrivateFieldGet(this, _RegisterClass_keys, "f").reverse();
|
|
6245
|
-
return this;
|
|
6246
|
-
}
|
|
6247
|
-
sort(compareFn) {
|
|
6248
|
-
const fwdCompareFn = compareFn
|
|
6249
|
-
? (a, b) => compareFn(__classPrivateFieldGet(this, _RegisterClass_values, "f").get(a), __classPrivateFieldGet(this, _RegisterClass_values, "f").get(b))
|
|
6250
|
-
: undefined;
|
|
6251
|
-
__classPrivateFieldGet(this, _RegisterClass_keys, "f").sort(fwdCompareFn);
|
|
6252
|
-
return this;
|
|
6253
|
-
}
|
|
6254
|
-
fill(value, start = 0, end = this.length) {
|
|
6255
|
-
const values = this.toArray();
|
|
6256
|
-
values.fill(value, start, end);
|
|
6257
|
-
this.rebuildFrom(values);
|
|
6258
|
-
return this;
|
|
6259
|
-
}
|
|
6260
|
-
copyWithin(target, start, end) {
|
|
6261
|
-
const values = this.toArray();
|
|
6262
|
-
values.copyWithin(target, start, end);
|
|
6263
|
-
this.rebuildFrom(values);
|
|
6264
|
-
return this;
|
|
6265
|
-
}
|
|
6266
|
-
};
|
|
6267
|
-
__setFunctionName(_classThis, "RegisterClass");
|
|
6268
|
-
(() => {
|
|
6269
|
-
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
6270
|
-
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
6271
|
-
_classThis = _classDescriptor.value;
|
|
6272
|
-
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
6273
|
-
__runInitializers(_classThis, _classExtraInitializers);
|
|
6274
|
-
})();
|
|
6275
|
-
return _classThis;
|
|
6276
|
-
})();
|
|
6277
|
-
const Register = RegisterClass;
|
|
6278
|
-
/**
|
|
6279
|
-
* Creates a reactive Register - an ordered, array-like collection with stable key-based identity.
|
|
6280
|
-
*
|
|
6281
|
-
* Register combines array semantics (indexable access, ordering, iteration) with Map-like
|
|
6282
|
-
* key-based lookups. Items with the same key share the same value instance, making it ideal
|
|
6283
|
-
* for UI lists keyed by IDs or when you need to preserve identity across reorders.
|
|
6284
|
-
*
|
|
6285
|
-
* @param keyFn - Function that extracts the key from each item
|
|
6286
|
-
* @param initial - Optional initial items to populate the register
|
|
6287
|
-
* @returns A reactive Register instance
|
|
6288
|
-
*
|
|
6289
|
-
* @example
|
|
6290
|
-
* ```typescript
|
|
6291
|
-
* const users = register(
|
|
6292
|
-
* (user: User) => user.id,
|
|
6293
|
-
* [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
|
|
6294
|
-
* )
|
|
6295
|
-
*
|
|
6296
|
-
* users.push({ id: 3, name: 'Charlie' })
|
|
6297
|
-
* const bob = users.get(2) // Get by key
|
|
6298
|
-
* ```
|
|
6299
|
-
*/
|
|
6300
|
-
function register(keyFn, initial) {
|
|
6301
|
-
return new RegisterClass(keyFn, initial);
|
|
5776
|
+
}));
|
|
5777
|
+
});
|
|
6302
5778
|
}
|
|
5779
|
+
//#endregion
|
|
6303
5780
|
|
|
6304
5781
|
/**
|
|
6305
5782
|
* Converts an iterator to a generator that yields reactive values
|
|
@@ -6382,6 +5859,7 @@
|
|
|
6382
5859
|
let _classSuper = Array;
|
|
6383
5860
|
let _instanceExtraInitializers = [];
|
|
6384
5861
|
let _fill_decorators;
|
|
5862
|
+
let _copyWithin_decorators;
|
|
6385
5863
|
let _pop_decorators;
|
|
6386
5864
|
let _push_decorators;
|
|
6387
5865
|
let _reverse_decorators;
|
|
@@ -6406,6 +5884,9 @@
|
|
|
6406
5884
|
fill(value, start, end) {
|
|
6407
5885
|
return super.fill(unwrap(value), start, end);
|
|
6408
5886
|
}
|
|
5887
|
+
copyWithin(target, start, end) {
|
|
5888
|
+
return super.copyWithin(target, start, end);
|
|
5889
|
+
}
|
|
6409
5890
|
filter(predicate, thisArg) {
|
|
6410
5891
|
return reactive(super.filter((v, i, a) => predicate.call(thisArg, reactive(v), i, a), thisArg));
|
|
6411
5892
|
}
|
|
@@ -6507,7 +5988,7 @@
|
|
|
6507
5988
|
dependant(this, keysOf);
|
|
6508
5989
|
return makeReactiveIterator(super.values());
|
|
6509
5990
|
}
|
|
6510
|
-
[(_fill_decorators = [atomic], _pop_decorators = [atomic], _push_decorators = [atomic], _reverse_decorators = [atomic], _shift_decorators = [atomic], _sort_decorators = [atomic], _splice_decorators = [atomic], _unshift_decorators = [atomic], Symbol.iterator)]() {
|
|
5991
|
+
[(_fill_decorators = [atomic], _copyWithin_decorators = [atomic], _pop_decorators = [atomic], _push_decorators = [atomic], _reverse_decorators = [atomic], _shift_decorators = [atomic], _sort_decorators = [atomic], _splice_decorators = [atomic], _unshift_decorators = [atomic], Symbol.iterator)]() {
|
|
6511
5992
|
dependant(this, keysOf);
|
|
6512
5993
|
return makeReactiveIterator(super[Symbol.iterator]());
|
|
6513
5994
|
}
|
|
@@ -6520,7 +6001,6 @@
|
|
|
6520
6001
|
: undefined;
|
|
6521
6002
|
return reactive(super.toSorted(wrappedCompare));
|
|
6522
6003
|
}
|
|
6523
|
-
// biome-ignore lint/complexity/noArguments: Needed to distinguish fn() from fn(undefined)
|
|
6524
6004
|
toSpliced(start, deleteCount, ...items) {
|
|
6525
6005
|
if (arguments.length > 2)
|
|
6526
6006
|
return reactive(super.toSpliced(start, deleteCount, ...items.map(unwrap)));
|
|
@@ -6541,6 +6021,7 @@
|
|
|
6541
6021
|
(() => {
|
|
6542
6022
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
6543
6023
|
__esDecorate(_a, null, _fill_decorators, { kind: "method", name: "fill", static: false, private: false, access: { has: obj => "fill" in obj, get: obj => obj.fill }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
6024
|
+
__esDecorate(_a, null, _copyWithin_decorators, { kind: "method", name: "copyWithin", static: false, private: false, access: { has: obj => "copyWithin" in obj, get: obj => obj.copyWithin }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
6544
6025
|
__esDecorate(_a, null, _pop_decorators, { kind: "method", name: "pop", static: false, private: false, access: { has: obj => "pop" in obj, get: obj => obj.pop }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
6545
6026
|
__esDecorate(_a, null, _push_decorators, { kind: "method", name: "push", static: false, private: false, access: { has: obj => "push" in obj, get: obj => obj.push }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
6546
6027
|
__esDecorate(_a, null, _reverse_decorators, { kind: "method", name: "reverse", static: false, private: false, access: { has: obj => "reverse" in obj, get: obj => obj.reverse }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -6561,18 +6042,18 @@
|
|
|
6561
6042
|
// Implement WeakMap interface methods with reactivity
|
|
6562
6043
|
delete(key) {
|
|
6563
6044
|
const hadKey = this.has(key);
|
|
6564
|
-
const result =
|
|
6045
|
+
const result = super.delete(key);
|
|
6565
6046
|
if (hadKey)
|
|
6566
6047
|
touched1(contentRef(this), { type: 'del', prop: key }, key);
|
|
6567
6048
|
return result;
|
|
6568
6049
|
}
|
|
6569
6050
|
get(key) {
|
|
6570
6051
|
dependant(contentRef(this), key);
|
|
6571
|
-
return reactive(
|
|
6052
|
+
return reactive(super.get(key));
|
|
6572
6053
|
}
|
|
6573
6054
|
has(key) {
|
|
6574
6055
|
dependant(contentRef(this), key);
|
|
6575
|
-
return
|
|
6056
|
+
return super.has(key);
|
|
6576
6057
|
}
|
|
6577
6058
|
set(key, value) {
|
|
6578
6059
|
const hadKey = this.has(key);
|
|
@@ -6593,16 +6074,18 @@
|
|
|
6593
6074
|
// Implement Map interface methods with reactivity
|
|
6594
6075
|
get size() {
|
|
6595
6076
|
dependant(this, 'size'); // The ReactiveMap instance still goes through proxy
|
|
6596
|
-
return
|
|
6077
|
+
return super.size;
|
|
6597
6078
|
}
|
|
6598
6079
|
clear() {
|
|
6599
6080
|
const hadEntries = this.size > 0;
|
|
6600
|
-
|
|
6081
|
+
super.clear();
|
|
6601
6082
|
if (hadEntries) {
|
|
6602
6083
|
const evolution = { type: 'bunch', method: 'clear' };
|
|
6603
6084
|
// Clear triggers all effects since all keys are affected
|
|
6604
|
-
|
|
6605
|
-
|
|
6085
|
+
batch(() => {
|
|
6086
|
+
touched1(this, evolution, 'size');
|
|
6087
|
+
touched(contentRef(this), evolution);
|
|
6088
|
+
});
|
|
6606
6089
|
}
|
|
6607
6090
|
}
|
|
6608
6091
|
entries() {
|
|
@@ -6637,32 +6120,36 @@
|
|
|
6637
6120
|
// Implement Map methods with reactivity
|
|
6638
6121
|
delete(key) {
|
|
6639
6122
|
const hadKey = this.has(key);
|
|
6640
|
-
const result =
|
|
6123
|
+
const result = super.delete(key);
|
|
6641
6124
|
if (hadKey) {
|
|
6642
6125
|
const evolution = { type: 'del', prop: key };
|
|
6643
|
-
|
|
6644
|
-
|
|
6126
|
+
batch(() => {
|
|
6127
|
+
touched1(contentRef(this), evolution, key);
|
|
6128
|
+
touched1(this, evolution, 'size');
|
|
6129
|
+
});
|
|
6645
6130
|
}
|
|
6646
6131
|
return result;
|
|
6647
6132
|
}
|
|
6648
6133
|
get(key) {
|
|
6649
6134
|
dependant(contentRef(this), key);
|
|
6650
|
-
return reactive(
|
|
6135
|
+
return reactive(super.get(key));
|
|
6651
6136
|
}
|
|
6652
6137
|
has(key) {
|
|
6653
6138
|
dependant(contentRef(this), key);
|
|
6654
|
-
return
|
|
6139
|
+
return super.has(key);
|
|
6655
6140
|
}
|
|
6656
6141
|
set(key, value) {
|
|
6657
6142
|
const hadKey = this.has(key);
|
|
6658
6143
|
const oldValue = this.get(key);
|
|
6659
6144
|
const reactiveValue = reactive(value);
|
|
6660
|
-
|
|
6145
|
+
super.set(key, reactiveValue);
|
|
6661
6146
|
if (!hadKey || oldValue !== reactiveValue) {
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6147
|
+
batch(() => {
|
|
6148
|
+
notifyPropertyChange(contentRef(this), key, oldValue, reactiveValue, hadKey);
|
|
6149
|
+
// Also notify size change for Map (WeakMap doesn't track size)
|
|
6150
|
+
const evolution = { type: hadKey ? 'set' : 'add', prop: key };
|
|
6151
|
+
touched1(this, evolution, 'size');
|
|
6152
|
+
});
|
|
6666
6153
|
}
|
|
6667
6154
|
return this;
|
|
6668
6155
|
}
|
|
@@ -6675,7 +6162,7 @@
|
|
|
6675
6162
|
class ReactiveWeakSet extends WeakSet {
|
|
6676
6163
|
add(value) {
|
|
6677
6164
|
const had = this.has(value);
|
|
6678
|
-
|
|
6165
|
+
super.add(value);
|
|
6679
6166
|
if (!had) {
|
|
6680
6167
|
// touch the specific value and the collection view
|
|
6681
6168
|
touched1(contentRef(this), { type: 'add', prop: value }, value);
|
|
@@ -6685,14 +6172,14 @@
|
|
|
6685
6172
|
}
|
|
6686
6173
|
delete(value) {
|
|
6687
6174
|
const had = this.has(value);
|
|
6688
|
-
const res =
|
|
6175
|
+
const res = super.delete(value);
|
|
6689
6176
|
if (had)
|
|
6690
6177
|
touched1(contentRef(this), { type: 'del', prop: value }, value);
|
|
6691
6178
|
return res;
|
|
6692
6179
|
}
|
|
6693
6180
|
has(value) {
|
|
6694
6181
|
dependant(contentRef(this), value);
|
|
6695
|
-
return
|
|
6182
|
+
return super.has(value);
|
|
6696
6183
|
}
|
|
6697
6184
|
}
|
|
6698
6185
|
/**
|
|
@@ -6708,31 +6195,37 @@
|
|
|
6708
6195
|
add(value) {
|
|
6709
6196
|
const had = this.has(value);
|
|
6710
6197
|
const reactiveValue = reactive(value);
|
|
6711
|
-
|
|
6198
|
+
super.add(reactiveValue);
|
|
6712
6199
|
if (!had) {
|
|
6713
6200
|
const evolution = { type: 'add', prop: reactiveValue };
|
|
6714
6201
|
// touch for value-specific and aggregate dependencies
|
|
6715
|
-
|
|
6716
|
-
|
|
6202
|
+
batch(() => {
|
|
6203
|
+
touched1(contentRef(this), evolution, reactiveValue);
|
|
6204
|
+
touched1(this, evolution, 'size');
|
|
6205
|
+
});
|
|
6717
6206
|
}
|
|
6718
6207
|
return this;
|
|
6719
6208
|
}
|
|
6720
6209
|
clear() {
|
|
6721
6210
|
const hadEntries = this.size > 0;
|
|
6722
|
-
|
|
6211
|
+
super.clear();
|
|
6723
6212
|
if (hadEntries) {
|
|
6724
6213
|
const evolution = { type: 'bunch', method: 'clear' };
|
|
6725
|
-
|
|
6726
|
-
|
|
6214
|
+
batch(() => {
|
|
6215
|
+
touched1(this, evolution, 'size');
|
|
6216
|
+
touched(contentRef(this), evolution);
|
|
6217
|
+
});
|
|
6727
6218
|
}
|
|
6728
6219
|
}
|
|
6729
6220
|
delete(value) {
|
|
6730
6221
|
const had = this.has(value);
|
|
6731
|
-
const res =
|
|
6222
|
+
const res = super.delete(value);
|
|
6732
6223
|
if (had) {
|
|
6733
6224
|
const evolution = { type: 'del', prop: value };
|
|
6734
|
-
|
|
6735
|
-
|
|
6225
|
+
batch(() => {
|
|
6226
|
+
touched1(contentRef(this), evolution, value);
|
|
6227
|
+
touched1(this, evolution, 'size');
|
|
6228
|
+
});
|
|
6736
6229
|
}
|
|
6737
6230
|
return res;
|
|
6738
6231
|
}
|
|
@@ -6771,14 +6264,12 @@
|
|
|
6771
6264
|
}
|
|
6772
6265
|
|
|
6773
6266
|
// Register native collection types to use specialized reactive wrappers
|
|
6774
|
-
console.time('mutts-reactive-init');
|
|
6775
6267
|
metaProtos.set(Array, ReactiveArray.prototype);
|
|
6776
6268
|
metaProtos.set(Set, ReactiveSet.prototype);
|
|
6777
6269
|
metaProtos.set(WeakSet, ReactiveWeakSet.prototype);
|
|
6778
6270
|
metaProtos.set(Map, ReactiveMap.prototype);
|
|
6779
6271
|
metaProtos.set(WeakMap, ReactiveWeakMap.prototype);
|
|
6780
6272
|
wrapProtos.set(Array, ReactiveArrayWrapper.prototype);
|
|
6781
|
-
console.timeEnd('mutts-reactive-init');
|
|
6782
6273
|
/**
|
|
6783
6274
|
* Object containing internal reactive system state for debugging and profiling
|
|
6784
6275
|
*/
|
|
@@ -6791,7 +6282,6 @@
|
|
|
6791
6282
|
objectsWithDeepWatchers,
|
|
6792
6283
|
deepWatchers,
|
|
6793
6284
|
effectToDeepWatchedObjects,
|
|
6794
|
-
nonReactiveObjects,
|
|
6795
6285
|
};
|
|
6796
6286
|
|
|
6797
6287
|
// In order to avoid async re-entrance, we could use zone.js or something like that.
|
|
@@ -6957,8 +6447,7 @@
|
|
|
6957
6447
|
},
|
|
6958
6448
|
}), {
|
|
6959
6449
|
warn: (target, propertyKey, message) => {
|
|
6960
|
-
|
|
6961
|
-
console.warn(`${target.constructor.name}.${String(propertyKey)} is deprecated${message ? `: ${message}` : ''}`);
|
|
6450
|
+
options.warn(`${target.constructor.name}.${String(propertyKey)} is deprecated${message ? `: ${message}` : ''}`);
|
|
6962
6451
|
},
|
|
6963
6452
|
});
|
|
6964
6453
|
/**
|
|
@@ -7021,12 +6510,11 @@
|
|
|
7021
6510
|
});
|
|
7022
6511
|
}
|
|
7023
6512
|
|
|
7024
|
-
var version$1 = "1.0.
|
|
6513
|
+
var version$1 = "1.0.12";
|
|
7025
6514
|
var pkg = {
|
|
7026
6515
|
version: version$1};
|
|
7027
6516
|
|
|
7028
6517
|
const { version } = pkg;
|
|
7029
|
-
// Singleton verification
|
|
7030
6518
|
const GLOBAL_MUTTS_KEY = '__MUTTS_INSTANCE__';
|
|
7031
6519
|
const globalScope = (typeof globalThis !== 'undefined'
|
|
7032
6520
|
? globalThis
|
|
@@ -7036,28 +6524,16 @@
|
|
|
7036
6524
|
? global
|
|
7037
6525
|
: false);
|
|
7038
6526
|
if (globalScope) {
|
|
7039
|
-
// Detect the source of this instance safely across different environments
|
|
7040
6527
|
let source = 'mutts/index';
|
|
7041
|
-
// biome-ignore lint/security/noGlobalEval: Intentional eval for cross-env import.meta detection
|
|
7042
|
-
const viteEval = eval;
|
|
7043
6528
|
try {
|
|
7044
6529
|
if (typeof __filename !== 'undefined')
|
|
7045
6530
|
source = __filename;
|
|
7046
|
-
else {
|
|
7047
|
-
|
|
7048
|
-
const meta = viteEval('import.meta');
|
|
7049
|
-
if (meta && meta.url)
|
|
7050
|
-
source = meta.url;
|
|
6531
|
+
else if (typeof ({ url: (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('mutts.umd.js', document.baseURI).href)) }) !== 'undefined' && (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('mutts.umd.js', document.baseURI).href))) {
|
|
6532
|
+
source = (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('mutts.umd.js', document.baseURI).href));
|
|
7051
6533
|
}
|
|
7052
6534
|
}
|
|
7053
|
-
catch (
|
|
7054
|
-
|
|
7055
|
-
}
|
|
7056
|
-
const currentSourceInfo = {
|
|
7057
|
-
version,
|
|
7058
|
-
source,
|
|
7059
|
-
timestamp: Date.now(),
|
|
7060
|
-
};
|
|
6535
|
+
catch (_e) { }
|
|
6536
|
+
const currentSourceInfo = { version, source, timestamp: Date.now() };
|
|
7061
6537
|
if (globalScope[GLOBAL_MUTTS_KEY]) {
|
|
7062
6538
|
const existing = globalScope[GLOBAL_MUTTS_KEY];
|
|
7063
6539
|
throw new Error(`[Mutts] Multiple instances detected!\n` +
|
|
@@ -7081,15 +6557,16 @@
|
|
|
7081
6557
|
exports.IterableWeakSet = IterableWeakSet;
|
|
7082
6558
|
exports.ReactiveBase = ReactiveBase;
|
|
7083
6559
|
exports.ReactiveError = ReactiveError;
|
|
7084
|
-
exports.Register = Register;
|
|
7085
6560
|
exports.Zone = Zone;
|
|
7086
6561
|
exports.ZoneAggregator = ZoneAggregator;
|
|
7087
6562
|
exports.ZoneHistory = ZoneHistory;
|
|
7088
6563
|
exports.addBatchCleanup = addBatchCleanup;
|
|
6564
|
+
exports.addUnreactiveProps = addUnreactiveProps;
|
|
7089
6565
|
exports.allocated = allocated;
|
|
7090
6566
|
exports.allocatedValues = allocatedValues;
|
|
7091
6567
|
exports.arrayDiff = arrayDiff;
|
|
7092
6568
|
exports.arrayEquals = arrayEquals;
|
|
6569
|
+
exports.assertUntracked = assertUntracked;
|
|
7093
6570
|
exports.asyncHook = asyncHook;
|
|
7094
6571
|
exports.asyncHooks = asyncHooks;
|
|
7095
6572
|
exports.asyncZone = asyncZone;
|
|
@@ -7100,12 +6577,12 @@
|
|
|
7100
6577
|
exports.cache = cache;
|
|
7101
6578
|
exports.cached = cached;
|
|
7102
6579
|
exports.callOnGC = callOnGC;
|
|
6580
|
+
exports.captured = captured;
|
|
7103
6581
|
exports.caught = caught;
|
|
7104
6582
|
exports.chainPromise = chainPromise;
|
|
7105
|
-
exports.cleanedBy = cleanedBy;
|
|
7106
|
-
exports.cleanup = cleanup;
|
|
7107
6583
|
exports.createFlavor = createFlavor;
|
|
7108
6584
|
exports.debounce = debounce;
|
|
6585
|
+
exports.debugPreset = debugPreset;
|
|
7109
6586
|
exports.decorator = decorator;
|
|
7110
6587
|
exports.deepCompare = deepCompare;
|
|
7111
6588
|
exports.deepWatch = deepWatch;
|
|
@@ -7113,6 +6590,7 @@
|
|
|
7113
6590
|
exports.deprecated = deprecated;
|
|
7114
6591
|
exports.descriptor = descriptor;
|
|
7115
6592
|
exports.destructor = destructor;
|
|
6593
|
+
exports.devPreset = devPreset;
|
|
7116
6594
|
exports.effect = effect;
|
|
7117
6595
|
exports.effectAggregator = effectAggregator;
|
|
7118
6596
|
exports.flavorOptions = flavorOptions;
|
|
@@ -7124,14 +6602,17 @@
|
|
|
7124
6602
|
exports.getAt = getAt;
|
|
7125
6603
|
exports.getState = getState;
|
|
7126
6604
|
exports.hooks = hooks$1;
|
|
7127
|
-
exports.immutables = immutables;
|
|
7128
6605
|
exports.isCached = isCached;
|
|
7129
6606
|
exports.isConstructor = isConstructor;
|
|
6607
|
+
exports.isDev = isDev;
|
|
7130
6608
|
exports.isNonReactive = isNonReactive;
|
|
7131
6609
|
exports.isObject = isObject;
|
|
6610
|
+
exports.isProd = isProd;
|
|
7132
6611
|
exports.isReactive = isReactive;
|
|
6612
|
+
exports.isTest = isTest;
|
|
7133
6613
|
exports.legacyDecorator = legacyDecorator;
|
|
7134
6614
|
exports.lift = lift;
|
|
6615
|
+
exports.link = link;
|
|
7135
6616
|
exports.memoize = memoize;
|
|
7136
6617
|
exports.mixin = mixin;
|
|
7137
6618
|
exports.modernDecorator = modernDecorator;
|
|
@@ -7141,22 +6622,21 @@
|
|
|
7141
6622
|
exports.onEffectThrow = onEffectThrow;
|
|
7142
6623
|
exports.organize = organize;
|
|
7143
6624
|
exports.organized = organized;
|
|
6625
|
+
exports.prodPreset = prodPreset;
|
|
7144
6626
|
exports.profileInfo = profileInfo;
|
|
7145
6627
|
exports.project = morph;
|
|
7146
6628
|
exports.proxyToObject = proxyToObject;
|
|
7147
6629
|
exports.reactive = reactive;
|
|
7148
6630
|
exports.reactiveOptions = options;
|
|
7149
|
-
exports.register = register;
|
|
7150
6631
|
exports.reset = reset;
|
|
7151
6632
|
exports.resource = resource;
|
|
7152
6633
|
exports.root = root;
|
|
7153
|
-
exports.scan = scan;
|
|
7154
6634
|
exports.setAt = setAt;
|
|
7155
|
-
exports.stopped = stopped;
|
|
7156
6635
|
exports.tag = tag;
|
|
7157
6636
|
exports.throttle = throttle;
|
|
7158
6637
|
exports.touched = touched;
|
|
7159
6638
|
exports.touched1 = touched1;
|
|
6639
|
+
exports.unlink = unlink;
|
|
7160
6640
|
exports.unreactive = unreactive;
|
|
7161
6641
|
exports.untracked = untracked;
|
|
7162
6642
|
exports.unwrap = unwrap;
|