mutts 1.0.5 → 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 +1 -0
- 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-Cvxdw6Ax.js → index-CDCOjzTy.js} +396 -500
- package/dist/chunks/index-CDCOjzTy.js.map +1 -0
- package/dist/chunks/{index-qiWwozOc.esm.js → index-DiP0RXoZ.esm.js} +301 -403
- 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 +48 -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 +25 -0
- package/dist/reactive.esm.js +3 -3
- package/dist/reactive.js +4 -4
- 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/debugging.md +158 -0
- package/docs/reactive.md +6 -5
- package/package.json +16 -66
- package/src/decorator.ts +11 -9
- package/src/destroyable.ts +3 -3
- package/src/index.ts +46 -0
- package/src/reactive/change.ts +1 -1
- 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 +44 -16
- package/src/reactive/index.ts +1 -1
- package/src/reactive/interface.ts +9 -8
- package/src/reactive/memoize.ts +77 -31
- package/src/reactive/proxy.ts +4 -4
- package/src/reactive/registry.ts +67 -0
- package/src/reactive/tracking.ts +12 -41
- package/src/reactive/types.ts +37 -0
- package/src/std-decorators.ts +9 -9
- 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-Cvxdw6Ax.js.map +0 -1
- package/dist/chunks/index-qiWwozOc.esm.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { i as isConstructor,
|
|
1
|
+
import { i as isConstructor, a as ReflectGet, g as rootFunction, o as options, h as allProps, R as ReactiveError, j as ReactiveErrorCode, k as cleanup$1, s as stopped, d as decorator, n as nonReactiveMark, p as nativeReactive, q as prototypeForwarding, u as unreactiveProperties, b as ReflectSet, f as isOwnAccessor, r as renamed, e as deepCompare, t as projectionInfo } from './decorator-BGILvPtN.esm.js';
|
|
2
2
|
import { Indexable, setAt, getAt, ArrayReadForward, forwardArray } from '../indexable.esm.js';
|
|
3
|
-
import {
|
|
3
|
+
import { _ as __setFunctionName, a as __esDecorate, b as __runInitializers, c as __classPrivateFieldSet, d as __classPrivateFieldGet } from './_tslib-MCKDzsSq.esm.js';
|
|
4
4
|
|
|
5
5
|
/// <reference lib="esnext.collection" />
|
|
6
6
|
var _a, _b;
|
|
@@ -332,323 +332,6 @@ function mixin(mixinFunction, unwrapFunction) {
|
|
|
332
332
|
});
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
-
// biome-ignore-all lint/suspicious/noConfusingVoidType: Type 'void' is not assignable to type 'ScopedCallback | undefined'.
|
|
336
|
-
// Argument of type '() => void' is not assignable to parameter of type '(dep: DependencyFunction) => ScopedCallback | undefined'.
|
|
337
|
-
// Track native reactivity
|
|
338
|
-
const nativeReactive = Symbol('native-reactive');
|
|
339
|
-
/**
|
|
340
|
-
* Symbol to mark individual objects as non-reactive
|
|
341
|
-
*/
|
|
342
|
-
const nonReactiveMark = Symbol('non-reactive');
|
|
343
|
-
/**
|
|
344
|
-
* Symbol to mark class properties as non-reactive
|
|
345
|
-
*/
|
|
346
|
-
const unreactiveProperties = Symbol('unreactive-properties');
|
|
347
|
-
/**
|
|
348
|
-
* Symbol for prototype forwarding in reactive objects
|
|
349
|
-
*/
|
|
350
|
-
const prototypeForwarding = Symbol('prototype-forwarding');
|
|
351
|
-
/**
|
|
352
|
-
* Symbol representing all properties in reactive tracking
|
|
353
|
-
*/
|
|
354
|
-
const allProps = Symbol('all-props');
|
|
355
|
-
/**
|
|
356
|
-
* Symbol for accessing projection information on reactive objects
|
|
357
|
-
*/
|
|
358
|
-
const projectionInfo = Symbol('projection-info');
|
|
359
|
-
// Symbol to mark functions with their root function
|
|
360
|
-
const rootFunction = Symbol('root-function');
|
|
361
|
-
/**
|
|
362
|
-
* Structured error codes for machine-readable diagnosis
|
|
363
|
-
*/
|
|
364
|
-
var ReactiveErrorCode;
|
|
365
|
-
(function (ReactiveErrorCode) {
|
|
366
|
-
ReactiveErrorCode["CycleDetected"] = "CYCLE_DETECTED";
|
|
367
|
-
ReactiveErrorCode["MaxDepthExceeded"] = "MAX_DEPTH_EXCEEDED";
|
|
368
|
-
ReactiveErrorCode["MaxReactionExceeded"] = "MAX_REACTION_EXCEEDED";
|
|
369
|
-
ReactiveErrorCode["WriteInComputed"] = "WRITE_IN_COMPUTED";
|
|
370
|
-
ReactiveErrorCode["TrackingError"] = "TRACKING_ERROR";
|
|
371
|
-
})(ReactiveErrorCode || (ReactiveErrorCode = {}));
|
|
372
|
-
/**
|
|
373
|
-
* Error class for reactive system errors
|
|
374
|
-
*/
|
|
375
|
-
class ReactiveError extends Error {
|
|
376
|
-
constructor(message, debugInfo) {
|
|
377
|
-
super(message);
|
|
378
|
-
this.debugInfo = debugInfo;
|
|
379
|
-
this.name = 'ReactiveError';
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
// biome-ignore-start lint/correctness/noUnusedFunctionParameters: Interface declaration with empty defaults
|
|
383
|
-
/**
|
|
384
|
-
* Global options for the reactive system
|
|
385
|
-
*/
|
|
386
|
-
const options = {
|
|
387
|
-
/**
|
|
388
|
-
* Debug purpose: called when an effect is entered
|
|
389
|
-
* @param effect - The effect that is entered
|
|
390
|
-
*/
|
|
391
|
-
enter: (_effect) => { },
|
|
392
|
-
/**
|
|
393
|
-
* Debug purpose: called when an effect is left
|
|
394
|
-
* @param effect - The effect that is left
|
|
395
|
-
*/
|
|
396
|
-
leave: (_effect) => { },
|
|
397
|
-
/**
|
|
398
|
-
* Debug purpose: called when an effect is chained
|
|
399
|
-
* @param target - The effect that is being triggered
|
|
400
|
-
* @param caller - The effect that is calling the target
|
|
401
|
-
*/
|
|
402
|
-
chain: (_targets, _caller) => { },
|
|
403
|
-
/**
|
|
404
|
-
* Debug purpose: called when an effect chain is started
|
|
405
|
-
* @param target - The effect that is being triggered
|
|
406
|
-
*/
|
|
407
|
-
beginChain: (_targets) => { },
|
|
408
|
-
/**
|
|
409
|
-
* Debug purpose: called when an effect chain is ended
|
|
410
|
-
*/
|
|
411
|
-
endChain: () => { },
|
|
412
|
-
garbageCollected: (_fn) => { },
|
|
413
|
-
/**
|
|
414
|
-
* Debug purpose: called when an object is touched
|
|
415
|
-
* @param obj - The object that is touched
|
|
416
|
-
* @param evolution - The type of change
|
|
417
|
-
* @param props - The properties that changed
|
|
418
|
-
* @param deps - The dependencies that changed
|
|
419
|
-
*/
|
|
420
|
-
touched: (_obj, _evolution, _props, _deps) => { },
|
|
421
|
-
/**
|
|
422
|
-
* Debug purpose: called when an effect is skipped because it's already running
|
|
423
|
-
* @param effect - The effect that is already running
|
|
424
|
-
* @param runningChain - The array of effects from the detected one to the currently running one
|
|
425
|
-
*/
|
|
426
|
-
skipRunningEffect: (_effect, _runningChain) => { },
|
|
427
|
-
/**
|
|
428
|
-
* Debug purpose: maximum effect chain (like call stack max depth)
|
|
429
|
-
* Used to prevent infinite loops
|
|
430
|
-
* @default 100
|
|
431
|
-
*/
|
|
432
|
-
maxEffectChain: 100,
|
|
433
|
-
/**
|
|
434
|
-
* Maximum number of times an effect can be triggered by the same cause in a single batch
|
|
435
|
-
* Used to detect aggressive re-computation or infinite loops
|
|
436
|
-
* @default 10
|
|
437
|
-
*/
|
|
438
|
-
maxTriggerPerBatch: 10,
|
|
439
|
-
/**
|
|
440
|
-
* Debug purpose: maximum effect reaction (like call stack max depth)
|
|
441
|
-
* Used to prevent infinite loops
|
|
442
|
-
* @default 'throw'
|
|
443
|
-
*/
|
|
444
|
-
maxEffectReaction: 'throw',
|
|
445
|
-
/**
|
|
446
|
-
* How to handle cycles detected in effect batches
|
|
447
|
-
* - 'throw': Throw an error with cycle information (default, recommended for development)
|
|
448
|
-
* - 'warn': Log a warning and break the cycle by executing one effect
|
|
449
|
-
* - 'break': Silently break the cycle by executing one effect (recommended for production)
|
|
450
|
-
* - 'strict': Prevent cycle creation by checking graph before execution (throws error)
|
|
451
|
-
* @default 'throw'
|
|
452
|
-
*/
|
|
453
|
-
cycleHandling: 'throw',
|
|
454
|
-
/**
|
|
455
|
-
* Maximum depth for deep watching traversal
|
|
456
|
-
* Used to prevent infinite recursion in circular references
|
|
457
|
-
* @default 100
|
|
458
|
-
*/
|
|
459
|
-
maxDeepWatchDepth: 100,
|
|
460
|
-
/**
|
|
461
|
-
* Only react on instance members modification (not inherited properties)
|
|
462
|
-
* For instance, do not track class methods
|
|
463
|
-
* @default true
|
|
464
|
-
*/
|
|
465
|
-
instanceMembers: true,
|
|
466
|
-
/**
|
|
467
|
-
* Ignore accessors (getters and setters) and only track direct properties
|
|
468
|
-
* @default true
|
|
469
|
-
*/
|
|
470
|
-
ignoreAccessors: true,
|
|
471
|
-
/**
|
|
472
|
-
* Enable recursive touching when objects with the same prototype are replaced
|
|
473
|
-
* When enabled, replacing an object with another of the same prototype triggers
|
|
474
|
-
* recursive diffing instead of notifying parent effects
|
|
475
|
-
* @default true
|
|
476
|
-
*/
|
|
477
|
-
recursiveTouching: true,
|
|
478
|
-
/**
|
|
479
|
-
* Default async execution mode for effects that return Promises
|
|
480
|
-
* - 'cancel': Cancel previous async execution when dependencies change (default, enables async zone)
|
|
481
|
-
* - 'queue': Queue next execution to run after current completes (enables async zone)
|
|
482
|
-
* - 'ignore': Ignore new executions while async work is running (enables async zone)
|
|
483
|
-
* - false: Disable async zone and async mode handling (effects run concurrently)
|
|
484
|
-
*
|
|
485
|
-
* **When truthy:** Enables async zone (Promise.prototype wrapping) for automatic context
|
|
486
|
-
* preservation in Promise callbacks. Warning: This modifies Promise.prototype globally.
|
|
487
|
-
* Only enable if no other library modifies Promise.prototype.
|
|
488
|
-
*
|
|
489
|
-
* **When false:** Async zone is disabled. Use `tracked()` manually in Promise callbacks.
|
|
490
|
-
*
|
|
491
|
-
* Can be overridden per-effect via EffectOptions
|
|
492
|
-
* @default 'cancel'
|
|
493
|
-
*/
|
|
494
|
-
asyncMode: 'cancel',
|
|
495
|
-
// biome-ignore lint/suspicious/noConsole: This is the whole point here
|
|
496
|
-
warn: (...args) => console.warn(...args),
|
|
497
|
-
/**
|
|
498
|
-
* Configuration for the introspection system
|
|
499
|
-
*/
|
|
500
|
-
introspection: {
|
|
501
|
-
/**
|
|
502
|
-
* Whether to keep a history of mutations for debugging
|
|
503
|
-
* @default false
|
|
504
|
-
*/
|
|
505
|
-
enableHistory: false,
|
|
506
|
-
/**
|
|
507
|
-
* Number of mutations to keep in history
|
|
508
|
-
* @default 50
|
|
509
|
-
*/
|
|
510
|
-
historySize: 50,
|
|
511
|
-
},
|
|
512
|
-
/**
|
|
513
|
-
* Configuration for zone hooks - control which async APIs are hooked
|
|
514
|
-
* Each option controls whether the corresponding async API is wrapped to preserve effect context
|
|
515
|
-
* Only applies when asyncMode is enabled (truthy)
|
|
516
|
-
*/
|
|
517
|
-
zones: {
|
|
518
|
-
/**
|
|
519
|
-
* Hook setTimeout to preserve effect context
|
|
520
|
-
* @default true
|
|
521
|
-
*/
|
|
522
|
-
setTimeout: true,
|
|
523
|
-
/**
|
|
524
|
-
* Hook setInterval to preserve effect context
|
|
525
|
-
* @default true
|
|
526
|
-
*/
|
|
527
|
-
setInterval: true,
|
|
528
|
-
/**
|
|
529
|
-
* Hook requestAnimationFrame (runs in untracked context when hooked)
|
|
530
|
-
* @default true
|
|
531
|
-
*/
|
|
532
|
-
requestAnimationFrame: true,
|
|
533
|
-
/**
|
|
534
|
-
* Hook queueMicrotask to preserve effect context
|
|
535
|
-
* @default true
|
|
536
|
-
*/
|
|
537
|
-
queueMicrotask: true,
|
|
538
|
-
},
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* Effect context stack for nested tracking (front = active, next = parent)
|
|
543
|
-
*/
|
|
544
|
-
const stack = [];
|
|
545
|
-
function captureEffectStack() {
|
|
546
|
-
return stack.slice();
|
|
547
|
-
}
|
|
548
|
-
function isRunning(effect) {
|
|
549
|
-
const rootEffect = getRoot(effect);
|
|
550
|
-
// Check if the effect is directly in the stack
|
|
551
|
-
const rootIndex = stack.indexOf(rootEffect);
|
|
552
|
-
if (rootIndex !== -1) {
|
|
553
|
-
return stack.slice(0, rootIndex + 1).reverse();
|
|
554
|
-
}
|
|
555
|
-
// Check if any effect in the stack is a descendant of this effect
|
|
556
|
-
// (i.e., walk up the parent chain from each stack effect to see if we reach this effect)
|
|
557
|
-
for (let i = 0; i < stack.length; i++) {
|
|
558
|
-
const stackEffect = stack[i];
|
|
559
|
-
let current = stackEffect;
|
|
560
|
-
const visited = new WeakSet();
|
|
561
|
-
const ancestorChain = [];
|
|
562
|
-
// TODO: That's perhaps a lot of computations for an `assert`
|
|
563
|
-
// Walk up the parent chain to find if this effect is an ancestor
|
|
564
|
-
while (current && !visited.has(current)) {
|
|
565
|
-
visited.add(current);
|
|
566
|
-
const currentRoot = getRoot(current);
|
|
567
|
-
ancestorChain.push(currentRoot);
|
|
568
|
-
if (currentRoot === rootEffect) {
|
|
569
|
-
// Found a descendant - build the full chain from ancestor to active
|
|
570
|
-
// The ancestorChain contains [descendant, parent, ..., ancestor] (walking up)
|
|
571
|
-
// We need [ancestor (effect), ..., parent, descendant, ...stack from descendant to active]
|
|
572
|
-
const chainFromAncestor = ancestorChain.reverse(); // [ancestor, ..., descendant]
|
|
573
|
-
// Prepend the actual effect we're checking (in case current is a wrapper)
|
|
574
|
-
if (chainFromAncestor[0] !== rootEffect) {
|
|
575
|
-
chainFromAncestor.unshift(rootEffect);
|
|
576
|
-
}
|
|
577
|
-
// Append the rest of the stack from the descendant to the active effect
|
|
578
|
-
const stackFromDescendant = stack.slice(0, i + 1).reverse(); // [descendant, ..., active]
|
|
579
|
-
// Remove duplicate descendant (it's both at end of chainFromAncestor and start of stackFromDescendant)
|
|
580
|
-
if (chainFromAncestor.length > 0 && stackFromDescendant.length > 0) {
|
|
581
|
-
stackFromDescendant.shift(); // Remove duplicate descendant
|
|
582
|
-
}
|
|
583
|
-
return [...chainFromAncestor, ...stackFromDescendant];
|
|
584
|
-
}
|
|
585
|
-
current = effectParent.get(current);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
return false;
|
|
589
|
-
}
|
|
590
|
-
function withEffectStack(snapshot, fn) {
|
|
591
|
-
const previousStack = stack.slice();
|
|
592
|
-
assignStack(snapshot);
|
|
593
|
-
try {
|
|
594
|
-
return fn();
|
|
595
|
-
}
|
|
596
|
-
finally {
|
|
597
|
-
assignStack(previousStack);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
function getActiveEffect() {
|
|
601
|
-
return stack[0];
|
|
602
|
-
}
|
|
603
|
-
/**
|
|
604
|
-
* Executes a function with a specific effect context
|
|
605
|
-
* @param effect - The effect to use as context
|
|
606
|
-
* @param fn - The function to execute
|
|
607
|
-
* @param keepParent - Whether to keep the parent effect context
|
|
608
|
-
* @returns The result of the function
|
|
609
|
-
*/
|
|
610
|
-
function withEffect(effect, fn) {
|
|
611
|
-
// console.log('[Mutts] withEffect', effect ? 'Active' : 'NULL');
|
|
612
|
-
if (getRoot(effect) === getRoot(getActiveEffect()))
|
|
613
|
-
return fn();
|
|
614
|
-
stack.unshift(effect);
|
|
615
|
-
try {
|
|
616
|
-
return fn();
|
|
617
|
-
}
|
|
618
|
-
finally {
|
|
619
|
-
const recoveredEffect = stack.shift();
|
|
620
|
-
if (recoveredEffect !== effect)
|
|
621
|
-
throw new ReactiveError('[reactive] Effect stack mismatch');
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
function assignStack(values) {
|
|
625
|
-
stack.length = 0;
|
|
626
|
-
stack.push(...values);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
const objectToProxy = new WeakMap();
|
|
630
|
-
const proxyToObject = new WeakMap();
|
|
631
|
-
function storeProxyRelationship(target, proxy) {
|
|
632
|
-
objectToProxy.set(target, proxy);
|
|
633
|
-
proxyToObject.set(proxy, target);
|
|
634
|
-
}
|
|
635
|
-
function getExistingProxy(target) {
|
|
636
|
-
return objectToProxy.get(target);
|
|
637
|
-
}
|
|
638
|
-
function trackProxyObject(proxy, target) {
|
|
639
|
-
proxyToObject.set(proxy, target);
|
|
640
|
-
}
|
|
641
|
-
function unwrap(obj) {
|
|
642
|
-
let current = obj;
|
|
643
|
-
while (current && typeof current === 'object' && current !== null && proxyToObject.has(current)) {
|
|
644
|
-
current = proxyToObject.get(current);
|
|
645
|
-
}
|
|
646
|
-
return current;
|
|
647
|
-
}
|
|
648
|
-
function isReactive(obj) {
|
|
649
|
-
return proxyToObject.has(obj);
|
|
650
|
-
}
|
|
651
|
-
|
|
652
335
|
// Track which effects are watching which reactive objects for cleanup
|
|
653
336
|
const effectToReactiveObjects = new WeakMap();
|
|
654
337
|
// Track effects per reactive object and property
|
|
@@ -657,13 +340,29 @@ const watchers = new WeakMap();
|
|
|
657
340
|
const effectChildren = new WeakMap();
|
|
658
341
|
// Track parent effect relationships for hierarchy traversal (used in deep touch filtering)
|
|
659
342
|
const effectParent = new WeakMap();
|
|
343
|
+
// Track reverse mapping to ensure unicity: One Root -> One Function
|
|
344
|
+
const reverseRoots = new WeakMap();
|
|
660
345
|
/**
|
|
661
346
|
* Marks a function with its root function for effect tracking
|
|
347
|
+
* Enforces strict unicity: A root function can only identify ONE function.
|
|
662
348
|
* @param fn - The function to mark
|
|
663
349
|
* @param root - The root function
|
|
664
350
|
* @returns The marked function
|
|
665
351
|
*/
|
|
666
352
|
function markWithRoot(fn, root) {
|
|
353
|
+
// Check for collision
|
|
354
|
+
const existingRef = reverseRoots.get(root);
|
|
355
|
+
const existing = existingRef?.deref();
|
|
356
|
+
if (existing && existing !== fn) {
|
|
357
|
+
const rootName = root.name || 'anonymous';
|
|
358
|
+
const existingName = existing.name || 'anonymous';
|
|
359
|
+
const fnName = fn.name || 'anonymous';
|
|
360
|
+
throw new Error(`[reactive] Abusive Shared Root detected: Root '${rootName}' is already identifying function '${existingName}'. ` +
|
|
361
|
+
`Cannot reuse it for '${fnName}'. Shared roots cause lost updates and broken identity logic.`);
|
|
362
|
+
}
|
|
363
|
+
// Always update the map so subsequent checks find this one
|
|
364
|
+
// (Last writer wins for the check)
|
|
365
|
+
reverseRoots.set(root, new WeakRef(fn));
|
|
667
366
|
// Mark fn with the new root
|
|
668
367
|
return Object.defineProperty(fn, rootFunction, {
|
|
669
368
|
value: getRoot(root),
|
|
@@ -676,64 +375,15 @@ function markWithRoot(fn, root) {
|
|
|
676
375
|
* @returns The root function
|
|
677
376
|
*/
|
|
678
377
|
function getRoot(fn) {
|
|
679
|
-
|
|
378
|
+
while (fn && rootFunction in fn)
|
|
379
|
+
fn = fn[rootFunction];
|
|
380
|
+
return fn;
|
|
680
381
|
}
|
|
681
382
|
// Flag to disable dependency tracking for the current active effect (not globally)
|
|
682
383
|
const trackingDisabledEffects = new WeakSet();
|
|
683
384
|
let globalTrackingDisabled = false;
|
|
684
|
-
function
|
|
685
|
-
|
|
686
|
-
if (!active)
|
|
687
|
-
return globalTrackingDisabled;
|
|
688
|
-
return trackingDisabledEffects.has(getRoot(active));
|
|
689
|
-
}
|
|
690
|
-
function setTrackingDisabled(value) {
|
|
691
|
-
const active = getActiveEffect();
|
|
692
|
-
if (!active) {
|
|
693
|
-
globalTrackingDisabled = value;
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
const root = getRoot(active);
|
|
697
|
-
if (value)
|
|
698
|
-
trackingDisabledEffects.add(root);
|
|
699
|
-
else
|
|
700
|
-
trackingDisabledEffects.delete(root);
|
|
701
|
-
}
|
|
702
|
-
/**
|
|
703
|
-
* Marks a property as a dependency of the current effect
|
|
704
|
-
* @param obj - The object containing the property
|
|
705
|
-
* @param prop - The property name (defaults to allProps)
|
|
706
|
-
*/
|
|
707
|
-
function dependant(obj, prop = allProps) {
|
|
708
|
-
obj = unwrap(obj);
|
|
709
|
-
const currentActiveEffect = getActiveEffect();
|
|
710
|
-
// Early return if no active effect, tracking disabled, or invalid prop
|
|
711
|
-
if (!currentActiveEffect ||
|
|
712
|
-
getTrackingDisabled() ||
|
|
713
|
-
(typeof prop === 'symbol' && prop !== allProps))
|
|
714
|
-
return;
|
|
715
|
-
registerDependency(obj, prop, currentActiveEffect);
|
|
716
|
-
}
|
|
717
|
-
function registerDependency(obj, prop, currentActiveEffect) {
|
|
718
|
-
let objectWatchers = watchers.get(obj);
|
|
719
|
-
if (!objectWatchers) {
|
|
720
|
-
objectWatchers = new Map();
|
|
721
|
-
watchers.set(obj, objectWatchers);
|
|
722
|
-
}
|
|
723
|
-
let deps = objectWatchers.get(prop);
|
|
724
|
-
if (!deps) {
|
|
725
|
-
deps = new Set();
|
|
726
|
-
objectWatchers.set(prop, deps);
|
|
727
|
-
}
|
|
728
|
-
deps.add(currentActiveEffect);
|
|
729
|
-
// Track which reactive objects this effect is watching
|
|
730
|
-
const effectObjects = effectToReactiveObjects.get(currentActiveEffect);
|
|
731
|
-
if (effectObjects) {
|
|
732
|
-
effectObjects.add(obj);
|
|
733
|
-
}
|
|
734
|
-
else {
|
|
735
|
-
effectToReactiveObjects.set(currentActiveEffect, new Set([obj]));
|
|
736
|
-
}
|
|
385
|
+
function setGlobalTrackingDisabled(value) {
|
|
386
|
+
globalTrackingDisabled = value;
|
|
737
387
|
}
|
|
738
388
|
|
|
739
389
|
/**
|
|
@@ -1088,6 +738,171 @@ function addToMutationHistory(source, target, obj, prop, evolution) {
|
|
|
1088
738
|
}
|
|
1089
739
|
}
|
|
1090
740
|
|
|
741
|
+
/**
|
|
742
|
+
* Effect context stack for nested tracking (front = active, next = parent)
|
|
743
|
+
*/
|
|
744
|
+
const stack = [];
|
|
745
|
+
function captureEffectStack() {
|
|
746
|
+
return stack.slice();
|
|
747
|
+
}
|
|
748
|
+
function isRunning(effect) {
|
|
749
|
+
const rootEffect = getRoot(effect);
|
|
750
|
+
// Check if the effect is directly in the stack
|
|
751
|
+
const rootIndex = stack.indexOf(rootEffect);
|
|
752
|
+
if (rootIndex !== -1) {
|
|
753
|
+
return stack.slice(0, rootIndex + 1).reverse();
|
|
754
|
+
}
|
|
755
|
+
// Check if any effect in the stack is a descendant of this effect
|
|
756
|
+
// (i.e., walk up the parent chain from each stack effect to see if we reach this effect)
|
|
757
|
+
for (let i = 0; i < stack.length; i++) {
|
|
758
|
+
const stackEffect = stack[i];
|
|
759
|
+
let current = stackEffect;
|
|
760
|
+
const visited = new WeakSet();
|
|
761
|
+
const ancestorChain = [];
|
|
762
|
+
// TODO: That's perhaps a lot of computations for an `assert`
|
|
763
|
+
// Walk up the parent chain to find if this effect is an ancestor
|
|
764
|
+
while (current && !visited.has(current)) {
|
|
765
|
+
visited.add(current);
|
|
766
|
+
const currentRoot = getRoot(current);
|
|
767
|
+
ancestorChain.push(currentRoot);
|
|
768
|
+
if (currentRoot === rootEffect) {
|
|
769
|
+
// Found a descendant - build the full chain from ancestor to active
|
|
770
|
+
// The ancestorChain contains [descendant, parent, ..., ancestor] (walking up)
|
|
771
|
+
// We need [ancestor (effect), ..., parent, descendant, ...stack from descendant to active]
|
|
772
|
+
const chainFromAncestor = ancestorChain.reverse(); // [ancestor, ..., descendant]
|
|
773
|
+
// Prepend the actual effect we're checking (in case current is a wrapper)
|
|
774
|
+
if (chainFromAncestor[0] !== rootEffect) {
|
|
775
|
+
chainFromAncestor.unshift(rootEffect);
|
|
776
|
+
}
|
|
777
|
+
// Append the rest of the stack from the descendant to the active effect
|
|
778
|
+
const stackFromDescendant = stack.slice(0, i + 1).reverse(); // [descendant, ..., active]
|
|
779
|
+
// Remove duplicate descendant (it's both at end of chainFromAncestor and start of stackFromDescendant)
|
|
780
|
+
if (chainFromAncestor.length > 0 && stackFromDescendant.length > 0) {
|
|
781
|
+
stackFromDescendant.shift(); // Remove duplicate descendant
|
|
782
|
+
}
|
|
783
|
+
return [...chainFromAncestor, ...stackFromDescendant];
|
|
784
|
+
}
|
|
785
|
+
current = effectParent.get(current);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
function withEffectStack(snapshot, fn) {
|
|
791
|
+
const previousStack = stack.slice();
|
|
792
|
+
assignStack(snapshot);
|
|
793
|
+
try {
|
|
794
|
+
return fn();
|
|
795
|
+
}
|
|
796
|
+
finally {
|
|
797
|
+
assignStack(previousStack);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
function getActiveEffect() {
|
|
801
|
+
return stack[0];
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Executes a function with a specific effect context
|
|
805
|
+
* @param effect - The effect to use as context
|
|
806
|
+
* @param fn - The function to execute
|
|
807
|
+
* @param keepParent - Whether to keep the parent effect context
|
|
808
|
+
* @returns The result of the function
|
|
809
|
+
*/
|
|
810
|
+
function withEffect(effect, fn) {
|
|
811
|
+
if (getRoot(effect) === getRoot(getActiveEffect()))
|
|
812
|
+
return fn();
|
|
813
|
+
stack.unshift(effect);
|
|
814
|
+
try {
|
|
815
|
+
return fn();
|
|
816
|
+
}
|
|
817
|
+
finally {
|
|
818
|
+
const recoveredEffect = stack.shift();
|
|
819
|
+
if (recoveredEffect !== effect)
|
|
820
|
+
throw new ReactiveError('[reactive] Effect stack mismatch');
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
function assignStack(values) {
|
|
824
|
+
stack.length = 0;
|
|
825
|
+
stack.push(...values);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const objectToProxy = new WeakMap();
|
|
829
|
+
const proxyToObject = new WeakMap();
|
|
830
|
+
function storeProxyRelationship(target, proxy) {
|
|
831
|
+
objectToProxy.set(target, proxy);
|
|
832
|
+
proxyToObject.set(proxy, target);
|
|
833
|
+
}
|
|
834
|
+
function getExistingProxy(target) {
|
|
835
|
+
return objectToProxy.get(target);
|
|
836
|
+
}
|
|
837
|
+
function trackProxyObject(proxy, target) {
|
|
838
|
+
proxyToObject.set(proxy, target);
|
|
839
|
+
}
|
|
840
|
+
function unwrap(obj) {
|
|
841
|
+
let current = obj;
|
|
842
|
+
while (current && typeof current === 'object' && current !== null && proxyToObject.has(current)) {
|
|
843
|
+
current = proxyToObject.get(current);
|
|
844
|
+
}
|
|
845
|
+
return current;
|
|
846
|
+
}
|
|
847
|
+
function isReactive(obj) {
|
|
848
|
+
return proxyToObject.has(obj);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function getTrackingDisabled() {
|
|
852
|
+
const active = getActiveEffect();
|
|
853
|
+
if (!active)
|
|
854
|
+
return globalTrackingDisabled;
|
|
855
|
+
return trackingDisabledEffects.has(getRoot(active));
|
|
856
|
+
}
|
|
857
|
+
function setTrackingDisabled(value) {
|
|
858
|
+
const active = getActiveEffect();
|
|
859
|
+
if (!active) {
|
|
860
|
+
setGlobalTrackingDisabled(value);
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
const root = getRoot(active);
|
|
864
|
+
if (value)
|
|
865
|
+
trackingDisabledEffects.add(root);
|
|
866
|
+
else
|
|
867
|
+
trackingDisabledEffects.delete(root);
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Marks a property as a dependency of the current effect
|
|
871
|
+
* @param obj - The object containing the property
|
|
872
|
+
* @param prop - The property name (defaults to allProps)
|
|
873
|
+
*/
|
|
874
|
+
function dependant(obj, prop = allProps) {
|
|
875
|
+
obj = unwrap(obj);
|
|
876
|
+
const currentActiveEffect = getActiveEffect();
|
|
877
|
+
// Early return if no active effect, tracking disabled, or invalid prop
|
|
878
|
+
if (!currentActiveEffect ||
|
|
879
|
+
getTrackingDisabled() ||
|
|
880
|
+
(typeof prop === 'symbol' && prop !== allProps))
|
|
881
|
+
return;
|
|
882
|
+
registerDependency(obj, prop, currentActiveEffect);
|
|
883
|
+
}
|
|
884
|
+
function registerDependency(obj, prop, currentActiveEffect) {
|
|
885
|
+
let objectWatchers = watchers.get(obj);
|
|
886
|
+
if (!objectWatchers) {
|
|
887
|
+
objectWatchers = new Map();
|
|
888
|
+
watchers.set(obj, objectWatchers);
|
|
889
|
+
}
|
|
890
|
+
let deps = objectWatchers.get(prop);
|
|
891
|
+
if (!deps) {
|
|
892
|
+
deps = new Set();
|
|
893
|
+
objectWatchers.set(prop, deps);
|
|
894
|
+
}
|
|
895
|
+
deps.add(currentActiveEffect);
|
|
896
|
+
// Track which reactive objects this effect is watching
|
|
897
|
+
const effectObjects = effectToReactiveObjects.get(currentActiveEffect);
|
|
898
|
+
if (effectObjects) {
|
|
899
|
+
effectObjects.add(obj);
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
effectToReactiveObjects.set(currentActiveEffect, new Set([obj]));
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
1091
906
|
/**
|
|
1092
907
|
* Zone-like async context preservation for reactive effects
|
|
1093
908
|
*
|
|
@@ -1473,6 +1288,7 @@ function addGraphEdge(callerRoot, targetRoot) {
|
|
|
1473
1288
|
* @param end - Target node
|
|
1474
1289
|
* @param exclude - Node to exclude from the path
|
|
1475
1290
|
* @returns true if a path exists without going through the excluded node
|
|
1291
|
+
* @todo Can be REALLY costly - optimise or make optional or ...
|
|
1476
1292
|
*/
|
|
1477
1293
|
function hasPathExcluding(start, end, exclude) {
|
|
1478
1294
|
if (start === end)
|
|
@@ -1760,6 +1576,12 @@ function wouldCreateCycle(callerRoot, targetRoot) {
|
|
|
1760
1576
|
* @param immediate - If true, don't create edges in the dependency graph
|
|
1761
1577
|
*/
|
|
1762
1578
|
function addToBatch(effect, caller, immediate) {
|
|
1579
|
+
const cleanupFn = effect[cleanup$1];
|
|
1580
|
+
if (cleanupFn)
|
|
1581
|
+
cleanupFn();
|
|
1582
|
+
// If the effect was stopped during cleanup (e.g. lazy memoization), don't add it to the batch
|
|
1583
|
+
if (effect[stopped])
|
|
1584
|
+
return;
|
|
1763
1585
|
if (!batchQueue)
|
|
1764
1586
|
return;
|
|
1765
1587
|
const root = getRoot(effect);
|
|
@@ -2195,12 +2017,18 @@ function batch(effect, immediate) {
|
|
|
2195
2017
|
const atomic = decorator({
|
|
2196
2018
|
method(original) {
|
|
2197
2019
|
return function (...args) {
|
|
2198
|
-
|
|
2020
|
+
const atomicEffect = () => original.apply(this, args);
|
|
2021
|
+
// Debug: helpful to have a name
|
|
2022
|
+
Object.defineProperty(atomicEffect, 'name', { value: `atomic(${original.name})` });
|
|
2023
|
+
return batch(atomicEffect, 'immediate');
|
|
2199
2024
|
};
|
|
2200
2025
|
},
|
|
2201
2026
|
default(original) {
|
|
2202
2027
|
return function (...args) {
|
|
2203
|
-
|
|
2028
|
+
const atomicEffect = () => original.apply(this, args);
|
|
2029
|
+
// Debug: helpful to have a name
|
|
2030
|
+
Object.defineProperty(atomicEffect, 'name', { value: `atomic(${original.name})` });
|
|
2031
|
+
return batch(atomicEffect, 'immediate');
|
|
2204
2032
|
};
|
|
2205
2033
|
},
|
|
2206
2034
|
});
|
|
@@ -2234,7 +2062,7 @@ fn, effectOptions) {
|
|
|
2234
2062
|
let cleanup = null;
|
|
2235
2063
|
// capture the parent effect at creation time for ascend
|
|
2236
2064
|
const parentsForAscend = captureEffectStack();
|
|
2237
|
-
const tracked =
|
|
2065
|
+
const tracked = (cb) => withEffect(runEffect, cb);
|
|
2238
2066
|
const ascend = (cb) => withEffectStack(parentsForAscend, cb);
|
|
2239
2067
|
let effectStopped = false;
|
|
2240
2068
|
let hasReacted = false;
|
|
@@ -2367,8 +2195,25 @@ fn, effectOptions) {
|
|
|
2367
2195
|
cleanupEffectFromGraph(runEffect);
|
|
2368
2196
|
fr.unregister(stopEffect);
|
|
2369
2197
|
};
|
|
2198
|
+
function augmentedRv(rv) {
|
|
2199
|
+
Object.defineProperty(rv, stopped, {
|
|
2200
|
+
get() {
|
|
2201
|
+
return effectStopped;
|
|
2202
|
+
},
|
|
2203
|
+
});
|
|
2204
|
+
Object.defineProperty(rv, cleanup$1, {
|
|
2205
|
+
value: () => {
|
|
2206
|
+
if (cleanup) {
|
|
2207
|
+
const prevCleanup = cleanup;
|
|
2208
|
+
cleanup = null;
|
|
2209
|
+
withEffect(undefined, () => prevCleanup());
|
|
2210
|
+
}
|
|
2211
|
+
},
|
|
2212
|
+
});
|
|
2213
|
+
return rv;
|
|
2214
|
+
}
|
|
2370
2215
|
if (isRootEffect) {
|
|
2371
|
-
const callIfCollected = () => stopEffect();
|
|
2216
|
+
const callIfCollected = augmentedRv(() => stopEffect());
|
|
2372
2217
|
fr.register(callIfCollected, () => {
|
|
2373
2218
|
stopEffect();
|
|
2374
2219
|
options.garbageCollected(fn);
|
|
@@ -2381,14 +2226,14 @@ fn, effectOptions) {
|
|
|
2381
2226
|
children = new Set();
|
|
2382
2227
|
effectChildren.set(parent, children);
|
|
2383
2228
|
}
|
|
2384
|
-
const subEffectCleanup = () => {
|
|
2229
|
+
const subEffectCleanup = augmentedRv(() => {
|
|
2385
2230
|
children.delete(subEffectCleanup);
|
|
2386
2231
|
if (children.size === 0) {
|
|
2387
2232
|
effectChildren.delete(parent);
|
|
2388
2233
|
}
|
|
2389
2234
|
// Execute this child effect cleanup (which triggers its own mainCleanup)
|
|
2390
2235
|
stopEffect();
|
|
2391
|
-
};
|
|
2236
|
+
});
|
|
2392
2237
|
children.add(subEffectCleanup);
|
|
2393
2238
|
return subEffectCleanup;
|
|
2394
2239
|
}
|
|
@@ -2987,13 +2832,13 @@ const reactiveHandlers = {
|
|
|
2987
2832
|
const receiverDesc = Object.getOwnPropertyDescriptor(unwrappedReceiver, prop);
|
|
2988
2833
|
const targetDesc = Object.getOwnPropertyDescriptor(unwrappedObj, prop);
|
|
2989
2834
|
const desc = receiverDesc || targetDesc;
|
|
2990
|
-
//
|
|
2991
|
-
//
|
|
2835
|
+
// We *need* to use `receiver` and not `unwrappedObj` here, otherwise we break
|
|
2836
|
+
// the dependency tracking for memoized getters
|
|
2992
2837
|
if (desc?.get && !desc?.set) {
|
|
2993
|
-
oldVal = withEffect(undefined, () => Reflect.get(unwrappedObj, prop,
|
|
2838
|
+
oldVal = withEffect(undefined, () => Reflect.get(unwrappedObj, prop, receiver));
|
|
2994
2839
|
}
|
|
2995
2840
|
else {
|
|
2996
|
-
oldVal = Reflect.get(unwrappedObj, prop,
|
|
2841
|
+
oldVal = withEffect(undefined, () => Reflect.get(unwrappedObj, prop, receiver));
|
|
2997
2842
|
}
|
|
2998
2843
|
}
|
|
2999
2844
|
if (objectsWithDeepWatchers.has(obj)) {
|
|
@@ -3283,12 +3128,12 @@ function watchObject(value, changed, { immediate = false, deep = false } = {}) {
|
|
|
3283
3128
|
const myParentEffect = getActiveEffect();
|
|
3284
3129
|
if (deep)
|
|
3285
3130
|
return deepWatch(value, changed, { immediate });
|
|
3286
|
-
return effect(
|
|
3131
|
+
return effect(function watchObjectEffect() {
|
|
3287
3132
|
dependant(value);
|
|
3288
3133
|
if (immediate)
|
|
3289
3134
|
withEffect(myParentEffect, () => changed(value));
|
|
3290
3135
|
immediate = true;
|
|
3291
|
-
}
|
|
3136
|
+
});
|
|
3292
3137
|
}
|
|
3293
3138
|
function watchCallBack(value, changed, { immediate = false, deep = false } = {}) {
|
|
3294
3139
|
const myParentEffect = getActiveEffect();
|
|
@@ -3297,7 +3142,7 @@ function watchCallBack(value, changed, { immediate = false, deep = false } = {})
|
|
|
3297
3142
|
const cbCleanup = effect(markWithRoot(function watchCallBackEffect(access) {
|
|
3298
3143
|
const newValue = value(access);
|
|
3299
3144
|
if (oldValue !== newValue)
|
|
3300
|
-
withEffect(myParentEffect,
|
|
3145
|
+
withEffect(myParentEffect, () => {
|
|
3301
3146
|
if (oldValue === unsetYet) {
|
|
3302
3147
|
if (immediate)
|
|
3303
3148
|
changed(newValue);
|
|
@@ -3308,9 +3153,9 @@ function watchCallBack(value, changed, { immediate = false, deep = false } = {})
|
|
|
3308
3153
|
if (deep) {
|
|
3309
3154
|
if (deepCleanup)
|
|
3310
3155
|
deepCleanup();
|
|
3311
|
-
deepCleanup = deepWatch(newValue,
|
|
3156
|
+
deepCleanup = deepWatch(newValue, (value) => changed(value, value));
|
|
3312
3157
|
}
|
|
3313
|
-
}
|
|
3158
|
+
});
|
|
3314
3159
|
}, value));
|
|
3315
3160
|
return () => {
|
|
3316
3161
|
cbCleanup();
|
|
@@ -3383,9 +3228,9 @@ function cleanedBy(obj, cleanupFn) {
|
|
|
3383
3228
|
*/
|
|
3384
3229
|
function derived(compute) {
|
|
3385
3230
|
const rv = { value: undefined };
|
|
3386
|
-
return cleanedBy(rv, untracked(() => effect(
|
|
3231
|
+
return cleanedBy(rv, untracked(() => effect(function derivedEffect(access) {
|
|
3387
3232
|
rv.value = compute(access);
|
|
3388
|
-
}
|
|
3233
|
+
})));
|
|
3389
3234
|
}
|
|
3390
3235
|
|
|
3391
3236
|
/**
|
|
@@ -3841,6 +3686,7 @@ function reduced(inputs, compute) {
|
|
|
3841
3686
|
}
|
|
3842
3687
|
|
|
3843
3688
|
const memoizedRegistry = new WeakMap();
|
|
3689
|
+
const wrapperRegistry = new WeakMap();
|
|
3844
3690
|
function getBranch(tree, key) {
|
|
3845
3691
|
tree.branches ?? (tree.branches = new WeakMap());
|
|
3846
3692
|
let branch = tree.branches.get(key);
|
|
@@ -3861,15 +3707,30 @@ function memoizeFunction(fn) {
|
|
|
3861
3707
|
if (localArgs.some((arg) => !(arg && ['object', 'symbol', 'function'].includes(typeof arg))))
|
|
3862
3708
|
throw new Error('memoize expects non-null object arguments');
|
|
3863
3709
|
let node = cacheRoot;
|
|
3710
|
+
// Note: decorators add `this` as first argument
|
|
3864
3711
|
for (const arg of localArgs) {
|
|
3865
3712
|
node = getBranch(node, arg);
|
|
3866
3713
|
}
|
|
3867
3714
|
dependant(node, 'memoize');
|
|
3868
|
-
if ('result' in node)
|
|
3715
|
+
if ('result' in node) {
|
|
3716
|
+
if (options.onMemoizationDiscrepancy) {
|
|
3717
|
+
const wasVerification = options.isVerificationRun;
|
|
3718
|
+
options.isVerificationRun = true;
|
|
3719
|
+
try {
|
|
3720
|
+
const fresh = untracked(() => fn(...localArgs));
|
|
3721
|
+
if (!deepCompare(node.result, fresh)) {
|
|
3722
|
+
options.onMemoizationDiscrepancy(node.result, fresh, fn, localArgs, 'calculation');
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
finally {
|
|
3726
|
+
options.isVerificationRun = wasVerification;
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3869
3729
|
return node.result;
|
|
3730
|
+
}
|
|
3870
3731
|
// Create memoize internal effect to track dependencies and invalidate cache
|
|
3871
3732
|
// Use untracked to prevent the effect creation from being affected by parent effects
|
|
3872
|
-
node.cleanup = root(() => effect(
|
|
3733
|
+
node.cleanup = root(() => effect(() => {
|
|
3873
3734
|
// Execute the function and track its dependencies
|
|
3874
3735
|
// The function execution will automatically track dependencies on reactive objects
|
|
3875
3736
|
node.result = fn(...localArgs);
|
|
@@ -3877,8 +3738,27 @@ function memoizeFunction(fn) {
|
|
|
3877
3738
|
// When dependencies change, clear the cache and notify consumers
|
|
3878
3739
|
delete node.result;
|
|
3879
3740
|
touched1(node, { type: 'invalidate', prop: localArgs }, 'memoize');
|
|
3741
|
+
// Lazy memoization: stop the effect so it doesn't re-run immediately.
|
|
3742
|
+
// It will be re-created on next access.
|
|
3743
|
+
if (node.cleanup) {
|
|
3744
|
+
node.cleanup();
|
|
3745
|
+
node.cleanup = undefined;
|
|
3746
|
+
}
|
|
3880
3747
|
};
|
|
3881
|
-
},
|
|
3748
|
+
}, { opaque: true }));
|
|
3749
|
+
if (options.onMemoizationDiscrepancy) {
|
|
3750
|
+
const wasVerification = options.isVerificationRun;
|
|
3751
|
+
options.isVerificationRun = true;
|
|
3752
|
+
try {
|
|
3753
|
+
const fresh = untracked(() => fn(...localArgs));
|
|
3754
|
+
if (!deepCompare(node.result, fresh)) {
|
|
3755
|
+
options.onMemoizationDiscrepancy(node.result, fresh, fn, localArgs, 'comparison');
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
finally {
|
|
3759
|
+
options.isVerificationRun = wasVerification;
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3882
3762
|
return node.result;
|
|
3883
3763
|
}, fn);
|
|
3884
3764
|
memoizedRegistry.set(fnRoot, memoized);
|
|
@@ -3886,19 +3766,37 @@ function memoizeFunction(fn) {
|
|
|
3886
3766
|
return memoized;
|
|
3887
3767
|
}
|
|
3888
3768
|
const memoize = decorator({
|
|
3889
|
-
getter(original, propertyKey) {
|
|
3890
|
-
const memoized = memoizeFunction(markWithRoot(renamed((that) => {
|
|
3891
|
-
return original.call(that);
|
|
3892
|
-
}, `${String(this.constructor.name)}.${String(propertyKey)}`), original));
|
|
3769
|
+
getter(original, target, propertyKey) {
|
|
3893
3770
|
return function () {
|
|
3771
|
+
let wrapper = wrapperRegistry.get(original);
|
|
3772
|
+
if (!wrapper) {
|
|
3773
|
+
wrapper = markWithRoot(renamed((that) => {
|
|
3774
|
+
return original.call(that);
|
|
3775
|
+
}, `${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(propertyKey)}`), {
|
|
3776
|
+
method: original,
|
|
3777
|
+
propertyKey,
|
|
3778
|
+
...(original[rootFunction] ? { [rootFunction]: original[rootFunction] } : {}),
|
|
3779
|
+
});
|
|
3780
|
+
wrapperRegistry.set(original, wrapper);
|
|
3781
|
+
}
|
|
3782
|
+
const memoized = memoizeFunction(wrapper);
|
|
3894
3783
|
return memoized(this);
|
|
3895
3784
|
};
|
|
3896
3785
|
},
|
|
3897
|
-
method(original, name) {
|
|
3898
|
-
const memoized = memoizeFunction(markWithRoot(renamed((that, ...args) => {
|
|
3899
|
-
return original.call(that, ...args);
|
|
3900
|
-
}, `${String(this.constructor.name)}.${String(name)}`), original));
|
|
3786
|
+
method(original, target, name) {
|
|
3901
3787
|
return function (...args) {
|
|
3788
|
+
let wrapper = wrapperRegistry.get(original);
|
|
3789
|
+
if (!wrapper) {
|
|
3790
|
+
wrapper = markWithRoot(renamed((that, ...args) => {
|
|
3791
|
+
return original.call(that, ...args);
|
|
3792
|
+
}, `${String(target?.constructor?.name ?? target?.name ?? 'Object')}.${String(name)}`), {
|
|
3793
|
+
method: original,
|
|
3794
|
+
propertyKey: name,
|
|
3795
|
+
...(original[rootFunction] ? { [rootFunction]: original[rootFunction] } : {}),
|
|
3796
|
+
});
|
|
3797
|
+
wrapperRegistry.set(original, wrapper);
|
|
3798
|
+
}
|
|
3799
|
+
const memoized = memoizeFunction(wrapper);
|
|
3902
3800
|
return memoized(this, ...args);
|
|
3903
3801
|
};
|
|
3904
3802
|
},
|
|
@@ -5003,5 +4901,5 @@ const profileInfo = {
|
|
|
5003
4901
|
nonReactiveObjects,
|
|
5004
4902
|
};
|
|
5005
4903
|
|
|
5006
|
-
export {
|
|
5007
|
-
//# sourceMappingURL=index-
|
|
4904
|
+
export { mixin as A, organize as B, organized as C, profileInfo as D, project as E, reactive as F, reduced as G, register as H, IterableWeakMap as I, registerEffectForDebug as J, registerNativeReactivity as K, registerObjectForDebug as L, root as M, setEffectName as N, setObjectName as O, setZoneEnabled as P, touched as Q, ReactiveBase as R, touched1 as S, trackEffect as T, unreactive as U, untracked as V, unwrap as W, watch as X, IterableWeakSet as a, ReadOnlyError as b, Register as c, addBatchCleanup as d, atomic as e, batch as f, biDi as g, buildReactivityGraph as h, cleanedBy as i, cleanup as j, deepWatch as k, defer as l, derived as m, effect as n, enableDevTools as o, getActivationLog as p, getActiveEffect as q, getActiveProjection as r, getState as s, immutables as t, isDevtoolsEnabled as u, isNonReactive as v, isReactive as w, isZoneEnabled as x, mapped as y, memoize as z };
|
|
4905
|
+
//# sourceMappingURL=index-DiP0RXoZ.esm.js.map
|