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 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)`
@@ -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>;
@@ -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,CA4JrE;AA8UD;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAUtC;AAED;;GAEG;AACH,wBAAgB,kBAAkB;;;;;;;EAMjC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,UAAQ,GAAG,GAAG,CA4B3D"}
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;AAUlE,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;AAsTD;;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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Track dependencies during selector execution - tracks individual path segments
2
+ * Track dependencies during selector execution - tracks full dotted paths
3
3
  */
4
4
  export declare function trackDependencies<T>(selector: () => T): {
5
5
  value: T;
@@ -13,16 +13,28 @@ export declare function isCurrentlyTracking(): boolean;
13
13
  * Temporarily skip tracking (for array method internals)
14
14
  */
15
15
  export declare function skipTracking<T>(fn: () => T): T;
16
+ /**
17
+ * Run fn while capturing any newly tracked paths, then remove them from the main set.
18
+ * Caller decides whether to re-add captured paths (e.g. on a matched find iteration).
19
+ */
20
+ export declare function capturePathsDuring<T>(fn: () => T): {
21
+ value: T;
22
+ paths: string[];
23
+ };
24
+ /**
25
+ * Add paths to the active tracking set (used by smart array method overrides)
26
+ */
27
+ export declare function addTrackedPaths(paths: string[]): void;
16
28
  /**
17
29
  * Get current tracking state (for debugging)
18
30
  */
19
31
  export declare function getTrackingState(): {
20
32
  isTracking: boolean;
21
- currentPath: string[];
33
+ trackedPaths: string[];
22
34
  skipDepth: number;
23
35
  };
24
36
  /**
25
- * Add a path segment to tracking during proxy get operations
37
+ * Add a full path to tracking during proxy get operations
26
38
  */
27
39
  export declare function trackPathAccess(path: string[]): void;
28
40
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"tracking.d.ts","sourceRoot":"","sources":["../../src/core/tracking.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAqBrF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAO9C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB,CAMA;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAepD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAQ1D;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAIpC"}
1
+ {"version":3,"file":"tracking.d.ts","sourceRoot":"","sources":["../../src/core/tracking.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAgBrF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAO9C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAahF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAGrD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB,CAMA;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CASpD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAK1D;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAIpC"}
@@ -1,4 +1,4 @@
1
- import type { CustomMethods, CustomArrayMethods } from '../types';
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
- * For objects, the returned value includes custom methods ($merge, $set, $delete, $update)
10
- * that allow you to modify the data directly and trigger reactive updates.
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
- * // Update user data directly (triggers re-render only for components using $.user)
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 The selected data, with custom methods attached if it's an object
27
+ * @returns A read-only snapshot of the selected data
27
28
  */
28
- export declare function useScope<T>(selector: () => T): T extends object ? T & CustomMethods<T> : T extends Array<infer U> ? U[] & CustomArrayMethods<U> : 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":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,QAAQ,EAAE,MAAM,CAAC,GAChB,CAAC,SAAS,MAAM,GACf,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,GACpB,CAAC,SAAS,KAAK,CAAC,MAAM,CAAC,CAAC,GACxB,CAAC,EAAE,GAAG,kBAAkB,CAAC,CAAC,CAAC,GAC3B,CAAC,CAiEJ"}
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,CA6ElB"}
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
@@ -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, receiver) {
604
+ set(obj, prop, value) {
596
605
  if (typeof prop === 'symbol') {
597
- return Reflect.set(obj, prop, value, receiver);
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
- // Handle object assignment with proxying
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
- const newValue = newProps[key];
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
- const newValue = newProps[key];
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
- let newValue = updater(currentValue);
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 = target.push;
792
- const originalSplice = target.splice;
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 processedItems = items.map(item => {
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('.'));
@@ -813,27 +780,40 @@ function addArrayMethods(target, path) {
813
780
  writable: true,
814
781
  configurable: true
815
782
  });
783
+ // Override find with smart tracking during dependency collection
784
+ Object.defineProperty(target, 'find', {
785
+ value: function (predicate, thisArg) {
786
+ if (!isCurrentlyTracking() || !proxyConfig.smartArrayTracking) {
787
+ return Array.prototype.find.call(this, predicate, thisArg);
788
+ }
789
+ const currentPath = proxyPathMap.get(this) || path;
790
+ const length = skipTracking(() => this.length);
791
+ for (let i = 0; i < length; i++) {
792
+ const element = skipTracking(() => Reflect.get(this, i));
793
+ const { value: matched, paths } = capturePathsDuring(() => Boolean(predicate.call(thisArg, element, i, this)));
794
+ if (matched) {
795
+ addTrackedPaths([[...currentPath, String(i)].join('.')]);
796
+ addTrackedPaths(paths);
797
+ return element;
798
+ }
799
+ }
800
+ return undefined;
801
+ },
802
+ writable: true,
803
+ configurable: true
804
+ });
816
805
  // Override splice
817
806
  Object.defineProperty(target, 'splice', {
818
807
  value: function (start, deleteCount, ...items) {
819
808
  const currentPath = proxyPathMap.get(this) || path;
820
809
  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
810
  const actualDeleteCount = deleteCount === undefined ? (arrayLength - start) : deleteCount;
829
- const result = originalSplice.apply(this, [start, actualDeleteCount, ...processedItems]);
811
+ const result = originalSplice.apply(this, [start, actualDeleteCount, ...items]);
830
812
  if (currentPath.length > 0) {
831
813
  if (proxyConfig.trackPathUsage) {
832
814
  pathUsageStats.modifiedPaths.add(currentPath.join('.'));
833
815
  }
834
- // Notify the array itself
835
816
  notifyListeners(currentPath);
836
- // Also notify about each index that was affected
837
817
  for (let i = start; i < arrayLength; i++) {
838
818
  const indexPath = [...currentPath, i.toString()];
839
819
  notifyListeners(indexPath);
@@ -854,14 +834,7 @@ function addArrayMethods(target, path) {
854
834
  }
855
835
  const currentPath = proxyPathMap.get(this) || path;
856
836
  this.length = 0;
857
- const processedItems = newArray.map((item, index) => {
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);
837
+ originalPush.apply(this, newArray);
865
838
  if (currentPath.length > 0) {
866
839
  if (proxyConfig.trackPathUsage) {
867
840
  pathUsageStats.modifiedPaths.add(currentPath.join('.'));
@@ -893,15 +866,8 @@ function addArrayMethods(target, path) {
893
866
  initialValue = [];
894
867
  }
895
868
  this.length = 0;
896
- const processedItems = initialValue.map((item, index) => {
897
- if (item && typeof item === 'object' && !proxyCache.has(item)) {
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);
869
+ if (initialValue.length > 0) {
870
+ originalPush.apply(this, JSON.parse(JSON.stringify(initialValue)));
905
871
  }
906
872
  if (currentPath.length > 0) {
907
873
  notifyListeners(currentPath);
@@ -976,48 +942,125 @@ function optimizeMemoryUsage(aggressive = false) {
976
942
  };
977
943
  }
978
944
 
979
- // Track the current path we're accessing during a selector function
980
- let currentPath = [];
945
+ // Unique full dotted paths accessed during selector execution
946
+ let trackedPaths = new Set();
981
947
  let isTracking = false;
982
948
  let skipTrackingDepth = 0;
983
949
  /**
984
- * Track dependencies during selector execution - tracks individual path segments
950
+ * Track dependencies during selector execution - tracks full dotted paths
985
951
  */
986
952
  function trackDependencies(selector) {
987
- // Start tracking
988
953
  isTracking = true;
989
- currentPath = [];
954
+ trackedPaths = new Set();
990
955
  skipTrackingDepth = 0;
991
- // Execute selector to track dependencies
992
956
  const value = selector();
993
- // Stop tracking and get the tracked paths
994
957
  isTracking = false;
995
- // Clean up and return individual path segments (not full paths)
996
- const cleanedPaths = [...currentPath].filter(segment => {
997
- // Filter out segments that would create overly long paths
998
- return segment && segment.length < 100; // Basic length check for individual segments
958
+ const paths = Array.from(trackedPaths).filter(path => path && path.length < 500);
959
+ trackedPaths = new Set();
960
+ return { value, paths };
961
+ }
962
+ /**
963
+ * Check if we're currently tracking dependencies
964
+ */
965
+ function isCurrentlyTracking() {
966
+ return isTracking && skipTrackingDepth === 0;
967
+ }
968
+ /**
969
+ * Temporarily skip tracking (for array method internals)
970
+ */
971
+ function skipTracking(fn) {
972
+ skipTrackingDepth++;
973
+ try {
974
+ return fn();
975
+ }
976
+ finally {
977
+ skipTrackingDepth--;
978
+ }
979
+ }
980
+ /**
981
+ * Run fn while capturing any newly tracked paths, then remove them from the main set.
982
+ * Caller decides whether to re-add captured paths (e.g. on a matched find iteration).
983
+ */
984
+ function capturePathsDuring(fn) {
985
+ const snapshot = new Set(trackedPaths);
986
+ const value = fn();
987
+ const newPaths = [];
988
+ trackedPaths.forEach(path => {
989
+ if (!snapshot.has(path)) {
990
+ newPaths.push(path);
991
+ trackedPaths.delete(path);
992
+ }
999
993
  });
1000
- currentPath = [];
1001
- return { value, paths: cleanedPaths };
994
+ return { value, paths: newPaths };
995
+ }
996
+ /**
997
+ * Add paths to the active tracking set (used by smart array method overrides)
998
+ */
999
+ function addTrackedPaths(paths) {
1000
+ if (!isTracking)
1001
+ return;
1002
+ paths.forEach(path => trackedPaths.add(path));
1002
1003
  }
1003
1004
  /**
1004
- * Add a path segment to tracking during proxy get operations
1005
+ * Add a full path to tracking during proxy get operations
1005
1006
  */
1006
1007
  function trackPathAccess(path) {
1007
1008
  if (!isTracking || skipTrackingDepth > 0)
1008
1009
  return;
1009
- // Track only the last property name (individual segment)
1010
- const prop = path[path.length - 1];
1011
- // Only track if prop exists and path isn't too deep
1012
- if (prop && path.length <= proxyConfig.maxPathLength) {
1013
- currentPath.push(prop);
1014
- // Add full path to usage stats and selector paths for ultra-selective proxying
1010
+ if (path.length <= proxyConfig.maxPathLength) {
1015
1011
  const fullPath = path.join('.');
1012
+ trackedPaths.add(fullPath);
1016
1013
  pathUsageStats.accessedPaths.add(fullPath);
1017
1014
  selectorPaths.add(fullPath);
1018
1015
  }
1019
1016
  }
1020
1017
 
1018
+ function isPlainObject(value) {
1019
+ const prototype = Object.getPrototypeOf(value);
1020
+ return prototype === Object.prototype || prototype === null;
1021
+ }
1022
+ function unwrapProxyTarget(value) {
1023
+ if (value && typeof value === 'object' && proxyTargetMap.has(value)) {
1024
+ return proxyTargetMap.get(value);
1025
+ }
1026
+ return value;
1027
+ }
1028
+ /**
1029
+ * Create a read-only snapshot suitable for rendering.
1030
+ *
1031
+ * Snapshots are plain arrays/objects with no proxy methods, which makes them
1032
+ * safe for React Compiler memoization while keeping `$` as the mutable API.
1033
+ */
1034
+ function createReadonlySnapshot(value, seen = new WeakMap()) {
1035
+ if (value === null || typeof value !== 'object') {
1036
+ return value;
1037
+ }
1038
+ const source = unwrapProxyTarget(value);
1039
+ if (seen.has(source)) {
1040
+ return seen.get(source);
1041
+ }
1042
+ if (Array.isArray(source)) {
1043
+ const snapshot = [];
1044
+ seen.set(source, snapshot);
1045
+ source.forEach(item => {
1046
+ snapshot.push(createReadonlySnapshot(item, seen));
1047
+ });
1048
+ return snapshot;
1049
+ }
1050
+ if (!isPlainObject(source)) {
1051
+ return source;
1052
+ }
1053
+ const snapshot = {};
1054
+ seen.set(source, snapshot);
1055
+ Object.keys(source).forEach(key => {
1056
+ const propertyValue = source[key];
1057
+ if (typeof propertyValue !== 'function') {
1058
+ snapshot[key] = createReadonlySnapshot(propertyValue, seen);
1059
+ }
1060
+ });
1061
+ return snapshot;
1062
+ }
1063
+
1021
1064
  /**
1022
1065
  * Hook to subscribe to the global store and re-render when specific data changes.
1023
1066
  *
@@ -1025,15 +1068,16 @@ function trackPathAccess(path) {
1025
1068
  * re-renders the component when those specific paths change. This provides
1026
1069
  * fine-grained reactivity without unnecessary renders.
1027
1070
  *
1028
- * For objects, the returned value includes custom methods ($merge, $set, $delete, $update)
1029
- * that allow you to modify the data directly and trigger reactive updates.
1071
+ * The returned value is a read-only snapshot of the selected state. Mutate the
1072
+ * store through the main `$` proxy (or a proxy created explicitly for commands),
1073
+ * and use the hook return value only for rendering.
1030
1074
  *
1031
1075
  * @example
1032
1076
  * // Subscribe to user data
1033
1077
  * const user = useScope(() => $.user);
1034
1078
  *
1035
- * // Update user data directly (triggers re-render only for components using $.user)
1036
- * user.$merge({ name: 'New Name' });
1079
+ * // Mutate through the main store proxy
1080
+ * $.user.$merge({ name: 'New Name' });
1037
1081
  *
1038
1082
  * // Subscribe to a specific property
1039
1083
  * const userName = useScope(() => $.user.name);
@@ -1042,22 +1086,24 @@ function trackPathAccess(path) {
1042
1086
  * const isAdmin = useScope(() => $.user.role === 'admin');
1043
1087
  *
1044
1088
  * @param selector - Function that returns the data you want to subscribe to
1045
- * @returns The selected data, with custom methods attached if it's an object
1089
+ * @returns A read-only snapshot of the selected data
1046
1090
  */
1047
1091
  function useScope(selector) {
1048
- // Use ref to store the latest selector to avoid stale closures
1049
- const selectorRef = useRef(selector);
1050
- selectorRef.current = selector;
1051
- // Track dependencies and get initial value with advanced tracking
1052
- const { value: initialValue, paths: trackedPaths } = trackDependencies(selector);
1092
+ const snapshotCacheRef = useRef({
1093
+ revision: -1,
1094
+ source: undefined,
1095
+ snapshot: undefined,
1096
+ });
1097
+ // Track dependencies and get the selected value from the store
1098
+ const { value: selectedValue, paths: trackedPaths } = trackDependencies(selector);
1053
1099
  // Add tracked paths to selector paths for ultra-selective proxying
1054
1100
  trackedPaths.forEach(path => {
1055
1101
  selectorPaths.add(path);
1056
1102
  pathUsageStats.subscribedPaths.add(path);
1057
1103
  });
1058
- // Use a counter to force re-renders instead of storing the value
1059
- // This way we always return the fresh proxy object from the selector
1060
- const [, forceUpdate] = useState(0);
1104
+ // Use a counter to invalidate the cached snapshot only when this hook
1105
+ // receives a relevant store notification.
1106
+ const [revision, forceUpdate] = useState(0);
1061
1107
  // Create stable update handler that forces re-render
1062
1108
  const handleChange = useCallback(() => {
1063
1109
  try {
@@ -1078,11 +1124,7 @@ function useScope(selector) {
1078
1124
  handleChange();
1079
1125
  }, 16);
1080
1126
  }
1081
- // Create path keys for subscription using the original approach
1082
- // If trackedPaths is ['user', 'name'], create subscriptions for ['user', 'user.name']
1083
- const pathKeys = trackedPaths.length > 0
1084
- ? trackedPaths.map((_, index, array) => array.slice(0, index + 1).join('.'))
1085
- : [''];
1127
+ const pathKeys = trackedPaths.length > 0 ? trackedPaths : [''];
1086
1128
  // Subscribe to all relevant paths
1087
1129
  const unsubscribeFunctions = pathKeys.map(pathKey => {
1088
1130
  // Mark this path as subscribed for usage tracking
@@ -1094,8 +1136,18 @@ function useScope(selector) {
1094
1136
  unsubscribeFunctions.forEach(unsubscribe => unsubscribe());
1095
1137
  };
1096
1138
  }, [trackedPaths.join(','), handleChange]); // Stable dependencies
1097
- // Always return the fresh result from the selector (preserves proxy and methods)
1098
- return selectorRef.current();
1139
+ if (selectedValue === null || typeof selectedValue !== 'object') {
1140
+ return selectedValue;
1141
+ }
1142
+ if (snapshotCacheRef.current.revision !== revision ||
1143
+ snapshotCacheRef.current.source !== selectedValue) {
1144
+ snapshotCacheRef.current = {
1145
+ revision,
1146
+ source: selectedValue,
1147
+ snapshot: createReadonlySnapshot(selectedValue),
1148
+ };
1149
+ }
1150
+ return snapshotCacheRef.current.snapshot;
1099
1151
  }
1100
1152
 
1101
1153
  /**