@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.
- package/dist/equality-5F2bPn7E.d.mts +13 -0
- package/dist/equality-5F2bPn7E.d.mts.map +1 -0
- package/dist/equality-BA46H9AL.mjs +27 -0
- package/dist/equality-BA46H9AL.mjs.map +1 -0
- package/dist/equality-C1s0kqxg.d.cts +13 -0
- package/dist/equality-C1s0kqxg.d.cts.map +1 -0
- package/dist/equality-Cz6riknL.cjs +33 -0
- package/dist/equality-Cz6riknL.cjs.map +1 -0
- package/dist/index.cjs +3 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -11
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +2 -12
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -26
- package/dist/index.mjs.map +1 -1
- package/dist/react/react.cjs +30 -1
- package/dist/react/react.cjs.map +1 -1
- package/dist/react/react.d.cts +26 -1
- package/dist/react/react.d.cts.map +1 -1
- package/dist/react/react.d.mts +27 -2
- package/dist/react/react.d.mts.map +1 -1
- package/dist/react/react.mjs +31 -3
- package/dist/react/react.mjs.map +1 -1
- package/dist/{snapshot-P0QPV1ER.mjs → snapshot-COzEerMu.mjs} +27 -2
- package/dist/snapshot-COzEerMu.mjs.map +1 -0
- package/dist/{snapshot-BKVFJLuo.cjs → snapshot-CbVbxG7s.cjs} +27 -2
- package/dist/snapshot-CbVbxG7s.cjs.map +1 -0
- package/dist/{types-vWYkF3tH.d.mts → types-Cf8Fp7kA.d.mts} +1 -1
- package/dist/{types-vWYkF3tH.d.mts.map → types-Cf8Fp7kA.d.mts.map} +1 -1
- package/dist/utils/index.cjs +176 -5
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.cts +89 -1
- package/dist/utils/index.d.cts.map +1 -1
- package/dist/utils/index.d.mts +89 -1
- package/dist/utils/index.d.mts.map +1 -1
- package/dist/utils/index.mjs +173 -6
- package/dist/utils/index.mjs.map +1 -1
- package/package.json +4 -3
- package/dist/snapshot-BKVFJLuo.cjs.map +0 -1
- package/dist/snapshot-P0QPV1ER.mjs.map +0 -1
package/dist/utils/index.cjs.map
CHANGED
|
@@ -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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"}
|
package/dist/utils/index.d.cts
CHANGED
|
@@ -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
|
-
|
|
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":";;
|
|
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"}
|
package/dist/utils/index.d.mts
CHANGED
|
@@ -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
|
-
|
|
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":";;
|
|
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"}
|
package/dist/utils/index.mjs
CHANGED
|
@@ -1,5 +1,137 @@
|
|
|
1
|
-
import { a as subscribe, s as findGetterDescriptor, t as snapshot } from "../snapshot-
|
|
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(
|
|
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)
|
|
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
|
-
|
|
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
|