use-storage-persisted-state 1.0.2 → 1.0.3

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/index.d.mts CHANGED
@@ -105,13 +105,13 @@ declare function useStoragePersistedState<T>(key: string, defaultValue: null | u
105
105
  *
106
106
  * Returns the provided defaultValue when the key is missing or parsing fails.
107
107
  */
108
- declare function readStoragePersistedState<T>(key: string, defaultValue: Exclude<T, null | undefined>, options?: StoragePersistedStateOptions<T>): T;
108
+ declare function getStoragePersistedState<T>(key: string, defaultValue: Exclude<T, null | undefined>, options?: StoragePersistedStateOptions<T>): T;
109
109
  /**
110
110
  * Read a persisted value from storage using an explicit codec.
111
111
  *
112
112
  * Use this overload when defaultValue is null or undefined.
113
113
  */
114
- declare function readStoragePersistedState<T>(key: string, defaultValue: null | undefined, options: StoragePersistedStateOptions<T> & {
114
+ declare function getStoragePersistedState<T>(key: string, defaultValue: null | undefined, options: StoragePersistedStateOptions<T> & {
115
115
  codec: Codec<T>;
116
116
  }): T | null;
117
117
  /**
@@ -128,5 +128,11 @@ declare function setStoragePersistedState<T>(key: string, newValue: Exclude<T, n
128
128
  declare function setStoragePersistedState<T>(key: string, newValue: T | ((prev: T | null) => T), options: StoragePersistedStateOptions<T> & {
129
129
  codec: Codec<T>;
130
130
  }): void;
131
+ /**
132
+ * Remove a persisted value from storage and notify active hooks for the same key.
133
+ *
134
+ * Defaults to localStorage when no storageType is provided.
135
+ */
136
+ declare function removeStoragePersistedState(key: string, storageType?: StorageType): void;
131
137
 
132
- export { BooleanCodec, type Codec, JsonCodec, NumberCodec, type StoragePersistedStateOptions, type StorageType, StringCodec, inferCodec, readStoragePersistedState, setStoragePersistedState, useStoragePersistedState };
138
+ export { BooleanCodec, type Codec, JsonCodec, NumberCodec, type StoragePersistedStateOptions, type StorageType, StringCodec, getStoragePersistedState, inferCodec, getStoragePersistedState as readStoragePersistedState, removeStoragePersistedState, setStoragePersistedState, useStoragePersistedState };
package/dist/index.d.ts CHANGED
@@ -105,13 +105,13 @@ declare function useStoragePersistedState<T>(key: string, defaultValue: null | u
105
105
  *
106
106
  * Returns the provided defaultValue when the key is missing or parsing fails.
107
107
  */
108
- declare function readStoragePersistedState<T>(key: string, defaultValue: Exclude<T, null | undefined>, options?: StoragePersistedStateOptions<T>): T;
108
+ declare function getStoragePersistedState<T>(key: string, defaultValue: Exclude<T, null | undefined>, options?: StoragePersistedStateOptions<T>): T;
109
109
  /**
110
110
  * Read a persisted value from storage using an explicit codec.
111
111
  *
112
112
  * Use this overload when defaultValue is null or undefined.
113
113
  */
114
- declare function readStoragePersistedState<T>(key: string, defaultValue: null | undefined, options: StoragePersistedStateOptions<T> & {
114
+ declare function getStoragePersistedState<T>(key: string, defaultValue: null | undefined, options: StoragePersistedStateOptions<T> & {
115
115
  codec: Codec<T>;
116
116
  }): T | null;
117
117
  /**
@@ -128,5 +128,11 @@ declare function setStoragePersistedState<T>(key: string, newValue: Exclude<T, n
128
128
  declare function setStoragePersistedState<T>(key: string, newValue: T | ((prev: T | null) => T), options: StoragePersistedStateOptions<T> & {
129
129
  codec: Codec<T>;
130
130
  }): void;
131
+ /**
132
+ * Remove a persisted value from storage and notify active hooks for the same key.
133
+ *
134
+ * Defaults to localStorage when no storageType is provided.
135
+ */
136
+ declare function removeStoragePersistedState(key: string, storageType?: StorageType): void;
131
137
 
132
- export { BooleanCodec, type Codec, JsonCodec, NumberCodec, type StoragePersistedStateOptions, type StorageType, StringCodec, inferCodec, readStoragePersistedState, setStoragePersistedState, useStoragePersistedState };
138
+ export { BooleanCodec, type Codec, JsonCodec, NumberCodec, type StoragePersistedStateOptions, type StorageType, StringCodec, getStoragePersistedState, inferCodec, getStoragePersistedState as readStoragePersistedState, removeStoragePersistedState, setStoragePersistedState, useStoragePersistedState };
package/dist/index.js CHANGED
@@ -26,8 +26,10 @@ __export(index_exports, {
26
26
  JsonCodec: () => JsonCodec,
27
27
  NumberCodec: () => NumberCodec,
28
28
  StringCodec: () => StringCodec,
29
+ getStoragePersistedState: () => getStoragePersistedState,
29
30
  inferCodec: () => inferCodec,
30
- readStoragePersistedState: () => readStoragePersistedState,
31
+ readStoragePersistedState: () => getStoragePersistedState,
32
+ removeStoragePersistedState: () => removeStoragePersistedState,
31
33
  setStoragePersistedState: () => setStoragePersistedState,
32
34
  useStoragePersistedState: () => useStoragePersistedState
33
35
  });
@@ -445,7 +447,7 @@ function resolveCodec(key, valueHint, options) {
445
447
  }
446
448
  return inferCodec(valueHint);
447
449
  }
448
- function readStoragePersistedState(key, defaultValue, options = {}) {
450
+ function getStoragePersistedState(key, defaultValue, options = {}) {
449
451
  const syncManager = getSyncManager(options.storageType);
450
452
  const adapter = syncManager.storage;
451
453
  const codec = resolveCodec(key, defaultValue, options);
@@ -493,14 +495,26 @@ function setStoragePersistedState(key, newValueOrFn, options = {}) {
493
495
  console.error(`Error setting storage key "${key}":`, error);
494
496
  }
495
497
  }
498
+ function removeStoragePersistedState(key, storageType) {
499
+ const syncManager = getSyncManager(storageType);
500
+ const adapter = syncManager.storage;
501
+ try {
502
+ adapter.removeItem(key);
503
+ syncManager.notify(key);
504
+ } catch (error) {
505
+ console.error(`Error removing storage key "${key}":`, error);
506
+ }
507
+ }
496
508
  // Annotate the CommonJS export names for ESM import in node:
497
509
  0 && (module.exports = {
498
510
  BooleanCodec,
499
511
  JsonCodec,
500
512
  NumberCodec,
501
513
  StringCodec,
514
+ getStoragePersistedState,
502
515
  inferCodec,
503
516
  readStoragePersistedState,
517
+ removeStoragePersistedState,
504
518
  setStoragePersistedState,
505
519
  useStoragePersistedState
506
520
  });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/useStoragePersistedState.ts","../src/codecs.ts","../src/storage.ts","../src/storagePersistedState.ts"],"sourcesContent":["export { useStoragePersistedState } from \"./useStoragePersistedState\";\nexport type {\n StoragePersistedStateOptions,\n StorageType,\n} from \"./useStoragePersistedState\";\nexport type { Codec } from \"./codecs\";\nexport {\n JsonCodec,\n StringCodec,\n BooleanCodec,\n NumberCodec,\n inferCodec,\n} from \"./codecs\";\nexport {\n readStoragePersistedState,\n setStoragePersistedState,\n} from \"./storagePersistedState\";\n","\"use client\";\n\nimport { useCallback, useMemo, useRef } from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim\";\nimport { Codec, inferCodec } from \"./codecs\";\nimport {\n localStorageSync,\n memoryStorageSync,\n sessionStorageSync,\n} from \"./storage\";\n\nexport type StorageType = \"localStorage\" | \"sessionStorage\" | \"memory\";\n\n/**\n * Options for the useStoragePersistedState hook\n */\nexport interface StoragePersistedStateOptions<T> {\n /**\n * Explicit codec for when defaultValue is null or undefined, for complex types, or special cases (e.g., data migration on read).\n * If not provided, codec is inferred from defaultValue type.\n */\n codec?: Codec<T>;\n /**\n * Storage type to use: 'localStorage' (default), 'sessionStorage', or 'memory'.\n */\n storageType?: StorageType;\n /**\n * Enable cross-tab synchronization via the StorageEvent. Note: disabling this will not stop other tabs from updating localStorage, but this hook will not automatically respond to those changes.\n * defaults to true.\n */\n crossTabSync?: boolean;\n /**\n * Polling interval in milliseconds for detecting storage changes made outside of React (e.g., DevTools, direct localStorage manipulation). Set to null to disable polling.\n * defaults to 2000ms.\n */\n pollingIntervalMs?: number | null;\n}\n\n// Overload 1: Default provided, T inferred\nexport function useStoragePersistedState<T>(\n key: string,\n defaultValue: Exclude<T, null | undefined>,\n options?: StoragePersistedStateOptions<T>,\n): [T, (newValue: T | ((prev: T) => T)) => void, () => void];\n\n// Overload 2: Explicit Codec provided, defaultValue can be null or undefined\nexport function useStoragePersistedState<T>(\n key: string,\n defaultValue: null | undefined,\n options: StoragePersistedStateOptions<T> & { codec: Codec<T> },\n): [T | null, (newValue: T | ((prev: T) => T)) => void, () => void];\n\n// Implementation\nexport function useStoragePersistedState<T>(\n key: string,\n defaultValue: T,\n options: StoragePersistedStateOptions<T> = {},\n) {\n const syncManager =\n options.storageType === \"sessionStorage\"\n ? sessionStorageSync\n : options.storageType === \"memory\"\n ? memoryStorageSync\n : localStorageSync;\n const adapter = syncManager.storage;\n\n // 1. Determine the codec.\n // If no explicit codec is passed, we try to infer it from defaultValue.\n // If defaultValue is undefined and no codec is passed, we fall back to JSON.\n const codec = useMemo(() => {\n if (options?.codec) return options.codec;\n return inferCodec(defaultValue);\n }, [defaultValue, options?.codec]);\n\n if ((defaultValue === undefined || defaultValue === null) && !options.codec) {\n console.warn(\n `useStorage: Key \"${key}\" uses undefined or null default without explicit Codec. defaulting to JSON.`,\n );\n }\n\n // Memoize the decoded value to prevent infinite loops in useSyncExternalStore\n // when the codec returns a new object reference (e.g. JSON.parse).\n const lastRaw = useRef<string | null>(null); // string | null, because storage returns null for missing keys\n const lastParsed = useRef<T | undefined>(undefined);\n\n const getSnapshot = useCallback(() => {\n const raw = adapter.getItem(key);\n\n // Return default if storage is missing the key\n if (raw === null && defaultValue !== undefined) {\n return defaultValue as T;\n }\n\n // If raw value matches cache, return cached object.\n if (raw === lastRaw.current) {\n if (lastParsed.current === undefined) {\n return null as T;\n }\n return lastParsed.current as T;\n }\n\n // Decode new raw value\n try {\n const decoded = codec.decode(raw);\n\n lastRaw.current = raw;\n lastParsed.current = decoded;\n return decoded;\n } catch (e) {\n console.error(`Error parsing storage key \"${key}\"`, e);\n return defaultValue as T;\n }\n }, [adapter, key, codec, defaultValue]);\n\n // 2. Subscribe to the external store (Local/SessionStorage + Polling/Events)\n // useSyncExternalStore handles the hydration mismatch automatically by\n // taking a `getServerSnapshot` (returning defaultValue).\n const value = useSyncExternalStore(\n (callback) =>\n syncManager.subscribe(key, callback, {\n crossTabSync: options.crossTabSync,\n pollingIntervalMs: options.pollingIntervalMs,\n }),\n getSnapshot,\n () => defaultValue as T, // Server Snapshot\n );\n\n // 3. Create the Setter\n const setValue = useCallback(\n (newValueOrFn: T | ((prev: T) => T)) => {\n try {\n const currentRaw = adapter.getItem(key);\n const current =\n currentRaw !== null ? codec.decode(currentRaw) : defaultValue;\n\n const newValue =\n newValueOrFn instanceof Function\n ? newValueOrFn(current)\n : newValueOrFn;\n\n if (newValue === undefined || newValue === null) {\n lastRaw.current = null;\n lastParsed.current = undefined;\n adapter.removeItem(key);\n } else {\n const encoded = codec.encode(newValue);\n lastRaw.current = encoded;\n lastParsed.current = newValue;\n\n if (encoded === null || encoded === undefined) {\n adapter.removeItem(key);\n } else {\n adapter.setItem(key, encoded);\n }\n }\n\n // Notify other hooks/tabs\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error setting storage key \"${key}\":`, error);\n }\n },\n [adapter, key, codec, defaultValue, syncManager],\n );\n\n const removeItem = useCallback(() => {\n try {\n lastRaw.current = null;\n lastParsed.current = undefined;\n adapter.removeItem(key);\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error removing storage key \"${key}\":`, error);\n }\n }, [adapter, key, syncManager]);\n\n return [value, setValue, removeItem] as const;\n}\n","/**\n * Defines how to serialize/deserialize a value from localStorage.\n * null means the key doesn't exist.\n */\nexport interface Codec<T> {\n encode: (value: T) => string | null;\n decode: (value: string | null) => T;\n}\n\n/**\n * A robust JSON codec that handles parsing errors gracefully.\n * Works with objects, arrays, and other JSON-serializable values.\n *\n * @example\n * ```ts\n * const [user, setUser] = useStoragePersistedState<User | null>(\n * \"user\",\n * null,\n * { codec: JsonCodec }\n * );\n * ```\n * any type is needed to allow objects and arrays of any shape.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const JsonCodec: Codec<any> = {\n encode: (value) => {\n if (value === null) return null;\n return JSON.stringify(value);\n },\n decode: (value) => {\n if (value === null) return null;\n try {\n return JSON.parse(value);\n } catch (e) {\n console.warn(`LocalStorage parse error for value \"${value}\".`, e);\n throw e;\n }\n },\n};\n\n/**\n * Codec for string values. Stores strings as-is without any transformation.\n *\n * @example\n * ```ts\n * const [name, setName] = useStoragePersistedState(\"name\", \"Guest\");\n * // Automatically uses StringCodec because defaultValue is a string\n * ```\n *\n * any type is needed to allow string enums.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const StringCodec: Codec<any> = {\n encode: (value) => {\n if (value == null) return null;\n if (typeof value === \"string\") return value;\n console.error(\n `StringCodec encode expected a string but got ${typeof value}.`,\n );\n return String(value);\n },\n decode: (value) => {\n if (value == null) return null;\n if (typeof value === \"string\") return value;\n console.error(\n `StringCodec decode expected a string but got ${typeof value}.`,\n );\n return String(value);\n },\n};\n\n/**\n * Codec for boolean values. Stores as \"true\" or \"false\" strings.\n *\n * @example\n * ```ts\n * const [isDark, setIsDark] = useStoragePersistedState(\"darkMode\", false);\n * // Automatically uses BooleanCodec because defaultValue is a boolean\n * ```\n */\nexport const BooleanCodec: Codec<boolean> = {\n encode: (value) => (value ? \"true\" : \"false\"),\n decode: (value) => value === \"true\",\n};\n\n/**\n * Codec for number values. Handles NaN, Infinity, and -Infinity correctly.\n * Returns null for unparseable values (e.g., empty string, invalid number string).\n *\n * @example\n * ```ts\n * const [count, setCount] = useStoragePersistedState(\"count\", 0);\n * // Automatically uses NumberCodec because defaultValue is a number\n * ```\n *\n * any type is needed to allow number enums.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const NumberCodec: Codec<any> = {\n encode: (value) => {\n if (value === null) return null;\n if (typeof value !== \"number\") {\n console.error(\n `NumberCodec encode expected a number but got ${typeof value}.`,\n );\n }\n return String(value);\n },\n decode: (value) => {\n if (value === null) return null;\n // Handle special numeric values\n if (value === \"NaN\") return NaN;\n if (value === \"Infinity\") return Infinity;\n if (value === \"-Infinity\") return -Infinity;\n if (value === \"\") {\n console.warn(\"NumberCodec decode received an empty string.\");\n return null;\n }\n const parsed = Number(value);\n if (Number.isNaN(parsed)) {\n console.warn(\n `NumberCodec decode received an invalid number string \"${value}\".`,\n );\n return null;\n }\n return parsed;\n },\n};\n\n/**\n * Infers the appropriate codec based on the default value's type.\n * Used internally when the user doesn't provide an explicit codec.\n *\n * - `boolean` → BooleanCodec\n * - `number` → NumberCodec\n * - `string` → StringCodec\n * - `object`, `array`, `undefined`, `null` → JsonCodec\n *\n * @param defaultValue - The default value to infer the codec from\n * @returns The inferred codec for the given value type\n */\nexport function inferCodec<T>(defaultValue: T): Codec<T> {\n const type = typeof defaultValue;\n\n // Type assertions through unknown are needed because we're doing runtime type inference\n // The actual codec returned will match the runtime type of defaultValue\n if (type === \"boolean\") return BooleanCodec as unknown as Codec<T>;\n if (type === \"number\") return NumberCodec as unknown as Codec<T>;\n if (type === \"string\") return StringCodec as unknown as Codec<T>;\n\n // Default to JSON for objects, arrays, or undefined\n return JsonCodec as unknown as Codec<T>;\n}\n","\"use client\";\n\n/**\n * Interface allows swapping LocalStorage for SessionStorage or Async Storage later\n */\nexport interface StorageAdapter {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nexport interface SubscribeOptions {\n crossTabSync?: boolean;\n pollingIntervalMs?: number | null;\n}\n\ninterface ListenerOptions {\n crossTabSync: boolean;\n pollingIntervalMs: number | null;\n}\n\ninterface StorageSyncManagerOptions {\n enableCrossTabSync?: boolean;\n enablePolling?: boolean;\n}\n\nclass MemoryStorageAdapter implements StorageAdapter {\n private store = new Map<string, string>();\n\n getItem(key: string) {\n return this.store.has(key) ? this.store.get(key)! : null;\n }\n\n setItem(key: string, value: string) {\n this.store.set(key, value);\n }\n\n removeItem(key: string) {\n this.store.delete(key);\n }\n}\n\nclass FallbackStorageAdapter implements StorageAdapter {\n constructor(\n private primary: StorageAdapter,\n private fallback: StorageAdapter,\n ) {}\n\n getItem(key: string) {\n const fallbackValue = this.fallback.getItem(key);\n if (fallbackValue !== null) return fallbackValue;\n return this.primary.getItem(key);\n }\n\n setItem(key: string, value: string) {\n try {\n this.primary.setItem(key, value);\n this.fallback.removeItem(key);\n } catch {\n this.fallback.setItem(key, value);\n }\n }\n\n removeItem(key: string) {\n try {\n this.primary.removeItem(key);\n } finally {\n this.fallback.removeItem(key);\n }\n }\n}\n\n// Singleton manager ensures we only have ONE listener per key globally\nclass StorageSyncManager {\n private listeners = new Map<string, Map<() => void, ListenerOptions>>();\n private pollingIntervalId: number | null = null;\n private pollingIntervalMsActive: number | null = null;\n private enableCrossTabSync: boolean;\n private enablePolling: boolean;\n\n constructor(\n public readonly storage: StorageAdapter,\n private defaultPollingIntervalMs = 2000,\n options: StorageSyncManagerOptions = {},\n ) {\n this.enableCrossTabSync = options.enableCrossTabSync ?? true;\n this.enablePolling = options.enablePolling ?? true;\n\n if (typeof window !== \"undefined\" && this.enableCrossTabSync) {\n // 1. Cross-tab sync (\"storage\" event is a built-in browser feature that fires\n // when localStorage/sessionStorage changes in ANOTHER tab)\n // We do not remove this listener because this manager is a singleton meant to last\n // the entire application lifecycle. It is effectively cleaned up when the page unloads.\n window.addEventListener(\"storage\", (e) => {\n if (e.key && this.listeners.has(e.key)) {\n this.notifyCrossTab(e.key);\n }\n });\n // 2. Start polling for DevTools or direct window.localStorage changes (robustness)\n // We only poll if there are listeners. Lazy start in subscribe.\n }\n }\n\n subscribe(key: string, callback: () => void, options: SubscribeOptions = {}) {\n if (!this.listeners.has(key)) {\n this.listeners.set(key, new Map());\n }\n const listenerOptions: ListenerOptions = {\n crossTabSync: this.enableCrossTabSync && (options.crossTabSync ?? true),\n pollingIntervalMs: this.enablePolling\n ? this.normalizePollingInterval(options.pollingIntervalMs)\n : null,\n };\n this.listeners.get(key)!.set(callback, listenerOptions);\n\n this.updatePollingInterval();\n\n return () => {\n const listenersForKey = this.listeners.get(key);\n listenersForKey?.delete(callback);\n if (listenersForKey?.size === 0) {\n this.listeners.delete(key);\n }\n this.updatePollingInterval();\n };\n }\n\n // Called when WE change the value in this tab\n notify(key: string) {\n this.notifyListeners(key);\n }\n\n // Keep a cache of values to detect changes during polling\n private snapshotCache = new Map<string, string | null>();\n\n private notifyListeners(\n key: string,\n predicate?: (options: ListenerOptions) => boolean,\n ) {\n this.listeners.get(key)?.forEach((options, cb) => {\n if (!predicate || predicate(options)) {\n cb();\n }\n });\n }\n\n private notifyCrossTab(key: string) {\n this.notifyListeners(key, (options) => options.crossTabSync);\n }\n\n private notifyPolling(key: string) {\n this.notifyListeners(key, (options) => options.pollingIntervalMs !== null);\n }\n\n private normalizePollingInterval(\n pollingIntervalMs: number | null | undefined,\n ) {\n if (pollingIntervalMs === null) return null;\n if (pollingIntervalMs === undefined) return this.defaultPollingIntervalMs;\n if (pollingIntervalMs <= 0 || Number.isNaN(pollingIntervalMs)) {\n return null;\n }\n return pollingIntervalMs;\n }\n\n private getMinPollingIntervalMs() {\n let minInterval: number | null = null;\n this.listeners.forEach((listenersForKey) => {\n listenersForKey.forEach((options) => {\n if (options.pollingIntervalMs === null) return;\n if (minInterval === null || options.pollingIntervalMs < minInterval) {\n minInterval = options.pollingIntervalMs;\n }\n });\n });\n return minInterval;\n }\n\n private updatePollingInterval() {\n if (typeof window === \"undefined\") return;\n if (!this.enablePolling) return;\n\n const nextInterval = this.getMinPollingIntervalMs();\n if (nextInterval === null) {\n if (this.pollingIntervalId) {\n clearInterval(this.pollingIntervalId);\n }\n this.pollingIntervalId = null;\n this.pollingIntervalMsActive = null;\n this.snapshotCache.clear();\n return;\n }\n\n if (\n this.pollingIntervalId &&\n this.pollingIntervalMsActive === nextInterval\n ) {\n return;\n }\n\n if (this.pollingIntervalId) {\n clearInterval(this.pollingIntervalId);\n }\n\n this.pollingIntervalId = window.setInterval(() => {\n this.listeners.forEach((listenersForKey, key) => {\n const shouldPoll = Array.from(listenersForKey.values()).some(\n (options) => options.pollingIntervalMs !== null,\n );\n if (!shouldPoll) return;\n\n const currentValue = this.storage.getItem(key);\n const lastValue = this.snapshotCache.get(key);\n\n if (currentValue !== lastValue) {\n this.snapshotCache.set(key, currentValue);\n this.notifyPolling(key);\n }\n });\n }, nextInterval);\n this.pollingIntervalMsActive = nextInterval;\n }\n}\n\n// Lazy-initialized singletons to avoid SSR crashes (window is not defined on server)\nconst localStorageFallback = new MemoryStorageAdapter();\nconst sessionStorageFallback = new MemoryStorageAdapter();\n\nlet _localStorageSync: StorageSyncManager | null = null;\nlet _sessionStorageSync: StorageSyncManager | null = null;\nlet _memoryStorageSync: StorageSyncManager | null = null;\n\nfunction getLocalStorageSync(): StorageSyncManager {\n if (!_localStorageSync) {\n if (typeof window !== \"undefined\") {\n _localStorageSync = new StorageSyncManager(\n new FallbackStorageAdapter(window.localStorage, localStorageFallback),\n );\n } else {\n // SSR fallback - use memory storage\n return getMemoryStorageSync();\n }\n }\n return _localStorageSync;\n}\n\nfunction getSessionStorageSync(): StorageSyncManager {\n if (!_sessionStorageSync) {\n if (typeof window !== \"undefined\") {\n _sessionStorageSync = new StorageSyncManager(\n new FallbackStorageAdapter(\n window.sessionStorage,\n sessionStorageFallback,\n ),\n );\n } else {\n // SSR fallback - use memory storage\n return getMemoryStorageSync();\n }\n }\n return _sessionStorageSync;\n}\n\nfunction getMemoryStorageSync(): StorageSyncManager {\n if (!_memoryStorageSync) {\n _memoryStorageSync = new StorageSyncManager(\n new MemoryStorageAdapter(),\n 2000,\n {\n enableCrossTabSync: false,\n enablePolling: false,\n },\n );\n }\n return _memoryStorageSync;\n}\n\n/**\n * Proxy object for localStorage sync manager with lazy initialization.\n * Safe to import in SSR environments.\n */\nexport const localStorageSync = {\n get storage() {\n return getLocalStorageSync().storage;\n },\n subscribe(key: string, callback: () => void, options?: SubscribeOptions) {\n return getLocalStorageSync().subscribe(key, callback, options);\n },\n notify(key: string) {\n return getLocalStorageSync().notify(key);\n },\n};\n\n/**\n * Proxy object for sessionStorage sync manager with lazy initialization.\n * Safe to import in SSR environments.\n */\nexport const sessionStorageSync = {\n get storage() {\n return getSessionStorageSync().storage;\n },\n subscribe(key: string, callback: () => void, options?: SubscribeOptions) {\n return getSessionStorageSync().subscribe(key, callback, options);\n },\n notify(key: string) {\n return getSessionStorageSync().notify(key);\n },\n};\n\n/**\n * Proxy object for memory storage sync manager with lazy initialization.\n * Safe to import in SSR environments.\n */\nexport const memoryStorageSync = {\n get storage() {\n return getMemoryStorageSync().storage;\n },\n subscribe(key: string, callback: () => void, options?: SubscribeOptions) {\n return getMemoryStorageSync().subscribe(key, callback, options);\n },\n notify(key: string) {\n return getMemoryStorageSync().notify(key);\n },\n};\n","\"use client\";\n\nimport { Codec, inferCodec, JsonCodec } from \"./codecs\";\nimport {\n localStorageSync,\n memoryStorageSync,\n sessionStorageSync,\n} from \"./storage\";\nimport {\n StoragePersistedStateOptions,\n StorageType,\n} from \"./useStoragePersistedState\";\n\nfunction getSyncManager(storageType: StorageType | undefined) {\n if (storageType === \"sessionStorage\") return sessionStorageSync;\n if (storageType === \"memory\") return memoryStorageSync;\n return localStorageSync;\n}\n\nfunction resolveCodec<T>(\n key: string,\n valueHint: T,\n options?: StoragePersistedStateOptions<T>,\n): Codec<T> {\n if (options?.codec) return options.codec;\n\n if ((valueHint === undefined || valueHint === null) && !options?.codec) {\n console.warn(\n `storagePersistedState: Key \"${key}\" uses undefined or null default without explicit Codec. defaulting to JSON.`,\n );\n }\n\n return inferCodec(valueHint);\n}\n\n// Overload 1: Default provided, T inferred\n/**\n * Read a persisted value from storage using the same codec behavior as the hook.\n *\n * Returns the provided defaultValue when the key is missing or parsing fails.\n */\nexport function readStoragePersistedState<T>(\n key: string,\n defaultValue: Exclude<T, null | undefined>,\n options?: StoragePersistedStateOptions<T>,\n): T;\n\n// Overload 2: Explicit Codec provided, defaultValue can be null or undefined\n/**\n * Read a persisted value from storage using an explicit codec.\n *\n * Use this overload when defaultValue is null or undefined.\n */\nexport function readStoragePersistedState<T>(\n key: string,\n defaultValue: null | undefined,\n options: StoragePersistedStateOptions<T> & { codec: Codec<T> },\n): T | null;\n\nexport function readStoragePersistedState<T>(\n key: string,\n defaultValue: T,\n options: StoragePersistedStateOptions<T> = {},\n) {\n const syncManager = getSyncManager(options.storageType);\n const adapter = syncManager.storage;\n const codec = resolveCodec(key, defaultValue, options);\n const raw = adapter.getItem(key);\n\n if (raw === null && defaultValue !== undefined) {\n return defaultValue as T;\n }\n\n try {\n return codec.decode(raw);\n } catch (error) {\n console.error(`Error parsing storage key \"${key}\"`, error);\n return defaultValue as T;\n }\n}\n\n// Overload 1: Default provided, T inferred\n/**\n * Set a persisted value in storage and notify active hooks for the same key.\n *\n * Supports functional updates using the current decoded value.\n */\nexport function setStoragePersistedState<T>(\n key: string,\n newValue: Exclude<T, null | undefined>,\n options?: StoragePersistedStateOptions<T>,\n): void;\n\n// Overload 2: Explicit Codec provided, newValue can be null or undefined\n/**\n * Set a persisted value in storage using an explicit codec and notify listeners.\n *\n * Use this overload when the new value is null or undefined or when you want custom serialization.\n */\nexport function setStoragePersistedState<T>(\n key: string,\n newValue: T | ((prev: T | null) => T),\n options: StoragePersistedStateOptions<T> & { codec: Codec<T> },\n): void;\n\nexport function setStoragePersistedState<T>(\n key: string,\n newValueOrFn: T | ((prev: T | null) => T),\n options: StoragePersistedStateOptions<T> = {},\n) {\n const syncManager = getSyncManager(options.storageType);\n const adapter = syncManager.storage;\n\n let codec: Codec<T>;\n if (!(newValueOrFn instanceof Function)) {\n codec = resolveCodec(key, newValueOrFn, options);\n } else {\n // For functional updates, we cannot infer from newValue\n if (!options.codec) {\n console.warn(\n `storagePersistedState: Key \"${key}\" uses functional update without explicit Codec. defaulting to JSON.`,\n );\n }\n codec = options.codec ?? (JsonCodec as unknown as Codec<T>);\n }\n\n try {\n const currentRaw = adapter.getItem(key);\n const current = currentRaw !== null ? codec.decode(currentRaw) : null;\n\n const newValue =\n newValueOrFn instanceof Function ? newValueOrFn(current) : newValueOrFn;\n\n if (newValue === undefined || newValue === null) {\n adapter.removeItem(key);\n } else {\n const encoded = codec.encode(newValue);\n if (encoded === null || encoded === undefined) {\n adapter.removeItem(key);\n } else {\n adapter.setItem(key, encoded);\n }\n }\n\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error setting storage key \"${key}\":`, error);\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/useStoragePersistedState.ts","../src/codecs.ts","../src/storage.ts","../src/storagePersistedState.ts"],"sourcesContent":["export { useStoragePersistedState } from \"./useStoragePersistedState\";\nexport type {\n StoragePersistedStateOptions,\n StorageType,\n} from \"./useStoragePersistedState\";\nexport type { Codec } from \"./codecs\";\nexport {\n JsonCodec,\n StringCodec,\n BooleanCodec,\n NumberCodec,\n inferCodec,\n} from \"./codecs\";\nexport {\n getStoragePersistedState,\n getStoragePersistedState as readStoragePersistedState,\n setStoragePersistedState,\n removeStoragePersistedState,\n} from \"./storagePersistedState\";\n","\"use client\";\n\nimport { useCallback, useMemo, useRef } from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim\";\nimport { Codec, inferCodec } from \"./codecs\";\nimport {\n localStorageSync,\n memoryStorageSync,\n sessionStorageSync,\n} from \"./storage\";\n\nexport type StorageType = \"localStorage\" | \"sessionStorage\" | \"memory\";\n\n/**\n * Options for the useStoragePersistedState hook\n */\nexport interface StoragePersistedStateOptions<T> {\n /**\n * Explicit codec for when defaultValue is null or undefined, for complex types, or special cases (e.g., data migration on read).\n * If not provided, codec is inferred from defaultValue type.\n */\n codec?: Codec<T>;\n /**\n * Storage type to use: 'localStorage' (default), 'sessionStorage', or 'memory'.\n */\n storageType?: StorageType;\n /**\n * Enable cross-tab synchronization via the StorageEvent. Note: disabling this will not stop other tabs from updating localStorage, but this hook will not automatically respond to those changes.\n * defaults to true.\n */\n crossTabSync?: boolean;\n /**\n * Polling interval in milliseconds for detecting storage changes made outside of React (e.g., DevTools, direct localStorage manipulation). Set to null to disable polling.\n * defaults to 2000ms.\n */\n pollingIntervalMs?: number | null;\n}\n\n// Overload 1: Default provided, T inferred\nexport function useStoragePersistedState<T>(\n key: string,\n defaultValue: Exclude<T, null | undefined>,\n options?: StoragePersistedStateOptions<T>,\n): [T, (newValue: T | ((prev: T) => T)) => void, () => void];\n\n// Overload 2: Explicit Codec provided, defaultValue can be null or undefined\nexport function useStoragePersistedState<T>(\n key: string,\n defaultValue: null | undefined,\n options: StoragePersistedStateOptions<T> & { codec: Codec<T> },\n): [T | null, (newValue: T | ((prev: T) => T)) => void, () => void];\n\n// Implementation\nexport function useStoragePersistedState<T>(\n key: string,\n defaultValue: T,\n options: StoragePersistedStateOptions<T> = {},\n) {\n const syncManager =\n options.storageType === \"sessionStorage\"\n ? sessionStorageSync\n : options.storageType === \"memory\"\n ? memoryStorageSync\n : localStorageSync;\n const adapter = syncManager.storage;\n\n // 1. Determine the codec.\n // If no explicit codec is passed, we try to infer it from defaultValue.\n // If defaultValue is undefined and no codec is passed, we fall back to JSON.\n const codec = useMemo(() => {\n if (options?.codec) return options.codec;\n return inferCodec(defaultValue);\n }, [defaultValue, options?.codec]);\n\n if ((defaultValue === undefined || defaultValue === null) && !options.codec) {\n console.warn(\n `useStorage: Key \"${key}\" uses undefined or null default without explicit Codec. defaulting to JSON.`,\n );\n }\n\n // Memoize the decoded value to prevent infinite loops in useSyncExternalStore\n // when the codec returns a new object reference (e.g. JSON.parse).\n const lastRaw = useRef<string | null>(null); // string | null, because storage returns null for missing keys\n const lastParsed = useRef<T | undefined>(undefined);\n\n const getSnapshot = useCallback(() => {\n const raw = adapter.getItem(key);\n\n // Return default if storage is missing the key\n if (raw === null && defaultValue !== undefined) {\n return defaultValue as T;\n }\n\n // If raw value matches cache, return cached object.\n if (raw === lastRaw.current) {\n if (lastParsed.current === undefined) {\n return null as T;\n }\n return lastParsed.current as T;\n }\n\n // Decode new raw value\n try {\n const decoded = codec.decode(raw);\n\n lastRaw.current = raw;\n lastParsed.current = decoded;\n return decoded;\n } catch (e) {\n console.error(`Error parsing storage key \"${key}\"`, e);\n return defaultValue as T;\n }\n }, [adapter, key, codec, defaultValue]);\n\n // 2. Subscribe to the external store (Local/SessionStorage + Polling/Events)\n // useSyncExternalStore handles the hydration mismatch automatically by\n // taking a `getServerSnapshot` (returning defaultValue).\n const value = useSyncExternalStore(\n (callback) =>\n syncManager.subscribe(key, callback, {\n crossTabSync: options.crossTabSync,\n pollingIntervalMs: options.pollingIntervalMs,\n }),\n getSnapshot,\n () => defaultValue as T, // Server Snapshot\n );\n\n // 3. Create the Setter\n const setValue = useCallback(\n (newValueOrFn: T | ((prev: T) => T)) => {\n try {\n const currentRaw = adapter.getItem(key);\n const current =\n currentRaw !== null ? codec.decode(currentRaw) : defaultValue;\n\n const newValue =\n newValueOrFn instanceof Function\n ? newValueOrFn(current)\n : newValueOrFn;\n\n if (newValue === undefined || newValue === null) {\n lastRaw.current = null;\n lastParsed.current = undefined;\n adapter.removeItem(key);\n } else {\n const encoded = codec.encode(newValue);\n lastRaw.current = encoded;\n lastParsed.current = newValue;\n\n if (encoded === null || encoded === undefined) {\n adapter.removeItem(key);\n } else {\n adapter.setItem(key, encoded);\n }\n }\n\n // Notify other hooks/tabs\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error setting storage key \"${key}\":`, error);\n }\n },\n [adapter, key, codec, defaultValue, syncManager],\n );\n\n const removeItem = useCallback(() => {\n try {\n lastRaw.current = null;\n lastParsed.current = undefined;\n adapter.removeItem(key);\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error removing storage key \"${key}\":`, error);\n }\n }, [adapter, key, syncManager]);\n\n return [value, setValue, removeItem] as const;\n}\n","/**\n * Defines how to serialize/deserialize a value from localStorage.\n * null means the key doesn't exist.\n */\nexport interface Codec<T> {\n encode: (value: T) => string | null;\n decode: (value: string | null) => T;\n}\n\n/**\n * A robust JSON codec that handles parsing errors gracefully.\n * Works with objects, arrays, and other JSON-serializable values.\n *\n * @example\n * ```ts\n * const [user, setUser] = useStoragePersistedState<User | null>(\n * \"user\",\n * null,\n * { codec: JsonCodec }\n * );\n * ```\n * any type is needed to allow objects and arrays of any shape.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const JsonCodec: Codec<any> = {\n encode: (value) => {\n if (value === null) return null;\n return JSON.stringify(value);\n },\n decode: (value) => {\n if (value === null) return null;\n try {\n return JSON.parse(value);\n } catch (e) {\n console.warn(`LocalStorage parse error for value \"${value}\".`, e);\n throw e;\n }\n },\n};\n\n/**\n * Codec for string values. Stores strings as-is without any transformation.\n *\n * @example\n * ```ts\n * const [name, setName] = useStoragePersistedState(\"name\", \"Guest\");\n * // Automatically uses StringCodec because defaultValue is a string\n * ```\n *\n * any type is needed to allow string enums.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const StringCodec: Codec<any> = {\n encode: (value) => {\n if (value == null) return null;\n if (typeof value === \"string\") return value;\n console.error(\n `StringCodec encode expected a string but got ${typeof value}.`,\n );\n return String(value);\n },\n decode: (value) => {\n if (value == null) return null;\n if (typeof value === \"string\") return value;\n console.error(\n `StringCodec decode expected a string but got ${typeof value}.`,\n );\n return String(value);\n },\n};\n\n/**\n * Codec for boolean values. Stores as \"true\" or \"false\" strings.\n *\n * @example\n * ```ts\n * const [isDark, setIsDark] = useStoragePersistedState(\"darkMode\", false);\n * // Automatically uses BooleanCodec because defaultValue is a boolean\n * ```\n */\nexport const BooleanCodec: Codec<boolean> = {\n encode: (value) => (value ? \"true\" : \"false\"),\n decode: (value) => value === \"true\",\n};\n\n/**\n * Codec for number values. Handles NaN, Infinity, and -Infinity correctly.\n * Returns null for unparseable values (e.g., empty string, invalid number string).\n *\n * @example\n * ```ts\n * const [count, setCount] = useStoragePersistedState(\"count\", 0);\n * // Automatically uses NumberCodec because defaultValue is a number\n * ```\n *\n * any type is needed to allow number enums.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const NumberCodec: Codec<any> = {\n encode: (value) => {\n if (value === null) return null;\n if (typeof value !== \"number\") {\n console.error(\n `NumberCodec encode expected a number but got ${typeof value}.`,\n );\n }\n return String(value);\n },\n decode: (value) => {\n if (value === null) return null;\n // Handle special numeric values\n if (value === \"NaN\") return NaN;\n if (value === \"Infinity\") return Infinity;\n if (value === \"-Infinity\") return -Infinity;\n if (value === \"\") {\n console.warn(\"NumberCodec decode received an empty string.\");\n return null;\n }\n const parsed = Number(value);\n if (Number.isNaN(parsed)) {\n console.warn(\n `NumberCodec decode received an invalid number string \"${value}\".`,\n );\n return null;\n }\n return parsed;\n },\n};\n\n/**\n * Infers the appropriate codec based on the default value's type.\n * Used internally when the user doesn't provide an explicit codec.\n *\n * - `boolean` → BooleanCodec\n * - `number` → NumberCodec\n * - `string` → StringCodec\n * - `object`, `array`, `undefined`, `null` → JsonCodec\n *\n * @param defaultValue - The default value to infer the codec from\n * @returns The inferred codec for the given value type\n */\nexport function inferCodec<T>(defaultValue: T): Codec<T> {\n const type = typeof defaultValue;\n\n // Type assertions through unknown are needed because we're doing runtime type inference\n // The actual codec returned will match the runtime type of defaultValue\n if (type === \"boolean\") return BooleanCodec as unknown as Codec<T>;\n if (type === \"number\") return NumberCodec as unknown as Codec<T>;\n if (type === \"string\") return StringCodec as unknown as Codec<T>;\n\n // Default to JSON for objects, arrays, or undefined\n return JsonCodec as unknown as Codec<T>;\n}\n","\"use client\";\n\n/**\n * Interface allows swapping LocalStorage for SessionStorage or Async Storage later\n */\nexport interface StorageAdapter {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nexport interface SubscribeOptions {\n crossTabSync?: boolean;\n pollingIntervalMs?: number | null;\n}\n\ninterface ListenerOptions {\n crossTabSync: boolean;\n pollingIntervalMs: number | null;\n}\n\ninterface StorageSyncManagerOptions {\n enableCrossTabSync?: boolean;\n enablePolling?: boolean;\n}\n\nclass MemoryStorageAdapter implements StorageAdapter {\n private store = new Map<string, string>();\n\n getItem(key: string) {\n return this.store.has(key) ? this.store.get(key)! : null;\n }\n\n setItem(key: string, value: string) {\n this.store.set(key, value);\n }\n\n removeItem(key: string) {\n this.store.delete(key);\n }\n}\n\nclass FallbackStorageAdapter implements StorageAdapter {\n constructor(\n private primary: StorageAdapter,\n private fallback: StorageAdapter,\n ) {}\n\n getItem(key: string) {\n const fallbackValue = this.fallback.getItem(key);\n if (fallbackValue !== null) return fallbackValue;\n return this.primary.getItem(key);\n }\n\n setItem(key: string, value: string) {\n try {\n this.primary.setItem(key, value);\n this.fallback.removeItem(key);\n } catch {\n this.fallback.setItem(key, value);\n }\n }\n\n removeItem(key: string) {\n try {\n this.primary.removeItem(key);\n } finally {\n this.fallback.removeItem(key);\n }\n }\n}\n\n// Singleton manager ensures we only have ONE listener per key globally\nclass StorageSyncManager {\n private listeners = new Map<string, Map<() => void, ListenerOptions>>();\n private pollingIntervalId: number | null = null;\n private pollingIntervalMsActive: number | null = null;\n private enableCrossTabSync: boolean;\n private enablePolling: boolean;\n\n constructor(\n public readonly storage: StorageAdapter,\n private defaultPollingIntervalMs = 2000,\n options: StorageSyncManagerOptions = {},\n ) {\n this.enableCrossTabSync = options.enableCrossTabSync ?? true;\n this.enablePolling = options.enablePolling ?? true;\n\n if (typeof window !== \"undefined\" && this.enableCrossTabSync) {\n // 1. Cross-tab sync (\"storage\" event is a built-in browser feature that fires\n // when localStorage/sessionStorage changes in ANOTHER tab)\n // We do not remove this listener because this manager is a singleton meant to last\n // the entire application lifecycle. It is effectively cleaned up when the page unloads.\n window.addEventListener(\"storage\", (e) => {\n if (e.key && this.listeners.has(e.key)) {\n this.notifyCrossTab(e.key);\n }\n });\n // 2. Start polling for DevTools or direct window.localStorage changes (robustness)\n // We only poll if there are listeners. Lazy start in subscribe.\n }\n }\n\n subscribe(key: string, callback: () => void, options: SubscribeOptions = {}) {\n if (!this.listeners.has(key)) {\n this.listeners.set(key, new Map());\n }\n const listenerOptions: ListenerOptions = {\n crossTabSync: this.enableCrossTabSync && (options.crossTabSync ?? true),\n pollingIntervalMs: this.enablePolling\n ? this.normalizePollingInterval(options.pollingIntervalMs)\n : null,\n };\n this.listeners.get(key)!.set(callback, listenerOptions);\n\n this.updatePollingInterval();\n\n return () => {\n const listenersForKey = this.listeners.get(key);\n listenersForKey?.delete(callback);\n if (listenersForKey?.size === 0) {\n this.listeners.delete(key);\n }\n this.updatePollingInterval();\n };\n }\n\n // Called when WE change the value in this tab\n notify(key: string) {\n this.notifyListeners(key);\n }\n\n // Keep a cache of values to detect changes during polling\n private snapshotCache = new Map<string, string | null>();\n\n private notifyListeners(\n key: string,\n predicate?: (options: ListenerOptions) => boolean,\n ) {\n this.listeners.get(key)?.forEach((options, cb) => {\n if (!predicate || predicate(options)) {\n cb();\n }\n });\n }\n\n private notifyCrossTab(key: string) {\n this.notifyListeners(key, (options) => options.crossTabSync);\n }\n\n private notifyPolling(key: string) {\n this.notifyListeners(key, (options) => options.pollingIntervalMs !== null);\n }\n\n private normalizePollingInterval(\n pollingIntervalMs: number | null | undefined,\n ) {\n if (pollingIntervalMs === null) return null;\n if (pollingIntervalMs === undefined) return this.defaultPollingIntervalMs;\n if (pollingIntervalMs <= 0 || Number.isNaN(pollingIntervalMs)) {\n return null;\n }\n return pollingIntervalMs;\n }\n\n private getMinPollingIntervalMs() {\n let minInterval: number | null = null;\n this.listeners.forEach((listenersForKey) => {\n listenersForKey.forEach((options) => {\n if (options.pollingIntervalMs === null) return;\n if (minInterval === null || options.pollingIntervalMs < minInterval) {\n minInterval = options.pollingIntervalMs;\n }\n });\n });\n return minInterval;\n }\n\n private updatePollingInterval() {\n if (typeof window === \"undefined\") return;\n if (!this.enablePolling) return;\n\n const nextInterval = this.getMinPollingIntervalMs();\n if (nextInterval === null) {\n if (this.pollingIntervalId) {\n clearInterval(this.pollingIntervalId);\n }\n this.pollingIntervalId = null;\n this.pollingIntervalMsActive = null;\n this.snapshotCache.clear();\n return;\n }\n\n if (\n this.pollingIntervalId &&\n this.pollingIntervalMsActive === nextInterval\n ) {\n return;\n }\n\n if (this.pollingIntervalId) {\n clearInterval(this.pollingIntervalId);\n }\n\n this.pollingIntervalId = window.setInterval(() => {\n this.listeners.forEach((listenersForKey, key) => {\n const shouldPoll = Array.from(listenersForKey.values()).some(\n (options) => options.pollingIntervalMs !== null,\n );\n if (!shouldPoll) return;\n\n const currentValue = this.storage.getItem(key);\n const lastValue = this.snapshotCache.get(key);\n\n if (currentValue !== lastValue) {\n this.snapshotCache.set(key, currentValue);\n this.notifyPolling(key);\n }\n });\n }, nextInterval);\n this.pollingIntervalMsActive = nextInterval;\n }\n}\n\n// Lazy-initialized singletons to avoid SSR crashes (window is not defined on server)\nconst localStorageFallback = new MemoryStorageAdapter();\nconst sessionStorageFallback = new MemoryStorageAdapter();\n\nlet _localStorageSync: StorageSyncManager | null = null;\nlet _sessionStorageSync: StorageSyncManager | null = null;\nlet _memoryStorageSync: StorageSyncManager | null = null;\n\nfunction getLocalStorageSync(): StorageSyncManager {\n if (!_localStorageSync) {\n if (typeof window !== \"undefined\") {\n _localStorageSync = new StorageSyncManager(\n new FallbackStorageAdapter(window.localStorage, localStorageFallback),\n );\n } else {\n // SSR fallback - use memory storage\n return getMemoryStorageSync();\n }\n }\n return _localStorageSync;\n}\n\nfunction getSessionStorageSync(): StorageSyncManager {\n if (!_sessionStorageSync) {\n if (typeof window !== \"undefined\") {\n _sessionStorageSync = new StorageSyncManager(\n new FallbackStorageAdapter(\n window.sessionStorage,\n sessionStorageFallback,\n ),\n );\n } else {\n // SSR fallback - use memory storage\n return getMemoryStorageSync();\n }\n }\n return _sessionStorageSync;\n}\n\nfunction getMemoryStorageSync(): StorageSyncManager {\n if (!_memoryStorageSync) {\n _memoryStorageSync = new StorageSyncManager(\n new MemoryStorageAdapter(),\n 2000,\n {\n enableCrossTabSync: false,\n enablePolling: false,\n },\n );\n }\n return _memoryStorageSync;\n}\n\n/**\n * Proxy object for localStorage sync manager with lazy initialization.\n * Safe to import in SSR environments.\n */\nexport const localStorageSync = {\n get storage() {\n return getLocalStorageSync().storage;\n },\n subscribe(key: string, callback: () => void, options?: SubscribeOptions) {\n return getLocalStorageSync().subscribe(key, callback, options);\n },\n notify(key: string) {\n return getLocalStorageSync().notify(key);\n },\n};\n\n/**\n * Proxy object for sessionStorage sync manager with lazy initialization.\n * Safe to import in SSR environments.\n */\nexport const sessionStorageSync = {\n get storage() {\n return getSessionStorageSync().storage;\n },\n subscribe(key: string, callback: () => void, options?: SubscribeOptions) {\n return getSessionStorageSync().subscribe(key, callback, options);\n },\n notify(key: string) {\n return getSessionStorageSync().notify(key);\n },\n};\n\n/**\n * Proxy object for memory storage sync manager with lazy initialization.\n * Safe to import in SSR environments.\n */\nexport const memoryStorageSync = {\n get storage() {\n return getMemoryStorageSync().storage;\n },\n subscribe(key: string, callback: () => void, options?: SubscribeOptions) {\n return getMemoryStorageSync().subscribe(key, callback, options);\n },\n notify(key: string) {\n return getMemoryStorageSync().notify(key);\n },\n};\n","\"use client\";\n\nimport { Codec, inferCodec, JsonCodec } from \"./codecs\";\nimport {\n localStorageSync,\n memoryStorageSync,\n sessionStorageSync,\n} from \"./storage\";\nimport {\n StoragePersistedStateOptions,\n StorageType,\n} from \"./useStoragePersistedState\";\n\nfunction getSyncManager(storageType: StorageType | undefined) {\n if (storageType === \"sessionStorage\") return sessionStorageSync;\n if (storageType === \"memory\") return memoryStorageSync;\n return localStorageSync;\n}\n\nfunction resolveCodec<T>(\n key: string,\n valueHint: T,\n options?: StoragePersistedStateOptions<T>,\n): Codec<T> {\n if (options?.codec) return options.codec;\n\n if ((valueHint === undefined || valueHint === null) && !options?.codec) {\n console.warn(\n `storagePersistedState: Key \"${key}\" uses undefined or null default without explicit Codec. defaulting to JSON.`,\n );\n }\n\n return inferCodec(valueHint);\n}\n\n// Overload 1: Default provided, T inferred\n/**\n * Read a persisted value from storage using the same codec behavior as the hook.\n *\n * Returns the provided defaultValue when the key is missing or parsing fails.\n */\nexport function getStoragePersistedState<T>(\n key: string,\n defaultValue: Exclude<T, null | undefined>,\n options?: StoragePersistedStateOptions<T>,\n): T;\n\n// Overload 2: Explicit Codec provided, defaultValue can be null or undefined\n/**\n * Read a persisted value from storage using an explicit codec.\n *\n * Use this overload when defaultValue is null or undefined.\n */\nexport function getStoragePersistedState<T>(\n key: string,\n defaultValue: null | undefined,\n options: StoragePersistedStateOptions<T> & { codec: Codec<T> },\n): T | null;\n\nexport function getStoragePersistedState<T>(\n key: string,\n defaultValue: T,\n options: StoragePersistedStateOptions<T> = {},\n) {\n const syncManager = getSyncManager(options.storageType);\n const adapter = syncManager.storage;\n const codec = resolveCodec(key, defaultValue, options);\n const raw = adapter.getItem(key);\n\n if (raw === null && defaultValue !== undefined) {\n return defaultValue as T;\n }\n\n try {\n return codec.decode(raw);\n } catch (error) {\n console.error(`Error parsing storage key \"${key}\"`, error);\n return defaultValue as T;\n }\n}\n\n// Overload 1: Default provided, T inferred\n/**\n * Set a persisted value in storage and notify active hooks for the same key.\n *\n * Supports functional updates using the current decoded value.\n */\nexport function setStoragePersistedState<T>(\n key: string,\n newValue: Exclude<T, null | undefined>,\n options?: StoragePersistedStateOptions<T>,\n): void;\n\n// Overload 2: Explicit Codec provided, newValue can be null or undefined\n/**\n * Set a persisted value in storage using an explicit codec and notify listeners.\n *\n * Use this overload when the new value is null or undefined or when you want custom serialization.\n */\nexport function setStoragePersistedState<T>(\n key: string,\n newValue: T | ((prev: T | null) => T),\n options: StoragePersistedStateOptions<T> & { codec: Codec<T> },\n): void;\n\nexport function setStoragePersistedState<T>(\n key: string,\n newValueOrFn: T | ((prev: T | null) => T),\n options: StoragePersistedStateOptions<T> = {},\n) {\n const syncManager = getSyncManager(options.storageType);\n const adapter = syncManager.storage;\n\n let codec: Codec<T>;\n if (!(newValueOrFn instanceof Function)) {\n codec = resolveCodec(key, newValueOrFn, options);\n } else {\n // For functional updates, we cannot infer from newValue\n if (!options.codec) {\n console.warn(\n `storagePersistedState: Key \"${key}\" uses functional update without explicit Codec. defaulting to JSON.`,\n );\n }\n codec = options.codec ?? (JsonCodec as unknown as Codec<T>);\n }\n\n try {\n const currentRaw = adapter.getItem(key);\n const current = currentRaw !== null ? codec.decode(currentRaw) : null;\n\n const newValue =\n newValueOrFn instanceof Function ? newValueOrFn(current) : newValueOrFn;\n\n if (newValue === undefined || newValue === null) {\n adapter.removeItem(key);\n } else {\n const encoded = codec.encode(newValue);\n if (encoded === null || encoded === undefined) {\n adapter.removeItem(key);\n } else {\n adapter.setItem(key, encoded);\n }\n }\n\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error setting storage key \"${key}\":`, error);\n }\n}\n\n/**\n * Remove a persisted value from storage and notify active hooks for the same key.\n *\n * Defaults to localStorage when no storageType is provided.\n */\nexport function removeStoragePersistedState(\n key: string,\n storageType?: StorageType,\n) {\n const syncManager = getSyncManager(storageType);\n const adapter = syncManager.storage;\n\n try {\n adapter.removeItem(key);\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error removing storage key \"${key}\":`, error);\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -414,7 +414,7 @@ function resolveCodec(key, valueHint, options) {
414
414
  }
415
415
  return inferCodec(valueHint);
416
416
  }
417
- function readStoragePersistedState(key, defaultValue, options = {}) {
417
+ function getStoragePersistedState(key, defaultValue, options = {}) {
418
418
  const syncManager = getSyncManager(options.storageType);
419
419
  const adapter = syncManager.storage;
420
420
  const codec = resolveCodec(key, defaultValue, options);
@@ -462,13 +462,25 @@ function setStoragePersistedState(key, newValueOrFn, options = {}) {
462
462
  console.error(`Error setting storage key "${key}":`, error);
463
463
  }
464
464
  }
465
+ function removeStoragePersistedState(key, storageType) {
466
+ const syncManager = getSyncManager(storageType);
467
+ const adapter = syncManager.storage;
468
+ try {
469
+ adapter.removeItem(key);
470
+ syncManager.notify(key);
471
+ } catch (error) {
472
+ console.error(`Error removing storage key "${key}":`, error);
473
+ }
474
+ }
465
475
  export {
466
476
  BooleanCodec,
467
477
  JsonCodec,
468
478
  NumberCodec,
469
479
  StringCodec,
480
+ getStoragePersistedState,
470
481
  inferCodec,
471
- readStoragePersistedState,
482
+ getStoragePersistedState as readStoragePersistedState,
483
+ removeStoragePersistedState,
472
484
  setStoragePersistedState,
473
485
  useStoragePersistedState
474
486
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useStoragePersistedState.ts","../src/codecs.ts","../src/storage.ts","../src/storagePersistedState.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useMemo, useRef } from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim\";\nimport { Codec, inferCodec } from \"./codecs\";\nimport {\n localStorageSync,\n memoryStorageSync,\n sessionStorageSync,\n} from \"./storage\";\n\nexport type StorageType = \"localStorage\" | \"sessionStorage\" | \"memory\";\n\n/**\n * Options for the useStoragePersistedState hook\n */\nexport interface StoragePersistedStateOptions<T> {\n /**\n * Explicit codec for when defaultValue is null or undefined, for complex types, or special cases (e.g., data migration on read).\n * If not provided, codec is inferred from defaultValue type.\n */\n codec?: Codec<T>;\n /**\n * Storage type to use: 'localStorage' (default), 'sessionStorage', or 'memory'.\n */\n storageType?: StorageType;\n /**\n * Enable cross-tab synchronization via the StorageEvent. Note: disabling this will not stop other tabs from updating localStorage, but this hook will not automatically respond to those changes.\n * defaults to true.\n */\n crossTabSync?: boolean;\n /**\n * Polling interval in milliseconds for detecting storage changes made outside of React (e.g., DevTools, direct localStorage manipulation). Set to null to disable polling.\n * defaults to 2000ms.\n */\n pollingIntervalMs?: number | null;\n}\n\n// Overload 1: Default provided, T inferred\nexport function useStoragePersistedState<T>(\n key: string,\n defaultValue: Exclude<T, null | undefined>,\n options?: StoragePersistedStateOptions<T>,\n): [T, (newValue: T | ((prev: T) => T)) => void, () => void];\n\n// Overload 2: Explicit Codec provided, defaultValue can be null or undefined\nexport function useStoragePersistedState<T>(\n key: string,\n defaultValue: null | undefined,\n options: StoragePersistedStateOptions<T> & { codec: Codec<T> },\n): [T | null, (newValue: T | ((prev: T) => T)) => void, () => void];\n\n// Implementation\nexport function useStoragePersistedState<T>(\n key: string,\n defaultValue: T,\n options: StoragePersistedStateOptions<T> = {},\n) {\n const syncManager =\n options.storageType === \"sessionStorage\"\n ? sessionStorageSync\n : options.storageType === \"memory\"\n ? memoryStorageSync\n : localStorageSync;\n const adapter = syncManager.storage;\n\n // 1. Determine the codec.\n // If no explicit codec is passed, we try to infer it from defaultValue.\n // If defaultValue is undefined and no codec is passed, we fall back to JSON.\n const codec = useMemo(() => {\n if (options?.codec) return options.codec;\n return inferCodec(defaultValue);\n }, [defaultValue, options?.codec]);\n\n if ((defaultValue === undefined || defaultValue === null) && !options.codec) {\n console.warn(\n `useStorage: Key \"${key}\" uses undefined or null default without explicit Codec. defaulting to JSON.`,\n );\n }\n\n // Memoize the decoded value to prevent infinite loops in useSyncExternalStore\n // when the codec returns a new object reference (e.g. JSON.parse).\n const lastRaw = useRef<string | null>(null); // string | null, because storage returns null for missing keys\n const lastParsed = useRef<T | undefined>(undefined);\n\n const getSnapshot = useCallback(() => {\n const raw = adapter.getItem(key);\n\n // Return default if storage is missing the key\n if (raw === null && defaultValue !== undefined) {\n return defaultValue as T;\n }\n\n // If raw value matches cache, return cached object.\n if (raw === lastRaw.current) {\n if (lastParsed.current === undefined) {\n return null as T;\n }\n return lastParsed.current as T;\n }\n\n // Decode new raw value\n try {\n const decoded = codec.decode(raw);\n\n lastRaw.current = raw;\n lastParsed.current = decoded;\n return decoded;\n } catch (e) {\n console.error(`Error parsing storage key \"${key}\"`, e);\n return defaultValue as T;\n }\n }, [adapter, key, codec, defaultValue]);\n\n // 2. Subscribe to the external store (Local/SessionStorage + Polling/Events)\n // useSyncExternalStore handles the hydration mismatch automatically by\n // taking a `getServerSnapshot` (returning defaultValue).\n const value = useSyncExternalStore(\n (callback) =>\n syncManager.subscribe(key, callback, {\n crossTabSync: options.crossTabSync,\n pollingIntervalMs: options.pollingIntervalMs,\n }),\n getSnapshot,\n () => defaultValue as T, // Server Snapshot\n );\n\n // 3. Create the Setter\n const setValue = useCallback(\n (newValueOrFn: T | ((prev: T) => T)) => {\n try {\n const currentRaw = adapter.getItem(key);\n const current =\n currentRaw !== null ? codec.decode(currentRaw) : defaultValue;\n\n const newValue =\n newValueOrFn instanceof Function\n ? newValueOrFn(current)\n : newValueOrFn;\n\n if (newValue === undefined || newValue === null) {\n lastRaw.current = null;\n lastParsed.current = undefined;\n adapter.removeItem(key);\n } else {\n const encoded = codec.encode(newValue);\n lastRaw.current = encoded;\n lastParsed.current = newValue;\n\n if (encoded === null || encoded === undefined) {\n adapter.removeItem(key);\n } else {\n adapter.setItem(key, encoded);\n }\n }\n\n // Notify other hooks/tabs\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error setting storage key \"${key}\":`, error);\n }\n },\n [adapter, key, codec, defaultValue, syncManager],\n );\n\n const removeItem = useCallback(() => {\n try {\n lastRaw.current = null;\n lastParsed.current = undefined;\n adapter.removeItem(key);\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error removing storage key \"${key}\":`, error);\n }\n }, [adapter, key, syncManager]);\n\n return [value, setValue, removeItem] as const;\n}\n","/**\n * Defines how to serialize/deserialize a value from localStorage.\n * null means the key doesn't exist.\n */\nexport interface Codec<T> {\n encode: (value: T) => string | null;\n decode: (value: string | null) => T;\n}\n\n/**\n * A robust JSON codec that handles parsing errors gracefully.\n * Works with objects, arrays, and other JSON-serializable values.\n *\n * @example\n * ```ts\n * const [user, setUser] = useStoragePersistedState<User | null>(\n * \"user\",\n * null,\n * { codec: JsonCodec }\n * );\n * ```\n * any type is needed to allow objects and arrays of any shape.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const JsonCodec: Codec<any> = {\n encode: (value) => {\n if (value === null) return null;\n return JSON.stringify(value);\n },\n decode: (value) => {\n if (value === null) return null;\n try {\n return JSON.parse(value);\n } catch (e) {\n console.warn(`LocalStorage parse error for value \"${value}\".`, e);\n throw e;\n }\n },\n};\n\n/**\n * Codec for string values. Stores strings as-is without any transformation.\n *\n * @example\n * ```ts\n * const [name, setName] = useStoragePersistedState(\"name\", \"Guest\");\n * // Automatically uses StringCodec because defaultValue is a string\n * ```\n *\n * any type is needed to allow string enums.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const StringCodec: Codec<any> = {\n encode: (value) => {\n if (value == null) return null;\n if (typeof value === \"string\") return value;\n console.error(\n `StringCodec encode expected a string but got ${typeof value}.`,\n );\n return String(value);\n },\n decode: (value) => {\n if (value == null) return null;\n if (typeof value === \"string\") return value;\n console.error(\n `StringCodec decode expected a string but got ${typeof value}.`,\n );\n return String(value);\n },\n};\n\n/**\n * Codec for boolean values. Stores as \"true\" or \"false\" strings.\n *\n * @example\n * ```ts\n * const [isDark, setIsDark] = useStoragePersistedState(\"darkMode\", false);\n * // Automatically uses BooleanCodec because defaultValue is a boolean\n * ```\n */\nexport const BooleanCodec: Codec<boolean> = {\n encode: (value) => (value ? \"true\" : \"false\"),\n decode: (value) => value === \"true\",\n};\n\n/**\n * Codec for number values. Handles NaN, Infinity, and -Infinity correctly.\n * Returns null for unparseable values (e.g., empty string, invalid number string).\n *\n * @example\n * ```ts\n * const [count, setCount] = useStoragePersistedState(\"count\", 0);\n * // Automatically uses NumberCodec because defaultValue is a number\n * ```\n *\n * any type is needed to allow number enums.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const NumberCodec: Codec<any> = {\n encode: (value) => {\n if (value === null) return null;\n if (typeof value !== \"number\") {\n console.error(\n `NumberCodec encode expected a number but got ${typeof value}.`,\n );\n }\n return String(value);\n },\n decode: (value) => {\n if (value === null) return null;\n // Handle special numeric values\n if (value === \"NaN\") return NaN;\n if (value === \"Infinity\") return Infinity;\n if (value === \"-Infinity\") return -Infinity;\n if (value === \"\") {\n console.warn(\"NumberCodec decode received an empty string.\");\n return null;\n }\n const parsed = Number(value);\n if (Number.isNaN(parsed)) {\n console.warn(\n `NumberCodec decode received an invalid number string \"${value}\".`,\n );\n return null;\n }\n return parsed;\n },\n};\n\n/**\n * Infers the appropriate codec based on the default value's type.\n * Used internally when the user doesn't provide an explicit codec.\n *\n * - `boolean` → BooleanCodec\n * - `number` → NumberCodec\n * - `string` → StringCodec\n * - `object`, `array`, `undefined`, `null` → JsonCodec\n *\n * @param defaultValue - The default value to infer the codec from\n * @returns The inferred codec for the given value type\n */\nexport function inferCodec<T>(defaultValue: T): Codec<T> {\n const type = typeof defaultValue;\n\n // Type assertions through unknown are needed because we're doing runtime type inference\n // The actual codec returned will match the runtime type of defaultValue\n if (type === \"boolean\") return BooleanCodec as unknown as Codec<T>;\n if (type === \"number\") return NumberCodec as unknown as Codec<T>;\n if (type === \"string\") return StringCodec as unknown as Codec<T>;\n\n // Default to JSON for objects, arrays, or undefined\n return JsonCodec as unknown as Codec<T>;\n}\n","\"use client\";\n\n/**\n * Interface allows swapping LocalStorage for SessionStorage or Async Storage later\n */\nexport interface StorageAdapter {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nexport interface SubscribeOptions {\n crossTabSync?: boolean;\n pollingIntervalMs?: number | null;\n}\n\ninterface ListenerOptions {\n crossTabSync: boolean;\n pollingIntervalMs: number | null;\n}\n\ninterface StorageSyncManagerOptions {\n enableCrossTabSync?: boolean;\n enablePolling?: boolean;\n}\n\nclass MemoryStorageAdapter implements StorageAdapter {\n private store = new Map<string, string>();\n\n getItem(key: string) {\n return this.store.has(key) ? this.store.get(key)! : null;\n }\n\n setItem(key: string, value: string) {\n this.store.set(key, value);\n }\n\n removeItem(key: string) {\n this.store.delete(key);\n }\n}\n\nclass FallbackStorageAdapter implements StorageAdapter {\n constructor(\n private primary: StorageAdapter,\n private fallback: StorageAdapter,\n ) {}\n\n getItem(key: string) {\n const fallbackValue = this.fallback.getItem(key);\n if (fallbackValue !== null) return fallbackValue;\n return this.primary.getItem(key);\n }\n\n setItem(key: string, value: string) {\n try {\n this.primary.setItem(key, value);\n this.fallback.removeItem(key);\n } catch {\n this.fallback.setItem(key, value);\n }\n }\n\n removeItem(key: string) {\n try {\n this.primary.removeItem(key);\n } finally {\n this.fallback.removeItem(key);\n }\n }\n}\n\n// Singleton manager ensures we only have ONE listener per key globally\nclass StorageSyncManager {\n private listeners = new Map<string, Map<() => void, ListenerOptions>>();\n private pollingIntervalId: number | null = null;\n private pollingIntervalMsActive: number | null = null;\n private enableCrossTabSync: boolean;\n private enablePolling: boolean;\n\n constructor(\n public readonly storage: StorageAdapter,\n private defaultPollingIntervalMs = 2000,\n options: StorageSyncManagerOptions = {},\n ) {\n this.enableCrossTabSync = options.enableCrossTabSync ?? true;\n this.enablePolling = options.enablePolling ?? true;\n\n if (typeof window !== \"undefined\" && this.enableCrossTabSync) {\n // 1. Cross-tab sync (\"storage\" event is a built-in browser feature that fires\n // when localStorage/sessionStorage changes in ANOTHER tab)\n // We do not remove this listener because this manager is a singleton meant to last\n // the entire application lifecycle. It is effectively cleaned up when the page unloads.\n window.addEventListener(\"storage\", (e) => {\n if (e.key && this.listeners.has(e.key)) {\n this.notifyCrossTab(e.key);\n }\n });\n // 2. Start polling for DevTools or direct window.localStorage changes (robustness)\n // We only poll if there are listeners. Lazy start in subscribe.\n }\n }\n\n subscribe(key: string, callback: () => void, options: SubscribeOptions = {}) {\n if (!this.listeners.has(key)) {\n this.listeners.set(key, new Map());\n }\n const listenerOptions: ListenerOptions = {\n crossTabSync: this.enableCrossTabSync && (options.crossTabSync ?? true),\n pollingIntervalMs: this.enablePolling\n ? this.normalizePollingInterval(options.pollingIntervalMs)\n : null,\n };\n this.listeners.get(key)!.set(callback, listenerOptions);\n\n this.updatePollingInterval();\n\n return () => {\n const listenersForKey = this.listeners.get(key);\n listenersForKey?.delete(callback);\n if (listenersForKey?.size === 0) {\n this.listeners.delete(key);\n }\n this.updatePollingInterval();\n };\n }\n\n // Called when WE change the value in this tab\n notify(key: string) {\n this.notifyListeners(key);\n }\n\n // Keep a cache of values to detect changes during polling\n private snapshotCache = new Map<string, string | null>();\n\n private notifyListeners(\n key: string,\n predicate?: (options: ListenerOptions) => boolean,\n ) {\n this.listeners.get(key)?.forEach((options, cb) => {\n if (!predicate || predicate(options)) {\n cb();\n }\n });\n }\n\n private notifyCrossTab(key: string) {\n this.notifyListeners(key, (options) => options.crossTabSync);\n }\n\n private notifyPolling(key: string) {\n this.notifyListeners(key, (options) => options.pollingIntervalMs !== null);\n }\n\n private normalizePollingInterval(\n pollingIntervalMs: number | null | undefined,\n ) {\n if (pollingIntervalMs === null) return null;\n if (pollingIntervalMs === undefined) return this.defaultPollingIntervalMs;\n if (pollingIntervalMs <= 0 || Number.isNaN(pollingIntervalMs)) {\n return null;\n }\n return pollingIntervalMs;\n }\n\n private getMinPollingIntervalMs() {\n let minInterval: number | null = null;\n this.listeners.forEach((listenersForKey) => {\n listenersForKey.forEach((options) => {\n if (options.pollingIntervalMs === null) return;\n if (minInterval === null || options.pollingIntervalMs < minInterval) {\n minInterval = options.pollingIntervalMs;\n }\n });\n });\n return minInterval;\n }\n\n private updatePollingInterval() {\n if (typeof window === \"undefined\") return;\n if (!this.enablePolling) return;\n\n const nextInterval = this.getMinPollingIntervalMs();\n if (nextInterval === null) {\n if (this.pollingIntervalId) {\n clearInterval(this.pollingIntervalId);\n }\n this.pollingIntervalId = null;\n this.pollingIntervalMsActive = null;\n this.snapshotCache.clear();\n return;\n }\n\n if (\n this.pollingIntervalId &&\n this.pollingIntervalMsActive === nextInterval\n ) {\n return;\n }\n\n if (this.pollingIntervalId) {\n clearInterval(this.pollingIntervalId);\n }\n\n this.pollingIntervalId = window.setInterval(() => {\n this.listeners.forEach((listenersForKey, key) => {\n const shouldPoll = Array.from(listenersForKey.values()).some(\n (options) => options.pollingIntervalMs !== null,\n );\n if (!shouldPoll) return;\n\n const currentValue = this.storage.getItem(key);\n const lastValue = this.snapshotCache.get(key);\n\n if (currentValue !== lastValue) {\n this.snapshotCache.set(key, currentValue);\n this.notifyPolling(key);\n }\n });\n }, nextInterval);\n this.pollingIntervalMsActive = nextInterval;\n }\n}\n\n// Lazy-initialized singletons to avoid SSR crashes (window is not defined on server)\nconst localStorageFallback = new MemoryStorageAdapter();\nconst sessionStorageFallback = new MemoryStorageAdapter();\n\nlet _localStorageSync: StorageSyncManager | null = null;\nlet _sessionStorageSync: StorageSyncManager | null = null;\nlet _memoryStorageSync: StorageSyncManager | null = null;\n\nfunction getLocalStorageSync(): StorageSyncManager {\n if (!_localStorageSync) {\n if (typeof window !== \"undefined\") {\n _localStorageSync = new StorageSyncManager(\n new FallbackStorageAdapter(window.localStorage, localStorageFallback),\n );\n } else {\n // SSR fallback - use memory storage\n return getMemoryStorageSync();\n }\n }\n return _localStorageSync;\n}\n\nfunction getSessionStorageSync(): StorageSyncManager {\n if (!_sessionStorageSync) {\n if (typeof window !== \"undefined\") {\n _sessionStorageSync = new StorageSyncManager(\n new FallbackStorageAdapter(\n window.sessionStorage,\n sessionStorageFallback,\n ),\n );\n } else {\n // SSR fallback - use memory storage\n return getMemoryStorageSync();\n }\n }\n return _sessionStorageSync;\n}\n\nfunction getMemoryStorageSync(): StorageSyncManager {\n if (!_memoryStorageSync) {\n _memoryStorageSync = new StorageSyncManager(\n new MemoryStorageAdapter(),\n 2000,\n {\n enableCrossTabSync: false,\n enablePolling: false,\n },\n );\n }\n return _memoryStorageSync;\n}\n\n/**\n * Proxy object for localStorage sync manager with lazy initialization.\n * Safe to import in SSR environments.\n */\nexport const localStorageSync = {\n get storage() {\n return getLocalStorageSync().storage;\n },\n subscribe(key: string, callback: () => void, options?: SubscribeOptions) {\n return getLocalStorageSync().subscribe(key, callback, options);\n },\n notify(key: string) {\n return getLocalStorageSync().notify(key);\n },\n};\n\n/**\n * Proxy object for sessionStorage sync manager with lazy initialization.\n * Safe to import in SSR environments.\n */\nexport const sessionStorageSync = {\n get storage() {\n return getSessionStorageSync().storage;\n },\n subscribe(key: string, callback: () => void, options?: SubscribeOptions) {\n return getSessionStorageSync().subscribe(key, callback, options);\n },\n notify(key: string) {\n return getSessionStorageSync().notify(key);\n },\n};\n\n/**\n * Proxy object for memory storage sync manager with lazy initialization.\n * Safe to import in SSR environments.\n */\nexport const memoryStorageSync = {\n get storage() {\n return getMemoryStorageSync().storage;\n },\n subscribe(key: string, callback: () => void, options?: SubscribeOptions) {\n return getMemoryStorageSync().subscribe(key, callback, options);\n },\n notify(key: string) {\n return getMemoryStorageSync().notify(key);\n },\n};\n","\"use client\";\n\nimport { Codec, inferCodec, JsonCodec } from \"./codecs\";\nimport {\n localStorageSync,\n memoryStorageSync,\n sessionStorageSync,\n} from \"./storage\";\nimport {\n StoragePersistedStateOptions,\n StorageType,\n} from \"./useStoragePersistedState\";\n\nfunction getSyncManager(storageType: StorageType | undefined) {\n if (storageType === \"sessionStorage\") return sessionStorageSync;\n if (storageType === \"memory\") return memoryStorageSync;\n return localStorageSync;\n}\n\nfunction resolveCodec<T>(\n key: string,\n valueHint: T,\n options?: StoragePersistedStateOptions<T>,\n): Codec<T> {\n if (options?.codec) return options.codec;\n\n if ((valueHint === undefined || valueHint === null) && !options?.codec) {\n console.warn(\n `storagePersistedState: Key \"${key}\" uses undefined or null default without explicit Codec. defaulting to JSON.`,\n );\n }\n\n return inferCodec(valueHint);\n}\n\n// Overload 1: Default provided, T inferred\n/**\n * Read a persisted value from storage using the same codec behavior as the hook.\n *\n * Returns the provided defaultValue when the key is missing or parsing fails.\n */\nexport function readStoragePersistedState<T>(\n key: string,\n defaultValue: Exclude<T, null | undefined>,\n options?: StoragePersistedStateOptions<T>,\n): T;\n\n// Overload 2: Explicit Codec provided, defaultValue can be null or undefined\n/**\n * Read a persisted value from storage using an explicit codec.\n *\n * Use this overload when defaultValue is null or undefined.\n */\nexport function readStoragePersistedState<T>(\n key: string,\n defaultValue: null | undefined,\n options: StoragePersistedStateOptions<T> & { codec: Codec<T> },\n): T | null;\n\nexport function readStoragePersistedState<T>(\n key: string,\n defaultValue: T,\n options: StoragePersistedStateOptions<T> = {},\n) {\n const syncManager = getSyncManager(options.storageType);\n const adapter = syncManager.storage;\n const codec = resolveCodec(key, defaultValue, options);\n const raw = adapter.getItem(key);\n\n if (raw === null && defaultValue !== undefined) {\n return defaultValue as T;\n }\n\n try {\n return codec.decode(raw);\n } catch (error) {\n console.error(`Error parsing storage key \"${key}\"`, error);\n return defaultValue as T;\n }\n}\n\n// Overload 1: Default provided, T inferred\n/**\n * Set a persisted value in storage and notify active hooks for the same key.\n *\n * Supports functional updates using the current decoded value.\n */\nexport function setStoragePersistedState<T>(\n key: string,\n newValue: Exclude<T, null | undefined>,\n options?: StoragePersistedStateOptions<T>,\n): void;\n\n// Overload 2: Explicit Codec provided, newValue can be null or undefined\n/**\n * Set a persisted value in storage using an explicit codec and notify listeners.\n *\n * Use this overload when the new value is null or undefined or when you want custom serialization.\n */\nexport function setStoragePersistedState<T>(\n key: string,\n newValue: T | ((prev: T | null) => T),\n options: StoragePersistedStateOptions<T> & { codec: Codec<T> },\n): void;\n\nexport function setStoragePersistedState<T>(\n key: string,\n newValueOrFn: T | ((prev: T | null) => T),\n options: StoragePersistedStateOptions<T> = {},\n) {\n const syncManager = getSyncManager(options.storageType);\n const adapter = syncManager.storage;\n\n let codec: Codec<T>;\n if (!(newValueOrFn instanceof Function)) {\n codec = resolveCodec(key, newValueOrFn, options);\n } else {\n // For functional updates, we cannot infer from newValue\n if (!options.codec) {\n console.warn(\n `storagePersistedState: Key \"${key}\" uses functional update without explicit Codec. defaulting to JSON.`,\n );\n }\n codec = options.codec ?? (JsonCodec as unknown as Codec<T>);\n }\n\n try {\n const currentRaw = adapter.getItem(key);\n const current = currentRaw !== null ? codec.decode(currentRaw) : null;\n\n const newValue =\n newValueOrFn instanceof Function ? newValueOrFn(current) : newValueOrFn;\n\n if (newValue === undefined || newValue === null) {\n adapter.removeItem(key);\n } else {\n const encoded = codec.encode(newValue);\n if (encoded === null || encoded === undefined) {\n adapter.removeItem(key);\n } else {\n adapter.setItem(key, encoded);\n }\n }\n\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error setting storage key \"${key}\":`, error);\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/useStoragePersistedState.ts","../src/codecs.ts","../src/storage.ts","../src/storagePersistedState.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useMemo, useRef } from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim\";\nimport { Codec, inferCodec } from \"./codecs\";\nimport {\n localStorageSync,\n memoryStorageSync,\n sessionStorageSync,\n} from \"./storage\";\n\nexport type StorageType = \"localStorage\" | \"sessionStorage\" | \"memory\";\n\n/**\n * Options for the useStoragePersistedState hook\n */\nexport interface StoragePersistedStateOptions<T> {\n /**\n * Explicit codec for when defaultValue is null or undefined, for complex types, or special cases (e.g., data migration on read).\n * If not provided, codec is inferred from defaultValue type.\n */\n codec?: Codec<T>;\n /**\n * Storage type to use: 'localStorage' (default), 'sessionStorage', or 'memory'.\n */\n storageType?: StorageType;\n /**\n * Enable cross-tab synchronization via the StorageEvent. Note: disabling this will not stop other tabs from updating localStorage, but this hook will not automatically respond to those changes.\n * defaults to true.\n */\n crossTabSync?: boolean;\n /**\n * Polling interval in milliseconds for detecting storage changes made outside of React (e.g., DevTools, direct localStorage manipulation). Set to null to disable polling.\n * defaults to 2000ms.\n */\n pollingIntervalMs?: number | null;\n}\n\n// Overload 1: Default provided, T inferred\nexport function useStoragePersistedState<T>(\n key: string,\n defaultValue: Exclude<T, null | undefined>,\n options?: StoragePersistedStateOptions<T>,\n): [T, (newValue: T | ((prev: T) => T)) => void, () => void];\n\n// Overload 2: Explicit Codec provided, defaultValue can be null or undefined\nexport function useStoragePersistedState<T>(\n key: string,\n defaultValue: null | undefined,\n options: StoragePersistedStateOptions<T> & { codec: Codec<T> },\n): [T | null, (newValue: T | ((prev: T) => T)) => void, () => void];\n\n// Implementation\nexport function useStoragePersistedState<T>(\n key: string,\n defaultValue: T,\n options: StoragePersistedStateOptions<T> = {},\n) {\n const syncManager =\n options.storageType === \"sessionStorage\"\n ? sessionStorageSync\n : options.storageType === \"memory\"\n ? memoryStorageSync\n : localStorageSync;\n const adapter = syncManager.storage;\n\n // 1. Determine the codec.\n // If no explicit codec is passed, we try to infer it from defaultValue.\n // If defaultValue is undefined and no codec is passed, we fall back to JSON.\n const codec = useMemo(() => {\n if (options?.codec) return options.codec;\n return inferCodec(defaultValue);\n }, [defaultValue, options?.codec]);\n\n if ((defaultValue === undefined || defaultValue === null) && !options.codec) {\n console.warn(\n `useStorage: Key \"${key}\" uses undefined or null default without explicit Codec. defaulting to JSON.`,\n );\n }\n\n // Memoize the decoded value to prevent infinite loops in useSyncExternalStore\n // when the codec returns a new object reference (e.g. JSON.parse).\n const lastRaw = useRef<string | null>(null); // string | null, because storage returns null for missing keys\n const lastParsed = useRef<T | undefined>(undefined);\n\n const getSnapshot = useCallback(() => {\n const raw = adapter.getItem(key);\n\n // Return default if storage is missing the key\n if (raw === null && defaultValue !== undefined) {\n return defaultValue as T;\n }\n\n // If raw value matches cache, return cached object.\n if (raw === lastRaw.current) {\n if (lastParsed.current === undefined) {\n return null as T;\n }\n return lastParsed.current as T;\n }\n\n // Decode new raw value\n try {\n const decoded = codec.decode(raw);\n\n lastRaw.current = raw;\n lastParsed.current = decoded;\n return decoded;\n } catch (e) {\n console.error(`Error parsing storage key \"${key}\"`, e);\n return defaultValue as T;\n }\n }, [adapter, key, codec, defaultValue]);\n\n // 2. Subscribe to the external store (Local/SessionStorage + Polling/Events)\n // useSyncExternalStore handles the hydration mismatch automatically by\n // taking a `getServerSnapshot` (returning defaultValue).\n const value = useSyncExternalStore(\n (callback) =>\n syncManager.subscribe(key, callback, {\n crossTabSync: options.crossTabSync,\n pollingIntervalMs: options.pollingIntervalMs,\n }),\n getSnapshot,\n () => defaultValue as T, // Server Snapshot\n );\n\n // 3. Create the Setter\n const setValue = useCallback(\n (newValueOrFn: T | ((prev: T) => T)) => {\n try {\n const currentRaw = adapter.getItem(key);\n const current =\n currentRaw !== null ? codec.decode(currentRaw) : defaultValue;\n\n const newValue =\n newValueOrFn instanceof Function\n ? newValueOrFn(current)\n : newValueOrFn;\n\n if (newValue === undefined || newValue === null) {\n lastRaw.current = null;\n lastParsed.current = undefined;\n adapter.removeItem(key);\n } else {\n const encoded = codec.encode(newValue);\n lastRaw.current = encoded;\n lastParsed.current = newValue;\n\n if (encoded === null || encoded === undefined) {\n adapter.removeItem(key);\n } else {\n adapter.setItem(key, encoded);\n }\n }\n\n // Notify other hooks/tabs\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error setting storage key \"${key}\":`, error);\n }\n },\n [adapter, key, codec, defaultValue, syncManager],\n );\n\n const removeItem = useCallback(() => {\n try {\n lastRaw.current = null;\n lastParsed.current = undefined;\n adapter.removeItem(key);\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error removing storage key \"${key}\":`, error);\n }\n }, [adapter, key, syncManager]);\n\n return [value, setValue, removeItem] as const;\n}\n","/**\n * Defines how to serialize/deserialize a value from localStorage.\n * null means the key doesn't exist.\n */\nexport interface Codec<T> {\n encode: (value: T) => string | null;\n decode: (value: string | null) => T;\n}\n\n/**\n * A robust JSON codec that handles parsing errors gracefully.\n * Works with objects, arrays, and other JSON-serializable values.\n *\n * @example\n * ```ts\n * const [user, setUser] = useStoragePersistedState<User | null>(\n * \"user\",\n * null,\n * { codec: JsonCodec }\n * );\n * ```\n * any type is needed to allow objects and arrays of any shape.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const JsonCodec: Codec<any> = {\n encode: (value) => {\n if (value === null) return null;\n return JSON.stringify(value);\n },\n decode: (value) => {\n if (value === null) return null;\n try {\n return JSON.parse(value);\n } catch (e) {\n console.warn(`LocalStorage parse error for value \"${value}\".`, e);\n throw e;\n }\n },\n};\n\n/**\n * Codec for string values. Stores strings as-is without any transformation.\n *\n * @example\n * ```ts\n * const [name, setName] = useStoragePersistedState(\"name\", \"Guest\");\n * // Automatically uses StringCodec because defaultValue is a string\n * ```\n *\n * any type is needed to allow string enums.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const StringCodec: Codec<any> = {\n encode: (value) => {\n if (value == null) return null;\n if (typeof value === \"string\") return value;\n console.error(\n `StringCodec encode expected a string but got ${typeof value}.`,\n );\n return String(value);\n },\n decode: (value) => {\n if (value == null) return null;\n if (typeof value === \"string\") return value;\n console.error(\n `StringCodec decode expected a string but got ${typeof value}.`,\n );\n return String(value);\n },\n};\n\n/**\n * Codec for boolean values. Stores as \"true\" or \"false\" strings.\n *\n * @example\n * ```ts\n * const [isDark, setIsDark] = useStoragePersistedState(\"darkMode\", false);\n * // Automatically uses BooleanCodec because defaultValue is a boolean\n * ```\n */\nexport const BooleanCodec: Codec<boolean> = {\n encode: (value) => (value ? \"true\" : \"false\"),\n decode: (value) => value === \"true\",\n};\n\n/**\n * Codec for number values. Handles NaN, Infinity, and -Infinity correctly.\n * Returns null for unparseable values (e.g., empty string, invalid number string).\n *\n * @example\n * ```ts\n * const [count, setCount] = useStoragePersistedState(\"count\", 0);\n * // Automatically uses NumberCodec because defaultValue is a number\n * ```\n *\n * any type is needed to allow number enums.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const NumberCodec: Codec<any> = {\n encode: (value) => {\n if (value === null) return null;\n if (typeof value !== \"number\") {\n console.error(\n `NumberCodec encode expected a number but got ${typeof value}.`,\n );\n }\n return String(value);\n },\n decode: (value) => {\n if (value === null) return null;\n // Handle special numeric values\n if (value === \"NaN\") return NaN;\n if (value === \"Infinity\") return Infinity;\n if (value === \"-Infinity\") return -Infinity;\n if (value === \"\") {\n console.warn(\"NumberCodec decode received an empty string.\");\n return null;\n }\n const parsed = Number(value);\n if (Number.isNaN(parsed)) {\n console.warn(\n `NumberCodec decode received an invalid number string \"${value}\".`,\n );\n return null;\n }\n return parsed;\n },\n};\n\n/**\n * Infers the appropriate codec based on the default value's type.\n * Used internally when the user doesn't provide an explicit codec.\n *\n * - `boolean` → BooleanCodec\n * - `number` → NumberCodec\n * - `string` → StringCodec\n * - `object`, `array`, `undefined`, `null` → JsonCodec\n *\n * @param defaultValue - The default value to infer the codec from\n * @returns The inferred codec for the given value type\n */\nexport function inferCodec<T>(defaultValue: T): Codec<T> {\n const type = typeof defaultValue;\n\n // Type assertions through unknown are needed because we're doing runtime type inference\n // The actual codec returned will match the runtime type of defaultValue\n if (type === \"boolean\") return BooleanCodec as unknown as Codec<T>;\n if (type === \"number\") return NumberCodec as unknown as Codec<T>;\n if (type === \"string\") return StringCodec as unknown as Codec<T>;\n\n // Default to JSON for objects, arrays, or undefined\n return JsonCodec as unknown as Codec<T>;\n}\n","\"use client\";\n\n/**\n * Interface allows swapping LocalStorage for SessionStorage or Async Storage later\n */\nexport interface StorageAdapter {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nexport interface SubscribeOptions {\n crossTabSync?: boolean;\n pollingIntervalMs?: number | null;\n}\n\ninterface ListenerOptions {\n crossTabSync: boolean;\n pollingIntervalMs: number | null;\n}\n\ninterface StorageSyncManagerOptions {\n enableCrossTabSync?: boolean;\n enablePolling?: boolean;\n}\n\nclass MemoryStorageAdapter implements StorageAdapter {\n private store = new Map<string, string>();\n\n getItem(key: string) {\n return this.store.has(key) ? this.store.get(key)! : null;\n }\n\n setItem(key: string, value: string) {\n this.store.set(key, value);\n }\n\n removeItem(key: string) {\n this.store.delete(key);\n }\n}\n\nclass FallbackStorageAdapter implements StorageAdapter {\n constructor(\n private primary: StorageAdapter,\n private fallback: StorageAdapter,\n ) {}\n\n getItem(key: string) {\n const fallbackValue = this.fallback.getItem(key);\n if (fallbackValue !== null) return fallbackValue;\n return this.primary.getItem(key);\n }\n\n setItem(key: string, value: string) {\n try {\n this.primary.setItem(key, value);\n this.fallback.removeItem(key);\n } catch {\n this.fallback.setItem(key, value);\n }\n }\n\n removeItem(key: string) {\n try {\n this.primary.removeItem(key);\n } finally {\n this.fallback.removeItem(key);\n }\n }\n}\n\n// Singleton manager ensures we only have ONE listener per key globally\nclass StorageSyncManager {\n private listeners = new Map<string, Map<() => void, ListenerOptions>>();\n private pollingIntervalId: number | null = null;\n private pollingIntervalMsActive: number | null = null;\n private enableCrossTabSync: boolean;\n private enablePolling: boolean;\n\n constructor(\n public readonly storage: StorageAdapter,\n private defaultPollingIntervalMs = 2000,\n options: StorageSyncManagerOptions = {},\n ) {\n this.enableCrossTabSync = options.enableCrossTabSync ?? true;\n this.enablePolling = options.enablePolling ?? true;\n\n if (typeof window !== \"undefined\" && this.enableCrossTabSync) {\n // 1. Cross-tab sync (\"storage\" event is a built-in browser feature that fires\n // when localStorage/sessionStorage changes in ANOTHER tab)\n // We do not remove this listener because this manager is a singleton meant to last\n // the entire application lifecycle. It is effectively cleaned up when the page unloads.\n window.addEventListener(\"storage\", (e) => {\n if (e.key && this.listeners.has(e.key)) {\n this.notifyCrossTab(e.key);\n }\n });\n // 2. Start polling for DevTools or direct window.localStorage changes (robustness)\n // We only poll if there are listeners. Lazy start in subscribe.\n }\n }\n\n subscribe(key: string, callback: () => void, options: SubscribeOptions = {}) {\n if (!this.listeners.has(key)) {\n this.listeners.set(key, new Map());\n }\n const listenerOptions: ListenerOptions = {\n crossTabSync: this.enableCrossTabSync && (options.crossTabSync ?? true),\n pollingIntervalMs: this.enablePolling\n ? this.normalizePollingInterval(options.pollingIntervalMs)\n : null,\n };\n this.listeners.get(key)!.set(callback, listenerOptions);\n\n this.updatePollingInterval();\n\n return () => {\n const listenersForKey = this.listeners.get(key);\n listenersForKey?.delete(callback);\n if (listenersForKey?.size === 0) {\n this.listeners.delete(key);\n }\n this.updatePollingInterval();\n };\n }\n\n // Called when WE change the value in this tab\n notify(key: string) {\n this.notifyListeners(key);\n }\n\n // Keep a cache of values to detect changes during polling\n private snapshotCache = new Map<string, string | null>();\n\n private notifyListeners(\n key: string,\n predicate?: (options: ListenerOptions) => boolean,\n ) {\n this.listeners.get(key)?.forEach((options, cb) => {\n if (!predicate || predicate(options)) {\n cb();\n }\n });\n }\n\n private notifyCrossTab(key: string) {\n this.notifyListeners(key, (options) => options.crossTabSync);\n }\n\n private notifyPolling(key: string) {\n this.notifyListeners(key, (options) => options.pollingIntervalMs !== null);\n }\n\n private normalizePollingInterval(\n pollingIntervalMs: number | null | undefined,\n ) {\n if (pollingIntervalMs === null) return null;\n if (pollingIntervalMs === undefined) return this.defaultPollingIntervalMs;\n if (pollingIntervalMs <= 0 || Number.isNaN(pollingIntervalMs)) {\n return null;\n }\n return pollingIntervalMs;\n }\n\n private getMinPollingIntervalMs() {\n let minInterval: number | null = null;\n this.listeners.forEach((listenersForKey) => {\n listenersForKey.forEach((options) => {\n if (options.pollingIntervalMs === null) return;\n if (minInterval === null || options.pollingIntervalMs < minInterval) {\n minInterval = options.pollingIntervalMs;\n }\n });\n });\n return minInterval;\n }\n\n private updatePollingInterval() {\n if (typeof window === \"undefined\") return;\n if (!this.enablePolling) return;\n\n const nextInterval = this.getMinPollingIntervalMs();\n if (nextInterval === null) {\n if (this.pollingIntervalId) {\n clearInterval(this.pollingIntervalId);\n }\n this.pollingIntervalId = null;\n this.pollingIntervalMsActive = null;\n this.snapshotCache.clear();\n return;\n }\n\n if (\n this.pollingIntervalId &&\n this.pollingIntervalMsActive === nextInterval\n ) {\n return;\n }\n\n if (this.pollingIntervalId) {\n clearInterval(this.pollingIntervalId);\n }\n\n this.pollingIntervalId = window.setInterval(() => {\n this.listeners.forEach((listenersForKey, key) => {\n const shouldPoll = Array.from(listenersForKey.values()).some(\n (options) => options.pollingIntervalMs !== null,\n );\n if (!shouldPoll) return;\n\n const currentValue = this.storage.getItem(key);\n const lastValue = this.snapshotCache.get(key);\n\n if (currentValue !== lastValue) {\n this.snapshotCache.set(key, currentValue);\n this.notifyPolling(key);\n }\n });\n }, nextInterval);\n this.pollingIntervalMsActive = nextInterval;\n }\n}\n\n// Lazy-initialized singletons to avoid SSR crashes (window is not defined on server)\nconst localStorageFallback = new MemoryStorageAdapter();\nconst sessionStorageFallback = new MemoryStorageAdapter();\n\nlet _localStorageSync: StorageSyncManager | null = null;\nlet _sessionStorageSync: StorageSyncManager | null = null;\nlet _memoryStorageSync: StorageSyncManager | null = null;\n\nfunction getLocalStorageSync(): StorageSyncManager {\n if (!_localStorageSync) {\n if (typeof window !== \"undefined\") {\n _localStorageSync = new StorageSyncManager(\n new FallbackStorageAdapter(window.localStorage, localStorageFallback),\n );\n } else {\n // SSR fallback - use memory storage\n return getMemoryStorageSync();\n }\n }\n return _localStorageSync;\n}\n\nfunction getSessionStorageSync(): StorageSyncManager {\n if (!_sessionStorageSync) {\n if (typeof window !== \"undefined\") {\n _sessionStorageSync = new StorageSyncManager(\n new FallbackStorageAdapter(\n window.sessionStorage,\n sessionStorageFallback,\n ),\n );\n } else {\n // SSR fallback - use memory storage\n return getMemoryStorageSync();\n }\n }\n return _sessionStorageSync;\n}\n\nfunction getMemoryStorageSync(): StorageSyncManager {\n if (!_memoryStorageSync) {\n _memoryStorageSync = new StorageSyncManager(\n new MemoryStorageAdapter(),\n 2000,\n {\n enableCrossTabSync: false,\n enablePolling: false,\n },\n );\n }\n return _memoryStorageSync;\n}\n\n/**\n * Proxy object for localStorage sync manager with lazy initialization.\n * Safe to import in SSR environments.\n */\nexport const localStorageSync = {\n get storage() {\n return getLocalStorageSync().storage;\n },\n subscribe(key: string, callback: () => void, options?: SubscribeOptions) {\n return getLocalStorageSync().subscribe(key, callback, options);\n },\n notify(key: string) {\n return getLocalStorageSync().notify(key);\n },\n};\n\n/**\n * Proxy object for sessionStorage sync manager with lazy initialization.\n * Safe to import in SSR environments.\n */\nexport const sessionStorageSync = {\n get storage() {\n return getSessionStorageSync().storage;\n },\n subscribe(key: string, callback: () => void, options?: SubscribeOptions) {\n return getSessionStorageSync().subscribe(key, callback, options);\n },\n notify(key: string) {\n return getSessionStorageSync().notify(key);\n },\n};\n\n/**\n * Proxy object for memory storage sync manager with lazy initialization.\n * Safe to import in SSR environments.\n */\nexport const memoryStorageSync = {\n get storage() {\n return getMemoryStorageSync().storage;\n },\n subscribe(key: string, callback: () => void, options?: SubscribeOptions) {\n return getMemoryStorageSync().subscribe(key, callback, options);\n },\n notify(key: string) {\n return getMemoryStorageSync().notify(key);\n },\n};\n","\"use client\";\n\nimport { Codec, inferCodec, JsonCodec } from \"./codecs\";\nimport {\n localStorageSync,\n memoryStorageSync,\n sessionStorageSync,\n} from \"./storage\";\nimport {\n StoragePersistedStateOptions,\n StorageType,\n} from \"./useStoragePersistedState\";\n\nfunction getSyncManager(storageType: StorageType | undefined) {\n if (storageType === \"sessionStorage\") return sessionStorageSync;\n if (storageType === \"memory\") return memoryStorageSync;\n return localStorageSync;\n}\n\nfunction resolveCodec<T>(\n key: string,\n valueHint: T,\n options?: StoragePersistedStateOptions<T>,\n): Codec<T> {\n if (options?.codec) return options.codec;\n\n if ((valueHint === undefined || valueHint === null) && !options?.codec) {\n console.warn(\n `storagePersistedState: Key \"${key}\" uses undefined or null default without explicit Codec. defaulting to JSON.`,\n );\n }\n\n return inferCodec(valueHint);\n}\n\n// Overload 1: Default provided, T inferred\n/**\n * Read a persisted value from storage using the same codec behavior as the hook.\n *\n * Returns the provided defaultValue when the key is missing or parsing fails.\n */\nexport function getStoragePersistedState<T>(\n key: string,\n defaultValue: Exclude<T, null | undefined>,\n options?: StoragePersistedStateOptions<T>,\n): T;\n\n// Overload 2: Explicit Codec provided, defaultValue can be null or undefined\n/**\n * Read a persisted value from storage using an explicit codec.\n *\n * Use this overload when defaultValue is null or undefined.\n */\nexport function getStoragePersistedState<T>(\n key: string,\n defaultValue: null | undefined,\n options: StoragePersistedStateOptions<T> & { codec: Codec<T> },\n): T | null;\n\nexport function getStoragePersistedState<T>(\n key: string,\n defaultValue: T,\n options: StoragePersistedStateOptions<T> = {},\n) {\n const syncManager = getSyncManager(options.storageType);\n const adapter = syncManager.storage;\n const codec = resolveCodec(key, defaultValue, options);\n const raw = adapter.getItem(key);\n\n if (raw === null && defaultValue !== undefined) {\n return defaultValue as T;\n }\n\n try {\n return codec.decode(raw);\n } catch (error) {\n console.error(`Error parsing storage key \"${key}\"`, error);\n return defaultValue as T;\n }\n}\n\n// Overload 1: Default provided, T inferred\n/**\n * Set a persisted value in storage and notify active hooks for the same key.\n *\n * Supports functional updates using the current decoded value.\n */\nexport function setStoragePersistedState<T>(\n key: string,\n newValue: Exclude<T, null | undefined>,\n options?: StoragePersistedStateOptions<T>,\n): void;\n\n// Overload 2: Explicit Codec provided, newValue can be null or undefined\n/**\n * Set a persisted value in storage using an explicit codec and notify listeners.\n *\n * Use this overload when the new value is null or undefined or when you want custom serialization.\n */\nexport function setStoragePersistedState<T>(\n key: string,\n newValue: T | ((prev: T | null) => T),\n options: StoragePersistedStateOptions<T> & { codec: Codec<T> },\n): void;\n\nexport function setStoragePersistedState<T>(\n key: string,\n newValueOrFn: T | ((prev: T | null) => T),\n options: StoragePersistedStateOptions<T> = {},\n) {\n const syncManager = getSyncManager(options.storageType);\n const adapter = syncManager.storage;\n\n let codec: Codec<T>;\n if (!(newValueOrFn instanceof Function)) {\n codec = resolveCodec(key, newValueOrFn, options);\n } else {\n // For functional updates, we cannot infer from newValue\n if (!options.codec) {\n console.warn(\n `storagePersistedState: Key \"${key}\" uses functional update without explicit Codec. defaulting to JSON.`,\n );\n }\n codec = options.codec ?? (JsonCodec as unknown as Codec<T>);\n }\n\n try {\n const currentRaw = adapter.getItem(key);\n const current = currentRaw !== null ? codec.decode(currentRaw) : null;\n\n const newValue =\n newValueOrFn instanceof Function ? newValueOrFn(current) : newValueOrFn;\n\n if (newValue === undefined || newValue === null) {\n adapter.removeItem(key);\n } else {\n const encoded = codec.encode(newValue);\n if (encoded === null || encoded === undefined) {\n adapter.removeItem(key);\n } else {\n adapter.setItem(key, encoded);\n }\n }\n\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error setting storage key \"${key}\":`, error);\n }\n}\n\n/**\n * Remove a persisted value from storage and notify active hooks for the same key.\n *\n * Defaults to localStorage when no storageType is provided.\n */\nexport function removeStoragePersistedState(\n key: string,\n storageType?: StorageType,\n) {\n const syncManager = getSyncManager(storageType);\n const adapter = syncManager.storage;\n\n try {\n adapter.removeItem(key);\n syncManager.notify(key);\n } catch (error) {\n console.error(`Error removing storage key \"${key}\":`, error);\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-storage-persisted-state",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "A robust, type-safe React hook for persisting state in localStorage/sessionStorage with cross-component and cross-tab sync",
5
5
  "license": "MIT",
6
6
  "author": "Mikko Haapanen <mikko.h@iki.fi>",