@codebelt/classy-store 0.1.0 → 0.1.1

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.
Files changed (41) hide show
  1. package/dist/equality-5F2bPn7E.d.mts +13 -0
  2. package/dist/equality-5F2bPn7E.d.mts.map +1 -0
  3. package/dist/equality-BA46H9AL.mjs +27 -0
  4. package/dist/equality-BA46H9AL.mjs.map +1 -0
  5. package/dist/equality-C1s0kqxg.d.cts +13 -0
  6. package/dist/equality-C1s0kqxg.d.cts.map +1 -0
  7. package/dist/equality-Cz6riknL.cjs +33 -0
  8. package/dist/equality-Cz6riknL.cjs.map +1 -0
  9. package/dist/index.cjs +3 -27
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +1 -11
  12. package/dist/index.d.cts.map +1 -1
  13. package/dist/index.d.mts +2 -12
  14. package/dist/index.d.mts.map +1 -1
  15. package/dist/index.mjs +2 -26
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/react/react.cjs +30 -1
  18. package/dist/react/react.cjs.map +1 -1
  19. package/dist/react/react.d.cts +26 -1
  20. package/dist/react/react.d.cts.map +1 -1
  21. package/dist/react/react.d.mts +27 -2
  22. package/dist/react/react.d.mts.map +1 -1
  23. package/dist/react/react.mjs +31 -3
  24. package/dist/react/react.mjs.map +1 -1
  25. package/dist/{snapshot-P0QPV1ER.mjs → snapshot-COzEerMu.mjs} +27 -2
  26. package/dist/snapshot-COzEerMu.mjs.map +1 -0
  27. package/dist/{snapshot-BKVFJLuo.cjs → snapshot-CbVbxG7s.cjs} +27 -2
  28. package/dist/snapshot-CbVbxG7s.cjs.map +1 -0
  29. package/dist/{types-vWYkF3tH.d.mts → types-Cf8Fp7kA.d.mts} +1 -1
  30. package/dist/{types-vWYkF3tH.d.mts.map → types-Cf8Fp7kA.d.mts.map} +1 -1
  31. package/dist/utils/index.cjs +176 -5
  32. package/dist/utils/index.cjs.map +1 -1
  33. package/dist/utils/index.d.cts +89 -1
  34. package/dist/utils/index.d.cts.map +1 -1
  35. package/dist/utils/index.d.mts +89 -1
  36. package/dist/utils/index.d.mts.map +1 -1
  37. package/dist/utils/index.mjs +173 -6
  38. package/dist/utils/index.mjs.map +1 -1
  39. package/package.json +4 -3
  40. package/dist/snapshot-BKVFJLuo.cjs.map +0 -1
  41. package/dist/snapshot-P0QPV1ER.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["snapshot","findGetterDescriptor","subscribe"],"sources":["../../src/utils/persist/persist.ts"],"sourcesContent":["import {subscribe} from '../../core/core';\nimport {snapshot} from '../../snapshot/snapshot';\nimport {findGetterDescriptor} from '../internal/internal';\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\n/**\n * Storage adapter interface. Compatible with `localStorage`, `sessionStorage`,\n * `AsyncStorage`, `localForage`, or any custom implementation.\n *\n * Methods may return synchronously or asynchronously — `persist` handles both.\n */\nexport type StorageAdapter = {\n getItem: (name: string) => string | null | Promise<string | null>;\n setItem: (name: string, value: string) => void | Promise<void>;\n removeItem: (name: string) => void | Promise<void>;\n};\n\n/**\n * Describes a per-property serialization transform.\n *\n * Use this when a property's value is not JSON-serializable (Date, ReactiveMap, etc.)\n * and needs custom conversion to/from a storable format.\n */\nexport type PropertyTransform<T extends object> = {\n /** The property key on the store class. */\n key: keyof T;\n\n /** Transform the value BEFORE saving to storage (after snapshot, before JSON.stringify). */\n serialize: (value: T[keyof T]) => unknown;\n\n /** Transform the value AFTER loading from storage (after JSON.parse, before applying to store). */\n deserialize: (stored: unknown) => T[keyof T];\n};\n\n/**\n * Options for `persist()`.\n */\nexport type PersistOptions<T extends object> = {\n /** Unique storage key. Required. */\n name: string;\n\n /**\n * Storage adapter. Defaults to `globalThis.localStorage`.\n * Any object with getItem/setItem/removeItem (sync or async).\n * Works with: localStorage, sessionStorage, AsyncStorage, localForage, etc.\n */\n storage?: StorageAdapter;\n\n /**\n * Which properties to persist.\n * Each entry is either a plain key (string) or a transform descriptor.\n * Defaults to all own enumerable data properties.\n *\n * **Getters and methods are always excluded.** Class getters (e.g., `get remaining()`)\n * are computed/derived values — they are not source-of-truth state. They recompute\n * automatically from the persisted data properties when accessed. Persisting a getter\n * result would be redundant and could produce stale values on restore.\n */\n properties?: Array<keyof T | PropertyTransform<T>>;\n\n /**\n * Debounce writes to storage (milliseconds).\n * Multiple rapid mutations coalesce into one write.\n * Default: 0 (write after every batched mutation).\n */\n debounce?: number;\n\n /**\n * Schema version number. Stored alongside the data.\n * When the stored version differs from this value, `migrate` is called.\n * Default: 0.\n */\n version?: number;\n\n /**\n * Migration function. Called when the stored version does not match `version`.\n * Receives the raw parsed state and the old version number.\n * Must return the state in the current shape.\n */\n migrate?: (\n persistedState: Record<string, unknown>,\n oldVersion: number,\n ) => Record<string, unknown>;\n\n /**\n * How to merge persisted state with current store state during hydration.\n *\n * - `'shallow'` (default): persisted values overwrite current values one key at a time.\n * Properties not in storage keep their current value.\n * - `'replace'`: same behavior as `'shallow'` for flat stores — only stored keys are assigned.\n * For nested objects, the entire object is replaced rather than merged.\n * - Custom function: receives `(persistedState, currentState)` and returns merged state.\n * Enables deep merge or any custom logic.\n *\n * This matters when the store adds new properties that don't exist in old\n * persisted data. `'shallow'` preserves the new defaults.\n */\n merge?:\n | 'shallow'\n | 'replace'\n | ((\n persisted: Record<string, unknown>,\n current: Record<string, unknown>,\n ) => Record<string, unknown>);\n\n /**\n * If true, do NOT hydrate automatically on init.\n * You must call `handle.rehydrate()` manually (e.g., in a `useEffect` for SSR).\n * Default: false.\n */\n skipHydration?: boolean;\n\n /**\n * Sync state across browser tabs via the `window.storage` event.\n * When another tab writes to the same storage key, this tab automatically\n * re-hydrates from the new value.\n *\n * Only works with `localStorage` (storage events don't fire for sessionStorage\n * or async adapters).\n *\n * Default: `true` when storage is `localStorage`, `false` otherwise.\n */\n syncTabs?: boolean;\n\n /**\n * Time-to-live in milliseconds. After this duration, stored data is\n * considered expired and skipped during hydration. The TTL resets on\n * every write (active sessions stay fresh as long as mutations happen).\n */\n expireIn?: number;\n\n /**\n * When `true`, automatically remove the storage key if data is found\n * expired during hydration. Default: `false` (expired data is skipped\n * but left in storage).\n */\n clearOnExpire?: boolean;\n};\n\n/**\n * Handle returned by `persist()`. Provides control over the persist lifecycle.\n */\nexport type PersistHandle = {\n /** Stop persisting and clean up (unsubscribe + cancel pending debounce + remove storage event listener). */\n unsubscribe: () => void;\n\n /** Promise that resolves when initial hydration from storage is complete. */\n hydrated: Promise<void>;\n\n /** Whether hydration from storage has completed. */\n isHydrated: boolean;\n\n /** Manually trigger a write to storage (bypasses debounce). */\n save: () => Promise<void>;\n\n /** Clear this store's persisted data from storage. */\n clear: () => Promise<void>;\n\n /** Manually re-hydrate the store from storage. */\n rehydrate: () => Promise<void>;\n\n /** True if the last hydration found expired data (requires `expireIn`). */\n isExpired: boolean;\n};\n\n// ── Storage envelope ─────────────────────────────────────────────────────────\n\n/** Internal shape of the JSON stored in storage. */\ntype PersistEnvelope = {\n version: number;\n state: Record<string, unknown>;\n expiresAt?: number;\n};\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\n/** Check if a value is a PropertyTransform descriptor (has `key` + `serialize`). */\nfunction isTransform<T extends object>(\n entry: keyof T | PropertyTransform<T>,\n): entry is PropertyTransform<T> {\n return (\n typeof entry === 'object' &&\n entry !== null &&\n 'key' in entry &&\n 'serialize' in entry\n );\n}\n\n/**\n * Build the normalized list of property keys and their optional transforms.\n * If `properties` is not provided, defaults to all own enumerable data properties\n * of the store (excluding getters and methods).\n */\nfunction resolveProperties<T extends object>(\n proxyStore: T,\n properties?: Array<keyof T | PropertyTransform<T>>,\n): Array<{key: string; transform?: PropertyTransform<T>}> {\n if (properties) {\n return properties.map((entry) => {\n if (isTransform(entry)) {\n return {key: entry.key as string, transform: entry};\n }\n return {key: entry as string};\n });\n }\n\n // Default: all own enumerable keys that are not getters or methods.\n const snap = snapshot(proxyStore) as Record<string, unknown>;\n const result: Array<{key: string}> = [];\n for (const key of Object.keys(snap)) {\n // Skip getters (they live on the prototype, but snapshot installs them).\n // We check the original store's target for getter descriptors.\n if (findGetterDescriptor(Object.getPrototypeOf(proxyStore), key)?.get)\n continue;\n // Skip functions (methods).\n const value = (proxyStore as Record<string, unknown>)[key];\n if (typeof value === 'function') continue;\n result.push({key});\n }\n return result;\n}\n\n/**\n * Detect if the given storage adapter is `globalThis.localStorage`.\n */\nfunction isLocalStorage(storage: StorageAdapter): boolean {\n try {\n return (\n typeof globalThis !== 'undefined' &&\n typeof globalThis.localStorage !== 'undefined' &&\n storage === (globalThis.localStorage as unknown as StorageAdapter)\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Get the default storage adapter (`localStorage`), or `undefined` if unavailable.\n */\nfunction getDefaultStorage(): StorageAdapter | undefined {\n try {\n if (\n typeof globalThis !== 'undefined' &&\n typeof globalThis.localStorage !== 'undefined'\n ) {\n return globalThis.localStorage as unknown as StorageAdapter;\n }\n } catch {\n // SSR or restricted environment — no localStorage.\n }\n return undefined;\n}\n\n// ── Main implementation ──────────────────────────────────────────────────────\n\n/**\n * Persist store state to a storage adapter.\n *\n * Subscribes to store mutations and writes the selected properties to storage\n * (with optional per-property transforms, debouncing, and versioned envelopes).\n * On init (or manual rehydrate), reads from storage and applies the state back\n * to the store proxy.\n *\n * @param proxyStore - A reactive proxy created by `createClassyStore()`.\n * @param options - Persistence configuration.\n * @returns A handle with lifecycle controls (unsubscribe, save, clear, rehydrate, hydrated).\n */\nexport function persist<T extends object>(\n proxyStore: T,\n options: PersistOptions<T>,\n): PersistHandle {\n const {\n name,\n properties: propertiesOption,\n debounce: debounceMs = 0,\n version = 0,\n migrate,\n merge = 'shallow',\n skipHydration = false,\n syncTabs: syncTabsOption,\n expireIn,\n clearOnExpire = false,\n } = options;\n\n const maybeStorage = options.storage ?? getDefaultStorage();\n if (!maybeStorage) {\n throw new Error(\n '@codebelt/classy-store: persist() requires a storage adapter. ' +\n 'No localStorage found — provide a `storage` option.',\n );\n }\n const storage: StorageAdapter = maybeStorage;\n\n const resolvedProps = resolveProperties(proxyStore, propertiesOption);\n\n // Build a map of key → transform for fast lookup during save/restore.\n const transformMap = new Map<string, PropertyTransform<T>>();\n for (const prop of resolvedProps) {\n if (prop.transform) {\n transformMap.set(prop.key, prop.transform);\n }\n }\n\n const propKeys = resolvedProps.map((p) => p.key);\n\n // ── State ────────────────────────────────────────────────────────────────\n\n let disposed = false;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n let hydratedFlag = false;\n let expiredFlag = false;\n\n // Hydration promise + resolver.\n let resolveHydrated: () => void;\n let rejectHydrated: (error: unknown) => void;\n const hydratedPromise = new Promise<void>((resolve, reject) => {\n resolveHydrated = resolve;\n rejectHydrated = reject;\n });\n\n // ── Save logic ───────────────────────────────────────────────────────────\n\n /** Serialize the current store state into a JSON string (versioned envelope). */\n function serializeState(): string {\n const snap = snapshot(proxyStore) as Record<string, unknown>;\n const state: Record<string, unknown> = {};\n\n for (const key of propKeys) {\n let value = snap[key];\n const transform = transformMap.get(key);\n if (transform) {\n value = transform.serialize(value as T[keyof T]);\n }\n state[key] = value;\n }\n\n const envelope: PersistEnvelope = {version, state};\n if (expireIn != null) {\n envelope.expiresAt = Date.now() + expireIn;\n }\n return JSON.stringify(envelope);\n }\n\n /** Write the current state to storage. */\n async function writeToStorage(): Promise<void> {\n if (disposed) return;\n const json = serializeState();\n await storage.setItem(name, json);\n }\n\n /** Schedule a debounced write (or write immediately if debounce is 0). */\n function scheduleWrite(): void {\n if (disposed) return;\n\n if (debounceMs <= 0) {\n void writeToStorage();\n return;\n }\n\n if (debounceTimer !== null) {\n clearTimeout(debounceTimer);\n }\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n void writeToStorage();\n }, debounceMs);\n }\n\n // ── Restore logic ────────────────────────────────────────────────────────\n\n /**\n * Parse a raw JSON string from storage, apply migration and transforms,\n * and merge the result into the store proxy.\n */\n function applyPersistedState(raw: string): void {\n let envelope: PersistEnvelope;\n try {\n envelope = JSON.parse(raw) as PersistEnvelope;\n } catch {\n // Corrupted data — skip.\n return;\n }\n\n if (\n !envelope ||\n typeof envelope !== 'object' ||\n typeof envelope.state !== 'object'\n ) {\n return;\n }\n\n // Expiry check — skip hydration if data has expired.\n if (\n typeof envelope.expiresAt === 'number' &&\n Date.now() >= envelope.expiresAt\n ) {\n expiredFlag = true;\n if (clearOnExpire) void storage.removeItem(name);\n return;\n }\n\n let {state} = envelope;\n\n // Version migration.\n if (migrate && envelope.version !== version) {\n state = migrate(state, envelope.version);\n }\n\n // Per-property deserialize transforms.\n for (const key of Object.keys(state)) {\n const transform = transformMap.get(key);\n if (transform) {\n state[key] = transform.deserialize(state[key]);\n }\n }\n\n // Build current state for merge.\n const currentSnap = snapshot(proxyStore) as Record<string, unknown>;\n const currentState: Record<string, unknown> = {};\n for (const key of propKeys) {\n currentState[key] = currentSnap[key];\n }\n\n // Merge strategy.\n let merged: Record<string, unknown>;\n if (typeof merge === 'function') {\n merged = merge(state, currentState);\n } else {\n // Both 'shallow' and 'replace' assign persisted keys onto the store.\n // The difference is conceptual for nested objects, but at this level\n // both just assign the persisted value per key.\n merged = {...currentState, ...state};\n }\n\n // Apply to store proxy (goes through SET traps → reactivity).\n for (const key of propKeys) {\n if (key in merged) {\n (proxyStore as Record<string, unknown>)[key] = merged[key];\n }\n }\n }\n\n /** Read from storage and apply to the store. */\n async function hydrateFromStorage(): Promise<void> {\n const raw = await storage.getItem(name);\n if (raw !== null) {\n applyPersistedState(raw);\n }\n }\n\n // ── Cross-tab sync ───────────────────────────────────────────────────────\n\n const shouldSyncTabs =\n syncTabsOption !== undefined ? syncTabsOption : isLocalStorage(storage);\n\n /** Handler for `window.storage` events. */\n function onStorageEvent(event: StorageEvent): void {\n if (disposed) return;\n if (event.key !== name) return;\n if (event.newValue === null) return; // cleared\n applyPersistedState(event.newValue);\n }\n\n if (\n shouldSyncTabs &&\n typeof globalThis !== 'undefined' &&\n typeof globalThis.addEventListener === 'function'\n ) {\n globalThis.addEventListener('storage', onStorageEvent);\n }\n\n // ── Subscribe to store mutations ─────────────────────────────────────────\n\n const unsubscribeFromStore = subscribe(proxyStore, scheduleWrite);\n\n // ── Kick off initial hydration ───────────────────────────────────────────\n\n if (!skipHydration) {\n void hydrateFromStorage()\n .then(() => {\n hydratedFlag = true;\n resolveHydrated();\n })\n .catch((error) => {\n hydratedFlag = true;\n rejectHydrated(error);\n });\n } else {\n // When hydration is skipped, the promise is left pending until\n // the user calls handle.rehydrate() manually.\n }\n\n // ── Build handle ─────────────────────────────────────────────────────────\n\n const handle: PersistHandle = {\n get isHydrated() {\n return hydratedFlag;\n },\n\n get isExpired() {\n return expiredFlag;\n },\n\n hydrated: hydratedPromise,\n\n unsubscribe() {\n if (disposed) return;\n disposed = true;\n\n // Cancel pending debounce.\n if (debounceTimer !== null) {\n clearTimeout(debounceTimer);\n debounceTimer = null;\n }\n\n // Unsubscribe from store mutations.\n unsubscribeFromStore();\n\n // Remove cross-tab sync listener.\n if (\n shouldSyncTabs &&\n typeof globalThis !== 'undefined' &&\n typeof globalThis.removeEventListener === 'function'\n ) {\n globalThis.removeEventListener('storage', onStorageEvent);\n }\n },\n\n async save() {\n if (disposed) return;\n // Cancel pending debounce and write immediately.\n if (debounceTimer !== null) {\n clearTimeout(debounceTimer);\n debounceTimer = null;\n }\n await writeToStorage();\n },\n\n async clear() {\n await storage.removeItem(name);\n },\n\n async rehydrate() {\n await hydrateFromStorage();\n if (!hydratedFlag) {\n hydratedFlag = true;\n resolveHydrated();\n }\n },\n };\n\n return handle;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["snapshot","subscribe","findGetterDescriptor","snapshot","findGetterDescriptor","subscribe","snapshot","findGetterDescriptor","subscribe","snapshot","subscribe"],"sources":["../../src/utils/devtools/devtools.ts","../../src/utils/history/history.ts","../../src/utils/persist/persist.ts","../../src/utils/subscribe-key/subscribe-key.ts"],"sourcesContent":["import {subscribe} from '../../core/core';\nimport {snapshot} from '../../snapshot/snapshot';\nimport {findGetterDescriptor} from '../internal/internal';\n\n// ── Redux DevTools types (minimal subset) ────────────────────────────────────\n\ntype DevToolsMessage = {\n type: string;\n payload?: {type?: string};\n state?: string;\n};\n\ntype DevToolsConnection = {\n init: (state: unknown) => void;\n send: (action: string | {type: string}, state: unknown) => void;\n subscribe: (\n listener: (message: DevToolsMessage) => void,\n ) => (() => void) | {unsubscribe: () => void};\n};\n\ntype DevToolsExtension = {\n connect: (options?: {name?: string}) => DevToolsConnection;\n};\n\ndeclare global {\n interface Window {\n __REDUX_DEVTOOLS_EXTENSION__?: DevToolsExtension;\n }\n}\n\n// ── Options ──────────────────────────────────────────────────────────────────\n\nexport type DevtoolsOptions = {\n /** Display name in the DevTools panel. Defaults to `'ClassyStore'`. */\n name?: string;\n /** Set to `false` to disable the integration (returns a noop). Defaults to `true`. */\n enabled?: boolean;\n};\n\n// ── Implementation ───────────────────────────────────────────────────────────\n\n/**\n * Connect a store proxy to Redux DevTools for state inspection and time-travel debugging.\n *\n * Uses `subscribe()` + `snapshot()` to send state on each change.\n * Listens for `DISPATCH` messages (`JUMP_TO_STATE`, `JUMP_TO_ACTION`) and applies\n * the received state back to the store proxy, skipping getters and methods.\n *\n * @param proxyStore - A reactive proxy created by `createClassyStore()`.\n * @param options - Optional configuration.\n * @returns A dispose function that disconnects from DevTools and unsubscribes.\n */\nexport function devtools<T extends object>(\n proxyStore: T,\n options?: DevtoolsOptions,\n): () => void {\n const {name = 'ClassyStore', enabled = true} = options ?? {};\n\n // Guard: no extension or disabled\n if (\n !enabled ||\n typeof window === 'undefined' ||\n !window.__REDUX_DEVTOOLS_EXTENSION__\n ) {\n return () => {};\n }\n\n const extension = window.__REDUX_DEVTOOLS_EXTENSION__;\n const connection = extension.connect({name});\n\n // Send initial state\n connection.init(snapshot(proxyStore));\n\n // Track whether we're currently applying time-travel state to avoid\n // re-sending the state we just applied.\n let isTimeTraveling = false;\n\n // Subscribe to store mutations → send to DevTools\n const unsubscribeFromStore = subscribe(proxyStore, () => {\n if (isTimeTraveling) return;\n connection.send({type: 'STORE_UPDATE'}, snapshot(proxyStore));\n });\n\n // Listen for DevTools dispatches (time-travel)\n const devToolsUnsub = connection.subscribe((message: DevToolsMessage) => {\n if (message.type === 'DISPATCH' && message.state) {\n const payloadType = message.payload?.type;\n if (payloadType === 'JUMP_TO_STATE' || payloadType === 'JUMP_TO_ACTION') {\n try {\n const newState = JSON.parse(message.state) as Record<string, unknown>;\n isTimeTraveling = true;\n\n // Apply state back to the proxy, skipping getters and methods\n for (const key of Object.keys(newState)) {\n // Skip getters\n if (findGetterDescriptor(proxyStore, key)?.get) continue;\n // Skip methods\n if (\n typeof (proxyStore as Record<string, unknown>)[key] === 'function'\n ) {\n continue;\n }\n (proxyStore as Record<string, unknown>)[key] = newState[key];\n }\n\n // Reset after microtask so the batched subscription callback\n // (which fires via queueMicrotask) still sees the flag as true.\n queueMicrotask(() => {\n isTimeTraveling = false;\n });\n } catch {\n // JSON.parse or property assignment failed — ignore and reset flag.\n isTimeTraveling = false;\n }\n }\n }\n });\n\n // Return dispose function\n return () => {\n unsubscribeFromStore();\n if (typeof devToolsUnsub === 'function') {\n devToolsUnsub();\n } else if (\n devToolsUnsub &&\n typeof devToolsUnsub.unsubscribe === 'function'\n ) {\n devToolsUnsub.unsubscribe();\n }\n };\n}\n","import {subscribe} from '../../core/core';\nimport {snapshot} from '../../snapshot/snapshot';\nimport type {Snapshot} from '../../types';\nimport {findGetterDescriptor} from '../internal/internal';\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport type HistoryOptions = {\n /** Maximum number of history entries. Default: 100. */\n limit?: number;\n};\n\nexport type HistoryHandle = {\n /** Restore the previous state. */\n undo: () => void;\n /** Restore the next state (after an undo). */\n redo: () => void;\n /** Whether there is a previous state to undo to. */\n readonly canUndo: boolean;\n /** Whether there is a next state to redo to. */\n readonly canRedo: boolean;\n /** Temporarily stop recording history entries. */\n pause: () => void;\n /** Resume recording history entries after a pause. */\n resume: () => void;\n /** Unsubscribe and clean up. */\n dispose: () => void;\n};\n\n// ── Implementation ───────────────────────────────────────────────────────────\n\n/**\n * Add undo/redo capability to a store proxy via a snapshot stack.\n *\n * Captures a snapshot on each mutation and maintains a history array with a\n * pointer. `undo()` and `redo()` apply previous/next snapshots back to the\n * store proxy, skipping getters and methods.\n *\n * @param proxyStore - A reactive proxy created by `createClassyStore()`.\n * @param options - Optional configuration (e.g., history limit).\n * @returns A `HistoryHandle` with undo/redo controls and a dispose function.\n */\nexport function withHistory<T extends object>(\n proxyStore: T,\n options?: HistoryOptions,\n): HistoryHandle {\n const limit = options?.limit ?? 100;\n\n // Snapshot stack and pointer\n const history: Snapshot<T>[] = [snapshot(proxyStore)];\n let pointer = 0;\n let paused = false;\n\n /**\n * Apply a snapshot's data properties back to the store proxy.\n * Skips getters and methods — same pattern as `persist`.\n */\n function applySnapshot(snap: Snapshot<T>): void {\n const snapRecord = snap as Record<string, unknown>;\n\n for (const key of Object.keys(snapRecord)) {\n // Skip getters\n if (findGetterDescriptor(proxyStore, key)?.get) continue;\n // Skip methods\n if (typeof (proxyStore as Record<string, unknown>)[key] === 'function') {\n continue;\n }\n (proxyStore as Record<string, unknown>)[key] = snapRecord[key];\n }\n }\n\n // Subscribe to store mutations\n const unsubscribeFromStore = subscribe(proxyStore, () => {\n if (paused) return;\n\n const snap = snapshot(proxyStore);\n\n // Truncate any redo entries after the current pointer\n if (pointer < history.length - 1) {\n history.length = pointer + 1;\n }\n\n // Push new snapshot\n history.push(snap);\n pointer = history.length - 1;\n\n // Enforce limit by shifting from front\n if (history.length > limit) {\n history.shift();\n pointer = history.length - 1;\n }\n });\n\n const handle: HistoryHandle = {\n undo() {\n if (pointer <= 0) return;\n pointer--;\n paused = true;\n try {\n applySnapshot(history[pointer]);\n } finally {\n paused = false;\n }\n },\n\n redo() {\n if (pointer >= history.length - 1) return;\n pointer++;\n paused = true;\n try {\n applySnapshot(history[pointer]);\n } finally {\n paused = false;\n }\n },\n\n get canUndo() {\n return pointer > 0;\n },\n\n get canRedo() {\n return pointer < history.length - 1;\n },\n\n pause() {\n paused = true;\n },\n\n resume() {\n paused = false;\n },\n\n dispose() {\n unsubscribeFromStore();\n history.length = 0;\n pointer = 0;\n },\n };\n\n return handle;\n}\n","import {subscribe} from '../../core/core';\nimport {snapshot} from '../../snapshot/snapshot';\nimport {findGetterDescriptor} from '../internal/internal';\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\n/**\n * Storage adapter interface. Compatible with `localStorage`, `sessionStorage`,\n * `AsyncStorage`, `localForage`, or any custom implementation.\n *\n * Methods may return synchronously or asynchronously — `persist` handles both.\n */\nexport type StorageAdapter = {\n getItem: (name: string) => string | null | Promise<string | null>;\n setItem: (name: string, value: string) => void | Promise<void>;\n removeItem: (name: string) => void | Promise<void>;\n};\n\n/**\n * Describes a per-property serialization transform.\n *\n * Use this when a property's value is not JSON-serializable (Date, ReactiveMap, etc.)\n * and needs custom conversion to/from a storable format.\n */\nexport type PropertyTransform<T extends object> = {\n /** The property key on the store class. */\n key: keyof T;\n\n /** Transform the value BEFORE saving to storage (after snapshot, before JSON.stringify). */\n serialize: (value: T[keyof T]) => unknown;\n\n /** Transform the value AFTER loading from storage (after JSON.parse, before applying to store). */\n deserialize: (stored: unknown) => T[keyof T];\n};\n\n/**\n * Options for `persist()`.\n */\nexport type PersistOptions<T extends object> = {\n /** Unique storage key. Required. */\n name: string;\n\n /**\n * Storage adapter. Defaults to `globalThis.localStorage`.\n * Any object with getItem/setItem/removeItem (sync or async).\n * Works with: localStorage, sessionStorage, AsyncStorage, localForage, etc.\n */\n storage?: StorageAdapter;\n\n /**\n * Which properties to persist.\n * Each entry is either a plain key (string) or a transform descriptor.\n * Defaults to all own enumerable data properties.\n *\n * **Getters and methods are always excluded.** Class getters (e.g., `get remaining()`)\n * are computed/derived values — they are not source-of-truth state. They recompute\n * automatically from the persisted data properties when accessed. Persisting a getter\n * result would be redundant and could produce stale values on restore.\n */\n properties?: Array<keyof T | PropertyTransform<T>>;\n\n /**\n * Debounce writes to storage (milliseconds).\n * Multiple rapid mutations coalesce into one write.\n * Default: 0 (write after every batched mutation).\n */\n debounce?: number;\n\n /**\n * Schema version number. Stored alongside the data.\n * When the stored version differs from this value, `migrate` is called.\n * Default: 0.\n */\n version?: number;\n\n /**\n * Migration function. Called when the stored version does not match `version`.\n * Receives the raw parsed state and the old version number.\n * Must return the state in the current shape.\n */\n migrate?: (\n persistedState: Record<string, unknown>,\n oldVersion: number,\n ) => Record<string, unknown>;\n\n /**\n * How to merge persisted state with current store state during hydration.\n *\n * - `'shallow'` (default): persisted values overwrite current values one key at a time.\n * Properties not in storage keep their current value.\n * - `'replace'`: same behavior as `'shallow'` for flat stores — only stored keys are assigned.\n * For nested objects, the entire object is replaced rather than merged.\n * - Custom function: receives `(persistedState, currentState)` and returns merged state.\n * Enables deep merge or any custom logic.\n *\n * This matters when the store adds new properties that don't exist in old\n * persisted data. `'shallow'` preserves the new defaults.\n */\n merge?:\n | 'shallow'\n | 'replace'\n | ((\n persisted: Record<string, unknown>,\n current: Record<string, unknown>,\n ) => Record<string, unknown>);\n\n /**\n * If true, do NOT hydrate automatically on init.\n * You must call `handle.rehydrate()` manually (e.g., in a `useEffect` for SSR).\n * Default: false.\n */\n skipHydration?: boolean;\n\n /**\n * Sync state across browser tabs via the `window.storage` event.\n * When another tab writes to the same storage key, this tab automatically\n * re-hydrates from the new value.\n *\n * Only works with `localStorage` (storage events don't fire for sessionStorage\n * or async adapters).\n *\n * Default: `true` when storage is `localStorage`, `false` otherwise.\n */\n syncTabs?: boolean;\n\n /**\n * Time-to-live in milliseconds. After this duration, stored data is\n * considered expired and skipped during hydration. The TTL resets on\n * every write (active sessions stay fresh as long as mutations happen).\n */\n expireIn?: number;\n\n /**\n * When `true`, automatically remove the storage key if data is found\n * expired during hydration. Default: `false` (expired data is skipped\n * but left in storage).\n */\n clearOnExpire?: boolean;\n};\n\n/**\n * Handle returned by `persist()`. Provides control over the persist lifecycle.\n */\nexport type PersistHandle = {\n /** Stop persisting and clean up (unsubscribe + cancel pending debounce + remove storage event listener). */\n unsubscribe: () => void;\n\n /** Promise that resolves when initial hydration from storage is complete. */\n hydrated: Promise<void>;\n\n /** Whether hydration from storage has completed. */\n isHydrated: boolean;\n\n /** Manually trigger a write to storage (bypasses debounce). */\n save: () => Promise<void>;\n\n /** Clear this store's persisted data from storage. */\n clear: () => Promise<void>;\n\n /** Manually re-hydrate the store from storage. */\n rehydrate: () => Promise<void>;\n\n /** True if the last hydration found expired data (requires `expireIn`). */\n isExpired: boolean;\n};\n\n// ── Storage envelope ─────────────────────────────────────────────────────────\n\n/** Internal shape of the JSON stored in storage. */\ntype PersistEnvelope = {\n version: number;\n state: Record<string, unknown>;\n expiresAt?: number;\n};\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\n/** Check if a value is a PropertyTransform descriptor (has `key` + `serialize`). */\nfunction isTransform<T extends object>(\n entry: keyof T | PropertyTransform<T>,\n): entry is PropertyTransform<T> {\n return (\n typeof entry === 'object' &&\n entry !== null &&\n 'key' in entry &&\n 'serialize' in entry\n );\n}\n\n/**\n * Build the normalized list of property keys and their optional transforms.\n * If `properties` is not provided, defaults to all own enumerable data properties\n * of the store (excluding getters and methods).\n */\nfunction resolveProperties<T extends object>(\n proxyStore: T,\n properties?: Array<keyof T | PropertyTransform<T>>,\n): Array<{key: string; transform?: PropertyTransform<T>}> {\n if (properties) {\n return properties.map((entry) => {\n if (isTransform(entry)) {\n return {key: entry.key as string, transform: entry};\n }\n return {key: entry as string};\n });\n }\n\n // Default: all own enumerable keys that are not getters or methods.\n const snap = snapshot(proxyStore) as Record<string, unknown>;\n const result: Array<{key: string}> = [];\n for (const key of Object.keys(snap)) {\n // Skip getters (they live on the prototype, but snapshot installs them).\n // We check the original store's target for getter descriptors.\n if (findGetterDescriptor(proxyStore, key)?.get) continue;\n // Skip functions (methods).\n const value = (proxyStore as Record<string, unknown>)[key];\n if (typeof value === 'function') continue;\n result.push({key});\n }\n return result;\n}\n\n/**\n * Detect if the given storage adapter is `globalThis.localStorage`.\n */\nfunction isLocalStorage(storage: StorageAdapter): boolean {\n try {\n return (\n typeof globalThis !== 'undefined' &&\n typeof globalThis.localStorage !== 'undefined' &&\n storage === (globalThis.localStorage as unknown as StorageAdapter)\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Get the default storage adapter (`localStorage`), or `undefined` if unavailable.\n */\nfunction getDefaultStorage(): StorageAdapter | undefined {\n try {\n if (\n typeof globalThis !== 'undefined' &&\n typeof globalThis.localStorage !== 'undefined'\n ) {\n return globalThis.localStorage as unknown as StorageAdapter;\n }\n } catch {\n // SSR or restricted environment — no localStorage.\n }\n return undefined;\n}\n\n// ── Main implementation ──────────────────────────────────────────────────────\n\n/**\n * Persist store state to a storage adapter.\n *\n * Subscribes to store mutations and writes the selected properties to storage\n * (with optional per-property transforms, debouncing, and versioned envelopes).\n * On init (or manual rehydrate), reads from storage and applies the state back\n * to the store proxy.\n *\n * @param proxyStore - A reactive proxy created by `createClassyStore()`.\n * @param options - Persistence configuration.\n * @returns A handle with lifecycle controls (unsubscribe, save, clear, rehydrate, hydrated).\n */\nexport function persist<T extends object>(\n proxyStore: T,\n options: PersistOptions<T>,\n): PersistHandle {\n const {\n name,\n properties: propertiesOption,\n debounce: debounceMs = 0,\n version = 0,\n migrate,\n merge = 'shallow',\n skipHydration = false,\n syncTabs: syncTabsOption,\n expireIn,\n clearOnExpire = false,\n } = options;\n\n const maybeStorage = options.storage ?? getDefaultStorage();\n if (!maybeStorage) {\n throw new Error(\n '@codebelt/classy-store: persist() requires a storage adapter. ' +\n 'No localStorage found — provide a `storage` option.',\n );\n }\n const storage: StorageAdapter = maybeStorage;\n\n const resolvedProps = resolveProperties(proxyStore, propertiesOption);\n\n // Build a map of key → transform for fast lookup during save/restore.\n const transformMap = new Map<string, PropertyTransform<T>>();\n for (const prop of resolvedProps) {\n if (prop.transform) {\n transformMap.set(prop.key, prop.transform);\n }\n }\n\n const propKeys = resolvedProps.map((p) => p.key);\n\n // ── State ────────────────────────────────────────────────────────────────\n\n let disposed = false;\n let hydrating = false;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n let hydratedFlag = false;\n let expiredFlag = false;\n\n // Hydration promise + resolver.\n let resolveHydrated: () => void;\n let rejectHydrated: (error: unknown) => void;\n const hydratedPromise = new Promise<void>((resolve, reject) => {\n resolveHydrated = resolve;\n rejectHydrated = reject;\n });\n\n // ── Save logic ───────────────────────────────────────────────────────────\n\n /** Serialize the current store state into a JSON string (versioned envelope). */\n function serializeState(): string {\n const snap = snapshot(proxyStore) as Record<string, unknown>;\n const state: Record<string, unknown> = {};\n\n for (const key of propKeys) {\n let value = snap[key];\n const transform = transformMap.get(key);\n if (transform) {\n value = transform.serialize(value as T[keyof T]);\n }\n state[key] = value;\n }\n\n const envelope: PersistEnvelope = {version, state};\n if (expireIn != null) {\n envelope.expiresAt = Date.now() + expireIn;\n }\n return JSON.stringify(envelope);\n }\n\n /** Write the current state to storage. */\n async function writeToStorage(): Promise<void> {\n if (disposed) return;\n const json = serializeState();\n await storage.setItem(name, json);\n }\n\n /** Schedule a debounced write (or write immediately if debounce is 0). */\n function scheduleWrite(): void {\n if (disposed || hydrating) return;\n\n if (debounceMs <= 0) {\n void writeToStorage();\n return;\n }\n\n if (debounceTimer !== null) {\n clearTimeout(debounceTimer);\n }\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n void writeToStorage();\n }, debounceMs);\n }\n\n // ── Restore logic ────────────────────────────────────────────────────────\n\n /**\n * Parse a raw JSON string from storage, apply migration and transforms,\n * and merge the result into the store proxy.\n */\n function applyPersistedState(raw: string): void {\n let envelope: PersistEnvelope;\n try {\n envelope = JSON.parse(raw) as PersistEnvelope;\n } catch {\n // Corrupted data — skip.\n return;\n }\n\n if (\n !envelope ||\n typeof envelope !== 'object' ||\n envelope.state === null ||\n typeof envelope.state !== 'object'\n ) {\n return;\n }\n\n // Expiry check — skip hydration if data has expired.\n if (\n typeof envelope.expiresAt === 'number' &&\n Date.now() >= envelope.expiresAt\n ) {\n expiredFlag = true;\n if (clearOnExpire) void storage.removeItem(name);\n return;\n }\n\n let {state} = envelope;\n\n // Version migration.\n if (migrate && envelope.version !== version) {\n state = migrate(state, envelope.version);\n }\n\n // Per-property deserialize transforms.\n for (const key of Object.keys(state)) {\n const transform = transformMap.get(key);\n if (transform) {\n state[key] = transform.deserialize(state[key]);\n }\n }\n\n // Build current state for merge.\n const currentSnap = snapshot(proxyStore) as Record<string, unknown>;\n const currentState: Record<string, unknown> = {};\n for (const key of propKeys) {\n currentState[key] = currentSnap[key];\n }\n\n // Merge strategy.\n let merged: Record<string, unknown>;\n if (typeof merge === 'function') {\n merged = merge(state, currentState);\n } else if (merge === 'replace') {\n // Only use persisted keys — new defaults not in storage are dropped.\n merged = state;\n } else {\n // 'shallow': persisted values overwrite current, but properties not\n // in storage keep their current (default) value.\n merged = {...currentState, ...state};\n }\n\n // Apply to store proxy (goes through SET traps → reactivity).\n for (const key of propKeys) {\n if (key in merged) {\n (proxyStore as Record<string, unknown>)[key] = merged[key];\n }\n }\n }\n\n /** Read from storage and apply to the store. */\n async function hydrateFromStorage(): Promise<void> {\n const raw = await storage.getItem(name);\n if (raw !== null) {\n hydrating = true;\n applyPersistedState(raw);\n // Reset after microtask so the batched subscription callback\n // (which fires via queueMicrotask) still sees the flag as true.\n await new Promise<void>((r) => queueMicrotask(r));\n hydrating = false;\n }\n }\n\n // ── Cross-tab sync ───────────────────────────────────────────────────────\n\n const shouldSyncTabs =\n syncTabsOption !== undefined ? syncTabsOption : isLocalStorage(storage);\n\n /** Handler for `window.storage` events. */\n function onStorageEvent(event: StorageEvent): void {\n if (disposed) return;\n if (event.key !== name) return;\n if (event.newValue === null) return; // cleared\n applyPersistedState(event.newValue);\n }\n\n if (\n shouldSyncTabs &&\n typeof globalThis !== 'undefined' &&\n typeof globalThis.addEventListener === 'function'\n ) {\n globalThis.addEventListener('storage', onStorageEvent);\n }\n\n // ── Subscribe to store mutations ─────────────────────────────────────────\n\n const unsubscribeFromStore = subscribe(proxyStore, scheduleWrite);\n\n // ── Kick off initial hydration ───────────────────────────────────────────\n\n if (!skipHydration) {\n void hydrateFromStorage()\n .then(() => {\n hydratedFlag = true;\n resolveHydrated();\n })\n .catch((error) => {\n hydratedFlag = true;\n rejectHydrated(error);\n });\n } else {\n // When hydration is skipped, the promise is left pending until\n // the user calls handle.rehydrate() manually.\n }\n\n // ── Build handle ─────────────────────────────────────────────────────────\n\n const handle: PersistHandle = {\n get isHydrated() {\n return hydratedFlag;\n },\n\n get isExpired() {\n return expiredFlag;\n },\n\n hydrated: hydratedPromise,\n\n unsubscribe() {\n if (disposed) return;\n disposed = true;\n\n // Cancel pending debounce.\n if (debounceTimer !== null) {\n clearTimeout(debounceTimer);\n debounceTimer = null;\n }\n\n // Unsubscribe from store mutations.\n unsubscribeFromStore();\n\n // Remove cross-tab sync listener.\n if (\n shouldSyncTabs &&\n typeof globalThis !== 'undefined' &&\n typeof globalThis.removeEventListener === 'function'\n ) {\n globalThis.removeEventListener('storage', onStorageEvent);\n }\n },\n\n async save() {\n if (disposed) return;\n // Cancel pending debounce and write immediately.\n if (debounceTimer !== null) {\n clearTimeout(debounceTimer);\n debounceTimer = null;\n }\n await writeToStorage();\n },\n\n async clear() {\n if (disposed) return;\n await storage.removeItem(name);\n },\n\n async rehydrate() {\n expiredFlag = false;\n await hydrateFromStorage();\n if (!hydratedFlag) {\n hydratedFlag = true;\n resolveHydrated();\n }\n },\n };\n\n return handle;\n}\n","import {subscribe} from '../../core/core';\nimport {snapshot} from '../../snapshot/snapshot';\nimport type {Snapshot} from '../../types';\n\n/**\n * Subscribe to changes on a single property of a store proxy.\n *\n * Wraps `subscribe()` + `snapshot()` and compares `snapshot()[key]` with the\n * previous value via `Object.is()`. The callback fires only when the watched\n * property actually changes.\n *\n * @param proxyStore - A reactive proxy created by `createClassyStore()`.\n * @param key - The property key to watch.\n * @param callback - Called with `(value, previousValue)` when the property changes.\n * @returns An unsubscribe function.\n */\nexport function subscribeKey<\n T extends object,\n K extends keyof T & keyof Snapshot<T>,\n>(\n proxyStore: T,\n key: K,\n callback: (value: Snapshot<T>[K], previousValue: Snapshot<T>[K]) => void,\n): () => void {\n let previousValue = snapshot(proxyStore)[key];\n\n return subscribe(proxyStore, () => {\n const snap = snapshot(proxyStore);\n const currentValue = snap[key];\n\n if (!Object.is(currentValue, previousValue)) {\n const prev = previousValue;\n previousValue = currentValue;\n callback(currentValue, prev);\n }\n });\n}\n"],"mappings":"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"}
@@ -1,3 +1,76 @@
1
+ import { t as Snapshot } from "../types-B6RZUB86.cjs";
2
+ import { t as shallowEqual } from "../equality-C1s0kqxg.cjs";
3
+
4
+ //#region src/utils/devtools/devtools.d.ts
5
+ type DevToolsMessage = {
6
+ type: string;
7
+ payload?: {
8
+ type?: string;
9
+ };
10
+ state?: string;
11
+ };
12
+ type DevToolsConnection = {
13
+ init: (state: unknown) => void;
14
+ send: (action: string | {
15
+ type: string;
16
+ }, state: unknown) => void;
17
+ subscribe: (listener: (message: DevToolsMessage) => void) => (() => void) | {
18
+ unsubscribe: () => void;
19
+ };
20
+ };
21
+ type DevToolsExtension = {
22
+ connect: (options?: {
23
+ name?: string;
24
+ }) => DevToolsConnection;
25
+ };
26
+ declare global {
27
+ interface Window {
28
+ __REDUX_DEVTOOLS_EXTENSION__?: DevToolsExtension;
29
+ }
30
+ }
31
+ type DevtoolsOptions = {
32
+ /** Display name in the DevTools panel. Defaults to `'ClassyStore'`. */name?: string; /** Set to `false` to disable the integration (returns a noop). Defaults to `true`. */
33
+ enabled?: boolean;
34
+ };
35
+ /**
36
+ * Connect a store proxy to Redux DevTools for state inspection and time-travel debugging.
37
+ *
38
+ * Uses `subscribe()` + `snapshot()` to send state on each change.
39
+ * Listens for `DISPATCH` messages (`JUMP_TO_STATE`, `JUMP_TO_ACTION`) and applies
40
+ * the received state back to the store proxy, skipping getters and methods.
41
+ *
42
+ * @param proxyStore - A reactive proxy created by `createClassyStore()`.
43
+ * @param options - Optional configuration.
44
+ * @returns A dispose function that disconnects from DevTools and unsubscribes.
45
+ */
46
+ declare function devtools<T extends object>(proxyStore: T, options?: DevtoolsOptions): () => void;
47
+ //#endregion
48
+ //#region src/utils/history/history.d.ts
49
+ type HistoryOptions = {
50
+ /** Maximum number of history entries. Default: 100. */limit?: number;
51
+ };
52
+ type HistoryHandle = {
53
+ /** Restore the previous state. */undo: () => void; /** Restore the next state (after an undo). */
54
+ redo: () => void; /** Whether there is a previous state to undo to. */
55
+ readonly canUndo: boolean; /** Whether there is a next state to redo to. */
56
+ readonly canRedo: boolean; /** Temporarily stop recording history entries. */
57
+ pause: () => void; /** Resume recording history entries after a pause. */
58
+ resume: () => void; /** Unsubscribe and clean up. */
59
+ dispose: () => void;
60
+ };
61
+ /**
62
+ * Add undo/redo capability to a store proxy via a snapshot stack.
63
+ *
64
+ * Captures a snapshot on each mutation and maintains a history array with a
65
+ * pointer. `undo()` and `redo()` apply previous/next snapshots back to the
66
+ * store proxy, skipping getters and methods.
67
+ *
68
+ * @param proxyStore - A reactive proxy created by `createClassyStore()`.
69
+ * @param options - Optional configuration (e.g., history limit).
70
+ * @returns A `HistoryHandle` with undo/redo controls and a dispose function.
71
+ */
72
+ declare function withHistory<T extends object>(proxyStore: T, options?: HistoryOptions): HistoryHandle;
73
+ //#endregion
1
74
  //#region src/utils/persist/persist.d.ts
2
75
  /**
3
76
  * Storage adapter interface. Compatible with `localStorage`, `sessionStorage`,
@@ -131,5 +204,20 @@ type PersistHandle = {
131
204
  */
132
205
  declare function persist<T extends object>(proxyStore: T, options: PersistOptions<T>): PersistHandle;
133
206
  //#endregion
134
- export { type PersistHandle, type PersistOptions, type PropertyTransform, type StorageAdapter, persist };
207
+ //#region src/utils/subscribe-key/subscribe-key.d.ts
208
+ /**
209
+ * Subscribe to changes on a single property of a store proxy.
210
+ *
211
+ * Wraps `subscribe()` + `snapshot()` and compares `snapshot()[key]` with the
212
+ * previous value via `Object.is()`. The callback fires only when the watched
213
+ * property actually changes.
214
+ *
215
+ * @param proxyStore - A reactive proxy created by `createClassyStore()`.
216
+ * @param key - The property key to watch.
217
+ * @param callback - Called with `(value, previousValue)` when the property changes.
218
+ * @returns An unsubscribe function.
219
+ */
220
+ declare function subscribeKey<T extends object, K extends keyof T & keyof Snapshot<T>>(proxyStore: T, key: K, callback: (value: Snapshot<T>[K], previousValue: Snapshot<T>[K]) => void): () => void;
221
+ //#endregion
222
+ export { type DevtoolsOptions, type HistoryHandle, type HistoryOptions, type PersistHandle, type PersistOptions, type PropertyTransform, type StorageAdapter, devtools, persist, shallowEqual, subscribeKey, withHistory };
135
223
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/utils/persist/persist.ts"],"mappings":";;AAYA;;;;;KAAY,cAAA;EACV,OAAA,GAAU,IAAA,6BAAiC,OAAA;EAC3C,OAAA,GAAU,IAAA,UAAc,KAAA,oBAAyB,OAAA;EACjD,UAAA,GAAa,IAAA,oBAAwB,OAAA;AAAA;;;;;;;KAS3B,iBAAA;EAT2B,2CAWrC,GAAA,QAAW,CAAA,EAXiC;EAc5C,SAAA,GAAY,KAAA,EAAO,CAAA,OAAQ,CAAA,eALA;EAQ3B,WAAA,GAAc,MAAA,cAAoB,CAAA,OAAQ,CAAA;AAAA;;;;KAMhC,cAAA;EANiC,oCAQ3C,IAAA;EAhB4B;;;;;EAuB5B,OAAA,GAAU,cAAA;EAlBE;;;;;;;AASd;;;EAqBE,UAAA,GAAa,KAAA,OAAY,CAAA,GAAI,iBAAA,CAAkB,CAAA;EAAtB;;;;;EAOzB,QAAA;EAoCiB;;;;;EA7BjB,OAAA;EAjCA;;;;;EAwCA,OAAA,IACE,cAAA,EAAgB,MAAA,mBAChB,UAAA,aACG,MAAA;EAxBwB;;;;;;;;;;;;;EAuC7B,KAAA,6BAIM,SAAA,EAAW,MAAA,mBACX,OAAA,EAAS,MAAA,sBACN,MAAA;EAAA;;;;;EAOT,aAAA;EA0Ba;AAMf;;;;;;;;;EApBE,QAAA;EAyBA;;;;;EAlBA,QAAA;EA2Ba;;;;;EApBb,aAAA;AAAA;;;;KAMU,aAAA;EAgID,4GA9HT,WAAA,cA+Hc;EA5Hd,QAAA,EAAU,OAAA,QAyHY;EAtHtB,UAAA,WAuHA;EApHA,IAAA,QAAY,OAAA,QAqHY;EAlHxB,KAAA,QAAa,OAAA,QAmHZ;EAhHD,SAAA,QAAiB,OAAA,QAgHH;EA7Gd,SAAA;AAAA;;;;;;;;;;;;;iBA0Gc,OAAA,kBAAA,CACd,UAAA,EAAY,CAAA,EACZ,OAAA,EAAS,cAAA,CAAe,CAAA,IACvB,aAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/utils/devtools/devtools.ts","../../src/utils/history/history.ts","../../src/utils/persist/persist.ts","../../src/utils/subscribe-key/subscribe-key.ts"],"mappings":";;;;KAMK,eAAA;EACH,IAAA;EACA,OAAA;IAAW,IAAA;EAAA;EACX,KAAA;AAAA;AAAA,KAGG,kBAAA;EACH,IAAA,GAAO,KAAA;EACP,IAAA,GAAO,MAAA;IAAkB,IAAA;EAAA,GAAe,KAAA;EACxC,SAAA,GACE,QAAA,GAAW,OAAA,EAAS,eAAA;IACD,WAAA;EAAA;AAAA;AAAA,KAGlB,iBAAA;EACH,OAAA,GAAU,OAAA;IAAW,IAAA;EAAA,MAAmB,kBAAA;AAAA;AAAA,QAGlC,MAAA;EAAA,UACI,MAAA;IACR,4BAAA,GAA+B,iBAAA;EAAA;AAAA;AAAA,KAMvB,eAAA;EAhBR,uEAkBF,IAAA,WAjBgC;EAmBhC,OAAA;AAAA;;;;;;;;;;AAf0D;;iBA+B5C,QAAA,kBAAA,CACd,UAAA,EAAY,CAAA,EACZ,OAAA,GAAU,eAAA;;;KC/CA,cAAA;yDAEV,KAAA;AAAA;AAAA,KAGU,aAAA;EDNQ,kCCQlB,IAAA,cDRkB;ECUlB,IAAA,cDRA;EAAA,SCUS,OAAA,WDTT;EAAA,SCWS,OAAA,WDXJ;ECaL,KAAA,cDVqB;ECYrB,MAAA,cDRqC;ECUrC,OAAA;AAAA;;;;;;;;;;;;iBAgBc,WAAA,kBAAA,CACd,UAAA,EAAY,CAAA,EACZ,OAAA,GAAU,cAAA,GACT,aAAA;;;;;;;;;KCjCS,cAAA;EACV,OAAA,GAAU,IAAA,6BAAiC,OAAA;EAC3C,OAAA,GAAU,IAAA,UAAc,KAAA,oBAAyB,OAAA;EACjD,UAAA,GAAa,IAAA,oBAAwB,OAAA;AAAA;;;;AFNhC;;;KEeK,iBAAA;EFXV,2CEaA,GAAA,QAAW,CAAA,EFZX;EEeA,SAAA,GAAY,KAAA,EAAO,CAAA,OAAQ,CAAA,eFfpB;EEkBP,WAAA,GAAc,MAAA,cAAoB,CAAA,OAAQ,CAAA;AAAA;;;;KAMhC,cAAA;EFrBsB,oCEuBhC,IAAA;EFpBG;;;;;EE2BH,OAAA,GAAU,cAAA;EF1BA;;;;AAAgD;;;;;;EEsC1D,UAAA,GAAa,KAAA,OAAY,CAAA,GAAI,iBAAA,CAAkB,CAAA;EFjCd;;;;AAMnC;EEkCE,QAAA;;;;AFdF;;EEqBE,OAAA;EFnByB;;;;;EE0BzB,OAAA,IACE,cAAA,EAAgB,MAAA,mBAChB,UAAA,aACG,MAAA;EF7BoB;;;;;AC/C3B;;;;;AAKA;;;ECsFE,KAAA,6BAIM,SAAA,EAAW,MAAA,mBACX,OAAA,EAAS,MAAA,sBACN,MAAA;ED1FT;;;;;ECiGA,aAAA;EDrFA;;;AAgBF;;;;;;;ECiFE,QAAA;EDjF0B;;;;;ECwF1B,QAAA;EDrFc;;;;;EC4Fd,aAAA;AAAA;;;;KAMU,aAAA;EAhIkC,4GAkI5C,WAAA,cApIA;EAuIA,QAAA,EAAU,OAAA,QAvIiC;EA0I3C,UAAA,WAzIU;EA4IV,IAAA,QAAY,OAAA,QA5IqC;EA+IjD,KAAA,QAAa,OAAA,QA9IA;EAiJb,SAAA,QAAiB,OAAA,QAjJ2B;EAoJ5C,SAAA;AAAA;;;;;;;;;;;;;iBAyGc,OAAA,kBAAA,CACd,UAAA,EAAY,CAAA,EACZ,OAAA,EAAS,cAAA,CAAe,CAAA,IACvB,aAAA;;;;;;;;;;;;;;;iBC/Pa,YAAA,mCAEE,CAAA,SAAU,QAAA,CAAS,CAAA,EAAA,CAEnC,UAAA,EAAY,CAAA,EACZ,GAAA,EAAK,CAAA,EACL,QAAA,GAAW,KAAA,EAAO,QAAA,CAAS,CAAA,EAAG,CAAA,GAAI,aAAA,EAAe,QAAA,CAAS,CAAA,EAAG,CAAA"}
@@ -1,3 +1,76 @@
1
+ import { t as Snapshot } from "../types-Cf8Fp7kA.mjs";
2
+ import { t as shallowEqual } from "../equality-5F2bPn7E.mjs";
3
+
4
+ //#region src/utils/devtools/devtools.d.ts
5
+ type DevToolsMessage = {
6
+ type: string;
7
+ payload?: {
8
+ type?: string;
9
+ };
10
+ state?: string;
11
+ };
12
+ type DevToolsConnection = {
13
+ init: (state: unknown) => void;
14
+ send: (action: string | {
15
+ type: string;
16
+ }, state: unknown) => void;
17
+ subscribe: (listener: (message: DevToolsMessage) => void) => (() => void) | {
18
+ unsubscribe: () => void;
19
+ };
20
+ };
21
+ type DevToolsExtension = {
22
+ connect: (options?: {
23
+ name?: string;
24
+ }) => DevToolsConnection;
25
+ };
26
+ declare global {
27
+ interface Window {
28
+ __REDUX_DEVTOOLS_EXTENSION__?: DevToolsExtension;
29
+ }
30
+ }
31
+ type DevtoolsOptions = {
32
+ /** Display name in the DevTools panel. Defaults to `'ClassyStore'`. */name?: string; /** Set to `false` to disable the integration (returns a noop). Defaults to `true`. */
33
+ enabled?: boolean;
34
+ };
35
+ /**
36
+ * Connect a store proxy to Redux DevTools for state inspection and time-travel debugging.
37
+ *
38
+ * Uses `subscribe()` + `snapshot()` to send state on each change.
39
+ * Listens for `DISPATCH` messages (`JUMP_TO_STATE`, `JUMP_TO_ACTION`) and applies
40
+ * the received state back to the store proxy, skipping getters and methods.
41
+ *
42
+ * @param proxyStore - A reactive proxy created by `createClassyStore()`.
43
+ * @param options - Optional configuration.
44
+ * @returns A dispose function that disconnects from DevTools and unsubscribes.
45
+ */
46
+ declare function devtools<T extends object>(proxyStore: T, options?: DevtoolsOptions): () => void;
47
+ //#endregion
48
+ //#region src/utils/history/history.d.ts
49
+ type HistoryOptions = {
50
+ /** Maximum number of history entries. Default: 100. */limit?: number;
51
+ };
52
+ type HistoryHandle = {
53
+ /** Restore the previous state. */undo: () => void; /** Restore the next state (after an undo). */
54
+ redo: () => void; /** Whether there is a previous state to undo to. */
55
+ readonly canUndo: boolean; /** Whether there is a next state to redo to. */
56
+ readonly canRedo: boolean; /** Temporarily stop recording history entries. */
57
+ pause: () => void; /** Resume recording history entries after a pause. */
58
+ resume: () => void; /** Unsubscribe and clean up. */
59
+ dispose: () => void;
60
+ };
61
+ /**
62
+ * Add undo/redo capability to a store proxy via a snapshot stack.
63
+ *
64
+ * Captures a snapshot on each mutation and maintains a history array with a
65
+ * pointer. `undo()` and `redo()` apply previous/next snapshots back to the
66
+ * store proxy, skipping getters and methods.
67
+ *
68
+ * @param proxyStore - A reactive proxy created by `createClassyStore()`.
69
+ * @param options - Optional configuration (e.g., history limit).
70
+ * @returns A `HistoryHandle` with undo/redo controls and a dispose function.
71
+ */
72
+ declare function withHistory<T extends object>(proxyStore: T, options?: HistoryOptions): HistoryHandle;
73
+ //#endregion
1
74
  //#region src/utils/persist/persist.d.ts
2
75
  /**
3
76
  * Storage adapter interface. Compatible with `localStorage`, `sessionStorage`,
@@ -131,5 +204,20 @@ type PersistHandle = {
131
204
  */
132
205
  declare function persist<T extends object>(proxyStore: T, options: PersistOptions<T>): PersistHandle;
133
206
  //#endregion
134
- export { type PersistHandle, type PersistOptions, type PropertyTransform, type StorageAdapter, persist };
207
+ //#region src/utils/subscribe-key/subscribe-key.d.ts
208
+ /**
209
+ * Subscribe to changes on a single property of a store proxy.
210
+ *
211
+ * Wraps `subscribe()` + `snapshot()` and compares `snapshot()[key]` with the
212
+ * previous value via `Object.is()`. The callback fires only when the watched
213
+ * property actually changes.
214
+ *
215
+ * @param proxyStore - A reactive proxy created by `createClassyStore()`.
216
+ * @param key - The property key to watch.
217
+ * @param callback - Called with `(value, previousValue)` when the property changes.
218
+ * @returns An unsubscribe function.
219
+ */
220
+ declare function subscribeKey<T extends object, K extends keyof T & keyof Snapshot<T>>(proxyStore: T, key: K, callback: (value: Snapshot<T>[K], previousValue: Snapshot<T>[K]) => void): () => void;
221
+ //#endregion
222
+ export { type DevtoolsOptions, type HistoryHandle, type HistoryOptions, type PersistHandle, type PersistOptions, type PropertyTransform, type StorageAdapter, devtools, persist, shallowEqual, subscribeKey, withHistory };
135
223
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/utils/persist/persist.ts"],"mappings":";;AAYA;;;;;KAAY,cAAA;EACV,OAAA,GAAU,IAAA,6BAAiC,OAAA;EAC3C,OAAA,GAAU,IAAA,UAAc,KAAA,oBAAyB,OAAA;EACjD,UAAA,GAAa,IAAA,oBAAwB,OAAA;AAAA;;;;;;;KAS3B,iBAAA;EAT2B,2CAWrC,GAAA,QAAW,CAAA,EAXiC;EAc5C,SAAA,GAAY,KAAA,EAAO,CAAA,OAAQ,CAAA,eALA;EAQ3B,WAAA,GAAc,MAAA,cAAoB,CAAA,OAAQ,CAAA;AAAA;;;;KAMhC,cAAA;EANiC,oCAQ3C,IAAA;EAhB4B;;;;;EAuB5B,OAAA,GAAU,cAAA;EAlBE;;;;;;;AASd;;;EAqBE,UAAA,GAAa,KAAA,OAAY,CAAA,GAAI,iBAAA,CAAkB,CAAA;EAAtB;;;;;EAOzB,QAAA;EAoCiB;;;;;EA7BjB,OAAA;EAjCA;;;;;EAwCA,OAAA,IACE,cAAA,EAAgB,MAAA,mBAChB,UAAA,aACG,MAAA;EAxBwB;;;;;;;;;;;;;EAuC7B,KAAA,6BAIM,SAAA,EAAW,MAAA,mBACX,OAAA,EAAS,MAAA,sBACN,MAAA;EAAA;;;;;EAOT,aAAA;EA0Ba;AAMf;;;;;;;;;EApBE,QAAA;EAyBA;;;;;EAlBA,QAAA;EA2Ba;;;;;EApBb,aAAA;AAAA;;;;KAMU,aAAA;EAgID,4GA9HT,WAAA,cA+Hc;EA5Hd,QAAA,EAAU,OAAA,QAyHY;EAtHtB,UAAA,WAuHA;EApHA,IAAA,QAAY,OAAA,QAqHY;EAlHxB,KAAA,QAAa,OAAA,QAmHZ;EAhHD,SAAA,QAAiB,OAAA,QAgHH;EA7Gd,SAAA;AAAA;;;;;;;;;;;;;iBA0Gc,OAAA,kBAAA,CACd,UAAA,EAAY,CAAA,EACZ,OAAA,EAAS,cAAA,CAAe,CAAA,IACvB,aAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/utils/devtools/devtools.ts","../../src/utils/history/history.ts","../../src/utils/persist/persist.ts","../../src/utils/subscribe-key/subscribe-key.ts"],"mappings":";;;;KAMK,eAAA;EACH,IAAA;EACA,OAAA;IAAW,IAAA;EAAA;EACX,KAAA;AAAA;AAAA,KAGG,kBAAA;EACH,IAAA,GAAO,KAAA;EACP,IAAA,GAAO,MAAA;IAAkB,IAAA;EAAA,GAAe,KAAA;EACxC,SAAA,GACE,QAAA,GAAW,OAAA,EAAS,eAAA;IACD,WAAA;EAAA;AAAA;AAAA,KAGlB,iBAAA;EACH,OAAA,GAAU,OAAA;IAAW,IAAA;EAAA,MAAmB,kBAAA;AAAA;AAAA,QAGlC,MAAA;EAAA,UACI,MAAA;IACR,4BAAA,GAA+B,iBAAA;EAAA;AAAA;AAAA,KAMvB,eAAA;EAhBR,uEAkBF,IAAA,WAjBgC;EAmBhC,OAAA;AAAA;;;;;;;;;;AAf0D;;iBA+B5C,QAAA,kBAAA,CACd,UAAA,EAAY,CAAA,EACZ,OAAA,GAAU,eAAA;;;KC/CA,cAAA;yDAEV,KAAA;AAAA;AAAA,KAGU,aAAA;EDNQ,kCCQlB,IAAA,cDRkB;ECUlB,IAAA,cDRA;EAAA,SCUS,OAAA,WDTT;EAAA,SCWS,OAAA,WDXJ;ECaL,KAAA,cDVqB;ECYrB,MAAA,cDRqC;ECUrC,OAAA;AAAA;;;;;;;;;;;;iBAgBc,WAAA,kBAAA,CACd,UAAA,EAAY,CAAA,EACZ,OAAA,GAAU,cAAA,GACT,aAAA;;;;;;;;;KCjCS,cAAA;EACV,OAAA,GAAU,IAAA,6BAAiC,OAAA;EAC3C,OAAA,GAAU,IAAA,UAAc,KAAA,oBAAyB,OAAA;EACjD,UAAA,GAAa,IAAA,oBAAwB,OAAA;AAAA;;;;AFNhC;;;KEeK,iBAAA;EFXV,2CEaA,GAAA,QAAW,CAAA,EFZX;EEeA,SAAA,GAAY,KAAA,EAAO,CAAA,OAAQ,CAAA,eFfpB;EEkBP,WAAA,GAAc,MAAA,cAAoB,CAAA,OAAQ,CAAA;AAAA;;;;KAMhC,cAAA;EFrBsB,oCEuBhC,IAAA;EFpBG;;;;;EE2BH,OAAA,GAAU,cAAA;EF1BA;;;;AAAgD;;;;;;EEsC1D,UAAA,GAAa,KAAA,OAAY,CAAA,GAAI,iBAAA,CAAkB,CAAA;EFjCd;;;;AAMnC;EEkCE,QAAA;;;;AFdF;;EEqBE,OAAA;EFnByB;;;;;EE0BzB,OAAA,IACE,cAAA,EAAgB,MAAA,mBAChB,UAAA,aACG,MAAA;EF7BoB;;;;;AC/C3B;;;;;AAKA;;;ECsFE,KAAA,6BAIM,SAAA,EAAW,MAAA,mBACX,OAAA,EAAS,MAAA,sBACN,MAAA;ED1FT;;;;;ECiGA,aAAA;EDrFA;;;AAgBF;;;;;;;ECiFE,QAAA;EDjF0B;;;;;ECwF1B,QAAA;EDrFc;;;;;EC4Fd,aAAA;AAAA;;;;KAMU,aAAA;EAhIkC,4GAkI5C,WAAA,cApIA;EAuIA,QAAA,EAAU,OAAA,QAvIiC;EA0I3C,UAAA,WAzIU;EA4IV,IAAA,QAAY,OAAA,QA5IqC;EA+IjD,KAAA,QAAa,OAAA,QA9IA;EAiJb,SAAA,QAAiB,OAAA,QAjJ2B;EAoJ5C,SAAA;AAAA;;;;;;;;;;;;;iBAyGc,OAAA,kBAAA,CACd,UAAA,EAAY,CAAA,EACZ,OAAA,EAAS,cAAA,CAAe,CAAA,IACvB,aAAA;;;;;;;;;;;;;;;iBC/Pa,YAAA,mCAEE,CAAA,SAAU,QAAA,CAAS,CAAA,EAAA,CAEnC,UAAA,EAAY,CAAA,EACZ,GAAA,EAAK,CAAA,EACL,QAAA,GAAW,KAAA,EAAO,QAAA,CAAS,CAAA,EAAG,CAAA,GAAI,aAAA,EAAe,QAAA,CAAS,CAAA,EAAG,CAAA"}
@@ -1,5 +1,137 @@
1
- import { a as subscribe, s as findGetterDescriptor, t as snapshot } from "../snapshot-P0QPV1ER.mjs";
1
+ import { a as subscribe, s as findGetterDescriptor, t as snapshot } from "../snapshot-COzEerMu.mjs";
2
+ import { t as shallowEqual } from "../equality-BA46H9AL.mjs";
2
3
 
4
+ //#region src/utils/devtools/devtools.ts
5
+ /**
6
+ * Connect a store proxy to Redux DevTools for state inspection and time-travel debugging.
7
+ *
8
+ * Uses `subscribe()` + `snapshot()` to send state on each change.
9
+ * Listens for `DISPATCH` messages (`JUMP_TO_STATE`, `JUMP_TO_ACTION`) and applies
10
+ * the received state back to the store proxy, skipping getters and methods.
11
+ *
12
+ * @param proxyStore - A reactive proxy created by `createClassyStore()`.
13
+ * @param options - Optional configuration.
14
+ * @returns A dispose function that disconnects from DevTools and unsubscribes.
15
+ */
16
+ function devtools(proxyStore, options) {
17
+ const { name = "ClassyStore", enabled = true } = options ?? {};
18
+ if (!enabled || typeof window === "undefined" || !window.__REDUX_DEVTOOLS_EXTENSION__) return () => {};
19
+ const connection = window.__REDUX_DEVTOOLS_EXTENSION__.connect({ name });
20
+ connection.init(snapshot(proxyStore));
21
+ let isTimeTraveling = false;
22
+ const unsubscribeFromStore = subscribe(proxyStore, () => {
23
+ if (isTimeTraveling) return;
24
+ connection.send({ type: "STORE_UPDATE" }, snapshot(proxyStore));
25
+ });
26
+ const devToolsUnsub = connection.subscribe((message) => {
27
+ if (message.type === "DISPATCH" && message.state) {
28
+ const payloadType = message.payload?.type;
29
+ if (payloadType === "JUMP_TO_STATE" || payloadType === "JUMP_TO_ACTION") try {
30
+ const newState = JSON.parse(message.state);
31
+ isTimeTraveling = true;
32
+ for (const key of Object.keys(newState)) {
33
+ if (findGetterDescriptor(proxyStore, key)?.get) continue;
34
+ if (typeof proxyStore[key] === "function") continue;
35
+ proxyStore[key] = newState[key];
36
+ }
37
+ queueMicrotask(() => {
38
+ isTimeTraveling = false;
39
+ });
40
+ } catch {
41
+ isTimeTraveling = false;
42
+ }
43
+ }
44
+ });
45
+ return () => {
46
+ unsubscribeFromStore();
47
+ if (typeof devToolsUnsub === "function") devToolsUnsub();
48
+ else if (devToolsUnsub && typeof devToolsUnsub.unsubscribe === "function") devToolsUnsub.unsubscribe();
49
+ };
50
+ }
51
+
52
+ //#endregion
53
+ //#region src/utils/history/history.ts
54
+ /**
55
+ * Add undo/redo capability to a store proxy via a snapshot stack.
56
+ *
57
+ * Captures a snapshot on each mutation and maintains a history array with a
58
+ * pointer. `undo()` and `redo()` apply previous/next snapshots back to the
59
+ * store proxy, skipping getters and methods.
60
+ *
61
+ * @param proxyStore - A reactive proxy created by `createClassyStore()`.
62
+ * @param options - Optional configuration (e.g., history limit).
63
+ * @returns A `HistoryHandle` with undo/redo controls and a dispose function.
64
+ */
65
+ function withHistory(proxyStore, options) {
66
+ const limit = options?.limit ?? 100;
67
+ const history = [snapshot(proxyStore)];
68
+ let pointer = 0;
69
+ let paused = false;
70
+ /**
71
+ * Apply a snapshot's data properties back to the store proxy.
72
+ * Skips getters and methods — same pattern as `persist`.
73
+ */
74
+ function applySnapshot(snap) {
75
+ const snapRecord = snap;
76
+ for (const key of Object.keys(snapRecord)) {
77
+ if (findGetterDescriptor(proxyStore, key)?.get) continue;
78
+ if (typeof proxyStore[key] === "function") continue;
79
+ proxyStore[key] = snapRecord[key];
80
+ }
81
+ }
82
+ const unsubscribeFromStore = subscribe(proxyStore, () => {
83
+ if (paused) return;
84
+ const snap = snapshot(proxyStore);
85
+ if (pointer < history.length - 1) history.length = pointer + 1;
86
+ history.push(snap);
87
+ pointer = history.length - 1;
88
+ if (history.length > limit) {
89
+ history.shift();
90
+ pointer = history.length - 1;
91
+ }
92
+ });
93
+ return {
94
+ undo() {
95
+ if (pointer <= 0) return;
96
+ pointer--;
97
+ paused = true;
98
+ try {
99
+ applySnapshot(history[pointer]);
100
+ } finally {
101
+ paused = false;
102
+ }
103
+ },
104
+ redo() {
105
+ if (pointer >= history.length - 1) return;
106
+ pointer++;
107
+ paused = true;
108
+ try {
109
+ applySnapshot(history[pointer]);
110
+ } finally {
111
+ paused = false;
112
+ }
113
+ },
114
+ get canUndo() {
115
+ return pointer > 0;
116
+ },
117
+ get canRedo() {
118
+ return pointer < history.length - 1;
119
+ },
120
+ pause() {
121
+ paused = true;
122
+ },
123
+ resume() {
124
+ paused = false;
125
+ },
126
+ dispose() {
127
+ unsubscribeFromStore();
128
+ history.length = 0;
129
+ pointer = 0;
130
+ }
131
+ };
132
+ }
133
+
134
+ //#endregion
3
135
  //#region src/utils/persist/persist.ts
4
136
  /** Check if a value is a PropertyTransform descriptor (has `key` + `serialize`). */
5
137
  function isTransform(entry) {
@@ -21,7 +153,7 @@ function resolveProperties(proxyStore, properties) {
21
153
  const snap = snapshot(proxyStore);
22
154
  const result = [];
23
155
  for (const key of Object.keys(snap)) {
24
- if (findGetterDescriptor(Object.getPrototypeOf(proxyStore), key)?.get) continue;
156
+ if (findGetterDescriptor(proxyStore, key)?.get) continue;
25
157
  if (typeof proxyStore[key] === "function") continue;
26
158
  result.push({ key });
27
159
  }
@@ -67,6 +199,7 @@ function persist(proxyStore, options) {
67
199
  for (const prop of resolvedProps) if (prop.transform) transformMap.set(prop.key, prop.transform);
68
200
  const propKeys = resolvedProps.map((p) => p.key);
69
201
  let disposed = false;
202
+ let hydrating = false;
70
203
  let debounceTimer = null;
71
204
  let hydratedFlag = false;
72
205
  let expiredFlag = false;
@@ -101,7 +234,7 @@ function persist(proxyStore, options) {
101
234
  }
102
235
  /** Schedule a debounced write (or write immediately if debounce is 0). */
103
236
  function scheduleWrite() {
104
- if (disposed) return;
237
+ if (disposed || hydrating) return;
105
238
  if (debounceMs <= 0) {
106
239
  writeToStorage();
107
240
  return;
@@ -123,7 +256,7 @@ function persist(proxyStore, options) {
123
256
  } catch {
124
257
  return;
125
258
  }
126
- if (!envelope || typeof envelope !== "object" || typeof envelope.state !== "object") return;
259
+ if (!envelope || typeof envelope !== "object" || envelope.state === null || typeof envelope.state !== "object") return;
127
260
  if (typeof envelope.expiresAt === "number" && Date.now() >= envelope.expiresAt) {
128
261
  expiredFlag = true;
129
262
  if (clearOnExpire) storage.removeItem(name);
@@ -140,6 +273,7 @@ function persist(proxyStore, options) {
140
273
  for (const key of propKeys) currentState[key] = currentSnap[key];
141
274
  let merged;
142
275
  if (typeof merge === "function") merged = merge(state, currentState);
276
+ else if (merge === "replace") merged = state;
143
277
  else merged = {
144
278
  ...currentState,
145
279
  ...state
@@ -149,7 +283,12 @@ function persist(proxyStore, options) {
149
283
  /** Read from storage and apply to the store. */
150
284
  async function hydrateFromStorage() {
151
285
  const raw = await storage.getItem(name);
152
- if (raw !== null) applyPersistedState(raw);
286
+ if (raw !== null) {
287
+ hydrating = true;
288
+ applyPersistedState(raw);
289
+ await new Promise((r) => queueMicrotask(r));
290
+ hydrating = false;
291
+ }
153
292
  }
154
293
  const shouldSyncTabs = syncTabsOption !== void 0 ? syncTabsOption : isLocalStorage(storage);
155
294
  /** Handler for `window.storage` events. */
@@ -195,9 +334,11 @@ function persist(proxyStore, options) {
195
334
  await writeToStorage();
196
335
  },
197
336
  async clear() {
337
+ if (disposed) return;
198
338
  await storage.removeItem(name);
199
339
  },
200
340
  async rehydrate() {
341
+ expiredFlag = false;
201
342
  await hydrateFromStorage();
202
343
  if (!hydratedFlag) {
203
344
  hydratedFlag = true;
@@ -208,5 +349,31 @@ function persist(proxyStore, options) {
208
349
  }
209
350
 
210
351
  //#endregion
211
- export { persist };
352
+ //#region src/utils/subscribe-key/subscribe-key.ts
353
+ /**
354
+ * Subscribe to changes on a single property of a store proxy.
355
+ *
356
+ * Wraps `subscribe()` + `snapshot()` and compares `snapshot()[key]` with the
357
+ * previous value via `Object.is()`. The callback fires only when the watched
358
+ * property actually changes.
359
+ *
360
+ * @param proxyStore - A reactive proxy created by `createClassyStore()`.
361
+ * @param key - The property key to watch.
362
+ * @param callback - Called with `(value, previousValue)` when the property changes.
363
+ * @returns An unsubscribe function.
364
+ */
365
+ function subscribeKey(proxyStore, key, callback) {
366
+ let previousValue = snapshot(proxyStore)[key];
367
+ return subscribe(proxyStore, () => {
368
+ const currentValue = snapshot(proxyStore)[key];
369
+ if (!Object.is(currentValue, previousValue)) {
370
+ const prev = previousValue;
371
+ previousValue = currentValue;
372
+ callback(currentValue, prev);
373
+ }
374
+ });
375
+ }
376
+
377
+ //#endregion
378
+ export { devtools, persist, shallowEqual, subscribeKey, withHistory };
212
379
  //# sourceMappingURL=index.mjs.map