@codebelt/classy-store 0.0.1 → 0.0.2

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.
@@ -0,0 +1,61 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_snapshot = require('../snapshot-CR8nA2Ob.cjs');
3
+ let proxy_compare = require("proxy-compare");
4
+ let react = require("react");
5
+
6
+ //#region src/react/react.ts
7
+ function useStore(proxyStore, selector, isEqual) {
8
+ require_snapshot.getInternal(proxyStore);
9
+ const subscribe$1 = (0, react.useCallback)((onStoreChange) => require_snapshot.subscribe(proxyStore, onStoreChange), [proxyStore]);
10
+ const snapRef = (0, react.useRef)(void 0);
11
+ const resultRef = (0, react.useRef)(void 0);
12
+ const affected = (0, react.useRef)(/* @__PURE__ */ new WeakMap()).current;
13
+ const proxyCache = (0, react.useRef)(/* @__PURE__ */ new WeakMap()).current;
14
+ const prevSnapRef = (0, react.useRef)(void 0);
15
+ const wrappedRef = (0, react.useRef)(void 0);
16
+ const getSnapshot = () => selector ? getSelectorSnapshot(proxyStore, snapRef, resultRef, selector, isEqual) : getAutoTrackSnapshot(proxyStore, affected, proxyCache, prevSnapRef, wrappedRef);
17
+ return (0, react.useSyncExternalStore)(subscribe$1, getSnapshot, getSnapshot);
18
+ }
19
+ /**
20
+ * `getSnapshot` implementation for selector mode.
21
+ *
22
+ * Fast-paths when the snapshot reference hasn't changed (O(1)). Otherwise
23
+ * runs the selector against the new snapshot and compares the result to the
24
+ * previous one via `Object.is` (or a custom `isEqual`). Returns the previous
25
+ * result reference when equal, preventing unnecessary React re-renders.
26
+ *
27
+ * Pure function -- no hooks, safe to call from `useSyncExternalStore`.
28
+ */
29
+ function getSelectorSnapshot(proxyStore, snapRef, resultRef, selector, isEqual) {
30
+ const nextSnap = require_snapshot.snapshot(proxyStore);
31
+ if (snapRef.current === nextSnap && resultRef.current !== void 0) return resultRef.current;
32
+ const nextResult = selector(nextSnap);
33
+ snapRef.current = nextSnap;
34
+ if (resultRef.current !== void 0 && (isEqual ? isEqual(resultRef.current, nextResult) : Object.is(resultRef.current, nextResult))) return resultRef.current;
35
+ resultRef.current = nextResult;
36
+ return nextResult;
37
+ }
38
+ /**
39
+ * `getSnapshot` implementation for auto-tracked (selectorless) mode.
40
+ *
41
+ * Uses `proxy-compare` to diff only the properties the component actually read.
42
+ * If the snapshot reference is the same, returns the cached tracking proxy.
43
+ * If the snapshot changed but no tracked property differs (`isChanged` returns
44
+ * false), also returns the cached proxy -- avoiding re-render. Only when a
45
+ * relevant property changed does it create a new `createProxy` wrapper.
46
+ *
47
+ * Pure function -- no hooks, safe to call from `useSyncExternalStore`.
48
+ */
49
+ function getAutoTrackSnapshot(proxyStore, affected, proxyCache, prevSnapRef, wrappedRef) {
50
+ const nextSnap = require_snapshot.snapshot(proxyStore);
51
+ if (prevSnapRef.current === nextSnap) return wrappedRef.current;
52
+ if (prevSnapRef.current !== void 0 && !(0, proxy_compare.isChanged)(prevSnapRef.current, nextSnap, affected)) return wrappedRef.current;
53
+ prevSnapRef.current = nextSnap;
54
+ const wrapped = (0, proxy_compare.createProxy)(nextSnap, affected, proxyCache);
55
+ wrappedRef.current = wrapped;
56
+ return wrapped;
57
+ }
58
+
59
+ //#endregion
60
+ exports.useStore = useStore;
61
+ //# sourceMappingURL=react.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.cjs","names":["subscribe","coreSubscribe","snapshot"],"sources":["../../src/react/react.ts"],"sourcesContent":["import {createProxy, isChanged} from 'proxy-compare';\nimport {useCallback, useRef, useSyncExternalStore} from 'react';\nimport {subscribe as coreSubscribe, getInternal} from '../core/core';\nimport {snapshot} from '../snapshot/snapshot';\nimport type {Snapshot} from '../types';\n\n// ── Overloads ─────────────────────────────────────────────────────────────────\n\n/**\n * Subscribe to a store proxy with an explicit selector.\n *\n * Re-renders only when the selected value changes (compared via `Object.is`\n * by default, or a custom `isEqual`).\n *\n * @param proxyStore - A reactive proxy created by `store()`.\n * @param selector - Picks data from the immutable snapshot.\n * @param isEqual - Optional custom equality function (default: `Object.is`).\n */\nexport function useStore<T extends object, S>(\n proxyStore: T,\n selector: (snap: Snapshot<T>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): S;\n\n/**\n * Subscribe to a store proxy **without** a selector (auto-tracked mode).\n *\n * Returns a `proxy-compare` tracking proxy over the immutable snapshot.\n * The component only re-renders when a property it actually read changes.\n *\n * @param proxyStore - A reactive proxy created by `store()`.\n */\nexport function useStore<T extends object>(proxyStore: T): Snapshot<T>;\n\n// ── Implementation ────────────────────────────────────────────────────────────\n\nexport function useStore<T extends object, S>(\n proxyStore: T,\n selector?: (snap: Snapshot<T>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Snapshot<T> | S {\n // Validate that the argument is actually a store proxy (throws if not).\n getInternal(proxyStore);\n\n // Stable subscribe function (internal identity never changes for a given store).\n const subscribe = useCallback(\n (onStoreChange: () => void) => coreSubscribe(proxyStore, onStoreChange),\n [proxyStore],\n );\n\n // ── Refs used by both modes (always allocated to satisfy Rules of Hooks) ──\n\n // Selector mode refs\n const snapRef = useRef<Snapshot<T> | undefined>(undefined);\n const resultRef = useRef<S | undefined>(undefined);\n\n // Auto-track mode refs\n const affected = useRef(new WeakMap<object, unknown>()).current;\n const proxyCache = useRef(new WeakMap<object, unknown>()).current;\n const prevSnapRef = useRef<Snapshot<T> | undefined>(undefined);\n const wrappedRef = useRef<Snapshot<T> | undefined>(undefined);\n\n // ── Single getSnapshot for useSyncExternalStore ───────────────────────────\n\n const getSnapshot = (): Snapshot<T> | S =>\n selector\n ? getSelectorSnapshot(proxyStore, snapRef, resultRef, selector, isEqual)\n : getAutoTrackSnapshot(\n proxyStore,\n affected,\n proxyCache,\n prevSnapRef,\n wrappedRef,\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n// ── Selector mode logic (pure function, no hooks) ─────────────────────────────\n\n/**\n * `getSnapshot` implementation for selector mode.\n *\n * Fast-paths when the snapshot reference hasn't changed (O(1)). Otherwise\n * runs the selector against the new snapshot and compares the result to the\n * previous one via `Object.is` (or a custom `isEqual`). Returns the previous\n * result reference when equal, preventing unnecessary React re-renders.\n *\n * Pure function -- no hooks, safe to call from `useSyncExternalStore`.\n */\nfunction getSelectorSnapshot<T extends object, S>(\n proxyStore: T,\n snapRef: React.RefObject<Snapshot<T> | undefined>,\n resultRef: React.RefObject<S | undefined>,\n selector: (snap: Snapshot<T>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): S {\n const nextSnap = snapshot(proxyStore);\n\n // Fast path: same snapshot reference → same result.\n if (snapRef.current === nextSnap && resultRef.current !== undefined) {\n return resultRef.current;\n }\n\n const nextResult = selector(nextSnap);\n snapRef.current = nextSnap;\n\n // Check equality with previous result.\n if (\n resultRef.current !== undefined &&\n (isEqual\n ? isEqual(resultRef.current, nextResult)\n : Object.is(resultRef.current, nextResult))\n ) {\n return resultRef.current;\n }\n\n resultRef.current = nextResult;\n return nextResult;\n}\n\n// ── Auto-tracked (selectorless) mode logic (pure function, no hooks) ──────────\n\n/**\n * `getSnapshot` implementation for auto-tracked (selectorless) mode.\n *\n * Uses `proxy-compare` to diff only the properties the component actually read.\n * If the snapshot reference is the same, returns the cached tracking proxy.\n * If the snapshot changed but no tracked property differs (`isChanged` returns\n * false), also returns the cached proxy -- avoiding re-render. Only when a\n * relevant property changed does it create a new `createProxy` wrapper.\n *\n * Pure function -- no hooks, safe to call from `useSyncExternalStore`.\n */\nfunction getAutoTrackSnapshot<T extends object>(\n proxyStore: T,\n affected: WeakMap<object, unknown>,\n proxyCache: WeakMap<object, unknown>,\n prevSnapRef: React.RefObject<Snapshot<T> | undefined>,\n wrappedRef: React.RefObject<Snapshot<T> | undefined>,\n): Snapshot<T> {\n const nextSnap = snapshot(proxyStore);\n\n // If the raw snapshot is the same reference, nothing changed.\n if (prevSnapRef.current === nextSnap) {\n return wrappedRef.current as Snapshot<T>;\n }\n\n // Check if any property the component actually read has changed.\n if (\n prevSnapRef.current !== undefined &&\n !isChanged(prevSnapRef.current, nextSnap, affected)\n ) {\n // No property the component cares about changed → return same wrapped proxy.\n return wrappedRef.current as Snapshot<T>;\n }\n\n // Something relevant changed — create a new tracking proxy.\n prevSnapRef.current = nextSnap;\n const wrapped = createProxy(nextSnap, affected, proxyCache) as Snapshot<T>;\n wrappedRef.current = wrapped;\n return wrapped;\n}\n"],"mappings":";;;;;;AAoCA,SAAgB,SACd,YACA,UACA,SACiB;AAEjB,8BAAY,WAAW;CAGvB,MAAMA,sCACH,kBAA8BC,2BAAc,YAAY,cAAc,EACvE,CAAC,WAAW,CACb;CAKD,MAAM,4BAA0C,OAAU;CAC1D,MAAM,8BAAkC,OAAU;CAGlD,MAAM,6CAAkB,IAAI,SAA0B,CAAC,CAAC;CACxD,MAAM,+CAAoB,IAAI,SAA0B,CAAC,CAAC;CAC1D,MAAM,gCAA8C,OAAU;CAC9D,MAAM,+BAA6C,OAAU;CAI7D,MAAM,oBACJ,WACI,oBAAoB,YAAY,SAAS,WAAW,UAAU,QAAQ,GACtE,qBACE,YACA,UACA,YACA,aACA,WACD;AAEP,wCAA4BD,aAAW,aAAa,YAAY;;;;;;;;;;;;AAelE,SAAS,oBACP,YACA,SACA,WACA,UACA,SACG;CACH,MAAM,WAAWE,0BAAS,WAAW;AAGrC,KAAI,QAAQ,YAAY,YAAY,UAAU,YAAY,OACxD,QAAO,UAAU;CAGnB,MAAM,aAAa,SAAS,SAAS;AACrC,SAAQ,UAAU;AAGlB,KACE,UAAU,YAAY,WACrB,UACG,QAAQ,UAAU,SAAS,WAAW,GACtC,OAAO,GAAG,UAAU,SAAS,WAAW,EAE5C,QAAO,UAAU;AAGnB,WAAU,UAAU;AACpB,QAAO;;;;;;;;;;;;;AAgBT,SAAS,qBACP,YACA,UACA,YACA,aACA,YACa;CACb,MAAM,WAAWA,0BAAS,WAAW;AAGrC,KAAI,YAAY,YAAY,SAC1B,QAAO,WAAW;AAIpB,KACE,YAAY,YAAY,UACxB,8BAAW,YAAY,SAAS,UAAU,SAAS,CAGnD,QAAO,WAAW;AAIpB,aAAY,UAAU;CACtB,MAAM,yCAAsB,UAAU,UAAU,WAAW;AAC3D,YAAW,UAAU;AACrB,QAAO"}
@@ -0,0 +1,26 @@
1
+ import { t as Snapshot } from "../types-B6RZUB86.cjs";
2
+
3
+ //#region src/react/react.d.ts
4
+ /**
5
+ * Subscribe to a store proxy with an explicit selector.
6
+ *
7
+ * Re-renders only when the selected value changes (compared via `Object.is`
8
+ * by default, or a custom `isEqual`).
9
+ *
10
+ * @param proxyStore - A reactive proxy created by `store()`.
11
+ * @param selector - Picks data from the immutable snapshot.
12
+ * @param isEqual - Optional custom equality function (default: `Object.is`).
13
+ */
14
+ declare function useStore<T extends object, S>(proxyStore: T, selector: (snap: Snapshot<T>) => S, isEqual?: (a: S, b: S) => boolean): S;
15
+ /**
16
+ * Subscribe to a store proxy **without** a selector (auto-tracked mode).
17
+ *
18
+ * Returns a `proxy-compare` tracking proxy over the immutable snapshot.
19
+ * The component only re-renders when a property it actually read changes.
20
+ *
21
+ * @param proxyStore - A reactive proxy created by `store()`.
22
+ */
23
+ declare function useStore<T extends object>(proxyStore: T): Snapshot<T>;
24
+ //#endregion
25
+ export { useStore };
26
+ //# sourceMappingURL=react.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.cts","names":[],"sources":["../../src/react/react.ts"],"mappings":";;;;;AAkBA;;;;;;;;iBAAgB,QAAA,qBAAA,CACd,UAAA,EAAY,CAAA,EACZ,QAAA,GAAW,IAAA,EAAM,QAAA,CAAS,CAAA,MAAO,CAAA,EACjC,OAAA,IAAW,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,eACnB,CAAA;;;;;;;;;iBAUa,QAAA,kBAAA,CAA2B,UAAA,EAAY,CAAA,GAAI,QAAA,CAAS,CAAA"}
@@ -0,0 +1,26 @@
1
+ import { t as Snapshot } from "../types-vWYkF3tH.mjs";
2
+
3
+ //#region src/react/react.d.ts
4
+ /**
5
+ * Subscribe to a store proxy with an explicit selector.
6
+ *
7
+ * Re-renders only when the selected value changes (compared via `Object.is`
8
+ * by default, or a custom `isEqual`).
9
+ *
10
+ * @param proxyStore - A reactive proxy created by `store()`.
11
+ * @param selector - Picks data from the immutable snapshot.
12
+ * @param isEqual - Optional custom equality function (default: `Object.is`).
13
+ */
14
+ declare function useStore<T extends object, S>(proxyStore: T, selector: (snap: Snapshot<T>) => S, isEqual?: (a: S, b: S) => boolean): S;
15
+ /**
16
+ * Subscribe to a store proxy **without** a selector (auto-tracked mode).
17
+ *
18
+ * Returns a `proxy-compare` tracking proxy over the immutable snapshot.
19
+ * The component only re-renders when a property it actually read changes.
20
+ *
21
+ * @param proxyStore - A reactive proxy created by `store()`.
22
+ */
23
+ declare function useStore<T extends object>(proxyStore: T): Snapshot<T>;
24
+ //#endregion
25
+ export { useStore };
26
+ //# sourceMappingURL=react.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.mts","names":[],"sources":["../../src/react/react.ts"],"mappings":";;;;;AAkBA;;;;;;;;iBAAgB,QAAA,qBAAA,CACd,UAAA,EAAY,CAAA,EACZ,QAAA,GAAW,IAAA,EAAM,QAAA,CAAS,CAAA,MAAO,CAAA,EACjC,OAAA,IAAW,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,eACnB,CAAA;;;;;;;;;iBAUa,QAAA,kBAAA,CAA2B,UAAA,EAAY,CAAA,GAAI,QAAA,CAAS,CAAA"}
@@ -0,0 +1,60 @@
1
+ import { a as subscribe, n as getInternal, t as snapshot } from "../snapshot-C8JDLu8L.mjs";
2
+ import { createProxy, isChanged } from "proxy-compare";
3
+ import { useCallback, useRef, useSyncExternalStore } from "react";
4
+
5
+ //#region src/react/react.ts
6
+ function useStore(proxyStore, selector, isEqual) {
7
+ getInternal(proxyStore);
8
+ const subscribe$1 = useCallback((onStoreChange) => subscribe(proxyStore, onStoreChange), [proxyStore]);
9
+ const snapRef = useRef(void 0);
10
+ const resultRef = useRef(void 0);
11
+ const affected = useRef(/* @__PURE__ */ new WeakMap()).current;
12
+ const proxyCache = useRef(/* @__PURE__ */ new WeakMap()).current;
13
+ const prevSnapRef = useRef(void 0);
14
+ const wrappedRef = useRef(void 0);
15
+ const getSnapshot = () => selector ? getSelectorSnapshot(proxyStore, snapRef, resultRef, selector, isEqual) : getAutoTrackSnapshot(proxyStore, affected, proxyCache, prevSnapRef, wrappedRef);
16
+ return useSyncExternalStore(subscribe$1, getSnapshot, getSnapshot);
17
+ }
18
+ /**
19
+ * `getSnapshot` implementation for selector mode.
20
+ *
21
+ * Fast-paths when the snapshot reference hasn't changed (O(1)). Otherwise
22
+ * runs the selector against the new snapshot and compares the result to the
23
+ * previous one via `Object.is` (or a custom `isEqual`). Returns the previous
24
+ * result reference when equal, preventing unnecessary React re-renders.
25
+ *
26
+ * Pure function -- no hooks, safe to call from `useSyncExternalStore`.
27
+ */
28
+ function getSelectorSnapshot(proxyStore, snapRef, resultRef, selector, isEqual) {
29
+ const nextSnap = snapshot(proxyStore);
30
+ if (snapRef.current === nextSnap && resultRef.current !== void 0) return resultRef.current;
31
+ const nextResult = selector(nextSnap);
32
+ snapRef.current = nextSnap;
33
+ if (resultRef.current !== void 0 && (isEqual ? isEqual(resultRef.current, nextResult) : Object.is(resultRef.current, nextResult))) return resultRef.current;
34
+ resultRef.current = nextResult;
35
+ return nextResult;
36
+ }
37
+ /**
38
+ * `getSnapshot` implementation for auto-tracked (selectorless) mode.
39
+ *
40
+ * Uses `proxy-compare` to diff only the properties the component actually read.
41
+ * If the snapshot reference is the same, returns the cached tracking proxy.
42
+ * If the snapshot changed but no tracked property differs (`isChanged` returns
43
+ * false), also returns the cached proxy -- avoiding re-render. Only when a
44
+ * relevant property changed does it create a new `createProxy` wrapper.
45
+ *
46
+ * Pure function -- no hooks, safe to call from `useSyncExternalStore`.
47
+ */
48
+ function getAutoTrackSnapshot(proxyStore, affected, proxyCache, prevSnapRef, wrappedRef) {
49
+ const nextSnap = snapshot(proxyStore);
50
+ if (prevSnapRef.current === nextSnap) return wrappedRef.current;
51
+ if (prevSnapRef.current !== void 0 && !isChanged(prevSnapRef.current, nextSnap, affected)) return wrappedRef.current;
52
+ prevSnapRef.current = nextSnap;
53
+ const wrapped = createProxy(nextSnap, affected, proxyCache);
54
+ wrappedRef.current = wrapped;
55
+ return wrapped;
56
+ }
57
+
58
+ //#endregion
59
+ export { useStore };
60
+ //# sourceMappingURL=react.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.mjs","names":["subscribe","coreSubscribe"],"sources":["../../src/react/react.ts"],"sourcesContent":["import {createProxy, isChanged} from 'proxy-compare';\nimport {useCallback, useRef, useSyncExternalStore} from 'react';\nimport {subscribe as coreSubscribe, getInternal} from '../core/core';\nimport {snapshot} from '../snapshot/snapshot';\nimport type {Snapshot} from '../types';\n\n// ── Overloads ─────────────────────────────────────────────────────────────────\n\n/**\n * Subscribe to a store proxy with an explicit selector.\n *\n * Re-renders only when the selected value changes (compared via `Object.is`\n * by default, or a custom `isEqual`).\n *\n * @param proxyStore - A reactive proxy created by `store()`.\n * @param selector - Picks data from the immutable snapshot.\n * @param isEqual - Optional custom equality function (default: `Object.is`).\n */\nexport function useStore<T extends object, S>(\n proxyStore: T,\n selector: (snap: Snapshot<T>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): S;\n\n/**\n * Subscribe to a store proxy **without** a selector (auto-tracked mode).\n *\n * Returns a `proxy-compare` tracking proxy over the immutable snapshot.\n * The component only re-renders when a property it actually read changes.\n *\n * @param proxyStore - A reactive proxy created by `store()`.\n */\nexport function useStore<T extends object>(proxyStore: T): Snapshot<T>;\n\n// ── Implementation ────────────────────────────────────────────────────────────\n\nexport function useStore<T extends object, S>(\n proxyStore: T,\n selector?: (snap: Snapshot<T>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Snapshot<T> | S {\n // Validate that the argument is actually a store proxy (throws if not).\n getInternal(proxyStore);\n\n // Stable subscribe function (internal identity never changes for a given store).\n const subscribe = useCallback(\n (onStoreChange: () => void) => coreSubscribe(proxyStore, onStoreChange),\n [proxyStore],\n );\n\n // ── Refs used by both modes (always allocated to satisfy Rules of Hooks) ──\n\n // Selector mode refs\n const snapRef = useRef<Snapshot<T> | undefined>(undefined);\n const resultRef = useRef<S | undefined>(undefined);\n\n // Auto-track mode refs\n const affected = useRef(new WeakMap<object, unknown>()).current;\n const proxyCache = useRef(new WeakMap<object, unknown>()).current;\n const prevSnapRef = useRef<Snapshot<T> | undefined>(undefined);\n const wrappedRef = useRef<Snapshot<T> | undefined>(undefined);\n\n // ── Single getSnapshot for useSyncExternalStore ───────────────────────────\n\n const getSnapshot = (): Snapshot<T> | S =>\n selector\n ? getSelectorSnapshot(proxyStore, snapRef, resultRef, selector, isEqual)\n : getAutoTrackSnapshot(\n proxyStore,\n affected,\n proxyCache,\n prevSnapRef,\n wrappedRef,\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n// ── Selector mode logic (pure function, no hooks) ─────────────────────────────\n\n/**\n * `getSnapshot` implementation for selector mode.\n *\n * Fast-paths when the snapshot reference hasn't changed (O(1)). Otherwise\n * runs the selector against the new snapshot and compares the result to the\n * previous one via `Object.is` (or a custom `isEqual`). Returns the previous\n * result reference when equal, preventing unnecessary React re-renders.\n *\n * Pure function -- no hooks, safe to call from `useSyncExternalStore`.\n */\nfunction getSelectorSnapshot<T extends object, S>(\n proxyStore: T,\n snapRef: React.RefObject<Snapshot<T> | undefined>,\n resultRef: React.RefObject<S | undefined>,\n selector: (snap: Snapshot<T>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): S {\n const nextSnap = snapshot(proxyStore);\n\n // Fast path: same snapshot reference → same result.\n if (snapRef.current === nextSnap && resultRef.current !== undefined) {\n return resultRef.current;\n }\n\n const nextResult = selector(nextSnap);\n snapRef.current = nextSnap;\n\n // Check equality with previous result.\n if (\n resultRef.current !== undefined &&\n (isEqual\n ? isEqual(resultRef.current, nextResult)\n : Object.is(resultRef.current, nextResult))\n ) {\n return resultRef.current;\n }\n\n resultRef.current = nextResult;\n return nextResult;\n}\n\n// ── Auto-tracked (selectorless) mode logic (pure function, no hooks) ──────────\n\n/**\n * `getSnapshot` implementation for auto-tracked (selectorless) mode.\n *\n * Uses `proxy-compare` to diff only the properties the component actually read.\n * If the snapshot reference is the same, returns the cached tracking proxy.\n * If the snapshot changed but no tracked property differs (`isChanged` returns\n * false), also returns the cached proxy -- avoiding re-render. Only when a\n * relevant property changed does it create a new `createProxy` wrapper.\n *\n * Pure function -- no hooks, safe to call from `useSyncExternalStore`.\n */\nfunction getAutoTrackSnapshot<T extends object>(\n proxyStore: T,\n affected: WeakMap<object, unknown>,\n proxyCache: WeakMap<object, unknown>,\n prevSnapRef: React.RefObject<Snapshot<T> | undefined>,\n wrappedRef: React.RefObject<Snapshot<T> | undefined>,\n): Snapshot<T> {\n const nextSnap = snapshot(proxyStore);\n\n // If the raw snapshot is the same reference, nothing changed.\n if (prevSnapRef.current === nextSnap) {\n return wrappedRef.current as Snapshot<T>;\n }\n\n // Check if any property the component actually read has changed.\n if (\n prevSnapRef.current !== undefined &&\n !isChanged(prevSnapRef.current, nextSnap, affected)\n ) {\n // No property the component cares about changed → return same wrapped proxy.\n return wrappedRef.current as Snapshot<T>;\n }\n\n // Something relevant changed — create a new tracking proxy.\n prevSnapRef.current = nextSnap;\n const wrapped = createProxy(nextSnap, affected, proxyCache) as Snapshot<T>;\n wrappedRef.current = wrapped;\n return wrapped;\n}\n"],"mappings":";;;;;AAoCA,SAAgB,SACd,YACA,UACA,SACiB;AAEjB,aAAY,WAAW;CAGvB,MAAMA,cAAY,aACf,kBAA8BC,UAAc,YAAY,cAAc,EACvE,CAAC,WAAW,CACb;CAKD,MAAM,UAAU,OAAgC,OAAU;CAC1D,MAAM,YAAY,OAAsB,OAAU;CAGlD,MAAM,WAAW,uBAAO,IAAI,SAA0B,CAAC,CAAC;CACxD,MAAM,aAAa,uBAAO,IAAI,SAA0B,CAAC,CAAC;CAC1D,MAAM,cAAc,OAAgC,OAAU;CAC9D,MAAM,aAAa,OAAgC,OAAU;CAI7D,MAAM,oBACJ,WACI,oBAAoB,YAAY,SAAS,WAAW,UAAU,QAAQ,GACtE,qBACE,YACA,UACA,YACA,aACA,WACD;AAEP,QAAO,qBAAqBD,aAAW,aAAa,YAAY;;;;;;;;;;;;AAelE,SAAS,oBACP,YACA,SACA,WACA,UACA,SACG;CACH,MAAM,WAAW,SAAS,WAAW;AAGrC,KAAI,QAAQ,YAAY,YAAY,UAAU,YAAY,OACxD,QAAO,UAAU;CAGnB,MAAM,aAAa,SAAS,SAAS;AACrC,SAAQ,UAAU;AAGlB,KACE,UAAU,YAAY,WACrB,UACG,QAAQ,UAAU,SAAS,WAAW,GACtC,OAAO,GAAG,UAAU,SAAS,WAAW,EAE5C,QAAO,UAAU;AAGnB,WAAU,UAAU;AACpB,QAAO;;;;;;;;;;;;;AAgBT,SAAS,qBACP,YACA,UACA,YACA,aACA,YACa;CACb,MAAM,WAAW,SAAS,WAAW;AAGrC,KAAI,YAAY,YAAY,SAC1B,QAAO,WAAW;AAIpB,KACE,YAAY,YAAY,UACxB,CAAC,UAAU,YAAY,SAAS,UAAU,SAAS,CAGnD,QAAO,WAAW;AAIpB,aAAY,UAAU;CACtB,MAAM,UAAU,YAAY,UAAU,UAAU,WAAW;AAC3D,YAAW,UAAU;AACrB,QAAO"}
@@ -1,4 +1,4 @@
1
- //#region src/utils.ts
1
+ //#region src/utils/internal/internal.ts
2
2
  const objectProto = Object.getPrototypeOf({});
3
3
  /**
4
4
  * Symbol that class instances can use to opt-in to deep proxying.
@@ -46,31 +46,9 @@ function findGetterDescriptor(target, prop) {
46
46
  proto = Object.getPrototypeOf(proto);
47
47
  }
48
48
  }
49
- /**
50
- * Shallow-equal comparison for objects and arrays.
51
- * Useful as a custom `isEqual` for `useStore` selectors that return objects/arrays.
52
- *
53
- * - Primitives compared with `Object.is`.
54
- * - Arrays: length + element-wise `Object.is`.
55
- * - Objects: key count + value-wise `Object.is`.
56
- */
57
- function shallowEqual(a, b) {
58
- if (Object.is(a, b)) return true;
59
- if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false;
60
- if (Array.isArray(a) && Array.isArray(b)) {
61
- if (a.length !== b.length) return false;
62
- for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
63
- return true;
64
- }
65
- const keysA = Object.keys(a);
66
- const keysB = Object.keys(b);
67
- if (keysA.length !== keysB.length) return false;
68
- for (const key of keysA) if (!Object.hasOwn(b, key) || !Object.is(a[key], b[key])) return false;
69
- return true;
70
- }
71
49
 
72
50
  //#endregion
73
- //#region src/core.ts
51
+ //#region src/core/core.ts
74
52
  /** Global version counter shared across all stores. */
75
53
  let globalVersion = 0;
76
54
  /** Maps every store proxy → its internal bookkeeping. */
@@ -300,7 +278,7 @@ function getVersion(proxy) {
300
278
  }
301
279
 
302
280
  //#endregion
303
- //#region src/snapshot.ts
281
+ //#region src/snapshot/snapshot.ts
304
282
  /**
305
283
  * Version-stamped snapshot cache for tracked (proxied) sub-trees.
306
284
  * Key: raw target object → [version, frozen snapshot].
@@ -542,5 +520,5 @@ function snapshot(proxyStore) {
542
520
  }
543
521
 
544
522
  //#endregion
545
- export { subscribe as a, shallowEqual as c, store as i, getInternal as n, PROXYABLE as o, getVersion as r, findGetterDescriptor as s, snapshot as t };
546
- //# sourceMappingURL=snapshot-fVu34Cr6.mjs.map
523
+ export { subscribe as a, store as i, getInternal as n, PROXYABLE as o, getVersion as r, findGetterDescriptor as s, snapshot as t };
524
+ //# sourceMappingURL=snapshot-C8JDLu8L.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-C8JDLu8L.mjs","names":[],"sources":["../src/utils/internal/internal.ts","../src/core/core.ts","../src/snapshot/snapshot.ts"],"sourcesContent":["const objectProto = Object.getPrototypeOf({});\n\n/**\n * Symbol that class instances can use to opt-in to deep proxying.\n * Classes with `static [PROXYABLE] = true` will be wrapped by the store proxy\n * just like plain objects, enabling nested reactivity.\n */\nexport const PROXYABLE = Symbol.for('@codebelt/classy-store.proxyable');\n\n/**\n * Returns `true` if `value` is a plain object (created via `{}` or `new Object()`).\n * Needed by `canProxy()` to distinguish plain objects (which should be deep-proxied)\n * from class instances, Date, Map, etc. (which should not).\n */\nexport function isPlainObject(\n value: unknown,\n): value is Record<string | symbol, unknown> {\n if (typeof value !== 'object' || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === objectProto || proto === null;\n}\n\n/**\n * Central gatekeeper for the proxy system: determines which values get wrapped\n * in child proxies (`core.ts`) or deep-cloned in snapshots (`snapshot.ts`).\n *\n * Returns `true` for arrays, plain objects, and class instances that opt-in\n * via `static [PROXYABLE] = true`. Everything else (Date, Map, Set, class\n * instances without PROXYABLE, primitives) is left as-is.\n */\nexport function canProxy(value: unknown): value is object {\n if (typeof value !== 'object' || value === null) return false;\n if (Array.isArray(value)) return true;\n // Allow class instances that opt-in via the PROXYABLE symbol.\n const ctor = (value as {constructor?: unknown}).constructor;\n if (ctor && (ctor as Record<symbol, unknown>)[PROXYABLE]) {\n return true;\n }\n return isPlainObject(value);\n}\n\n/**\n * Walk the prototype chain of `target` looking for a getter descriptor for `prop`.\n * Returns the first (most-derived) getter found, or `undefined` if none exists.\n *\n * Used by `core.ts` (GET trap) to detect class getters for memoized evaluation,\n * and by `snapshot.ts` to skip own-property copying for getter-backed keys.\n */\nexport function findGetterDescriptor(\n target: object,\n prop: string | symbol,\n): PropertyDescriptor | undefined {\n let proto: object | null = target;\n while (proto) {\n const desc = Object.getOwnPropertyDescriptor(proto, prop);\n if (desc?.get) return desc;\n proto = Object.getPrototypeOf(proto);\n }\n return undefined;\n}\n","import type {DepEntry, StoreInternal} from '../types';\nimport {canProxy, findGetterDescriptor} from '../utils/internal/internal';\n\n// ── Global state ──────────────────────────────────────────────────────────────\n\n/** Global version counter shared across all stores. */\nlet globalVersion = 0;\n\n/** Maps every store proxy → its internal bookkeeping. */\nconst internalsMap = new WeakMap<object, StoreInternal>();\n\n// ── Dependency tracking for computed getters ──────────────────────────────────\n\n/**\n * Stack of active dependency trackers. Each entry records which properties\n * a getter reads during evaluation. A stack (not a single variable) is needed\n * because getter A can read getter B, which pushes a second tracker.\n */\nconst trackerStack: {internal: StoreInternal; deps: DepEntry[]}[] = [];\n\n/** Returns the tracker currently recording deps, or `null` if none is active. */\nfunction activeTracker() {\n return trackerStack.length > 0 ? trackerStack[trackerStack.length - 1] : null;\n}\n\n/**\n * Record a dependency on the active tracker (if any).\n * Called from the GET trap whenever a non-getter, non-method property is read.\n */\nfunction recordDep(\n internal: StoreInternal,\n prop: string | symbol,\n value: unknown,\n): void {\n const tracker = activeTracker();\n if (!tracker) return;\n\n // Only record deps for reads on the SAME proxy that owns the getter.\n // Child-proxy reads don't need explicit tracking because child mutations\n // bubble up via version numbers on the parent's childInternals.\n if (tracker.internal !== internal) return;\n\n const childInternal = internal.childInternals.get(prop);\n if (childInternal) {\n tracker.deps.push({\n kind: 'version',\n internal: childInternal,\n version: childInternal.version,\n parentTarget: internal.target,\n prop,\n });\n } else {\n tracker.deps.push({kind: 'value', target: internal.target, prop, value});\n }\n}\n\n/**\n * Check whether all dependencies from a previous computation are still valid.\n */\nfunction areDepsValid(deps: DepEntry[]): boolean {\n for (const dep of deps) {\n if (dep.kind === 'version') {\n // Verify the parent property still references the same child object.\n // If the property was replaced entirely (e.g., store.items = newArray),\n // the old child internal is detached and its version never changes.\n // This check catches that case.\n if (\n !Object.is(Reflect.get(dep.parentTarget, dep.prop), dep.internal.target)\n )\n return false;\n if (dep.internal.version !== dep.version) return false;\n } else {\n // Check that the property still exists — if it was deleted and\n // dep.value was `undefined`, Object.is(undefined, undefined) would\n // incorrectly pass without this guard.\n if (!Reflect.has(dep.target, dep.prop)) return false;\n if (!Object.is(Reflect.get(dep.target, dep.prop), dep.value))\n return false;\n }\n }\n return true;\n}\n\n/**\n * Evaluate a computed getter with memoization.\n * Returns the cached value if dependencies haven't changed.\n * Otherwise re-evaluates with dependency tracking and caches the result.\n */\nfunction evaluateComputed(\n internal: StoreInternal,\n prop: string | symbol,\n getterFn: () => unknown,\n receiver: object,\n): unknown {\n const cached = internal.computedCache.get(prop);\n if (cached && areDepsValid(cached.deps)) {\n return cached.value;\n }\n\n // Push a new tracker frame for this getter evaluation.\n const frame = {internal, deps: [] as DepEntry[]};\n trackerStack.push(frame);\n try {\n const value = getterFn.call(receiver);\n internal.computedCache.set(prop, {value, deps: frame.deps});\n return value;\n } finally {\n trackerStack.pop();\n }\n}\n\n// ── Public helpers ────────────────────────────────────────────────────────────\n\n/**\n * Retrieve the internal bookkeeping for a store proxy.\n * Throws if the object was not created with `store()`.\n */\nexport function getInternal(proxy: object): StoreInternal {\n const internal = internalsMap.get(proxy);\n if (!internal)\n throw new Error('@codebelt/classy-store: object is not a store proxy');\n return internal;\n}\n\n/**\n * Returns `true` if the given object is a store proxy.\n */\nexport function isStoreProxy(value: unknown): boolean {\n return typeof value === 'object' && value !== null && internalsMap.has(value);\n}\n\n// ── Notification batching ─────────────────────────────────────────────────────\n\n/**\n * Bump version from the mutated node up to the root, invalidate snapshot caches,\n * and schedule a single microtask notification (deduped at the root level).\n *\n * Version propagation is what enables structural sharing in snapshots: unchanged\n * children keep their old version, so the snapshot cache returns the same frozen ref.\n */\nfunction scheduleNotify(internal: StoreInternal): void {\n let current: StoreInternal | null = internal;\n while (current) {\n current.version = ++globalVersion;\n current = current.parent;\n }\n\n const root = getRoot(internal);\n if (!root.notifyScheduled) {\n root.notifyScheduled = true;\n queueMicrotask(() => {\n root.notifyScheduled = false;\n for (const listener of root.listeners) {\n listener();\n }\n });\n }\n}\n\n/** Walk the parent chain to find the root StoreInternal (the notification hub). */\nfunction getRoot(internal: StoreInternal): StoreInternal {\n let current = internal;\n while (current.parent) {\n current = current.parent;\n }\n return current;\n}\n\n// ── Create proxy (recursive) ──────────────────────────────────────────────────\n\n/**\n * Create an ES6 Proxy around `target` with SET/GET/DELETE traps.\n *\n * This is the core of the library's reactivity. The proxy intercepts:\n * - **SET**: compares old/new with `Object.is`, cleans up child proxies on replacement,\n * forwards the write, and schedules a batched notification.\n * - **GET**: detects class getters (memoized), binds methods to the proxy, lazily wraps\n * nested objects/arrays in child proxies, and records dependencies for computed getters.\n * - **DELETE**: cleans up child proxies and schedules notification.\n *\n * Nested objects are recursively wrapped on first access (lazy deep proxy).\n */\nfunction createStoreProxy<T extends object>(\n target: T,\n parent: StoreInternal | null,\n): T {\n const internal: StoreInternal = {\n target,\n version: ++globalVersion,\n listeners: new Set(),\n childProxies: new Map(),\n childInternals: new Map(),\n parent,\n notifyScheduled: false,\n computedCache: new Map(),\n };\n\n /** Cache for bound methods so we return the same reference each time. */\n const boundMethods = new Map<\n string | symbol,\n (...args: unknown[]) => unknown\n >();\n\n const proxy = new Proxy(target, {\n set(_target, prop, value, _receiver) {\n const oldValue = Reflect.get(_target, prop);\n if (Object.is(oldValue, value)) return true; // noop — same value\n\n // If the new value replaces a child proxy, clean it up.\n if (internal.childProxies.has(prop)) {\n internal.childProxies.delete(prop);\n internal.childInternals.delete(prop);\n }\n\n Reflect.set(_target, prop, value);\n scheduleNotify(internal);\n return true;\n },\n\n get(_target, prop, receiver) {\n // 1. Check for getters on the prototype chain (computed values).\n const getterDesc = findGetterDescriptor(_target, prop);\n if (getterDesc?.get) {\n // Memoized: evaluate with dependency tracking, return cached if deps unchanged.\n return evaluateComputed(internal, prop, getterDesc.get, receiver);\n }\n\n const value = Reflect.get(_target, prop);\n\n // 2. Methods: bind to the proxy so `this.prop = x` goes through SET trap.\n if (typeof value === 'function') {\n // Array prototype methods should not be cached the same way as class methods.\n if (Array.isArray(_target)) {\n return value.bind(receiver);\n }\n const cached = boundMethods.get(prop);\n if (cached) return cached;\n const bound = (value as (...args: unknown[]) => unknown).bind(receiver);\n boundMethods.set(prop, bound);\n return bound;\n }\n\n // 3. Nested plain objects/arrays: lazy-wrap in a child proxy.\n if (canProxy(value)) {\n let childProxy = internal.childProxies.get(prop);\n if (!childProxy) {\n childProxy = createStoreProxy(value as object, internal);\n internal.childProxies.set(prop, childProxy);\n const childInternal = internalsMap.get(childProxy) as StoreInternal;\n internal.childInternals.set(prop, childInternal);\n }\n // Record dependency AFTER ensuring child proxy/internal exists.\n recordDep(internal, prop, value);\n return childProxy;\n }\n\n // 4. Primitives — record dependency and return.\n recordDep(internal, prop, value);\n return value;\n },\n\n deleteProperty(_target, prop) {\n if (internal.childProxies.has(prop)) {\n internal.childProxies.delete(prop);\n internal.childInternals.delete(prop);\n }\n const deleted = Reflect.deleteProperty(_target, prop);\n if (deleted) {\n scheduleNotify(internal);\n }\n return deleted;\n },\n });\n\n internalsMap.set(proxy, internal);\n return proxy;\n}\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\n/**\n * Wraps a class instance in a reactive proxy.\n *\n * - Mutations (property writes, array push/splice, etc.) are intercepted and\n * batched into a single notification per microtask.\n * - Class getters are automatically memoized — they only recompute when a\n * dependency they read changes.\n * - Methods are automatically bound so `this` mutations go through the proxy.\n *\n * @param instance - A class instance (or plain object) to make reactive.\n * @returns The same object wrapped in a reactive Proxy.\n */\nexport function store<T extends object>(instance: T): T {\n return createStoreProxy(instance, null);\n}\n\n/**\n * Subscribe to store changes. The callback fires once per batched mutation\n * (coalesced via `queueMicrotask`), not once per individual property write.\n *\n * @param proxy - A reactive proxy created by `store()`.\n * @param callback - Invoked after each batched mutation.\n * @returns An unsubscribe function. Call it to stop receiving notifications.\n */\nexport function subscribe(proxy: object, callback: () => void): () => void {\n const internal = getInternal(proxy);\n // Always subscribe on the root so notifications fire regardless of\n // whether the user subscribes to the root proxy or a child proxy.\n const root = getRoot(internal);\n root.listeners.add(callback);\n return () => {\n root.listeners.delete(callback);\n };\n}\n\n/**\n * Returns the current version number of a store proxy.\n *\n * Versions are monotonically increasing and bump on any mutation in the\n * store's subtree (child mutations propagate up to the root). Useful for\n * debugging, custom cache invalidation, or testing whether a store has changed.\n */\nexport function getVersion(proxy: object): number {\n return getInternal(proxy).version;\n}\n","import {getInternal} from '../core/core';\nimport type {Snapshot, StoreInternal} from '../types';\nimport {canProxy, findGetterDescriptor} from '../utils/internal/internal';\n\n// ── Caches ────────────────────────────────────────────────────────────────────\n\n/**\n * Version-stamped snapshot cache for tracked (proxied) sub-trees.\n * Key: raw target object → [version, frozen snapshot].\n */\nconst snapCache = new WeakMap<object, [version: number, snap: object]>();\n\n/**\n * Cache for untracked nested objects (never accessed through the proxy).\n * Key: the raw mutable object → frozen deep clone.\n * Safe because untracked objects can't be mutated through the proxy.\n */\nconst untrackedCache = new WeakMap<object, object>();\n\n/**\n * Per-snapshot, per-getter cache. Ensures repeated access to the same getter\n * on the same frozen snapshot returns the identical reference.\n */\nconst snapshotGetterCache = new WeakMap<\n object,\n Map<string | symbol, unknown>\n>();\n\n// ── Cross-snapshot getter memoization ─────────────────────────────────────────\n\n/**\n * A dependency recorded during getter execution.\n * prop: the property name read on `this`.\n * value: the value returned (reference).\n */\ntype GetterDep = {prop: string | symbol; value: unknown};\n\n/**\n * Cached result of a snapshot getter with the dependencies it read,\n * used for cross-snapshot memoization. When a new snapshot is created,\n * we check if the deps are still reference-equal (via structural sharing)\n * and return the cached result if so -- avoiding re-execution of the getter.\n */\ntype GetterMemoEntry = {deps: GetterDep[]; result: unknown};\n\n/**\n * Cross-snapshot getter memoization cache.\n * Keyed by raw class instance (target), maps each getter name to its last\n * deps + result. This enables getter result stability across snapshot\n * boundaries: if `this.items` hasn't changed between snapshots (same\n * reference via structural sharing), the getter returns the same result.\n */\nconst crossSnapMemo = new WeakMap<\n object,\n Map<string | symbol, GetterMemoEntry>\n>();\n\n/**\n * Run a getter against the snapshot with dependency tracking.\n *\n * Creates a lightweight Proxy that intercepts `this.prop` reads and records\n * which properties (and their values/references) the getter accessed. The\n * Proxy delegates to the real frozen snapshot for actual values, so getters\n * that read other getters trigger the memoized getter chain correctly.\n */\nfunction computeWithTracking(\n snap: object,\n getterFn: () => unknown,\n): {deps: GetterDep[]; result: unknown} {\n const deps: GetterDep[] = [];\n const readProps = new Set<string | symbol>();\n\n // Use an empty non-frozen target — we delegate everything to `snap`.\n const handler: ProxyHandler<object> = {\n get(_dummyTarget, prop, _receiver) {\n // Delegate to the real snapshot (receiver = snap so installed getters\n // run with `this = snap`, triggering their own memoization chain).\n const value = Reflect.get(snap, prop, snap);\n if (!readProps.has(prop)) {\n readProps.add(prop);\n deps.push({prop, value});\n }\n return value;\n },\n };\n\n const tracked = new Proxy({}, handler);\n const result = getterFn.call(tracked);\n return {deps, result};\n}\n\n/**\n * Check if all previously recorded deps still hold on the current snapshot.\n * For data properties this is a reference comparison (structural sharing\n * guarantees stable refs for unchanged sub-trees). For getter properties\n * this invokes the getter (which is itself memoized), then compares.\n */\nfunction areMemoedDepsValid(currentSnap: object, deps: GetterDep[]): boolean {\n for (const dep of deps) {\n const currentValue = Reflect.get(currentSnap, dep.prop, currentSnap);\n if (!Object.is(currentValue, dep.value)) return false;\n }\n return true;\n}\n\n/**\n * Evaluate a snapshot getter with two layers of caching:\n *\n * 1. **Per-snapshot cache** — same getter on the same frozen snapshot always\n * returns the same reference.\n * 2. **Cross-snapshot memo** — if the properties the getter read last time are\n * structurally the same (reference equality via structural sharing), the\n * previous result is returned without re-running the getter body.\n */\nfunction evaluateSnapshotGetter(\n currentSnap: object,\n target: object,\n key: string | symbol,\n getterFn: () => unknown,\n): unknown {\n // ── Per-snapshot fast path ──\n const perSnapCache = snapshotGetterCache.get(currentSnap);\n if (perSnapCache?.has(key)) return perSnapCache.get(key);\n\n // ── Cross-snapshot memo ──\n let memoMap = crossSnapMemo.get(target);\n const prev = memoMap?.get(key);\n\n let result: unknown;\n\n if (prev && areMemoedDepsValid(currentSnap, prev.deps)) {\n // Dependencies unchanged → reuse previous result.\n result = prev.result;\n } else {\n // Compute fresh with dep tracking.\n const computation = computeWithTracking(currentSnap, getterFn);\n result = computation.result;\n\n // Save cross-snapshot memo.\n if (!memoMap) {\n memoMap = new Map();\n crossSnapMemo.set(target, memoMap);\n }\n memoMap.set(key, {deps: computation.deps, result});\n }\n\n // Save per-snapshot cache.\n let cache = snapshotGetterCache.get(currentSnap);\n if (!cache) {\n cache = new Map();\n snapshotGetterCache.set(currentSnap, cache);\n }\n cache.set(key, result);\n\n return result;\n}\n\n// ── Internal helpers ──────────────────────────────────────────────────────────\n\n/**\n * Resolve a single property value into its snapshot equivalent.\n *\n * - If the key has a tracked child internal → recurse (version-cached, structural sharing).\n * - If the value is a nested plain object/array without tracking → deep-clone & freeze (cached by identity).\n * - Otherwise → return the value as-is (primitive, Date, Map, function, etc.).\n */\nfunction snapshotValue(\n value: unknown,\n parentInternal: StoreInternal,\n key: string | symbol,\n): unknown {\n const childInternal = parentInternal.childInternals.get(key);\n if (childInternal) {\n return createSnapshotRecursive(childInternal.target, childInternal);\n }\n if (canProxy(value)) {\n return deepFreezeClone(value as object);\n }\n return value;\n}\n\n/**\n * Deep-clone and freeze a plain object or array that is NOT tracked by a proxy.\n * Cached by raw object identity for structural sharing across snapshots.\n */\nfunction deepFreezeClone(value: object): object {\n const cached = untrackedCache.get(value);\n if (cached) return cached;\n\n let clone: Record<string | symbol, unknown> | unknown[];\n\n if (Array.isArray(value)) {\n clone = [];\n for (let i = 0; i < value.length; i++) {\n const item = value[i];\n (clone as unknown[])[i] = canProxy(item)\n ? deepFreezeClone(item as object)\n : item;\n }\n } else {\n clone = Object.create(Object.getPrototypeOf(value));\n for (const key of Reflect.ownKeys(value)) {\n const desc = Object.getOwnPropertyDescriptor(value, key);\n if (!desc || !('value' in desc)) continue;\n const item = desc.value;\n (clone as Record<string | symbol, unknown>)[key] = canProxy(item)\n ? deepFreezeClone(item as object)\n : item;\n }\n }\n\n Object.freeze(clone);\n untrackedCache.set(value, clone);\n return clone;\n}\n\n/**\n * Collect all getter descriptors from the prototype chain of `target`.\n * Returns an array of [propertyName, getterFunction] pairs.\n *\n * Only includes getters defined on the prototype (class getters), not on the\n * instance itself. When a getter is overridden in a subclass, the most-derived\n * version wins (we walk from the instance's direct prototype upward and skip\n * keys already seen).\n */\nfunction collectGetters(\n target: object,\n): Array<[string | symbol, () => unknown]> {\n const getters: Array<[string | symbol, () => unknown]> = [];\n const seen = new Set<string | symbol>();\n let proto: object | null = Object.getPrototypeOf(target);\n while (proto && proto !== Object.prototype) {\n for (const key of Reflect.ownKeys(proto)) {\n if (key === 'constructor') continue;\n if (seen.has(key)) continue; // most-derived version already collected\n const desc = Object.getOwnPropertyDescriptor(proto, key);\n if (desc?.get) {\n getters.push([key, desc.get]);\n seen.add(key);\n }\n }\n proto = Object.getPrototypeOf(proto);\n }\n return getters;\n}\n\n/**\n * Install lazy-memoizing getters on a snapshot object.\n *\n * Each getter uses cross-snapshot memoization:\n * - Tracks which `this` properties the getter reads on first evaluation.\n * - On subsequent snapshots, if those properties are structurally the same\n * (thanks to structural sharing), the previous result is returned.\n * - Within the same snapshot, repeated accesses always return the same ref.\n */\nfunction installMemoizedGetters(\n snap: Record<string | symbol, unknown>,\n target: object,\n): void {\n const getters = collectGetters(target);\n for (const [key, getterFn] of getters) {\n Object.defineProperty(snap, key, {\n get() {\n return evaluateSnapshotGetter(this as object, target, key, getterFn);\n },\n enumerable: true,\n configurable: true, // required so Object.freeze can make it non-configurable\n });\n }\n}\n\n/**\n * Recursively creates a frozen snapshot from a tracked (proxied) sub-tree.\n *\n * Each node checks its version-stamped cache first (O(1) hit). On a miss,\n * it builds a new frozen object by recursing into child internals (tracked\n * sub-trees) and deep-cloning untracked nested objects. Unchanged children\n * return the same cached reference, achieving structural sharing -- the key\n * to efficient `Object.is` equality in selectors.\n *\n * For class instances, the prototype chain is preserved and class getters\n * are installed as lazy-memoizing accessors via `installMemoizedGetters`.\n */\nfunction createSnapshotRecursive<T extends object>(\n target: T,\n internal: StoreInternal,\n): T {\n // Cache hit: version unchanged → return the same frozen snapshot reference.\n const cached = snapCache.get(target);\n if (cached && cached[0] === internal.version) {\n return cached[1] as T;\n }\n\n let snap: Record<string | symbol, unknown> | unknown[];\n\n if (Array.isArray(target)) {\n snap = [];\n for (let i = 0; i < target.length; i++) {\n (snap as unknown[])[i] = snapshotValue(target[i], internal, String(i));\n }\n } else {\n // Preserve the prototype chain and install memoized getters on the snapshot.\n snap = Object.create(Object.getPrototypeOf(target));\n for (const key of Reflect.ownKeys(target)) {\n // Skip prototype getters — they re-evaluate via the preserved prototype.\n if (findGetterDescriptor(target, key)?.get) continue;\n\n const desc = Object.getOwnPropertyDescriptor(target, key);\n if (!desc || !('value' in desc)) continue;\n\n (snap as Record<string | symbol, unknown>)[key] = snapshotValue(\n desc.value,\n internal,\n key,\n );\n }\n\n // Install lazy-memoizing getters with cross-snapshot caching.\n installMemoizedGetters(snap as Record<string | symbol, unknown>, target);\n }\n\n Object.freeze(snap);\n // Cache AFTER populating + freezing. The reference is stable.\n snapCache.set(target, [internal.version, snap]);\n return snap as T;\n}\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\n/**\n * Creates an immutable, deeply-frozen snapshot of the store proxy's current state.\n *\n * **Structural sharing:** unchanged sub-trees reuse the previous snapshot's\n * object reference, so `===` comparison can cheaply detect changes.\n *\n * **Version-cached:** calling `snapshot()` multiple times without intervening\n * mutations returns the identical snapshot object (O(1) cache hit).\n *\n * **Getters:** class getters are automatically memoized — they compute once\n * per snapshot and their results are stable across snapshots when dependencies\n * haven't changed (cross-snapshot memoization).\n *\n * @param proxyStore - A reactive proxy created by `store()`.\n * @returns A deeply frozen plain-JS object (Snapshot<T>).\n */\nexport function snapshot<T extends object>(proxyStore: T): Snapshot<T> {\n const internal = getInternal(proxyStore);\n return createSnapshotRecursive(internal.target, internal) as Snapshot<T>;\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
 
2
- //#region src/utils.ts
2
+ //#region src/utils/internal/internal.ts
3
3
  const objectProto = Object.getPrototypeOf({});
4
4
  /**
5
5
  * Symbol that class instances can use to opt-in to deep proxying.
@@ -47,31 +47,9 @@ function findGetterDescriptor(target, prop) {
47
47
  proto = Object.getPrototypeOf(proto);
48
48
  }
49
49
  }
50
- /**
51
- * Shallow-equal comparison for objects and arrays.
52
- * Useful as a custom `isEqual` for `useStore` selectors that return objects/arrays.
53
- *
54
- * - Primitives compared with `Object.is`.
55
- * - Arrays: length + element-wise `Object.is`.
56
- * - Objects: key count + value-wise `Object.is`.
57
- */
58
- function shallowEqual(a, b) {
59
- if (Object.is(a, b)) return true;
60
- if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false;
61
- if (Array.isArray(a) && Array.isArray(b)) {
62
- if (a.length !== b.length) return false;
63
- for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
64
- return true;
65
- }
66
- const keysA = Object.keys(a);
67
- const keysB = Object.keys(b);
68
- if (keysA.length !== keysB.length) return false;
69
- for (const key of keysA) if (!Object.hasOwn(b, key) || !Object.is(a[key], b[key])) return false;
70
- return true;
71
- }
72
50
 
73
51
  //#endregion
74
- //#region src/core.ts
52
+ //#region src/core/core.ts
75
53
  /** Global version counter shared across all stores. */
76
54
  let globalVersion = 0;
77
55
  /** Maps every store proxy → its internal bookkeeping. */
@@ -301,7 +279,7 @@ function getVersion(proxy) {
301
279
  }
302
280
 
303
281
  //#endregion
304
- //#region src/snapshot.ts
282
+ //#region src/snapshot/snapshot.ts
305
283
  /**
306
284
  * Version-stamped snapshot cache for tracked (proxied) sub-trees.
307
285
  * Key: raw target object → [version, frozen snapshot].
@@ -567,12 +545,6 @@ Object.defineProperty(exports, 'getVersion', {
567
545
  return getVersion;
568
546
  }
569
547
  });
570
- Object.defineProperty(exports, 'shallowEqual', {
571
- enumerable: true,
572
- get: function () {
573
- return shallowEqual;
574
- }
575
- });
576
548
  Object.defineProperty(exports, 'snapshot', {
577
549
  enumerable: true,
578
550
  get: function () {
@@ -591,4 +563,4 @@ Object.defineProperty(exports, 'subscribe', {
591
563
  return subscribe;
592
564
  }
593
565
  });
594
- //# sourceMappingURL=snapshot-TbHIUjvP.cjs.map
566
+ //# sourceMappingURL=snapshot-CR8nA2Ob.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-CR8nA2Ob.cjs","names":[],"sources":["../src/utils/internal/internal.ts","../src/core/core.ts","../src/snapshot/snapshot.ts"],"sourcesContent":["const objectProto = Object.getPrototypeOf({});\n\n/**\n * Symbol that class instances can use to opt-in to deep proxying.\n * Classes with `static [PROXYABLE] = true` will be wrapped by the store proxy\n * just like plain objects, enabling nested reactivity.\n */\nexport const PROXYABLE = Symbol.for('@codebelt/classy-store.proxyable');\n\n/**\n * Returns `true` if `value` is a plain object (created via `{}` or `new Object()`).\n * Needed by `canProxy()` to distinguish plain objects (which should be deep-proxied)\n * from class instances, Date, Map, etc. (which should not).\n */\nexport function isPlainObject(\n value: unknown,\n): value is Record<string | symbol, unknown> {\n if (typeof value !== 'object' || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === objectProto || proto === null;\n}\n\n/**\n * Central gatekeeper for the proxy system: determines which values get wrapped\n * in child proxies (`core.ts`) or deep-cloned in snapshots (`snapshot.ts`).\n *\n * Returns `true` for arrays, plain objects, and class instances that opt-in\n * via `static [PROXYABLE] = true`. Everything else (Date, Map, Set, class\n * instances without PROXYABLE, primitives) is left as-is.\n */\nexport function canProxy(value: unknown): value is object {\n if (typeof value !== 'object' || value === null) return false;\n if (Array.isArray(value)) return true;\n // Allow class instances that opt-in via the PROXYABLE symbol.\n const ctor = (value as {constructor?: unknown}).constructor;\n if (ctor && (ctor as Record<symbol, unknown>)[PROXYABLE]) {\n return true;\n }\n return isPlainObject(value);\n}\n\n/**\n * Walk the prototype chain of `target` looking for a getter descriptor for `prop`.\n * Returns the first (most-derived) getter found, or `undefined` if none exists.\n *\n * Used by `core.ts` (GET trap) to detect class getters for memoized evaluation,\n * and by `snapshot.ts` to skip own-property copying for getter-backed keys.\n */\nexport function findGetterDescriptor(\n target: object,\n prop: string | symbol,\n): PropertyDescriptor | undefined {\n let proto: object | null = target;\n while (proto) {\n const desc = Object.getOwnPropertyDescriptor(proto, prop);\n if (desc?.get) return desc;\n proto = Object.getPrototypeOf(proto);\n }\n return undefined;\n}\n","import type {DepEntry, StoreInternal} from '../types';\nimport {canProxy, findGetterDescriptor} from '../utils/internal/internal';\n\n// ── Global state ──────────────────────────────────────────────────────────────\n\n/** Global version counter shared across all stores. */\nlet globalVersion = 0;\n\n/** Maps every store proxy → its internal bookkeeping. */\nconst internalsMap = new WeakMap<object, StoreInternal>();\n\n// ── Dependency tracking for computed getters ──────────────────────────────────\n\n/**\n * Stack of active dependency trackers. Each entry records which properties\n * a getter reads during evaluation. A stack (not a single variable) is needed\n * because getter A can read getter B, which pushes a second tracker.\n */\nconst trackerStack: {internal: StoreInternal; deps: DepEntry[]}[] = [];\n\n/** Returns the tracker currently recording deps, or `null` if none is active. */\nfunction activeTracker() {\n return trackerStack.length > 0 ? trackerStack[trackerStack.length - 1] : null;\n}\n\n/**\n * Record a dependency on the active tracker (if any).\n * Called from the GET trap whenever a non-getter, non-method property is read.\n */\nfunction recordDep(\n internal: StoreInternal,\n prop: string | symbol,\n value: unknown,\n): void {\n const tracker = activeTracker();\n if (!tracker) return;\n\n // Only record deps for reads on the SAME proxy that owns the getter.\n // Child-proxy reads don't need explicit tracking because child mutations\n // bubble up via version numbers on the parent's childInternals.\n if (tracker.internal !== internal) return;\n\n const childInternal = internal.childInternals.get(prop);\n if (childInternal) {\n tracker.deps.push({\n kind: 'version',\n internal: childInternal,\n version: childInternal.version,\n parentTarget: internal.target,\n prop,\n });\n } else {\n tracker.deps.push({kind: 'value', target: internal.target, prop, value});\n }\n}\n\n/**\n * Check whether all dependencies from a previous computation are still valid.\n */\nfunction areDepsValid(deps: DepEntry[]): boolean {\n for (const dep of deps) {\n if (dep.kind === 'version') {\n // Verify the parent property still references the same child object.\n // If the property was replaced entirely (e.g., store.items = newArray),\n // the old child internal is detached and its version never changes.\n // This check catches that case.\n if (\n !Object.is(Reflect.get(dep.parentTarget, dep.prop), dep.internal.target)\n )\n return false;\n if (dep.internal.version !== dep.version) return false;\n } else {\n // Check that the property still exists — if it was deleted and\n // dep.value was `undefined`, Object.is(undefined, undefined) would\n // incorrectly pass without this guard.\n if (!Reflect.has(dep.target, dep.prop)) return false;\n if (!Object.is(Reflect.get(dep.target, dep.prop), dep.value))\n return false;\n }\n }\n return true;\n}\n\n/**\n * Evaluate a computed getter with memoization.\n * Returns the cached value if dependencies haven't changed.\n * Otherwise re-evaluates with dependency tracking and caches the result.\n */\nfunction evaluateComputed(\n internal: StoreInternal,\n prop: string | symbol,\n getterFn: () => unknown,\n receiver: object,\n): unknown {\n const cached = internal.computedCache.get(prop);\n if (cached && areDepsValid(cached.deps)) {\n return cached.value;\n }\n\n // Push a new tracker frame for this getter evaluation.\n const frame = {internal, deps: [] as DepEntry[]};\n trackerStack.push(frame);\n try {\n const value = getterFn.call(receiver);\n internal.computedCache.set(prop, {value, deps: frame.deps});\n return value;\n } finally {\n trackerStack.pop();\n }\n}\n\n// ── Public helpers ────────────────────────────────────────────────────────────\n\n/**\n * Retrieve the internal bookkeeping for a store proxy.\n * Throws if the object was not created with `store()`.\n */\nexport function getInternal(proxy: object): StoreInternal {\n const internal = internalsMap.get(proxy);\n if (!internal)\n throw new Error('@codebelt/classy-store: object is not a store proxy');\n return internal;\n}\n\n/**\n * Returns `true` if the given object is a store proxy.\n */\nexport function isStoreProxy(value: unknown): boolean {\n return typeof value === 'object' && value !== null && internalsMap.has(value);\n}\n\n// ── Notification batching ─────────────────────────────────────────────────────\n\n/**\n * Bump version from the mutated node up to the root, invalidate snapshot caches,\n * and schedule a single microtask notification (deduped at the root level).\n *\n * Version propagation is what enables structural sharing in snapshots: unchanged\n * children keep their old version, so the snapshot cache returns the same frozen ref.\n */\nfunction scheduleNotify(internal: StoreInternal): void {\n let current: StoreInternal | null = internal;\n while (current) {\n current.version = ++globalVersion;\n current = current.parent;\n }\n\n const root = getRoot(internal);\n if (!root.notifyScheduled) {\n root.notifyScheduled = true;\n queueMicrotask(() => {\n root.notifyScheduled = false;\n for (const listener of root.listeners) {\n listener();\n }\n });\n }\n}\n\n/** Walk the parent chain to find the root StoreInternal (the notification hub). */\nfunction getRoot(internal: StoreInternal): StoreInternal {\n let current = internal;\n while (current.parent) {\n current = current.parent;\n }\n return current;\n}\n\n// ── Create proxy (recursive) ──────────────────────────────────────────────────\n\n/**\n * Create an ES6 Proxy around `target` with SET/GET/DELETE traps.\n *\n * This is the core of the library's reactivity. The proxy intercepts:\n * - **SET**: compares old/new with `Object.is`, cleans up child proxies on replacement,\n * forwards the write, and schedules a batched notification.\n * - **GET**: detects class getters (memoized), binds methods to the proxy, lazily wraps\n * nested objects/arrays in child proxies, and records dependencies for computed getters.\n * - **DELETE**: cleans up child proxies and schedules notification.\n *\n * Nested objects are recursively wrapped on first access (lazy deep proxy).\n */\nfunction createStoreProxy<T extends object>(\n target: T,\n parent: StoreInternal | null,\n): T {\n const internal: StoreInternal = {\n target,\n version: ++globalVersion,\n listeners: new Set(),\n childProxies: new Map(),\n childInternals: new Map(),\n parent,\n notifyScheduled: false,\n computedCache: new Map(),\n };\n\n /** Cache for bound methods so we return the same reference each time. */\n const boundMethods = new Map<\n string | symbol,\n (...args: unknown[]) => unknown\n >();\n\n const proxy = new Proxy(target, {\n set(_target, prop, value, _receiver) {\n const oldValue = Reflect.get(_target, prop);\n if (Object.is(oldValue, value)) return true; // noop — same value\n\n // If the new value replaces a child proxy, clean it up.\n if (internal.childProxies.has(prop)) {\n internal.childProxies.delete(prop);\n internal.childInternals.delete(prop);\n }\n\n Reflect.set(_target, prop, value);\n scheduleNotify(internal);\n return true;\n },\n\n get(_target, prop, receiver) {\n // 1. Check for getters on the prototype chain (computed values).\n const getterDesc = findGetterDescriptor(_target, prop);\n if (getterDesc?.get) {\n // Memoized: evaluate with dependency tracking, return cached if deps unchanged.\n return evaluateComputed(internal, prop, getterDesc.get, receiver);\n }\n\n const value = Reflect.get(_target, prop);\n\n // 2. Methods: bind to the proxy so `this.prop = x` goes through SET trap.\n if (typeof value === 'function') {\n // Array prototype methods should not be cached the same way as class methods.\n if (Array.isArray(_target)) {\n return value.bind(receiver);\n }\n const cached = boundMethods.get(prop);\n if (cached) return cached;\n const bound = (value as (...args: unknown[]) => unknown).bind(receiver);\n boundMethods.set(prop, bound);\n return bound;\n }\n\n // 3. Nested plain objects/arrays: lazy-wrap in a child proxy.\n if (canProxy(value)) {\n let childProxy = internal.childProxies.get(prop);\n if (!childProxy) {\n childProxy = createStoreProxy(value as object, internal);\n internal.childProxies.set(prop, childProxy);\n const childInternal = internalsMap.get(childProxy) as StoreInternal;\n internal.childInternals.set(prop, childInternal);\n }\n // Record dependency AFTER ensuring child proxy/internal exists.\n recordDep(internal, prop, value);\n return childProxy;\n }\n\n // 4. Primitives — record dependency and return.\n recordDep(internal, prop, value);\n return value;\n },\n\n deleteProperty(_target, prop) {\n if (internal.childProxies.has(prop)) {\n internal.childProxies.delete(prop);\n internal.childInternals.delete(prop);\n }\n const deleted = Reflect.deleteProperty(_target, prop);\n if (deleted) {\n scheduleNotify(internal);\n }\n return deleted;\n },\n });\n\n internalsMap.set(proxy, internal);\n return proxy;\n}\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\n/**\n * Wraps a class instance in a reactive proxy.\n *\n * - Mutations (property writes, array push/splice, etc.) are intercepted and\n * batched into a single notification per microtask.\n * - Class getters are automatically memoized — they only recompute when a\n * dependency they read changes.\n * - Methods are automatically bound so `this` mutations go through the proxy.\n *\n * @param instance - A class instance (or plain object) to make reactive.\n * @returns The same object wrapped in a reactive Proxy.\n */\nexport function store<T extends object>(instance: T): T {\n return createStoreProxy(instance, null);\n}\n\n/**\n * Subscribe to store changes. The callback fires once per batched mutation\n * (coalesced via `queueMicrotask`), not once per individual property write.\n *\n * @param proxy - A reactive proxy created by `store()`.\n * @param callback - Invoked after each batched mutation.\n * @returns An unsubscribe function. Call it to stop receiving notifications.\n */\nexport function subscribe(proxy: object, callback: () => void): () => void {\n const internal = getInternal(proxy);\n // Always subscribe on the root so notifications fire regardless of\n // whether the user subscribes to the root proxy or a child proxy.\n const root = getRoot(internal);\n root.listeners.add(callback);\n return () => {\n root.listeners.delete(callback);\n };\n}\n\n/**\n * Returns the current version number of a store proxy.\n *\n * Versions are monotonically increasing and bump on any mutation in the\n * store's subtree (child mutations propagate up to the root). Useful for\n * debugging, custom cache invalidation, or testing whether a store has changed.\n */\nexport function getVersion(proxy: object): number {\n return getInternal(proxy).version;\n}\n","import {getInternal} from '../core/core';\nimport type {Snapshot, StoreInternal} from '../types';\nimport {canProxy, findGetterDescriptor} from '../utils/internal/internal';\n\n// ── Caches ────────────────────────────────────────────────────────────────────\n\n/**\n * Version-stamped snapshot cache for tracked (proxied) sub-trees.\n * Key: raw target object → [version, frozen snapshot].\n */\nconst snapCache = new WeakMap<object, [version: number, snap: object]>();\n\n/**\n * Cache for untracked nested objects (never accessed through the proxy).\n * Key: the raw mutable object → frozen deep clone.\n * Safe because untracked objects can't be mutated through the proxy.\n */\nconst untrackedCache = new WeakMap<object, object>();\n\n/**\n * Per-snapshot, per-getter cache. Ensures repeated access to the same getter\n * on the same frozen snapshot returns the identical reference.\n */\nconst snapshotGetterCache = new WeakMap<\n object,\n Map<string | symbol, unknown>\n>();\n\n// ── Cross-snapshot getter memoization ─────────────────────────────────────────\n\n/**\n * A dependency recorded during getter execution.\n * prop: the property name read on `this`.\n * value: the value returned (reference).\n */\ntype GetterDep = {prop: string | symbol; value: unknown};\n\n/**\n * Cached result of a snapshot getter with the dependencies it read,\n * used for cross-snapshot memoization. When a new snapshot is created,\n * we check if the deps are still reference-equal (via structural sharing)\n * and return the cached result if so -- avoiding re-execution of the getter.\n */\ntype GetterMemoEntry = {deps: GetterDep[]; result: unknown};\n\n/**\n * Cross-snapshot getter memoization cache.\n * Keyed by raw class instance (target), maps each getter name to its last\n * deps + result. This enables getter result stability across snapshot\n * boundaries: if `this.items` hasn't changed between snapshots (same\n * reference via structural sharing), the getter returns the same result.\n */\nconst crossSnapMemo = new WeakMap<\n object,\n Map<string | symbol, GetterMemoEntry>\n>();\n\n/**\n * Run a getter against the snapshot with dependency tracking.\n *\n * Creates a lightweight Proxy that intercepts `this.prop` reads and records\n * which properties (and their values/references) the getter accessed. The\n * Proxy delegates to the real frozen snapshot for actual values, so getters\n * that read other getters trigger the memoized getter chain correctly.\n */\nfunction computeWithTracking(\n snap: object,\n getterFn: () => unknown,\n): {deps: GetterDep[]; result: unknown} {\n const deps: GetterDep[] = [];\n const readProps = new Set<string | symbol>();\n\n // Use an empty non-frozen target — we delegate everything to `snap`.\n const handler: ProxyHandler<object> = {\n get(_dummyTarget, prop, _receiver) {\n // Delegate to the real snapshot (receiver = snap so installed getters\n // run with `this = snap`, triggering their own memoization chain).\n const value = Reflect.get(snap, prop, snap);\n if (!readProps.has(prop)) {\n readProps.add(prop);\n deps.push({prop, value});\n }\n return value;\n },\n };\n\n const tracked = new Proxy({}, handler);\n const result = getterFn.call(tracked);\n return {deps, result};\n}\n\n/**\n * Check if all previously recorded deps still hold on the current snapshot.\n * For data properties this is a reference comparison (structural sharing\n * guarantees stable refs for unchanged sub-trees). For getter properties\n * this invokes the getter (which is itself memoized), then compares.\n */\nfunction areMemoedDepsValid(currentSnap: object, deps: GetterDep[]): boolean {\n for (const dep of deps) {\n const currentValue = Reflect.get(currentSnap, dep.prop, currentSnap);\n if (!Object.is(currentValue, dep.value)) return false;\n }\n return true;\n}\n\n/**\n * Evaluate a snapshot getter with two layers of caching:\n *\n * 1. **Per-snapshot cache** — same getter on the same frozen snapshot always\n * returns the same reference.\n * 2. **Cross-snapshot memo** — if the properties the getter read last time are\n * structurally the same (reference equality via structural sharing), the\n * previous result is returned without re-running the getter body.\n */\nfunction evaluateSnapshotGetter(\n currentSnap: object,\n target: object,\n key: string | symbol,\n getterFn: () => unknown,\n): unknown {\n // ── Per-snapshot fast path ──\n const perSnapCache = snapshotGetterCache.get(currentSnap);\n if (perSnapCache?.has(key)) return perSnapCache.get(key);\n\n // ── Cross-snapshot memo ──\n let memoMap = crossSnapMemo.get(target);\n const prev = memoMap?.get(key);\n\n let result: unknown;\n\n if (prev && areMemoedDepsValid(currentSnap, prev.deps)) {\n // Dependencies unchanged → reuse previous result.\n result = prev.result;\n } else {\n // Compute fresh with dep tracking.\n const computation = computeWithTracking(currentSnap, getterFn);\n result = computation.result;\n\n // Save cross-snapshot memo.\n if (!memoMap) {\n memoMap = new Map();\n crossSnapMemo.set(target, memoMap);\n }\n memoMap.set(key, {deps: computation.deps, result});\n }\n\n // Save per-snapshot cache.\n let cache = snapshotGetterCache.get(currentSnap);\n if (!cache) {\n cache = new Map();\n snapshotGetterCache.set(currentSnap, cache);\n }\n cache.set(key, result);\n\n return result;\n}\n\n// ── Internal helpers ──────────────────────────────────────────────────────────\n\n/**\n * Resolve a single property value into its snapshot equivalent.\n *\n * - If the key has a tracked child internal → recurse (version-cached, structural sharing).\n * - If the value is a nested plain object/array without tracking → deep-clone & freeze (cached by identity).\n * - Otherwise → return the value as-is (primitive, Date, Map, function, etc.).\n */\nfunction snapshotValue(\n value: unknown,\n parentInternal: StoreInternal,\n key: string | symbol,\n): unknown {\n const childInternal = parentInternal.childInternals.get(key);\n if (childInternal) {\n return createSnapshotRecursive(childInternal.target, childInternal);\n }\n if (canProxy(value)) {\n return deepFreezeClone(value as object);\n }\n return value;\n}\n\n/**\n * Deep-clone and freeze a plain object or array that is NOT tracked by a proxy.\n * Cached by raw object identity for structural sharing across snapshots.\n */\nfunction deepFreezeClone(value: object): object {\n const cached = untrackedCache.get(value);\n if (cached) return cached;\n\n let clone: Record<string | symbol, unknown> | unknown[];\n\n if (Array.isArray(value)) {\n clone = [];\n for (let i = 0; i < value.length; i++) {\n const item = value[i];\n (clone as unknown[])[i] = canProxy(item)\n ? deepFreezeClone(item as object)\n : item;\n }\n } else {\n clone = Object.create(Object.getPrototypeOf(value));\n for (const key of Reflect.ownKeys(value)) {\n const desc = Object.getOwnPropertyDescriptor(value, key);\n if (!desc || !('value' in desc)) continue;\n const item = desc.value;\n (clone as Record<string | symbol, unknown>)[key] = canProxy(item)\n ? deepFreezeClone(item as object)\n : item;\n }\n }\n\n Object.freeze(clone);\n untrackedCache.set(value, clone);\n return clone;\n}\n\n/**\n * Collect all getter descriptors from the prototype chain of `target`.\n * Returns an array of [propertyName, getterFunction] pairs.\n *\n * Only includes getters defined on the prototype (class getters), not on the\n * instance itself. When a getter is overridden in a subclass, the most-derived\n * version wins (we walk from the instance's direct prototype upward and skip\n * keys already seen).\n */\nfunction collectGetters(\n target: object,\n): Array<[string | symbol, () => unknown]> {\n const getters: Array<[string | symbol, () => unknown]> = [];\n const seen = new Set<string | symbol>();\n let proto: object | null = Object.getPrototypeOf(target);\n while (proto && proto !== Object.prototype) {\n for (const key of Reflect.ownKeys(proto)) {\n if (key === 'constructor') continue;\n if (seen.has(key)) continue; // most-derived version already collected\n const desc = Object.getOwnPropertyDescriptor(proto, key);\n if (desc?.get) {\n getters.push([key, desc.get]);\n seen.add(key);\n }\n }\n proto = Object.getPrototypeOf(proto);\n }\n return getters;\n}\n\n/**\n * Install lazy-memoizing getters on a snapshot object.\n *\n * Each getter uses cross-snapshot memoization:\n * - Tracks which `this` properties the getter reads on first evaluation.\n * - On subsequent snapshots, if those properties are structurally the same\n * (thanks to structural sharing), the previous result is returned.\n * - Within the same snapshot, repeated accesses always return the same ref.\n */\nfunction installMemoizedGetters(\n snap: Record<string | symbol, unknown>,\n target: object,\n): void {\n const getters = collectGetters(target);\n for (const [key, getterFn] of getters) {\n Object.defineProperty(snap, key, {\n get() {\n return evaluateSnapshotGetter(this as object, target, key, getterFn);\n },\n enumerable: true,\n configurable: true, // required so Object.freeze can make it non-configurable\n });\n }\n}\n\n/**\n * Recursively creates a frozen snapshot from a tracked (proxied) sub-tree.\n *\n * Each node checks its version-stamped cache first (O(1) hit). On a miss,\n * it builds a new frozen object by recursing into child internals (tracked\n * sub-trees) and deep-cloning untracked nested objects. Unchanged children\n * return the same cached reference, achieving structural sharing -- the key\n * to efficient `Object.is` equality in selectors.\n *\n * For class instances, the prototype chain is preserved and class getters\n * are installed as lazy-memoizing accessors via `installMemoizedGetters`.\n */\nfunction createSnapshotRecursive<T extends object>(\n target: T,\n internal: StoreInternal,\n): T {\n // Cache hit: version unchanged → return the same frozen snapshot reference.\n const cached = snapCache.get(target);\n if (cached && cached[0] === internal.version) {\n return cached[1] as T;\n }\n\n let snap: Record<string | symbol, unknown> | unknown[];\n\n if (Array.isArray(target)) {\n snap = [];\n for (let i = 0; i < target.length; i++) {\n (snap as unknown[])[i] = snapshotValue(target[i], internal, String(i));\n }\n } else {\n // Preserve the prototype chain and install memoized getters on the snapshot.\n snap = Object.create(Object.getPrototypeOf(target));\n for (const key of Reflect.ownKeys(target)) {\n // Skip prototype getters — they re-evaluate via the preserved prototype.\n if (findGetterDescriptor(target, key)?.get) continue;\n\n const desc = Object.getOwnPropertyDescriptor(target, key);\n if (!desc || !('value' in desc)) continue;\n\n (snap as Record<string | symbol, unknown>)[key] = snapshotValue(\n desc.value,\n internal,\n key,\n );\n }\n\n // Install lazy-memoizing getters with cross-snapshot caching.\n installMemoizedGetters(snap as Record<string | symbol, unknown>, target);\n }\n\n Object.freeze(snap);\n // Cache AFTER populating + freezing. The reference is stable.\n snapCache.set(target, [internal.version, snap]);\n return snap as T;\n}\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\n/**\n * Creates an immutable, deeply-frozen snapshot of the store proxy's current state.\n *\n * **Structural sharing:** unchanged sub-trees reuse the previous snapshot's\n * object reference, so `===` comparison can cheaply detect changes.\n *\n * **Version-cached:** calling `snapshot()` multiple times without intervening\n * mutations returns the identical snapshot object (O(1) cache hit).\n *\n * **Getters:** class getters are automatically memoized — they compute once\n * per snapshot and their results are stable across snapshots when dependencies\n * haven't changed (cross-snapshot memoization).\n *\n * @param proxyStore - A reactive proxy created by `store()`.\n * @returns A deeply frozen plain-JS object (Snapshot<T>).\n */\nexport function snapshot<T extends object>(proxyStore: T): Snapshot<T> {\n const internal = getInternal(proxyStore);\n return createSnapshotRecursive(internal.target, internal) as Snapshot<T>;\n}\n"],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ //#region src/types.d.ts
2
+ /**
3
+ * Primitive types that should not be proxied or snapshot-cloned.
4
+ * Used by `Snapshot<T>` to identify leaf types that pass through unchanged.
5
+ */
6
+ type Primitive = string | number | boolean | null | undefined | symbol | bigint;
7
+ /**
8
+ * Function type shorthand.
9
+ * Functions are treated as snapshot leaves -- they pass through unchanged
10
+ * because freezing or cloning them would break callable references.
11
+ */
12
+ type AnyFunction = (...args: unknown[]) => unknown;
13
+ /**
14
+ * Types that `snapshot()` returns as-is (no deep clone or freeze).
15
+ * These are either already immutable-ish (Date, RegExp), use internal slots
16
+ * that proxies can't intercept (Map, Set, WeakMap, WeakSet), or can't be
17
+ * meaningfully frozen (functions, Promises, Errors).
18
+ */
19
+ type SnapshotLeaf = Primitive | AnyFunction | Date | RegExp | Error | Map<unknown, unknown> | Set<unknown> | WeakMap<object, unknown> | WeakSet<object> | Promise<unknown>;
20
+ /**
21
+ * Recursively converts an object type to a deeply readonly version.
22
+ * Leaf types (primitives, Date, Map, etc.) pass through unchanged.
23
+ */
24
+ type Snapshot<T> = T extends SnapshotLeaf ? T : T extends Array<infer U> ? ReadonlyArray<Snapshot<U>> : T extends object ? { readonly [K in keyof T]: Snapshot<T[K]> } : T;
25
+ //#endregion
26
+ export { Snapshot as t };
27
+ //# sourceMappingURL=types-B6RZUB86.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-B6RZUB86.d.cts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;KAIK,SAAA;;AAAS;;;;KAOT,WAAA,OAAkB,IAAA;AAAI;;;;;;AAAA,KAQtB,YAAA,GACD,SAAA,GACA,WAAA,GACA,IAAA,GACA,MAAA,GACA,KAAA,GACA,GAAA,qBACA,GAAA,YACA,OAAA,oBACA,OAAA,WACA,OAAA;;;;;KAMQ,QAAA,MAAc,CAAA,SAAU,YAAA,GAChC,CAAA,GACA,CAAA,SAAU,KAAA,YACR,aAAA,CAAc,QAAA,CAAS,CAAA,KACvB,CAAA,yCACwB,CAAA,GAAI,QAAA,CAAS,CAAA,CAAE,CAAA,OACrC,CAAA"}
@@ -0,0 +1,27 @@
1
+ //#region src/types.d.ts
2
+ /**
3
+ * Primitive types that should not be proxied or snapshot-cloned.
4
+ * Used by `Snapshot<T>` to identify leaf types that pass through unchanged.
5
+ */
6
+ type Primitive = string | number | boolean | null | undefined | symbol | bigint;
7
+ /**
8
+ * Function type shorthand.
9
+ * Functions are treated as snapshot leaves -- they pass through unchanged
10
+ * because freezing or cloning them would break callable references.
11
+ */
12
+ type AnyFunction = (...args: unknown[]) => unknown;
13
+ /**
14
+ * Types that `snapshot()` returns as-is (no deep clone or freeze).
15
+ * These are either already immutable-ish (Date, RegExp), use internal slots
16
+ * that proxies can't intercept (Map, Set, WeakMap, WeakSet), or can't be
17
+ * meaningfully frozen (functions, Promises, Errors).
18
+ */
19
+ type SnapshotLeaf = Primitive | AnyFunction | Date | RegExp | Error | Map<unknown, unknown> | Set<unknown> | WeakMap<object, unknown> | WeakSet<object> | Promise<unknown>;
20
+ /**
21
+ * Recursively converts an object type to a deeply readonly version.
22
+ * Leaf types (primitives, Date, Map, etc.) pass through unchanged.
23
+ */
24
+ type Snapshot<T> = T extends SnapshotLeaf ? T : T extends Array<infer U> ? ReadonlyArray<Snapshot<U>> : T extends object ? { readonly [K in keyof T]: Snapshot<T[K]> } : T;
25
+ //#endregion
26
+ export { Snapshot as t };
27
+ //# sourceMappingURL=types-vWYkF3tH.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-vWYkF3tH.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;KAIK,SAAA;;AAAS;;;;KAOT,WAAA,OAAkB,IAAA;AAAI;;;;;;AAAA,KAQtB,YAAA,GACD,SAAA,GACA,WAAA,GACA,IAAA,GACA,MAAA,GACA,KAAA,GACA,GAAA,qBACA,GAAA,YACA,OAAA,oBACA,OAAA,WACA,OAAA;;;;;KAMQ,QAAA,MAAc,CAAA,SAAU,YAAA,GAChC,CAAA,GACA,CAAA,SAAU,KAAA,YACR,aAAA,CAAc,QAAA,CAAS,CAAA,KACvB,CAAA,yCACwB,CAAA,GAAI,QAAA,CAAS,CAAA,CAAE,CAAA,OACrC,CAAA"}