diff --git a/.changeset/silent-peas-accept.md b/.changeset/silent-peas-accept.md new file mode 100644 index 0000000..a69e28a --- /dev/null +++ b/.changeset/silent-peas-accept.md @@ -0,0 +1,6 @@ +--- +"@codebelt/classy-store": patch +--- + +restructure internal folder hierarchy and module imports + diff --git a/biome.json b/biome.json index f3a9c15..9585da7 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.15/schema.json", + "$schema": "https://biomejs.dev/schemas/2.4.0/schema.json", "vcs": { "enabled": true, "clientKind": "git", diff --git a/examples/rendering/src/AsyncDemo.tsx b/examples/rendering/src/AsyncDemo.tsx index cc3c0f0..e2b9076 100644 --- a/examples/rendering/src/AsyncDemo.tsx +++ b/examples/rendering/src/AsyncDemo.tsx @@ -1,4 +1,4 @@ -import {useStore} from '@codebelt/classy-store'; +import {useStore} from '@codebelt/classy-store/react'; import {Panel} from './Panel'; import {RenderBadge} from './RenderBadge'; import {postStore} from './stores'; diff --git a/examples/rendering/src/CollectionsDemo.tsx b/examples/rendering/src/CollectionsDemo.tsx index 1ba61be..232368e 100644 --- a/examples/rendering/src/CollectionsDemo.tsx +++ b/examples/rendering/src/CollectionsDemo.tsx @@ -1,4 +1,4 @@ -import {useStore} from '@codebelt/classy-store'; +import {useStore} from '@codebelt/classy-store/react'; import {Panel} from './Panel'; import {RenderBadge} from './RenderBadge'; import {collectionStore} from './stores'; diff --git a/examples/rendering/src/KitchenSinkPersistDemo.tsx b/examples/rendering/src/KitchenSinkPersistDemo.tsx index 01c29ec..8eaba17 100644 --- a/examples/rendering/src/KitchenSinkPersistDemo.tsx +++ b/examples/rendering/src/KitchenSinkPersistDemo.tsx @@ -1,4 +1,4 @@ -import {useStore} from '@codebelt/classy-store'; +import {useStore} from '@codebelt/classy-store/react'; import {useEffect, useState} from 'react'; import {Panel} from './Panel'; import {kitchenSinkHandle, kitchenSinkStore} from './persistStores'; diff --git a/examples/rendering/src/ReactiveFundamentalsDemo.tsx b/examples/rendering/src/ReactiveFundamentalsDemo.tsx index 68a36f7..3bb822f 100644 --- a/examples/rendering/src/ReactiveFundamentalsDemo.tsx +++ b/examples/rendering/src/ReactiveFundamentalsDemo.tsx @@ -1,4 +1,5 @@ -import {store, useStore} from '@codebelt/classy-store'; +import {store} from '@codebelt/classy-store'; +import {useStore} from '@codebelt/classy-store/react'; import {useState} from 'react'; import {Panel} from './Panel'; import {RenderBadge} from './RenderBadge'; diff --git a/examples/rendering/src/SimplePersistDemo.tsx b/examples/rendering/src/SimplePersistDemo.tsx index 8b8e838..71731c0 100644 --- a/examples/rendering/src/SimplePersistDemo.tsx +++ b/examples/rendering/src/SimplePersistDemo.tsx @@ -1,4 +1,4 @@ -import {useStore} from '@codebelt/classy-store'; +import {useStore} from '@codebelt/classy-store/react'; import {useState} from 'react'; import {Panel} from './Panel'; import {preferencesHandle, preferencesStore} from './persistStores'; diff --git a/examples/rendering/src/StructuralSharingDemo.tsx b/examples/rendering/src/StructuralSharingDemo.tsx index 7522b31..1d282d5 100644 --- a/examples/rendering/src/StructuralSharingDemo.tsx +++ b/examples/rendering/src/StructuralSharingDemo.tsx @@ -1,4 +1,5 @@ -import {snapshot, subscribe, useStore} from '@codebelt/classy-store'; +import {snapshot, subscribe} from '@codebelt/classy-store'; +import {useStore} from '@codebelt/classy-store/react'; import {useEffect, useRef, useState} from 'react'; import {Panel} from './Panel'; import {documentStore} from './stores'; diff --git a/package.json b/package.json index 1ac0a6f..d14249a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@codebelt/classy-store", "version": "0.0.1", - "description": "Class-based reactive state management for React — ES6 Proxy + immutable snapshots + useSyncExternalStore", + "description": "Class-based reactive state management — ES6 Proxy + immutable snapshots", "type": "module", "main": "./dist/index.cjs", "module": "./dist/index.mjs", @@ -17,6 +17,16 @@ "default": "./dist/index.cjs" } }, + "./react": { + "import": { + "types": "./dist/react/react.d.mts", + "default": "./dist/react/react.mjs" + }, + "require": { + "types": "./dist/react/react.d.cts", + "default": "./dist/react/react.cjs" + } + }, "./utils": { "import": { "types": "./dist/utils/index.d.mts", @@ -66,6 +76,11 @@ "peerDependencies": { "react": ">=18.0.0" }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + }, "devDependencies": { "@biomejs/biome": "2.4.0", "@changesets/changelog-github": "0.5.2", @@ -90,5 +105,6 @@ "react", "hooks" ], - "license": "MIT" + "license": "MIT", + "sideEffects": false } diff --git a/src/collections.test.ts b/src/collections/collections.test.ts similarity index 97% rename from src/collections.test.ts rename to src/collections/collections.test.ts index 62aeb46..35a6ebd 100644 --- a/src/collections.test.ts +++ b/src/collections/collections.test.ts @@ -1,5 +1,7 @@ import {describe, expect, test} from 'bun:test'; -import {reactiveMap, reactiveSet, snapshot, store, subscribe} from './index'; +import {store, subscribe} from '../core/core'; +import {snapshot} from '../snapshot/snapshot'; +import {reactiveMap, reactiveSet} from './collections'; // ── helpers ─────────────────────────────────────────────────────────────────── diff --git a/src/collections.ts b/src/collections/collections.ts similarity index 99% rename from src/collections.ts rename to src/collections/collections.ts index ad7d46b..091b083 100644 --- a/src/collections.ts +++ b/src/collections/collections.ts @@ -1,4 +1,4 @@ -import {PROXYABLE} from './utils'; +import {PROXYABLE} from '../utils/internal/internal'; // ── ReactiveMap ─────────────────────────────────────────────────────────────── diff --git a/src/computed.test.tsx b/src/core/computed.test.tsx similarity index 99% rename from src/computed.test.tsx rename to src/core/computed.test.tsx index 6541d30..3c63158 100644 --- a/src/computed.test.tsx +++ b/src/core/computed.test.tsx @@ -1,9 +1,9 @@ import {afterEach, describe, expect, it, mock} from 'bun:test'; import {act, type ReactNode} from 'react'; import {createRoot} from 'react-dom/client'; +import {useStore} from '../react/react'; +import {snapshot} from '../snapshot/snapshot'; import {store} from './core'; -import {snapshot} from './snapshot'; -import {useStore} from './useStore'; /** Helper: flush the queueMicrotask-based batching. */ const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); diff --git a/src/core.test.ts b/src/core/core.test.ts similarity index 100% rename from src/core.test.ts rename to src/core/core.test.ts diff --git a/src/core.ts b/src/core/core.ts similarity index 98% rename from src/core.ts rename to src/core/core.ts index cb56bcc..bb08983 100644 --- a/src/core.ts +++ b/src/core/core.ts @@ -1,5 +1,5 @@ -import type {DepEntry, StoreInternal} from './types'; -import {canProxy, findGetterDescriptor} from './utils'; +import type {DepEntry, StoreInternal} from '../types'; +import {canProxy, findGetterDescriptor} from '../utils/internal/internal'; // ── Global state ────────────────────────────────────────────────────────────── diff --git a/src/index.ts b/src/index.ts index e07d479..b9cef8f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,21 +1,14 @@ /** - * @codebelt/classy-store -- Class-based reactive state management for React. + * @codebelt/classy-store -- Class-based reactive state management (Core/Vanilla). * - * Public API: - * - `store(instance)` -- wrap a class instance in a reactive proxy - * - `useStore(store, selector?)` -- React hook (selector or auto-tracked mode) - * - `snapshot(store)` -- create an immutable frozen snapshot - * - `subscribe(store, callback)` -- low-level change subscription - * - `getVersion(store)` -- current version number (for debugging/caching) - * - `shallowEqual(a, b)` -- shallow comparison helper for custom equality - * - `reactiveMap(initial?)` / `reactiveSet(initial?)` -- proxy-compatible collections + * This entry point includes only the core logic. + * For React usage, import from `@codebelt/classy-store/react`. * * @module @codebelt/classy-store */ -export type {ReactiveMap, ReactiveSet} from './collections'; -export {reactiveMap, reactiveSet} from './collections'; -export {getVersion, store, subscribe} from './core'; -export {snapshot} from './snapshot'; +export type {ReactiveMap, ReactiveSet} from './collections/collections'; +export {reactiveMap, reactiveSet} from './collections/collections'; +export {getVersion, store, subscribe} from './core/core'; +export {snapshot} from './snapshot/snapshot'; export type {Snapshot} from './types'; -export {useStore} from './useStore'; -export {shallowEqual} from './utils'; +export {shallowEqual} from './utils/equality/equality'; diff --git a/src/useStore.behavior.test.tsx b/src/react/react.behavior.test.tsx similarity index 99% rename from src/useStore.behavior.test.tsx rename to src/react/react.behavior.test.tsx index 13a1c71..7359ec8 100644 --- a/src/useStore.behavior.test.tsx +++ b/src/react/react.behavior.test.tsx @@ -1,9 +1,9 @@ import {afterEach, describe, expect, it, mock} from 'bun:test'; import {act, type ReactNode} from 'react'; import {createRoot, type Root} from 'react-dom/client'; -import {store} from './core'; -import {useStore} from './useStore'; -import {shallowEqual} from './utils'; +import {store} from '../core/core'; +import {shallowEqual} from '../utils/equality/equality'; +import {useStore} from './react'; // ── Test harness ──────────────────────────────────────────────────────────── diff --git a/src/useStore.test.tsx b/src/react/react.test.tsx similarity index 99% rename from src/useStore.test.tsx rename to src/react/react.test.tsx index b8774f2..e966d04 100644 --- a/src/useStore.test.tsx +++ b/src/react/react.test.tsx @@ -1,8 +1,8 @@ import {afterEach, describe, expect, it, mock} from 'bun:test'; import {act, type ReactNode} from 'react'; import {createRoot} from 'react-dom/client'; -import {store} from './core'; -import {useStore} from './useStore'; +import {store} from '../core/core'; +import {useStore} from './react'; // ── Test harness ──────────────────────────────────────────────────────────── diff --git a/src/useStore.ts b/src/react/react.ts similarity index 97% rename from src/useStore.ts rename to src/react/react.ts index 023d89e..8905c2b 100644 --- a/src/useStore.ts +++ b/src/react/react.ts @@ -1,8 +1,8 @@ import {createProxy, isChanged} from 'proxy-compare'; import {useCallback, useRef, useSyncExternalStore} from 'react'; -import {subscribe as coreSubscribe, getInternal} from './core'; -import {snapshot} from './snapshot'; -import type {Snapshot} from './types'; +import {subscribe as coreSubscribe, getInternal} from '../core/core'; +import {snapshot} from '../snapshot/snapshot'; +import type {Snapshot} from '../types'; // ── Overloads ───────────────────────────────────────────────────────────────── diff --git a/src/snapshot.test.ts b/src/snapshot/snapshot.test.ts similarity index 99% rename from src/snapshot.test.ts rename to src/snapshot/snapshot.test.ts index 75dd705..450c3c1 100644 --- a/src/snapshot.test.ts +++ b/src/snapshot/snapshot.test.ts @@ -1,5 +1,5 @@ import {describe, expect, it} from 'bun:test'; -import {store} from './core'; +import {store} from '../core/core'; import {snapshot} from './snapshot'; /** Helper: flush the queueMicrotask-based batching. */ diff --git a/src/snapshot.ts b/src/snapshot/snapshot.ts similarity index 98% rename from src/snapshot.ts rename to src/snapshot/snapshot.ts index 7396768..f1d5fd8 100644 --- a/src/snapshot.ts +++ b/src/snapshot/snapshot.ts @@ -1,6 +1,6 @@ -import {getInternal} from './core'; -import type {Snapshot, StoreInternal} from './types'; -import {canProxy, findGetterDescriptor} from './utils'; +import {getInternal} from '../core/core'; +import type {Snapshot, StoreInternal} from '../types'; +import {canProxy, findGetterDescriptor} from '../utils/internal/internal'; // ── Caches ──────────────────────────────────────────────────────────────────── diff --git a/src/utils/equality/equality.test.ts b/src/utils/equality/equality.test.ts new file mode 100644 index 0000000..bf7b7ac --- /dev/null +++ b/src/utils/equality/equality.test.ts @@ -0,0 +1,82 @@ +import {describe, expect, it} from 'bun:test'; +import {shallowEqual} from './equality'; + +// ── shallowEqual ────────────────────────────────────────────────────────────── + +describe('shallowEqual', () => { + // ── Identity / reference ──────────────────────────────────────────────── + + it('returns true for identical references', () => { + const obj = {a: 1, b: 2}; + expect(shallowEqual(obj, obj)).toBe(true); + }); + + // ── Primitive equality (Object.is) ────────────────────────────────────── + + it('returns true for equal primitives', () => { + expect(shallowEqual(1, 1)).toBe(true); + expect(shallowEqual('hello', 'hello')).toBe(true); + expect(shallowEqual(true, true)).toBe(true); + expect(shallowEqual(null, null)).toBe(true); + expect(shallowEqual(undefined, undefined)).toBe(true); + }); + + it('returns false for different primitives', () => { + expect(shallowEqual(1, 2)).toBe(false); + expect(shallowEqual('a', 'b')).toBe(false); + expect(shallowEqual(true, false)).toBe(false); + }); + + it('returns false for null vs object', () => { + expect(shallowEqual(null, {a: 1})).toBe(false); + expect(shallowEqual({a: 1}, null)).toBe(false); + }); + + // ── Shallow object equality ───────────────────────────────────────────── + + it('returns true for shallow-equal plain objects', () => { + expect(shallowEqual({a: 1, b: 'x'}, {a: 1, b: 'x'})).toBe(true); + }); + + it('returns false for objects with different values', () => { + expect(shallowEqual({a: 1}, {a: 2})).toBe(false); + }); + + it('returns false for objects with different key counts', () => { + expect(shallowEqual({a: 1}, {a: 1, b: 2})).toBe(false); + }); + + it('returns false for objects with different keys', () => { + expect(shallowEqual({a: 1}, {b: 1})).toBe(false); + }); + + it('compares nested objects by reference only (not deep)', () => { + const inner1 = {x: 1}; + const inner2 = {x: 1}; // same shape but different reference + expect(shallowEqual({a: inner1}, {a: inner2})).toBe(false); + }); + + // ── Array equality ────────────────────────────────────────────────────── + + it('returns true for shallow-equal arrays', () => { + expect(shallowEqual([1, 'a', true], [1, 'a', true])).toBe(true); + }); + + it('returns false for arrays with different lengths', () => { + expect(shallowEqual([1, 2], [1, 2, 3])).toBe(false); + }); + + it('returns false for arrays with different elements', () => { + expect(shallowEqual([1, 2, 3], [1, 2, 4])).toBe(false); + }); + + // ── Object.is edge cases ─────────────────────────────────────────────── + + it('treats NaN as equal to NaN (Object.is semantics)', () => { + expect(shallowEqual(Number.NaN, Number.NaN)).toBe(true); + }); + + it('treats +0 and -0 as not equal (Object.is semantics)', () => { + expect(shallowEqual(+0, -0)).toBe(false); + }); +}); diff --git a/src/utils/equality/equality.ts b/src/utils/equality/equality.ts new file mode 100644 index 0000000..7148c13 --- /dev/null +++ b/src/utils/equality/equality.ts @@ -0,0 +1,44 @@ +/** + * Shallow-equal comparison for objects and arrays. + * Useful as a custom `isEqual` for `useStore` selectors that return objects/arrays. + * + * - Primitives compared with `Object.is`. + * - Arrays: length + element-wise `Object.is`. + * - Objects: key count + value-wise `Object.is`. + */ +export function shallowEqual(a: T, b: T): boolean { + if (Object.is(a, b)) return true; + if ( + typeof a !== 'object' || + a === null || + typeof b !== 'object' || + b === null + ) { + return false; + } + + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + if (!Object.is(a[i], b[i])) return false; + } + return true; + } + + const keysA = Object.keys(a); + const keysB = Object.keys(b); + if (keysA.length !== keysB.length) return false; + + for (const key of keysA) { + if ( + !Object.hasOwn(b, key) || + !Object.is( + (a as Record)[key], + (b as Record)[key], + ) + ) { + return false; + } + } + return true; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 7019a55..84a493d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -12,5 +12,5 @@ export type { PersistOptions, PropertyTransform, StorageAdapter, -} from './persist'; -export {persist} from './persist'; +} from './persist/persist'; +export {persist} from './persist/persist'; diff --git a/src/utils.test.ts b/src/utils/internal/internal.test.ts similarity index 58% rename from src/utils.test.ts rename to src/utils/internal/internal.test.ts index dc81b4d..ee1a99f 100644 --- a/src/utils.test.ts +++ b/src/utils/internal/internal.test.ts @@ -4,8 +4,7 @@ import { findGetterDescriptor, isPlainObject, PROXYABLE, - shallowEqual, -} from './utils'; +} from './internal'; // ── isPlainObject ───────────────────────────────────────────────────────────── @@ -152,83 +151,3 @@ describe('findGetterDescriptor', () => { expect(findGetterDescriptor(obj, 'x')).toBeUndefined(); }); }); - -// ── shallowEqual ────────────────────────────────────────────────────────────── - -describe('shallowEqual', () => { - // ── Identity / reference ──────────────────────────────────────────────── - - it('returns true for identical references', () => { - const obj = {a: 1, b: 2}; - expect(shallowEqual(obj, obj)).toBe(true); - }); - - // ── Primitive equality (Object.is) ────────────────────────────────────── - - it('returns true for equal primitives', () => { - expect(shallowEqual(1, 1)).toBe(true); - expect(shallowEqual('hello', 'hello')).toBe(true); - expect(shallowEqual(true, true)).toBe(true); - expect(shallowEqual(null, null)).toBe(true); - expect(shallowEqual(undefined, undefined)).toBe(true); - }); - - it('returns false for different primitives', () => { - expect(shallowEqual(1, 2)).toBe(false); - expect(shallowEqual('a', 'b')).toBe(false); - expect(shallowEqual(true, false)).toBe(false); - }); - - it('returns false for null vs object', () => { - expect(shallowEqual(null, {a: 1})).toBe(false); - expect(shallowEqual({a: 1}, null)).toBe(false); - }); - - // ── Shallow object equality ───────────────────────────────────────────── - - it('returns true for shallow-equal plain objects', () => { - expect(shallowEqual({a: 1, b: 'x'}, {a: 1, b: 'x'})).toBe(true); - }); - - it('returns false for objects with different values', () => { - expect(shallowEqual({a: 1}, {a: 2})).toBe(false); - }); - - it('returns false for objects with different key counts', () => { - expect(shallowEqual({a: 1}, {a: 1, b: 2})).toBe(false); - }); - - it('returns false for objects with different keys', () => { - expect(shallowEqual({a: 1}, {b: 1})).toBe(false); - }); - - it('compares nested objects by reference only (not deep)', () => { - const inner1 = {x: 1}; - const inner2 = {x: 1}; // same shape but different reference - expect(shallowEqual({a: inner1}, {a: inner2})).toBe(false); - }); - - // ── Array equality ────────────────────────────────────────────────────── - - it('returns true for shallow-equal arrays', () => { - expect(shallowEqual([1, 'a', true], [1, 'a', true])).toBe(true); - }); - - it('returns false for arrays with different lengths', () => { - expect(shallowEqual([1, 2], [1, 2, 3])).toBe(false); - }); - - it('returns false for arrays with different elements', () => { - expect(shallowEqual([1, 2, 3], [1, 2, 4])).toBe(false); - }); - - // ── Object.is edge cases ─────────────────────────────────────────────── - - it('treats NaN as equal to NaN (Object.is semantics)', () => { - expect(shallowEqual(Number.NaN, Number.NaN)).toBe(true); - }); - - it('treats +0 and -0 as not equal (Object.is semantics)', () => { - expect(shallowEqual(+0, -0)).toBe(false); - }); -}); diff --git a/src/utils.ts b/src/utils/internal/internal.ts similarity index 68% rename from src/utils.ts rename to src/utils/internal/internal.ts index 9e4da40..1b37494 100644 --- a/src/utils.ts +++ b/src/utils/internal/internal.ts @@ -58,48 +58,3 @@ export function findGetterDescriptor( } return undefined; } - -/** - * Shallow-equal comparison for objects and arrays. - * Useful as a custom `isEqual` for `useStore` selectors that return objects/arrays. - * - * - Primitives compared with `Object.is`. - * - Arrays: length + element-wise `Object.is`. - * - Objects: key count + value-wise `Object.is`. - */ -export function shallowEqual(a: T, b: T): boolean { - if (Object.is(a, b)) return true; - if ( - typeof a !== 'object' || - a === null || - typeof b !== 'object' || - b === null - ) { - return false; - } - - if (Array.isArray(a) && Array.isArray(b)) { - if (a.length !== b.length) return false; - for (let i = 0; i < a.length; i++) { - if (!Object.is(a[i], b[i])) return false; - } - return true; - } - - const keysA = Object.keys(a); - const keysB = Object.keys(b); - if (keysA.length !== keysB.length) return false; - - for (const key of keysA) { - if ( - !Object.hasOwn(b, key) || - !Object.is( - (a as Record)[key], - (b as Record)[key], - ) - ) { - return false; - } - } - return true; -} diff --git a/src/utils/persist.test.ts b/src/utils/persist/persist.test.ts similarity index 99% rename from src/utils/persist.test.ts rename to src/utils/persist/persist.test.ts index 2ad6bb4..846d15b 100644 --- a/src/utils/persist.test.ts +++ b/src/utils/persist/persist.test.ts @@ -1,6 +1,7 @@ import {describe, expect, it, mock} from 'bun:test'; -import type {ReactiveMap} from '../collections'; -import {reactiveMap, store} from '../index'; +import type {ReactiveMap} from '../../collections/collections'; +import {reactiveMap} from '../../collections/collections'; +import {store} from '../../core/core'; import type {StorageAdapter} from './persist'; import {persist} from './persist'; diff --git a/src/utils/persist.ts b/src/utils/persist/persist.ts similarity index 99% rename from src/utils/persist.ts rename to src/utils/persist/persist.ts index 7a53c4d..2fb5c0e 100644 --- a/src/utils/persist.ts +++ b/src/utils/persist/persist.ts @@ -1,6 +1,6 @@ -import {subscribe} from '../core'; -import {snapshot} from '../snapshot'; -import {findGetterDescriptor} from '../utils'; +import {subscribe} from '../../core/core'; +import {snapshot} from '../../snapshot/snapshot'; +import {findGetterDescriptor} from '../internal/internal'; // ── Types ──────────────────────────────────────────────────────────────────── diff --git a/tsdown.config.ts b/tsdown.config.ts index bbade6e..a4dd446 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -1,7 +1,7 @@ import {defineConfig} from 'tsdown'; export default defineConfig({ - entry: ['src/index.ts', 'src/utils/index.ts'], + entry: ['src/index.ts', 'src/react/react.ts', 'src/utils/index.ts'], format: ['esm', 'cjs'], dts: true, sourcemap: true, diff --git a/website/docs/ARCHITECTURE.md b/website/docs/ARCHITECTURE.md index 6544f96..ff36566 100644 --- a/website/docs/ARCHITECTURE.md +++ b/website/docs/ARCHITECTURE.md @@ -66,23 +66,32 @@ flowchart TB packages/store/ ├── src/ │ ├── index.ts # Barrel export: store, useStore, snapshot, subscribe, getVersion, shallowEqual, Snapshot, reactiveMap, reactiveSet, ReactiveMap, ReactiveSet -│ ├── core.ts # Layer 1: Write Proxy — store(), subscribe(), getVersion() -│ ├── snapshot.ts # Layer 2: Immutable Snapshots — snapshot() -│ ├── useStore.ts # Layer 3: React Hook — useStore() -│ ├── types.ts # Shared types: Snapshot, StoreInternal, DepEntry, ComputedEntry -│ ├── utils.ts # Helpers: isPlainObject, canProxy, shallowEqual, findGetterDescriptor, PROXYABLE -│ ├── collections.ts # ReactiveMap and ReactiveSet implementations +│ ├── collections/ +│ │ ├── collections.ts # ReactiveMap and ReactiveSet implementations +│ │ └── collections.test.ts # tests: ReactiveMap, ReactiveSet, class store integration +│ ├── core/ +│ │ ├── core.ts # Layer 1: Write Proxy — store(), subscribe(), getVersion() +│ │ ├── core.test.ts # tests: mutations, batching, methods, getters, arrays +│ │ └── computed.test.tsx # tests: write proxy + snapshot memoization, useStore integration +│ ├── react/ +│ │ ├── react.ts # Layer 3: React Hook — useStore() +│ │ ├── react.test.tsx # tests: selector mode, auto-tracked mode, re-render control +│ │ └── react.behavior.test.tsx # tests: batching, set-then-revert, async, multi-component, unmount +│ ├── snapshot/ +│ │ ├── snapshot.ts # Layer 2: Immutable Snapshots — snapshot() +│ │ └── snapshot.test.ts # tests: freezing, caching, structural sharing, getters │ ├── utils/ │ │ ├── index.ts # Barrel export for @codebelt/classy-store/utils: persist -│ │ ├── persist.ts # persist() utility: storage, transforms, versioning, cross-tab sync -│ │ └── persist.test.ts # tests: round-trip, transforms, debounce, migration, merge, SSR, cross-tab -│ ├── core.test.ts # tests: mutations, batching, methods, getters, arrays -│ ├── snapshot.test.ts # tests: freezing, caching, structural sharing, getters -│ ├── computed.test.tsx # tests: write proxy + snapshot memoization, useStore integration -│ ├── useStore.test.tsx # tests: selector mode, auto-tracked mode, re-render control -│ ├── useStore.behavior.test.tsx # tests: batching, set-then-revert, async, multi-component, unmount -│ ├── utils.test.ts # Utility function tests: shallowEqual, isPlainObject, canProxy, findGetterDescriptor -│ └── collections.test.ts # tests: ReactiveMap, ReactiveSet, class store integration +│ │ ├── equality/ +│ │ │ ├── equality.ts # shallowEqual +│ │ │ └── equality.test.ts # tests for shallowEqual +│ │ ├── internal/ +│ │ │ ├── internal.ts # Internal helpers: isPlainObject, canProxy, findGetterDescriptor, PROXYABLE +│ │ │ └── internal.test.ts # tests for internal helpers +│ │ ├── persist/ +│ │ │ ├── persist.ts # persist() utility: storage, transforms, versioning, cross-tab sync +│ │ │ └── persist.test.ts # tests: round-trip, transforms, debounce, migration, merge, SSR, cross-tab +│ ├── types.ts # Shared types: Snapshot, StoreInternal, DepEntry, ComputedEntry ├── package.json ├── tsconfig.json ├── tsdown.config.ts diff --git a/website/docs/PERSIST_ARCHITECTURE.md b/website/docs/PERSIST_ARCHITECTURE.md index 22eb3f0..d191035 100644 --- a/website/docs/PERSIST_ARCHITECTURE.md +++ b/website/docs/PERSIST_ARCHITECTURE.md @@ -51,9 +51,10 @@ packages/store/ src/ index.ts # Existing barrel (unchanged) utils/ - index.ts # Barrel: export { persist } from './persist' - persist.ts # persist(), types, and all logic - persist.test.ts # 38 tests with mock storage adapters + index.ts # Barrel: export { persist } from './persist/persist' + persist/ + persist.ts # persist(), types, and all logic + persist.test.ts # 38 tests with mock storage adapters package.json # "./utils" export entry tsdown.config.ts # 'src/utils/index.ts' in entry array ```