scope-state 0.1.4 → 0.1.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 +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/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 +108 -95
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +108 -95
- 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 +1 -1
package/README.md
CHANGED
|
@@ -170,7 +170,10 @@ const TodoList = () => {
|
|
|
170
170
|
### Core Functions
|
|
171
171
|
|
|
172
172
|
#### `useScope(selector)`
|
|
173
|
-
Subscribe to reactive state changes.
|
|
173
|
+
Subscribe to reactive state changes and receive a read-only snapshot for rendering.
|
|
174
|
+
|
|
175
|
+
Mutate state through the main `$` proxy. The value returned from `useScope()` is
|
|
176
|
+
intended for reads only.
|
|
174
177
|
|
|
175
178
|
```tsx
|
|
176
179
|
// Subscribe to entire object
|
|
@@ -184,6 +187,10 @@ const isAdmin = useScope(() => $.user.role === 'admin');
|
|
|
184
187
|
|
|
185
188
|
// Subscribe to array
|
|
186
189
|
const todos = useScope(() => $.todos);
|
|
190
|
+
|
|
191
|
+
// Mutate through the main store proxy
|
|
192
|
+
$.user.$merge({ name: 'John' });
|
|
193
|
+
$.todos.push({ id: 1, text: 'Ship feature', completed: false });
|
|
187
194
|
```
|
|
188
195
|
|
|
189
196
|
#### `configure(options)`
|
package/dist/core/proxy.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CustomMethods, CustomArrayMethods } from '../types';
|
|
2
2
|
export declare const proxyPathMap: WeakMap<object, string[]>;
|
|
3
|
+
export declare const proxyTargetMap: WeakMap<object, object>;
|
|
3
4
|
export declare const pathUsageStats: {
|
|
4
5
|
accessedPaths: Set<string>;
|
|
5
6
|
modifiedPaths: Set<string>;
|
package/dist/core/proxy.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/core/proxy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAIlE,eAAO,MAAM,YAAY,2BAAkC,CAAC;AAM5D,eAAO,MAAM,cAAc;;;;CAI1B,CAAC;AAGF,eAAO,MAAM,aAAa,aAAoB,CAAC;AAM/C,eAAO,MAAM,eAAe;;;;;CAK3B,CAAC;AAiGF,eAAO,MAAM,cAAc;;;;;;2BA4CF,MAAM;CAS9B,CAAC;AAEF;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI,CAErD;AAYD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,EAClD,MAAM,EAAE,CAAC,EACT,IAAI,GAAE,MAAM,EAAO,EACnB,KAAK,GAAE,MAAU,GAChB,CAAC,GAAG,CAAC,CAAC,SAAS,GAAG,EAAE,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/core/proxy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAIlE,eAAO,MAAM,YAAY,2BAAkC,CAAC;AAC5D,eAAO,MAAM,cAAc,yBAAgC,CAAC;AAM5D,eAAO,MAAM,cAAc;;;;CAI1B,CAAC;AAGF,eAAO,MAAM,aAAa,aAAoB,CAAC;AAM/C,eAAO,MAAM,eAAe;;;;;CAK3B,CAAC;AAiGF,eAAO,MAAM,cAAc;;;;;;2BA4CF,MAAM;CAS9B,CAAC;AAEF;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI,CAErD;AAYD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,EAClD,MAAM,EAAE,CAAC,EACT,IAAI,GAAE,MAAM,EAAO,EACnB,KAAK,GAAE,MAAU,GAChB,CAAC,GAAG,CAAC,CAAC,SAAS,GAAG,EAAE,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,CAwJrE;AAqRD;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAUtC;AAED;;GAEG;AACH,wBAAgB,kBAAkB;;;;;;;EAMjC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,UAAQ,GAAG,GAAG,CA4B3D"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ScopeSnapshot } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Create a read-only snapshot suitable for rendering.
|
|
4
|
+
*
|
|
5
|
+
* Snapshots are plain arrays/objects with no proxy methods, which makes them
|
|
6
|
+
* safe for React Compiler memoization while keeping `$` as the mutable API.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createReadonlySnapshot<T>(value: T, seen?: WeakMap<object, unknown>): ScopeSnapshot<T>;
|
|
9
|
+
//# sourceMappingURL=snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/core/snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAgB9C;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,EACtC,KAAK,EAAE,CAAC,EACR,IAAI,2BAAiC,GACpC,aAAa,CAAC,CAAC,CAAC,CAsClB"}
|
package/dist/hooks/useScope.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ScopeSnapshot } from '../types';
|
|
2
2
|
/**
|
|
3
3
|
* Hook to subscribe to the global store and re-render when specific data changes.
|
|
4
4
|
*
|
|
@@ -6,15 +6,16 @@ import type { CustomMethods, CustomArrayMethods } from '../types';
|
|
|
6
6
|
* re-renders the component when those specific paths change. This provides
|
|
7
7
|
* fine-grained reactivity without unnecessary renders.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* The returned value is a read-only snapshot of the selected state. Mutate the
|
|
10
|
+
* store through the main `$` proxy (or a proxy created explicitly for commands),
|
|
11
|
+
* and use the hook return value only for rendering.
|
|
11
12
|
*
|
|
12
13
|
* @example
|
|
13
14
|
* // Subscribe to user data
|
|
14
15
|
* const user = useScope(() => $.user);
|
|
15
16
|
*
|
|
16
|
-
* //
|
|
17
|
-
* user.$merge({ name: 'New Name' });
|
|
17
|
+
* // Mutate through the main store proxy
|
|
18
|
+
* $.user.$merge({ name: 'New Name' });
|
|
18
19
|
*
|
|
19
20
|
* // Subscribe to a specific property
|
|
20
21
|
* const userName = useScope(() => $.user.name);
|
|
@@ -23,7 +24,7 @@ import type { CustomMethods, CustomArrayMethods } from '../types';
|
|
|
23
24
|
* const isAdmin = useScope(() => $.user.role === 'admin');
|
|
24
25
|
*
|
|
25
26
|
* @param selector - Function that returns the data you want to subscribe to
|
|
26
|
-
* @returns
|
|
27
|
+
* @returns A read-only snapshot of the selected data
|
|
27
28
|
*/
|
|
28
|
-
export declare function useScope<T>(selector: () => T):
|
|
29
|
+
export declare function useScope<T>(selector: () => T): ScopeSnapshot<T>;
|
|
29
30
|
//# sourceMappingURL=useScope.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useScope.d.ts","sourceRoot":"","sources":["../../src/hooks/useScope.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useScope.d.ts","sourceRoot":"","sources":["../../src/hooks/useScope.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,QAAQ,EAAE,MAAM,CAAC,GAChB,aAAa,CAAC,CAAC,CAAC,CAiFlB"}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { persistenceAPI, hydrateState, mergePersistedIntoState } from './persist
|
|
|
7
7
|
export { createLocalStorageAdapter, createMemoryAdapter, createCachedAdapter, getDefaultAdapter } from './persistence/adapters';
|
|
8
8
|
export { setStorageAdapter, getStorageAdapter } from './persistence/storage';
|
|
9
9
|
export { setInitialStoreState, createAdvancedProxy, pathUsageStats, selectorPaths, clearProxyCache, getProxyCacheStats, optimizeMemoryUsage, proxyPathMap } from './core/proxy';
|
|
10
|
-
export type { ScopeConfig, ProxyConfig, MonitoringConfig, PersistenceConfig, StorageAdapter, MaybePromise, CustomMethods, CustomArrayMethods, StoreType, MonitoringStats, ProxyCacheStats, PathUsageStats } from './types';
|
|
10
|
+
export type { ScopeConfig, ProxyConfig, MonitoringConfig, PersistenceConfig, StorageAdapter, MaybePromise, CustomMethods, CustomArrayMethods, ScopeSnapshot, StoreType, MonitoringStats, ProxyCacheStats, PathUsageStats } from './types';
|
|
11
11
|
import type { StoreType, CustomMethods, CustomArrayMethods, ScopeConfig } from './types';
|
|
12
12
|
/**
|
|
13
13
|
* Configure Scope with custom settings and return a properly typed store
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAIrE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAC/F,OAAO,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChI,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACb,MAAM,cAAc,CAAC;AAGtB,YAAY,EACV,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,SAAS,EACT,eAAe,EACf,eAAe,EACf,cAAc,EACf,MAAM,SAAS,CAAC;AASjB,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAYzF;;;GAGG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACrD,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,GACrB,SAAS,CAAC,CAAC,CAAC,CAwEd;AAGD,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAqB7F;AASD,eAAO,MAAM,CAAC,EAAE,SAAS,CAAC,GAAG,CAAoB,CAAC;AAGlD,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,SAAS,GAAG,EAAE,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,CAE5H;AAGD,eAAO,MAAM,MAAM,uBAAiB,CAAC;AAGrC,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAE5C;AAGD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAyBrC;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAEtC;AAED,wBAAgB,QAAQ,CAAC,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC,CAsClE;AAED,wBAAgB,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAK5D;AAYD,eAAO,MAAM,SAAS;;;;CAUrB,CAAC;AAGF,eAAO,MAAM,QAAQ,KAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAIrE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAC/F,OAAO,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChI,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACb,MAAM,cAAc,CAAC;AAGtB,YAAY,EACV,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,aAAa,EACb,SAAS,EACT,eAAe,EACf,eAAe,EACf,cAAc,EACf,MAAM,SAAS,CAAC;AASjB,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAYzF;;;GAGG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACrD,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,GACrB,SAAS,CAAC,CAAC,CAAC,CAwEd;AAGD,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAqB7F;AASD,eAAO,MAAM,CAAC,EAAE,SAAS,CAAC,GAAG,CAAoB,CAAC;AAGlD,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,SAAS,GAAG,EAAE,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,CAE5H;AAGD,eAAO,MAAM,MAAM,uBAAiB,CAAC;AAGrC,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAE5C;AAGD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAyBrC;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAEtC;AAED,wBAAgB,QAAQ,CAAC,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC,CAsClE;AAED,wBAAgB,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAK5D;AAYD,eAAO,MAAM,SAAS;;;;CAUrB,CAAC;AAGF,eAAO,MAAM,QAAQ,KAAc,CAAC"}
|
package/dist/index.esm.js
CHANGED
|
@@ -351,6 +351,7 @@ function logSubscriptionRemoved(path) {
|
|
|
351
351
|
|
|
352
352
|
// Track proxy to path mapping for type-safe activation
|
|
353
353
|
const proxyPathMap = new WeakMap();
|
|
354
|
+
const proxyTargetMap = new WeakMap();
|
|
354
355
|
// Store a deep clone of the initial store state for use with $reset
|
|
355
356
|
let initialStoreState$1 = {};
|
|
356
357
|
// Path usage tracking
|
|
@@ -568,6 +569,11 @@ function createAdvancedProxy(target, path = [], depth = 0) {
|
|
|
568
569
|
if (typeof prop === 'symbol') {
|
|
569
570
|
return Reflect.get(obj, prop, receiver);
|
|
570
571
|
}
|
|
572
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
573
|
+
// Functions are commands/helpers, not reactive data dependencies.
|
|
574
|
+
if (typeof value === 'function') {
|
|
575
|
+
return value;
|
|
576
|
+
}
|
|
571
577
|
const currentPropPath = [...path, prop.toString()];
|
|
572
578
|
const propPathKey = currentPropPath.join('.');
|
|
573
579
|
// Track path access during dependency tracking
|
|
@@ -578,9 +584,12 @@ function createAdvancedProxy(target, path = [], depth = 0) {
|
|
|
578
584
|
}
|
|
579
585
|
// Track path access for dependency tracking (inline to avoid circular dependency)
|
|
580
586
|
trackPathAccess(currentPropPath);
|
|
581
|
-
const value = obj[prop];
|
|
582
587
|
// For objects, create proxies for nested values
|
|
583
588
|
if (value && typeof value === 'object' && path.length < proxyConfig.maxDepth) {
|
|
589
|
+
// If value is already a proxy, return it directly to prevent double-wrapping
|
|
590
|
+
if (proxyPathMap.has(value)) {
|
|
591
|
+
return value;
|
|
592
|
+
}
|
|
584
593
|
const shouldProxy = !proxyConfig.lazyProxyDeepObjects ||
|
|
585
594
|
pathUsageStats.accessedPaths.has(propPathKey) ||
|
|
586
595
|
pathUsageStats.modifiedPaths.has(propPathKey) ||
|
|
@@ -592,27 +601,16 @@ function createAdvancedProxy(target, path = [], depth = 0) {
|
|
|
592
601
|
}
|
|
593
602
|
return value;
|
|
594
603
|
},
|
|
595
|
-
set(obj, prop, value
|
|
604
|
+
set(obj, prop, value) {
|
|
596
605
|
if (typeof prop === 'symbol') {
|
|
597
|
-
return Reflect.set(obj, prop, value
|
|
606
|
+
return Reflect.set(obj, prop, value);
|
|
598
607
|
}
|
|
599
608
|
const propPath = [...path, prop.toString()];
|
|
600
609
|
const propPathKey = propPath.join('.');
|
|
601
|
-
// Track modification
|
|
602
610
|
if (proxyConfig.trackPathUsage) {
|
|
603
611
|
pathUsageStats.modifiedPaths.add(propPathKey);
|
|
604
612
|
}
|
|
605
|
-
|
|
606
|
-
if (value && typeof value === 'object' && !proxyCache.has(value)) {
|
|
607
|
-
const newPath = [...path, prop.toString()];
|
|
608
|
-
const proxiedValue = createAdvancedProxy(value, newPath, 0);
|
|
609
|
-
const result = Reflect.set(obj, prop, proxiedValue, receiver);
|
|
610
|
-
// Notify the specific property path and all parent/child paths
|
|
611
|
-
notifyListeners(propPath);
|
|
612
|
-
return result;
|
|
613
|
-
}
|
|
614
|
-
const result = Reflect.set(obj, prop, value, receiver);
|
|
615
|
-
// Notify the specific property path (this will also notify parent/child paths)
|
|
613
|
+
const result = Reflect.set(obj, prop, value);
|
|
616
614
|
notifyListeners(propPath);
|
|
617
615
|
return result;
|
|
618
616
|
},
|
|
@@ -638,6 +636,7 @@ function createAdvancedProxy(target, path = [], depth = 0) {
|
|
|
638
636
|
proxyCacheLRU.add(target, proxy);
|
|
639
637
|
// Track the path for this proxy
|
|
640
638
|
proxyPathMap.set(proxy, [...path]);
|
|
639
|
+
proxyTargetMap.set(proxy, target);
|
|
641
640
|
return proxy;
|
|
642
641
|
}
|
|
643
642
|
/**
|
|
@@ -654,16 +653,7 @@ function addObjectMethods(target, path) {
|
|
|
654
653
|
const propPath = [...currentPath, key].join('.');
|
|
655
654
|
pathUsageStats.modifiedPaths.add(propPath);
|
|
656
655
|
}
|
|
657
|
-
|
|
658
|
-
if (newValue && typeof newValue === 'object' && !proxyCache.has(newValue)) {
|
|
659
|
-
const newPath = [...currentPath, key];
|
|
660
|
-
// Use Reflect.set with this proxy as the receiver to trigger the set handler
|
|
661
|
-
Reflect.set(this, key, createAdvancedProxy(newValue, newPath, 0), this);
|
|
662
|
-
}
|
|
663
|
-
else {
|
|
664
|
-
// Use Reflect.set with this proxy as the receiver to trigger the set handler
|
|
665
|
-
Reflect.set(this, key, newValue, this);
|
|
666
|
-
}
|
|
656
|
+
Reflect.set(this, key, newProps[key], this);
|
|
667
657
|
});
|
|
668
658
|
return this;
|
|
669
659
|
},
|
|
@@ -674,28 +664,17 @@ function addObjectMethods(target, path) {
|
|
|
674
664
|
methodsToDefine.$set = {
|
|
675
665
|
value: function (newProps) {
|
|
676
666
|
const currentPath = proxyPathMap.get(this) || path;
|
|
677
|
-
// Clear existing properties
|
|
678
667
|
Object.keys(this).forEach(key => {
|
|
679
668
|
if (typeof this[key] !== 'function') {
|
|
680
669
|
Reflect.deleteProperty(this, key);
|
|
681
670
|
}
|
|
682
671
|
});
|
|
683
|
-
// Set new properties
|
|
684
672
|
Object.keys(newProps || {}).forEach(key => {
|
|
685
673
|
if (proxyConfig.trackPathUsage) {
|
|
686
674
|
const propPath = [...currentPath, key].join('.');
|
|
687
675
|
pathUsageStats.modifiedPaths.add(propPath);
|
|
688
676
|
}
|
|
689
|
-
|
|
690
|
-
if (newValue && typeof newValue === 'object' && !proxyCache.has(newValue)) {
|
|
691
|
-
const newPath = [...currentPath, key];
|
|
692
|
-
// Use Reflect.set with this proxy as the receiver to trigger the set handler
|
|
693
|
-
Reflect.set(this, key, createAdvancedProxy(newValue, newPath, 0), this);
|
|
694
|
-
}
|
|
695
|
-
else {
|
|
696
|
-
// Use Reflect.set with this proxy as the receiver to trigger the set handler
|
|
697
|
-
Reflect.set(this, key, newValue, this);
|
|
698
|
-
}
|
|
677
|
+
Reflect.set(this, key, newProps[key], this);
|
|
699
678
|
});
|
|
700
679
|
return this;
|
|
701
680
|
},
|
|
@@ -725,12 +704,7 @@ function addObjectMethods(target, path) {
|
|
|
725
704
|
pathUsageStats.modifiedPaths.add(propPath);
|
|
726
705
|
}
|
|
727
706
|
const currentValue = this[key];
|
|
728
|
-
|
|
729
|
-
if (newValue && typeof newValue === 'object' && !proxyCache.has(newValue)) {
|
|
730
|
-
const newPath = [...currentPath, key];
|
|
731
|
-
newValue = createAdvancedProxy(newValue, newPath, 0);
|
|
732
|
-
}
|
|
733
|
-
// Use Reflect.set with this proxy as the receiver to trigger the set handler
|
|
707
|
+
const newValue = updater(currentValue);
|
|
734
708
|
Reflect.set(this, key, newValue, this);
|
|
735
709
|
return this;
|
|
736
710
|
},
|
|
@@ -788,20 +762,13 @@ function addObjectMethods(target, path) {
|
|
|
788
762
|
* Add custom methods to array targets
|
|
789
763
|
*/
|
|
790
764
|
function addArrayMethods(target, path) {
|
|
791
|
-
const originalPush =
|
|
792
|
-
const originalSplice =
|
|
765
|
+
const originalPush = Array.prototype.push;
|
|
766
|
+
const originalSplice = Array.prototype.splice;
|
|
793
767
|
// Override push
|
|
794
768
|
Object.defineProperty(target, 'push', {
|
|
795
769
|
value: function (...items) {
|
|
796
770
|
const currentPath = proxyPathMap.get(this) || path;
|
|
797
|
-
const
|
|
798
|
-
if (item && typeof item === 'object' && !proxyCache.has(item)) {
|
|
799
|
-
const itemPath = [...currentPath, '_item'];
|
|
800
|
-
return createAdvancedProxy(item, itemPath, 0);
|
|
801
|
-
}
|
|
802
|
-
return item;
|
|
803
|
-
});
|
|
804
|
-
const result = originalPush.apply(this, processedItems);
|
|
771
|
+
const result = originalPush.apply(this, items);
|
|
805
772
|
if (currentPath.length > 0) {
|
|
806
773
|
if (proxyConfig.trackPathUsage) {
|
|
807
774
|
pathUsageStats.modifiedPaths.add(currentPath.join('.'));
|
|
@@ -818,22 +785,13 @@ function addArrayMethods(target, path) {
|
|
|
818
785
|
value: function (start, deleteCount, ...items) {
|
|
819
786
|
const currentPath = proxyPathMap.get(this) || path;
|
|
820
787
|
const arrayLength = this.length;
|
|
821
|
-
const processedItems = items.map((item, index) => {
|
|
822
|
-
if (item && typeof item === 'object' && !proxyCache.has(item)) {
|
|
823
|
-
const itemPath = [...currentPath, (start + index).toString()];
|
|
824
|
-
return createAdvancedProxy(item, itemPath, 0);
|
|
825
|
-
}
|
|
826
|
-
return item;
|
|
827
|
-
});
|
|
828
788
|
const actualDeleteCount = deleteCount === undefined ? (arrayLength - start) : deleteCount;
|
|
829
|
-
const result = originalSplice.apply(this, [start, actualDeleteCount, ...
|
|
789
|
+
const result = originalSplice.apply(this, [start, actualDeleteCount, ...items]);
|
|
830
790
|
if (currentPath.length > 0) {
|
|
831
791
|
if (proxyConfig.trackPathUsage) {
|
|
832
792
|
pathUsageStats.modifiedPaths.add(currentPath.join('.'));
|
|
833
793
|
}
|
|
834
|
-
// Notify the array itself
|
|
835
794
|
notifyListeners(currentPath);
|
|
836
|
-
// Also notify about each index that was affected
|
|
837
795
|
for (let i = start; i < arrayLength; i++) {
|
|
838
796
|
const indexPath = [...currentPath, i.toString()];
|
|
839
797
|
notifyListeners(indexPath);
|
|
@@ -854,14 +812,7 @@ function addArrayMethods(target, path) {
|
|
|
854
812
|
}
|
|
855
813
|
const currentPath = proxyPathMap.get(this) || path;
|
|
856
814
|
this.length = 0;
|
|
857
|
-
|
|
858
|
-
if (item && typeof item === 'object' && !proxyCache.has(item)) {
|
|
859
|
-
const itemPath = [...currentPath, index.toString()];
|
|
860
|
-
return createAdvancedProxy(item, itemPath, 0);
|
|
861
|
-
}
|
|
862
|
-
return item;
|
|
863
|
-
});
|
|
864
|
-
originalPush.apply(this, processedItems);
|
|
815
|
+
originalPush.apply(this, newArray);
|
|
865
816
|
if (currentPath.length > 0) {
|
|
866
817
|
if (proxyConfig.trackPathUsage) {
|
|
867
818
|
pathUsageStats.modifiedPaths.add(currentPath.join('.'));
|
|
@@ -893,15 +844,8 @@ function addArrayMethods(target, path) {
|
|
|
893
844
|
initialValue = [];
|
|
894
845
|
}
|
|
895
846
|
this.length = 0;
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
const itemPath = [...currentPath, index.toString()];
|
|
899
|
-
return createAdvancedProxy(item, itemPath, 0);
|
|
900
|
-
}
|
|
901
|
-
return item;
|
|
902
|
-
});
|
|
903
|
-
if (processedItems.length > 0) {
|
|
904
|
-
originalPush.apply(this, processedItems);
|
|
847
|
+
if (initialValue.length > 0) {
|
|
848
|
+
originalPush.apply(this, JSON.parse(JSON.stringify(initialValue)));
|
|
905
849
|
}
|
|
906
850
|
if (currentPath.length > 0) {
|
|
907
851
|
notifyListeners(currentPath);
|
|
@@ -1018,6 +962,52 @@ function trackPathAccess(path) {
|
|
|
1018
962
|
}
|
|
1019
963
|
}
|
|
1020
964
|
|
|
965
|
+
function isPlainObject(value) {
|
|
966
|
+
const prototype = Object.getPrototypeOf(value);
|
|
967
|
+
return prototype === Object.prototype || prototype === null;
|
|
968
|
+
}
|
|
969
|
+
function unwrapProxyTarget(value) {
|
|
970
|
+
if (value && typeof value === 'object' && proxyTargetMap.has(value)) {
|
|
971
|
+
return proxyTargetMap.get(value);
|
|
972
|
+
}
|
|
973
|
+
return value;
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Create a read-only snapshot suitable for rendering.
|
|
977
|
+
*
|
|
978
|
+
* Snapshots are plain arrays/objects with no proxy methods, which makes them
|
|
979
|
+
* safe for React Compiler memoization while keeping `$` as the mutable API.
|
|
980
|
+
*/
|
|
981
|
+
function createReadonlySnapshot(value, seen = new WeakMap()) {
|
|
982
|
+
if (value === null || typeof value !== 'object') {
|
|
983
|
+
return value;
|
|
984
|
+
}
|
|
985
|
+
const source = unwrapProxyTarget(value);
|
|
986
|
+
if (seen.has(source)) {
|
|
987
|
+
return seen.get(source);
|
|
988
|
+
}
|
|
989
|
+
if (Array.isArray(source)) {
|
|
990
|
+
const snapshot = [];
|
|
991
|
+
seen.set(source, snapshot);
|
|
992
|
+
source.forEach(item => {
|
|
993
|
+
snapshot.push(createReadonlySnapshot(item, seen));
|
|
994
|
+
});
|
|
995
|
+
return snapshot;
|
|
996
|
+
}
|
|
997
|
+
if (!isPlainObject(source)) {
|
|
998
|
+
return source;
|
|
999
|
+
}
|
|
1000
|
+
const snapshot = {};
|
|
1001
|
+
seen.set(source, snapshot);
|
|
1002
|
+
Object.keys(source).forEach(key => {
|
|
1003
|
+
const propertyValue = source[key];
|
|
1004
|
+
if (typeof propertyValue !== 'function') {
|
|
1005
|
+
snapshot[key] = createReadonlySnapshot(propertyValue, seen);
|
|
1006
|
+
}
|
|
1007
|
+
});
|
|
1008
|
+
return snapshot;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1021
1011
|
/**
|
|
1022
1012
|
* Hook to subscribe to the global store and re-render when specific data changes.
|
|
1023
1013
|
*
|
|
@@ -1025,15 +1015,16 @@ function trackPathAccess(path) {
|
|
|
1025
1015
|
* re-renders the component when those specific paths change. This provides
|
|
1026
1016
|
* fine-grained reactivity without unnecessary renders.
|
|
1027
1017
|
*
|
|
1028
|
-
*
|
|
1029
|
-
*
|
|
1018
|
+
* The returned value is a read-only snapshot of the selected state. Mutate the
|
|
1019
|
+
* store through the main `$` proxy (or a proxy created explicitly for commands),
|
|
1020
|
+
* and use the hook return value only for rendering.
|
|
1030
1021
|
*
|
|
1031
1022
|
* @example
|
|
1032
1023
|
* // Subscribe to user data
|
|
1033
1024
|
* const user = useScope(() => $.user);
|
|
1034
1025
|
*
|
|
1035
|
-
* //
|
|
1036
|
-
* user.$merge({ name: 'New Name' });
|
|
1026
|
+
* // Mutate through the main store proxy
|
|
1027
|
+
* $.user.$merge({ name: 'New Name' });
|
|
1037
1028
|
*
|
|
1038
1029
|
* // Subscribe to a specific property
|
|
1039
1030
|
* const userName = useScope(() => $.user.name);
|
|
@@ -1042,22 +1033,24 @@ function trackPathAccess(path) {
|
|
|
1042
1033
|
* const isAdmin = useScope(() => $.user.role === 'admin');
|
|
1043
1034
|
*
|
|
1044
1035
|
* @param selector - Function that returns the data you want to subscribe to
|
|
1045
|
-
* @returns
|
|
1036
|
+
* @returns A read-only snapshot of the selected data
|
|
1046
1037
|
*/
|
|
1047
1038
|
function useScope(selector) {
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1039
|
+
const snapshotCacheRef = useRef({
|
|
1040
|
+
revision: -1,
|
|
1041
|
+
source: undefined,
|
|
1042
|
+
snapshot: undefined,
|
|
1043
|
+
});
|
|
1044
|
+
// Track dependencies and get the selected value from the store
|
|
1045
|
+
const { value: selectedValue, paths: trackedPaths } = trackDependencies(selector);
|
|
1053
1046
|
// Add tracked paths to selector paths for ultra-selective proxying
|
|
1054
1047
|
trackedPaths.forEach(path => {
|
|
1055
1048
|
selectorPaths.add(path);
|
|
1056
1049
|
pathUsageStats.subscribedPaths.add(path);
|
|
1057
1050
|
});
|
|
1058
|
-
// Use a counter to
|
|
1059
|
-
//
|
|
1060
|
-
const [, forceUpdate] = useState(0);
|
|
1051
|
+
// Use a counter to invalidate the cached snapshot only when this hook
|
|
1052
|
+
// receives a relevant store notification.
|
|
1053
|
+
const [revision, forceUpdate] = useState(0);
|
|
1061
1054
|
// Create stable update handler that forces re-render
|
|
1062
1055
|
const handleChange = useCallback(() => {
|
|
1063
1056
|
try {
|
|
@@ -1068,6 +1061,16 @@ function useScope(selector) {
|
|
|
1068
1061
|
}
|
|
1069
1062
|
}, []);
|
|
1070
1063
|
useEffect(() => {
|
|
1064
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
1065
|
+
requestAnimationFrame(() => {
|
|
1066
|
+
handleChange();
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
else {
|
|
1070
|
+
setTimeout(() => {
|
|
1071
|
+
handleChange();
|
|
1072
|
+
}, 16);
|
|
1073
|
+
}
|
|
1071
1074
|
// Create path keys for subscription using the original approach
|
|
1072
1075
|
// If trackedPaths is ['user', 'name'], create subscriptions for ['user', 'user.name']
|
|
1073
1076
|
const pathKeys = trackedPaths.length > 0
|
|
@@ -1084,8 +1087,18 @@ function useScope(selector) {
|
|
|
1084
1087
|
unsubscribeFunctions.forEach(unsubscribe => unsubscribe());
|
|
1085
1088
|
};
|
|
1086
1089
|
}, [trackedPaths.join(','), handleChange]); // Stable dependencies
|
|
1087
|
-
|
|
1088
|
-
|
|
1090
|
+
if (selectedValue === null || typeof selectedValue !== 'object') {
|
|
1091
|
+
return selectedValue;
|
|
1092
|
+
}
|
|
1093
|
+
if (snapshotCacheRef.current.revision !== revision ||
|
|
1094
|
+
snapshotCacheRef.current.source !== selectedValue) {
|
|
1095
|
+
snapshotCacheRef.current = {
|
|
1096
|
+
revision,
|
|
1097
|
+
source: selectedValue,
|
|
1098
|
+
snapshot: createReadonlySnapshot(selectedValue),
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
return snapshotCacheRef.current.snapshot;
|
|
1089
1102
|
}
|
|
1090
1103
|
|
|
1091
1104
|
/**
|