scope-state 0.1.5 → 0.1.7
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 +8 -1
- package/dist/core/proxy.d.ts +1 -0
- package/dist/core/proxy.d.ts.map +1 -1
- package/dist/core/snapshot.d.ts +9 -0
- package/dist/core/snapshot.d.ts.map +1 -0
- package/dist/core/tracking.d.ts +15 -3
- package/dist/core/tracking.d.ts.map +1 -1
- package/dist/hooks/useScope.d.ts +8 -7
- package/dist/hooks/useScope.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +172 -120
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +172 -120
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +17 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -353,6 +353,7 @@ function logSubscriptionRemoved(path) {
|
|
|
353
353
|
|
|
354
354
|
// Track proxy to path mapping for type-safe activation
|
|
355
355
|
const proxyPathMap = new WeakMap();
|
|
356
|
+
const proxyTargetMap = new WeakMap();
|
|
356
357
|
// Store a deep clone of the initial store state for use with $reset
|
|
357
358
|
let initialStoreState$1 = {};
|
|
358
359
|
// Path usage tracking
|
|
@@ -570,6 +571,11 @@ function createAdvancedProxy(target, path = [], depth = 0) {
|
|
|
570
571
|
if (typeof prop === 'symbol') {
|
|
571
572
|
return Reflect.get(obj, prop, receiver);
|
|
572
573
|
}
|
|
574
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
575
|
+
// Functions are commands/helpers, not reactive data dependencies.
|
|
576
|
+
if (typeof value === 'function') {
|
|
577
|
+
return value;
|
|
578
|
+
}
|
|
573
579
|
const currentPropPath = [...path, prop.toString()];
|
|
574
580
|
const propPathKey = currentPropPath.join('.');
|
|
575
581
|
// Track path access during dependency tracking
|
|
@@ -580,9 +586,12 @@ function createAdvancedProxy(target, path = [], depth = 0) {
|
|
|
580
586
|
}
|
|
581
587
|
// Track path access for dependency tracking (inline to avoid circular dependency)
|
|
582
588
|
trackPathAccess(currentPropPath);
|
|
583
|
-
const value = obj[prop];
|
|
584
589
|
// For objects, create proxies for nested values
|
|
585
590
|
if (value && typeof value === 'object' && path.length < proxyConfig.maxDepth) {
|
|
591
|
+
// If value is already a proxy, return it directly to prevent double-wrapping
|
|
592
|
+
if (proxyPathMap.has(value)) {
|
|
593
|
+
return value;
|
|
594
|
+
}
|
|
586
595
|
const shouldProxy = !proxyConfig.lazyProxyDeepObjects ||
|
|
587
596
|
pathUsageStats.accessedPaths.has(propPathKey) ||
|
|
588
597
|
pathUsageStats.modifiedPaths.has(propPathKey) ||
|
|
@@ -594,27 +603,16 @@ function createAdvancedProxy(target, path = [], depth = 0) {
|
|
|
594
603
|
}
|
|
595
604
|
return value;
|
|
596
605
|
},
|
|
597
|
-
set(obj, prop, value
|
|
606
|
+
set(obj, prop, value) {
|
|
598
607
|
if (typeof prop === 'symbol') {
|
|
599
|
-
return Reflect.set(obj, prop, value
|
|
608
|
+
return Reflect.set(obj, prop, value);
|
|
600
609
|
}
|
|
601
610
|
const propPath = [...path, prop.toString()];
|
|
602
611
|
const propPathKey = propPath.join('.');
|
|
603
|
-
// Track modification
|
|
604
612
|
if (proxyConfig.trackPathUsage) {
|
|
605
613
|
pathUsageStats.modifiedPaths.add(propPathKey);
|
|
606
614
|
}
|
|
607
|
-
|
|
608
|
-
if (value && typeof value === 'object' && !proxyCache.has(value)) {
|
|
609
|
-
const newPath = [...path, prop.toString()];
|
|
610
|
-
const proxiedValue = createAdvancedProxy(value, newPath, 0);
|
|
611
|
-
const result = Reflect.set(obj, prop, proxiedValue, receiver);
|
|
612
|
-
// Notify the specific property path and all parent/child paths
|
|
613
|
-
notifyListeners(propPath);
|
|
614
|
-
return result;
|
|
615
|
-
}
|
|
616
|
-
const result = Reflect.set(obj, prop, value, receiver);
|
|
617
|
-
// Notify the specific property path (this will also notify parent/child paths)
|
|
615
|
+
const result = Reflect.set(obj, prop, value);
|
|
618
616
|
notifyListeners(propPath);
|
|
619
617
|
return result;
|
|
620
618
|
},
|
|
@@ -640,6 +638,7 @@ function createAdvancedProxy(target, path = [], depth = 0) {
|
|
|
640
638
|
proxyCacheLRU.add(target, proxy);
|
|
641
639
|
// Track the path for this proxy
|
|
642
640
|
proxyPathMap.set(proxy, [...path]);
|
|
641
|
+
proxyTargetMap.set(proxy, target);
|
|
643
642
|
return proxy;
|
|
644
643
|
}
|
|
645
644
|
/**
|
|
@@ -656,16 +655,7 @@ function addObjectMethods(target, path) {
|
|
|
656
655
|
const propPath = [...currentPath, key].join('.');
|
|
657
656
|
pathUsageStats.modifiedPaths.add(propPath);
|
|
658
657
|
}
|
|
659
|
-
|
|
660
|
-
if (newValue && typeof newValue === 'object' && !proxyCache.has(newValue)) {
|
|
661
|
-
const newPath = [...currentPath, key];
|
|
662
|
-
// Use Reflect.set with this proxy as the receiver to trigger the set handler
|
|
663
|
-
Reflect.set(this, key, createAdvancedProxy(newValue, newPath, 0), this);
|
|
664
|
-
}
|
|
665
|
-
else {
|
|
666
|
-
// Use Reflect.set with this proxy as the receiver to trigger the set handler
|
|
667
|
-
Reflect.set(this, key, newValue, this);
|
|
668
|
-
}
|
|
658
|
+
Reflect.set(this, key, newProps[key], this);
|
|
669
659
|
});
|
|
670
660
|
return this;
|
|
671
661
|
},
|
|
@@ -676,28 +666,17 @@ function addObjectMethods(target, path) {
|
|
|
676
666
|
methodsToDefine.$set = {
|
|
677
667
|
value: function (newProps) {
|
|
678
668
|
const currentPath = proxyPathMap.get(this) || path;
|
|
679
|
-
// Clear existing properties
|
|
680
669
|
Object.keys(this).forEach(key => {
|
|
681
670
|
if (typeof this[key] !== 'function') {
|
|
682
671
|
Reflect.deleteProperty(this, key);
|
|
683
672
|
}
|
|
684
673
|
});
|
|
685
|
-
// Set new properties
|
|
686
674
|
Object.keys(newProps || {}).forEach(key => {
|
|
687
675
|
if (proxyConfig.trackPathUsage) {
|
|
688
676
|
const propPath = [...currentPath, key].join('.');
|
|
689
677
|
pathUsageStats.modifiedPaths.add(propPath);
|
|
690
678
|
}
|
|
691
|
-
|
|
692
|
-
if (newValue && typeof newValue === 'object' && !proxyCache.has(newValue)) {
|
|
693
|
-
const newPath = [...currentPath, key];
|
|
694
|
-
// Use Reflect.set with this proxy as the receiver to trigger the set handler
|
|
695
|
-
Reflect.set(this, key, createAdvancedProxy(newValue, newPath, 0), this);
|
|
696
|
-
}
|
|
697
|
-
else {
|
|
698
|
-
// Use Reflect.set with this proxy as the receiver to trigger the set handler
|
|
699
|
-
Reflect.set(this, key, newValue, this);
|
|
700
|
-
}
|
|
679
|
+
Reflect.set(this, key, newProps[key], this);
|
|
701
680
|
});
|
|
702
681
|
return this;
|
|
703
682
|
},
|
|
@@ -727,12 +706,7 @@ function addObjectMethods(target, path) {
|
|
|
727
706
|
pathUsageStats.modifiedPaths.add(propPath);
|
|
728
707
|
}
|
|
729
708
|
const currentValue = this[key];
|
|
730
|
-
|
|
731
|
-
if (newValue && typeof newValue === 'object' && !proxyCache.has(newValue)) {
|
|
732
|
-
const newPath = [...currentPath, key];
|
|
733
|
-
newValue = createAdvancedProxy(newValue, newPath, 0);
|
|
734
|
-
}
|
|
735
|
-
// Use Reflect.set with this proxy as the receiver to trigger the set handler
|
|
709
|
+
const newValue = updater(currentValue);
|
|
736
710
|
Reflect.set(this, key, newValue, this);
|
|
737
711
|
return this;
|
|
738
712
|
},
|
|
@@ -790,20 +764,13 @@ function addObjectMethods(target, path) {
|
|
|
790
764
|
* Add custom methods to array targets
|
|
791
765
|
*/
|
|
792
766
|
function addArrayMethods(target, path) {
|
|
793
|
-
const originalPush =
|
|
794
|
-
const originalSplice =
|
|
767
|
+
const originalPush = Array.prototype.push;
|
|
768
|
+
const originalSplice = Array.prototype.splice;
|
|
795
769
|
// Override push
|
|
796
770
|
Object.defineProperty(target, 'push', {
|
|
797
771
|
value: function (...items) {
|
|
798
772
|
const currentPath = proxyPathMap.get(this) || path;
|
|
799
|
-
const
|
|
800
|
-
if (item && typeof item === 'object' && !proxyCache.has(item)) {
|
|
801
|
-
const itemPath = [...currentPath, '_item'];
|
|
802
|
-
return createAdvancedProxy(item, itemPath, 0);
|
|
803
|
-
}
|
|
804
|
-
return item;
|
|
805
|
-
});
|
|
806
|
-
const result = originalPush.apply(this, processedItems);
|
|
773
|
+
const result = originalPush.apply(this, items);
|
|
807
774
|
if (currentPath.length > 0) {
|
|
808
775
|
if (proxyConfig.trackPathUsage) {
|
|
809
776
|
pathUsageStats.modifiedPaths.add(currentPath.join('.'));
|
|
@@ -815,27 +782,40 @@ function addArrayMethods(target, path) {
|
|
|
815
782
|
writable: true,
|
|
816
783
|
configurable: true
|
|
817
784
|
});
|
|
785
|
+
// Override find with smart tracking during dependency collection
|
|
786
|
+
Object.defineProperty(target, 'find', {
|
|
787
|
+
value: function (predicate, thisArg) {
|
|
788
|
+
if (!isCurrentlyTracking() || !proxyConfig.smartArrayTracking) {
|
|
789
|
+
return Array.prototype.find.call(this, predicate, thisArg);
|
|
790
|
+
}
|
|
791
|
+
const currentPath = proxyPathMap.get(this) || path;
|
|
792
|
+
const length = skipTracking(() => this.length);
|
|
793
|
+
for (let i = 0; i < length; i++) {
|
|
794
|
+
const element = skipTracking(() => Reflect.get(this, i));
|
|
795
|
+
const { value: matched, paths } = capturePathsDuring(() => Boolean(predicate.call(thisArg, element, i, this)));
|
|
796
|
+
if (matched) {
|
|
797
|
+
addTrackedPaths([[...currentPath, String(i)].join('.')]);
|
|
798
|
+
addTrackedPaths(paths);
|
|
799
|
+
return element;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return undefined;
|
|
803
|
+
},
|
|
804
|
+
writable: true,
|
|
805
|
+
configurable: true
|
|
806
|
+
});
|
|
818
807
|
// Override splice
|
|
819
808
|
Object.defineProperty(target, 'splice', {
|
|
820
809
|
value: function (start, deleteCount, ...items) {
|
|
821
810
|
const currentPath = proxyPathMap.get(this) || path;
|
|
822
811
|
const arrayLength = this.length;
|
|
823
|
-
const processedItems = items.map((item, index) => {
|
|
824
|
-
if (item && typeof item === 'object' && !proxyCache.has(item)) {
|
|
825
|
-
const itemPath = [...currentPath, (start + index).toString()];
|
|
826
|
-
return createAdvancedProxy(item, itemPath, 0);
|
|
827
|
-
}
|
|
828
|
-
return item;
|
|
829
|
-
});
|
|
830
812
|
const actualDeleteCount = deleteCount === undefined ? (arrayLength - start) : deleteCount;
|
|
831
|
-
const result = originalSplice.apply(this, [start, actualDeleteCount, ...
|
|
813
|
+
const result = originalSplice.apply(this, [start, actualDeleteCount, ...items]);
|
|
832
814
|
if (currentPath.length > 0) {
|
|
833
815
|
if (proxyConfig.trackPathUsage) {
|
|
834
816
|
pathUsageStats.modifiedPaths.add(currentPath.join('.'));
|
|
835
817
|
}
|
|
836
|
-
// Notify the array itself
|
|
837
818
|
notifyListeners(currentPath);
|
|
838
|
-
// Also notify about each index that was affected
|
|
839
819
|
for (let i = start; i < arrayLength; i++) {
|
|
840
820
|
const indexPath = [...currentPath, i.toString()];
|
|
841
821
|
notifyListeners(indexPath);
|
|
@@ -856,14 +836,7 @@ function addArrayMethods(target, path) {
|
|
|
856
836
|
}
|
|
857
837
|
const currentPath = proxyPathMap.get(this) || path;
|
|
858
838
|
this.length = 0;
|
|
859
|
-
|
|
860
|
-
if (item && typeof item === 'object' && !proxyCache.has(item)) {
|
|
861
|
-
const itemPath = [...currentPath, index.toString()];
|
|
862
|
-
return createAdvancedProxy(item, itemPath, 0);
|
|
863
|
-
}
|
|
864
|
-
return item;
|
|
865
|
-
});
|
|
866
|
-
originalPush.apply(this, processedItems);
|
|
839
|
+
originalPush.apply(this, newArray);
|
|
867
840
|
if (currentPath.length > 0) {
|
|
868
841
|
if (proxyConfig.trackPathUsage) {
|
|
869
842
|
pathUsageStats.modifiedPaths.add(currentPath.join('.'));
|
|
@@ -895,15 +868,8 @@ function addArrayMethods(target, path) {
|
|
|
895
868
|
initialValue = [];
|
|
896
869
|
}
|
|
897
870
|
this.length = 0;
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
const itemPath = [...currentPath, index.toString()];
|
|
901
|
-
return createAdvancedProxy(item, itemPath, 0);
|
|
902
|
-
}
|
|
903
|
-
return item;
|
|
904
|
-
});
|
|
905
|
-
if (processedItems.length > 0) {
|
|
906
|
-
originalPush.apply(this, processedItems);
|
|
871
|
+
if (initialValue.length > 0) {
|
|
872
|
+
originalPush.apply(this, JSON.parse(JSON.stringify(initialValue)));
|
|
907
873
|
}
|
|
908
874
|
if (currentPath.length > 0) {
|
|
909
875
|
notifyListeners(currentPath);
|
|
@@ -978,48 +944,125 @@ function optimizeMemoryUsage(aggressive = false) {
|
|
|
978
944
|
};
|
|
979
945
|
}
|
|
980
946
|
|
|
981
|
-
//
|
|
982
|
-
let
|
|
947
|
+
// Unique full dotted paths accessed during selector execution
|
|
948
|
+
let trackedPaths = new Set();
|
|
983
949
|
let isTracking = false;
|
|
984
950
|
let skipTrackingDepth = 0;
|
|
985
951
|
/**
|
|
986
|
-
* Track dependencies during selector execution - tracks
|
|
952
|
+
* Track dependencies during selector execution - tracks full dotted paths
|
|
987
953
|
*/
|
|
988
954
|
function trackDependencies(selector) {
|
|
989
|
-
// Start tracking
|
|
990
955
|
isTracking = true;
|
|
991
|
-
|
|
956
|
+
trackedPaths = new Set();
|
|
992
957
|
skipTrackingDepth = 0;
|
|
993
|
-
// Execute selector to track dependencies
|
|
994
958
|
const value = selector();
|
|
995
|
-
// Stop tracking and get the tracked paths
|
|
996
959
|
isTracking = false;
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
960
|
+
const paths = Array.from(trackedPaths).filter(path => path && path.length < 500);
|
|
961
|
+
trackedPaths = new Set();
|
|
962
|
+
return { value, paths };
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Check if we're currently tracking dependencies
|
|
966
|
+
*/
|
|
967
|
+
function isCurrentlyTracking() {
|
|
968
|
+
return isTracking && skipTrackingDepth === 0;
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Temporarily skip tracking (for array method internals)
|
|
972
|
+
*/
|
|
973
|
+
function skipTracking(fn) {
|
|
974
|
+
skipTrackingDepth++;
|
|
975
|
+
try {
|
|
976
|
+
return fn();
|
|
977
|
+
}
|
|
978
|
+
finally {
|
|
979
|
+
skipTrackingDepth--;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Run fn while capturing any newly tracked paths, then remove them from the main set.
|
|
984
|
+
* Caller decides whether to re-add captured paths (e.g. on a matched find iteration).
|
|
985
|
+
*/
|
|
986
|
+
function capturePathsDuring(fn) {
|
|
987
|
+
const snapshot = new Set(trackedPaths);
|
|
988
|
+
const value = fn();
|
|
989
|
+
const newPaths = [];
|
|
990
|
+
trackedPaths.forEach(path => {
|
|
991
|
+
if (!snapshot.has(path)) {
|
|
992
|
+
newPaths.push(path);
|
|
993
|
+
trackedPaths.delete(path);
|
|
994
|
+
}
|
|
1001
995
|
});
|
|
1002
|
-
|
|
1003
|
-
|
|
996
|
+
return { value, paths: newPaths };
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Add paths to the active tracking set (used by smart array method overrides)
|
|
1000
|
+
*/
|
|
1001
|
+
function addTrackedPaths(paths) {
|
|
1002
|
+
if (!isTracking)
|
|
1003
|
+
return;
|
|
1004
|
+
paths.forEach(path => trackedPaths.add(path));
|
|
1004
1005
|
}
|
|
1005
1006
|
/**
|
|
1006
|
-
* Add a path
|
|
1007
|
+
* Add a full path to tracking during proxy get operations
|
|
1007
1008
|
*/
|
|
1008
1009
|
function trackPathAccess(path) {
|
|
1009
1010
|
if (!isTracking || skipTrackingDepth > 0)
|
|
1010
1011
|
return;
|
|
1011
|
-
|
|
1012
|
-
const prop = path[path.length - 1];
|
|
1013
|
-
// Only track if prop exists and path isn't too deep
|
|
1014
|
-
if (prop && path.length <= proxyConfig.maxPathLength) {
|
|
1015
|
-
currentPath.push(prop);
|
|
1016
|
-
// Add full path to usage stats and selector paths for ultra-selective proxying
|
|
1012
|
+
if (path.length <= proxyConfig.maxPathLength) {
|
|
1017
1013
|
const fullPath = path.join('.');
|
|
1014
|
+
trackedPaths.add(fullPath);
|
|
1018
1015
|
pathUsageStats.accessedPaths.add(fullPath);
|
|
1019
1016
|
selectorPaths.add(fullPath);
|
|
1020
1017
|
}
|
|
1021
1018
|
}
|
|
1022
1019
|
|
|
1020
|
+
function isPlainObject(value) {
|
|
1021
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1022
|
+
return prototype === Object.prototype || prototype === null;
|
|
1023
|
+
}
|
|
1024
|
+
function unwrapProxyTarget(value) {
|
|
1025
|
+
if (value && typeof value === 'object' && proxyTargetMap.has(value)) {
|
|
1026
|
+
return proxyTargetMap.get(value);
|
|
1027
|
+
}
|
|
1028
|
+
return value;
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Create a read-only snapshot suitable for rendering.
|
|
1032
|
+
*
|
|
1033
|
+
* Snapshots are plain arrays/objects with no proxy methods, which makes them
|
|
1034
|
+
* safe for React Compiler memoization while keeping `$` as the mutable API.
|
|
1035
|
+
*/
|
|
1036
|
+
function createReadonlySnapshot(value, seen = new WeakMap()) {
|
|
1037
|
+
if (value === null || typeof value !== 'object') {
|
|
1038
|
+
return value;
|
|
1039
|
+
}
|
|
1040
|
+
const source = unwrapProxyTarget(value);
|
|
1041
|
+
if (seen.has(source)) {
|
|
1042
|
+
return seen.get(source);
|
|
1043
|
+
}
|
|
1044
|
+
if (Array.isArray(source)) {
|
|
1045
|
+
const snapshot = [];
|
|
1046
|
+
seen.set(source, snapshot);
|
|
1047
|
+
source.forEach(item => {
|
|
1048
|
+
snapshot.push(createReadonlySnapshot(item, seen));
|
|
1049
|
+
});
|
|
1050
|
+
return snapshot;
|
|
1051
|
+
}
|
|
1052
|
+
if (!isPlainObject(source)) {
|
|
1053
|
+
return source;
|
|
1054
|
+
}
|
|
1055
|
+
const snapshot = {};
|
|
1056
|
+
seen.set(source, snapshot);
|
|
1057
|
+
Object.keys(source).forEach(key => {
|
|
1058
|
+
const propertyValue = source[key];
|
|
1059
|
+
if (typeof propertyValue !== 'function') {
|
|
1060
|
+
snapshot[key] = createReadonlySnapshot(propertyValue, seen);
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1063
|
+
return snapshot;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1023
1066
|
/**
|
|
1024
1067
|
* Hook to subscribe to the global store and re-render when specific data changes.
|
|
1025
1068
|
*
|
|
@@ -1027,15 +1070,16 @@ function trackPathAccess(path) {
|
|
|
1027
1070
|
* re-renders the component when those specific paths change. This provides
|
|
1028
1071
|
* fine-grained reactivity without unnecessary renders.
|
|
1029
1072
|
*
|
|
1030
|
-
*
|
|
1031
|
-
*
|
|
1073
|
+
* The returned value is a read-only snapshot of the selected state. Mutate the
|
|
1074
|
+
* store through the main `$` proxy (or a proxy created explicitly for commands),
|
|
1075
|
+
* and use the hook return value only for rendering.
|
|
1032
1076
|
*
|
|
1033
1077
|
* @example
|
|
1034
1078
|
* // Subscribe to user data
|
|
1035
1079
|
* const user = useScope(() => $.user);
|
|
1036
1080
|
*
|
|
1037
|
-
* //
|
|
1038
|
-
* user.$merge({ name: 'New Name' });
|
|
1081
|
+
* // Mutate through the main store proxy
|
|
1082
|
+
* $.user.$merge({ name: 'New Name' });
|
|
1039
1083
|
*
|
|
1040
1084
|
* // Subscribe to a specific property
|
|
1041
1085
|
* const userName = useScope(() => $.user.name);
|
|
@@ -1044,22 +1088,24 @@ function trackPathAccess(path) {
|
|
|
1044
1088
|
* const isAdmin = useScope(() => $.user.role === 'admin');
|
|
1045
1089
|
*
|
|
1046
1090
|
* @param selector - Function that returns the data you want to subscribe to
|
|
1047
|
-
* @returns
|
|
1091
|
+
* @returns A read-only snapshot of the selected data
|
|
1048
1092
|
*/
|
|
1049
1093
|
function useScope(selector) {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1094
|
+
const snapshotCacheRef = react.useRef({
|
|
1095
|
+
revision: -1,
|
|
1096
|
+
source: undefined,
|
|
1097
|
+
snapshot: undefined,
|
|
1098
|
+
});
|
|
1099
|
+
// Track dependencies and get the selected value from the store
|
|
1100
|
+
const { value: selectedValue, paths: trackedPaths } = trackDependencies(selector);
|
|
1055
1101
|
// Add tracked paths to selector paths for ultra-selective proxying
|
|
1056
1102
|
trackedPaths.forEach(path => {
|
|
1057
1103
|
selectorPaths.add(path);
|
|
1058
1104
|
pathUsageStats.subscribedPaths.add(path);
|
|
1059
1105
|
});
|
|
1060
|
-
// Use a counter to
|
|
1061
|
-
//
|
|
1062
|
-
const [, forceUpdate] = react.useState(0);
|
|
1106
|
+
// Use a counter to invalidate the cached snapshot only when this hook
|
|
1107
|
+
// receives a relevant store notification.
|
|
1108
|
+
const [revision, forceUpdate] = react.useState(0);
|
|
1063
1109
|
// Create stable update handler that forces re-render
|
|
1064
1110
|
const handleChange = react.useCallback(() => {
|
|
1065
1111
|
try {
|
|
@@ -1080,11 +1126,7 @@ function useScope(selector) {
|
|
|
1080
1126
|
handleChange();
|
|
1081
1127
|
}, 16);
|
|
1082
1128
|
}
|
|
1083
|
-
|
|
1084
|
-
// If trackedPaths is ['user', 'name'], create subscriptions for ['user', 'user.name']
|
|
1085
|
-
const pathKeys = trackedPaths.length > 0
|
|
1086
|
-
? trackedPaths.map((_, index, array) => array.slice(0, index + 1).join('.'))
|
|
1087
|
-
: [''];
|
|
1129
|
+
const pathKeys = trackedPaths.length > 0 ? trackedPaths : [''];
|
|
1088
1130
|
// Subscribe to all relevant paths
|
|
1089
1131
|
const unsubscribeFunctions = pathKeys.map(pathKey => {
|
|
1090
1132
|
// Mark this path as subscribed for usage tracking
|
|
@@ -1096,8 +1138,18 @@ function useScope(selector) {
|
|
|
1096
1138
|
unsubscribeFunctions.forEach(unsubscribe => unsubscribe());
|
|
1097
1139
|
};
|
|
1098
1140
|
}, [trackedPaths.join(','), handleChange]); // Stable dependencies
|
|
1099
|
-
|
|
1100
|
-
|
|
1141
|
+
if (selectedValue === null || typeof selectedValue !== 'object') {
|
|
1142
|
+
return selectedValue;
|
|
1143
|
+
}
|
|
1144
|
+
if (snapshotCacheRef.current.revision !== revision ||
|
|
1145
|
+
snapshotCacheRef.current.source !== selectedValue) {
|
|
1146
|
+
snapshotCacheRef.current = {
|
|
1147
|
+
revision,
|
|
1148
|
+
source: selectedValue,
|
|
1149
|
+
snapshot: createReadonlySnapshot(selectedValue),
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
return snapshotCacheRef.current.snapshot;
|
|
1101
1153
|
}
|
|
1102
1154
|
|
|
1103
1155
|
/**
|