mutts 1.0.13 → 1.0.14
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/BROWSER_ASYNC_POLYFILL.md +79 -0
- package/README.md +2 -2
- package/dist/browser.cjs +145 -26
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +42 -9
- package/dist/browser.dev.cjs +12 -2
- 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 +137 -28
- package/dist/browser.esm.js.map +1 -1
- package/dist/chunks/{index-CAdnMJev.cjs → index-BnTNC9eC.cjs} +158 -90
- package/dist/chunks/index-BnTNC9eC.cjs.map +1 -0
- package/dist/chunks/{index-XsYTUhHx.esm.js → index-CAWVZL7P.esm.js} +156 -88
- package/dist/chunks/index-CAWVZL7P.esm.js.map +1 -0
- package/dist/chunks/node-Df_5r_WA.cjs +187 -0
- package/dist/chunks/node-Df_5r_WA.cjs.map +1 -0
- package/dist/chunks/node-DuIduHw3.esm.js +185 -0
- package/dist/chunks/node-DuIduHw3.esm.js.map +1 -0
- package/dist/chunks/{proxy-BtmPFjSr.esm.js → proxy-C2lnvvbx.esm.js} +652 -222
- package/dist/chunks/proxy-C2lnvvbx.esm.js.map +1 -0
- package/dist/chunks/{proxy-DBHj3kGK.cjs → proxy-HA_QQnd5.cjs} +662 -223
- package/dist/chunks/proxy-HA_QQnd5.cjs.map +1 -0
- package/dist/debug.cjs +37 -10
- package/dist/debug.cjs.map +1 -1
- package/dist/debug.esm.js +37 -10
- package/dist/debug.esm.js.map +1 -1
- package/dist/mutts.umd.js +4086 -3469
- package/dist/mutts.umd.js.map +1 -1
- package/dist/mutts.umd.min.js +1 -1
- package/dist/mutts.umd.min.js.map +1 -1
- package/dist/node.cjs +13 -3
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.dev.cjs +13 -3
- 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.d.ts +30 -15
- package/docs/ai/api-reference.md +3 -1
- package/docs/ai/manual.md +17 -5
- package/docs/reactive/advanced.md +169 -10
- package/docs/reactive/debugging.md +15 -13
- package/docs/reactive.md +2 -1
- package/package.json +12 -7
- package/dist/chunks/index-CAdnMJev.cjs.map +0 -1
- package/dist/chunks/index-XsYTUhHx.esm.js.map +0 -1
- package/dist/chunks/node-DrrphEPf.cjs +0 -98
- package/dist/chunks/node-DrrphEPf.cjs.map +0 -1
- package/dist/chunks/node-NEZvVo4M.esm.js +0 -96
- package/dist/chunks/node-NEZvVo4M.esm.js.map +0 -1
- package/dist/chunks/proxy-BtmPFjSr.esm.js.map +0 -1
- package/dist/chunks/proxy-DBHj3kGK.cjs.map +0 -1
|
@@ -1,29 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
4
|
-
// Queue for hooks registered before the environment is ready (circular dependency fix)
|
|
5
|
-
const hooks = new Set();
|
|
6
|
-
const asyncHooks = {
|
|
7
|
-
addHook(hook) {
|
|
8
|
-
hooks.add(hook);
|
|
9
|
-
return () => hooks.delete(hook);
|
|
10
|
-
},
|
|
11
|
-
/**
|
|
12
|
-
* [Hack] Sanitize a promise (or value) to prevent context leaks.
|
|
13
|
-
* Default: Identity function.
|
|
14
|
-
* Browser: Uses Macrotask wrapping to break microtask chains.
|
|
15
|
-
*/
|
|
16
|
-
sanitizePromise(p) {
|
|
17
|
-
return p;
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
|
-
* Register a hook that will be called whenever an asynchronous operation is initiated.
|
|
22
|
-
* The hook should return a restorer function which will be called just before the async callback runs.
|
|
23
|
-
* That restorer should in turn return an undoer function which will be called just after the async callback finishes.
|
|
24
|
-
*/
|
|
25
|
-
const asyncHook = (hook) => asyncHooks.addHook(hook);
|
|
26
|
-
|
|
27
4
|
/**
|
|
28
5
|
* Yields tuples containing elements from each input array, stopping at the longest array length
|
|
29
6
|
* @param args - Arrays to zip together
|
|
@@ -277,7 +254,7 @@ function named(name, fn) {
|
|
|
277
254
|
}
|
|
278
255
|
const runtimeGlobals = globalThis;
|
|
279
256
|
const _mode = runtimeGlobals.process?.env?.NODE_ENV ||
|
|
280
|
-
(typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunks/proxy-
|
|
257
|
+
(typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunks/proxy-HA_QQnd5.cjs', document.baseURI).href)) }) !== 'undefined' && undefined?.MODE) ||
|
|
281
258
|
'production';
|
|
282
259
|
const isDev = _mode === 'development';
|
|
283
260
|
const isProd = _mode === 'production';
|
|
@@ -785,7 +762,13 @@ class IterableWeakSet {
|
|
|
785
762
|
[Symbol.iterator]() {
|
|
786
763
|
return this.keys();
|
|
787
764
|
}
|
|
788
|
-
union(other) {
|
|
765
|
+
union(other, ...sets) {
|
|
766
|
+
if (sets.length > 0) {
|
|
767
|
+
for (const set of [other, ...sets])
|
|
768
|
+
for (const value of set)
|
|
769
|
+
this.add(value);
|
|
770
|
+
return this;
|
|
771
|
+
}
|
|
789
772
|
const others = {
|
|
790
773
|
[Symbol.iterator]() {
|
|
791
774
|
return other.keys();
|
|
@@ -857,100 +840,6 @@ class IterableWeakSet {
|
|
|
857
840
|
}
|
|
858
841
|
_b = Symbol.toStringTag;
|
|
859
842
|
|
|
860
|
-
/**
|
|
861
|
-
* Creates a mixin that can be used both as a class (extends) and as a function (mixin)
|
|
862
|
-
*
|
|
863
|
-
* This function supports:
|
|
864
|
-
* - Using mixins as base classes: `class MyClass extends MyMixin`
|
|
865
|
-
* - Using mixins as functions: `class MyClass extends MyMixin(SomeBase)`
|
|
866
|
-
* - Composing mixins: `const Composed = MixinA(MixinB)`
|
|
867
|
-
* - Type-safe property inference for all patterns
|
|
868
|
-
*
|
|
869
|
-
* @param mixinFunction - The function that creates the mixin
|
|
870
|
-
* @param unwrapFunction - Optional function to unwrap reactive objects for method calls
|
|
871
|
-
* @returns A mixin that can be used both as a class and as a function
|
|
872
|
-
*/
|
|
873
|
-
function mixin(mixinFunction, unwrapFunction) {
|
|
874
|
-
/**
|
|
875
|
-
* Cache for mixin results to ensure the same base class always returns the same mixed class
|
|
876
|
-
*/
|
|
877
|
-
const mixinCache = new WeakMap();
|
|
878
|
-
// Apply the mixin to Object as the base class
|
|
879
|
-
const MixedBase = mixinFunction(Object);
|
|
880
|
-
mixinCache.set(Object, MixedBase);
|
|
881
|
-
// Create the proxy that handles both constructor and function calls
|
|
882
|
-
return new Proxy(MixedBase, {
|
|
883
|
-
// Handle `MixinClass(SomeBase)` - use as mixin function
|
|
884
|
-
apply(_target, _thisArg, args) {
|
|
885
|
-
if (args.length === 0) {
|
|
886
|
-
throw new Error('Mixin requires a base class');
|
|
887
|
-
}
|
|
888
|
-
const baseClass = args[0];
|
|
889
|
-
if (typeof baseClass !== 'function') {
|
|
890
|
-
throw new Error('Mixin requires a constructor function');
|
|
891
|
-
}
|
|
892
|
-
// Check if it's a valid constructor or a mixin
|
|
893
|
-
if (!isConstructor(baseClass) &&
|
|
894
|
-
!(baseClass && typeof baseClass === 'function' && baseClass.prototype)) {
|
|
895
|
-
throw new Error('Mixin requires a valid constructor');
|
|
896
|
-
}
|
|
897
|
-
// Check cache first
|
|
898
|
-
const cached = mixinCache.get(baseClass);
|
|
899
|
-
if (cached) {
|
|
900
|
-
return cached;
|
|
901
|
-
}
|
|
902
|
-
let usedBase = baseClass;
|
|
903
|
-
if (unwrapFunction) {
|
|
904
|
-
// Create a proxied base class that handles method unwrapping
|
|
905
|
-
const ProxiedBaseClass = class extends baseClass {
|
|
906
|
-
};
|
|
907
|
-
// Proxy the prototype methods to handle unwrapping
|
|
908
|
-
const originalPrototype = baseClass.prototype;
|
|
909
|
-
const proxiedPrototype = new Proxy(originalPrototype, {
|
|
910
|
-
get(target, prop, receiver) {
|
|
911
|
-
const value = FoolProof.get(target, prop, receiver);
|
|
912
|
-
// Only wrap methods that are likely to access private fields
|
|
913
|
-
// Skip symbols and special properties that the reactive system needs
|
|
914
|
-
if (typeof value === 'function' &&
|
|
915
|
-
typeof prop === 'string' &&
|
|
916
|
-
!['constructor', 'toString', 'valueOf'].includes(prop)) {
|
|
917
|
-
// Return a wrapped version that uses unwrapped context
|
|
918
|
-
return function (...args) {
|
|
919
|
-
// Use the unwrapping function if provided, otherwise use this
|
|
920
|
-
const context = unwrapFunction(this);
|
|
921
|
-
return value.apply(context, args);
|
|
922
|
-
};
|
|
923
|
-
}
|
|
924
|
-
return value;
|
|
925
|
-
},
|
|
926
|
-
});
|
|
927
|
-
// Set the proxied prototype
|
|
928
|
-
Object.setPrototypeOf(ProxiedBaseClass.prototype, proxiedPrototype);
|
|
929
|
-
usedBase = ProxiedBaseClass;
|
|
930
|
-
}
|
|
931
|
-
// Create the mixed class using the proxied base class
|
|
932
|
-
const mixedClass = mixinFunction(usedBase);
|
|
933
|
-
// Cache the result
|
|
934
|
-
mixinCache.set(baseClass, mixedClass);
|
|
935
|
-
return mixedClass;
|
|
936
|
-
},
|
|
937
|
-
});
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
const debugHooks = {
|
|
941
|
-
isDevtoolsEnabled: () => false,
|
|
942
|
-
registerEffect: () => { },
|
|
943
|
-
getTriggerChain: () => [],
|
|
944
|
-
captureStack: () => [],
|
|
945
|
-
captureLineage: () => new Error().stack,
|
|
946
|
-
formatStack: (stack) => [stack],
|
|
947
|
-
recordTriggerLink: () => { },
|
|
948
|
-
decorateError: () => { },
|
|
949
|
-
};
|
|
950
|
-
function setDebugHooks(hooks) {
|
|
951
|
-
Object.assign(debugHooks, hooks);
|
|
952
|
-
}
|
|
953
|
-
|
|
954
843
|
/******************************************************************************
|
|
955
844
|
Copyright (c) Microsoft Corporation.
|
|
956
845
|
|
|
@@ -1013,6 +902,29 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
1013
902
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
1014
903
|
};
|
|
1015
904
|
|
|
905
|
+
// Queue for hooks registered before the environment is ready (circular dependency fix)
|
|
906
|
+
const hooks = new Set();
|
|
907
|
+
const asyncHooks = {
|
|
908
|
+
addHook(hook) {
|
|
909
|
+
hooks.add(hook);
|
|
910
|
+
return () => hooks.delete(hook);
|
|
911
|
+
},
|
|
912
|
+
/**
|
|
913
|
+
* [Hack] Sanitize a promise (or value) to prevent context leaks.
|
|
914
|
+
* Default: Identity function.
|
|
915
|
+
* Browser: Uses Macrotask wrapping to break microtask chains.
|
|
916
|
+
*/
|
|
917
|
+
sanitizePromise(p) {
|
|
918
|
+
return p;
|
|
919
|
+
},
|
|
920
|
+
};
|
|
921
|
+
/**
|
|
922
|
+
* Register a hook that will be called whenever an asynchronous operation is initiated.
|
|
923
|
+
* The hook should return a restorer function which will be called just before the async callback runs.
|
|
924
|
+
* That restorer should in turn return an undoer function which will be called just after the async callback finishes.
|
|
925
|
+
*/
|
|
926
|
+
const asyncHook = (hook) => asyncHooks.addHook(hook);
|
|
927
|
+
|
|
1016
928
|
var _ZoneAggregator_zones;
|
|
1017
929
|
function isu(z) {
|
|
1018
930
|
return z;
|
|
@@ -1348,6 +1260,85 @@ function unlink(obj, reason) {
|
|
|
1348
1260
|
}
|
|
1349
1261
|
}
|
|
1350
1262
|
|
|
1263
|
+
function extractRawStack(error = new Error()) {
|
|
1264
|
+
if (typeof error === 'string')
|
|
1265
|
+
return error;
|
|
1266
|
+
if (error && typeof error === 'object' && 'stack' in error) {
|
|
1267
|
+
const stack = error.stack;
|
|
1268
|
+
return typeof stack === 'string' ? stack : undefined;
|
|
1269
|
+
}
|
|
1270
|
+
return undefined;
|
|
1271
|
+
}
|
|
1272
|
+
function trimStack(stack) {
|
|
1273
|
+
const raw = extractRawStack(stack);
|
|
1274
|
+
if (!raw)
|
|
1275
|
+
return [];
|
|
1276
|
+
const lines = raw
|
|
1277
|
+
.split('\n')
|
|
1278
|
+
.map((line) => line.trim())
|
|
1279
|
+
.filter(Boolean);
|
|
1280
|
+
if (lines[0]?.startsWith('Error'))
|
|
1281
|
+
lines.shift();
|
|
1282
|
+
while (lines[0] &&
|
|
1283
|
+
(lines[0].includes('captureLineage') ||
|
|
1284
|
+
lines[0].includes('captureDeferredLineage') ||
|
|
1285
|
+
lines[0].includes('debug-hooks.ts')))
|
|
1286
|
+
lines.shift();
|
|
1287
|
+
return lines;
|
|
1288
|
+
}
|
|
1289
|
+
function digestDeferredLineage(lineage) {
|
|
1290
|
+
if (lineage.segments)
|
|
1291
|
+
return lineage.segments;
|
|
1292
|
+
const segments = [];
|
|
1293
|
+
let effect = lineage.effect;
|
|
1294
|
+
let stack = trimStack(lineage.stack);
|
|
1295
|
+
if (!effect) {
|
|
1296
|
+
lineage.segments = [{ effectName: 'root', stack }];
|
|
1297
|
+
return lineage.segments;
|
|
1298
|
+
}
|
|
1299
|
+
while (effect) {
|
|
1300
|
+
const root = getRoot(effect);
|
|
1301
|
+
segments.push({
|
|
1302
|
+
effectName: root.name || 'anonymous',
|
|
1303
|
+
stack,
|
|
1304
|
+
});
|
|
1305
|
+
const node = getEffectNode(effect);
|
|
1306
|
+
effect = node.parent;
|
|
1307
|
+
stack = trimStack(node.creationStack);
|
|
1308
|
+
}
|
|
1309
|
+
if (stack.length)
|
|
1310
|
+
segments.push({ effectName: 'root', stack });
|
|
1311
|
+
lineage.segments = segments;
|
|
1312
|
+
return segments;
|
|
1313
|
+
}
|
|
1314
|
+
function formatDeferredLineage(lineage) {
|
|
1315
|
+
return digestDeferredLineage(lineage)
|
|
1316
|
+
.map((segment) => [`${segment.effectName}:`, ...segment.stack.map((line) => ` ${line}`)].join('\n'))
|
|
1317
|
+
.join('\n');
|
|
1318
|
+
}
|
|
1319
|
+
function captureDeferredLineage(effect = getActiveEffect(), stack = new Error()) {
|
|
1320
|
+
return {
|
|
1321
|
+
effect,
|
|
1322
|
+
stack,
|
|
1323
|
+
toString() {
|
|
1324
|
+
return formatDeferredLineage(this);
|
|
1325
|
+
},
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
const debugHooks = {
|
|
1329
|
+
isDevtoolsEnabled: () => false,
|
|
1330
|
+
registerEffect: () => { },
|
|
1331
|
+
getTriggerChain: () => [],
|
|
1332
|
+
captureStack: (error) => extractRawStack(error ?? new Error()),
|
|
1333
|
+
captureLineage: captureDeferredLineage,
|
|
1334
|
+
formatStack: (stack) => [stack],
|
|
1335
|
+
recordTriggerLink: () => { },
|
|
1336
|
+
decorateError: () => { },
|
|
1337
|
+
};
|
|
1338
|
+
function setDebugHooks(hooks) {
|
|
1339
|
+
Object.assign(debugHooks, hooks);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1351
1342
|
const effectMarker = {
|
|
1352
1343
|
enter: 'effect:enter',
|
|
1353
1344
|
leave: 'effect:leave',
|
|
@@ -1498,6 +1489,17 @@ class ReactiveError extends Error {
|
|
|
1498
1489
|
return this.debugInfo?.cause;
|
|
1499
1490
|
}
|
|
1500
1491
|
}
|
|
1492
|
+
function normalizeSchedulerMode(mode) {
|
|
1493
|
+
switch (mode) {
|
|
1494
|
+
case 'production':
|
|
1495
|
+
return 'raw';
|
|
1496
|
+
case 'development':
|
|
1497
|
+
return 'ordered';
|
|
1498
|
+
default:
|
|
1499
|
+
return mode;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
let schedulerMode = 'ordered';
|
|
1501
1503
|
// biome-ignore-start lint/correctness/noUnusedFunctionParameters: Interface declaration with empty defaults
|
|
1502
1504
|
/**
|
|
1503
1505
|
* Global options for the reactive system
|
|
@@ -1588,22 +1590,42 @@ const options = {
|
|
|
1588
1590
|
*/
|
|
1589
1591
|
onMemoizationDiscrepancy: undefined,
|
|
1590
1592
|
/**
|
|
1591
|
-
*
|
|
1593
|
+
* Effect scheduler mode.
|
|
1592
1594
|
*
|
|
1593
|
-
* - `'
|
|
1594
|
-
*
|
|
1595
|
-
*
|
|
1595
|
+
* - `'ordered'` (Default): maintains the causal effect graph so effects that are already
|
|
1596
|
+
* queued together can run in dependency order. It also preserves parent/child effect
|
|
1597
|
+
* lifecycle ordering and catches cycles eagerly when edges are created.
|
|
1596
1598
|
*
|
|
1597
|
-
* - `'
|
|
1598
|
-
*
|
|
1599
|
-
* basic path information. Good balance of debugging help with moderate overhead.
|
|
1599
|
+
* - `'raw'`: fastest FIFO scheduler. It does not maintain the effect graph.
|
|
1600
|
+
* Cycle detection is heuristic, using maxEffectChain execution counts.
|
|
1600
1601
|
*
|
|
1601
|
-
* - `'debug'`:
|
|
1602
|
-
*
|
|
1602
|
+
* - `'debug'`: ordered scheduling plus the most detailed graph diagnostics. Highest overhead,
|
|
1603
|
+
* best for investigation.
|
|
1603
1604
|
*
|
|
1604
|
-
* @default '
|
|
1605
|
+
* @default 'ordered'
|
|
1605
1606
|
*/
|
|
1606
|
-
|
|
1607
|
+
get scheduler() {
|
|
1608
|
+
return schedulerMode;
|
|
1609
|
+
},
|
|
1610
|
+
set scheduler(mode) {
|
|
1611
|
+
schedulerMode = mode;
|
|
1612
|
+
},
|
|
1613
|
+
/**
|
|
1614
|
+
* @deprecated Use `scheduler` instead.
|
|
1615
|
+
*
|
|
1616
|
+
* Backward-compatible alias for older names:
|
|
1617
|
+
* - `'production'` maps to `scheduler = 'raw'`
|
|
1618
|
+
* - `'development'` maps to `scheduler = 'ordered'`
|
|
1619
|
+
* - `'debug'` maps to `scheduler = 'debug'`
|
|
1620
|
+
*
|
|
1621
|
+
* The new names describe scheduler behavior rather than runtime environment.
|
|
1622
|
+
*/
|
|
1623
|
+
get cycleHandling() {
|
|
1624
|
+
return schedulerMode;
|
|
1625
|
+
},
|
|
1626
|
+
set cycleHandling(mode) {
|
|
1627
|
+
schedulerMode = normalizeSchedulerMode(mode);
|
|
1628
|
+
},
|
|
1607
1629
|
/**
|
|
1608
1630
|
* Internal flag used by memoization discrepancy detector to avoid counting calls in tests
|
|
1609
1631
|
* @warning Do not modify this flag manually, this flag is given by the engine
|
|
@@ -1693,14 +1715,14 @@ function optionCall(name, ...args) {
|
|
|
1693
1715
|
/** Production preset: no introspection, heuristic cycle detection, minimal overhead */
|
|
1694
1716
|
const prodPreset = {
|
|
1695
1717
|
maxEffectReaction: 'throw',
|
|
1696
|
-
|
|
1718
|
+
scheduler: 'raw',
|
|
1697
1719
|
introspection: null,
|
|
1698
1720
|
onMemoizationDiscrepancy: undefined,
|
|
1699
1721
|
};
|
|
1700
|
-
/** Development preset
|
|
1722
|
+
/** Development preset: introspection on, early cycle detection, warnings */
|
|
1701
1723
|
const devPreset = {
|
|
1702
1724
|
maxEffectReaction: 'warn',
|
|
1703
|
-
|
|
1725
|
+
scheduler: 'ordered',
|
|
1704
1726
|
introspection: {
|
|
1705
1727
|
gatherReasons: { lineages: 'touch' },
|
|
1706
1728
|
logErrors: true,
|
|
@@ -1712,7 +1734,7 @@ const devPreset = {
|
|
|
1712
1734
|
/** Debug preset: full diagnostics, throws on violations, rich lineage capture */
|
|
1713
1735
|
const debugPreset = {
|
|
1714
1736
|
maxEffectReaction: 'debug',
|
|
1715
|
-
|
|
1737
|
+
scheduler: 'debug',
|
|
1716
1738
|
introspection: {
|
|
1717
1739
|
gatherReasons: { lineages: 'both' },
|
|
1718
1740
|
logErrors: true,
|
|
@@ -1735,6 +1757,7 @@ function unwrap(obj) {
|
|
|
1735
1757
|
return obj;
|
|
1736
1758
|
return proxyToObject.get(obj) || obj;
|
|
1737
1759
|
}
|
|
1760
|
+
const toRaw = unwrap;
|
|
1738
1761
|
function isReactive(obj) {
|
|
1739
1762
|
return proxyToObject.has(obj);
|
|
1740
1763
|
}
|
|
@@ -1782,8 +1805,9 @@ function dependant(obj, prop = allProps) {
|
|
|
1782
1805
|
if (!currentActiveEffect || (typeof prop === 'symbol' && prop !== allProps && prop !== keysOf))
|
|
1783
1806
|
return;
|
|
1784
1807
|
const node = getEffectNode(currentActiveEffect);
|
|
1785
|
-
|
|
1786
|
-
|
|
1808
|
+
const hasDependencyHook = node.dependencyHook !== undefined;
|
|
1809
|
+
if (hasDependencyHook) {
|
|
1810
|
+
node.dependencyHook(obj, prop);
|
|
1787
1811
|
}
|
|
1788
1812
|
let objectWatchers = exports.watchers.get(obj);
|
|
1789
1813
|
if (!objectWatchers) {
|
|
@@ -1805,25 +1829,25 @@ function dependant(obj, prop = allProps) {
|
|
|
1805
1829
|
exports.effectToReactiveObjects.set(currentActiveEffect, new Set([obj]));
|
|
1806
1830
|
}
|
|
1807
1831
|
// Store dependency stack if introspection is enabled
|
|
1808
|
-
const
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
objStacks.set(prop, propStacks);
|
|
1821
|
-
}
|
|
1822
|
-
propStacks.set(currentActiveEffect, debugHooks.captureLineage());
|
|
1832
|
+
const lineageMode = options.introspection?.gatherReasons?.lineages;
|
|
1833
|
+
const shouldGatherDependencyLineage = lineageMode === 'dependency' || lineageMode === 'both';
|
|
1834
|
+
if (shouldGatherDependencyLineage) {
|
|
1835
|
+
let objStacks = dependencyStacks.get(obj);
|
|
1836
|
+
if (!objStacks) {
|
|
1837
|
+
objStacks = new Map();
|
|
1838
|
+
dependencyStacks.set(obj, objStacks);
|
|
1839
|
+
}
|
|
1840
|
+
let propStacks = objStacks.get(prop);
|
|
1841
|
+
if (!propStacks) {
|
|
1842
|
+
propStacks = new Map();
|
|
1843
|
+
objStacks.set(prop, propStacks);
|
|
1823
1844
|
}
|
|
1845
|
+
propStacks.set(currentActiveEffect, debugHooks.captureLineage());
|
|
1824
1846
|
}
|
|
1825
1847
|
}
|
|
1826
1848
|
|
|
1849
|
+
// Simple module to manage inert state without circular dependencies
|
|
1850
|
+
exports.inertDepth = 0;
|
|
1827
1851
|
/**
|
|
1828
1852
|
* Finds a cycle in a sequence of functions by looking for the first repetition
|
|
1829
1853
|
*/
|
|
@@ -1925,6 +1949,40 @@ let causesClosure = new WeakMap();
|
|
|
1925
1949
|
let consequencesClosure = new WeakMap();
|
|
1926
1950
|
// Batch re-entrance depth and broken state
|
|
1927
1951
|
let broken = false;
|
|
1952
|
+
/** True after an unrecoverable reactive failure until `reset()`. */
|
|
1953
|
+
function isReactiveBroken() {
|
|
1954
|
+
return broken;
|
|
1955
|
+
}
|
|
1956
|
+
const reactiveBrokenHandlers = new Set();
|
|
1957
|
+
const reactiveResetHandlers = new Set();
|
|
1958
|
+
function onReactiveBroken(handler) {
|
|
1959
|
+
reactiveBrokenHandlers.add(handler);
|
|
1960
|
+
return () => reactiveBrokenHandlers.delete(handler);
|
|
1961
|
+
}
|
|
1962
|
+
function onReactiveReset(handler) {
|
|
1963
|
+
reactiveResetHandlers.add(handler);
|
|
1964
|
+
return () => reactiveResetHandlers.delete(handler);
|
|
1965
|
+
}
|
|
1966
|
+
function notifyReactiveBroken(error) {
|
|
1967
|
+
for (const handler of Array.from(reactiveBrokenHandlers)) {
|
|
1968
|
+
try {
|
|
1969
|
+
handler(error);
|
|
1970
|
+
}
|
|
1971
|
+
catch (handlerError) {
|
|
1972
|
+
options.warn('[reactive] onReactiveBroken handler threw', handlerError);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
function notifyReactiveReset() {
|
|
1977
|
+
for (const handler of Array.from(reactiveResetHandlers)) {
|
|
1978
|
+
try {
|
|
1979
|
+
handler();
|
|
1980
|
+
}
|
|
1981
|
+
catch (handlerError) {
|
|
1982
|
+
options.warn('[reactive] onReactiveReset handler threw', handlerError);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1928
1986
|
/**
|
|
1929
1987
|
* Gets or creates an IterableWeakSet for a closure map
|
|
1930
1988
|
*/
|
|
@@ -1943,7 +2001,7 @@ function getOrCreateClosure(closure, root) {
|
|
|
1943
2001
|
* @param targetRoot - Root function of the effect being triggered
|
|
1944
2002
|
*/
|
|
1945
2003
|
function addGraphEdge(callerRoot, targetRoot) {
|
|
1946
|
-
if (options.
|
|
2004
|
+
if (options.scheduler === 'raw')
|
|
1947
2005
|
return;
|
|
1948
2006
|
// Add to forward graph: callerRoot → targetRoot
|
|
1949
2007
|
const triggers = effectTriggers.get(callerRoot);
|
|
@@ -2058,7 +2116,7 @@ function hasPathExcluding(start, end, exclude) {
|
|
|
2058
2116
|
* @param effect - The effect being cleaned up
|
|
2059
2117
|
*/
|
|
2060
2118
|
function cleanupEffectFromGraph(effect) {
|
|
2061
|
-
if (options.
|
|
2119
|
+
if (options.scheduler === 'raw')
|
|
2062
2120
|
return;
|
|
2063
2121
|
const root = getRoot(effect);
|
|
2064
2122
|
// Get closures before removing direct edges (needed for propagation)
|
|
@@ -2167,7 +2225,7 @@ const executingStack = [];
|
|
|
2167
2225
|
* Called once when batch starts or when new effects are added
|
|
2168
2226
|
*/
|
|
2169
2227
|
function computeAllInDegrees(batch) {
|
|
2170
|
-
if (options.
|
|
2228
|
+
if (options.scheduler === 'raw')
|
|
2171
2229
|
return;
|
|
2172
2230
|
const activeEffect = getActiveEffect();
|
|
2173
2231
|
const activeRoot = activeEffect ? getRoot(activeEffect) : null;
|
|
@@ -2175,6 +2233,16 @@ function computeAllInDegrees(batch) {
|
|
|
2175
2233
|
batch.inDegrees.clear();
|
|
2176
2234
|
for (const [root] of batch.all) {
|
|
2177
2235
|
let inDegree = 0;
|
|
2236
|
+
// Make sure parents are executed before children: if parent is in batch, count it as a dependency
|
|
2237
|
+
const effect = batch.all.get(root);
|
|
2238
|
+
const parent = getEffectNode(effect).parent;
|
|
2239
|
+
const parentRoot = parent ? getRoot(parent) : undefined;
|
|
2240
|
+
if (parentRoot &&
|
|
2241
|
+
batch.all.has(parentRoot) &&
|
|
2242
|
+
parentRoot !== activeRoot &&
|
|
2243
|
+
parentRoot !== root) {
|
|
2244
|
+
inDegree++;
|
|
2245
|
+
}
|
|
2178
2246
|
const causes = causesClosure.get(root);
|
|
2179
2247
|
if (causes) {
|
|
2180
2248
|
for (const causeRoot of causes) {
|
|
@@ -2194,17 +2262,26 @@ function computeAllInDegrees(batch) {
|
|
|
2194
2262
|
function decrementInDegreesForExecuted(batch, executedRoot) {
|
|
2195
2263
|
// Get all effects that this executed effect triggers
|
|
2196
2264
|
const consequences = consequencesClosure.get(executedRoot);
|
|
2197
|
-
if (
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2265
|
+
if (consequences) {
|
|
2266
|
+
for (const consequenceRoot of consequences) {
|
|
2267
|
+
// Only update if it's still in the batch
|
|
2268
|
+
if (batch.all.has(consequenceRoot)) {
|
|
2269
|
+
const currentDegree = batch.inDegrees.get(consequenceRoot) ?? 0;
|
|
2270
|
+
if (currentDegree > 0) {
|
|
2271
|
+
batch.inDegrees.set(consequenceRoot, currentDegree - 1);
|
|
2272
|
+
}
|
|
2205
2273
|
}
|
|
2206
2274
|
}
|
|
2207
2275
|
}
|
|
2276
|
+
for (const [root, effect] of batch.all) {
|
|
2277
|
+
const parent = getEffectNode(effect).parent;
|
|
2278
|
+
if (!parent || getRoot(parent) !== executedRoot)
|
|
2279
|
+
continue;
|
|
2280
|
+
const currentDegree = batch.inDegrees.get(root) ?? 0;
|
|
2281
|
+
if (currentDegree > 0) {
|
|
2282
|
+
batch.inDegrees.set(root, currentDegree - 1);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2208
2285
|
}
|
|
2209
2286
|
/**
|
|
2210
2287
|
* Finds a path from startRoot to endRoot in the dependency graph
|
|
@@ -2343,15 +2420,15 @@ function addToBatch(effect, caller, immediate, reason) {
|
|
|
2343
2420
|
}
|
|
2344
2421
|
}
|
|
2345
2422
|
// 1. Add to batch first (needed for cycle detection)
|
|
2346
|
-
// TODO: Check if
|
|
2347
|
-
if (options.
|
|
2348
|
-
//
|
|
2423
|
+
// TODO: Check if this difference between raw and graph-backed scheduling is the right tradeoff.
|
|
2424
|
+
if (options.scheduler === 'raw') {
|
|
2425
|
+
// Raw mode: FIFO (delete and re-add to move to end)
|
|
2349
2426
|
if (currentBatch.all.has(root)) {
|
|
2350
2427
|
currentBatch.all.delete(root);
|
|
2351
2428
|
}
|
|
2352
2429
|
}
|
|
2353
2430
|
else {
|
|
2354
|
-
//
|
|
2431
|
+
// Graph-backed modes: skip if already queued — the existing entry will re-run
|
|
2355
2432
|
if (currentBatch.all.has(root)) {
|
|
2356
2433
|
return;
|
|
2357
2434
|
}
|
|
@@ -2360,7 +2437,7 @@ function addToBatch(effect, caller, immediate, reason) {
|
|
|
2360
2437
|
if (node.stopped)
|
|
2361
2438
|
return;
|
|
2362
2439
|
currentBatch.all.set(root, effect);
|
|
2363
|
-
if (caller && true && options.
|
|
2440
|
+
if (caller && true && options.scheduler !== 'raw') {
|
|
2364
2441
|
const callerRoot = getRoot(caller);
|
|
2365
2442
|
// const root = getRoot(effect) // Already have root
|
|
2366
2443
|
// Check for cycle BEFORE adding edge
|
|
@@ -2382,6 +2459,9 @@ function addToBatch(effect, caller, immediate, reason) {
|
|
|
2382
2459
|
}
|
|
2383
2460
|
addGraphEdge(callerRoot, root);
|
|
2384
2461
|
}
|
|
2462
|
+
if (options.scheduler !== 'raw') {
|
|
2463
|
+
computeAllInDegrees(currentBatch);
|
|
2464
|
+
}
|
|
2385
2465
|
}
|
|
2386
2466
|
/**
|
|
2387
2467
|
* Adds a cleanup function to be called when the current batch of effects completes
|
|
@@ -2478,7 +2558,7 @@ function executeNext(effectuatedRoots) {
|
|
|
2478
2558
|
// Find an effect with in-degree 0 using cached values
|
|
2479
2559
|
let nextEffect = null;
|
|
2480
2560
|
let nextRoot = null;
|
|
2481
|
-
if (options.
|
|
2561
|
+
if (options.scheduler === 'raw') {
|
|
2482
2562
|
// In flat mode, we just take the first effect in the queue (FIFO)
|
|
2483
2563
|
const first = currentBatch.all.entries().next().value;
|
|
2484
2564
|
if (first) {
|
|
@@ -2568,9 +2648,7 @@ function executeNext(effectuatedRoots) {
|
|
|
2568
2648
|
}
|
|
2569
2649
|
return result;
|
|
2570
2650
|
}
|
|
2571
|
-
|
|
2572
|
-
// These are all the effects triggered under `activeEffect` and all their sub-effects
|
|
2573
|
-
function batch(effect, immediate, caller) {
|
|
2651
|
+
function batch(effect, batchOptions) {
|
|
2574
2652
|
if (broken) {
|
|
2575
2653
|
throw new ReactiveError('[reactive] Reactive system is broken after an unrecoverable error. Call reset() to recover.', { code: exports.ReactiveErrorCode.BrokenEffects });
|
|
2576
2654
|
}
|
|
@@ -2585,16 +2663,38 @@ function batch(effect, immediate, caller) {
|
|
|
2585
2663
|
throw new Error('Activation registry already exists');
|
|
2586
2664
|
optionCall('beginChain', roots);
|
|
2587
2665
|
}
|
|
2588
|
-
|
|
2589
|
-
const
|
|
2590
|
-
const callerToUse = caller ||
|
|
2666
|
+
const immediate = batchOptions?.immediate === true;
|
|
2667
|
+
const contained = batchOptions?.contained === true;
|
|
2668
|
+
const callerToUse = batchOptions?.caller || getActiveEffect();
|
|
2591
2669
|
// Optimization: If nested and NOT immediate, just join the existing batch
|
|
2592
|
-
if (!isNewBatch && !immediate) {
|
|
2670
|
+
if (!isNewBatch && !contained && !immediate) {
|
|
2593
2671
|
for (let i = 0; i < effect.length; i++) {
|
|
2594
2672
|
addToBatch(effect[i], callerToUse);
|
|
2595
2673
|
}
|
|
2596
2674
|
return;
|
|
2597
2675
|
}
|
|
2676
|
+
if (!isNewBatch && !contained && immediate) {
|
|
2677
|
+
const firstReturn = {};
|
|
2678
|
+
for (let i = 0; i < effect.length; i++) {
|
|
2679
|
+
executingStack.push(effect[i]);
|
|
2680
|
+
try {
|
|
2681
|
+
const node = getEffectNode(effect[i]);
|
|
2682
|
+
const reason = node.nextReason;
|
|
2683
|
+
if (node.cleanup) {
|
|
2684
|
+
const cleanup = node.cleanup;
|
|
2685
|
+
node.cleanup = undefined;
|
|
2686
|
+
cleanup(reason);
|
|
2687
|
+
}
|
|
2688
|
+
const rv = effect[i]();
|
|
2689
|
+
if (rv !== undefined && !('value' in firstReturn))
|
|
2690
|
+
firstReturn.value = rv;
|
|
2691
|
+
}
|
|
2692
|
+
finally {
|
|
2693
|
+
executingStack.pop();
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
return firstReturn.value;
|
|
2697
|
+
}
|
|
2598
2698
|
const currentBatch = {
|
|
2599
2699
|
all: new Map(),
|
|
2600
2700
|
inDegrees: new Map(),
|
|
@@ -2602,9 +2702,11 @@ function batch(effect, immediate, caller) {
|
|
|
2602
2702
|
};
|
|
2603
2703
|
batchStack.push(currentBatch);
|
|
2604
2704
|
let success = false;
|
|
2705
|
+
let failure;
|
|
2605
2706
|
try {
|
|
2606
2707
|
const effectuatedRoots = [];
|
|
2607
2708
|
const firstReturn = {};
|
|
2709
|
+
let initialError;
|
|
2608
2710
|
if (immediate) {
|
|
2609
2711
|
// Execute initial effects in providing order
|
|
2610
2712
|
for (let i = 0; i < effect.length; i++) {
|
|
@@ -2621,11 +2723,17 @@ function batch(effect, immediate, caller) {
|
|
|
2621
2723
|
if (rv !== undefined && !('value' in firstReturn))
|
|
2622
2724
|
firstReturn.value = rv;
|
|
2623
2725
|
}
|
|
2726
|
+
catch (error) {
|
|
2727
|
+
initialError = error;
|
|
2728
|
+
break;
|
|
2729
|
+
}
|
|
2624
2730
|
finally {
|
|
2625
2731
|
executingStack.pop();
|
|
2626
2732
|
currentBatch.all.delete(getRoot(effect[i]));
|
|
2627
2733
|
}
|
|
2628
2734
|
}
|
|
2735
|
+
if (initialError)
|
|
2736
|
+
throw initialError;
|
|
2629
2737
|
}
|
|
2630
2738
|
else {
|
|
2631
2739
|
// Add initial effects to batch and compute dependencies
|
|
@@ -2685,13 +2793,17 @@ function batch(effect, immediate, caller) {
|
|
|
2685
2793
|
return firstReturn.value;
|
|
2686
2794
|
}
|
|
2687
2795
|
catch (error) {
|
|
2796
|
+
failure = error;
|
|
2688
2797
|
if (batchStack.length === 1)
|
|
2689
2798
|
optionCall('error', '[reactive] Root batch failure before broken state:', error);
|
|
2690
2799
|
throw error;
|
|
2691
2800
|
}
|
|
2692
2801
|
finally {
|
|
2693
2802
|
if (!success && batchStack.length === 1) {
|
|
2803
|
+
const wasBroken = broken;
|
|
2694
2804
|
broken = true;
|
|
2805
|
+
if (!wasBroken)
|
|
2806
|
+
notifyReactiveBroken(failure);
|
|
2695
2807
|
}
|
|
2696
2808
|
batchStack.pop();
|
|
2697
2809
|
if (batchStack.length === 0) {
|
|
@@ -2707,6 +2819,7 @@ function batch(effect, immediate, caller) {
|
|
|
2707
2819
|
* All existing effects become orphaned and must be recreated.
|
|
2708
2820
|
*/
|
|
2709
2821
|
function reset() {
|
|
2822
|
+
const wasBroken = broken;
|
|
2710
2823
|
broken = false;
|
|
2711
2824
|
activationRegistry = undefined;
|
|
2712
2825
|
batchStack.length = 0;
|
|
@@ -2717,6 +2830,8 @@ function reset() {
|
|
|
2717
2830
|
resetRegistry();
|
|
2718
2831
|
resetTracking();
|
|
2719
2832
|
effectHistory.present.active = undefined;
|
|
2833
|
+
if (wasBroken)
|
|
2834
|
+
notifyReactiveReset();
|
|
2720
2835
|
}
|
|
2721
2836
|
// Inject batch function to allow atomic game loops in requestAnimationFrame/setTimeout/...
|
|
2722
2837
|
// Note: Automatic batching of async callbacks (setTimeout, Promise.then, etc.) is NOT implemented.
|
|
@@ -2733,7 +2848,7 @@ const atomic = decorator({
|
|
|
2733
2848
|
const atomicEffect = () => original.apply(this, args);
|
|
2734
2849
|
// Debug: helpful to have a name
|
|
2735
2850
|
Object.defineProperty(atomicEffect, 'name', { value: `atomic(${original.name})` });
|
|
2736
|
-
return batch(atomicEffect,
|
|
2851
|
+
return batch(atomicEffect, { immediate: true });
|
|
2737
2852
|
};
|
|
2738
2853
|
},
|
|
2739
2854
|
default(original) {
|
|
@@ -2741,7 +2856,7 @@ const atomic = decorator({
|
|
|
2741
2856
|
const atomicEffect = () => original.apply(this, args);
|
|
2742
2857
|
// Debug: helpful to have a name
|
|
2743
2858
|
Object.defineProperty(atomicEffect, 'name', { value: `atomic(${original.name})` });
|
|
2744
|
-
return batch(atomicEffect,
|
|
2859
|
+
return batch(atomicEffect, { immediate: true });
|
|
2745
2860
|
};
|
|
2746
2861
|
},
|
|
2747
2862
|
});
|
|
@@ -2780,7 +2895,7 @@ function captured(prev, fn) {
|
|
|
2780
2895
|
* ```
|
|
2781
2896
|
*/
|
|
2782
2897
|
function atom(fn) {
|
|
2783
|
-
return batch(fn,
|
|
2898
|
+
return batch(fn, { immediate: true });
|
|
2784
2899
|
}
|
|
2785
2900
|
const fr = new FinalizationRegistry((f) => f());
|
|
2786
2901
|
/**
|
|
@@ -3024,7 +3139,7 @@ const effect = captioned(named(effectMarker.leave, flavored(function effect(fn,
|
|
|
3024
3139
|
abortController = undefined;
|
|
3025
3140
|
}
|
|
3026
3141
|
};
|
|
3027
|
-
batch(runEffect,
|
|
3142
|
+
batch(runEffect, { immediate: true });
|
|
3028
3143
|
// Only ROOT effects are registered for GC cleanup and zone tracking
|
|
3029
3144
|
const isRootEffect = !parent;
|
|
3030
3145
|
const stopEffect = (reason) => {
|
|
@@ -3094,16 +3209,53 @@ const untracked = captioned(function untracked(fn) {
|
|
|
3094
3209
|
? externalReason.with(external, () => effectHistory.present.root(fn))
|
|
3095
3210
|
: effectHistory.present.root(fn);
|
|
3096
3211
|
});
|
|
3212
|
+
function runInert(fn) {
|
|
3213
|
+
// Increment the counter
|
|
3214
|
+
const originalDepth = exports.inertDepth;
|
|
3215
|
+
exports.inertDepth = originalDepth + 1;
|
|
3216
|
+
try {
|
|
3217
|
+
return fn();
|
|
3218
|
+
}
|
|
3219
|
+
finally {
|
|
3220
|
+
exports.inertDepth = originalDepth;
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
function wrapInert(fn) {
|
|
3224
|
+
function inertEffect(...args) {
|
|
3225
|
+
return runInert(() => fn.apply(this, args));
|
|
3226
|
+
}
|
|
3227
|
+
Object.defineProperty(inertEffect, 'name', { value: `inert(${fn.name})` });
|
|
3228
|
+
return inertEffect;
|
|
3229
|
+
}
|
|
3230
|
+
/**
|
|
3231
|
+
* Executes a function with fast-path reads that bypass proxy overhead and dependency tracking.
|
|
3232
|
+
* Writes remain fully reactive. Uses a counter for safe nesting.
|
|
3233
|
+
* Can also decorate methods so the whole method body runs inertly.
|
|
3234
|
+
* @param fn - The function to execute
|
|
3235
|
+
*/
|
|
3236
|
+
const inert = decorator({
|
|
3237
|
+
method(original) {
|
|
3238
|
+
return wrapInert(original);
|
|
3239
|
+
},
|
|
3240
|
+
default(fn) {
|
|
3241
|
+
if (typeof fn !== 'function')
|
|
3242
|
+
throw new Error('inert() expects a function');
|
|
3243
|
+
return runInert(fn);
|
|
3244
|
+
},
|
|
3245
|
+
});
|
|
3097
3246
|
/**
|
|
3098
3247
|
* Executes a function from a virgin/root context - no parent effect, no tracking
|
|
3099
3248
|
* Creates completely independent effects that won't be cleaned up by any parent
|
|
3100
3249
|
* @param fn - The function to execute
|
|
3101
3250
|
*/
|
|
3102
3251
|
const root = captioned(function root(fn) {
|
|
3252
|
+
// When broken, `atomic`/`batch` throws immediately. DOM wrappers (e.g. Sursaut
|
|
3253
|
+
// `root\`event:…\``) still need to run listener code for inspection UI; skip batching.
|
|
3254
|
+
const runner = broken ? fn : atomic(fn);
|
|
3103
3255
|
const external = externalReasonFrom(fn);
|
|
3104
3256
|
return external
|
|
3105
|
-
? externalReason.with(external, () => effectHistory.root(
|
|
3106
|
-
: effectHistory.root(
|
|
3257
|
+
? externalReason.with(external, () => effectHistory.root(runner))
|
|
3258
|
+
: effectHistory.root(runner);
|
|
3107
3259
|
});
|
|
3108
3260
|
function biDi(received, get, set) {
|
|
3109
3261
|
if (typeof get !== 'function') {
|
|
@@ -3126,6 +3278,86 @@ function biDi(received, get, set) {
|
|
|
3126
3278
|
: () => { };
|
|
3127
3279
|
}
|
|
3128
3280
|
|
|
3281
|
+
/**
|
|
3282
|
+
* Creates a mixin that can be used both as a class (extends) and as a function (mixin)
|
|
3283
|
+
*
|
|
3284
|
+
* This function supports:
|
|
3285
|
+
* - Using mixins as base classes: `class MyClass extends MyMixin`
|
|
3286
|
+
* - Using mixins as functions: `class MyClass extends MyMixin(SomeBase)`
|
|
3287
|
+
* - Composing mixins: `const Composed = MixinA(MixinB)`
|
|
3288
|
+
* - Type-safe property inference for all patterns
|
|
3289
|
+
*
|
|
3290
|
+
* @param mixinFunction - The function that creates the mixin
|
|
3291
|
+
* @param unwrapFunction - Optional function to unwrap reactive objects for method calls
|
|
3292
|
+
* @returns A mixin that can be used both as a class and as a function
|
|
3293
|
+
*/
|
|
3294
|
+
function mixin(mixinFunction, unwrapFunction) {
|
|
3295
|
+
/**
|
|
3296
|
+
* Cache for mixin results to ensure the same base class always returns the same mixed class
|
|
3297
|
+
*/
|
|
3298
|
+
const mixinCache = new WeakMap();
|
|
3299
|
+
// Apply the mixin to Object as the base class
|
|
3300
|
+
const MixedBase = mixinFunction(Object);
|
|
3301
|
+
mixinCache.set(Object, MixedBase);
|
|
3302
|
+
// Create the proxy that handles both constructor and function calls
|
|
3303
|
+
return new Proxy(MixedBase, {
|
|
3304
|
+
// Handle `MixinClass(SomeBase)` - use as mixin function
|
|
3305
|
+
apply(_target, _thisArg, args) {
|
|
3306
|
+
if (args.length === 0) {
|
|
3307
|
+
throw new Error('Mixin requires a base class');
|
|
3308
|
+
}
|
|
3309
|
+
const baseClass = args[0];
|
|
3310
|
+
if (typeof baseClass !== 'function') {
|
|
3311
|
+
throw new Error('Mixin requires a constructor function');
|
|
3312
|
+
}
|
|
3313
|
+
// Check if it's a valid constructor or a mixin
|
|
3314
|
+
if (!isConstructor(baseClass) &&
|
|
3315
|
+
!(baseClass && typeof baseClass === 'function' && baseClass.prototype)) {
|
|
3316
|
+
throw new Error('Mixin requires a valid constructor');
|
|
3317
|
+
}
|
|
3318
|
+
// Check cache first
|
|
3319
|
+
const cached = mixinCache.get(baseClass);
|
|
3320
|
+
if (cached) {
|
|
3321
|
+
return cached;
|
|
3322
|
+
}
|
|
3323
|
+
let usedBase = baseClass;
|
|
3324
|
+
if (unwrapFunction) {
|
|
3325
|
+
// Create a proxied base class that handles method unwrapping
|
|
3326
|
+
const ProxiedBaseClass = class extends baseClass {
|
|
3327
|
+
};
|
|
3328
|
+
// Proxy the prototype methods to handle unwrapping
|
|
3329
|
+
const originalPrototype = baseClass.prototype;
|
|
3330
|
+
const proxiedPrototype = new Proxy(originalPrototype, {
|
|
3331
|
+
get(target, prop, receiver) {
|
|
3332
|
+
const value = FoolProof.get(target, prop, receiver);
|
|
3333
|
+
// Only wrap methods that are likely to access private fields
|
|
3334
|
+
// Skip symbols and special properties that the reactive system needs
|
|
3335
|
+
if (typeof value === 'function' &&
|
|
3336
|
+
typeof prop === 'string' &&
|
|
3337
|
+
!['constructor', 'toString', 'valueOf'].includes(prop)) {
|
|
3338
|
+
// Return a wrapped version that uses unwrapped context
|
|
3339
|
+
return function (...args) {
|
|
3340
|
+
// Use the unwrapping function if provided, otherwise use this
|
|
3341
|
+
const context = unwrapFunction(this);
|
|
3342
|
+
return value.apply(context, args);
|
|
3343
|
+
};
|
|
3344
|
+
}
|
|
3345
|
+
return value;
|
|
3346
|
+
},
|
|
3347
|
+
});
|
|
3348
|
+
// Set the proxied prototype
|
|
3349
|
+
Object.setPrototypeOf(ProxiedBaseClass.prototype, proxiedPrototype);
|
|
3350
|
+
usedBase = ProxiedBaseClass;
|
|
3351
|
+
}
|
|
3352
|
+
// Create the mixed class using the proxied base class
|
|
3353
|
+
const mixedClass = mixinFunction(usedBase);
|
|
3354
|
+
// Cache the result
|
|
3355
|
+
mixinCache.set(baseClass, mixedClass);
|
|
3356
|
+
return mixedClass;
|
|
3357
|
+
},
|
|
3358
|
+
});
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3129
3361
|
// Track which objects contain which other objects (back-references)
|
|
3130
3362
|
const objectParents = new WeakMap();
|
|
3131
3363
|
// Track which objects have deep watchers
|
|
@@ -3255,7 +3487,7 @@ function getState(obj) {
|
|
|
3255
3487
|
}
|
|
3256
3488
|
return state;
|
|
3257
3489
|
}
|
|
3258
|
-
function collectEffects(obj, evolution, effects, objectWatchers, ...keyChains) {
|
|
3490
|
+
function collectEffects(obj, evolution, effects, objectWatchers, collectDependencyStack, ...keyChains) {
|
|
3259
3491
|
const sourceEffect = getActiveEffect();
|
|
3260
3492
|
for (const keys of keyChains)
|
|
3261
3493
|
for (const key of keys) {
|
|
@@ -3271,7 +3503,7 @@ function collectEffects(obj, evolution, effects, objectWatchers, ...keyChains) {
|
|
|
3271
3503
|
continue;
|
|
3272
3504
|
}
|
|
3273
3505
|
if (!effects.has(effect)) {
|
|
3274
|
-
effects.set(effect, getDependencyStack(effect, obj, key));
|
|
3506
|
+
effects.set(effect, collectDependencyStack ? getDependencyStack(effect, obj, key) : undefined);
|
|
3275
3507
|
if (!hasBatched(effect))
|
|
3276
3508
|
recordActivation(effect, obj, evolution, key);
|
|
3277
3509
|
}
|
|
@@ -3304,17 +3536,18 @@ function touched(obj, evolution, props) {
|
|
|
3304
3536
|
const effects = new Map();
|
|
3305
3537
|
const structural = !['set', 'invalidate'].includes(evolution.type);
|
|
3306
3538
|
const broad = structural ? [allProps, keysOf] : [allProps];
|
|
3539
|
+
const gatherReasons = options.introspection?.gatherReasons;
|
|
3540
|
+
const lineageConfig = gatherReasons?.lineages;
|
|
3541
|
+
const collectDependencyStack = lineageConfig === 'dependency' || lineageConfig === 'both';
|
|
3307
3542
|
if (props)
|
|
3308
|
-
collectEffects(obj, evolution, effects, objectWatchers, broad, props);
|
|
3543
|
+
collectEffects(obj, evolution, effects, objectWatchers, collectDependencyStack, broad, props);
|
|
3309
3544
|
else
|
|
3310
|
-
collectEffects(obj, evolution, effects, objectWatchers, objectWatchers.keys());
|
|
3545
|
+
collectEffects(obj, evolution, effects, objectWatchers, collectDependencyStack, objectWatchers.keys());
|
|
3311
3546
|
const triggers = Array.from(effects.keys());
|
|
3312
3547
|
const sourceEffect = getActiveEffect();
|
|
3313
3548
|
optionCall('touched', obj, evolution, props, triggers);
|
|
3314
3549
|
// Store pending triggers for CleanupReason before batching
|
|
3315
|
-
if (
|
|
3316
|
-
const gatherReasons = options.introspection.gatherReasons;
|
|
3317
|
-
const lineageConfig = gatherReasons.lineages;
|
|
3550
|
+
if (gatherReasons && effects.size > 0) {
|
|
3318
3551
|
let touchLineage;
|
|
3319
3552
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
3320
3553
|
touchLineage = debugHooks.captureLineage();
|
|
@@ -3331,7 +3564,7 @@ function touched(obj, evolution, props) {
|
|
|
3331
3564
|
});
|
|
3332
3565
|
}
|
|
3333
3566
|
}
|
|
3334
|
-
batch(triggers,
|
|
3567
|
+
batch(triggers, { caller: sourceEffect });
|
|
3335
3568
|
}
|
|
3336
3569
|
// Bubble up changes if this object has deep watchers
|
|
3337
3570
|
if (objectsWithDeepWatchers.has(obj)) {
|
|
@@ -3355,6 +3588,7 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
3355
3588
|
const gather = options.introspection?.gatherReasons;
|
|
3356
3589
|
if (gather) {
|
|
3357
3590
|
const lineageConfig = gather.lineages;
|
|
3591
|
+
let touchLineage;
|
|
3358
3592
|
for (const effect of deps) {
|
|
3359
3593
|
const node = getEffectNode(effect);
|
|
3360
3594
|
if (!node.isOpaque)
|
|
@@ -3366,10 +3600,9 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
3366
3600
|
}
|
|
3367
3601
|
effects.add(effect);
|
|
3368
3602
|
if (gather) {
|
|
3369
|
-
let touchLineage;
|
|
3370
3603
|
let dependencyStack;
|
|
3371
3604
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
3372
|
-
touchLineage = debugHooks.captureLineage();
|
|
3605
|
+
touchLineage ?? (touchLineage = debugHooks.captureLineage());
|
|
3373
3606
|
}
|
|
3374
3607
|
if (lineageConfig === 'dependency' || lineageConfig === 'both') {
|
|
3375
3608
|
dependencyStack = getDependencyStack(effect, obj, prop);
|
|
@@ -3405,7 +3638,7 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
3405
3638
|
}
|
|
3406
3639
|
if (effects.size > 0) {
|
|
3407
3640
|
optionCall('touched', obj, evolution, [prop], Array.from(effects));
|
|
3408
|
-
batch(Array.from(effects),
|
|
3641
|
+
batch(Array.from(effects), { caller: sourceEffect });
|
|
3409
3642
|
}
|
|
3410
3643
|
}
|
|
3411
3644
|
|
|
@@ -3452,6 +3685,8 @@ function nonReactive(...obj) {
|
|
|
3452
3685
|
}
|
|
3453
3686
|
return obj[0];
|
|
3454
3687
|
}
|
|
3688
|
+
const markRaw = nonReactive;
|
|
3689
|
+
const markRawProps = addUnreactiveProps;
|
|
3455
3690
|
function nonReactiveClass(...cls) {
|
|
3456
3691
|
for (const c of cls)
|
|
3457
3692
|
if (c)
|
|
@@ -3669,7 +3904,7 @@ function dispatchNotifications(notifications) {
|
|
|
3669
3904
|
const originWatchers = exports.watchers.get(origin.obj);
|
|
3670
3905
|
if (originWatchers) {
|
|
3671
3906
|
const originEffects = new Map();
|
|
3672
|
-
collectEffects(origin.obj, { type: 'set', prop: origin.prop }, originEffects, originWatchers, [allProps], [origin.prop]);
|
|
3907
|
+
collectEffects(origin.obj, { type: 'set', prop: origin.prop }, originEffects, originWatchers, false, [allProps], [origin.prop]);
|
|
3673
3908
|
allowedEffects = new Set(originEffects.keys());
|
|
3674
3909
|
}
|
|
3675
3910
|
// If no allowed effects, skip all notifications (no one should be notified)
|
|
@@ -3688,7 +3923,7 @@ function dispatchNotifications(notifications) {
|
|
|
3688
3923
|
if (objectWatchers) {
|
|
3689
3924
|
currentEffects = new Map();
|
|
3690
3925
|
const broad = evolution.type !== 'set' ? [allProps, keysOf] : [allProps];
|
|
3691
|
-
collectEffects(obj, evolution, currentEffects, objectWatchers, broad, propsArray);
|
|
3926
|
+
collectEffects(obj, evolution, currentEffects, objectWatchers, false, broad, propsArray);
|
|
3692
3927
|
// Filter effects by ancestor chain if origin exists
|
|
3693
3928
|
// Include effects that either directly depend on origin or have an ancestor that does
|
|
3694
3929
|
if (origin && allowedEffects) {
|
|
@@ -3750,12 +3985,34 @@ const metaProtos = new WeakMap();
|
|
|
3750
3985
|
const wrapProtos = new WeakMap();
|
|
3751
3986
|
const arrayLengths = new WeakMap();
|
|
3752
3987
|
const hasReentry = new Set();
|
|
3988
|
+
const accessAnalysisCache = new WeakMap();
|
|
3989
|
+
const readonlyObjectToProxy = new WeakMap();
|
|
3990
|
+
const shallowObjectToProxy = new WeakMap();
|
|
3991
|
+
const readonlyMutators = new Set([
|
|
3992
|
+
'copyWithin',
|
|
3993
|
+
'fill',
|
|
3994
|
+
'pop',
|
|
3995
|
+
'push',
|
|
3996
|
+
'reverse',
|
|
3997
|
+
'shift',
|
|
3998
|
+
'sort',
|
|
3999
|
+
'splice',
|
|
4000
|
+
'unshift',
|
|
4001
|
+
'add',
|
|
4002
|
+
'clear',
|
|
4003
|
+
'delete',
|
|
4004
|
+
'set',
|
|
4005
|
+
]);
|
|
3753
4006
|
// Sub-proxy registration for custom reactive behaviors
|
|
3754
4007
|
const subsRegister = new WeakMap();
|
|
3755
4008
|
// Internal untracked flag for setter/getter operations - only used when testing oldValue while setting a value
|
|
3756
4009
|
// TODO: `touched` trigger also compares to old value and should use the internalUntracked flag
|
|
3757
4010
|
let internalUntracked = false;
|
|
3758
4011
|
function wrapReactiveValue(obj, prop, value) {
|
|
4012
|
+
// Optional fast-path for inert reads - skips reactive wrapping
|
|
4013
|
+
// Disabled by default for safety, can be enabled for performance-critical read-only contexts
|
|
4014
|
+
if (exports.inertDepth > 0)
|
|
4015
|
+
return value;
|
|
3759
4016
|
if (!isReactive(value) && typeof value === 'object' && value !== null) {
|
|
3760
4017
|
const reactiveValue = reactiveObject(value);
|
|
3761
4018
|
// Only create back-references if this object needs them
|
|
@@ -3766,6 +4023,56 @@ function wrapReactiveValue(obj, prop, value) {
|
|
|
3766
4023
|
}
|
|
3767
4024
|
return value;
|
|
3768
4025
|
}
|
|
4026
|
+
function computeAccessAnalysis(obj, prop, receiver) {
|
|
4027
|
+
const proto = Object.getPrototypeOf(obj);
|
|
4028
|
+
const isOwnProp = Object.hasOwn(obj, prop);
|
|
4029
|
+
const shouldIgnoreAccessor = options.ignoreAccessors &&
|
|
4030
|
+
isOwnProp &&
|
|
4031
|
+
proto !== null &&
|
|
4032
|
+
(isOwnAccessor(receiver, prop) || isOwnAccessor(obj, prop));
|
|
4033
|
+
let hasProp = isOwnProp;
|
|
4034
|
+
let owner = isOwnProp ? obj : undefined;
|
|
4035
|
+
if (!isOwnProp) {
|
|
4036
|
+
let raw = proto;
|
|
4037
|
+
while (raw && raw !== Object.prototype) {
|
|
4038
|
+
if (Object.hasOwn(raw, prop)) {
|
|
4039
|
+
hasProp = true;
|
|
4040
|
+
owner = raw;
|
|
4041
|
+
break;
|
|
4042
|
+
}
|
|
4043
|
+
raw = Object.getPrototypeOf(raw);
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
return {
|
|
4047
|
+
hasProp,
|
|
4048
|
+
owner,
|
|
4049
|
+
isInheritedAccess: hasProp && !isOwnProp,
|
|
4050
|
+
shouldIgnoreAccessor,
|
|
4051
|
+
ignoreAccessors: options.ignoreAccessors,
|
|
4052
|
+
instanceMembers: options.instanceMembers,
|
|
4053
|
+
};
|
|
4054
|
+
}
|
|
4055
|
+
function analyzeAccess(obj, prop, receiver) {
|
|
4056
|
+
const proto = Object.getPrototypeOf(obj);
|
|
4057
|
+
if (Object.hasOwn(obj, prop))
|
|
4058
|
+
return computeAccessAnalysis(obj, prop, receiver);
|
|
4059
|
+
if (proto === null || Array.isArray(obj))
|
|
4060
|
+
return computeAccessAnalysis(obj, prop, receiver);
|
|
4061
|
+
let propCache = accessAnalysisCache.get(proto);
|
|
4062
|
+
if (!propCache) {
|
|
4063
|
+
propCache = new Map();
|
|
4064
|
+
accessAnalysisCache.set(proto, propCache);
|
|
4065
|
+
}
|
|
4066
|
+
const cached = propCache.get(prop);
|
|
4067
|
+
if (cached &&
|
|
4068
|
+
cached.ignoreAccessors === options.ignoreAccessors &&
|
|
4069
|
+
cached.instanceMembers === options.instanceMembers)
|
|
4070
|
+
return cached;
|
|
4071
|
+
const analysis = computeAccessAnalysis(obj, prop, receiver);
|
|
4072
|
+
if (analysis.hasProp)
|
|
4073
|
+
propCache.set(prop, analysis);
|
|
4074
|
+
return analysis;
|
|
4075
|
+
}
|
|
3769
4076
|
const reactiveHandlers = {
|
|
3770
4077
|
[Symbol.toStringTag]: 'MutTs Reactive',
|
|
3771
4078
|
get(obj, prop, receiver) {
|
|
@@ -3793,36 +4100,29 @@ const reactiveHandlers = {
|
|
|
3793
4100
|
// Symbols: fast-path — no reactivity tracking
|
|
3794
4101
|
if (typeof prop === 'symbol' || prop === 'constructor' || isUnreactiveProp(obj, prop))
|
|
3795
4102
|
return FoolProof.get(obj, prop, receiver);
|
|
3796
|
-
|
|
3797
|
-
|
|
4103
|
+
const subProxy = subsRegister.get(obj);
|
|
4104
|
+
if (exports.inertDepth > 0) {
|
|
4105
|
+
const value = (subProxy?.get || FoolProof.get)(obj, prop, receiver);
|
|
3798
4106
|
return wrapReactiveValue(obj, prop, value);
|
|
3799
4107
|
}
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
let hasProp = isOwnProp;
|
|
3813
|
-
let owner = isOwnProp ? obj : undefined;
|
|
3814
|
-
if (!isOwnProp) {
|
|
3815
|
-
let raw = Object.getPrototypeOf(obj);
|
|
3816
|
-
while (raw && raw !== Object.prototype) {
|
|
3817
|
-
if (Object.hasOwn(raw, prop)) {
|
|
3818
|
-
hasProp = true;
|
|
3819
|
-
owner = raw;
|
|
3820
|
-
break;
|
|
4108
|
+
const activeEffect = getActiveEffect();
|
|
4109
|
+
if (!activeEffect) {
|
|
4110
|
+
const value = (subProxy?.get || FoolProof.get)(obj, prop, receiver);
|
|
4111
|
+
return wrapReactiveValue(obj, prop, value);
|
|
4112
|
+
}
|
|
4113
|
+
if (!subProxy && !Array.isArray(obj)) {
|
|
4114
|
+
const proto = Object.getPrototypeOf(obj);
|
|
4115
|
+
if (proto === Object.prototype || proto === null) {
|
|
4116
|
+
const ownDesc = Object.getOwnPropertyDescriptor(obj, prop);
|
|
4117
|
+
if (ownDesc && 'value' in ownDesc) {
|
|
4118
|
+
dependant(obj, prop);
|
|
4119
|
+
return wrapReactiveValue(obj, prop, ownDesc.value);
|
|
3821
4120
|
}
|
|
3822
|
-
raw = Object.getPrototypeOf(raw);
|
|
3823
4121
|
}
|
|
3824
4122
|
}
|
|
3825
|
-
|
|
4123
|
+
// Check if property exists using a trap-free walk to avoid triggering
|
|
4124
|
+
// the has-trap cascade on prototype chains of reactive proxies.
|
|
4125
|
+
const { hasProp, owner, isInheritedAccess, shouldIgnoreAccessor } = analyzeAccess(obj, prop, receiver);
|
|
3826
4126
|
// Depend if...
|
|
3827
4127
|
if (!hasProp ||
|
|
3828
4128
|
(!(options.instanceMembers && isInheritedAccess && obj instanceof Object) &&
|
|
@@ -3835,7 +4135,7 @@ const reactiveHandlers = {
|
|
|
3835
4135
|
}
|
|
3836
4136
|
// For arrays, use FoolProof.get (Indexer path) for numeric index reactivity.
|
|
3837
4137
|
// For all other objects, inline Reflect.get directly (skips 3 function calls).
|
|
3838
|
-
const value = (
|
|
4138
|
+
const value = (subProxy?.get || FoolProof.get)(obj, prop, receiver);
|
|
3839
4139
|
return wrapReactiveValue(obj, prop, value);
|
|
3840
4140
|
},
|
|
3841
4141
|
set(obj, prop, value, receiver) {
|
|
@@ -3909,6 +4209,11 @@ const reactiveHandlers = {
|
|
|
3909
4209
|
cycle: [], // We don't have the full cycle here, but we know it involves obj
|
|
3910
4210
|
});
|
|
3911
4211
|
hasReentry.add(obj);
|
|
4212
|
+
if (exports.inertDepth > 0) {
|
|
4213
|
+
const rv = (subsRegister.get(obj)?.has || Reflect.has)(obj, prop);
|
|
4214
|
+
hasReentry.delete(obj);
|
|
4215
|
+
return rv;
|
|
4216
|
+
}
|
|
3912
4217
|
if (!internalUntracked && !isUnreactiveProp(obj, prop))
|
|
3913
4218
|
dependant(obj, prop);
|
|
3914
4219
|
const rv = (subsRegister.get(obj)?.has || Reflect.has)(obj, prop);
|
|
@@ -3917,7 +4222,7 @@ const reactiveHandlers = {
|
|
|
3917
4222
|
},
|
|
3918
4223
|
deleteProperty(obj, prop) {
|
|
3919
4224
|
if (!Object.hasOwn(obj, prop))
|
|
3920
|
-
return
|
|
4225
|
+
return true;
|
|
3921
4226
|
const oldVal = obj[prop];
|
|
3922
4227
|
// Remove back-references if this object has deep watchers
|
|
3923
4228
|
if (objectsWithDeepWatchers.has(obj) && typeof oldVal === 'object' && oldVal !== null) {
|
|
@@ -3932,6 +4237,9 @@ const reactiveHandlers = {
|
|
|
3932
4237
|
return true;
|
|
3933
4238
|
},
|
|
3934
4239
|
ownKeys(obj) {
|
|
4240
|
+
if (exports.inertDepth > 0) {
|
|
4241
|
+
return subsRegister.get(obj)?.ownKeys?.(obj) || Reflect.ownKeys(obj);
|
|
4242
|
+
}
|
|
3935
4243
|
dependant(obj, keysOf);
|
|
3936
4244
|
return subsRegister.get(obj)?.ownKeys?.(obj) || Reflect.ownKeys(obj);
|
|
3937
4245
|
},
|
|
@@ -3940,6 +4248,98 @@ const reactiveHandlers = {
|
|
|
3940
4248
|
Reflect.getOwnPropertyDescriptor(obj, prop));
|
|
3941
4249
|
},
|
|
3942
4250
|
};
|
|
4251
|
+
function readonlyError(prop) {
|
|
4252
|
+
return new ReactiveError(`[reactive] Cannot mutate readonly reactive property '${String(prop)}'`, {
|
|
4253
|
+
code: exports.ReactiveErrorCode.WriteInComputed,
|
|
4254
|
+
});
|
|
4255
|
+
}
|
|
4256
|
+
function readonlyValue(value) {
|
|
4257
|
+
if (!value || typeof value !== 'object')
|
|
4258
|
+
return value;
|
|
4259
|
+
return readonlyReactive(value);
|
|
4260
|
+
}
|
|
4261
|
+
const shallowReactiveHandlers = {
|
|
4262
|
+
get(obj, prop, receiver) {
|
|
4263
|
+
if (typeof prop === 'symbol' || prop === 'constructor' || isUnreactiveProp(obj, prop))
|
|
4264
|
+
return Reflect.get(obj, prop, receiver);
|
|
4265
|
+
if (getActiveEffect())
|
|
4266
|
+
dependant(obj, prop);
|
|
4267
|
+
return Reflect.get(obj, prop, receiver);
|
|
4268
|
+
},
|
|
4269
|
+
set(obj, prop, value, receiver) {
|
|
4270
|
+
const unwrapped = unwrap(receiver);
|
|
4271
|
+
if (obj !== unwrapped)
|
|
4272
|
+
return Object.defineProperty(unwrapped, prop, {
|
|
4273
|
+
value,
|
|
4274
|
+
configurable: true,
|
|
4275
|
+
writable: true,
|
|
4276
|
+
enumerable: true,
|
|
4277
|
+
});
|
|
4278
|
+
if (isUnreactiveProp(obj, prop))
|
|
4279
|
+
return FoolProof.set(obj, prop, value, receiver);
|
|
4280
|
+
const hadProperty = Reflect.has(obj, prop);
|
|
4281
|
+
const oldVal = hadProperty ? Reflect.get(obj, prop, receiver) : absent;
|
|
4282
|
+
const newValue = unwrap(value);
|
|
4283
|
+
if (oldVal !== newValue && FoolProof.set(obj, prop, newValue, receiver)) {
|
|
4284
|
+
touched1(obj, { type: hadProperty ? 'set' : 'add', prop }, prop);
|
|
4285
|
+
}
|
|
4286
|
+
return true;
|
|
4287
|
+
},
|
|
4288
|
+
has(obj, prop) {
|
|
4289
|
+
return reactiveHandlers.has(obj, prop);
|
|
4290
|
+
},
|
|
4291
|
+
deleteProperty(obj, prop) {
|
|
4292
|
+
if (!Object.hasOwn(obj, prop))
|
|
4293
|
+
return true;
|
|
4294
|
+
delete obj[prop];
|
|
4295
|
+
touched1(obj, { type: 'del', prop }, prop);
|
|
4296
|
+
return true;
|
|
4297
|
+
},
|
|
4298
|
+
ownKeys(obj) {
|
|
4299
|
+
return reactiveHandlers.ownKeys(obj);
|
|
4300
|
+
},
|
|
4301
|
+
getOwnPropertyDescriptor(obj, prop) {
|
|
4302
|
+
return Reflect.getOwnPropertyDescriptor(obj, prop);
|
|
4303
|
+
},
|
|
4304
|
+
};
|
|
4305
|
+
const readonlyReactiveHandlers = {
|
|
4306
|
+
get(obj, prop, receiver) {
|
|
4307
|
+
if (readonlyMutators.has(prop)) {
|
|
4308
|
+
return () => {
|
|
4309
|
+
throw readonlyError(prop);
|
|
4310
|
+
};
|
|
4311
|
+
}
|
|
4312
|
+
const reactiveTarget = reactiveObject(obj);
|
|
4313
|
+
const value = FoolProof.get(reactiveTarget, prop, receiver);
|
|
4314
|
+
if (typeof value === 'function') {
|
|
4315
|
+
return (...args) => readonlyValue(value.apply(reactiveTarget, args));
|
|
4316
|
+
}
|
|
4317
|
+
return readonlyValue(value);
|
|
4318
|
+
},
|
|
4319
|
+
set(_obj, prop) {
|
|
4320
|
+
throw readonlyError(prop);
|
|
4321
|
+
},
|
|
4322
|
+
deleteProperty(_obj, prop) {
|
|
4323
|
+
throw readonlyError(prop);
|
|
4324
|
+
},
|
|
4325
|
+
defineProperty(_obj, prop) {
|
|
4326
|
+
throw readonlyError(prop);
|
|
4327
|
+
},
|
|
4328
|
+
setPrototypeOf() {
|
|
4329
|
+
throw readonlyError('[[Prototype]]');
|
|
4330
|
+
},
|
|
4331
|
+
has(obj, prop) {
|
|
4332
|
+
const reactiveTarget = reactiveObject(obj);
|
|
4333
|
+
return Reflect.has(reactiveTarget, prop);
|
|
4334
|
+
},
|
|
4335
|
+
ownKeys(obj) {
|
|
4336
|
+
const reactiveTarget = reactiveObject(obj);
|
|
4337
|
+
return Reflect.ownKeys(reactiveTarget);
|
|
4338
|
+
},
|
|
4339
|
+
getOwnPropertyDescriptor(obj, prop) {
|
|
4340
|
+
return Reflect.getOwnPropertyDescriptor(obj, prop);
|
|
4341
|
+
},
|
|
4342
|
+
};
|
|
3943
4343
|
const reactiveClasses = new WeakSet();
|
|
3944
4344
|
// Create the ReactiveBase mixin
|
|
3945
4345
|
/**
|
|
@@ -3981,6 +4381,34 @@ function reactiveObject(anyTarget, subProxy) {
|
|
|
3981
4381
|
storeProxyRelationship(target, proxy);
|
|
3982
4382
|
return proxy;
|
|
3983
4383
|
}
|
|
4384
|
+
function shallowReactiveObject(anyTarget) {
|
|
4385
|
+
if (!anyTarget || typeof anyTarget !== 'object')
|
|
4386
|
+
return anyTarget;
|
|
4387
|
+
const target = unwrap(anyTarget);
|
|
4388
|
+
if (isNonReactive(target))
|
|
4389
|
+
return target;
|
|
4390
|
+
const existing = shallowObjectToProxy.get(target);
|
|
4391
|
+
if (existing)
|
|
4392
|
+
return existing;
|
|
4393
|
+
const proxy = new Proxy(target, shallowReactiveHandlers);
|
|
4394
|
+
shallowObjectToProxy.set(target, proxy);
|
|
4395
|
+
proxyToObject.set(proxy, target);
|
|
4396
|
+
return proxy;
|
|
4397
|
+
}
|
|
4398
|
+
function readonlyReactiveObject(anyTarget) {
|
|
4399
|
+
if (!anyTarget || typeof anyTarget !== 'object')
|
|
4400
|
+
return anyTarget;
|
|
4401
|
+
const target = unwrap(anyTarget);
|
|
4402
|
+
if (isNonReactive(target))
|
|
4403
|
+
return target;
|
|
4404
|
+
const existing = readonlyObjectToProxy.get(target);
|
|
4405
|
+
if (existing)
|
|
4406
|
+
return existing;
|
|
4407
|
+
const proxy = new Proxy(target, readonlyReactiveHandlers);
|
|
4408
|
+
readonlyObjectToProxy.set(target, proxy);
|
|
4409
|
+
proxyToObject.set(proxy, target);
|
|
4410
|
+
return proxy;
|
|
4411
|
+
}
|
|
3984
4412
|
/**
|
|
3985
4413
|
* Main decorator for making classes reactive
|
|
3986
4414
|
* Automatically makes class instances reactive when created
|
|
@@ -4011,6 +4439,8 @@ const reactive = decorator({
|
|
|
4011
4439
|
},
|
|
4012
4440
|
default: reactiveObject,
|
|
4013
4441
|
});
|
|
4442
|
+
const shallowReactive = shallowReactiveObject;
|
|
4443
|
+
const readonlyReactive = readonlyReactiveObject;
|
|
4014
4444
|
|
|
4015
4445
|
exports.AZone = AZone;
|
|
4016
4446
|
exports.CompareSymbol = CompareSymbol;
|
|
@@ -4035,7 +4465,6 @@ exports.asyncHooks = asyncHooks;
|
|
|
4035
4465
|
exports.asyncZone = asyncZone;
|
|
4036
4466
|
exports.atom = atom;
|
|
4037
4467
|
exports.atomic = atomic;
|
|
4038
|
-
exports.batch = batch;
|
|
4039
4468
|
exports.biDi = biDi;
|
|
4040
4469
|
exports.captioned = captioned;
|
|
4041
4470
|
exports.captured = captured;
|
|
@@ -4065,6 +4494,7 @@ exports.getEffectNode = getEffectNode;
|
|
|
4065
4494
|
exports.getRoot = getRoot;
|
|
4066
4495
|
exports.getState = getState;
|
|
4067
4496
|
exports.hooks = hooks;
|
|
4497
|
+
exports.inert = inert;
|
|
4068
4498
|
exports.inheritCaption = inheritCaption;
|
|
4069
4499
|
exports.isConstructor = isConstructor;
|
|
4070
4500
|
exports.isDev = isDev;
|
|
@@ -4072,10 +4502,13 @@ exports.isNonReactive = isNonReactive;
|
|
|
4072
4502
|
exports.isObject = isObject;
|
|
4073
4503
|
exports.isProd = isProd;
|
|
4074
4504
|
exports.isReactive = isReactive;
|
|
4505
|
+
exports.isReactiveBroken = isReactiveBroken;
|
|
4075
4506
|
exports.isTest = isTest;
|
|
4076
4507
|
exports.keysOf = keysOf;
|
|
4077
4508
|
exports.legacyDecorator = legacyDecorator;
|
|
4078
4509
|
exports.link = link;
|
|
4510
|
+
exports.markRaw = markRaw;
|
|
4511
|
+
exports.markRawProps = markRawProps;
|
|
4079
4512
|
exports.markWithRoot = markWithRoot;
|
|
4080
4513
|
exports.metaProtos = metaProtos;
|
|
4081
4514
|
exports.mixin = mixin;
|
|
@@ -4086,17 +4519,22 @@ exports.objectParents = objectParents;
|
|
|
4086
4519
|
exports.objectToProxy = objectToProxy;
|
|
4087
4520
|
exports.objectsWithDeepWatchers = objectsWithDeepWatchers;
|
|
4088
4521
|
exports.onEffectThrow = onEffectThrow;
|
|
4522
|
+
exports.onReactiveBroken = onReactiveBroken;
|
|
4523
|
+
exports.onReactiveReset = onReactiveReset;
|
|
4089
4524
|
exports.optionCall = optionCall;
|
|
4090
4525
|
exports.options = options;
|
|
4091
4526
|
exports.prodPreset = prodPreset;
|
|
4092
4527
|
exports.proxyToObject = proxyToObject;
|
|
4093
4528
|
exports.reactive = reactive;
|
|
4529
|
+
exports.readonlyReactive = readonlyReactive;
|
|
4094
4530
|
exports.registerDeepWatcher = registerDeepWatcher;
|
|
4095
4531
|
exports.reset = reset;
|
|
4096
4532
|
exports.root = root;
|
|
4097
4533
|
exports.rootFunctionSymbol = rootFunctionSymbol;
|
|
4098
4534
|
exports.setDebugHooks = setDebugHooks;
|
|
4535
|
+
exports.shallowReactive = shallowReactive;
|
|
4099
4536
|
exports.tag = tag;
|
|
4537
|
+
exports.toRaw = toRaw;
|
|
4100
4538
|
exports.touched = touched;
|
|
4101
4539
|
exports.touched1 = touched1;
|
|
4102
4540
|
exports.unlink = unlink;
|
|
@@ -4104,6 +4542,7 @@ exports.unreactiveProperties = unreactiveProperties;
|
|
|
4104
4542
|
exports.untracked = untracked;
|
|
4105
4543
|
exports.unwrap = unwrap;
|
|
4106
4544
|
exports.withEffectContext = withEffectContext;
|
|
4545
|
+
exports.wrapInert = wrapInert;
|
|
4107
4546
|
exports.wrapProtos = wrapProtos;
|
|
4108
4547
|
exports.zip = zip;
|
|
4109
|
-
//# sourceMappingURL=proxy-
|
|
4548
|
+
//# sourceMappingURL=proxy-HA_QQnd5.cjs.map
|