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,26 +1,3 @@
|
|
|
1
|
-
// Queue for hooks registered before the environment is ready (circular dependency fix)
|
|
2
|
-
const hooks = new Set();
|
|
3
|
-
const asyncHooks = {
|
|
4
|
-
addHook(hook) {
|
|
5
|
-
hooks.add(hook);
|
|
6
|
-
return () => hooks.delete(hook);
|
|
7
|
-
},
|
|
8
|
-
/**
|
|
9
|
-
* [Hack] Sanitize a promise (or value) to prevent context leaks.
|
|
10
|
-
* Default: Identity function.
|
|
11
|
-
* Browser: Uses Macrotask wrapping to break microtask chains.
|
|
12
|
-
*/
|
|
13
|
-
sanitizePromise(p) {
|
|
14
|
-
return p;
|
|
15
|
-
},
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Register a hook that will be called whenever an asynchronous operation is initiated.
|
|
19
|
-
* The hook should return a restorer function which will be called just before the async callback runs.
|
|
20
|
-
* That restorer should in turn return an undoer function which will be called just after the async callback finishes.
|
|
21
|
-
*/
|
|
22
|
-
const asyncHook = (hook) => asyncHooks.addHook(hook);
|
|
23
|
-
|
|
24
1
|
/**
|
|
25
2
|
* Yields tuples containing elements from each input array, stopping at the longest array length
|
|
26
3
|
* @param args - Arrays to zip together
|
|
@@ -782,7 +759,13 @@ class IterableWeakSet {
|
|
|
782
759
|
[Symbol.iterator]() {
|
|
783
760
|
return this.keys();
|
|
784
761
|
}
|
|
785
|
-
union(other) {
|
|
762
|
+
union(other, ...sets) {
|
|
763
|
+
if (sets.length > 0) {
|
|
764
|
+
for (const set of [other, ...sets])
|
|
765
|
+
for (const value of set)
|
|
766
|
+
this.add(value);
|
|
767
|
+
return this;
|
|
768
|
+
}
|
|
786
769
|
const others = {
|
|
787
770
|
[Symbol.iterator]() {
|
|
788
771
|
return other.keys();
|
|
@@ -854,100 +837,6 @@ class IterableWeakSet {
|
|
|
854
837
|
}
|
|
855
838
|
_b = Symbol.toStringTag;
|
|
856
839
|
|
|
857
|
-
/**
|
|
858
|
-
* Creates a mixin that can be used both as a class (extends) and as a function (mixin)
|
|
859
|
-
*
|
|
860
|
-
* This function supports:
|
|
861
|
-
* - Using mixins as base classes: `class MyClass extends MyMixin`
|
|
862
|
-
* - Using mixins as functions: `class MyClass extends MyMixin(SomeBase)`
|
|
863
|
-
* - Composing mixins: `const Composed = MixinA(MixinB)`
|
|
864
|
-
* - Type-safe property inference for all patterns
|
|
865
|
-
*
|
|
866
|
-
* @param mixinFunction - The function that creates the mixin
|
|
867
|
-
* @param unwrapFunction - Optional function to unwrap reactive objects for method calls
|
|
868
|
-
* @returns A mixin that can be used both as a class and as a function
|
|
869
|
-
*/
|
|
870
|
-
function mixin(mixinFunction, unwrapFunction) {
|
|
871
|
-
/**
|
|
872
|
-
* Cache for mixin results to ensure the same base class always returns the same mixed class
|
|
873
|
-
*/
|
|
874
|
-
const mixinCache = new WeakMap();
|
|
875
|
-
// Apply the mixin to Object as the base class
|
|
876
|
-
const MixedBase = mixinFunction(Object);
|
|
877
|
-
mixinCache.set(Object, MixedBase);
|
|
878
|
-
// Create the proxy that handles both constructor and function calls
|
|
879
|
-
return new Proxy(MixedBase, {
|
|
880
|
-
// Handle `MixinClass(SomeBase)` - use as mixin function
|
|
881
|
-
apply(_target, _thisArg, args) {
|
|
882
|
-
if (args.length === 0) {
|
|
883
|
-
throw new Error('Mixin requires a base class');
|
|
884
|
-
}
|
|
885
|
-
const baseClass = args[0];
|
|
886
|
-
if (typeof baseClass !== 'function') {
|
|
887
|
-
throw new Error('Mixin requires a constructor function');
|
|
888
|
-
}
|
|
889
|
-
// Check if it's a valid constructor or a mixin
|
|
890
|
-
if (!isConstructor(baseClass) &&
|
|
891
|
-
!(baseClass && typeof baseClass === 'function' && baseClass.prototype)) {
|
|
892
|
-
throw new Error('Mixin requires a valid constructor');
|
|
893
|
-
}
|
|
894
|
-
// Check cache first
|
|
895
|
-
const cached = mixinCache.get(baseClass);
|
|
896
|
-
if (cached) {
|
|
897
|
-
return cached;
|
|
898
|
-
}
|
|
899
|
-
let usedBase = baseClass;
|
|
900
|
-
if (unwrapFunction) {
|
|
901
|
-
// Create a proxied base class that handles method unwrapping
|
|
902
|
-
const ProxiedBaseClass = class extends baseClass {
|
|
903
|
-
};
|
|
904
|
-
// Proxy the prototype methods to handle unwrapping
|
|
905
|
-
const originalPrototype = baseClass.prototype;
|
|
906
|
-
const proxiedPrototype = new Proxy(originalPrototype, {
|
|
907
|
-
get(target, prop, receiver) {
|
|
908
|
-
const value = FoolProof.get(target, prop, receiver);
|
|
909
|
-
// Only wrap methods that are likely to access private fields
|
|
910
|
-
// Skip symbols and special properties that the reactive system needs
|
|
911
|
-
if (typeof value === 'function' &&
|
|
912
|
-
typeof prop === 'string' &&
|
|
913
|
-
!['constructor', 'toString', 'valueOf'].includes(prop)) {
|
|
914
|
-
// Return a wrapped version that uses unwrapped context
|
|
915
|
-
return function (...args) {
|
|
916
|
-
// Use the unwrapping function if provided, otherwise use this
|
|
917
|
-
const context = unwrapFunction(this);
|
|
918
|
-
return value.apply(context, args);
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
return value;
|
|
922
|
-
},
|
|
923
|
-
});
|
|
924
|
-
// Set the proxied prototype
|
|
925
|
-
Object.setPrototypeOf(ProxiedBaseClass.prototype, proxiedPrototype);
|
|
926
|
-
usedBase = ProxiedBaseClass;
|
|
927
|
-
}
|
|
928
|
-
// Create the mixed class using the proxied base class
|
|
929
|
-
const mixedClass = mixinFunction(usedBase);
|
|
930
|
-
// Cache the result
|
|
931
|
-
mixinCache.set(baseClass, mixedClass);
|
|
932
|
-
return mixedClass;
|
|
933
|
-
},
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
const debugHooks = {
|
|
938
|
-
isDevtoolsEnabled: () => false,
|
|
939
|
-
registerEffect: () => { },
|
|
940
|
-
getTriggerChain: () => [],
|
|
941
|
-
captureStack: () => [],
|
|
942
|
-
captureLineage: () => new Error().stack,
|
|
943
|
-
formatStack: (stack) => [stack],
|
|
944
|
-
recordTriggerLink: () => { },
|
|
945
|
-
decorateError: () => { },
|
|
946
|
-
};
|
|
947
|
-
function setDebugHooks(hooks) {
|
|
948
|
-
Object.assign(debugHooks, hooks);
|
|
949
|
-
}
|
|
950
|
-
|
|
951
840
|
/******************************************************************************
|
|
952
841
|
Copyright (c) Microsoft Corporation.
|
|
953
842
|
|
|
@@ -1010,6 +899,29 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
1010
899
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
1011
900
|
};
|
|
1012
901
|
|
|
902
|
+
// Queue for hooks registered before the environment is ready (circular dependency fix)
|
|
903
|
+
const hooks = new Set();
|
|
904
|
+
const asyncHooks = {
|
|
905
|
+
addHook(hook) {
|
|
906
|
+
hooks.add(hook);
|
|
907
|
+
return () => hooks.delete(hook);
|
|
908
|
+
},
|
|
909
|
+
/**
|
|
910
|
+
* [Hack] Sanitize a promise (or value) to prevent context leaks.
|
|
911
|
+
* Default: Identity function.
|
|
912
|
+
* Browser: Uses Macrotask wrapping to break microtask chains.
|
|
913
|
+
*/
|
|
914
|
+
sanitizePromise(p) {
|
|
915
|
+
return p;
|
|
916
|
+
},
|
|
917
|
+
};
|
|
918
|
+
/**
|
|
919
|
+
* Register a hook that will be called whenever an asynchronous operation is initiated.
|
|
920
|
+
* The hook should return a restorer function which will be called just before the async callback runs.
|
|
921
|
+
* That restorer should in turn return an undoer function which will be called just after the async callback finishes.
|
|
922
|
+
*/
|
|
923
|
+
const asyncHook = (hook) => asyncHooks.addHook(hook);
|
|
924
|
+
|
|
1013
925
|
var _ZoneAggregator_zones;
|
|
1014
926
|
function isu(z) {
|
|
1015
927
|
return z;
|
|
@@ -1345,6 +1257,85 @@ function unlink(obj, reason) {
|
|
|
1345
1257
|
}
|
|
1346
1258
|
}
|
|
1347
1259
|
|
|
1260
|
+
function extractRawStack(error = new Error()) {
|
|
1261
|
+
if (typeof error === 'string')
|
|
1262
|
+
return error;
|
|
1263
|
+
if (error && typeof error === 'object' && 'stack' in error) {
|
|
1264
|
+
const stack = error.stack;
|
|
1265
|
+
return typeof stack === 'string' ? stack : undefined;
|
|
1266
|
+
}
|
|
1267
|
+
return undefined;
|
|
1268
|
+
}
|
|
1269
|
+
function trimStack(stack) {
|
|
1270
|
+
const raw = extractRawStack(stack);
|
|
1271
|
+
if (!raw)
|
|
1272
|
+
return [];
|
|
1273
|
+
const lines = raw
|
|
1274
|
+
.split('\n')
|
|
1275
|
+
.map((line) => line.trim())
|
|
1276
|
+
.filter(Boolean);
|
|
1277
|
+
if (lines[0]?.startsWith('Error'))
|
|
1278
|
+
lines.shift();
|
|
1279
|
+
while (lines[0] &&
|
|
1280
|
+
(lines[0].includes('captureLineage') ||
|
|
1281
|
+
lines[0].includes('captureDeferredLineage') ||
|
|
1282
|
+
lines[0].includes('debug-hooks.ts')))
|
|
1283
|
+
lines.shift();
|
|
1284
|
+
return lines;
|
|
1285
|
+
}
|
|
1286
|
+
function digestDeferredLineage(lineage) {
|
|
1287
|
+
if (lineage.segments)
|
|
1288
|
+
return lineage.segments;
|
|
1289
|
+
const segments = [];
|
|
1290
|
+
let effect = lineage.effect;
|
|
1291
|
+
let stack = trimStack(lineage.stack);
|
|
1292
|
+
if (!effect) {
|
|
1293
|
+
lineage.segments = [{ effectName: 'root', stack }];
|
|
1294
|
+
return lineage.segments;
|
|
1295
|
+
}
|
|
1296
|
+
while (effect) {
|
|
1297
|
+
const root = getRoot(effect);
|
|
1298
|
+
segments.push({
|
|
1299
|
+
effectName: root.name || 'anonymous',
|
|
1300
|
+
stack,
|
|
1301
|
+
});
|
|
1302
|
+
const node = getEffectNode(effect);
|
|
1303
|
+
effect = node.parent;
|
|
1304
|
+
stack = trimStack(node.creationStack);
|
|
1305
|
+
}
|
|
1306
|
+
if (stack.length)
|
|
1307
|
+
segments.push({ effectName: 'root', stack });
|
|
1308
|
+
lineage.segments = segments;
|
|
1309
|
+
return segments;
|
|
1310
|
+
}
|
|
1311
|
+
function formatDeferredLineage(lineage) {
|
|
1312
|
+
return digestDeferredLineage(lineage)
|
|
1313
|
+
.map((segment) => [`${segment.effectName}:`, ...segment.stack.map((line) => ` ${line}`)].join('\n'))
|
|
1314
|
+
.join('\n');
|
|
1315
|
+
}
|
|
1316
|
+
function captureDeferredLineage(effect = getActiveEffect(), stack = new Error()) {
|
|
1317
|
+
return {
|
|
1318
|
+
effect,
|
|
1319
|
+
stack,
|
|
1320
|
+
toString() {
|
|
1321
|
+
return formatDeferredLineage(this);
|
|
1322
|
+
},
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
const debugHooks = {
|
|
1326
|
+
isDevtoolsEnabled: () => false,
|
|
1327
|
+
registerEffect: () => { },
|
|
1328
|
+
getTriggerChain: () => [],
|
|
1329
|
+
captureStack: (error) => extractRawStack(error ?? new Error()),
|
|
1330
|
+
captureLineage: captureDeferredLineage,
|
|
1331
|
+
formatStack: (stack) => [stack],
|
|
1332
|
+
recordTriggerLink: () => { },
|
|
1333
|
+
decorateError: () => { },
|
|
1334
|
+
};
|
|
1335
|
+
function setDebugHooks(hooks) {
|
|
1336
|
+
Object.assign(debugHooks, hooks);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1348
1339
|
const effectMarker = {
|
|
1349
1340
|
enter: 'effect:enter',
|
|
1350
1341
|
leave: 'effect:leave',
|
|
@@ -1495,6 +1486,17 @@ class ReactiveError extends Error {
|
|
|
1495
1486
|
return this.debugInfo?.cause;
|
|
1496
1487
|
}
|
|
1497
1488
|
}
|
|
1489
|
+
function normalizeSchedulerMode(mode) {
|
|
1490
|
+
switch (mode) {
|
|
1491
|
+
case 'production':
|
|
1492
|
+
return 'raw';
|
|
1493
|
+
case 'development':
|
|
1494
|
+
return 'ordered';
|
|
1495
|
+
default:
|
|
1496
|
+
return mode;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
let schedulerMode = 'ordered';
|
|
1498
1500
|
// biome-ignore-start lint/correctness/noUnusedFunctionParameters: Interface declaration with empty defaults
|
|
1499
1501
|
/**
|
|
1500
1502
|
* Global options for the reactive system
|
|
@@ -1585,22 +1587,42 @@ const options = {
|
|
|
1585
1587
|
*/
|
|
1586
1588
|
onMemoizationDiscrepancy: undefined,
|
|
1587
1589
|
/**
|
|
1588
|
-
*
|
|
1590
|
+
* Effect scheduler mode.
|
|
1589
1591
|
*
|
|
1590
|
-
* - `'
|
|
1591
|
-
*
|
|
1592
|
-
*
|
|
1592
|
+
* - `'ordered'` (Default): maintains the causal effect graph so effects that are already
|
|
1593
|
+
* queued together can run in dependency order. It also preserves parent/child effect
|
|
1594
|
+
* lifecycle ordering and catches cycles eagerly when edges are created.
|
|
1593
1595
|
*
|
|
1594
|
-
* - `'
|
|
1595
|
-
*
|
|
1596
|
-
* basic path information. Good balance of debugging help with moderate overhead.
|
|
1596
|
+
* - `'raw'`: fastest FIFO scheduler. It does not maintain the effect graph.
|
|
1597
|
+
* Cycle detection is heuristic, using maxEffectChain execution counts.
|
|
1597
1598
|
*
|
|
1598
|
-
* - `'debug'`:
|
|
1599
|
-
*
|
|
1599
|
+
* - `'debug'`: ordered scheduling plus the most detailed graph diagnostics. Highest overhead,
|
|
1600
|
+
* best for investigation.
|
|
1600
1601
|
*
|
|
1601
|
-
* @default '
|
|
1602
|
+
* @default 'ordered'
|
|
1602
1603
|
*/
|
|
1603
|
-
|
|
1604
|
+
get scheduler() {
|
|
1605
|
+
return schedulerMode;
|
|
1606
|
+
},
|
|
1607
|
+
set scheduler(mode) {
|
|
1608
|
+
schedulerMode = mode;
|
|
1609
|
+
},
|
|
1610
|
+
/**
|
|
1611
|
+
* @deprecated Use `scheduler` instead.
|
|
1612
|
+
*
|
|
1613
|
+
* Backward-compatible alias for older names:
|
|
1614
|
+
* - `'production'` maps to `scheduler = 'raw'`
|
|
1615
|
+
* - `'development'` maps to `scheduler = 'ordered'`
|
|
1616
|
+
* - `'debug'` maps to `scheduler = 'debug'`
|
|
1617
|
+
*
|
|
1618
|
+
* The new names describe scheduler behavior rather than runtime environment.
|
|
1619
|
+
*/
|
|
1620
|
+
get cycleHandling() {
|
|
1621
|
+
return schedulerMode;
|
|
1622
|
+
},
|
|
1623
|
+
set cycleHandling(mode) {
|
|
1624
|
+
schedulerMode = normalizeSchedulerMode(mode);
|
|
1625
|
+
},
|
|
1604
1626
|
/**
|
|
1605
1627
|
* Internal flag used by memoization discrepancy detector to avoid counting calls in tests
|
|
1606
1628
|
* @warning Do not modify this flag manually, this flag is given by the engine
|
|
@@ -1690,14 +1712,14 @@ function optionCall(name, ...args) {
|
|
|
1690
1712
|
/** Production preset: no introspection, heuristic cycle detection, minimal overhead */
|
|
1691
1713
|
const prodPreset = {
|
|
1692
1714
|
maxEffectReaction: 'throw',
|
|
1693
|
-
|
|
1715
|
+
scheduler: 'raw',
|
|
1694
1716
|
introspection: null,
|
|
1695
1717
|
onMemoizationDiscrepancy: undefined,
|
|
1696
1718
|
};
|
|
1697
|
-
/** Development preset
|
|
1719
|
+
/** Development preset: introspection on, early cycle detection, warnings */
|
|
1698
1720
|
const devPreset = {
|
|
1699
1721
|
maxEffectReaction: 'warn',
|
|
1700
|
-
|
|
1722
|
+
scheduler: 'ordered',
|
|
1701
1723
|
introspection: {
|
|
1702
1724
|
gatherReasons: { lineages: 'touch' },
|
|
1703
1725
|
logErrors: true,
|
|
@@ -1709,7 +1731,7 @@ const devPreset = {
|
|
|
1709
1731
|
/** Debug preset: full diagnostics, throws on violations, rich lineage capture */
|
|
1710
1732
|
const debugPreset = {
|
|
1711
1733
|
maxEffectReaction: 'debug',
|
|
1712
|
-
|
|
1734
|
+
scheduler: 'debug',
|
|
1713
1735
|
introspection: {
|
|
1714
1736
|
gatherReasons: { lineages: 'both' },
|
|
1715
1737
|
logErrors: true,
|
|
@@ -1732,6 +1754,7 @@ function unwrap(obj) {
|
|
|
1732
1754
|
return obj;
|
|
1733
1755
|
return proxyToObject.get(obj) || obj;
|
|
1734
1756
|
}
|
|
1757
|
+
const toRaw = unwrap;
|
|
1735
1758
|
function isReactive(obj) {
|
|
1736
1759
|
return proxyToObject.has(obj);
|
|
1737
1760
|
}
|
|
@@ -1779,8 +1802,9 @@ function dependant(obj, prop = allProps) {
|
|
|
1779
1802
|
if (!currentActiveEffect || (typeof prop === 'symbol' && prop !== allProps && prop !== keysOf))
|
|
1780
1803
|
return;
|
|
1781
1804
|
const node = getEffectNode(currentActiveEffect);
|
|
1782
|
-
|
|
1783
|
-
|
|
1805
|
+
const hasDependencyHook = node.dependencyHook !== undefined;
|
|
1806
|
+
if (hasDependencyHook) {
|
|
1807
|
+
node.dependencyHook(obj, prop);
|
|
1784
1808
|
}
|
|
1785
1809
|
let objectWatchers = watchers.get(obj);
|
|
1786
1810
|
if (!objectWatchers) {
|
|
@@ -1802,25 +1826,25 @@ function dependant(obj, prop = allProps) {
|
|
|
1802
1826
|
effectToReactiveObjects.set(currentActiveEffect, new Set([obj]));
|
|
1803
1827
|
}
|
|
1804
1828
|
// Store dependency stack if introspection is enabled
|
|
1805
|
-
const
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
objStacks.set(prop, propStacks);
|
|
1818
|
-
}
|
|
1819
|
-
propStacks.set(currentActiveEffect, debugHooks.captureLineage());
|
|
1829
|
+
const lineageMode = options.introspection?.gatherReasons?.lineages;
|
|
1830
|
+
const shouldGatherDependencyLineage = lineageMode === 'dependency' || lineageMode === 'both';
|
|
1831
|
+
if (shouldGatherDependencyLineage) {
|
|
1832
|
+
let objStacks = dependencyStacks.get(obj);
|
|
1833
|
+
if (!objStacks) {
|
|
1834
|
+
objStacks = new Map();
|
|
1835
|
+
dependencyStacks.set(obj, objStacks);
|
|
1836
|
+
}
|
|
1837
|
+
let propStacks = objStacks.get(prop);
|
|
1838
|
+
if (!propStacks) {
|
|
1839
|
+
propStacks = new Map();
|
|
1840
|
+
objStacks.set(prop, propStacks);
|
|
1820
1841
|
}
|
|
1842
|
+
propStacks.set(currentActiveEffect, debugHooks.captureLineage());
|
|
1821
1843
|
}
|
|
1822
1844
|
}
|
|
1823
1845
|
|
|
1846
|
+
// Simple module to manage inert state without circular dependencies
|
|
1847
|
+
let inertDepth = 0;
|
|
1824
1848
|
/**
|
|
1825
1849
|
* Finds a cycle in a sequence of functions by looking for the first repetition
|
|
1826
1850
|
*/
|
|
@@ -1922,6 +1946,40 @@ let causesClosure = new WeakMap();
|
|
|
1922
1946
|
let consequencesClosure = new WeakMap();
|
|
1923
1947
|
// Batch re-entrance depth and broken state
|
|
1924
1948
|
let broken = false;
|
|
1949
|
+
/** True after an unrecoverable reactive failure until `reset()`. */
|
|
1950
|
+
function isReactiveBroken() {
|
|
1951
|
+
return broken;
|
|
1952
|
+
}
|
|
1953
|
+
const reactiveBrokenHandlers = new Set();
|
|
1954
|
+
const reactiveResetHandlers = new Set();
|
|
1955
|
+
function onReactiveBroken(handler) {
|
|
1956
|
+
reactiveBrokenHandlers.add(handler);
|
|
1957
|
+
return () => reactiveBrokenHandlers.delete(handler);
|
|
1958
|
+
}
|
|
1959
|
+
function onReactiveReset(handler) {
|
|
1960
|
+
reactiveResetHandlers.add(handler);
|
|
1961
|
+
return () => reactiveResetHandlers.delete(handler);
|
|
1962
|
+
}
|
|
1963
|
+
function notifyReactiveBroken(error) {
|
|
1964
|
+
for (const handler of Array.from(reactiveBrokenHandlers)) {
|
|
1965
|
+
try {
|
|
1966
|
+
handler(error);
|
|
1967
|
+
}
|
|
1968
|
+
catch (handlerError) {
|
|
1969
|
+
options.warn('[reactive] onReactiveBroken handler threw', handlerError);
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
function notifyReactiveReset() {
|
|
1974
|
+
for (const handler of Array.from(reactiveResetHandlers)) {
|
|
1975
|
+
try {
|
|
1976
|
+
handler();
|
|
1977
|
+
}
|
|
1978
|
+
catch (handlerError) {
|
|
1979
|
+
options.warn('[reactive] onReactiveReset handler threw', handlerError);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1925
1983
|
/**
|
|
1926
1984
|
* Gets or creates an IterableWeakSet for a closure map
|
|
1927
1985
|
*/
|
|
@@ -1940,7 +1998,7 @@ function getOrCreateClosure(closure, root) {
|
|
|
1940
1998
|
* @param targetRoot - Root function of the effect being triggered
|
|
1941
1999
|
*/
|
|
1942
2000
|
function addGraphEdge(callerRoot, targetRoot) {
|
|
1943
|
-
if (options.
|
|
2001
|
+
if (options.scheduler === 'raw')
|
|
1944
2002
|
return;
|
|
1945
2003
|
// Add to forward graph: callerRoot → targetRoot
|
|
1946
2004
|
const triggers = effectTriggers.get(callerRoot);
|
|
@@ -2055,7 +2113,7 @@ function hasPathExcluding(start, end, exclude) {
|
|
|
2055
2113
|
* @param effect - The effect being cleaned up
|
|
2056
2114
|
*/
|
|
2057
2115
|
function cleanupEffectFromGraph(effect) {
|
|
2058
|
-
if (options.
|
|
2116
|
+
if (options.scheduler === 'raw')
|
|
2059
2117
|
return;
|
|
2060
2118
|
const root = getRoot(effect);
|
|
2061
2119
|
// Get closures before removing direct edges (needed for propagation)
|
|
@@ -2164,7 +2222,7 @@ const executingStack = [];
|
|
|
2164
2222
|
* Called once when batch starts or when new effects are added
|
|
2165
2223
|
*/
|
|
2166
2224
|
function computeAllInDegrees(batch) {
|
|
2167
|
-
if (options.
|
|
2225
|
+
if (options.scheduler === 'raw')
|
|
2168
2226
|
return;
|
|
2169
2227
|
const activeEffect = getActiveEffect();
|
|
2170
2228
|
const activeRoot = activeEffect ? getRoot(activeEffect) : null;
|
|
@@ -2172,6 +2230,16 @@ function computeAllInDegrees(batch) {
|
|
|
2172
2230
|
batch.inDegrees.clear();
|
|
2173
2231
|
for (const [root] of batch.all) {
|
|
2174
2232
|
let inDegree = 0;
|
|
2233
|
+
// Make sure parents are executed before children: if parent is in batch, count it as a dependency
|
|
2234
|
+
const effect = batch.all.get(root);
|
|
2235
|
+
const parent = getEffectNode(effect).parent;
|
|
2236
|
+
const parentRoot = parent ? getRoot(parent) : undefined;
|
|
2237
|
+
if (parentRoot &&
|
|
2238
|
+
batch.all.has(parentRoot) &&
|
|
2239
|
+
parentRoot !== activeRoot &&
|
|
2240
|
+
parentRoot !== root) {
|
|
2241
|
+
inDegree++;
|
|
2242
|
+
}
|
|
2175
2243
|
const causes = causesClosure.get(root);
|
|
2176
2244
|
if (causes) {
|
|
2177
2245
|
for (const causeRoot of causes) {
|
|
@@ -2191,17 +2259,26 @@ function computeAllInDegrees(batch) {
|
|
|
2191
2259
|
function decrementInDegreesForExecuted(batch, executedRoot) {
|
|
2192
2260
|
// Get all effects that this executed effect triggers
|
|
2193
2261
|
const consequences = consequencesClosure.get(executedRoot);
|
|
2194
|
-
if (
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2262
|
+
if (consequences) {
|
|
2263
|
+
for (const consequenceRoot of consequences) {
|
|
2264
|
+
// Only update if it's still in the batch
|
|
2265
|
+
if (batch.all.has(consequenceRoot)) {
|
|
2266
|
+
const currentDegree = batch.inDegrees.get(consequenceRoot) ?? 0;
|
|
2267
|
+
if (currentDegree > 0) {
|
|
2268
|
+
batch.inDegrees.set(consequenceRoot, currentDegree - 1);
|
|
2269
|
+
}
|
|
2202
2270
|
}
|
|
2203
2271
|
}
|
|
2204
2272
|
}
|
|
2273
|
+
for (const [root, effect] of batch.all) {
|
|
2274
|
+
const parent = getEffectNode(effect).parent;
|
|
2275
|
+
if (!parent || getRoot(parent) !== executedRoot)
|
|
2276
|
+
continue;
|
|
2277
|
+
const currentDegree = batch.inDegrees.get(root) ?? 0;
|
|
2278
|
+
if (currentDegree > 0) {
|
|
2279
|
+
batch.inDegrees.set(root, currentDegree - 1);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2205
2282
|
}
|
|
2206
2283
|
/**
|
|
2207
2284
|
* Finds a path from startRoot to endRoot in the dependency graph
|
|
@@ -2340,15 +2417,15 @@ function addToBatch(effect, caller, immediate, reason) {
|
|
|
2340
2417
|
}
|
|
2341
2418
|
}
|
|
2342
2419
|
// 1. Add to batch first (needed for cycle detection)
|
|
2343
|
-
// TODO: Check if
|
|
2344
|
-
if (options.
|
|
2345
|
-
//
|
|
2420
|
+
// TODO: Check if this difference between raw and graph-backed scheduling is the right tradeoff.
|
|
2421
|
+
if (options.scheduler === 'raw') {
|
|
2422
|
+
// Raw mode: FIFO (delete and re-add to move to end)
|
|
2346
2423
|
if (currentBatch.all.has(root)) {
|
|
2347
2424
|
currentBatch.all.delete(root);
|
|
2348
2425
|
}
|
|
2349
2426
|
}
|
|
2350
2427
|
else {
|
|
2351
|
-
//
|
|
2428
|
+
// Graph-backed modes: skip if already queued — the existing entry will re-run
|
|
2352
2429
|
if (currentBatch.all.has(root)) {
|
|
2353
2430
|
return;
|
|
2354
2431
|
}
|
|
@@ -2357,7 +2434,7 @@ function addToBatch(effect, caller, immediate, reason) {
|
|
|
2357
2434
|
if (node.stopped)
|
|
2358
2435
|
return;
|
|
2359
2436
|
currentBatch.all.set(root, effect);
|
|
2360
|
-
if (caller && true && options.
|
|
2437
|
+
if (caller && true && options.scheduler !== 'raw') {
|
|
2361
2438
|
const callerRoot = getRoot(caller);
|
|
2362
2439
|
// const root = getRoot(effect) // Already have root
|
|
2363
2440
|
// Check for cycle BEFORE adding edge
|
|
@@ -2379,6 +2456,9 @@ function addToBatch(effect, caller, immediate, reason) {
|
|
|
2379
2456
|
}
|
|
2380
2457
|
addGraphEdge(callerRoot, root);
|
|
2381
2458
|
}
|
|
2459
|
+
if (options.scheduler !== 'raw') {
|
|
2460
|
+
computeAllInDegrees(currentBatch);
|
|
2461
|
+
}
|
|
2382
2462
|
}
|
|
2383
2463
|
/**
|
|
2384
2464
|
* Adds a cleanup function to be called when the current batch of effects completes
|
|
@@ -2475,7 +2555,7 @@ function executeNext(effectuatedRoots) {
|
|
|
2475
2555
|
// Find an effect with in-degree 0 using cached values
|
|
2476
2556
|
let nextEffect = null;
|
|
2477
2557
|
let nextRoot = null;
|
|
2478
|
-
if (options.
|
|
2558
|
+
if (options.scheduler === 'raw') {
|
|
2479
2559
|
// In flat mode, we just take the first effect in the queue (FIFO)
|
|
2480
2560
|
const first = currentBatch.all.entries().next().value;
|
|
2481
2561
|
if (first) {
|
|
@@ -2565,9 +2645,7 @@ function executeNext(effectuatedRoots) {
|
|
|
2565
2645
|
}
|
|
2566
2646
|
return result;
|
|
2567
2647
|
}
|
|
2568
|
-
|
|
2569
|
-
// These are all the effects triggered under `activeEffect` and all their sub-effects
|
|
2570
|
-
function batch(effect, immediate, caller) {
|
|
2648
|
+
function batch(effect, batchOptions) {
|
|
2571
2649
|
if (broken) {
|
|
2572
2650
|
throw new ReactiveError('[reactive] Reactive system is broken after an unrecoverable error. Call reset() to recover.', { code: ReactiveErrorCode.BrokenEffects });
|
|
2573
2651
|
}
|
|
@@ -2582,16 +2660,38 @@ function batch(effect, immediate, caller) {
|
|
|
2582
2660
|
throw new Error('Activation registry already exists');
|
|
2583
2661
|
optionCall('beginChain', roots);
|
|
2584
2662
|
}
|
|
2585
|
-
|
|
2586
|
-
const
|
|
2587
|
-
const callerToUse = caller ||
|
|
2663
|
+
const immediate = batchOptions?.immediate === true;
|
|
2664
|
+
const contained = batchOptions?.contained === true;
|
|
2665
|
+
const callerToUse = batchOptions?.caller || getActiveEffect();
|
|
2588
2666
|
// Optimization: If nested and NOT immediate, just join the existing batch
|
|
2589
|
-
if (!isNewBatch && !immediate) {
|
|
2667
|
+
if (!isNewBatch && !contained && !immediate) {
|
|
2590
2668
|
for (let i = 0; i < effect.length; i++) {
|
|
2591
2669
|
addToBatch(effect[i], callerToUse);
|
|
2592
2670
|
}
|
|
2593
2671
|
return;
|
|
2594
2672
|
}
|
|
2673
|
+
if (!isNewBatch && !contained && immediate) {
|
|
2674
|
+
const firstReturn = {};
|
|
2675
|
+
for (let i = 0; i < effect.length; i++) {
|
|
2676
|
+
executingStack.push(effect[i]);
|
|
2677
|
+
try {
|
|
2678
|
+
const node = getEffectNode(effect[i]);
|
|
2679
|
+
const reason = node.nextReason;
|
|
2680
|
+
if (node.cleanup) {
|
|
2681
|
+
const cleanup = node.cleanup;
|
|
2682
|
+
node.cleanup = undefined;
|
|
2683
|
+
cleanup(reason);
|
|
2684
|
+
}
|
|
2685
|
+
const rv = effect[i]();
|
|
2686
|
+
if (rv !== undefined && !('value' in firstReturn))
|
|
2687
|
+
firstReturn.value = rv;
|
|
2688
|
+
}
|
|
2689
|
+
finally {
|
|
2690
|
+
executingStack.pop();
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
return firstReturn.value;
|
|
2694
|
+
}
|
|
2595
2695
|
const currentBatch = {
|
|
2596
2696
|
all: new Map(),
|
|
2597
2697
|
inDegrees: new Map(),
|
|
@@ -2599,9 +2699,11 @@ function batch(effect, immediate, caller) {
|
|
|
2599
2699
|
};
|
|
2600
2700
|
batchStack.push(currentBatch);
|
|
2601
2701
|
let success = false;
|
|
2702
|
+
let failure;
|
|
2602
2703
|
try {
|
|
2603
2704
|
const effectuatedRoots = [];
|
|
2604
2705
|
const firstReturn = {};
|
|
2706
|
+
let initialError;
|
|
2605
2707
|
if (immediate) {
|
|
2606
2708
|
// Execute initial effects in providing order
|
|
2607
2709
|
for (let i = 0; i < effect.length; i++) {
|
|
@@ -2618,11 +2720,17 @@ function batch(effect, immediate, caller) {
|
|
|
2618
2720
|
if (rv !== undefined && !('value' in firstReturn))
|
|
2619
2721
|
firstReturn.value = rv;
|
|
2620
2722
|
}
|
|
2723
|
+
catch (error) {
|
|
2724
|
+
initialError = error;
|
|
2725
|
+
break;
|
|
2726
|
+
}
|
|
2621
2727
|
finally {
|
|
2622
2728
|
executingStack.pop();
|
|
2623
2729
|
currentBatch.all.delete(getRoot(effect[i]));
|
|
2624
2730
|
}
|
|
2625
2731
|
}
|
|
2732
|
+
if (initialError)
|
|
2733
|
+
throw initialError;
|
|
2626
2734
|
}
|
|
2627
2735
|
else {
|
|
2628
2736
|
// Add initial effects to batch and compute dependencies
|
|
@@ -2682,13 +2790,17 @@ function batch(effect, immediate, caller) {
|
|
|
2682
2790
|
return firstReturn.value;
|
|
2683
2791
|
}
|
|
2684
2792
|
catch (error) {
|
|
2793
|
+
failure = error;
|
|
2685
2794
|
if (batchStack.length === 1)
|
|
2686
2795
|
optionCall('error', '[reactive] Root batch failure before broken state:', error);
|
|
2687
2796
|
throw error;
|
|
2688
2797
|
}
|
|
2689
2798
|
finally {
|
|
2690
2799
|
if (!success && batchStack.length === 1) {
|
|
2800
|
+
const wasBroken = broken;
|
|
2691
2801
|
broken = true;
|
|
2802
|
+
if (!wasBroken)
|
|
2803
|
+
notifyReactiveBroken(failure);
|
|
2692
2804
|
}
|
|
2693
2805
|
batchStack.pop();
|
|
2694
2806
|
if (batchStack.length === 0) {
|
|
@@ -2704,6 +2816,7 @@ function batch(effect, immediate, caller) {
|
|
|
2704
2816
|
* All existing effects become orphaned and must be recreated.
|
|
2705
2817
|
*/
|
|
2706
2818
|
function reset() {
|
|
2819
|
+
const wasBroken = broken;
|
|
2707
2820
|
broken = false;
|
|
2708
2821
|
activationRegistry = undefined;
|
|
2709
2822
|
batchStack.length = 0;
|
|
@@ -2714,6 +2827,8 @@ function reset() {
|
|
|
2714
2827
|
resetRegistry();
|
|
2715
2828
|
resetTracking();
|
|
2716
2829
|
effectHistory.present.active = undefined;
|
|
2830
|
+
if (wasBroken)
|
|
2831
|
+
notifyReactiveReset();
|
|
2717
2832
|
}
|
|
2718
2833
|
// Inject batch function to allow atomic game loops in requestAnimationFrame/setTimeout/...
|
|
2719
2834
|
// Note: Automatic batching of async callbacks (setTimeout, Promise.then, etc.) is NOT implemented.
|
|
@@ -2730,7 +2845,7 @@ const atomic = decorator({
|
|
|
2730
2845
|
const atomicEffect = () => original.apply(this, args);
|
|
2731
2846
|
// Debug: helpful to have a name
|
|
2732
2847
|
Object.defineProperty(atomicEffect, 'name', { value: `atomic(${original.name})` });
|
|
2733
|
-
return batch(atomicEffect,
|
|
2848
|
+
return batch(atomicEffect, { immediate: true });
|
|
2734
2849
|
};
|
|
2735
2850
|
},
|
|
2736
2851
|
default(original) {
|
|
@@ -2738,7 +2853,7 @@ const atomic = decorator({
|
|
|
2738
2853
|
const atomicEffect = () => original.apply(this, args);
|
|
2739
2854
|
// Debug: helpful to have a name
|
|
2740
2855
|
Object.defineProperty(atomicEffect, 'name', { value: `atomic(${original.name})` });
|
|
2741
|
-
return batch(atomicEffect,
|
|
2856
|
+
return batch(atomicEffect, { immediate: true });
|
|
2742
2857
|
};
|
|
2743
2858
|
},
|
|
2744
2859
|
});
|
|
@@ -2777,7 +2892,7 @@ function captured(prev, fn) {
|
|
|
2777
2892
|
* ```
|
|
2778
2893
|
*/
|
|
2779
2894
|
function atom(fn) {
|
|
2780
|
-
return batch(fn,
|
|
2895
|
+
return batch(fn, { immediate: true });
|
|
2781
2896
|
}
|
|
2782
2897
|
const fr = new FinalizationRegistry((f) => f());
|
|
2783
2898
|
/**
|
|
@@ -3021,7 +3136,7 @@ const effect = captioned(named(effectMarker.leave, flavored(function effect(fn,
|
|
|
3021
3136
|
abortController = undefined;
|
|
3022
3137
|
}
|
|
3023
3138
|
};
|
|
3024
|
-
batch(runEffect,
|
|
3139
|
+
batch(runEffect, { immediate: true });
|
|
3025
3140
|
// Only ROOT effects are registered for GC cleanup and zone tracking
|
|
3026
3141
|
const isRootEffect = !parent;
|
|
3027
3142
|
const stopEffect = (reason) => {
|
|
@@ -3091,16 +3206,53 @@ const untracked = captioned(function untracked(fn) {
|
|
|
3091
3206
|
? externalReason.with(external, () => effectHistory.present.root(fn))
|
|
3092
3207
|
: effectHistory.present.root(fn);
|
|
3093
3208
|
});
|
|
3209
|
+
function runInert(fn) {
|
|
3210
|
+
// Increment the counter
|
|
3211
|
+
const originalDepth = inertDepth;
|
|
3212
|
+
inertDepth = originalDepth + 1;
|
|
3213
|
+
try {
|
|
3214
|
+
return fn();
|
|
3215
|
+
}
|
|
3216
|
+
finally {
|
|
3217
|
+
inertDepth = originalDepth;
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
function wrapInert(fn) {
|
|
3221
|
+
function inertEffect(...args) {
|
|
3222
|
+
return runInert(() => fn.apply(this, args));
|
|
3223
|
+
}
|
|
3224
|
+
Object.defineProperty(inertEffect, 'name', { value: `inert(${fn.name})` });
|
|
3225
|
+
return inertEffect;
|
|
3226
|
+
}
|
|
3227
|
+
/**
|
|
3228
|
+
* Executes a function with fast-path reads that bypass proxy overhead and dependency tracking.
|
|
3229
|
+
* Writes remain fully reactive. Uses a counter for safe nesting.
|
|
3230
|
+
* Can also decorate methods so the whole method body runs inertly.
|
|
3231
|
+
* @param fn - The function to execute
|
|
3232
|
+
*/
|
|
3233
|
+
const inert = decorator({
|
|
3234
|
+
method(original) {
|
|
3235
|
+
return wrapInert(original);
|
|
3236
|
+
},
|
|
3237
|
+
default(fn) {
|
|
3238
|
+
if (typeof fn !== 'function')
|
|
3239
|
+
throw new Error('inert() expects a function');
|
|
3240
|
+
return runInert(fn);
|
|
3241
|
+
},
|
|
3242
|
+
});
|
|
3094
3243
|
/**
|
|
3095
3244
|
* Executes a function from a virgin/root context - no parent effect, no tracking
|
|
3096
3245
|
* Creates completely independent effects that won't be cleaned up by any parent
|
|
3097
3246
|
* @param fn - The function to execute
|
|
3098
3247
|
*/
|
|
3099
3248
|
const root = captioned(function root(fn) {
|
|
3249
|
+
// When broken, `atomic`/`batch` throws immediately. DOM wrappers (e.g. Sursaut
|
|
3250
|
+
// `root\`event:…\``) still need to run listener code for inspection UI; skip batching.
|
|
3251
|
+
const runner = broken ? fn : atomic(fn);
|
|
3100
3252
|
const external = externalReasonFrom(fn);
|
|
3101
3253
|
return external
|
|
3102
|
-
? externalReason.with(external, () => effectHistory.root(
|
|
3103
|
-
: effectHistory.root(
|
|
3254
|
+
? externalReason.with(external, () => effectHistory.root(runner))
|
|
3255
|
+
: effectHistory.root(runner);
|
|
3104
3256
|
});
|
|
3105
3257
|
function biDi(received, get, set) {
|
|
3106
3258
|
if (typeof get !== 'function') {
|
|
@@ -3123,6 +3275,86 @@ function biDi(received, get, set) {
|
|
|
3123
3275
|
: () => { };
|
|
3124
3276
|
}
|
|
3125
3277
|
|
|
3278
|
+
/**
|
|
3279
|
+
* Creates a mixin that can be used both as a class (extends) and as a function (mixin)
|
|
3280
|
+
*
|
|
3281
|
+
* This function supports:
|
|
3282
|
+
* - Using mixins as base classes: `class MyClass extends MyMixin`
|
|
3283
|
+
* - Using mixins as functions: `class MyClass extends MyMixin(SomeBase)`
|
|
3284
|
+
* - Composing mixins: `const Composed = MixinA(MixinB)`
|
|
3285
|
+
* - Type-safe property inference for all patterns
|
|
3286
|
+
*
|
|
3287
|
+
* @param mixinFunction - The function that creates the mixin
|
|
3288
|
+
* @param unwrapFunction - Optional function to unwrap reactive objects for method calls
|
|
3289
|
+
* @returns A mixin that can be used both as a class and as a function
|
|
3290
|
+
*/
|
|
3291
|
+
function mixin(mixinFunction, unwrapFunction) {
|
|
3292
|
+
/**
|
|
3293
|
+
* Cache for mixin results to ensure the same base class always returns the same mixed class
|
|
3294
|
+
*/
|
|
3295
|
+
const mixinCache = new WeakMap();
|
|
3296
|
+
// Apply the mixin to Object as the base class
|
|
3297
|
+
const MixedBase = mixinFunction(Object);
|
|
3298
|
+
mixinCache.set(Object, MixedBase);
|
|
3299
|
+
// Create the proxy that handles both constructor and function calls
|
|
3300
|
+
return new Proxy(MixedBase, {
|
|
3301
|
+
// Handle `MixinClass(SomeBase)` - use as mixin function
|
|
3302
|
+
apply(_target, _thisArg, args) {
|
|
3303
|
+
if (args.length === 0) {
|
|
3304
|
+
throw new Error('Mixin requires a base class');
|
|
3305
|
+
}
|
|
3306
|
+
const baseClass = args[0];
|
|
3307
|
+
if (typeof baseClass !== 'function') {
|
|
3308
|
+
throw new Error('Mixin requires a constructor function');
|
|
3309
|
+
}
|
|
3310
|
+
// Check if it's a valid constructor or a mixin
|
|
3311
|
+
if (!isConstructor(baseClass) &&
|
|
3312
|
+
!(baseClass && typeof baseClass === 'function' && baseClass.prototype)) {
|
|
3313
|
+
throw new Error('Mixin requires a valid constructor');
|
|
3314
|
+
}
|
|
3315
|
+
// Check cache first
|
|
3316
|
+
const cached = mixinCache.get(baseClass);
|
|
3317
|
+
if (cached) {
|
|
3318
|
+
return cached;
|
|
3319
|
+
}
|
|
3320
|
+
let usedBase = baseClass;
|
|
3321
|
+
if (unwrapFunction) {
|
|
3322
|
+
// Create a proxied base class that handles method unwrapping
|
|
3323
|
+
const ProxiedBaseClass = class extends baseClass {
|
|
3324
|
+
};
|
|
3325
|
+
// Proxy the prototype methods to handle unwrapping
|
|
3326
|
+
const originalPrototype = baseClass.prototype;
|
|
3327
|
+
const proxiedPrototype = new Proxy(originalPrototype, {
|
|
3328
|
+
get(target, prop, receiver) {
|
|
3329
|
+
const value = FoolProof.get(target, prop, receiver);
|
|
3330
|
+
// Only wrap methods that are likely to access private fields
|
|
3331
|
+
// Skip symbols and special properties that the reactive system needs
|
|
3332
|
+
if (typeof value === 'function' &&
|
|
3333
|
+
typeof prop === 'string' &&
|
|
3334
|
+
!['constructor', 'toString', 'valueOf'].includes(prop)) {
|
|
3335
|
+
// Return a wrapped version that uses unwrapped context
|
|
3336
|
+
return function (...args) {
|
|
3337
|
+
// Use the unwrapping function if provided, otherwise use this
|
|
3338
|
+
const context = unwrapFunction(this);
|
|
3339
|
+
return value.apply(context, args);
|
|
3340
|
+
};
|
|
3341
|
+
}
|
|
3342
|
+
return value;
|
|
3343
|
+
},
|
|
3344
|
+
});
|
|
3345
|
+
// Set the proxied prototype
|
|
3346
|
+
Object.setPrototypeOf(ProxiedBaseClass.prototype, proxiedPrototype);
|
|
3347
|
+
usedBase = ProxiedBaseClass;
|
|
3348
|
+
}
|
|
3349
|
+
// Create the mixed class using the proxied base class
|
|
3350
|
+
const mixedClass = mixinFunction(usedBase);
|
|
3351
|
+
// Cache the result
|
|
3352
|
+
mixinCache.set(baseClass, mixedClass);
|
|
3353
|
+
return mixedClass;
|
|
3354
|
+
},
|
|
3355
|
+
});
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3126
3358
|
// Track which objects contain which other objects (back-references)
|
|
3127
3359
|
const objectParents = new WeakMap();
|
|
3128
3360
|
// Track which objects have deep watchers
|
|
@@ -3252,7 +3484,7 @@ function getState(obj) {
|
|
|
3252
3484
|
}
|
|
3253
3485
|
return state;
|
|
3254
3486
|
}
|
|
3255
|
-
function collectEffects(obj, evolution, effects, objectWatchers, ...keyChains) {
|
|
3487
|
+
function collectEffects(obj, evolution, effects, objectWatchers, collectDependencyStack, ...keyChains) {
|
|
3256
3488
|
const sourceEffect = getActiveEffect();
|
|
3257
3489
|
for (const keys of keyChains)
|
|
3258
3490
|
for (const key of keys) {
|
|
@@ -3268,7 +3500,7 @@ function collectEffects(obj, evolution, effects, objectWatchers, ...keyChains) {
|
|
|
3268
3500
|
continue;
|
|
3269
3501
|
}
|
|
3270
3502
|
if (!effects.has(effect)) {
|
|
3271
|
-
effects.set(effect, getDependencyStack(effect, obj, key));
|
|
3503
|
+
effects.set(effect, collectDependencyStack ? getDependencyStack(effect, obj, key) : undefined);
|
|
3272
3504
|
if (!hasBatched(effect))
|
|
3273
3505
|
recordActivation(effect, obj, evolution, key);
|
|
3274
3506
|
}
|
|
@@ -3301,17 +3533,18 @@ function touched(obj, evolution, props) {
|
|
|
3301
3533
|
const effects = new Map();
|
|
3302
3534
|
const structural = !['set', 'invalidate'].includes(evolution.type);
|
|
3303
3535
|
const broad = structural ? [allProps, keysOf] : [allProps];
|
|
3536
|
+
const gatherReasons = options.introspection?.gatherReasons;
|
|
3537
|
+
const lineageConfig = gatherReasons?.lineages;
|
|
3538
|
+
const collectDependencyStack = lineageConfig === 'dependency' || lineageConfig === 'both';
|
|
3304
3539
|
if (props)
|
|
3305
|
-
collectEffects(obj, evolution, effects, objectWatchers, broad, props);
|
|
3540
|
+
collectEffects(obj, evolution, effects, objectWatchers, collectDependencyStack, broad, props);
|
|
3306
3541
|
else
|
|
3307
|
-
collectEffects(obj, evolution, effects, objectWatchers, objectWatchers.keys());
|
|
3542
|
+
collectEffects(obj, evolution, effects, objectWatchers, collectDependencyStack, objectWatchers.keys());
|
|
3308
3543
|
const triggers = Array.from(effects.keys());
|
|
3309
3544
|
const sourceEffect = getActiveEffect();
|
|
3310
3545
|
optionCall('touched', obj, evolution, props, triggers);
|
|
3311
3546
|
// Store pending triggers for CleanupReason before batching
|
|
3312
|
-
if (
|
|
3313
|
-
const gatherReasons = options.introspection.gatherReasons;
|
|
3314
|
-
const lineageConfig = gatherReasons.lineages;
|
|
3547
|
+
if (gatherReasons && effects.size > 0) {
|
|
3315
3548
|
let touchLineage;
|
|
3316
3549
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
3317
3550
|
touchLineage = debugHooks.captureLineage();
|
|
@@ -3328,7 +3561,7 @@ function touched(obj, evolution, props) {
|
|
|
3328
3561
|
});
|
|
3329
3562
|
}
|
|
3330
3563
|
}
|
|
3331
|
-
batch(triggers,
|
|
3564
|
+
batch(triggers, { caller: sourceEffect });
|
|
3332
3565
|
}
|
|
3333
3566
|
// Bubble up changes if this object has deep watchers
|
|
3334
3567
|
if (objectsWithDeepWatchers.has(obj)) {
|
|
@@ -3352,6 +3585,7 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
3352
3585
|
const gather = options.introspection?.gatherReasons;
|
|
3353
3586
|
if (gather) {
|
|
3354
3587
|
const lineageConfig = gather.lineages;
|
|
3588
|
+
let touchLineage;
|
|
3355
3589
|
for (const effect of deps) {
|
|
3356
3590
|
const node = getEffectNode(effect);
|
|
3357
3591
|
if (!node.isOpaque)
|
|
@@ -3363,10 +3597,9 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
3363
3597
|
}
|
|
3364
3598
|
effects.add(effect);
|
|
3365
3599
|
if (gather) {
|
|
3366
|
-
let touchLineage;
|
|
3367
3600
|
let dependencyStack;
|
|
3368
3601
|
if (lineageConfig === 'touch' || lineageConfig === 'both') {
|
|
3369
|
-
touchLineage = debugHooks.captureLineage();
|
|
3602
|
+
touchLineage ?? (touchLineage = debugHooks.captureLineage());
|
|
3370
3603
|
}
|
|
3371
3604
|
if (lineageConfig === 'dependency' || lineageConfig === 'both') {
|
|
3372
3605
|
dependencyStack = getDependencyStack(effect, obj, prop);
|
|
@@ -3402,7 +3635,7 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
3402
3635
|
}
|
|
3403
3636
|
if (effects.size > 0) {
|
|
3404
3637
|
optionCall('touched', obj, evolution, [prop], Array.from(effects));
|
|
3405
|
-
batch(Array.from(effects),
|
|
3638
|
+
batch(Array.from(effects), { caller: sourceEffect });
|
|
3406
3639
|
}
|
|
3407
3640
|
}
|
|
3408
3641
|
|
|
@@ -3449,6 +3682,8 @@ function nonReactive(...obj) {
|
|
|
3449
3682
|
}
|
|
3450
3683
|
return obj[0];
|
|
3451
3684
|
}
|
|
3685
|
+
const markRaw = nonReactive;
|
|
3686
|
+
const markRawProps = addUnreactiveProps;
|
|
3452
3687
|
function nonReactiveClass(...cls) {
|
|
3453
3688
|
for (const c of cls)
|
|
3454
3689
|
if (c)
|
|
@@ -3666,7 +3901,7 @@ function dispatchNotifications(notifications) {
|
|
|
3666
3901
|
const originWatchers = watchers.get(origin.obj);
|
|
3667
3902
|
if (originWatchers) {
|
|
3668
3903
|
const originEffects = new Map();
|
|
3669
|
-
collectEffects(origin.obj, { type: 'set', prop: origin.prop }, originEffects, originWatchers, [allProps], [origin.prop]);
|
|
3904
|
+
collectEffects(origin.obj, { type: 'set', prop: origin.prop }, originEffects, originWatchers, false, [allProps], [origin.prop]);
|
|
3670
3905
|
allowedEffects = new Set(originEffects.keys());
|
|
3671
3906
|
}
|
|
3672
3907
|
// If no allowed effects, skip all notifications (no one should be notified)
|
|
@@ -3685,7 +3920,7 @@ function dispatchNotifications(notifications) {
|
|
|
3685
3920
|
if (objectWatchers) {
|
|
3686
3921
|
currentEffects = new Map();
|
|
3687
3922
|
const broad = evolution.type !== 'set' ? [allProps, keysOf] : [allProps];
|
|
3688
|
-
collectEffects(obj, evolution, currentEffects, objectWatchers, broad, propsArray);
|
|
3923
|
+
collectEffects(obj, evolution, currentEffects, objectWatchers, false, broad, propsArray);
|
|
3689
3924
|
// Filter effects by ancestor chain if origin exists
|
|
3690
3925
|
// Include effects that either directly depend on origin or have an ancestor that does
|
|
3691
3926
|
if (origin && allowedEffects) {
|
|
@@ -3747,12 +3982,34 @@ const metaProtos = new WeakMap();
|
|
|
3747
3982
|
const wrapProtos = new WeakMap();
|
|
3748
3983
|
const arrayLengths = new WeakMap();
|
|
3749
3984
|
const hasReentry = new Set();
|
|
3985
|
+
const accessAnalysisCache = new WeakMap();
|
|
3986
|
+
const readonlyObjectToProxy = new WeakMap();
|
|
3987
|
+
const shallowObjectToProxy = new WeakMap();
|
|
3988
|
+
const readonlyMutators = new Set([
|
|
3989
|
+
'copyWithin',
|
|
3990
|
+
'fill',
|
|
3991
|
+
'pop',
|
|
3992
|
+
'push',
|
|
3993
|
+
'reverse',
|
|
3994
|
+
'shift',
|
|
3995
|
+
'sort',
|
|
3996
|
+
'splice',
|
|
3997
|
+
'unshift',
|
|
3998
|
+
'add',
|
|
3999
|
+
'clear',
|
|
4000
|
+
'delete',
|
|
4001
|
+
'set',
|
|
4002
|
+
]);
|
|
3750
4003
|
// Sub-proxy registration for custom reactive behaviors
|
|
3751
4004
|
const subsRegister = new WeakMap();
|
|
3752
4005
|
// Internal untracked flag for setter/getter operations - only used when testing oldValue while setting a value
|
|
3753
4006
|
// TODO: `touched` trigger also compares to old value and should use the internalUntracked flag
|
|
3754
4007
|
let internalUntracked = false;
|
|
3755
4008
|
function wrapReactiveValue(obj, prop, value) {
|
|
4009
|
+
// Optional fast-path for inert reads - skips reactive wrapping
|
|
4010
|
+
// Disabled by default for safety, can be enabled for performance-critical read-only contexts
|
|
4011
|
+
if (inertDepth > 0)
|
|
4012
|
+
return value;
|
|
3756
4013
|
if (!isReactive(value) && typeof value === 'object' && value !== null) {
|
|
3757
4014
|
const reactiveValue = reactiveObject(value);
|
|
3758
4015
|
// Only create back-references if this object needs them
|
|
@@ -3763,6 +4020,56 @@ function wrapReactiveValue(obj, prop, value) {
|
|
|
3763
4020
|
}
|
|
3764
4021
|
return value;
|
|
3765
4022
|
}
|
|
4023
|
+
function computeAccessAnalysis(obj, prop, receiver) {
|
|
4024
|
+
const proto = Object.getPrototypeOf(obj);
|
|
4025
|
+
const isOwnProp = Object.hasOwn(obj, prop);
|
|
4026
|
+
const shouldIgnoreAccessor = options.ignoreAccessors &&
|
|
4027
|
+
isOwnProp &&
|
|
4028
|
+
proto !== null &&
|
|
4029
|
+
(isOwnAccessor(receiver, prop) || isOwnAccessor(obj, prop));
|
|
4030
|
+
let hasProp = isOwnProp;
|
|
4031
|
+
let owner = isOwnProp ? obj : undefined;
|
|
4032
|
+
if (!isOwnProp) {
|
|
4033
|
+
let raw = proto;
|
|
4034
|
+
while (raw && raw !== Object.prototype) {
|
|
4035
|
+
if (Object.hasOwn(raw, prop)) {
|
|
4036
|
+
hasProp = true;
|
|
4037
|
+
owner = raw;
|
|
4038
|
+
break;
|
|
4039
|
+
}
|
|
4040
|
+
raw = Object.getPrototypeOf(raw);
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
return {
|
|
4044
|
+
hasProp,
|
|
4045
|
+
owner,
|
|
4046
|
+
isInheritedAccess: hasProp && !isOwnProp,
|
|
4047
|
+
shouldIgnoreAccessor,
|
|
4048
|
+
ignoreAccessors: options.ignoreAccessors,
|
|
4049
|
+
instanceMembers: options.instanceMembers,
|
|
4050
|
+
};
|
|
4051
|
+
}
|
|
4052
|
+
function analyzeAccess(obj, prop, receiver) {
|
|
4053
|
+
const proto = Object.getPrototypeOf(obj);
|
|
4054
|
+
if (Object.hasOwn(obj, prop))
|
|
4055
|
+
return computeAccessAnalysis(obj, prop, receiver);
|
|
4056
|
+
if (proto === null || Array.isArray(obj))
|
|
4057
|
+
return computeAccessAnalysis(obj, prop, receiver);
|
|
4058
|
+
let propCache = accessAnalysisCache.get(proto);
|
|
4059
|
+
if (!propCache) {
|
|
4060
|
+
propCache = new Map();
|
|
4061
|
+
accessAnalysisCache.set(proto, propCache);
|
|
4062
|
+
}
|
|
4063
|
+
const cached = propCache.get(prop);
|
|
4064
|
+
if (cached &&
|
|
4065
|
+
cached.ignoreAccessors === options.ignoreAccessors &&
|
|
4066
|
+
cached.instanceMembers === options.instanceMembers)
|
|
4067
|
+
return cached;
|
|
4068
|
+
const analysis = computeAccessAnalysis(obj, prop, receiver);
|
|
4069
|
+
if (analysis.hasProp)
|
|
4070
|
+
propCache.set(prop, analysis);
|
|
4071
|
+
return analysis;
|
|
4072
|
+
}
|
|
3766
4073
|
const reactiveHandlers = {
|
|
3767
4074
|
[Symbol.toStringTag]: 'MutTs Reactive',
|
|
3768
4075
|
get(obj, prop, receiver) {
|
|
@@ -3790,36 +4097,29 @@ const reactiveHandlers = {
|
|
|
3790
4097
|
// Symbols: fast-path — no reactivity tracking
|
|
3791
4098
|
if (typeof prop === 'symbol' || prop === 'constructor' || isUnreactiveProp(obj, prop))
|
|
3792
4099
|
return FoolProof.get(obj, prop, receiver);
|
|
3793
|
-
|
|
3794
|
-
|
|
4100
|
+
const subProxy = subsRegister.get(obj);
|
|
4101
|
+
if (inertDepth > 0) {
|
|
4102
|
+
const value = (subProxy?.get || FoolProof.get)(obj, prop, receiver);
|
|
3795
4103
|
return wrapReactiveValue(obj, prop, value);
|
|
3796
4104
|
}
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
let hasProp = isOwnProp;
|
|
3810
|
-
let owner = isOwnProp ? obj : undefined;
|
|
3811
|
-
if (!isOwnProp) {
|
|
3812
|
-
let raw = Object.getPrototypeOf(obj);
|
|
3813
|
-
while (raw && raw !== Object.prototype) {
|
|
3814
|
-
if (Object.hasOwn(raw, prop)) {
|
|
3815
|
-
hasProp = true;
|
|
3816
|
-
owner = raw;
|
|
3817
|
-
break;
|
|
4105
|
+
const activeEffect = getActiveEffect();
|
|
4106
|
+
if (!activeEffect) {
|
|
4107
|
+
const value = (subProxy?.get || FoolProof.get)(obj, prop, receiver);
|
|
4108
|
+
return wrapReactiveValue(obj, prop, value);
|
|
4109
|
+
}
|
|
4110
|
+
if (!subProxy && !Array.isArray(obj)) {
|
|
4111
|
+
const proto = Object.getPrototypeOf(obj);
|
|
4112
|
+
if (proto === Object.prototype || proto === null) {
|
|
4113
|
+
const ownDesc = Object.getOwnPropertyDescriptor(obj, prop);
|
|
4114
|
+
if (ownDesc && 'value' in ownDesc) {
|
|
4115
|
+
dependant(obj, prop);
|
|
4116
|
+
return wrapReactiveValue(obj, prop, ownDesc.value);
|
|
3818
4117
|
}
|
|
3819
|
-
raw = Object.getPrototypeOf(raw);
|
|
3820
4118
|
}
|
|
3821
4119
|
}
|
|
3822
|
-
|
|
4120
|
+
// Check if property exists using a trap-free walk to avoid triggering
|
|
4121
|
+
// the has-trap cascade on prototype chains of reactive proxies.
|
|
4122
|
+
const { hasProp, owner, isInheritedAccess, shouldIgnoreAccessor } = analyzeAccess(obj, prop, receiver);
|
|
3823
4123
|
// Depend if...
|
|
3824
4124
|
if (!hasProp ||
|
|
3825
4125
|
(!(options.instanceMembers && isInheritedAccess && obj instanceof Object) &&
|
|
@@ -3832,7 +4132,7 @@ const reactiveHandlers = {
|
|
|
3832
4132
|
}
|
|
3833
4133
|
// For arrays, use FoolProof.get (Indexer path) for numeric index reactivity.
|
|
3834
4134
|
// For all other objects, inline Reflect.get directly (skips 3 function calls).
|
|
3835
|
-
const value = (
|
|
4135
|
+
const value = (subProxy?.get || FoolProof.get)(obj, prop, receiver);
|
|
3836
4136
|
return wrapReactiveValue(obj, prop, value);
|
|
3837
4137
|
},
|
|
3838
4138
|
set(obj, prop, value, receiver) {
|
|
@@ -3906,6 +4206,11 @@ const reactiveHandlers = {
|
|
|
3906
4206
|
cycle: [], // We don't have the full cycle here, but we know it involves obj
|
|
3907
4207
|
});
|
|
3908
4208
|
hasReentry.add(obj);
|
|
4209
|
+
if (inertDepth > 0) {
|
|
4210
|
+
const rv = (subsRegister.get(obj)?.has || Reflect.has)(obj, prop);
|
|
4211
|
+
hasReentry.delete(obj);
|
|
4212
|
+
return rv;
|
|
4213
|
+
}
|
|
3909
4214
|
if (!internalUntracked && !isUnreactiveProp(obj, prop))
|
|
3910
4215
|
dependant(obj, prop);
|
|
3911
4216
|
const rv = (subsRegister.get(obj)?.has || Reflect.has)(obj, prop);
|
|
@@ -3914,7 +4219,7 @@ const reactiveHandlers = {
|
|
|
3914
4219
|
},
|
|
3915
4220
|
deleteProperty(obj, prop) {
|
|
3916
4221
|
if (!Object.hasOwn(obj, prop))
|
|
3917
|
-
return
|
|
4222
|
+
return true;
|
|
3918
4223
|
const oldVal = obj[prop];
|
|
3919
4224
|
// Remove back-references if this object has deep watchers
|
|
3920
4225
|
if (objectsWithDeepWatchers.has(obj) && typeof oldVal === 'object' && oldVal !== null) {
|
|
@@ -3929,6 +4234,9 @@ const reactiveHandlers = {
|
|
|
3929
4234
|
return true;
|
|
3930
4235
|
},
|
|
3931
4236
|
ownKeys(obj) {
|
|
4237
|
+
if (inertDepth > 0) {
|
|
4238
|
+
return subsRegister.get(obj)?.ownKeys?.(obj) || Reflect.ownKeys(obj);
|
|
4239
|
+
}
|
|
3932
4240
|
dependant(obj, keysOf);
|
|
3933
4241
|
return subsRegister.get(obj)?.ownKeys?.(obj) || Reflect.ownKeys(obj);
|
|
3934
4242
|
},
|
|
@@ -3937,6 +4245,98 @@ const reactiveHandlers = {
|
|
|
3937
4245
|
Reflect.getOwnPropertyDescriptor(obj, prop));
|
|
3938
4246
|
},
|
|
3939
4247
|
};
|
|
4248
|
+
function readonlyError(prop) {
|
|
4249
|
+
return new ReactiveError(`[reactive] Cannot mutate readonly reactive property '${String(prop)}'`, {
|
|
4250
|
+
code: ReactiveErrorCode.WriteInComputed,
|
|
4251
|
+
});
|
|
4252
|
+
}
|
|
4253
|
+
function readonlyValue(value) {
|
|
4254
|
+
if (!value || typeof value !== 'object')
|
|
4255
|
+
return value;
|
|
4256
|
+
return readonlyReactive(value);
|
|
4257
|
+
}
|
|
4258
|
+
const shallowReactiveHandlers = {
|
|
4259
|
+
get(obj, prop, receiver) {
|
|
4260
|
+
if (typeof prop === 'symbol' || prop === 'constructor' || isUnreactiveProp(obj, prop))
|
|
4261
|
+
return Reflect.get(obj, prop, receiver);
|
|
4262
|
+
if (getActiveEffect())
|
|
4263
|
+
dependant(obj, prop);
|
|
4264
|
+
return Reflect.get(obj, prop, receiver);
|
|
4265
|
+
},
|
|
4266
|
+
set(obj, prop, value, receiver) {
|
|
4267
|
+
const unwrapped = unwrap(receiver);
|
|
4268
|
+
if (obj !== unwrapped)
|
|
4269
|
+
return Object.defineProperty(unwrapped, prop, {
|
|
4270
|
+
value,
|
|
4271
|
+
configurable: true,
|
|
4272
|
+
writable: true,
|
|
4273
|
+
enumerable: true,
|
|
4274
|
+
});
|
|
4275
|
+
if (isUnreactiveProp(obj, prop))
|
|
4276
|
+
return FoolProof.set(obj, prop, value, receiver);
|
|
4277
|
+
const hadProperty = Reflect.has(obj, prop);
|
|
4278
|
+
const oldVal = hadProperty ? Reflect.get(obj, prop, receiver) : absent;
|
|
4279
|
+
const newValue = unwrap(value);
|
|
4280
|
+
if (oldVal !== newValue && FoolProof.set(obj, prop, newValue, receiver)) {
|
|
4281
|
+
touched1(obj, { type: hadProperty ? 'set' : 'add', prop }, prop);
|
|
4282
|
+
}
|
|
4283
|
+
return true;
|
|
4284
|
+
},
|
|
4285
|
+
has(obj, prop) {
|
|
4286
|
+
return reactiveHandlers.has(obj, prop);
|
|
4287
|
+
},
|
|
4288
|
+
deleteProperty(obj, prop) {
|
|
4289
|
+
if (!Object.hasOwn(obj, prop))
|
|
4290
|
+
return true;
|
|
4291
|
+
delete obj[prop];
|
|
4292
|
+
touched1(obj, { type: 'del', prop }, prop);
|
|
4293
|
+
return true;
|
|
4294
|
+
},
|
|
4295
|
+
ownKeys(obj) {
|
|
4296
|
+
return reactiveHandlers.ownKeys(obj);
|
|
4297
|
+
},
|
|
4298
|
+
getOwnPropertyDescriptor(obj, prop) {
|
|
4299
|
+
return Reflect.getOwnPropertyDescriptor(obj, prop);
|
|
4300
|
+
},
|
|
4301
|
+
};
|
|
4302
|
+
const readonlyReactiveHandlers = {
|
|
4303
|
+
get(obj, prop, receiver) {
|
|
4304
|
+
if (readonlyMutators.has(prop)) {
|
|
4305
|
+
return () => {
|
|
4306
|
+
throw readonlyError(prop);
|
|
4307
|
+
};
|
|
4308
|
+
}
|
|
4309
|
+
const reactiveTarget = reactiveObject(obj);
|
|
4310
|
+
const value = FoolProof.get(reactiveTarget, prop, receiver);
|
|
4311
|
+
if (typeof value === 'function') {
|
|
4312
|
+
return (...args) => readonlyValue(value.apply(reactiveTarget, args));
|
|
4313
|
+
}
|
|
4314
|
+
return readonlyValue(value);
|
|
4315
|
+
},
|
|
4316
|
+
set(_obj, prop) {
|
|
4317
|
+
throw readonlyError(prop);
|
|
4318
|
+
},
|
|
4319
|
+
deleteProperty(_obj, prop) {
|
|
4320
|
+
throw readonlyError(prop);
|
|
4321
|
+
},
|
|
4322
|
+
defineProperty(_obj, prop) {
|
|
4323
|
+
throw readonlyError(prop);
|
|
4324
|
+
},
|
|
4325
|
+
setPrototypeOf() {
|
|
4326
|
+
throw readonlyError('[[Prototype]]');
|
|
4327
|
+
},
|
|
4328
|
+
has(obj, prop) {
|
|
4329
|
+
const reactiveTarget = reactiveObject(obj);
|
|
4330
|
+
return Reflect.has(reactiveTarget, prop);
|
|
4331
|
+
},
|
|
4332
|
+
ownKeys(obj) {
|
|
4333
|
+
const reactiveTarget = reactiveObject(obj);
|
|
4334
|
+
return Reflect.ownKeys(reactiveTarget);
|
|
4335
|
+
},
|
|
4336
|
+
getOwnPropertyDescriptor(obj, prop) {
|
|
4337
|
+
return Reflect.getOwnPropertyDescriptor(obj, prop);
|
|
4338
|
+
},
|
|
4339
|
+
};
|
|
3940
4340
|
const reactiveClasses = new WeakSet();
|
|
3941
4341
|
// Create the ReactiveBase mixin
|
|
3942
4342
|
/**
|
|
@@ -3978,6 +4378,34 @@ function reactiveObject(anyTarget, subProxy) {
|
|
|
3978
4378
|
storeProxyRelationship(target, proxy);
|
|
3979
4379
|
return proxy;
|
|
3980
4380
|
}
|
|
4381
|
+
function shallowReactiveObject(anyTarget) {
|
|
4382
|
+
if (!anyTarget || typeof anyTarget !== 'object')
|
|
4383
|
+
return anyTarget;
|
|
4384
|
+
const target = unwrap(anyTarget);
|
|
4385
|
+
if (isNonReactive(target))
|
|
4386
|
+
return target;
|
|
4387
|
+
const existing = shallowObjectToProxy.get(target);
|
|
4388
|
+
if (existing)
|
|
4389
|
+
return existing;
|
|
4390
|
+
const proxy = new Proxy(target, shallowReactiveHandlers);
|
|
4391
|
+
shallowObjectToProxy.set(target, proxy);
|
|
4392
|
+
proxyToObject.set(proxy, target);
|
|
4393
|
+
return proxy;
|
|
4394
|
+
}
|
|
4395
|
+
function readonlyReactiveObject(anyTarget) {
|
|
4396
|
+
if (!anyTarget || typeof anyTarget !== 'object')
|
|
4397
|
+
return anyTarget;
|
|
4398
|
+
const target = unwrap(anyTarget);
|
|
4399
|
+
if (isNonReactive(target))
|
|
4400
|
+
return target;
|
|
4401
|
+
const existing = readonlyObjectToProxy.get(target);
|
|
4402
|
+
if (existing)
|
|
4403
|
+
return existing;
|
|
4404
|
+
const proxy = new Proxy(target, readonlyReactiveHandlers);
|
|
4405
|
+
readonlyObjectToProxy.set(target, proxy);
|
|
4406
|
+
proxyToObject.set(proxy, target);
|
|
4407
|
+
return proxy;
|
|
4408
|
+
}
|
|
3981
4409
|
/**
|
|
3982
4410
|
* Main decorator for making classes reactive
|
|
3983
4411
|
* Automatically makes class instances reactive when created
|
|
@@ -4008,6 +4436,8 @@ const reactive = decorator({
|
|
|
4008
4436
|
},
|
|
4009
4437
|
default: reactiveObject,
|
|
4010
4438
|
});
|
|
4439
|
+
const shallowReactive = shallowReactiveObject;
|
|
4440
|
+
const readonlyReactive = readonlyReactiveObject;
|
|
4011
4441
|
|
|
4012
|
-
export {
|
|
4013
|
-
//# sourceMappingURL=proxy-
|
|
4442
|
+
export { markRawProps as $, AZone as A, effectContext as B, CompareSymbol as C, DecoratorError as D, flavorOptions as E, flavored as F, formatCleanupReason as G, getActivationLog as H, IterableWeakMap as I, getActiveEffect as J, getState as K, hooks as L, inert as M, inheritCaption as N, isConstructor as O, isDev as P, isNonReactive as Q, ReactiveBase as R, isObject as S, isProd as T, isReactive as U, isReactiveBroken as V, isTest as W, legacyDecorator as X, link as Y, Zone as Z, markRaw as _, IterableWeakSet as a, mixin as a0, modernDecorator as a1, named as a2, objectToProxy as a3, onEffectThrow as a4, onReactiveBroken as a5, onReactiveReset as a6, prodPreset as a7, proxyToObject as a8, reactive as a9, inertDepth as aA, optionCall as aB, FoolProof as aC, effectHistory as aD, unreactiveProperties as aE, __runInitializers as aF, __esDecorate as aG, contentRef as aH, notifyPropertyChange as aI, metaProtos as aJ, wrapProtos as aK, objectParents as aL, watchers as aM, effectToReactiveObjects as aN, effectMarker as aO, setDebugHooks as aP, allProps as aQ, options as aa, readonlyReactive as ab, reset as ac, root as ad, shallowReactive as ae, tag as af, toRaw as ag, touched as ah, touched1 as ai, unlink as aj, untracked as ak, unwrap as al, withEffectContext as am, wrapInert as an, zip as ao, markWithRoot as ap, dependant as aq, getEffectNode as ar, chainExternalReason as as, keysOf as at, objectsWithDeepWatchers as au, effectToDeepWatchedObjects as av, deepWatchers as aw, registerDeepWatcher as ax, rootFunctionSymbol as ay, getRoot as az, ReactiveError as b, ReactiveErrorCode as c, ZoneAggregator as d, ZoneHistory as e, addBatchCleanup as f, addUnreactiveProps as g, arrayEquals as h, assertUntracked as i, asyncHook as j, asyncHooks as k, asyncZone as l, atom as m, atomic as n, biDi as o, captioned as p, captured as q, caught as r, createFlavor as s, debugPreset as t, decorator as u, deepCompare as v, defer as w, devPreset as x, effect as y, effectAggregator as z };
|
|
4443
|
+
//# sourceMappingURL=proxy-C2lnvvbx.esm.js.map
|