mutts 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/chunks/{_tslib-Mzh1rNsX.esm.js → _tslib-MCKDzsSq.esm.js} +2 -2
- package/dist/chunks/_tslib-MCKDzsSq.esm.js.map +1 -0
- package/dist/chunks/decorator-BGILvPtN.esm.js +627 -0
- package/dist/chunks/decorator-BGILvPtN.esm.js.map +1 -0
- package/dist/chunks/decorator-BQ2eBTCj.js +651 -0
- package/dist/chunks/decorator-BQ2eBTCj.js.map +1 -0
- package/dist/chunks/{index-GRBSx0mB.js → index-CDCOjzTy.js} +543 -495
- package/dist/chunks/index-CDCOjzTy.js.map +1 -0
- package/dist/chunks/{index-79Kk8D6e.esm.js → index-DiP0RXoZ.esm.js} +452 -404
- package/dist/chunks/index-DiP0RXoZ.esm.js.map +1 -0
- package/dist/decorator.d.ts +3 -3
- package/dist/decorator.esm.js +1 -1
- package/dist/decorator.js +1 -1
- package/dist/destroyable.esm.js +4 -4
- package/dist/destroyable.esm.js.map +1 -1
- package/dist/destroyable.js +4 -4
- package/dist/destroyable.js.map +1 -1
- package/dist/devtools/panel.js.map +1 -1
- package/dist/eventful.esm.js +1 -1
- package/dist/index.esm.js +48 -3
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +50 -4
- package/dist/index.js.map +1 -1
- package/dist/mutts.umd.js +1 -1
- 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/reactive.d.ts +54 -1
- package/dist/reactive.esm.js +3 -3
- package/dist/reactive.js +6 -4
- package/dist/reactive.js.map +1 -1
- package/dist/std-decorators.d.ts +1 -1
- package/dist/std-decorators.esm.js +10 -10
- package/dist/std-decorators.esm.js.map +1 -1
- package/dist/std-decorators.js +10 -10
- package/dist/std-decorators.js.map +1 -1
- package/docs/ai/manual.md +14 -95
- package/docs/reactive/advanced.md +6 -107
- package/docs/reactive/core.md +16 -16
- package/docs/reactive/debugging.md +158 -0
- package/docs/reactive.md +8 -0
- package/package.json +16 -66
- package/src/decorator.ts +11 -9
- package/src/destroyable.ts +5 -5
- package/src/index.ts +46 -0
- package/src/reactive/array.ts +3 -5
- package/src/reactive/change.ts +7 -3
- package/src/reactive/debug.ts +1 -1
- package/src/reactive/deep-touch.ts +1 -1
- package/src/reactive/deep-watch.ts +1 -1
- package/src/reactive/effect-context.ts +2 -2
- package/src/reactive/effects.ts +114 -17
- package/src/reactive/index.ts +3 -2
- package/src/reactive/interface.ts +10 -9
- package/src/reactive/map.ts +6 -6
- package/src/reactive/mapped.ts +2 -3
- package/src/reactive/memoize.ts +77 -31
- package/src/reactive/project.ts +103 -6
- package/src/reactive/proxy.ts +4 -4
- package/src/reactive/registry.ts +67 -0
- package/src/reactive/set.ts +6 -6
- package/src/reactive/tracking.ts +12 -41
- package/src/reactive/types.ts +59 -0
- package/src/reactive/zone.ts +1 -1
- package/src/std-decorators.ts +10 -10
- package/src/utils.ts +141 -0
- package/dist/chunks/_tslib-Mzh1rNsX.esm.js.map +0 -1
- package/dist/chunks/decorator-DLvrD0UF.js +0 -265
- package/dist/chunks/decorator-DLvrD0UF.js.map +0 -1
- package/dist/chunks/decorator-DqiszP7i.esm.js +0 -253
- package/dist/chunks/decorator-DqiszP7i.esm.js.map +0 -1
- package/dist/chunks/index-79Kk8D6e.esm.js.map +0 -1
- package/dist/chunks/index-GRBSx0mB.js.map +0 -1
- /package/{src/reactive/project.project.md → docs/reactive/project.md} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var decorator = require('./decorator-
|
|
3
|
+
var decorator = require('./decorator-BQ2eBTCj.js');
|
|
4
4
|
var indexable = require('../indexable.js');
|
|
5
5
|
var _tslib = require('./_tslib-BgjropY9.js');
|
|
6
6
|
|
|
@@ -334,313 +334,6 @@ function mixin(mixinFunction, unwrapFunction) {
|
|
|
334
334
|
});
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
-
// biome-ignore-all lint/suspicious/noConfusingVoidType: Type 'void' is not assignable to type 'ScopedCallback | undefined'.
|
|
338
|
-
// Argument of type '() => void' is not assignable to parameter of type '(dep: DependencyFunction) => ScopedCallback | undefined'.
|
|
339
|
-
// Track native reactivity
|
|
340
|
-
const nativeReactive = Symbol('native-reactive');
|
|
341
|
-
/**
|
|
342
|
-
* Symbol to mark individual objects as non-reactive
|
|
343
|
-
*/
|
|
344
|
-
const nonReactiveMark = Symbol('non-reactive');
|
|
345
|
-
/**
|
|
346
|
-
* Symbol to mark class properties as non-reactive
|
|
347
|
-
*/
|
|
348
|
-
const unreactiveProperties = Symbol('unreactive-properties');
|
|
349
|
-
/**
|
|
350
|
-
* Symbol for prototype forwarding in reactive objects
|
|
351
|
-
*/
|
|
352
|
-
const prototypeForwarding = Symbol('prototype-forwarding');
|
|
353
|
-
/**
|
|
354
|
-
* Symbol representing all properties in reactive tracking
|
|
355
|
-
*/
|
|
356
|
-
const allProps = Symbol('all-props');
|
|
357
|
-
// Symbol to mark functions with their root function
|
|
358
|
-
const rootFunction = Symbol('root-function');
|
|
359
|
-
/**
|
|
360
|
-
* Structured error codes for machine-readable diagnosis
|
|
361
|
-
*/
|
|
362
|
-
var ReactiveErrorCode;
|
|
363
|
-
(function (ReactiveErrorCode) {
|
|
364
|
-
ReactiveErrorCode["CycleDetected"] = "CYCLE_DETECTED";
|
|
365
|
-
ReactiveErrorCode["MaxDepthExceeded"] = "MAX_DEPTH_EXCEEDED";
|
|
366
|
-
ReactiveErrorCode["MaxReactionExceeded"] = "MAX_REACTION_EXCEEDED";
|
|
367
|
-
ReactiveErrorCode["WriteInComputed"] = "WRITE_IN_COMPUTED";
|
|
368
|
-
ReactiveErrorCode["TrackingError"] = "TRACKING_ERROR";
|
|
369
|
-
})(ReactiveErrorCode || (ReactiveErrorCode = {}));
|
|
370
|
-
/**
|
|
371
|
-
* Error class for reactive system errors
|
|
372
|
-
*/
|
|
373
|
-
class ReactiveError extends Error {
|
|
374
|
-
constructor(message, debugInfo) {
|
|
375
|
-
super(message);
|
|
376
|
-
this.debugInfo = debugInfo;
|
|
377
|
-
this.name = 'ReactiveError';
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
// biome-ignore-start lint/correctness/noUnusedFunctionParameters: Interface declaration with empty defaults
|
|
381
|
-
/**
|
|
382
|
-
* Global options for the reactive system
|
|
383
|
-
*/
|
|
384
|
-
const options = {
|
|
385
|
-
/**
|
|
386
|
-
* Debug purpose: called when an effect is entered
|
|
387
|
-
* @param effect - The effect that is entered
|
|
388
|
-
*/
|
|
389
|
-
enter: (_effect) => { },
|
|
390
|
-
/**
|
|
391
|
-
* Debug purpose: called when an effect is left
|
|
392
|
-
* @param effect - The effect that is left
|
|
393
|
-
*/
|
|
394
|
-
leave: (_effect) => { },
|
|
395
|
-
/**
|
|
396
|
-
* Debug purpose: called when an effect is chained
|
|
397
|
-
* @param target - The effect that is being triggered
|
|
398
|
-
* @param caller - The effect that is calling the target
|
|
399
|
-
*/
|
|
400
|
-
chain: (_targets, _caller) => { },
|
|
401
|
-
/**
|
|
402
|
-
* Debug purpose: called when an effect chain is started
|
|
403
|
-
* @param target - The effect that is being triggered
|
|
404
|
-
*/
|
|
405
|
-
beginChain: (_targets) => { },
|
|
406
|
-
/**
|
|
407
|
-
* Debug purpose: called when an effect chain is ended
|
|
408
|
-
*/
|
|
409
|
-
endChain: () => { },
|
|
410
|
-
garbageCollected: (_fn) => { },
|
|
411
|
-
/**
|
|
412
|
-
* Debug purpose: called when an object is touched
|
|
413
|
-
* @param obj - The object that is touched
|
|
414
|
-
* @param evolution - The type of change
|
|
415
|
-
* @param props - The properties that changed
|
|
416
|
-
* @param deps - The dependencies that changed
|
|
417
|
-
*/
|
|
418
|
-
touched: (_obj, _evolution, _props, _deps) => { },
|
|
419
|
-
/**
|
|
420
|
-
* Debug purpose: called when an effect is skipped because it's already running
|
|
421
|
-
* @param effect - The effect that is already running
|
|
422
|
-
* @param runningChain - The array of effects from the detected one to the currently running one
|
|
423
|
-
*/
|
|
424
|
-
skipRunningEffect: (_effect, _runningChain) => { },
|
|
425
|
-
/**
|
|
426
|
-
* Debug purpose: maximum effect chain (like call stack max depth)
|
|
427
|
-
* Used to prevent infinite loops
|
|
428
|
-
* @default 100
|
|
429
|
-
*/
|
|
430
|
-
maxEffectChain: 100,
|
|
431
|
-
/**
|
|
432
|
-
* Debug purpose: maximum effect reaction (like call stack max depth)
|
|
433
|
-
* Used to prevent infinite loops
|
|
434
|
-
* @default 'throw'
|
|
435
|
-
*/
|
|
436
|
-
maxEffectReaction: 'throw',
|
|
437
|
-
/**
|
|
438
|
-
* How to handle cycles detected in effect batches
|
|
439
|
-
* - 'throw': Throw an error with cycle information (default, recommended for development)
|
|
440
|
-
* - 'warn': Log a warning and break the cycle by executing one effect
|
|
441
|
-
* - 'break': Silently break the cycle by executing one effect (recommended for production)
|
|
442
|
-
* - 'strict': Prevent cycle creation by checking graph before execution (throws error)
|
|
443
|
-
* @default 'throw'
|
|
444
|
-
*/
|
|
445
|
-
cycleHandling: 'throw',
|
|
446
|
-
/**
|
|
447
|
-
* Maximum depth for deep watching traversal
|
|
448
|
-
* Used to prevent infinite recursion in circular references
|
|
449
|
-
* @default 100
|
|
450
|
-
*/
|
|
451
|
-
maxDeepWatchDepth: 100,
|
|
452
|
-
/**
|
|
453
|
-
* Only react on instance members modification (not inherited properties)
|
|
454
|
-
* For instance, do not track class methods
|
|
455
|
-
* @default true
|
|
456
|
-
*/
|
|
457
|
-
instanceMembers: true,
|
|
458
|
-
/**
|
|
459
|
-
* Ignore accessors (getters and setters) and only track direct properties
|
|
460
|
-
* @default true
|
|
461
|
-
*/
|
|
462
|
-
ignoreAccessors: true,
|
|
463
|
-
/**
|
|
464
|
-
* Enable recursive touching when objects with the same prototype are replaced
|
|
465
|
-
* When enabled, replacing an object with another of the same prototype triggers
|
|
466
|
-
* recursive diffing instead of notifying parent effects
|
|
467
|
-
* @default true
|
|
468
|
-
*/
|
|
469
|
-
recursiveTouching: true,
|
|
470
|
-
/**
|
|
471
|
-
* Default async execution mode for effects that return Promises
|
|
472
|
-
* - 'cancel': Cancel previous async execution when dependencies change (default, enables async zone)
|
|
473
|
-
* - 'queue': Queue next execution to run after current completes (enables async zone)
|
|
474
|
-
* - 'ignore': Ignore new executions while async work is running (enables async zone)
|
|
475
|
-
* - false: Disable async zone and async mode handling (effects run concurrently)
|
|
476
|
-
*
|
|
477
|
-
* **When truthy:** Enables async zone (Promise.prototype wrapping) for automatic context
|
|
478
|
-
* preservation in Promise callbacks. Warning: This modifies Promise.prototype globally.
|
|
479
|
-
* Only enable if no other library modifies Promise.prototype.
|
|
480
|
-
*
|
|
481
|
-
* **When false:** Async zone is disabled. Use `tracked()` manually in Promise callbacks.
|
|
482
|
-
*
|
|
483
|
-
* Can be overridden per-effect via EffectOptions
|
|
484
|
-
* @default 'cancel'
|
|
485
|
-
*/
|
|
486
|
-
asyncMode: 'cancel',
|
|
487
|
-
// biome-ignore lint/suspicious/noConsole: This is the whole point here
|
|
488
|
-
warn: (...args) => console.warn(...args),
|
|
489
|
-
/**
|
|
490
|
-
* Configuration for the introspection system
|
|
491
|
-
*/
|
|
492
|
-
introspection: {
|
|
493
|
-
/**
|
|
494
|
-
* Whether to keep a history of mutations for debugging
|
|
495
|
-
* @default false
|
|
496
|
-
*/
|
|
497
|
-
enableHistory: false,
|
|
498
|
-
/**
|
|
499
|
-
* Number of mutations to keep in history
|
|
500
|
-
* @default 50
|
|
501
|
-
*/
|
|
502
|
-
historySize: 50,
|
|
503
|
-
},
|
|
504
|
-
/**
|
|
505
|
-
* Configuration for zone hooks - control which async APIs are hooked
|
|
506
|
-
* Each option controls whether the corresponding async API is wrapped to preserve effect context
|
|
507
|
-
* Only applies when asyncMode is enabled (truthy)
|
|
508
|
-
*/
|
|
509
|
-
zones: {
|
|
510
|
-
/**
|
|
511
|
-
* Hook setTimeout to preserve effect context
|
|
512
|
-
* @default true
|
|
513
|
-
*/
|
|
514
|
-
setTimeout: true,
|
|
515
|
-
/**
|
|
516
|
-
* Hook setInterval to preserve effect context
|
|
517
|
-
* @default true
|
|
518
|
-
*/
|
|
519
|
-
setInterval: true,
|
|
520
|
-
/**
|
|
521
|
-
* Hook requestAnimationFrame (runs in untracked context when hooked)
|
|
522
|
-
* @default true
|
|
523
|
-
*/
|
|
524
|
-
requestAnimationFrame: true,
|
|
525
|
-
/**
|
|
526
|
-
* Hook queueMicrotask to preserve effect context
|
|
527
|
-
* @default true
|
|
528
|
-
*/
|
|
529
|
-
queueMicrotask: true,
|
|
530
|
-
},
|
|
531
|
-
};
|
|
532
|
-
|
|
533
|
-
/**
|
|
534
|
-
* Effect context stack for nested tracking (front = active, next = parent)
|
|
535
|
-
*/
|
|
536
|
-
const stack = [];
|
|
537
|
-
function captureEffectStack() {
|
|
538
|
-
return stack.slice();
|
|
539
|
-
}
|
|
540
|
-
function isRunning(effect) {
|
|
541
|
-
const rootEffect = getRoot(effect);
|
|
542
|
-
// Check if the effect is directly in the stack
|
|
543
|
-
const rootIndex = stack.indexOf(rootEffect);
|
|
544
|
-
if (rootIndex !== -1) {
|
|
545
|
-
return stack.slice(0, rootIndex + 1).reverse();
|
|
546
|
-
}
|
|
547
|
-
// Check if any effect in the stack is a descendant of this effect
|
|
548
|
-
// (i.e., walk up the parent chain from each stack effect to see if we reach this effect)
|
|
549
|
-
for (let i = 0; i < stack.length; i++) {
|
|
550
|
-
const stackEffect = stack[i];
|
|
551
|
-
let current = stackEffect;
|
|
552
|
-
const visited = new WeakSet();
|
|
553
|
-
const ancestorChain = [];
|
|
554
|
-
// TODO: That's perhaps a lot of computations for an `assert`
|
|
555
|
-
// Walk up the parent chain to find if this effect is an ancestor
|
|
556
|
-
while (current && !visited.has(current)) {
|
|
557
|
-
visited.add(current);
|
|
558
|
-
const currentRoot = getRoot(current);
|
|
559
|
-
ancestorChain.push(currentRoot);
|
|
560
|
-
if (currentRoot === rootEffect) {
|
|
561
|
-
// Found a descendant - build the full chain from ancestor to active
|
|
562
|
-
// The ancestorChain contains [descendant, parent, ..., ancestor] (walking up)
|
|
563
|
-
// We need [ancestor (effect), ..., parent, descendant, ...stack from descendant to active]
|
|
564
|
-
const chainFromAncestor = ancestorChain.reverse(); // [ancestor, ..., descendant]
|
|
565
|
-
// Prepend the actual effect we're checking (in case current is a wrapper)
|
|
566
|
-
if (chainFromAncestor[0] !== rootEffect) {
|
|
567
|
-
chainFromAncestor.unshift(rootEffect);
|
|
568
|
-
}
|
|
569
|
-
// Append the rest of the stack from the descendant to the active effect
|
|
570
|
-
const stackFromDescendant = stack.slice(0, i + 1).reverse(); // [descendant, ..., active]
|
|
571
|
-
// Remove duplicate descendant (it's both at end of chainFromAncestor and start of stackFromDescendant)
|
|
572
|
-
if (chainFromAncestor.length > 0 && stackFromDescendant.length > 0) {
|
|
573
|
-
stackFromDescendant.shift(); // Remove duplicate descendant
|
|
574
|
-
}
|
|
575
|
-
return [...chainFromAncestor, ...stackFromDescendant];
|
|
576
|
-
}
|
|
577
|
-
current = effectParent.get(current);
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
return false;
|
|
581
|
-
}
|
|
582
|
-
function withEffectStack(snapshot, fn) {
|
|
583
|
-
const previousStack = stack.slice();
|
|
584
|
-
assignStack(snapshot);
|
|
585
|
-
try {
|
|
586
|
-
return fn();
|
|
587
|
-
}
|
|
588
|
-
finally {
|
|
589
|
-
assignStack(previousStack);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
function getActiveEffect() {
|
|
593
|
-
return stack[0];
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* Executes a function with a specific effect context
|
|
597
|
-
* @param effect - The effect to use as context
|
|
598
|
-
* @param fn - The function to execute
|
|
599
|
-
* @param keepParent - Whether to keep the parent effect context
|
|
600
|
-
* @returns The result of the function
|
|
601
|
-
*/
|
|
602
|
-
function withEffect(effect, fn) {
|
|
603
|
-
// console.log('[Mutts] withEffect', effect ? 'Active' : 'NULL');
|
|
604
|
-
if (getRoot(effect) === getRoot(getActiveEffect()))
|
|
605
|
-
return fn();
|
|
606
|
-
stack.unshift(effect);
|
|
607
|
-
try {
|
|
608
|
-
return fn();
|
|
609
|
-
}
|
|
610
|
-
finally {
|
|
611
|
-
const recoveredEffect = stack.shift();
|
|
612
|
-
if (recoveredEffect !== effect)
|
|
613
|
-
throw new ReactiveError('[reactive] Effect stack mismatch');
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
function assignStack(values) {
|
|
617
|
-
stack.length = 0;
|
|
618
|
-
stack.push(...values);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
const objectToProxy = new WeakMap();
|
|
622
|
-
const proxyToObject = new WeakMap();
|
|
623
|
-
function storeProxyRelationship(target, proxy) {
|
|
624
|
-
objectToProxy.set(target, proxy);
|
|
625
|
-
proxyToObject.set(proxy, target);
|
|
626
|
-
}
|
|
627
|
-
function getExistingProxy(target) {
|
|
628
|
-
return objectToProxy.get(target);
|
|
629
|
-
}
|
|
630
|
-
function trackProxyObject(proxy, target) {
|
|
631
|
-
proxyToObject.set(proxy, target);
|
|
632
|
-
}
|
|
633
|
-
function unwrap(obj) {
|
|
634
|
-
let current = obj;
|
|
635
|
-
while (current && typeof current === 'object' && current !== null && proxyToObject.has(current)) {
|
|
636
|
-
current = proxyToObject.get(current);
|
|
637
|
-
}
|
|
638
|
-
return current;
|
|
639
|
-
}
|
|
640
|
-
function isReactive(obj) {
|
|
641
|
-
return proxyToObject.has(obj);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
337
|
// Track which effects are watching which reactive objects for cleanup
|
|
645
338
|
const effectToReactiveObjects = new WeakMap();
|
|
646
339
|
// Track effects per reactive object and property
|
|
@@ -649,15 +342,31 @@ const watchers = new WeakMap();
|
|
|
649
342
|
const effectChildren = new WeakMap();
|
|
650
343
|
// Track parent effect relationships for hierarchy traversal (used in deep touch filtering)
|
|
651
344
|
const effectParent = new WeakMap();
|
|
345
|
+
// Track reverse mapping to ensure unicity: One Root -> One Function
|
|
346
|
+
const reverseRoots = new WeakMap();
|
|
652
347
|
/**
|
|
653
348
|
* Marks a function with its root function for effect tracking
|
|
349
|
+
* Enforces strict unicity: A root function can only identify ONE function.
|
|
654
350
|
* @param fn - The function to mark
|
|
655
351
|
* @param root - The root function
|
|
656
352
|
* @returns The marked function
|
|
657
353
|
*/
|
|
658
354
|
function markWithRoot(fn, root) {
|
|
355
|
+
// Check for collision
|
|
356
|
+
const existingRef = reverseRoots.get(root);
|
|
357
|
+
const existing = existingRef?.deref();
|
|
358
|
+
if (existing && existing !== fn) {
|
|
359
|
+
const rootName = root.name || 'anonymous';
|
|
360
|
+
const existingName = existing.name || 'anonymous';
|
|
361
|
+
const fnName = fn.name || 'anonymous';
|
|
362
|
+
throw new Error(`[reactive] Abusive Shared Root detected: Root '${rootName}' is already identifying function '${existingName}'. ` +
|
|
363
|
+
`Cannot reuse it for '${fnName}'. Shared roots cause lost updates and broken identity logic.`);
|
|
364
|
+
}
|
|
365
|
+
// Always update the map so subsequent checks find this one
|
|
366
|
+
// (Last writer wins for the check)
|
|
367
|
+
reverseRoots.set(root, new WeakRef(fn));
|
|
659
368
|
// Mark fn with the new root
|
|
660
|
-
return Object.defineProperty(fn, rootFunction, {
|
|
369
|
+
return Object.defineProperty(fn, decorator.rootFunction, {
|
|
661
370
|
value: getRoot(root),
|
|
662
371
|
writable: false,
|
|
663
372
|
});
|
|
@@ -668,64 +377,15 @@ function markWithRoot(fn, root) {
|
|
|
668
377
|
* @returns The root function
|
|
669
378
|
*/
|
|
670
379
|
function getRoot(fn) {
|
|
671
|
-
|
|
380
|
+
while (fn && decorator.rootFunction in fn)
|
|
381
|
+
fn = fn[decorator.rootFunction];
|
|
382
|
+
return fn;
|
|
672
383
|
}
|
|
673
384
|
// Flag to disable dependency tracking for the current active effect (not globally)
|
|
674
385
|
const trackingDisabledEffects = new WeakSet();
|
|
675
386
|
let globalTrackingDisabled = false;
|
|
676
|
-
function
|
|
677
|
-
|
|
678
|
-
if (!active)
|
|
679
|
-
return globalTrackingDisabled;
|
|
680
|
-
return trackingDisabledEffects.has(getRoot(active));
|
|
681
|
-
}
|
|
682
|
-
function setTrackingDisabled(value) {
|
|
683
|
-
const active = getActiveEffect();
|
|
684
|
-
if (!active) {
|
|
685
|
-
globalTrackingDisabled = value;
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
const root = getRoot(active);
|
|
689
|
-
if (value)
|
|
690
|
-
trackingDisabledEffects.add(root);
|
|
691
|
-
else
|
|
692
|
-
trackingDisabledEffects.delete(root);
|
|
693
|
-
}
|
|
694
|
-
/**
|
|
695
|
-
* Marks a property as a dependency of the current effect
|
|
696
|
-
* @param obj - The object containing the property
|
|
697
|
-
* @param prop - The property name (defaults to allProps)
|
|
698
|
-
*/
|
|
699
|
-
function dependant(obj, prop = allProps) {
|
|
700
|
-
obj = unwrap(obj);
|
|
701
|
-
const currentActiveEffect = getActiveEffect();
|
|
702
|
-
// Early return if no active effect, tracking disabled, or invalid prop
|
|
703
|
-
if (!currentActiveEffect ||
|
|
704
|
-
getTrackingDisabled() ||
|
|
705
|
-
(typeof prop === 'symbol' && prop !== allProps))
|
|
706
|
-
return;
|
|
707
|
-
registerDependency(obj, prop, currentActiveEffect);
|
|
708
|
-
}
|
|
709
|
-
function registerDependency(obj, prop, currentActiveEffect) {
|
|
710
|
-
let objectWatchers = watchers.get(obj);
|
|
711
|
-
if (!objectWatchers) {
|
|
712
|
-
objectWatchers = new Map();
|
|
713
|
-
watchers.set(obj, objectWatchers);
|
|
714
|
-
}
|
|
715
|
-
let deps = objectWatchers.get(prop);
|
|
716
|
-
if (!deps) {
|
|
717
|
-
deps = new Set();
|
|
718
|
-
objectWatchers.set(prop, deps);
|
|
719
|
-
}
|
|
720
|
-
deps.add(currentActiveEffect);
|
|
721
|
-
// Track which reactive objects this effect is watching
|
|
722
|
-
const effectObjects = effectToReactiveObjects.get(currentActiveEffect);
|
|
723
|
-
if (effectObjects) {
|
|
724
|
-
effectObjects.add(obj);
|
|
725
|
-
}
|
|
726
|
-
else {
|
|
727
|
-
effectToReactiveObjects.set(currentActiveEffect, new Set([obj]));
|
|
728
|
-
}
|
|
387
|
+
function setGlobalTrackingDisabled(value) {
|
|
388
|
+
globalTrackingDisabled = value;
|
|
729
389
|
}
|
|
730
390
|
|
|
731
391
|
/**
|
|
@@ -766,7 +426,7 @@ function ensureObjectName(obj) {
|
|
|
766
426
|
}
|
|
767
427
|
function describeProp(obj, prop) {
|
|
768
428
|
const objectName = ensureObjectName(obj);
|
|
769
|
-
if (prop === allProps)
|
|
429
|
+
if (prop === decorator.allProps)
|
|
770
430
|
return `${objectName}.*`;
|
|
771
431
|
if (typeof prop === 'symbol')
|
|
772
432
|
return `${objectName}.${prop.description ?? prop.toString()}`;
|
|
@@ -863,7 +523,7 @@ function registerObjectForDebug(obj) {
|
|
|
863
523
|
* @param evolution - The type of change (set/add/del/bunch)
|
|
864
524
|
*/
|
|
865
525
|
function recordTriggerLink(source, target, obj, prop, evolution) {
|
|
866
|
-
if (options.introspection.enableHistory) {
|
|
526
|
+
if (decorator.options.introspection.enableHistory) {
|
|
867
527
|
addToMutationHistory(source, target, obj, prop, evolution);
|
|
868
528
|
}
|
|
869
529
|
if (!devtoolsEnabled)
|
|
@@ -1075,11 +735,176 @@ function addToMutationHistory(source, target, obj, prop, evolution) {
|
|
|
1075
735
|
type: evolution.type,
|
|
1076
736
|
};
|
|
1077
737
|
mutationHistory.push(record);
|
|
1078
|
-
if (mutationHistory.length > options.introspection.historySize) {
|
|
738
|
+
if (mutationHistory.length > decorator.options.introspection.historySize) {
|
|
1079
739
|
mutationHistory.shift();
|
|
1080
740
|
}
|
|
1081
741
|
}
|
|
1082
742
|
|
|
743
|
+
/**
|
|
744
|
+
* Effect context stack for nested tracking (front = active, next = parent)
|
|
745
|
+
*/
|
|
746
|
+
const stack = [];
|
|
747
|
+
function captureEffectStack() {
|
|
748
|
+
return stack.slice();
|
|
749
|
+
}
|
|
750
|
+
function isRunning(effect) {
|
|
751
|
+
const rootEffect = getRoot(effect);
|
|
752
|
+
// Check if the effect is directly in the stack
|
|
753
|
+
const rootIndex = stack.indexOf(rootEffect);
|
|
754
|
+
if (rootIndex !== -1) {
|
|
755
|
+
return stack.slice(0, rootIndex + 1).reverse();
|
|
756
|
+
}
|
|
757
|
+
// Check if any effect in the stack is a descendant of this effect
|
|
758
|
+
// (i.e., walk up the parent chain from each stack effect to see if we reach this effect)
|
|
759
|
+
for (let i = 0; i < stack.length; i++) {
|
|
760
|
+
const stackEffect = stack[i];
|
|
761
|
+
let current = stackEffect;
|
|
762
|
+
const visited = new WeakSet();
|
|
763
|
+
const ancestorChain = [];
|
|
764
|
+
// TODO: That's perhaps a lot of computations for an `assert`
|
|
765
|
+
// Walk up the parent chain to find if this effect is an ancestor
|
|
766
|
+
while (current && !visited.has(current)) {
|
|
767
|
+
visited.add(current);
|
|
768
|
+
const currentRoot = getRoot(current);
|
|
769
|
+
ancestorChain.push(currentRoot);
|
|
770
|
+
if (currentRoot === rootEffect) {
|
|
771
|
+
// Found a descendant - build the full chain from ancestor to active
|
|
772
|
+
// The ancestorChain contains [descendant, parent, ..., ancestor] (walking up)
|
|
773
|
+
// We need [ancestor (effect), ..., parent, descendant, ...stack from descendant to active]
|
|
774
|
+
const chainFromAncestor = ancestorChain.reverse(); // [ancestor, ..., descendant]
|
|
775
|
+
// Prepend the actual effect we're checking (in case current is a wrapper)
|
|
776
|
+
if (chainFromAncestor[0] !== rootEffect) {
|
|
777
|
+
chainFromAncestor.unshift(rootEffect);
|
|
778
|
+
}
|
|
779
|
+
// Append the rest of the stack from the descendant to the active effect
|
|
780
|
+
const stackFromDescendant = stack.slice(0, i + 1).reverse(); // [descendant, ..., active]
|
|
781
|
+
// Remove duplicate descendant (it's both at end of chainFromAncestor and start of stackFromDescendant)
|
|
782
|
+
if (chainFromAncestor.length > 0 && stackFromDescendant.length > 0) {
|
|
783
|
+
stackFromDescendant.shift(); // Remove duplicate descendant
|
|
784
|
+
}
|
|
785
|
+
return [...chainFromAncestor, ...stackFromDescendant];
|
|
786
|
+
}
|
|
787
|
+
current = effectParent.get(current);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
function withEffectStack(snapshot, fn) {
|
|
793
|
+
const previousStack = stack.slice();
|
|
794
|
+
assignStack(snapshot);
|
|
795
|
+
try {
|
|
796
|
+
return fn();
|
|
797
|
+
}
|
|
798
|
+
finally {
|
|
799
|
+
assignStack(previousStack);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
function getActiveEffect() {
|
|
803
|
+
return stack[0];
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Executes a function with a specific effect context
|
|
807
|
+
* @param effect - The effect to use as context
|
|
808
|
+
* @param fn - The function to execute
|
|
809
|
+
* @param keepParent - Whether to keep the parent effect context
|
|
810
|
+
* @returns The result of the function
|
|
811
|
+
*/
|
|
812
|
+
function withEffect(effect, fn) {
|
|
813
|
+
if (getRoot(effect) === getRoot(getActiveEffect()))
|
|
814
|
+
return fn();
|
|
815
|
+
stack.unshift(effect);
|
|
816
|
+
try {
|
|
817
|
+
return fn();
|
|
818
|
+
}
|
|
819
|
+
finally {
|
|
820
|
+
const recoveredEffect = stack.shift();
|
|
821
|
+
if (recoveredEffect !== effect)
|
|
822
|
+
throw new decorator.ReactiveError('[reactive] Effect stack mismatch');
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
function assignStack(values) {
|
|
826
|
+
stack.length = 0;
|
|
827
|
+
stack.push(...values);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const objectToProxy = new WeakMap();
|
|
831
|
+
const proxyToObject = new WeakMap();
|
|
832
|
+
function storeProxyRelationship(target, proxy) {
|
|
833
|
+
objectToProxy.set(target, proxy);
|
|
834
|
+
proxyToObject.set(proxy, target);
|
|
835
|
+
}
|
|
836
|
+
function getExistingProxy(target) {
|
|
837
|
+
return objectToProxy.get(target);
|
|
838
|
+
}
|
|
839
|
+
function trackProxyObject(proxy, target) {
|
|
840
|
+
proxyToObject.set(proxy, target);
|
|
841
|
+
}
|
|
842
|
+
function unwrap(obj) {
|
|
843
|
+
let current = obj;
|
|
844
|
+
while (current && typeof current === 'object' && current !== null && proxyToObject.has(current)) {
|
|
845
|
+
current = proxyToObject.get(current);
|
|
846
|
+
}
|
|
847
|
+
return current;
|
|
848
|
+
}
|
|
849
|
+
function isReactive(obj) {
|
|
850
|
+
return proxyToObject.has(obj);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function getTrackingDisabled() {
|
|
854
|
+
const active = getActiveEffect();
|
|
855
|
+
if (!active)
|
|
856
|
+
return globalTrackingDisabled;
|
|
857
|
+
return trackingDisabledEffects.has(getRoot(active));
|
|
858
|
+
}
|
|
859
|
+
function setTrackingDisabled(value) {
|
|
860
|
+
const active = getActiveEffect();
|
|
861
|
+
if (!active) {
|
|
862
|
+
setGlobalTrackingDisabled(value);
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const root = getRoot(active);
|
|
866
|
+
if (value)
|
|
867
|
+
trackingDisabledEffects.add(root);
|
|
868
|
+
else
|
|
869
|
+
trackingDisabledEffects.delete(root);
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Marks a property as a dependency of the current effect
|
|
873
|
+
* @param obj - The object containing the property
|
|
874
|
+
* @param prop - The property name (defaults to allProps)
|
|
875
|
+
*/
|
|
876
|
+
function dependant(obj, prop = decorator.allProps) {
|
|
877
|
+
obj = unwrap(obj);
|
|
878
|
+
const currentActiveEffect = getActiveEffect();
|
|
879
|
+
// Early return if no active effect, tracking disabled, or invalid prop
|
|
880
|
+
if (!currentActiveEffect ||
|
|
881
|
+
getTrackingDisabled() ||
|
|
882
|
+
(typeof prop === 'symbol' && prop !== decorator.allProps))
|
|
883
|
+
return;
|
|
884
|
+
registerDependency(obj, prop, currentActiveEffect);
|
|
885
|
+
}
|
|
886
|
+
function registerDependency(obj, prop, currentActiveEffect) {
|
|
887
|
+
let objectWatchers = watchers.get(obj);
|
|
888
|
+
if (!objectWatchers) {
|
|
889
|
+
objectWatchers = new Map();
|
|
890
|
+
watchers.set(obj, objectWatchers);
|
|
891
|
+
}
|
|
892
|
+
let deps = objectWatchers.get(prop);
|
|
893
|
+
if (!deps) {
|
|
894
|
+
deps = new Set();
|
|
895
|
+
objectWatchers.set(prop, deps);
|
|
896
|
+
}
|
|
897
|
+
deps.add(currentActiveEffect);
|
|
898
|
+
// Track which reactive objects this effect is watching
|
|
899
|
+
const effectObjects = effectToReactiveObjects.get(currentActiveEffect);
|
|
900
|
+
if (effectObjects) {
|
|
901
|
+
effectObjects.add(obj);
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
effectToReactiveObjects.set(currentActiveEffect, new Set([obj]));
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
1083
908
|
/**
|
|
1084
909
|
* Zone-like async context preservation for reactive effects
|
|
1085
910
|
*
|
|
@@ -1120,7 +945,7 @@ let batchFn;
|
|
|
1120
945
|
function ensureZoneHooked(batch) {
|
|
1121
946
|
if (batch)
|
|
1122
947
|
batchFn = batch;
|
|
1123
|
-
if (zoneHooked || !options.asyncMode)
|
|
948
|
+
if (zoneHooked || !decorator.options.asyncMode)
|
|
1124
949
|
return;
|
|
1125
950
|
hookZone();
|
|
1126
951
|
zoneHooked = true;
|
|
@@ -1144,7 +969,7 @@ function hookZone() {
|
|
|
1144
969
|
};
|
|
1145
970
|
// Hook setTimeout - preserve original function properties for Node.js compatibility
|
|
1146
971
|
const wrappedSetTimeout = ((callback, delay, ...args) => {
|
|
1147
|
-
const capturedStack = options.zones.setTimeout ? captureEffectStack() : undefined;
|
|
972
|
+
const capturedStack = decorator.options.zones.setTimeout ? captureEffectStack() : undefined;
|
|
1148
973
|
return originalSetTimeout.apply(globalThis, [
|
|
1149
974
|
wrapCallback(callback, capturedStack),
|
|
1150
975
|
delay,
|
|
@@ -1155,7 +980,7 @@ function hookZone() {
|
|
|
1155
980
|
globalThis.setTimeout = wrappedSetTimeout;
|
|
1156
981
|
// Hook setInterval - preserve original function properties for Node.js compatibility
|
|
1157
982
|
const wrappedSetInterval = ((callback, delay, ...args) => {
|
|
1158
|
-
const capturedStack = options.zones.setInterval ? captureEffectStack() : undefined;
|
|
983
|
+
const capturedStack = decorator.options.zones.setInterval ? captureEffectStack() : undefined;
|
|
1159
984
|
return originalSetInterval.apply(globalThis, [
|
|
1160
985
|
wrapCallback(callback, capturedStack),
|
|
1161
986
|
delay,
|
|
@@ -1167,14 +992,14 @@ function hookZone() {
|
|
|
1167
992
|
// Hook requestAnimationFrame if available
|
|
1168
993
|
if (originalRequestAnimationFrame) {
|
|
1169
994
|
globalThis.requestAnimationFrame = ((callback) => {
|
|
1170
|
-
const capturedStack = options.zones.requestAnimationFrame ? captureEffectStack() : undefined;
|
|
995
|
+
const capturedStack = decorator.options.zones.requestAnimationFrame ? captureEffectStack() : undefined;
|
|
1171
996
|
return originalRequestAnimationFrame.call(globalThis, wrapCallback(callback, capturedStack));
|
|
1172
997
|
});
|
|
1173
998
|
}
|
|
1174
999
|
// Hook queueMicrotask if available
|
|
1175
1000
|
if (originalQueueMicrotask) {
|
|
1176
1001
|
globalThis.queueMicrotask = ((callback) => {
|
|
1177
|
-
const capturedStack = options.zones.queueMicrotask ? captureEffectStack() : undefined;
|
|
1002
|
+
const capturedStack = decorator.options.zones.queueMicrotask ? captureEffectStack() : undefined;
|
|
1178
1003
|
originalQueueMicrotask.call(globalThis, wrapCallback(callback, capturedStack));
|
|
1179
1004
|
});
|
|
1180
1005
|
}
|
|
@@ -1260,6 +1085,50 @@ function formatRoots(roots, limit = 20) {
|
|
|
1260
1085
|
const end = names.slice(-10);
|
|
1261
1086
|
return `${start.join(' → ')} ... (${names.length - 15} more) ... ${end.join(' → ')}`;
|
|
1262
1087
|
}
|
|
1088
|
+
// Nested map structure for efficient counting and batch cleanup
|
|
1089
|
+
// batchId -> effect root -> obj -> prop -> count
|
|
1090
|
+
let activationRegistry;
|
|
1091
|
+
const activationLog = new Array(100);
|
|
1092
|
+
function getActivationLog() {
|
|
1093
|
+
return activationLog;
|
|
1094
|
+
}
|
|
1095
|
+
function recordActivation(effect, obj, evolution, prop) {
|
|
1096
|
+
const root = getRoot(effect);
|
|
1097
|
+
if (!activationRegistry)
|
|
1098
|
+
return;
|
|
1099
|
+
let effectData = activationRegistry.get(root);
|
|
1100
|
+
if (!effectData) {
|
|
1101
|
+
effectData = new Map();
|
|
1102
|
+
activationRegistry.set(root, effectData);
|
|
1103
|
+
}
|
|
1104
|
+
let objData = effectData.get(obj);
|
|
1105
|
+
if (!objData) {
|
|
1106
|
+
objData = new Map();
|
|
1107
|
+
effectData.set(obj, objData);
|
|
1108
|
+
}
|
|
1109
|
+
const count = (objData.get(prop) ?? 0) + 1;
|
|
1110
|
+
objData.set(prop, count);
|
|
1111
|
+
// Keep a limited history for diagnostics
|
|
1112
|
+
activationLog.unshift({
|
|
1113
|
+
effect,
|
|
1114
|
+
obj,
|
|
1115
|
+
evolution,
|
|
1116
|
+
prop,
|
|
1117
|
+
});
|
|
1118
|
+
activationLog.pop();
|
|
1119
|
+
if (count >= decorator.options.maxTriggerPerBatch) {
|
|
1120
|
+
const effectName = root?.name || 'anonymous';
|
|
1121
|
+
const message = `Aggressive trigger detected: effect "${effectName}" triggered ${count} times in the batch by the same cause.`;
|
|
1122
|
+
if (decorator.options.maxEffectReaction === 'throw') {
|
|
1123
|
+
throw new decorator.ReactiveError(message, {
|
|
1124
|
+
code: decorator.ReactiveErrorCode.MaxReactionExceeded,
|
|
1125
|
+
count,
|
|
1126
|
+
effect: effectName,
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
decorator.options.warn(`[reactive] ${message}`);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1263
1132
|
/**
|
|
1264
1133
|
* Registers a debug callback that is called when the current effect is triggered by a dependency change
|
|
1265
1134
|
*
|
|
@@ -1421,6 +1290,7 @@ function addGraphEdge(callerRoot, targetRoot) {
|
|
|
1421
1290
|
* @param end - Target node
|
|
1422
1291
|
* @param exclude - Node to exclude from the path
|
|
1423
1292
|
* @returns true if a path exists without going through the excluded node
|
|
1293
|
+
* @todo Can be REALLY costly - optimise or make optional or ...
|
|
1424
1294
|
*/
|
|
1425
1295
|
function hasPathExcluding(start, end, exclude) {
|
|
1426
1296
|
if (start === end)
|
|
@@ -1549,6 +1419,9 @@ function cleanupEffectFromGraph(effect) {
|
|
|
1549
1419
|
// Track currently executing effects to prevent re-execution
|
|
1550
1420
|
// These are all the effects triggered under `activeEffect`
|
|
1551
1421
|
let batchQueue;
|
|
1422
|
+
function hasBatched(effect) {
|
|
1423
|
+
return batchQueue?.all.has(getRoot(effect));
|
|
1424
|
+
}
|
|
1552
1425
|
const batchCleanups = new Set();
|
|
1553
1426
|
/**
|
|
1554
1427
|
* Computes and caches in-degrees for all effects in the batch
|
|
@@ -1705,6 +1578,12 @@ function wouldCreateCycle(callerRoot, targetRoot) {
|
|
|
1705
1578
|
* @param immediate - If true, don't create edges in the dependency graph
|
|
1706
1579
|
*/
|
|
1707
1580
|
function addToBatch(effect, caller, immediate) {
|
|
1581
|
+
const cleanupFn = effect[decorator.cleanup];
|
|
1582
|
+
if (cleanupFn)
|
|
1583
|
+
cleanupFn();
|
|
1584
|
+
// If the effect was stopped during cleanup (e.g. lazy memoization), don't add it to the batch
|
|
1585
|
+
if (effect[decorator.stopped])
|
|
1586
|
+
return;
|
|
1708
1587
|
if (!batchQueue)
|
|
1709
1588
|
return;
|
|
1710
1589
|
const root = getRoot(effect);
|
|
@@ -1723,14 +1602,14 @@ function addToBatch(effect, caller, immediate) {
|
|
|
1723
1602
|
const cycleMessage = cyclePath.length > 0
|
|
1724
1603
|
? `Cycle detected: ${cyclePath.map((r) => r.name || r.toString()).join(' → ')}`
|
|
1725
1604
|
: `Cycle detected: ${callerRoot.name || callerRoot.toString()} → ${root.name || root.toString()} (and back)`;
|
|
1726
|
-
const cycleHandling = options.cycleHandling;
|
|
1605
|
+
const cycleHandling = decorator.options.cycleHandling;
|
|
1727
1606
|
// In strict mode, we throw immediately on detection
|
|
1728
1607
|
if (cycleHandling === 'strict') {
|
|
1729
1608
|
batchQueue.all.delete(root);
|
|
1730
1609
|
const causalChain = getTriggerChain(effect);
|
|
1731
1610
|
const creationStack = effectCreationStacks.get(root);
|
|
1732
|
-
throw new ReactiveError(`[reactive] Strict Cycle Prevention: ${cycleMessage}`, {
|
|
1733
|
-
code: ReactiveErrorCode.CycleDetected,
|
|
1611
|
+
throw new decorator.ReactiveError(`[reactive] Strict Cycle Prevention: ${cycleMessage}`, {
|
|
1612
|
+
code: decorator.ReactiveErrorCode.CycleDetected,
|
|
1734
1613
|
cycle: cyclePath.map((r) => r.name || r.toString()),
|
|
1735
1614
|
details: cycleMessage,
|
|
1736
1615
|
causalChain,
|
|
@@ -1743,8 +1622,8 @@ function addToBatch(effect, caller, immediate) {
|
|
|
1743
1622
|
batchQueue.all.delete(root);
|
|
1744
1623
|
const causalChain = getTriggerChain(effect);
|
|
1745
1624
|
const creationStack = effectCreationStacks.get(root);
|
|
1746
|
-
throw new ReactiveError(`[reactive] ${cycleMessage}`, {
|
|
1747
|
-
code: ReactiveErrorCode.CycleDetected,
|
|
1625
|
+
throw new decorator.ReactiveError(`[reactive] ${cycleMessage}`, {
|
|
1626
|
+
code: decorator.ReactiveErrorCode.CycleDetected,
|
|
1748
1627
|
cycle: cyclePath.map((r) => r.name || r.toString()),
|
|
1749
1628
|
details: cycleMessage,
|
|
1750
1629
|
causalChain,
|
|
@@ -1752,7 +1631,7 @@ function addToBatch(effect, caller, immediate) {
|
|
|
1752
1631
|
});
|
|
1753
1632
|
}
|
|
1754
1633
|
case 'warn':
|
|
1755
|
-
options.warn(`[reactive] ${cycleMessage}`);
|
|
1634
|
+
decorator.options.warn(`[reactive] ${cycleMessage}`);
|
|
1756
1635
|
// Don't add the edge, break the cycle
|
|
1757
1636
|
batchQueue.all.delete(root);
|
|
1758
1637
|
return;
|
|
@@ -1901,12 +1780,12 @@ function executeNext(effectuatedRoots) {
|
|
|
1901
1780
|
const cycleMessage = cycle.length > 0
|
|
1902
1781
|
? `Cycle detected: ${cycle.map((r) => r.name || '<anonymous>').join(' → ')}`
|
|
1903
1782
|
: 'Cycle detected in effect batch - all effects have dependencies that prevent execution';
|
|
1904
|
-
const cycleHandling = options.cycleHandling;
|
|
1783
|
+
const cycleHandling = decorator.options.cycleHandling;
|
|
1905
1784
|
switch (cycleHandling) {
|
|
1906
1785
|
case 'throw':
|
|
1907
|
-
throw new ReactiveError(`[reactive] ${cycleMessage}`);
|
|
1786
|
+
throw new decorator.ReactiveError(`[reactive] ${cycleMessage}`);
|
|
1908
1787
|
case 'warn': {
|
|
1909
|
-
options.warn(`[reactive] ${cycleMessage}`);
|
|
1788
|
+
decorator.options.warn(`[reactive] ${cycleMessage}`);
|
|
1910
1789
|
// Break the cycle by executing one effect anyway
|
|
1911
1790
|
const firstEffect = batchQueue.all.values().next().value;
|
|
1912
1791
|
if (firstEffect) {
|
|
@@ -1949,7 +1828,7 @@ function batch(effect, immediate) {
|
|
|
1949
1828
|
const roots = effect.map(getRoot);
|
|
1950
1829
|
if (batchQueue) {
|
|
1951
1830
|
// Nested batch - add to existing
|
|
1952
|
-
options?.chain(roots, getRoot(getActiveEffect()));
|
|
1831
|
+
decorator.options?.chain(roots, getRoot(getActiveEffect()));
|
|
1953
1832
|
const caller = getActiveEffect();
|
|
1954
1833
|
for (let i = 0; i < effect.length; i++) {
|
|
1955
1834
|
addToBatch(effect[i], caller, immediate === 'immediate');
|
|
@@ -1974,7 +1853,11 @@ function batch(effect, immediate) {
|
|
|
1974
1853
|
}
|
|
1975
1854
|
else {
|
|
1976
1855
|
// New batch - initialize
|
|
1977
|
-
|
|
1856
|
+
if (!activationRegistry)
|
|
1857
|
+
activationRegistry = new Map();
|
|
1858
|
+
else
|
|
1859
|
+
throw new Error('Batch already in progress');
|
|
1860
|
+
decorator.options.beginChain(roots);
|
|
1978
1861
|
batchQueue = {
|
|
1979
1862
|
all: new Map(),
|
|
1980
1863
|
inDegrees: new Map(),
|
|
@@ -2004,7 +1887,7 @@ function batch(effect, immediate) {
|
|
|
2004
1887
|
// After immediate execution, execute any effects that were triggered during execution
|
|
2005
1888
|
// This is important for @atomic decorator - effects triggered inside should still run
|
|
2006
1889
|
while (batchQueue.all.size > 0) {
|
|
2007
|
-
if (effectuatedRoots.length > options.maxEffectChain) {
|
|
1890
|
+
if (effectuatedRoots.length > decorator.options.maxEffectChain) {
|
|
2008
1891
|
const cycle = findCycleInChain(effectuatedRoots);
|
|
2009
1892
|
const trace = formatRoots(effectuatedRoots);
|
|
2010
1893
|
const message = cycle
|
|
@@ -2013,11 +1896,11 @@ function batch(effect, immediate) {
|
|
|
2013
1896
|
const queuedRoots = batchQueue ? Array.from(batchQueue.all.keys()) : [];
|
|
2014
1897
|
const queued = queuedRoots.map((r) => r.name || '<anonymous>');
|
|
2015
1898
|
const debugInfo = {
|
|
2016
|
-
code: ReactiveErrorCode.MaxDepthExceeded,
|
|
1899
|
+
code: decorator.ReactiveErrorCode.MaxDepthExceeded,
|
|
2017
1900
|
effectuatedRoots,
|
|
2018
1901
|
cycle,
|
|
2019
1902
|
trace,
|
|
2020
|
-
maxEffectChain: options.maxEffectChain,
|
|
1903
|
+
maxEffectChain: decorator.options.maxEffectChain,
|
|
2021
1904
|
queued: queued.slice(0, 50),
|
|
2022
1905
|
queuedCount: queued.length,
|
|
2023
1906
|
// Try to get causation for the last effect
|
|
@@ -2025,15 +1908,15 @@ function batch(effect, immediate) {
|
|
|
2025
1908
|
? getTriggerChain(batchQueue.all.get(effectuatedRoots[effectuatedRoots.length - 1]))
|
|
2026
1909
|
: [],
|
|
2027
1910
|
};
|
|
2028
|
-
switch (options.maxEffectReaction) {
|
|
1911
|
+
switch (decorator.options.maxEffectReaction) {
|
|
2029
1912
|
case 'throw':
|
|
2030
|
-
throw new ReactiveError(`[reactive] ${message}`, debugInfo);
|
|
1913
|
+
throw new decorator.ReactiveError(`[reactive] ${message}`, debugInfo);
|
|
2031
1914
|
case 'debug':
|
|
2032
1915
|
// biome-ignore lint/suspicious/noDebugger: This is the whole point here
|
|
2033
1916
|
debugger;
|
|
2034
|
-
throw new ReactiveError(`[reactive] ${message}`, debugInfo);
|
|
1917
|
+
throw new decorator.ReactiveError(`[reactive] ${message}`, debugInfo);
|
|
2035
1918
|
case 'warn':
|
|
2036
|
-
options.warn(`[reactive] ${message} (queued: ${queued.slice(0, 10).join(', ')}${queued.length > 10 ? ', …' : ''})`);
|
|
1919
|
+
decorator.options.warn(`[reactive] ${message} (queued: ${queued.slice(0, 10).join(', ')}${queued.length > 10 ? ', …' : ''})`);
|
|
2037
1920
|
break;
|
|
2038
1921
|
}
|
|
2039
1922
|
}
|
|
@@ -2052,8 +1935,9 @@ function batch(effect, immediate) {
|
|
|
2052
1935
|
return firstReturn.value;
|
|
2053
1936
|
}
|
|
2054
1937
|
finally {
|
|
1938
|
+
activationRegistry = undefined;
|
|
2055
1939
|
batchQueue = undefined;
|
|
2056
|
-
options.endChain();
|
|
1940
|
+
decorator.options.endChain();
|
|
2057
1941
|
}
|
|
2058
1942
|
}
|
|
2059
1943
|
else {
|
|
@@ -2065,7 +1949,7 @@ function batch(effect, immediate) {
|
|
|
2065
1949
|
while (batchQueue.all.size > 0 || batchCleanups.size > 0) {
|
|
2066
1950
|
// Inner loop: execute all pending effects
|
|
2067
1951
|
while (batchQueue.all.size > 0) {
|
|
2068
|
-
if (effectuatedRoots.length > options.maxEffectChain) {
|
|
1952
|
+
if (effectuatedRoots.length > decorator.options.maxEffectChain) {
|
|
2069
1953
|
const cycle = findCycleInChain(effectuatedRoots);
|
|
2070
1954
|
const trace = formatRoots(effectuatedRoots);
|
|
2071
1955
|
const message = cycle
|
|
@@ -2074,11 +1958,11 @@ function batch(effect, immediate) {
|
|
|
2074
1958
|
const queuedRoots = batchQueue ? Array.from(batchQueue.all.keys()) : [];
|
|
2075
1959
|
const queued = queuedRoots.map((r) => r.name || '<anonymous>');
|
|
2076
1960
|
const debugInfo = {
|
|
2077
|
-
code: ReactiveErrorCode.MaxDepthExceeded,
|
|
1961
|
+
code: decorator.ReactiveErrorCode.MaxDepthExceeded,
|
|
2078
1962
|
effectuatedRoots,
|
|
2079
1963
|
cycle,
|
|
2080
1964
|
trace,
|
|
2081
|
-
maxEffectChain: options.maxEffectChain,
|
|
1965
|
+
maxEffectChain: decorator.options.maxEffectChain,
|
|
2082
1966
|
queued: queued.slice(0, 50),
|
|
2083
1967
|
queuedCount: queued.length,
|
|
2084
1968
|
// Try to get causation for the last effect
|
|
@@ -2086,15 +1970,15 @@ function batch(effect, immediate) {
|
|
|
2086
1970
|
? getTriggerChain(batchQueue.all.get(effectuatedRoots[effectuatedRoots.length - 1]))
|
|
2087
1971
|
: [],
|
|
2088
1972
|
};
|
|
2089
|
-
switch (options.maxEffectReaction) {
|
|
1973
|
+
switch (decorator.options.maxEffectReaction) {
|
|
2090
1974
|
case 'throw':
|
|
2091
|
-
throw new ReactiveError(`[reactive] ${message}`, debugInfo);
|
|
1975
|
+
throw new decorator.ReactiveError(`[reactive] ${message}`, debugInfo);
|
|
2092
1976
|
case 'debug':
|
|
2093
1977
|
// biome-ignore lint/suspicious/noDebugger: This is the whole point here
|
|
2094
1978
|
debugger;
|
|
2095
|
-
throw new ReactiveError(`[reactive] ${message}`, debugInfo);
|
|
1979
|
+
throw new decorator.ReactiveError(`[reactive] ${message}`, debugInfo);
|
|
2096
1980
|
case 'warn':
|
|
2097
|
-
options.warn(`[reactive] ${message} (queued: ${queued.slice(0, 10).join(', ')}${queued.length > 10 ? ', …' : ''})`);
|
|
1981
|
+
decorator.options.warn(`[reactive] ${message} (queued: ${queued.slice(0, 10).join(', ')}${queued.length > 10 ? ', …' : ''})`);
|
|
2098
1982
|
break;
|
|
2099
1983
|
}
|
|
2100
1984
|
}
|
|
@@ -2122,8 +2006,9 @@ function batch(effect, immediate) {
|
|
|
2122
2006
|
return firstReturn.value;
|
|
2123
2007
|
}
|
|
2124
2008
|
finally {
|
|
2009
|
+
activationRegistry = undefined;
|
|
2125
2010
|
batchQueue = undefined;
|
|
2126
|
-
options.endChain();
|
|
2011
|
+
decorator.options.endChain();
|
|
2127
2012
|
}
|
|
2128
2013
|
}
|
|
2129
2014
|
}
|
|
@@ -2134,12 +2019,18 @@ function batch(effect, immediate) {
|
|
|
2134
2019
|
const atomic = decorator.decorator({
|
|
2135
2020
|
method(original) {
|
|
2136
2021
|
return function (...args) {
|
|
2137
|
-
|
|
2022
|
+
const atomicEffect = () => original.apply(this, args);
|
|
2023
|
+
// Debug: helpful to have a name
|
|
2024
|
+
Object.defineProperty(atomicEffect, 'name', { value: `atomic(${original.name})` });
|
|
2025
|
+
return batch(atomicEffect, 'immediate');
|
|
2138
2026
|
};
|
|
2139
2027
|
},
|
|
2140
2028
|
default(original) {
|
|
2141
2029
|
return function (...args) {
|
|
2142
|
-
|
|
2030
|
+
const atomicEffect = () => original.apply(this, args);
|
|
2031
|
+
// Debug: helpful to have a name
|
|
2032
|
+
Object.defineProperty(atomicEffect, 'name', { value: `atomic(${original.name})` });
|
|
2033
|
+
return batch(atomicEffect, 'immediate');
|
|
2143
2034
|
};
|
|
2144
2035
|
},
|
|
2145
2036
|
});
|
|
@@ -2161,8 +2052,8 @@ fn, effectOptions) {
|
|
|
2161
2052
|
// Inject batch function to allow atomic game loops in requestAnimationFrame
|
|
2162
2053
|
ensureZoneHooked(batch);
|
|
2163
2054
|
// Use per-effect asyncMode or fall back to global option
|
|
2164
|
-
const asyncMode = effectOptions?.asyncMode ?? options.asyncMode ?? 'cancel';
|
|
2165
|
-
if (options.introspection.enableHistory) {
|
|
2055
|
+
const asyncMode = effectOptions?.asyncMode ?? decorator.options.asyncMode ?? 'cancel';
|
|
2056
|
+
if (decorator.options.introspection.enableHistory) {
|
|
2166
2057
|
const stack = new Error().stack;
|
|
2167
2058
|
if (stack) {
|
|
2168
2059
|
// Clean up the stack trace to remove internal frames
|
|
@@ -2173,7 +2064,7 @@ fn, effectOptions) {
|
|
|
2173
2064
|
let cleanup = null;
|
|
2174
2065
|
// capture the parent effect at creation time for ascend
|
|
2175
2066
|
const parentsForAscend = captureEffectStack();
|
|
2176
|
-
const tracked =
|
|
2067
|
+
const tracked = (cb) => withEffect(runEffect, cb);
|
|
2177
2068
|
const ascend = (cb) => withEffectStack(parentsForAscend, cb);
|
|
2178
2069
|
let effectStopped = false;
|
|
2179
2070
|
let hasReacted = false;
|
|
@@ -2203,7 +2094,7 @@ fn, effectOptions) {
|
|
|
2203
2094
|
// The effect has been stopped after having been planned
|
|
2204
2095
|
if (effectStopped)
|
|
2205
2096
|
return;
|
|
2206
|
-
options.enter(getRoot(fn));
|
|
2097
|
+
decorator.options.enter(getRoot(fn));
|
|
2207
2098
|
let reactionCleanup;
|
|
2208
2099
|
let result;
|
|
2209
2100
|
try {
|
|
@@ -2211,7 +2102,7 @@ fn, effectOptions) {
|
|
|
2211
2102
|
if (result &&
|
|
2212
2103
|
typeof result !== 'function' &&
|
|
2213
2104
|
(typeof result !== 'object' || !('then' in result)))
|
|
2214
|
-
throw new ReactiveError(`[reactive] Effect returned a non-function value: ${result}`);
|
|
2105
|
+
throw new decorator.ReactiveError(`[reactive] Effect returned a non-function value: ${result}`);
|
|
2215
2106
|
// Check if result is a Promise (async effect)
|
|
2216
2107
|
if (result && typeof result === 'object' && typeof result.then === 'function') {
|
|
2217
2108
|
const originalPromise = result;
|
|
@@ -2220,7 +2111,7 @@ fn, effectOptions) {
|
|
|
2220
2111
|
const cancelPromise = new Promise((_, reject) => {
|
|
2221
2112
|
cancelReject = reject;
|
|
2222
2113
|
});
|
|
2223
|
-
const cancelError = new ReactiveError('[reactive] Effect canceled due to dependency change');
|
|
2114
|
+
const cancelError = new decorator.ReactiveError('[reactive] Effect canceled due to dependency change');
|
|
2224
2115
|
// Race between the actual promise and cancellation
|
|
2225
2116
|
// If canceled, the race rejects, which will propagate through any promise chain
|
|
2226
2117
|
runningPromise = Promise.race([originalPromise, cancelPromise]);
|
|
@@ -2242,7 +2133,7 @@ fn, effectOptions) {
|
|
|
2242
2133
|
}
|
|
2243
2134
|
finally {
|
|
2244
2135
|
hasReacted = true;
|
|
2245
|
-
options.leave(fn);
|
|
2136
|
+
decorator.options.leave(fn);
|
|
2246
2137
|
}
|
|
2247
2138
|
// Create cleanup function for next run
|
|
2248
2139
|
cleanup = () => {
|
|
@@ -2306,11 +2197,28 @@ fn, effectOptions) {
|
|
|
2306
2197
|
cleanupEffectFromGraph(runEffect);
|
|
2307
2198
|
fr.unregister(stopEffect);
|
|
2308
2199
|
};
|
|
2200
|
+
function augmentedRv(rv) {
|
|
2201
|
+
Object.defineProperty(rv, decorator.stopped, {
|
|
2202
|
+
get() {
|
|
2203
|
+
return effectStopped;
|
|
2204
|
+
},
|
|
2205
|
+
});
|
|
2206
|
+
Object.defineProperty(rv, decorator.cleanup, {
|
|
2207
|
+
value: () => {
|
|
2208
|
+
if (cleanup) {
|
|
2209
|
+
const prevCleanup = cleanup;
|
|
2210
|
+
cleanup = null;
|
|
2211
|
+
withEffect(undefined, () => prevCleanup());
|
|
2212
|
+
}
|
|
2213
|
+
},
|
|
2214
|
+
});
|
|
2215
|
+
return rv;
|
|
2216
|
+
}
|
|
2309
2217
|
if (isRootEffect) {
|
|
2310
|
-
const callIfCollected = () => stopEffect();
|
|
2218
|
+
const callIfCollected = augmentedRv(() => stopEffect());
|
|
2311
2219
|
fr.register(callIfCollected, () => {
|
|
2312
2220
|
stopEffect();
|
|
2313
|
-
options.garbageCollected(fn);
|
|
2221
|
+
decorator.options.garbageCollected(fn);
|
|
2314
2222
|
}, stopEffect);
|
|
2315
2223
|
return callIfCollected;
|
|
2316
2224
|
}
|
|
@@ -2320,14 +2228,14 @@ fn, effectOptions) {
|
|
|
2320
2228
|
children = new Set();
|
|
2321
2229
|
effectChildren.set(parent, children);
|
|
2322
2230
|
}
|
|
2323
|
-
const subEffectCleanup = () => {
|
|
2231
|
+
const subEffectCleanup = augmentedRv(() => {
|
|
2324
2232
|
children.delete(subEffectCleanup);
|
|
2325
2233
|
if (children.size === 0) {
|
|
2326
2234
|
effectChildren.delete(parent);
|
|
2327
2235
|
}
|
|
2328
2236
|
// Execute this child effect cleanup (which triggers its own mainCleanup)
|
|
2329
2237
|
stopEffect();
|
|
2330
|
-
};
|
|
2238
|
+
});
|
|
2331
2239
|
children.add(subEffectCleanup);
|
|
2332
2240
|
return subEffectCleanup;
|
|
2333
2241
|
}
|
|
@@ -2488,10 +2396,14 @@ function collectEffects(obj, evolution, effects, objectWatchers, ...keyChains) {
|
|
|
2488
2396
|
for (const effect of deps) {
|
|
2489
2397
|
const runningChain = isRunning(effect);
|
|
2490
2398
|
if (runningChain) {
|
|
2491
|
-
options.skipRunningEffect(effect, runningChain);
|
|
2399
|
+
decorator.options.skipRunningEffect(effect, runningChain);
|
|
2492
2400
|
continue;
|
|
2493
2401
|
}
|
|
2494
|
-
effects.
|
|
2402
|
+
if (!effects.has(effect)) {
|
|
2403
|
+
effects.add(effect);
|
|
2404
|
+
if (!hasBatched(effect))
|
|
2405
|
+
recordActivation(effect, obj, evolution, key);
|
|
2406
|
+
}
|
|
2495
2407
|
const trackers = effectTrackers.get(effect);
|
|
2496
2408
|
recordTriggerLink(sourceEffect, effect, obj, key, evolution);
|
|
2497
2409
|
if (trackers) {
|
|
@@ -2525,10 +2437,10 @@ function touched(obj, evolution, props) {
|
|
|
2525
2437
|
// Note: we have to collect effects to remove duplicates in the specific case when no batch is running
|
|
2526
2438
|
const effects = new Set();
|
|
2527
2439
|
if (props)
|
|
2528
|
-
collectEffects(obj, evolution, effects, objectWatchers, [allProps], props);
|
|
2440
|
+
collectEffects(obj, evolution, effects, objectWatchers, [decorator.allProps], props);
|
|
2529
2441
|
else
|
|
2530
2442
|
collectEffects(obj, evolution, effects, objectWatchers, objectWatchers.keys());
|
|
2531
|
-
options.touched(obj, evolution, props, effects);
|
|
2443
|
+
decorator.options.touched(obj, evolution, props, effects);
|
|
2532
2444
|
batch(Array.from(effects));
|
|
2533
2445
|
}
|
|
2534
2446
|
// Bubble up changes if this object has deep watchers
|
|
@@ -2555,10 +2467,11 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
2555
2467
|
continue;
|
|
2556
2468
|
const runningChain = isRunning(effect);
|
|
2557
2469
|
if (runningChain) {
|
|
2558
|
-
options.skipRunningEffect(effect, runningChain);
|
|
2470
|
+
decorator.options.skipRunningEffect(effect, runningChain);
|
|
2559
2471
|
continue;
|
|
2560
2472
|
}
|
|
2561
2473
|
effects.add(effect);
|
|
2474
|
+
recordActivation(effect, obj, evolution, prop);
|
|
2562
2475
|
const trackers = effectTrackers.get(effect);
|
|
2563
2476
|
recordTriggerLink(sourceEffect, effect, obj, prop, evolution);
|
|
2564
2477
|
if (trackers) {
|
|
@@ -2568,7 +2481,7 @@ function touchedOpaque(obj, evolution, prop) {
|
|
|
2568
2481
|
}
|
|
2569
2482
|
}
|
|
2570
2483
|
if (effects.size > 0) {
|
|
2571
|
-
options.touched(obj, evolution, [prop], effects);
|
|
2484
|
+
decorator.options.touched(obj, evolution, [prop], effects);
|
|
2572
2485
|
batch(Array.from(effects));
|
|
2573
2486
|
}
|
|
2574
2487
|
}
|
|
@@ -2579,7 +2492,7 @@ const absent = Symbol('absent');
|
|
|
2579
2492
|
function markNonReactive(...obj) {
|
|
2580
2493
|
for (const o of obj) {
|
|
2581
2494
|
try {
|
|
2582
|
-
Object.defineProperty(o, nonReactiveMark, {
|
|
2495
|
+
Object.defineProperty(o, decorator.nonReactiveMark, {
|
|
2583
2496
|
value: true,
|
|
2584
2497
|
writable: false,
|
|
2585
2498
|
enumerable: false,
|
|
@@ -2587,7 +2500,7 @@ function markNonReactive(...obj) {
|
|
|
2587
2500
|
});
|
|
2588
2501
|
}
|
|
2589
2502
|
catch { }
|
|
2590
|
-
if (!(nonReactiveMark in o))
|
|
2503
|
+
if (!(decorator.nonReactiveMark in o))
|
|
2591
2504
|
nonReactiveObjects.add(o);
|
|
2592
2505
|
}
|
|
2593
2506
|
return obj[0];
|
|
@@ -2595,7 +2508,7 @@ function markNonReactive(...obj) {
|
|
|
2595
2508
|
function nonReactiveClass(...cls) {
|
|
2596
2509
|
for (const c of cls)
|
|
2597
2510
|
if (c)
|
|
2598
|
-
c.prototype[nonReactiveMark] = true;
|
|
2511
|
+
c.prototype[decorator.nonReactiveMark] = true;
|
|
2599
2512
|
return cls[0];
|
|
2600
2513
|
}
|
|
2601
2514
|
function isNonReactive(obj) {
|
|
@@ -2603,7 +2516,7 @@ function isNonReactive(obj) {
|
|
|
2603
2516
|
return true;
|
|
2604
2517
|
if (nonReactiveObjects.has(obj))
|
|
2605
2518
|
return true;
|
|
2606
|
-
if (obj[nonReactiveMark])
|
|
2519
|
+
if (obj[decorator.nonReactiveMark])
|
|
2607
2520
|
return true;
|
|
2608
2521
|
for (const fn of immutables)
|
|
2609
2522
|
if (fn(obj))
|
|
@@ -2611,7 +2524,7 @@ function isNonReactive(obj) {
|
|
|
2611
2524
|
return false;
|
|
2612
2525
|
}
|
|
2613
2526
|
function registerNativeReactivity(originalClass, reactiveClass) {
|
|
2614
|
-
originalClass.prototype[nativeReactive] = reactiveClass;
|
|
2527
|
+
originalClass.prototype[decorator.nativeReactive] = reactiveClass;
|
|
2615
2528
|
nonReactiveClass(reactiveClass);
|
|
2616
2529
|
}
|
|
2617
2530
|
nonReactiveClass(Date, RegExp, Error, Promise, Function);
|
|
@@ -2657,7 +2570,7 @@ function shouldRecurseTouch(oldValue, newValue) {
|
|
|
2657
2570
|
*/
|
|
2658
2571
|
function notifyPropertyChange(targetObj, prop, oldValue, newValue, hadProperty) {
|
|
2659
2572
|
const evolution = { type: hadProperty ? 'set' : 'add', prop };
|
|
2660
|
-
if (options.recursiveTouching &&
|
|
2573
|
+
if (decorator.options.recursiveTouching &&
|
|
2661
2574
|
oldValue !== undefined &&
|
|
2662
2575
|
shouldRecurseTouch(oldValue, newValue)) {
|
|
2663
2576
|
const unwrappedObj = unwrap(targetObj);
|
|
@@ -2794,7 +2707,7 @@ function dispatchNotifications(notifications) {
|
|
|
2794
2707
|
const originWatchers = watchers.get(origin.obj);
|
|
2795
2708
|
if (originWatchers) {
|
|
2796
2709
|
const originEffects = new Set();
|
|
2797
|
-
collectEffects(origin.obj, { type: 'set', prop: origin.prop }, originEffects, originWatchers, [allProps], [origin.prop]);
|
|
2710
|
+
collectEffects(origin.obj, { type: 'set', prop: origin.prop }, originEffects, originWatchers, [decorator.allProps], [origin.prop]);
|
|
2798
2711
|
for (const effect of originEffects)
|
|
2799
2712
|
allowedEffects.add(effect);
|
|
2800
2713
|
}
|
|
@@ -2812,7 +2725,7 @@ function dispatchNotifications(notifications) {
|
|
|
2812
2725
|
const propsArray = [prop];
|
|
2813
2726
|
if (objectWatchers) {
|
|
2814
2727
|
currentEffects = new Set();
|
|
2815
|
-
collectEffects(obj, evolution, currentEffects, objectWatchers, [allProps], propsArray);
|
|
2728
|
+
collectEffects(obj, evolution, currentEffects, objectWatchers, [decorator.allProps], propsArray);
|
|
2816
2729
|
// Filter effects by ancestor chain if origin exists
|
|
2817
2730
|
// Include effects that either directly depend on origin or have an ancestor that does
|
|
2818
2731
|
if (origin && allowedEffects) {
|
|
@@ -2828,7 +2741,7 @@ function dispatchNotifications(notifications) {
|
|
|
2828
2741
|
for (const effect of currentEffects)
|
|
2829
2742
|
combinedEffects.add(effect);
|
|
2830
2743
|
}
|
|
2831
|
-
options.touched(obj, evolution, propsArray, currentEffects);
|
|
2744
|
+
decorator.options.touched(obj, evolution, propsArray, currentEffects);
|
|
2832
2745
|
if (objectsWithDeepWatchers.has(obj))
|
|
2833
2746
|
bubbleUpChange(obj);
|
|
2834
2747
|
}
|
|
@@ -2840,18 +2753,18 @@ const hasReentry = [];
|
|
|
2840
2753
|
const reactiveHandlers = {
|
|
2841
2754
|
[Symbol.toStringTag]: 'MutTs Reactive',
|
|
2842
2755
|
get(obj, prop, receiver) {
|
|
2843
|
-
if (prop === nonReactiveMark)
|
|
2756
|
+
if (prop === decorator.nonReactiveMark)
|
|
2844
2757
|
return false;
|
|
2845
2758
|
const unwrappedObj = unwrap(obj);
|
|
2846
2759
|
// Check if this property is marked as unreactive
|
|
2847
|
-
if (unwrappedObj[unreactiveProperties]?.has(prop) || typeof prop === 'symbol')
|
|
2760
|
+
if (unwrappedObj[decorator.unreactiveProperties]?.has(prop) || typeof prop === 'symbol')
|
|
2848
2761
|
return decorator.ReflectGet(obj, prop, receiver);
|
|
2849
2762
|
// Special-case: array wrappers use prototype forwarding + numeric accessors.
|
|
2850
2763
|
// With options.instanceMembers=true, inherited reads are normally not tracked, which breaks
|
|
2851
2764
|
// reactivity for array indices/length (they appear inherited on the proxy).
|
|
2852
|
-
const isArrayCase = prototypeForwarding in obj &&
|
|
2765
|
+
const isArrayCase = decorator.prototypeForwarding in obj &&
|
|
2853
2766
|
// biome-ignore lint/suspicious/useIsArray: This is the whole point here
|
|
2854
|
-
obj[prototypeForwarding] instanceof Array &&
|
|
2767
|
+
obj[decorator.prototypeForwarding] instanceof Array &&
|
|
2855
2768
|
typeof prop === 'string' &&
|
|
2856
2769
|
(prop === 'length' || !Number.isNaN(Number(prop)));
|
|
2857
2770
|
if (isArrayCase) {
|
|
@@ -2863,16 +2776,16 @@ const reactiveHandlers = {
|
|
|
2863
2776
|
const isInheritedAccess = hasProp && !isOwnProp;
|
|
2864
2777
|
// For accessor properties, check the unwrapped object to see if it's an accessor
|
|
2865
2778
|
// This ensures ignoreAccessors works correctly even after operations like Object.setPrototypeOf
|
|
2866
|
-
const shouldIgnoreAccessor = options.ignoreAccessors &&
|
|
2779
|
+
const shouldIgnoreAccessor = decorator.options.ignoreAccessors &&
|
|
2867
2780
|
isOwnProp &&
|
|
2868
2781
|
(decorator.isOwnAccessor(receiver, prop) || decorator.isOwnAccessor(unwrappedObj, prop));
|
|
2869
2782
|
// Depend if...
|
|
2870
2783
|
if (!hasProp ||
|
|
2871
|
-
(!(options.instanceMembers && isInheritedAccess && obj instanceof Object) &&
|
|
2784
|
+
(!(decorator.options.instanceMembers && isInheritedAccess && obj instanceof Object) &&
|
|
2872
2785
|
!shouldIgnoreAccessor))
|
|
2873
2786
|
dependant(obj, prop);
|
|
2874
2787
|
// Watch the whole prototype chain when requested or for null-proto objects
|
|
2875
|
-
if (isInheritedAccess && (!options.instanceMembers || !(obj instanceof Object))) {
|
|
2788
|
+
if (isInheritedAccess && (!decorator.options.instanceMembers || !(obj instanceof Object))) {
|
|
2876
2789
|
let current = reactiveObject(Object.getPrototypeOf(obj));
|
|
2877
2790
|
while (current && current !== Object.prototype) {
|
|
2878
2791
|
dependant(current, prop);
|
|
@@ -2901,12 +2814,12 @@ const reactiveHandlers = {
|
|
|
2901
2814
|
const unwrappedObj = unwrap(obj);
|
|
2902
2815
|
const unwrappedReceiver = unwrap(receiver);
|
|
2903
2816
|
// Check if this property is marked as unreactive
|
|
2904
|
-
if (unwrappedObj[unreactiveProperties]?.has(prop) || unwrappedObj !== unwrappedReceiver)
|
|
2817
|
+
if (unwrappedObj[decorator.unreactiveProperties]?.has(prop) || unwrappedObj !== unwrappedReceiver)
|
|
2905
2818
|
return decorator.ReflectSet(obj, prop, value, receiver);
|
|
2906
2819
|
// Really specific case for when Array is forwarder, in order to let it manage the reactivity
|
|
2907
|
-
const isArrayCase = prototypeForwarding in obj &&
|
|
2820
|
+
const isArrayCase = decorator.prototypeForwarding in obj &&
|
|
2908
2821
|
// biome-ignore lint/suspicious/useIsArray: This is the whole point here
|
|
2909
|
-
obj[prototypeForwarding] instanceof Array &&
|
|
2822
|
+
obj[decorator.prototypeForwarding] instanceof Array &&
|
|
2910
2823
|
(!Number.isNaN(Number(prop)) || prop === 'length');
|
|
2911
2824
|
const newValue = unwrap(value);
|
|
2912
2825
|
if (isArrayCase) {
|
|
@@ -2921,13 +2834,13 @@ const reactiveHandlers = {
|
|
|
2921
2834
|
const receiverDesc = Object.getOwnPropertyDescriptor(unwrappedReceiver, prop);
|
|
2922
2835
|
const targetDesc = Object.getOwnPropertyDescriptor(unwrappedObj, prop);
|
|
2923
2836
|
const desc = receiverDesc || targetDesc;
|
|
2924
|
-
//
|
|
2925
|
-
//
|
|
2837
|
+
// We *need* to use `receiver` and not `unwrappedObj` here, otherwise we break
|
|
2838
|
+
// the dependency tracking for memoized getters
|
|
2926
2839
|
if (desc?.get && !desc?.set) {
|
|
2927
|
-
oldVal = withEffect(undefined, () => Reflect.get(unwrappedObj, prop,
|
|
2840
|
+
oldVal = withEffect(undefined, () => Reflect.get(unwrappedObj, prop, receiver));
|
|
2928
2841
|
}
|
|
2929
2842
|
else {
|
|
2930
|
-
oldVal = Reflect.get(unwrappedObj, prop,
|
|
2843
|
+
oldVal = withEffect(undefined, () => Reflect.get(unwrappedObj, prop, receiver));
|
|
2931
2844
|
}
|
|
2932
2845
|
}
|
|
2933
2846
|
if (objectsWithDeepWatchers.has(obj)) {
|
|
@@ -2950,8 +2863,8 @@ const reactiveHandlers = {
|
|
|
2950
2863
|
},
|
|
2951
2864
|
has(obj, prop) {
|
|
2952
2865
|
if (hasReentry.includes(obj))
|
|
2953
|
-
throw new ReactiveError(`[reactive] Circular dependency detected in 'has' check for property '${String(prop)}'`, {
|
|
2954
|
-
code: ReactiveErrorCode.CycleDetected,
|
|
2866
|
+
throw new decorator.ReactiveError(`[reactive] Circular dependency detected in 'has' check for property '${String(prop)}'`, {
|
|
2867
|
+
code: decorator.ReactiveErrorCode.CycleDetected,
|
|
2955
2868
|
cycle: [], // We don't have the full cycle here, but we know it involves obj
|
|
2956
2869
|
});
|
|
2957
2870
|
hasReentry.push(obj);
|
|
@@ -2977,18 +2890,18 @@ const reactiveHandlers = {
|
|
|
2977
2890
|
return true;
|
|
2978
2891
|
},
|
|
2979
2892
|
getPrototypeOf(obj) {
|
|
2980
|
-
if (prototypeForwarding in obj)
|
|
2981
|
-
return obj[prototypeForwarding];
|
|
2893
|
+
if (decorator.prototypeForwarding in obj)
|
|
2894
|
+
return obj[decorator.prototypeForwarding];
|
|
2982
2895
|
return Object.getPrototypeOf(obj);
|
|
2983
2896
|
},
|
|
2984
2897
|
setPrototypeOf(obj, proto) {
|
|
2985
|
-
if (prototypeForwarding in obj)
|
|
2898
|
+
if (decorator.prototypeForwarding in obj)
|
|
2986
2899
|
return false;
|
|
2987
2900
|
Object.setPrototypeOf(obj, proto);
|
|
2988
2901
|
return true;
|
|
2989
2902
|
},
|
|
2990
2903
|
ownKeys(obj) {
|
|
2991
|
-
dependant(obj, allProps);
|
|
2904
|
+
dependant(obj, decorator.allProps);
|
|
2992
2905
|
return Reflect.ownKeys(obj);
|
|
2993
2906
|
},
|
|
2994
2907
|
};
|
|
@@ -3024,8 +2937,8 @@ function reactiveObject(anyTarget) {
|
|
|
3024
2937
|
const existing = getExistingProxy(target);
|
|
3025
2938
|
if (existing !== undefined)
|
|
3026
2939
|
return existing;
|
|
3027
|
-
const proxied = nativeReactive in target && !(target instanceof target[nativeReactive])
|
|
3028
|
-
? new target[nativeReactive](target)
|
|
2940
|
+
const proxied = decorator.nativeReactive in target && !(target instanceof target[decorator.nativeReactive])
|
|
2941
|
+
? new target[decorator.nativeReactive](target)
|
|
3029
2942
|
: target;
|
|
3030
2943
|
if (proxied !== target)
|
|
3031
2944
|
trackProxyObject(proxied, target);
|
|
@@ -3048,7 +2961,7 @@ const reactive = decorator.decorator({
|
|
|
3048
2961
|
constructor(...args) {
|
|
3049
2962
|
super(...args);
|
|
3050
2963
|
if (new.target !== Reactive && !reactiveClasses.has(new.target))
|
|
3051
|
-
options.warn(`${original.name} has been inherited by ${this.constructor.name} that is not reactive.
|
|
2964
|
+
decorator.options.warn(`${original.name} has been inherited by ${this.constructor.name} that is not reactive.
|
|
3052
2965
|
@reactive decorator must be applied to the leaf class OR classes have to extend ReactiveBase.`);
|
|
3053
2966
|
// biome-ignore lint/correctness/noConstructorReturn: This is the whole point here
|
|
3054
2967
|
return reactive(this);
|
|
@@ -3105,7 +3018,7 @@ function deepWatch(target, callback, { immediate = false } = {}) {
|
|
|
3105
3018
|
const visited = new WeakSet();
|
|
3106
3019
|
function traverseAndTrack(obj, depth = 0) {
|
|
3107
3020
|
// Prevent infinite recursion and excessive depth
|
|
3108
|
-
if (!obj || visited.has(obj) || !isObject(obj) || depth > options.maxDeepWatchDepth)
|
|
3021
|
+
if (!obj || visited.has(obj) || !isObject(obj) || depth > decorator.options.maxDeepWatchDepth)
|
|
3109
3022
|
return;
|
|
3110
3023
|
// Do not traverse into unreactive objects
|
|
3111
3024
|
if (isNonReactive(obj))
|
|
@@ -3217,12 +3130,12 @@ function watchObject(value, changed, { immediate = false, deep = false } = {}) {
|
|
|
3217
3130
|
const myParentEffect = getActiveEffect();
|
|
3218
3131
|
if (deep)
|
|
3219
3132
|
return deepWatch(value, changed, { immediate });
|
|
3220
|
-
return effect(
|
|
3133
|
+
return effect(function watchObjectEffect() {
|
|
3221
3134
|
dependant(value);
|
|
3222
3135
|
if (immediate)
|
|
3223
3136
|
withEffect(myParentEffect, () => changed(value));
|
|
3224
3137
|
immediate = true;
|
|
3225
|
-
}
|
|
3138
|
+
});
|
|
3226
3139
|
}
|
|
3227
3140
|
function watchCallBack(value, changed, { immediate = false, deep = false } = {}) {
|
|
3228
3141
|
const myParentEffect = getActiveEffect();
|
|
@@ -3231,7 +3144,7 @@ function watchCallBack(value, changed, { immediate = false, deep = false } = {})
|
|
|
3231
3144
|
const cbCleanup = effect(markWithRoot(function watchCallBackEffect(access) {
|
|
3232
3145
|
const newValue = value(access);
|
|
3233
3146
|
if (oldValue !== newValue)
|
|
3234
|
-
withEffect(myParentEffect,
|
|
3147
|
+
withEffect(myParentEffect, () => {
|
|
3235
3148
|
if (oldValue === unsetYet) {
|
|
3236
3149
|
if (immediate)
|
|
3237
3150
|
changed(newValue);
|
|
@@ -3242,9 +3155,9 @@ function watchCallBack(value, changed, { immediate = false, deep = false } = {})
|
|
|
3242
3155
|
if (deep) {
|
|
3243
3156
|
if (deepCleanup)
|
|
3244
3157
|
deepCleanup();
|
|
3245
|
-
deepCleanup = deepWatch(newValue,
|
|
3158
|
+
deepCleanup = deepWatch(newValue, (value) => changed(value, value));
|
|
3246
3159
|
}
|
|
3247
|
-
}
|
|
3160
|
+
});
|
|
3248
3161
|
}, value));
|
|
3249
3162
|
return () => {
|
|
3250
3163
|
cbCleanup();
|
|
@@ -3263,7 +3176,7 @@ function deepNonReactive(obj) {
|
|
|
3263
3176
|
if (isNonReactive(obj))
|
|
3264
3177
|
return obj;
|
|
3265
3178
|
try {
|
|
3266
|
-
Object.defineProperty(obj, nonReactiveMark, {
|
|
3179
|
+
Object.defineProperty(obj, decorator.nonReactiveMark, {
|
|
3267
3180
|
value: true,
|
|
3268
3181
|
writable: false,
|
|
3269
3182
|
enumerable: false,
|
|
@@ -3271,7 +3184,7 @@ function deepNonReactive(obj) {
|
|
|
3271
3184
|
});
|
|
3272
3185
|
}
|
|
3273
3186
|
catch { }
|
|
3274
|
-
if (!(nonReactiveMark in obj))
|
|
3187
|
+
if (!(decorator.nonReactiveMark in obj))
|
|
3275
3188
|
nonReactiveObjects.add(obj);
|
|
3276
3189
|
//for (const key in obj) deepNonReactive(obj[key])
|
|
3277
3190
|
return obj;
|
|
@@ -3281,11 +3194,11 @@ function unreactiveApplication(arg1, ...args) {
|
|
|
3281
3194
|
? deepNonReactive(arg1)
|
|
3282
3195
|
: ((original) => {
|
|
3283
3196
|
// Copy the parent's unreactive properties if they exist
|
|
3284
|
-
original.prototype[unreactiveProperties] = new Set(original.prototype[unreactiveProperties] || []);
|
|
3197
|
+
original.prototype[decorator.unreactiveProperties] = new Set(original.prototype[decorator.unreactiveProperties] || []);
|
|
3285
3198
|
// Add all arguments (including the first one)
|
|
3286
|
-
original.prototype[unreactiveProperties].add(arg1);
|
|
3199
|
+
original.prototype[decorator.unreactiveProperties].add(arg1);
|
|
3287
3200
|
for (const arg of args)
|
|
3288
|
-
original.prototype[unreactiveProperties].add(arg);
|
|
3201
|
+
original.prototype[decorator.unreactiveProperties].add(arg);
|
|
3289
3202
|
return original; // Return the class
|
|
3290
3203
|
});
|
|
3291
3204
|
}
|
|
@@ -3317,9 +3230,9 @@ function cleanedBy(obj, cleanupFn) {
|
|
|
3317
3230
|
*/
|
|
3318
3231
|
function derived(compute) {
|
|
3319
3232
|
const rv = { value: undefined };
|
|
3320
|
-
return cleanedBy(rv, untracked(() => effect(
|
|
3233
|
+
return cleanedBy(rv, untracked(() => effect(function derivedEffect(access) {
|
|
3321
3234
|
rv.value = compute(access);
|
|
3322
|
-
}
|
|
3235
|
+
})));
|
|
3323
3236
|
}
|
|
3324
3237
|
|
|
3325
3238
|
/**
|
|
@@ -3347,11 +3260,10 @@ function* makeReactiveEntriesIterator(iterator) {
|
|
|
3347
3260
|
const native$2 = Symbol('native');
|
|
3348
3261
|
const isArray = Array.isArray;
|
|
3349
3262
|
Array.isArray = ((value) => isArray(value) ||
|
|
3350
|
-
// biome-ignore lint/suspicious/useIsArray: We are defining it
|
|
3351
3263
|
(value &&
|
|
3352
3264
|
typeof value === 'object' &&
|
|
3353
|
-
prototypeForwarding in value &&
|
|
3354
|
-
Array.isArray(value[prototypeForwarding])));
|
|
3265
|
+
decorator.prototypeForwarding in value &&
|
|
3266
|
+
Array.isArray(value[decorator.prototypeForwarding])));
|
|
3355
3267
|
class ReactiveBaseArray {
|
|
3356
3268
|
// Safe array access with negative indices
|
|
3357
3269
|
at(index) {
|
|
@@ -3564,7 +3476,7 @@ class ReactiveArray extends indexable.Indexable(ReactiveBaseArray, {
|
|
|
3564
3476
|
Object.defineProperties(this, {
|
|
3565
3477
|
// We have to make it double, as [native] must be `unique symbol` - impossible through import
|
|
3566
3478
|
[native$2]: { value: original },
|
|
3567
|
-
[prototypeForwarding]: { value: original },
|
|
3479
|
+
[decorator.prototypeForwarding]: { value: original },
|
|
3568
3480
|
});
|
|
3569
3481
|
}
|
|
3570
3482
|
push(...items) {
|
|
@@ -3695,7 +3607,7 @@ class ReactiveReadOnlyArrayClass extends indexable.Indexable(ReactiveBaseArray,
|
|
|
3695
3607
|
Object.defineProperties(this, {
|
|
3696
3608
|
// We have to make it double, as [native] must be `unique symbol` - impossible through import
|
|
3697
3609
|
[native$2]: { value: original },
|
|
3698
|
-
[prototypeForwarding]: { value: original },
|
|
3610
|
+
[decorator.prototypeForwarding]: { value: original },
|
|
3699
3611
|
});
|
|
3700
3612
|
}
|
|
3701
3613
|
push(..._items) {
|
|
@@ -3776,6 +3688,7 @@ function reduced(inputs, compute) {
|
|
|
3776
3688
|
}
|
|
3777
3689
|
|
|
3778
3690
|
const memoizedRegistry = new WeakMap();
|
|
3691
|
+
const wrapperRegistry = new WeakMap();
|
|
3779
3692
|
function getBranch(tree, key) {
|
|
3780
3693
|
tree.branches ?? (tree.branches = new WeakMap());
|
|
3781
3694
|
let branch = tree.branches.get(key);
|
|
@@ -3796,15 +3709,30 @@ function memoizeFunction(fn) {
|
|
|
3796
3709
|
if (localArgs.some((arg) => !(arg && ['object', 'symbol', 'function'].includes(typeof arg))))
|
|
3797
3710
|
throw new Error('memoize expects non-null object arguments');
|
|
3798
3711
|
let node = cacheRoot;
|
|
3712
|
+
// Note: decorators add `this` as first argument
|
|
3799
3713
|
for (const arg of localArgs) {
|
|
3800
3714
|
node = getBranch(node, arg);
|
|
3801
3715
|
}
|
|
3802
3716
|
dependant(node, 'memoize');
|
|
3803
|
-
if ('result' in node)
|
|
3717
|
+
if ('result' in node) {
|
|
3718
|
+
if (decorator.options.onMemoizationDiscrepancy) {
|
|
3719
|
+
const wasVerification = decorator.options.isVerificationRun;
|
|
3720
|
+
decorator.options.isVerificationRun = true;
|
|
3721
|
+
try {
|
|
3722
|
+
const fresh = untracked(() => fn(...localArgs));
|
|
3723
|
+
if (!decorator.deepCompare(node.result, fresh)) {
|
|
3724
|
+
decorator.options.onMemoizationDiscrepancy(node.result, fresh, fn, localArgs, 'calculation');
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
finally {
|
|
3728
|
+
decorator.options.isVerificationRun = wasVerification;
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3804
3731
|
return node.result;
|
|
3732
|
+
}
|
|
3805
3733
|
// Create memoize internal effect to track dependencies and invalidate cache
|
|
3806
3734
|
// Use untracked to prevent the effect creation from being affected by parent effects
|
|
3807
|
-
node.cleanup = root(() => effect(
|
|
3735
|
+
node.cleanup = root(() => effect(() => {
|
|
3808
3736
|
// Execute the function and track its dependencies
|
|
3809
3737
|
// The function execution will automatically track dependencies on reactive objects
|
|
3810
3738
|
node.result = fn(...localArgs);
|
|
@@ -3812,8 +3740,27 @@ function memoizeFunction(fn) {
|
|
|
3812
3740
|
// When dependencies change, clear the cache and notify consumers
|
|
3813
3741
|
delete node.result;
|
|
3814
3742
|
touched1(node, { type: 'invalidate', prop: localArgs }, 'memoize');
|
|
3743
|
+
// Lazy memoization: stop the effect so it doesn't re-run immediately.
|
|
3744
|
+
// It will be re-created on next access.
|
|
3745
|
+
if (node.cleanup) {
|
|
3746
|
+
node.cleanup();
|
|
3747
|
+
node.cleanup = undefined;
|
|
3748
|
+
}
|
|
3815
3749
|
};
|
|
3816
|
-
},
|
|
3750
|
+
}, { opaque: true }));
|
|
3751
|
+
if (decorator.options.onMemoizationDiscrepancy) {
|
|
3752
|
+
const wasVerification = decorator.options.isVerificationRun;
|
|
3753
|
+
decorator.options.isVerificationRun = true;
|
|
3754
|
+
try {
|
|
3755
|
+
const fresh = untracked(() => fn(...localArgs));
|
|
3756
|
+
if (!decorator.deepCompare(node.result, fresh)) {
|
|
3757
|
+
decorator.options.onMemoizationDiscrepancy(node.result, fresh, fn, localArgs, 'comparison');
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
finally {
|
|
3761
|
+
decorator.options.isVerificationRun = wasVerification;
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3817
3764
|
return node.result;
|
|
3818
3765
|
}, fn);
|
|
3819
3766
|
memoizedRegistry.set(fnRoot, memoized);
|
|
@@ -3821,19 +3768,37 @@ function memoizeFunction(fn) {
|
|
|
3821
3768
|
return memoized;
|
|
3822
3769
|
}
|
|
3823
3770
|
const memoize = decorator.decorator({
|
|
3824
|
-
getter(original, propertyKey) {
|
|
3825
|
-
const memoized = memoizeFunction(markWithRoot(decorator.renamed((that) => {
|
|
3826
|
-
return original.call(that);
|
|
3827
|
-
}, `${String(this.constructor.name)}.${String(propertyKey)}`), original));
|
|
3771
|
+
getter(original, target, propertyKey) {
|
|
3828
3772
|
return function () {
|
|
3773
|
+
let wrapper = wrapperRegistry.get(original);
|
|
3774
|
+
if (!wrapper) {
|
|
3775
|
+
wrapper = markWithRoot(decorator.renamed((that) => {
|
|
3776
|
+
return original.call(that);
|
|
3777
|
+
}, `${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(propertyKey)}`), {
|
|
3778
|
+
method: original,
|
|
3779
|
+
propertyKey,
|
|
3780
|
+
...(original[decorator.rootFunction] ? { [decorator.rootFunction]: original[decorator.rootFunction] } : {}),
|
|
3781
|
+
});
|
|
3782
|
+
wrapperRegistry.set(original, wrapper);
|
|
3783
|
+
}
|
|
3784
|
+
const memoized = memoizeFunction(wrapper);
|
|
3829
3785
|
return memoized(this);
|
|
3830
3786
|
};
|
|
3831
3787
|
},
|
|
3832
|
-
method(original, name) {
|
|
3833
|
-
const memoized = memoizeFunction(markWithRoot(decorator.renamed((that, ...args) => {
|
|
3834
|
-
return original.call(that, ...args);
|
|
3835
|
-
}, `${String(this.constructor.name)}.${String(name)}`), original));
|
|
3788
|
+
method(original, target, name) {
|
|
3836
3789
|
return function (...args) {
|
|
3790
|
+
let wrapper = wrapperRegistry.get(original);
|
|
3791
|
+
if (!wrapper) {
|
|
3792
|
+
wrapper = markWithRoot(decorator.renamed((that, ...args) => {
|
|
3793
|
+
return original.call(that, ...args);
|
|
3794
|
+
}, `${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(name)}`), {
|
|
3795
|
+
method: original,
|
|
3796
|
+
propertyKey: name,
|
|
3797
|
+
...(original[decorator.rootFunction] ? { [decorator.rootFunction]: original[decorator.rootFunction] } : {}),
|
|
3798
|
+
});
|
|
3799
|
+
wrapperRegistry.set(original, wrapper);
|
|
3800
|
+
}
|
|
3801
|
+
const memoized = memoizeFunction(wrapper);
|
|
3837
3802
|
return memoized(this, ...args);
|
|
3838
3803
|
};
|
|
3839
3804
|
},
|
|
@@ -3895,7 +3860,7 @@ let RegisterClass = (() => {
|
|
|
3895
3860
|
_tslib.__classPrivateFieldSet(this, _RegisterClass_keys, reactive([]), "f");
|
|
3896
3861
|
_tslib.__classPrivateFieldSet(this, _RegisterClass_values, reactive(new Map()), "f");
|
|
3897
3862
|
Object.defineProperties(this, {
|
|
3898
|
-
[prototypeForwarding]: { value: _tslib.__classPrivateFieldGet(this, _RegisterClass_keys, "f") },
|
|
3863
|
+
[decorator.prototypeForwarding]: { value: _tslib.__classPrivateFieldGet(this, _RegisterClass_keys, "f") },
|
|
3899
3864
|
});
|
|
3900
3865
|
if (initial)
|
|
3901
3866
|
this.push(...initial);
|
|
@@ -4243,6 +4208,17 @@ function register(keyFn, initial) {
|
|
|
4243
4208
|
return new RegisterClass(keyFn, initial);
|
|
4244
4209
|
}
|
|
4245
4210
|
|
|
4211
|
+
/**
|
|
4212
|
+
* Maps projection effects (item effects) to their projection context
|
|
4213
|
+
*/
|
|
4214
|
+
const effectProjectionMetadata = new WeakMap();
|
|
4215
|
+
/**
|
|
4216
|
+
* Returns the projection context of the currently running effect, if any.
|
|
4217
|
+
*/
|
|
4218
|
+
function getActiveProjection() {
|
|
4219
|
+
const active = getActiveEffect();
|
|
4220
|
+
return active ? effectProjectionMetadata.get(active) : undefined;
|
|
4221
|
+
}
|
|
4246
4222
|
function defineAccessValue(access) {
|
|
4247
4223
|
Object.defineProperty(access, 'value', {
|
|
4248
4224
|
get: access.get,
|
|
@@ -4251,7 +4227,15 @@ function defineAccessValue(access) {
|
|
|
4251
4227
|
enumerable: true,
|
|
4252
4228
|
});
|
|
4253
4229
|
}
|
|
4254
|
-
function makeCleanup(target, effectMap, onDispose) {
|
|
4230
|
+
function makeCleanup(target, effectMap, onDispose, metadata) {
|
|
4231
|
+
if (metadata) {
|
|
4232
|
+
Object.defineProperty(target, decorator.projectionInfo, {
|
|
4233
|
+
value: metadata,
|
|
4234
|
+
writable: false,
|
|
4235
|
+
enumerable: false,
|
|
4236
|
+
configurable: true,
|
|
4237
|
+
});
|
|
4238
|
+
}
|
|
4255
4239
|
return cleanedBy(target, () => {
|
|
4256
4240
|
onDispose();
|
|
4257
4241
|
for (const stop of effectMap.values())
|
|
@@ -4274,6 +4258,8 @@ function projectArray(source, apply) {
|
|
|
4274
4258
|
Reflect.deleteProperty(target, index);
|
|
4275
4259
|
}
|
|
4276
4260
|
}
|
|
4261
|
+
const parent = getActiveProjection();
|
|
4262
|
+
const depth = parent ? parent.depth + 1 : 0;
|
|
4277
4263
|
const cleanupLength = effect(function projectArrayLengthEffect({ ascend }) {
|
|
4278
4264
|
const length = observedSource.length;
|
|
4279
4265
|
normalizeTargetLength(length);
|
|
@@ -4296,6 +4282,14 @@ function projectArray(source, apply) {
|
|
|
4296
4282
|
const produced = apply(accessBase, target);
|
|
4297
4283
|
target[index] = produced;
|
|
4298
4284
|
});
|
|
4285
|
+
setEffectName(stop, `project[${depth}]:${index}`);
|
|
4286
|
+
effectProjectionMetadata.set(stop, {
|
|
4287
|
+
source: observedSource,
|
|
4288
|
+
key: index,
|
|
4289
|
+
target,
|
|
4290
|
+
depth,
|
|
4291
|
+
parent,
|
|
4292
|
+
});
|
|
4299
4293
|
indexEffects.set(i, stop);
|
|
4300
4294
|
});
|
|
4301
4295
|
}
|
|
@@ -4303,7 +4297,13 @@ function projectArray(source, apply) {
|
|
|
4303
4297
|
if (index >= length)
|
|
4304
4298
|
disposeIndex(index);
|
|
4305
4299
|
});
|
|
4306
|
-
return makeCleanup(target, indexEffects, () => cleanupLength()
|
|
4300
|
+
return makeCleanup(target, indexEffects, () => cleanupLength(), {
|
|
4301
|
+
source: observedSource,
|
|
4302
|
+
target,
|
|
4303
|
+
apply,
|
|
4304
|
+
depth,
|
|
4305
|
+
parent,
|
|
4306
|
+
});
|
|
4307
4307
|
}
|
|
4308
4308
|
function projectRegister(source, apply) {
|
|
4309
4309
|
const observedSource = reactive(source);
|
|
@@ -4318,6 +4318,8 @@ function projectRegister(source, apply) {
|
|
|
4318
4318
|
target.delete(key);
|
|
4319
4319
|
}
|
|
4320
4320
|
}
|
|
4321
|
+
const parent = getActiveProjection();
|
|
4322
|
+
const depth = parent ? parent.depth + 1 : 0;
|
|
4321
4323
|
const cleanupKeys = effect(function projectRegisterEffect({ ascend }) {
|
|
4322
4324
|
const keys = new Set();
|
|
4323
4325
|
for (const key of observedSource.mapKeys())
|
|
@@ -4342,6 +4344,14 @@ function projectRegister(source, apply) {
|
|
|
4342
4344
|
const produced = apply(accessBase, target);
|
|
4343
4345
|
target.set(key, produced);
|
|
4344
4346
|
});
|
|
4347
|
+
setEffectName(stop, `project[${depth}]:${String(key)}`);
|
|
4348
|
+
effectProjectionMetadata.set(stop, {
|
|
4349
|
+
source: observedSource,
|
|
4350
|
+
key,
|
|
4351
|
+
target,
|
|
4352
|
+
depth,
|
|
4353
|
+
parent,
|
|
4354
|
+
});
|
|
4345
4355
|
keyEffects.set(key, stop);
|
|
4346
4356
|
});
|
|
4347
4357
|
}
|
|
@@ -4349,7 +4359,13 @@ function projectRegister(source, apply) {
|
|
|
4349
4359
|
if (!keys.has(key))
|
|
4350
4360
|
disposeKey(key);
|
|
4351
4361
|
});
|
|
4352
|
-
return makeCleanup(target, keyEffects, () => cleanupKeys()
|
|
4362
|
+
return makeCleanup(target, keyEffects, () => cleanupKeys(), {
|
|
4363
|
+
source: observedSource,
|
|
4364
|
+
target,
|
|
4365
|
+
apply,
|
|
4366
|
+
depth,
|
|
4367
|
+
parent,
|
|
4368
|
+
});
|
|
4353
4369
|
}
|
|
4354
4370
|
function projectRecord(source, apply) {
|
|
4355
4371
|
const observedSource = reactive(source);
|
|
@@ -4363,6 +4379,8 @@ function projectRecord(source, apply) {
|
|
|
4363
4379
|
Reflect.deleteProperty(target, key);
|
|
4364
4380
|
}
|
|
4365
4381
|
}
|
|
4382
|
+
const parent = getActiveProjection();
|
|
4383
|
+
const depth = parent ? parent.depth + 1 : 0;
|
|
4366
4384
|
const cleanupKeys = effect(function projectRecordEffect({ ascend }) {
|
|
4367
4385
|
const keys = new Set();
|
|
4368
4386
|
for (const key in observedSource)
|
|
@@ -4388,6 +4406,14 @@ function projectRecord(source, apply) {
|
|
|
4388
4406
|
const produced = apply(accessBase, target);
|
|
4389
4407
|
target[sourceKey] = produced;
|
|
4390
4408
|
});
|
|
4409
|
+
setEffectName(stop, `project[${depth}]:${String(key)}`);
|
|
4410
|
+
effectProjectionMetadata.set(stop, {
|
|
4411
|
+
source: observedSource,
|
|
4412
|
+
key,
|
|
4413
|
+
target,
|
|
4414
|
+
depth,
|
|
4415
|
+
parent,
|
|
4416
|
+
});
|
|
4391
4417
|
keyEffects.set(key, stop);
|
|
4392
4418
|
});
|
|
4393
4419
|
}
|
|
@@ -4395,7 +4421,13 @@ function projectRecord(source, apply) {
|
|
|
4395
4421
|
if (!keys.has(key))
|
|
4396
4422
|
disposeKey(key);
|
|
4397
4423
|
});
|
|
4398
|
-
return makeCleanup(target, keyEffects, () => cleanupKeys()
|
|
4424
|
+
return makeCleanup(target, keyEffects, () => cleanupKeys(), {
|
|
4425
|
+
source: observedSource,
|
|
4426
|
+
target,
|
|
4427
|
+
apply,
|
|
4428
|
+
depth,
|
|
4429
|
+
parent,
|
|
4430
|
+
});
|
|
4399
4431
|
}
|
|
4400
4432
|
function projectMap(source, apply) {
|
|
4401
4433
|
const observedSource = reactive(source);
|
|
@@ -4410,6 +4442,8 @@ function projectMap(source, apply) {
|
|
|
4410
4442
|
target.delete(key);
|
|
4411
4443
|
}
|
|
4412
4444
|
}
|
|
4445
|
+
const parent = getActiveProjection();
|
|
4446
|
+
const depth = parent ? parent.depth + 1 : 0;
|
|
4413
4447
|
const cleanupKeys = effect(function projectMapEffect({ ascend }) {
|
|
4414
4448
|
const keys = new Set();
|
|
4415
4449
|
for (const key of observedSource.keys())
|
|
@@ -4434,6 +4468,14 @@ function projectMap(source, apply) {
|
|
|
4434
4468
|
const produced = apply(accessBase, target);
|
|
4435
4469
|
target.set(key, produced);
|
|
4436
4470
|
});
|
|
4471
|
+
setEffectName(stop, `project[${depth}]:${String(key)}`);
|
|
4472
|
+
effectProjectionMetadata.set(stop, {
|
|
4473
|
+
source: observedSource,
|
|
4474
|
+
key,
|
|
4475
|
+
target,
|
|
4476
|
+
depth,
|
|
4477
|
+
parent,
|
|
4478
|
+
});
|
|
4437
4479
|
keyEffects.set(key, stop);
|
|
4438
4480
|
});
|
|
4439
4481
|
}
|
|
@@ -4441,7 +4483,13 @@ function projectMap(source, apply) {
|
|
|
4441
4483
|
if (!keys.has(key))
|
|
4442
4484
|
disposeKey(key);
|
|
4443
4485
|
});
|
|
4444
|
-
return makeCleanup(target, keyEffects, () => cleanupKeys()
|
|
4486
|
+
return makeCleanup(target, keyEffects, () => cleanupKeys(), {
|
|
4487
|
+
source: observedSource,
|
|
4488
|
+
target,
|
|
4489
|
+
apply,
|
|
4490
|
+
depth,
|
|
4491
|
+
parent,
|
|
4492
|
+
});
|
|
4445
4493
|
}
|
|
4446
4494
|
function projectCore(source, apply) {
|
|
4447
4495
|
if (Array.isArray(source))
|
|
@@ -4588,8 +4636,8 @@ class ReactiveWeakMap {
|
|
|
4588
4636
|
constructor(original) {
|
|
4589
4637
|
Object.defineProperties(this, {
|
|
4590
4638
|
[native$1]: { value: original },
|
|
4591
|
-
[prototypeForwarding]: { value: original },
|
|
4592
|
-
content: { value: Symbol('
|
|
4639
|
+
[decorator.prototypeForwarding]: { value: original },
|
|
4640
|
+
content: { value: Symbol('WeakMapContent') },
|
|
4593
4641
|
[Symbol.toStringTag]: { value: 'ReactiveWeakMap' },
|
|
4594
4642
|
});
|
|
4595
4643
|
}
|
|
@@ -4628,8 +4676,8 @@ class ReactiveMap {
|
|
|
4628
4676
|
constructor(original) {
|
|
4629
4677
|
Object.defineProperties(this, {
|
|
4630
4678
|
[native$1]: { value: original },
|
|
4631
|
-
[prototypeForwarding]: { value: original },
|
|
4632
|
-
content: { value: Symbol('
|
|
4679
|
+
[decorator.prototypeForwarding]: { value: original },
|
|
4680
|
+
content: { value: Symbol('MapContent') },
|
|
4633
4681
|
[Symbol.toStringTag]: { value: 'ReactiveMap' },
|
|
4634
4682
|
});
|
|
4635
4683
|
}
|
|
@@ -4723,8 +4771,8 @@ class ReactiveWeakSet {
|
|
|
4723
4771
|
constructor(original) {
|
|
4724
4772
|
Object.defineProperties(this, {
|
|
4725
4773
|
[native]: { value: original },
|
|
4726
|
-
[prototypeForwarding]: { value: original },
|
|
4727
|
-
content: { value: Symbol('
|
|
4774
|
+
[decorator.prototypeForwarding]: { value: original },
|
|
4775
|
+
content: { value: Symbol('WeakSetContent') },
|
|
4728
4776
|
[Symbol.toStringTag]: { value: 'ReactiveWeakSet' },
|
|
4729
4777
|
});
|
|
4730
4778
|
}
|
|
@@ -4758,8 +4806,8 @@ class ReactiveSet {
|
|
|
4758
4806
|
constructor(original) {
|
|
4759
4807
|
Object.defineProperties(this, {
|
|
4760
4808
|
[native]: { value: original },
|
|
4761
|
-
[prototypeForwarding]: { value: original },
|
|
4762
|
-
content: { value: Symbol('
|
|
4809
|
+
[decorator.prototypeForwarding]: { value: original },
|
|
4810
|
+
content: { value: Symbol('SetContent') },
|
|
4763
4811
|
[Symbol.toStringTag]: { value: 'ReactiveSet' },
|
|
4764
4812
|
});
|
|
4765
4813
|
}
|
|
@@ -4858,7 +4906,6 @@ const profileInfo = {
|
|
|
4858
4906
|
exports.IterableWeakMap = IterableWeakMap;
|
|
4859
4907
|
exports.IterableWeakSet = IterableWeakSet;
|
|
4860
4908
|
exports.ReactiveBase = ReactiveBase;
|
|
4861
|
-
exports.ReactiveError = ReactiveError;
|
|
4862
4909
|
exports.ReadOnlyError = ReadOnlyError;
|
|
4863
4910
|
exports.Register = Register;
|
|
4864
4911
|
exports.addBatchCleanup = addBatchCleanup;
|
|
@@ -4873,7 +4920,9 @@ exports.defer = defer;
|
|
|
4873
4920
|
exports.derived = derived;
|
|
4874
4921
|
exports.effect = effect;
|
|
4875
4922
|
exports.enableDevTools = enableDevTools;
|
|
4923
|
+
exports.getActivationLog = getActivationLog;
|
|
4876
4924
|
exports.getActiveEffect = getActiveEffect;
|
|
4925
|
+
exports.getActiveProjection = getActiveProjection;
|
|
4877
4926
|
exports.getState = getState;
|
|
4878
4927
|
exports.immutables = immutables;
|
|
4879
4928
|
exports.isDevtoolsEnabled = isDevtoolsEnabled;
|
|
@@ -4883,7 +4932,6 @@ exports.isZoneEnabled = isZoneEnabled;
|
|
|
4883
4932
|
exports.mapped = mapped;
|
|
4884
4933
|
exports.memoize = memoize;
|
|
4885
4934
|
exports.mixin = mixin;
|
|
4886
|
-
exports.options = options;
|
|
4887
4935
|
exports.organize = organize;
|
|
4888
4936
|
exports.organized = organized;
|
|
4889
4937
|
exports.profileInfo = profileInfo;
|
|
@@ -4905,4 +4953,4 @@ exports.unreactive = unreactive;
|
|
|
4905
4953
|
exports.untracked = untracked;
|
|
4906
4954
|
exports.unwrap = unwrap;
|
|
4907
4955
|
exports.watch = watch;
|
|
4908
|
-
//# sourceMappingURL=index-
|
|
4956
|
+
//# sourceMappingURL=index-CDCOjzTy.js.map
|