@pyreon/storage 0.11.4 → 0.11.6

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/README.md CHANGED
@@ -15,9 +15,9 @@ import { useStorage, useCookie, useSessionStorage, useIndexedDB } from '@pyreon/
15
15
 
16
16
  // localStorage — persistent, cross-tab synced
17
17
  const theme = useStorage('theme', 'light')
18
- theme() // read reactively
19
- theme.set('dark') // updates signal + localStorage
20
- theme.remove() // remove from storage
18
+ theme() // read reactively
19
+ theme.set('dark') // updates signal + localStorage
20
+ theme.remove() // remove from storage
21
21
 
22
22
  // Cookie — SSR-readable, configurable expiry
23
23
  const locale = useCookie('locale', 'en', { maxAge: 365 * 86400 })
@@ -47,13 +47,13 @@ const secret = useEncryptedStorage('api-key', '')
47
47
 
48
48
  ### Hooks
49
49
 
50
- | Hook | Backend | Description |
51
- | --- | --- | --- |
52
- | `useStorage(key, default, options?)` | localStorage | Persistent, cross-tab synced |
53
- | `useSessionStorage(key, default, options?)` | sessionStorage | Tab-scoped |
54
- | `useCookie(key, default, options?)` | document.cookie | SSR-readable, configurable expiry |
55
- | `useIndexedDB(key, default, options?)` | IndexedDB | Large data, debounced writes |
56
- | `useMemoryStorage(key, default)` | in-memory | SSR/testing |
50
+ | Hook | Backend | Description |
51
+ | ------------------------------------------- | --------------- | --------------------------------- |
52
+ | `useStorage(key, default, options?)` | localStorage | Persistent, cross-tab synced |
53
+ | `useSessionStorage(key, default, options?)` | sessionStorage | Tab-scoped |
54
+ | `useCookie(key, default, options?)` | document.cookie | SSR-readable, configurable expiry |
55
+ | `useIndexedDB(key, default, options?)` | IndexedDB | Large data, debounced writes |
56
+ | `useMemoryStorage(key, default)` | in-memory | SSR/testing |
57
57
 
58
58
  All hooks return `StorageSignal<T>` — extends `Signal<T>` with `.remove()`.
59
59
 
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/registry.ts","../src/utils.ts","../src/cookie.ts","../src/custom.ts","../src/indexed-db.ts","../src/local.ts","../src/session.ts","../src/clear.ts"],"sourcesContent":["import type { StorageSignal } from \"./types\"\n\n// ─── Signal Registry ─────────────────────────────────────────────────────────\n\ninterface RegistryEntry<T = unknown> {\n signal: StorageSignal<T>\n defaultValue: T\n backend: string\n}\n\nconst registry = new Map<string, RegistryEntry>()\n\n/**\n * Build a composite key from backend type + storage key to avoid\n * collisions between different backends using the same key name.\n */\nfunction registryKey(backend: string, key: string): string {\n return `${backend}:${key}`\n}\n\n/**\n * Get an existing signal from the registry.\n */\nexport function getEntry<T>(backend: string, key: string): RegistryEntry<T> | undefined {\n return registry.get(registryKey(backend, key)) as RegistryEntry<T> | undefined\n}\n\n/**\n * Register a new signal in the registry.\n */\nexport function setEntry<T>(\n backend: string,\n key: string,\n signal: StorageSignal<T>,\n defaultValue: T,\n): void {\n registry.set(registryKey(backend, key), { signal, defaultValue, backend })\n}\n\n/**\n * Remove an entry from the registry.\n */\nexport function removeEntry(backend: string, key: string): void {\n registry.delete(registryKey(backend, key))\n}\n\n/**\n * Get all entries for a specific backend.\n */\nexport function getEntriesByBackend(backend: string): RegistryEntry[] {\n const entries: RegistryEntry[] = []\n for (const entry of registry.values()) {\n if (entry.backend === backend) entries.push(entry)\n }\n return entries\n}\n\n/**\n * Clear all entries from the registry. Used for testing.\n */\nexport function _resetRegistry(): void {\n registry.clear()\n}\n","import type { StorageOptions } from \"./types\"\n\n// ─── SSR Detection ───────────────────────────────────────────────────────────\n\n/**\n * Check if we're running in a browser environment.\n */\nexport function isBrowser(): boolean {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\"\n}\n\n// ─── Serialization ───────────────────────────────────────────────────────────\n\n/**\n * Serialize a value to a string for storage.\n */\nexport function serialize<T>(value: T, serializer?: StorageOptions<T>[\"serializer\"]): string {\n if (serializer) return serializer(value)\n return JSON.stringify(value)\n}\n\n/**\n * Deserialize a raw string from storage back to a typed value.\n * Returns the default value if deserialization fails.\n */\nexport function deserialize<T>(\n raw: string,\n defaultValue: T,\n deserializer?: StorageOptions<T>[\"deserializer\"],\n onError?: StorageOptions<T>[\"onError\"],\n): T {\n try {\n if (deserializer) return deserializer(raw)\n return JSON.parse(raw) as T\n } catch (e) {\n if (onError) {\n const result = onError(e as Error)\n return result !== undefined ? result : defaultValue\n }\n return defaultValue\n }\n}\n\n// ─── Safe Storage Access ─────────────────────────────────────────────────────\n\n/**\n * Safely get a Web Storage instance (localStorage or sessionStorage).\n * Returns null if not available (SSR, security restrictions, etc.).\n */\nexport function getWebStorage(type: \"local\" | \"session\"): Storage | null {\n if (!isBrowser()) return null\n try {\n const storage = type === \"local\" ? window.localStorage : window.sessionStorage\n // Test that it actually works (can throw in private browsing)\n const testKey = \"__pyreon_storage_test__\"\n storage.setItem(testKey, \"1\")\n storage.removeItem(testKey)\n return storage\n } catch {\n return null\n }\n}\n","import { signal } from \"@pyreon/reactivity\"\nimport { getEntry, removeEntry, setEntry } from \"./registry\"\nimport type { CookieOptions, StorageSignal } from \"./types\"\nimport { deserialize, isBrowser, serialize } from \"./utils\"\n\n// ─── Server-side cookie source ───────────────────────────────────────────────\n\nlet serverCookieString = \"\"\n\n/**\n * Set the cookie source string for SSR. Call this once per request\n * with the raw Cookie header value.\n *\n * @example\n * ```ts\n * // In your SSR request handler\n * setCookieSource(request.headers.get('cookie') ?? '')\n * ```\n */\nexport function setCookieSource(cookieHeader: string): void {\n serverCookieString = cookieHeader\n}\n\n// ─── Cookie parsing ──────────────────────────────────────────────────────────\n\nfunction parseCookies(cookieString: string): Map<string, string> {\n const cookies = new Map<string, string>()\n if (!cookieString) return cookies\n\n for (const pair of cookieString.split(\";\")) {\n const eqIndex = pair.indexOf(\"=\")\n if (eqIndex === -1) continue\n const name = pair.slice(0, eqIndex).trim()\n const value = pair.slice(eqIndex + 1).trim()\n if (name) cookies.set(name, decodeURIComponent(value))\n }\n\n return cookies\n}\n\nfunction getCookieString(): string {\n if (isBrowser()) return document.cookie\n return serverCookieString\n}\n\nfunction readCookie(key: string): string | null {\n const cookies = parseCookies(getCookieString())\n return cookies.get(key) ?? null\n}\n\n// ─── Cookie writing ──────────────────────────────────────────────────────────\n\nfunction writeCookie<T>(key: string, value: T, options: CookieOptions<T>): void {\n if (!isBrowser()) return\n\n const serialized = serialize(value, options.serializer)\n let cookie = `${encodeURIComponent(key)}=${encodeURIComponent(serialized)}`\n\n if (options.maxAge !== undefined) {\n cookie += `; max-age=${options.maxAge}`\n }\n if (options.expires) {\n cookie += `; expires=${options.expires.toUTCString()}`\n }\n cookie += `; path=${options.path ?? \"/\"}`\n if (options.domain) {\n cookie += `; domain=${options.domain}`\n }\n if (options.secure) {\n cookie += \"; secure\"\n }\n cookie += `; samesite=${options.sameSite ?? \"lax\"}`\n\n // biome-ignore lint/suspicious/noDocumentCookie: document.cookie is the standard cookie write API\n document.cookie = cookie\n}\n\nfunction deleteCookie<T>(key: string, options: CookieOptions<T>): void {\n if (!isBrowser()) return\n\n let cookie = `${encodeURIComponent(key)}=; max-age=0`\n cookie += `; path=${options.path ?? \"/\"}`\n if (options.domain) {\n cookie += `; domain=${options.domain}`\n }\n\n // biome-ignore lint/suspicious/noDocumentCookie: document.cookie is the standard cookie write API\n document.cookie = cookie\n}\n\n// ─── useCookie ───────────────────────────────────────────────────────────────\n\n/**\n * Reactive signal backed by a browser cookie. SSR-compatible when\n * used with setCookieSource().\n *\n * @example\n * ```ts\n * const locale = useCookie('locale', 'en', {\n * maxAge: 60 * 60 * 24 * 365, // 1 year\n * path: '/',\n * sameSite: 'lax',\n * })\n * locale() // 'en'\n * locale.set('de') // sets cookie + updates signal\n * locale.remove() // deletes cookie, resets to default\n * ```\n */\nexport function useCookie<T>(\n key: string,\n defaultValue: T,\n options: CookieOptions<T> = {},\n): StorageSignal<T> {\n // Return existing signal if already registered\n const existing = getEntry<T>(\"cookie\", key)\n if (existing) return existing.signal\n\n // Read initial value from cookie\n const raw = readCookie(key)\n const initialValue =\n raw !== null\n ? deserialize(raw, defaultValue, options.deserializer, options.onError)\n : defaultValue\n\n const sig = signal<T>(initialValue)\n\n // Build the storage signal\n const storageSig = (() => sig()) as unknown as StorageSignal<T>\n\n storageSig.peek = () => sig.peek()\n storageSig.subscribe = (listener: () => void) => sig.subscribe(listener)\n storageSig.direct = (updater: () => void) => sig.direct(updater)\n storageSig.debug = () => sig.debug()\n\n Object.defineProperty(storageSig, \"label\", {\n get: () => sig.label,\n set: (v: string | undefined) => {\n sig.label = v\n },\n })\n\n storageSig.set = (value: T) => {\n sig.set(value)\n writeCookie(key, value, options)\n }\n\n storageSig.update = (fn: (current: T) => T) => {\n const newValue = fn(sig.peek())\n storageSig.set(newValue)\n }\n\n storageSig.remove = () => {\n sig.set(defaultValue)\n deleteCookie(key, options)\n removeEntry(\"cookie\", key)\n }\n\n setEntry(\"cookie\", key, storageSig, defaultValue)\n\n return storageSig\n}\n","import { signal } from \"@pyreon/reactivity\"\nimport { getEntry, removeEntry, setEntry } from \"./registry\"\nimport type { StorageBackend, StorageOptions, StorageSignal } from \"./types\"\nimport { deserialize, serialize } from \"./utils\"\n\n// ─── createStorage ───────────────────────────────────────────────────────────\n\n/**\n * Create a custom storage hook backed by any synchronous storage backend.\n * Useful for encrypted storage, in-memory storage, or custom adapters.\n *\n * @example\n * ```ts\n * const useEncrypted = createStorage({\n * get: (key) => decrypt(localStorage.getItem(key)),\n * set: (key, value) => localStorage.setItem(key, encrypt(value)),\n * remove: (key) => localStorage.removeItem(key),\n * })\n *\n * const secret = useEncrypted('api-key', '')\n * ```\n */\nexport function createStorage(\n backend: StorageBackend,\n backendName?: string,\n): <T>(key: string, defaultValue: T, options?: StorageOptions<T>) => StorageSignal<T> {\n const name = backendName ?? \"custom\"\n\n return function useCustomStorage<T>(\n key: string,\n defaultValue: T,\n options?: StorageOptions<T>,\n ): StorageSignal<T> {\n // Return existing signal if already registered\n const existing = getEntry<T>(name, key)\n if (existing) return existing.signal\n\n // Read initial value\n let initialValue = defaultValue\n try {\n const raw = backend.get(key)\n if (raw !== null) {\n initialValue = deserialize(raw, defaultValue, options?.deserializer, options?.onError)\n }\n } catch {\n // Backend read failed — use default\n }\n\n const sig = signal<T>(initialValue)\n\n // Build the storage signal\n const storageSig = (() => sig()) as unknown as StorageSignal<T>\n\n storageSig.peek = () => sig.peek()\n storageSig.subscribe = (listener: () => void) => sig.subscribe(listener)\n storageSig.direct = (updater: () => void) => sig.direct(updater)\n storageSig.debug = () => sig.debug()\n\n Object.defineProperty(storageSig, \"label\", {\n get: () => sig.label,\n set: (v: string | undefined) => {\n sig.label = v\n },\n })\n\n storageSig.set = (value: T) => {\n sig.set(value)\n try {\n backend.set(key, serialize(value, options?.serializer))\n } catch {\n // Write failed — signal still updates\n }\n }\n\n storageSig.update = (fn: (current: T) => T) => {\n const newValue = fn(sig.peek())\n storageSig.set(newValue)\n }\n\n storageSig.remove = () => {\n sig.set(defaultValue)\n try {\n backend.remove(key)\n } catch {\n // Remove failed\n }\n removeEntry(name, key)\n }\n\n setEntry(name, key, storageSig, defaultValue)\n\n return storageSig\n }\n}\n\n// ─── Memory storage ──────────────────────────────────────────────────────────\n\n/**\n * In-memory storage backend. Useful for SSR, testing, or ephemeral state.\n * Values are lost on page unload.\n *\n * @example\n * ```ts\n * import { useMemoryStorage } from '@pyreon/storage'\n *\n * const temp = useMemoryStorage('key', 'default')\n * ```\n */\nexport const useMemoryStorage = createStorage(\n (() => {\n const store = new Map<string, string>()\n return {\n get: (key: string) => store.get(key) ?? null,\n set: (key: string, value: string) => store.set(key, value),\n remove: (key: string) => store.delete(key),\n }\n })(),\n \"memory\",\n)\n","import { signal } from \"@pyreon/reactivity\"\nimport { getEntry, removeEntry, setEntry } from \"./registry\"\nimport type { IndexedDBOptions, StorageSignal } from \"./types\"\nimport { deserialize, isBrowser, serialize } from \"./utils\"\n\n// ─── Database management ─────────────────────────────────────────────────────\n\nconst dbCache = new Map<string, Promise<IDBDatabase>>()\n\nfunction openDB(dbName: string, storeName: string): Promise<IDBDatabase> {\n const cacheKey = `${dbName}:${storeName}`\n const cached = dbCache.get(cacheKey)\n if (cached) return cached\n\n const promise = new Promise<IDBDatabase>((resolve, reject) => {\n const request = indexedDB.open(dbName, 1)\n\n request.onupgradeneeded = () => {\n const db = request.result\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName)\n }\n }\n\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n\n dbCache.set(cacheKey, promise)\n return promise\n}\n\nfunction idbGet(db: IDBDatabase, storeName: string, key: string): Promise<string | null> {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(storeName, \"readonly\")\n const store = tx.objectStore(storeName)\n const request = store.get(key)\n request.onsuccess = () =>\n resolve(request.result !== undefined ? (request.result as string) : null)\n request.onerror = () => reject(request.error)\n })\n}\n\nfunction idbSet(db: IDBDatabase, storeName: string, key: string, value: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(storeName, \"readwrite\")\n const store = tx.objectStore(storeName)\n const request = store.put(value, key)\n request.onsuccess = () => resolve()\n request.onerror = () => reject(request.error)\n })\n}\n\nfunction idbDelete(db: IDBDatabase, storeName: string, key: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(storeName, \"readwrite\")\n const store = tx.objectStore(storeName)\n const request = store.delete(key)\n request.onsuccess = () => resolve()\n request.onerror = () => reject(request.error)\n })\n}\n\n// ─── useIndexedDB ────────────────────────────────────────────────────────────\n\n/**\n * Reactive signal backed by IndexedDB. Suitable for large or structured\n * data that exceeds localStorage limits. Writes are debounced.\n *\n * The signal starts with `defaultValue` and updates asynchronously\n * when the stored value is read from IndexedDB.\n *\n * @example\n * ```ts\n * const draft = useIndexedDB('article-draft', { title: '', body: '' })\n * draft() // { title: '', body: '' } initially, then stored value\n * draft.set({ title: 'My Post', body: '...' }) // signal updates immediately, IDB write is debounced\n * ```\n */\nexport function useIndexedDB<T>(\n key: string,\n defaultValue: T,\n options: IndexedDBOptions<T> = {},\n): StorageSignal<T> {\n // Return existing signal if already registered\n const existing = getEntry<T>(\"indexeddb\", key)\n if (existing) return existing.signal\n\n const dbName = options.dbName ?? \"pyreon-storage\"\n const storeName = options.storeName ?? \"kv\"\n const debounceMs = options.debounceMs ?? 100\n\n const sig = signal<T>(defaultValue)\n\n // Async initial load\n if (isBrowser() && typeof indexedDB !== \"undefined\") {\n openDB(dbName, storeName)\n .then((db) => idbGet(db, storeName, key))\n .then((raw) => {\n if (raw !== null) {\n const value = deserialize(raw, defaultValue, options.deserializer, options.onError)\n sig.set(value)\n }\n })\n .catch(() => {\n // IndexedDB not available — signal keeps defaultValue\n })\n }\n\n // Debounced write\n let writeTimer: ReturnType<typeof setTimeout> | null = null\n let pendingValue: T | undefined\n\n function flushWrite(): void {\n if (pendingValue === undefined) return\n const value = pendingValue\n pendingValue = undefined\n\n if (!isBrowser() || typeof indexedDB === \"undefined\") return\n\n openDB(dbName, storeName)\n .then((db) => idbSet(db, storeName, key, serialize(value, options.serializer)))\n .catch(() => {\n // Write failed — signal still has the correct value\n })\n }\n\n function scheduleWrite(value: T): void {\n pendingValue = value\n if (writeTimer !== null) clearTimeout(writeTimer)\n writeTimer = setTimeout(flushWrite, debounceMs)\n }\n\n // Build the storage signal\n const storageSig = (() => sig()) as unknown as StorageSignal<T>\n\n storageSig.peek = () => sig.peek()\n storageSig.subscribe = (listener: () => void) => sig.subscribe(listener)\n storageSig.direct = (updater: () => void) => sig.direct(updater)\n storageSig.debug = () => sig.debug()\n\n Object.defineProperty(storageSig, \"label\", {\n get: () => sig.label,\n set: (v: string | undefined) => {\n sig.label = v\n },\n })\n\n storageSig.set = (value: T) => {\n sig.set(value)\n scheduleWrite(value)\n }\n\n storageSig.update = (fn: (current: T) => T) => {\n const newValue = fn(sig.peek())\n storageSig.set(newValue)\n }\n\n storageSig.remove = () => {\n sig.set(defaultValue)\n pendingValue = undefined\n if (writeTimer !== null) clearTimeout(writeTimer)\n\n if (isBrowser() && typeof indexedDB !== \"undefined\") {\n openDB(dbName, storeName)\n .then((db) => idbDelete(db, storeName, key))\n .catch(() => {\n // Delete failed — signal already reset\n })\n }\n\n removeEntry(\"indexeddb\", key)\n }\n\n setEntry(\"indexeddb\", key, storageSig, defaultValue)\n\n return storageSig\n}\n\n/**\n * Reset the database cache. For testing only.\n */\nexport function _resetDBCache(): void {\n dbCache.clear()\n}\n","import { signal } from \"@pyreon/reactivity\"\nimport { getEntry, removeEntry, setEntry } from \"./registry\"\nimport type { StorageOptions, StorageSignal } from \"./types\"\nimport { deserialize, getWebStorage, isBrowser, serialize } from \"./utils\"\n\n// ─── Cross-tab sync ──────────────────────────────────────────────────────────\n\nlet listenerAttached = false\n\nfunction attachStorageListener(): void {\n if (listenerAttached || !isBrowser()) return\n listenerAttached = true\n\n window.addEventListener(\"storage\", (e) => {\n if (!e.key) return\n const entry = getEntry(\"local\", e.key)\n if (!entry) return\n\n const newValue =\n e.newValue !== null ? deserialize(e.newValue, entry.defaultValue) : entry.defaultValue\n\n entry.signal.set(newValue)\n })\n}\n\n// ─── useStorage ──────────────────────────────────────────────────────────────\n\n/**\n * Reactive signal backed by localStorage. Automatically syncs across\n * browser tabs via the native `storage` event.\n *\n * @example\n * ```ts\n * const theme = useStorage('theme', 'light')\n * theme() // 'light' (or stored value)\n * theme.set('dark') // updates signal + localStorage\n * theme.remove() // clears storage, resets to default\n * ```\n */\nexport function useStorage<T>(\n key: string,\n defaultValue: T,\n options?: StorageOptions<T>,\n): StorageSignal<T> {\n // Return existing signal if already registered\n const existing = getEntry<T>(\"local\", key)\n if (existing) return existing.signal\n\n const storage = getWebStorage(\"local\")\n\n // Read initial value from storage\n let initialValue = defaultValue\n if (storage) {\n const raw = storage.getItem(key)\n if (raw !== null) {\n initialValue = deserialize(raw, defaultValue, options?.deserializer, options?.onError)\n }\n }\n\n const sig = signal<T>(initialValue)\n\n // Create the storage signal by extending the base signal\n const storageSig = createStorageSignal(sig, key, defaultValue, \"local\", options)\n\n setEntry(\"local\", key, storageSig, defaultValue)\n attachStorageListener()\n\n return storageSig\n}\n\n// ─── Storage Signal Factory ──────────────────────────────────────────────────\n\n/**\n * Wraps a base signal with storage persistence behavior.\n * Used by both useStorage and useSessionStorage.\n */\nexport function createStorageSignal<T>(\n sig: ReturnType<typeof signal<T>>,\n key: string,\n defaultValue: T,\n backend: \"local\" | \"session\",\n options?: StorageOptions<T>,\n): StorageSignal<T> {\n const storage = getWebStorage(backend)\n\n // The callable signal function (read)\n const storageSig = (() => sig()) as unknown as StorageSignal<T>\n\n // Delegate all signal methods\n storageSig.peek = () => sig.peek()\n storageSig.subscribe = (listener: () => void) => sig.subscribe(listener)\n storageSig.direct = (updater: () => void) => sig.direct(updater)\n storageSig.debug = () => sig.debug()\n\n Object.defineProperty(storageSig, \"label\", {\n get: () => sig.label,\n set: (v: string | undefined) => {\n sig.label = v\n },\n })\n\n // Override set to persist\n storageSig.set = (value: T) => {\n sig.set(value)\n if (storage) {\n try {\n storage.setItem(key, serialize(value, options?.serializer))\n } catch {\n // Storage full or blocked — signal still updates\n }\n }\n }\n\n // Override update to persist\n storageSig.update = (fn: (current: T) => T) => {\n const newValue = fn(sig.peek())\n storageSig.set(newValue)\n }\n\n // Add remove method\n storageSig.remove = () => {\n sig.set(defaultValue)\n if (storage) {\n storage.removeItem(key)\n }\n removeEntry(backend, key)\n }\n\n return storageSig\n}\n","import { signal } from \"@pyreon/reactivity\"\nimport { createStorageSignal } from \"./local\"\nimport { getEntry, setEntry } from \"./registry\"\nimport type { StorageOptions, StorageSignal } from \"./types\"\nimport { deserialize, getWebStorage } from \"./utils\"\n\n// ─── useSessionStorage ───────────────────────────────────────────────────────\n\n/**\n * Reactive signal backed by sessionStorage. Scoped to the current\n * browser tab — does not sync across tabs.\n *\n * @example\n * ```ts\n * const step = useSessionStorage('wizard-step', 0)\n * step() // 0 (or stored value)\n * step.set(3) // updates signal + sessionStorage\n * step.remove() // clears storage, resets to default\n * ```\n */\nexport function useSessionStorage<T>(\n key: string,\n defaultValue: T,\n options?: StorageOptions<T>,\n): StorageSignal<T> {\n // Return existing signal if already registered\n const existing = getEntry<T>(\"session\", key)\n if (existing) return existing.signal\n\n const storage = getWebStorage(\"session\")\n\n // Read initial value from storage\n let initialValue = defaultValue\n if (storage) {\n const raw = storage.getItem(key)\n if (raw !== null) {\n initialValue = deserialize(raw, defaultValue, options?.deserializer, options?.onError)\n }\n }\n\n const sig = signal<T>(initialValue)\n const storageSig = createStorageSignal(sig, key, defaultValue, \"session\", options)\n\n setEntry(\"session\", key, storageSig, defaultValue)\n\n return storageSig\n}\n","import { getEntriesByBackend, getEntry, removeEntry } from \"./registry\"\nimport { getWebStorage, isBrowser } from \"./utils\"\n\n// ─── Storage type mapping ────────────────────────────────────────────────────\n\ntype StorageType = \"local\" | \"session\" | \"cookie\" | \"indexeddb\" | \"all\"\n\n// ─── removeStorage ───────────────────────────────────────────────────────────\n\n/**\n * Remove a specific key from storage and reset its signal to the default value.\n *\n * @example\n * ```ts\n * removeStorage('theme') // from localStorage\n * removeStorage('step', { type: 'session' }) // from sessionStorage\n * removeStorage('locale', { type: 'cookie' }) // deletes cookie\n * ```\n */\nexport function removeStorage(\n key: string,\n options?: { type?: \"local\" | \"session\" | \"cookie\" | \"indexeddb\" },\n): void {\n const type = options?.type ?? \"local\"\n const entry = getEntry(type, key)\n\n if (entry) {\n entry.signal.remove()\n } else {\n // No signal registered — still try to clear the raw storage\n if (type === \"local\" || type === \"session\") {\n const storage = getWebStorage(type)\n if (storage) storage.removeItem(key)\n } else if (type === \"cookie\" && isBrowser()) {\n // biome-ignore lint/suspicious/noDocumentCookie: standard cookie deletion API\n document.cookie = `${encodeURIComponent(key)}=; max-age=0; path=/`\n }\n removeEntry(type, key)\n }\n}\n\n// ─── clearStorage ────────────────────────────────────────────────────────────\n\n/**\n * Clear all managed storage entries for a specific backend, or all backends.\n *\n * @example\n * ```ts\n * clearStorage() // clear all localStorage entries managed by @pyreon/storage\n * clearStorage('session') // clear all sessionStorage entries\n * clearStorage('cookie') // clear all managed cookies\n * clearStorage('all') // clear everything\n * ```\n */\nexport function clearStorage(type: StorageType = \"local\"): void {\n if (type === \"all\") {\n clearBackend(\"local\")\n clearBackend(\"session\")\n clearBackend(\"cookie\")\n clearBackend(\"indexeddb\")\n return\n }\n\n clearBackend(type)\n}\n\nfunction clearBackend(type: string): void {\n const entries = getEntriesByBackend(type)\n for (const entry of entries) {\n entry.signal.remove()\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/registry.ts","../src/utils.ts","../src/cookie.ts","../src/custom.ts","../src/indexed-db.ts","../src/local.ts","../src/session.ts","../src/clear.ts"],"sourcesContent":["import type { StorageSignal } from './types'\n\n// ─── Signal Registry ─────────────────────────────────────────────────────────\n\ninterface RegistryEntry<T = unknown> {\n signal: StorageSignal<T>\n defaultValue: T\n backend: string\n}\n\nconst registry = new Map<string, RegistryEntry>()\n\n/**\n * Build a composite key from backend type + storage key to avoid\n * collisions between different backends using the same key name.\n */\nfunction registryKey(backend: string, key: string): string {\n return `${backend}:${key}`\n}\n\n/**\n * Get an existing signal from the registry.\n */\nexport function getEntry<T>(backend: string, key: string): RegistryEntry<T> | undefined {\n return registry.get(registryKey(backend, key)) as RegistryEntry<T> | undefined\n}\n\n/**\n * Register a new signal in the registry.\n */\nexport function setEntry<T>(\n backend: string,\n key: string,\n signal: StorageSignal<T>,\n defaultValue: T,\n): void {\n registry.set(registryKey(backend, key), { signal, defaultValue, backend })\n}\n\n/**\n * Remove an entry from the registry.\n */\nexport function removeEntry(backend: string, key: string): void {\n registry.delete(registryKey(backend, key))\n}\n\n/**\n * Get all entries for a specific backend.\n */\nexport function getEntriesByBackend(backend: string): RegistryEntry[] {\n const entries: RegistryEntry[] = []\n for (const entry of registry.values()) {\n if (entry.backend === backend) entries.push(entry)\n }\n return entries\n}\n\n/**\n * Clear all entries from the registry. Used for testing.\n */\nexport function _resetRegistry(): void {\n registry.clear()\n}\n","import type { StorageOptions } from './types'\n\n// ─── SSR Detection ───────────────────────────────────────────────────────────\n\n/**\n * Check if we're running in a browser environment.\n */\nexport function isBrowser(): boolean {\n return typeof window !== 'undefined' && typeof document !== 'undefined'\n}\n\n// ─── Serialization ───────────────────────────────────────────────────────────\n\n/**\n * Serialize a value to a string for storage.\n */\nexport function serialize<T>(value: T, serializer?: StorageOptions<T>['serializer']): string {\n if (serializer) return serializer(value)\n return JSON.stringify(value)\n}\n\n/**\n * Deserialize a raw string from storage back to a typed value.\n * Returns the default value if deserialization fails.\n */\nexport function deserialize<T>(\n raw: string,\n defaultValue: T,\n deserializer?: StorageOptions<T>['deserializer'],\n onError?: StorageOptions<T>['onError'],\n): T {\n try {\n if (deserializer) return deserializer(raw)\n return JSON.parse(raw) as T\n } catch (e) {\n if (onError) {\n const result = onError(e as Error)\n return result !== undefined ? result : defaultValue\n }\n return defaultValue\n }\n}\n\n// ─── Safe Storage Access ─────────────────────────────────────────────────────\n\n/**\n * Safely get a Web Storage instance (localStorage or sessionStorage).\n * Returns null if not available (SSR, security restrictions, etc.).\n */\nexport function getWebStorage(type: 'local' | 'session'): Storage | null {\n if (!isBrowser()) return null\n try {\n const storage = type === 'local' ? window.localStorage : window.sessionStorage\n // Test that it actually works (can throw in private browsing)\n const testKey = '__pyreon_storage_test__'\n storage.setItem(testKey, '1')\n storage.removeItem(testKey)\n return storage\n } catch {\n return null\n }\n}\n","import { signal } from '@pyreon/reactivity'\nimport { getEntry, removeEntry, setEntry } from './registry'\nimport type { CookieOptions, StorageSignal } from './types'\nimport { deserialize, isBrowser, serialize } from './utils'\n\n// ─── Server-side cookie source ───────────────────────────────────────────────\n\nlet serverCookieString = ''\n\n/**\n * Set the cookie source string for SSR. Call this once per request\n * with the raw Cookie header value.\n *\n * @example\n * ```ts\n * // In your SSR request handler\n * setCookieSource(request.headers.get('cookie') ?? '')\n * ```\n */\nexport function setCookieSource(cookieHeader: string): void {\n serverCookieString = cookieHeader\n}\n\n// ─── Cookie parsing ──────────────────────────────────────────────────────────\n\nfunction parseCookies(cookieString: string): Map<string, string> {\n const cookies = new Map<string, string>()\n if (!cookieString) return cookies\n\n for (const pair of cookieString.split(';')) {\n const eqIndex = pair.indexOf('=')\n if (eqIndex === -1) continue\n const name = pair.slice(0, eqIndex).trim()\n const value = pair.slice(eqIndex + 1).trim()\n if (name) cookies.set(name, decodeURIComponent(value))\n }\n\n return cookies\n}\n\nfunction getCookieString(): string {\n if (isBrowser()) return document.cookie\n return serverCookieString\n}\n\nfunction readCookie(key: string): string | null {\n const cookies = parseCookies(getCookieString())\n return cookies.get(key) ?? null\n}\n\n// ─── Cookie writing ──────────────────────────────────────────────────────────\n\nfunction writeCookie<T>(key: string, value: T, options: CookieOptions<T>): void {\n if (!isBrowser()) return\n\n const serialized = serialize(value, options.serializer)\n let cookie = `${encodeURIComponent(key)}=${encodeURIComponent(serialized)}`\n\n if (options.maxAge !== undefined) {\n cookie += `; max-age=${options.maxAge}`\n }\n if (options.expires) {\n cookie += `; expires=${options.expires.toUTCString()}`\n }\n cookie += `; path=${options.path ?? '/'}`\n if (options.domain) {\n cookie += `; domain=${options.domain}`\n }\n if (options.secure) {\n cookie += '; secure'\n }\n cookie += `; samesite=${options.sameSite ?? 'lax'}`\n\n // biome-ignore lint/suspicious/noDocumentCookie: document.cookie is the standard cookie write API\n document.cookie = cookie\n}\n\nfunction deleteCookie<T>(key: string, options: CookieOptions<T>): void {\n if (!isBrowser()) return\n\n let cookie = `${encodeURIComponent(key)}=; max-age=0`\n cookie += `; path=${options.path ?? '/'}`\n if (options.domain) {\n cookie += `; domain=${options.domain}`\n }\n\n // biome-ignore lint/suspicious/noDocumentCookie: document.cookie is the standard cookie write API\n document.cookie = cookie\n}\n\n// ─── useCookie ───────────────────────────────────────────────────────────────\n\n/**\n * Reactive signal backed by a browser cookie. SSR-compatible when\n * used with setCookieSource().\n *\n * @example\n * ```ts\n * const locale = useCookie('locale', 'en', {\n * maxAge: 60 * 60 * 24 * 365, // 1 year\n * path: '/',\n * sameSite: 'lax',\n * })\n * locale() // 'en'\n * locale.set('de') // sets cookie + updates signal\n * locale.remove() // deletes cookie, resets to default\n * ```\n */\nexport function useCookie<T>(\n key: string,\n defaultValue: T,\n options: CookieOptions<T> = {},\n): StorageSignal<T> {\n // Return existing signal if already registered\n const existing = getEntry<T>('cookie', key)\n if (existing) return existing.signal\n\n // Read initial value from cookie\n const raw = readCookie(key)\n const initialValue =\n raw !== null\n ? deserialize(raw, defaultValue, options.deserializer, options.onError)\n : defaultValue\n\n const sig = signal<T>(initialValue)\n\n // Build the storage signal\n const storageSig = (() => sig()) as unknown as StorageSignal<T>\n\n storageSig.peek = () => sig.peek()\n storageSig.subscribe = (listener: () => void) => sig.subscribe(listener)\n storageSig.direct = (updater: () => void) => sig.direct(updater)\n storageSig.debug = () => sig.debug()\n\n Object.defineProperty(storageSig, 'label', {\n get: () => sig.label,\n set: (v: string | undefined) => {\n sig.label = v\n },\n })\n\n storageSig.set = (value: T) => {\n sig.set(value)\n writeCookie(key, value, options)\n }\n\n storageSig.update = (fn: (current: T) => T) => {\n const newValue = fn(sig.peek())\n storageSig.set(newValue)\n }\n\n storageSig.remove = () => {\n sig.set(defaultValue)\n deleteCookie(key, options)\n removeEntry('cookie', key)\n }\n\n setEntry('cookie', key, storageSig, defaultValue)\n\n return storageSig\n}\n","import { signal } from '@pyreon/reactivity'\nimport { getEntry, removeEntry, setEntry } from './registry'\nimport type { StorageBackend, StorageOptions, StorageSignal } from './types'\nimport { deserialize, serialize } from './utils'\n\n// ─── createStorage ───────────────────────────────────────────────────────────\n\n/**\n * Create a custom storage hook backed by any synchronous storage backend.\n * Useful for encrypted storage, in-memory storage, or custom adapters.\n *\n * @example\n * ```ts\n * const useEncrypted = createStorage({\n * get: (key) => decrypt(localStorage.getItem(key)),\n * set: (key, value) => localStorage.setItem(key, encrypt(value)),\n * remove: (key) => localStorage.removeItem(key),\n * })\n *\n * const secret = useEncrypted('api-key', '')\n * ```\n */\nexport function createStorage(\n backend: StorageBackend,\n backendName?: string,\n): <T>(key: string, defaultValue: T, options?: StorageOptions<T>) => StorageSignal<T> {\n const name = backendName ?? 'custom'\n\n return function useCustomStorage<T>(\n key: string,\n defaultValue: T,\n options?: StorageOptions<T>,\n ): StorageSignal<T> {\n // Return existing signal if already registered\n const existing = getEntry<T>(name, key)\n if (existing) return existing.signal\n\n // Read initial value\n let initialValue = defaultValue\n try {\n const raw = backend.get(key)\n if (raw !== null) {\n initialValue = deserialize(raw, defaultValue, options?.deserializer, options?.onError)\n }\n } catch {\n // Backend read failed — use default\n }\n\n const sig = signal<T>(initialValue)\n\n // Build the storage signal\n const storageSig = (() => sig()) as unknown as StorageSignal<T>\n\n storageSig.peek = () => sig.peek()\n storageSig.subscribe = (listener: () => void) => sig.subscribe(listener)\n storageSig.direct = (updater: () => void) => sig.direct(updater)\n storageSig.debug = () => sig.debug()\n\n Object.defineProperty(storageSig, 'label', {\n get: () => sig.label,\n set: (v: string | undefined) => {\n sig.label = v\n },\n })\n\n storageSig.set = (value: T) => {\n sig.set(value)\n try {\n backend.set(key, serialize(value, options?.serializer))\n } catch {\n // Write failed — signal still updates\n }\n }\n\n storageSig.update = (fn: (current: T) => T) => {\n const newValue = fn(sig.peek())\n storageSig.set(newValue)\n }\n\n storageSig.remove = () => {\n sig.set(defaultValue)\n try {\n backend.remove(key)\n } catch {\n // Remove failed\n }\n removeEntry(name, key)\n }\n\n setEntry(name, key, storageSig, defaultValue)\n\n return storageSig\n }\n}\n\n// ─── Memory storage ──────────────────────────────────────────────────────────\n\n/**\n * In-memory storage backend. Useful for SSR, testing, or ephemeral state.\n * Values are lost on page unload.\n *\n * @example\n * ```ts\n * import { useMemoryStorage } from '@pyreon/storage'\n *\n * const temp = useMemoryStorage('key', 'default')\n * ```\n */\nexport const useMemoryStorage = createStorage(\n (() => {\n const store = new Map<string, string>()\n return {\n get: (key: string) => store.get(key) ?? null,\n set: (key: string, value: string) => store.set(key, value),\n remove: (key: string) => store.delete(key),\n }\n })(),\n 'memory',\n)\n","import { signal } from '@pyreon/reactivity'\nimport { getEntry, removeEntry, setEntry } from './registry'\nimport type { IndexedDBOptions, StorageSignal } from './types'\nimport { deserialize, isBrowser, serialize } from './utils'\n\n// ─── Database management ─────────────────────────────────────────────────────\n\nconst dbCache = new Map<string, Promise<IDBDatabase>>()\n\nfunction openDB(dbName: string, storeName: string): Promise<IDBDatabase> {\n const cacheKey = `${dbName}:${storeName}`\n const cached = dbCache.get(cacheKey)\n if (cached) return cached\n\n const promise = new Promise<IDBDatabase>((resolve, reject) => {\n const request = indexedDB.open(dbName, 1)\n\n request.onupgradeneeded = () => {\n const db = request.result\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName)\n }\n }\n\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n\n dbCache.set(cacheKey, promise)\n return promise\n}\n\nfunction idbGet(db: IDBDatabase, storeName: string, key: string): Promise<string | null> {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(storeName, 'readonly')\n const store = tx.objectStore(storeName)\n const request = store.get(key)\n request.onsuccess = () =>\n resolve(request.result !== undefined ? (request.result as string) : null)\n request.onerror = () => reject(request.error)\n })\n}\n\nfunction idbSet(db: IDBDatabase, storeName: string, key: string, value: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(storeName, 'readwrite')\n const store = tx.objectStore(storeName)\n const request = store.put(value, key)\n request.onsuccess = () => resolve()\n request.onerror = () => reject(request.error)\n })\n}\n\nfunction idbDelete(db: IDBDatabase, storeName: string, key: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(storeName, 'readwrite')\n const store = tx.objectStore(storeName)\n const request = store.delete(key)\n request.onsuccess = () => resolve()\n request.onerror = () => reject(request.error)\n })\n}\n\n// ─── useIndexedDB ────────────────────────────────────────────────────────────\n\n/**\n * Reactive signal backed by IndexedDB. Suitable for large or structured\n * data that exceeds localStorage limits. Writes are debounced.\n *\n * The signal starts with `defaultValue` and updates asynchronously\n * when the stored value is read from IndexedDB.\n *\n * @example\n * ```ts\n * const draft = useIndexedDB('article-draft', { title: '', body: '' })\n * draft() // { title: '', body: '' } initially, then stored value\n * draft.set({ title: 'My Post', body: '...' }) // signal updates immediately, IDB write is debounced\n * ```\n */\nexport function useIndexedDB<T>(\n key: string,\n defaultValue: T,\n options: IndexedDBOptions<T> = {},\n): StorageSignal<T> {\n // Return existing signal if already registered\n const existing = getEntry<T>('indexeddb', key)\n if (existing) return existing.signal\n\n const dbName = options.dbName ?? 'pyreon-storage'\n const storeName = options.storeName ?? 'kv'\n const debounceMs = options.debounceMs ?? 100\n\n const sig = signal<T>(defaultValue)\n\n // Async initial load\n if (isBrowser() && typeof indexedDB !== 'undefined') {\n openDB(dbName, storeName)\n .then((db) => idbGet(db, storeName, key))\n .then((raw) => {\n if (raw !== null) {\n const value = deserialize(raw, defaultValue, options.deserializer, options.onError)\n sig.set(value)\n }\n })\n .catch(() => {\n // IndexedDB not available — signal keeps defaultValue\n })\n }\n\n // Debounced write\n let writeTimer: ReturnType<typeof setTimeout> | null = null\n let pendingValue: T | undefined\n\n function flushWrite(): void {\n if (pendingValue === undefined) return\n const value = pendingValue\n pendingValue = undefined\n\n if (!isBrowser() || typeof indexedDB === 'undefined') return\n\n openDB(dbName, storeName)\n .then((db) => idbSet(db, storeName, key, serialize(value, options.serializer)))\n .catch(() => {\n // Write failed — signal still has the correct value\n })\n }\n\n function scheduleWrite(value: T): void {\n pendingValue = value\n if (writeTimer !== null) clearTimeout(writeTimer)\n writeTimer = setTimeout(flushWrite, debounceMs)\n }\n\n // Build the storage signal\n const storageSig = (() => sig()) as unknown as StorageSignal<T>\n\n storageSig.peek = () => sig.peek()\n storageSig.subscribe = (listener: () => void) => sig.subscribe(listener)\n storageSig.direct = (updater: () => void) => sig.direct(updater)\n storageSig.debug = () => sig.debug()\n\n Object.defineProperty(storageSig, 'label', {\n get: () => sig.label,\n set: (v: string | undefined) => {\n sig.label = v\n },\n })\n\n storageSig.set = (value: T) => {\n sig.set(value)\n scheduleWrite(value)\n }\n\n storageSig.update = (fn: (current: T) => T) => {\n const newValue = fn(sig.peek())\n storageSig.set(newValue)\n }\n\n storageSig.remove = () => {\n sig.set(defaultValue)\n pendingValue = undefined\n if (writeTimer !== null) clearTimeout(writeTimer)\n\n if (isBrowser() && typeof indexedDB !== 'undefined') {\n openDB(dbName, storeName)\n .then((db) => idbDelete(db, storeName, key))\n .catch(() => {\n // Delete failed — signal already reset\n })\n }\n\n removeEntry('indexeddb', key)\n }\n\n setEntry('indexeddb', key, storageSig, defaultValue)\n\n return storageSig\n}\n\n/**\n * Reset the database cache. For testing only.\n */\nexport function _resetDBCache(): void {\n dbCache.clear()\n}\n","import { signal } from '@pyreon/reactivity'\nimport { getEntry, removeEntry, setEntry } from './registry'\nimport type { StorageOptions, StorageSignal } from './types'\nimport { deserialize, getWebStorage, isBrowser, serialize } from './utils'\n\n// ─── Cross-tab sync ──────────────────────────────────────────────────────────\n\nlet listenerAttached = false\n\nfunction attachStorageListener(): void {\n if (listenerAttached || !isBrowser()) return\n listenerAttached = true\n\n window.addEventListener('storage', (e) => {\n if (!e.key) return\n const entry = getEntry('local', e.key)\n if (!entry) return\n\n const newValue =\n e.newValue !== null ? deserialize(e.newValue, entry.defaultValue) : entry.defaultValue\n\n entry.signal.set(newValue)\n })\n}\n\n// ─── useStorage ──────────────────────────────────────────────────────────────\n\n/**\n * Reactive signal backed by localStorage. Automatically syncs across\n * browser tabs via the native `storage` event.\n *\n * @example\n * ```ts\n * const theme = useStorage('theme', 'light')\n * theme() // 'light' (or stored value)\n * theme.set('dark') // updates signal + localStorage\n * theme.remove() // clears storage, resets to default\n * ```\n */\nexport function useStorage<T>(\n key: string,\n defaultValue: T,\n options?: StorageOptions<T>,\n): StorageSignal<T> {\n // Return existing signal if already registered\n const existing = getEntry<T>('local', key)\n if (existing) return existing.signal\n\n const storage = getWebStorage('local')\n\n // Read initial value from storage\n let initialValue = defaultValue\n if (storage) {\n const raw = storage.getItem(key)\n if (raw !== null) {\n initialValue = deserialize(raw, defaultValue, options?.deserializer, options?.onError)\n }\n }\n\n const sig = signal<T>(initialValue)\n\n // Create the storage signal by extending the base signal\n const storageSig = createStorageSignal(sig, key, defaultValue, 'local', options)\n\n setEntry('local', key, storageSig, defaultValue)\n attachStorageListener()\n\n return storageSig\n}\n\n// ─── Storage Signal Factory ──────────────────────────────────────────────────\n\n/**\n * Wraps a base signal with storage persistence behavior.\n * Used by both useStorage and useSessionStorage.\n */\nexport function createStorageSignal<T>(\n sig: ReturnType<typeof signal<T>>,\n key: string,\n defaultValue: T,\n backend: 'local' | 'session',\n options?: StorageOptions<T>,\n): StorageSignal<T> {\n const storage = getWebStorage(backend)\n\n // The callable signal function (read)\n const storageSig = (() => sig()) as unknown as StorageSignal<T>\n\n // Delegate all signal methods\n storageSig.peek = () => sig.peek()\n storageSig.subscribe = (listener: () => void) => sig.subscribe(listener)\n storageSig.direct = (updater: () => void) => sig.direct(updater)\n storageSig.debug = () => sig.debug()\n\n Object.defineProperty(storageSig, 'label', {\n get: () => sig.label,\n set: (v: string | undefined) => {\n sig.label = v\n },\n })\n\n // Override set to persist\n storageSig.set = (value: T) => {\n sig.set(value)\n if (storage) {\n try {\n storage.setItem(key, serialize(value, options?.serializer))\n } catch {\n // Storage full or blocked — signal still updates\n }\n }\n }\n\n // Override update to persist\n storageSig.update = (fn: (current: T) => T) => {\n const newValue = fn(sig.peek())\n storageSig.set(newValue)\n }\n\n // Add remove method\n storageSig.remove = () => {\n sig.set(defaultValue)\n if (storage) {\n storage.removeItem(key)\n }\n removeEntry(backend, key)\n }\n\n return storageSig\n}\n","import { signal } from '@pyreon/reactivity'\nimport { createStorageSignal } from './local'\nimport { getEntry, setEntry } from './registry'\nimport type { StorageOptions, StorageSignal } from './types'\nimport { deserialize, getWebStorage } from './utils'\n\n// ─── useSessionStorage ───────────────────────────────────────────────────────\n\n/**\n * Reactive signal backed by sessionStorage. Scoped to the current\n * browser tab — does not sync across tabs.\n *\n * @example\n * ```ts\n * const step = useSessionStorage('wizard-step', 0)\n * step() // 0 (or stored value)\n * step.set(3) // updates signal + sessionStorage\n * step.remove() // clears storage, resets to default\n * ```\n */\nexport function useSessionStorage<T>(\n key: string,\n defaultValue: T,\n options?: StorageOptions<T>,\n): StorageSignal<T> {\n // Return existing signal if already registered\n const existing = getEntry<T>('session', key)\n if (existing) return existing.signal\n\n const storage = getWebStorage('session')\n\n // Read initial value from storage\n let initialValue = defaultValue\n if (storage) {\n const raw = storage.getItem(key)\n if (raw !== null) {\n initialValue = deserialize(raw, defaultValue, options?.deserializer, options?.onError)\n }\n }\n\n const sig = signal<T>(initialValue)\n const storageSig = createStorageSignal(sig, key, defaultValue, 'session', options)\n\n setEntry('session', key, storageSig, defaultValue)\n\n return storageSig\n}\n","import { getEntriesByBackend, getEntry, removeEntry } from './registry'\nimport { getWebStorage, isBrowser } from './utils'\n\n// ─── Storage type mapping ────────────────────────────────────────────────────\n\ntype StorageType = 'local' | 'session' | 'cookie' | 'indexeddb' | 'all'\n\n// ─── removeStorage ───────────────────────────────────────────────────────────\n\n/**\n * Remove a specific key from storage and reset its signal to the default value.\n *\n * @example\n * ```ts\n * removeStorage('theme') // from localStorage\n * removeStorage('step', { type: 'session' }) // from sessionStorage\n * removeStorage('locale', { type: 'cookie' }) // deletes cookie\n * ```\n */\nexport function removeStorage(\n key: string,\n options?: { type?: 'local' | 'session' | 'cookie' | 'indexeddb' },\n): void {\n const type = options?.type ?? 'local'\n const entry = getEntry(type, key)\n\n if (entry) {\n entry.signal.remove()\n } else {\n // No signal registered — still try to clear the raw storage\n if (type === 'local' || type === 'session') {\n const storage = getWebStorage(type)\n if (storage) storage.removeItem(key)\n } else if (type === 'cookie' && isBrowser()) {\n // biome-ignore lint/suspicious/noDocumentCookie: standard cookie deletion API\n document.cookie = `${encodeURIComponent(key)}=; max-age=0; path=/`\n }\n removeEntry(type, key)\n }\n}\n\n// ─── clearStorage ────────────────────────────────────────────────────────────\n\n/**\n * Clear all managed storage entries for a specific backend, or all backends.\n *\n * @example\n * ```ts\n * clearStorage() // clear all localStorage entries managed by @pyreon/storage\n * clearStorage('session') // clear all sessionStorage entries\n * clearStorage('cookie') // clear all managed cookies\n * clearStorage('all') // clear everything\n * ```\n */\nexport function clearStorage(type: StorageType = 'local'): void {\n if (type === 'all') {\n clearBackend('local')\n clearBackend('session')\n clearBackend('cookie')\n clearBackend('indexeddb')\n return\n }\n\n clearBackend(type)\n}\n\nfunction clearBackend(type: string): void {\n const entries = getEntriesByBackend(type)\n for (const entry of entries) {\n entry.signal.remove()\n }\n}\n"],"mappings":"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"}
@@ -35,7 +35,7 @@ interface CookieOptions<T> extends StorageOptions<T> {
35
35
  /** HTTPS only — default: false */
36
36
  secure?: boolean;
37
37
  /** SameSite policy — default: 'lax' */
38
- sameSite?: "strict" | "lax" | "none";
38
+ sameSite?: 'strict' | 'lax' | 'none';
39
39
  }
40
40
  /**
41
41
  * Options for the useIndexedDB hook.
@@ -183,7 +183,7 @@ declare function useStorage<T>(key: string, defaultValue: T, options?: StorageOp
183
183
  declare function useSessionStorage<T>(key: string, defaultValue: T, options?: StorageOptions<T>): StorageSignal<T>;
184
184
  //#endregion
185
185
  //#region src/clear.d.ts
186
- type StorageType = "local" | "session" | "cookie" | "indexeddb" | "all";
186
+ type StorageType = 'local' | 'session' | 'cookie' | 'indexeddb' | 'all';
187
187
  /**
188
188
  * Remove a specific key from storage and reset its signal to the default value.
189
189
  *
@@ -195,7 +195,7 @@ type StorageType = "local" | "session" | "cookie" | "indexeddb" | "all";
195
195
  * ```
196
196
  */
197
197
  declare function removeStorage(key: string, options?: {
198
- type?: "local" | "session" | "cookie" | "indexeddb";
198
+ type?: 'local' | 'session' | 'cookie' | 'indexeddb';
199
199
  }): void;
200
200
  /**
201
201
  * Clear all managed storage entries for a specific backend, or all backends.
package/package.json CHANGED
@@ -1,20 +1,17 @@
1
1
  {
2
2
  "name": "@pyreon/storage",
3
- "version": "0.11.4",
3
+ "version": "0.11.6",
4
4
  "description": "Reactive client-side storage for Pyreon — localStorage, sessionStorage, cookies, IndexedDB",
5
+ "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/storage#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/pyreon/pyreon/issues"
8
+ },
5
9
  "license": "MIT",
6
10
  "repository": {
7
11
  "type": "git",
8
12
  "url": "https://github.com/pyreon/pyreon.git",
9
13
  "directory": "packages/fundamentals/storage"
10
14
  },
11
- "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/storage#readme",
12
- "bugs": {
13
- "url": "https://github.com/pyreon/pyreon/issues"
14
- },
15
- "publishConfig": {
16
- "access": "public"
17
- },
18
15
  "files": [
19
16
  "lib",
20
17
  "src",
@@ -22,6 +19,7 @@
22
19
  "LICENSE"
23
20
  ],
24
21
  "type": "module",
22
+ "sideEffects": false,
25
23
  "main": "./lib/index.js",
26
24
  "module": "./lib/index.js",
27
25
  "types": "./lib/types/index.d.ts",
@@ -32,20 +30,22 @@
32
30
  "types": "./lib/types/index.d.ts"
33
31
  }
34
32
  },
35
- "sideEffects": false,
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
36
  "scripts": {
37
37
  "build": "vl_rolldown_build",
38
38
  "dev": "vl_rolldown_build-watch",
39
39
  "test": "vitest run",
40
40
  "typecheck": "tsc --noEmit",
41
- "lint": "biome check ."
42
- },
43
- "peerDependencies": {
44
- "@pyreon/reactivity": "^0.11.4"
41
+ "lint": "oxlint ."
45
42
  },
46
43
  "devDependencies": {
47
44
  "@happy-dom/global-registrator": "^20.8.3",
48
- "@pyreon/reactivity": "^0.11.4",
45
+ "@pyreon/reactivity": "^0.11.6",
49
46
  "@vitus-labs/tools-lint": "^1.11.0"
47
+ },
48
+ "peerDependencies": {
49
+ "@pyreon/reactivity": "^0.11.6"
50
50
  }
51
51
  }
package/src/clear.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { getEntriesByBackend, getEntry, removeEntry } from "./registry"
2
- import { getWebStorage, isBrowser } from "./utils"
1
+ import { getEntriesByBackend, getEntry, removeEntry } from './registry'
2
+ import { getWebStorage, isBrowser } from './utils'
3
3
 
4
4
  // ─── Storage type mapping ────────────────────────────────────────────────────
5
5
 
6
- type StorageType = "local" | "session" | "cookie" | "indexeddb" | "all"
6
+ type StorageType = 'local' | 'session' | 'cookie' | 'indexeddb' | 'all'
7
7
 
8
8
  // ─── removeStorage ───────────────────────────────────────────────────────────
9
9
 
@@ -19,19 +19,19 @@ type StorageType = "local" | "session" | "cookie" | "indexeddb" | "all"
19
19
  */
20
20
  export function removeStorage(
21
21
  key: string,
22
- options?: { type?: "local" | "session" | "cookie" | "indexeddb" },
22
+ options?: { type?: 'local' | 'session' | 'cookie' | 'indexeddb' },
23
23
  ): void {
24
- const type = options?.type ?? "local"
24
+ const type = options?.type ?? 'local'
25
25
  const entry = getEntry(type, key)
26
26
 
27
27
  if (entry) {
28
28
  entry.signal.remove()
29
29
  } else {
30
30
  // No signal registered — still try to clear the raw storage
31
- if (type === "local" || type === "session") {
31
+ if (type === 'local' || type === 'session') {
32
32
  const storage = getWebStorage(type)
33
33
  if (storage) storage.removeItem(key)
34
- } else if (type === "cookie" && isBrowser()) {
34
+ } else if (type === 'cookie' && isBrowser()) {
35
35
  // biome-ignore lint/suspicious/noDocumentCookie: standard cookie deletion API
36
36
  document.cookie = `${encodeURIComponent(key)}=; max-age=0; path=/`
37
37
  }
@@ -52,12 +52,12 @@ export function removeStorage(
52
52
  * clearStorage('all') // clear everything
53
53
  * ```
54
54
  */
55
- export function clearStorage(type: StorageType = "local"): void {
56
- if (type === "all") {
57
- clearBackend("local")
58
- clearBackend("session")
59
- clearBackend("cookie")
60
- clearBackend("indexeddb")
55
+ export function clearStorage(type: StorageType = 'local'): void {
56
+ if (type === 'all') {
57
+ clearBackend('local')
58
+ clearBackend('session')
59
+ clearBackend('cookie')
60
+ clearBackend('indexeddb')
61
61
  return
62
62
  }
63
63
 
package/src/cookie.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { signal } from "@pyreon/reactivity"
2
- import { getEntry, removeEntry, setEntry } from "./registry"
3
- import type { CookieOptions, StorageSignal } from "./types"
4
- import { deserialize, isBrowser, serialize } from "./utils"
1
+ import { signal } from '@pyreon/reactivity'
2
+ import { getEntry, removeEntry, setEntry } from './registry'
3
+ import type { CookieOptions, StorageSignal } from './types'
4
+ import { deserialize, isBrowser, serialize } from './utils'
5
5
 
6
6
  // ─── Server-side cookie source ───────────────────────────────────────────────
7
7
 
8
- let serverCookieString = ""
8
+ let serverCookieString = ''
9
9
 
10
10
  /**
11
11
  * Set the cookie source string for SSR. Call this once per request
@@ -27,8 +27,8 @@ function parseCookies(cookieString: string): Map<string, string> {
27
27
  const cookies = new Map<string, string>()
28
28
  if (!cookieString) return cookies
29
29
 
30
- for (const pair of cookieString.split(";")) {
31
- const eqIndex = pair.indexOf("=")
30
+ for (const pair of cookieString.split(';')) {
31
+ const eqIndex = pair.indexOf('=')
32
32
  if (eqIndex === -1) continue
33
33
  const name = pair.slice(0, eqIndex).trim()
34
34
  const value = pair.slice(eqIndex + 1).trim()
@@ -62,14 +62,14 @@ function writeCookie<T>(key: string, value: T, options: CookieOptions<T>): void
62
62
  if (options.expires) {
63
63
  cookie += `; expires=${options.expires.toUTCString()}`
64
64
  }
65
- cookie += `; path=${options.path ?? "/"}`
65
+ cookie += `; path=${options.path ?? '/'}`
66
66
  if (options.domain) {
67
67
  cookie += `; domain=${options.domain}`
68
68
  }
69
69
  if (options.secure) {
70
- cookie += "; secure"
70
+ cookie += '; secure'
71
71
  }
72
- cookie += `; samesite=${options.sameSite ?? "lax"}`
72
+ cookie += `; samesite=${options.sameSite ?? 'lax'}`
73
73
 
74
74
  // biome-ignore lint/suspicious/noDocumentCookie: document.cookie is the standard cookie write API
75
75
  document.cookie = cookie
@@ -79,7 +79,7 @@ function deleteCookie<T>(key: string, options: CookieOptions<T>): void {
79
79
  if (!isBrowser()) return
80
80
 
81
81
  let cookie = `${encodeURIComponent(key)}=; max-age=0`
82
- cookie += `; path=${options.path ?? "/"}`
82
+ cookie += `; path=${options.path ?? '/'}`
83
83
  if (options.domain) {
84
84
  cookie += `; domain=${options.domain}`
85
85
  }
@@ -112,7 +112,7 @@ export function useCookie<T>(
112
112
  options: CookieOptions<T> = {},
113
113
  ): StorageSignal<T> {
114
114
  // Return existing signal if already registered
115
- const existing = getEntry<T>("cookie", key)
115
+ const existing = getEntry<T>('cookie', key)
116
116
  if (existing) return existing.signal
117
117
 
118
118
  // Read initial value from cookie
@@ -132,7 +132,7 @@ export function useCookie<T>(
132
132
  storageSig.direct = (updater: () => void) => sig.direct(updater)
133
133
  storageSig.debug = () => sig.debug()
134
134
 
135
- Object.defineProperty(storageSig, "label", {
135
+ Object.defineProperty(storageSig, 'label', {
136
136
  get: () => sig.label,
137
137
  set: (v: string | undefined) => {
138
138
  sig.label = v
@@ -152,10 +152,10 @@ export function useCookie<T>(
152
152
  storageSig.remove = () => {
153
153
  sig.set(defaultValue)
154
154
  deleteCookie(key, options)
155
- removeEntry("cookie", key)
155
+ removeEntry('cookie', key)
156
156
  }
157
157
 
158
- setEntry("cookie", key, storageSig, defaultValue)
158
+ setEntry('cookie', key, storageSig, defaultValue)
159
159
 
160
160
  return storageSig
161
161
  }
package/src/custom.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { signal } from "@pyreon/reactivity"
2
- import { getEntry, removeEntry, setEntry } from "./registry"
3
- import type { StorageBackend, StorageOptions, StorageSignal } from "./types"
4
- import { deserialize, serialize } from "./utils"
1
+ import { signal } from '@pyreon/reactivity'
2
+ import { getEntry, removeEntry, setEntry } from './registry'
3
+ import type { StorageBackend, StorageOptions, StorageSignal } from './types'
4
+ import { deserialize, serialize } from './utils'
5
5
 
6
6
  // ─── createStorage ───────────────────────────────────────────────────────────
7
7
 
@@ -24,7 +24,7 @@ export function createStorage(
24
24
  backend: StorageBackend,
25
25
  backendName?: string,
26
26
  ): <T>(key: string, defaultValue: T, options?: StorageOptions<T>) => StorageSignal<T> {
27
- const name = backendName ?? "custom"
27
+ const name = backendName ?? 'custom'
28
28
 
29
29
  return function useCustomStorage<T>(
30
30
  key: string,
@@ -56,7 +56,7 @@ export function createStorage(
56
56
  storageSig.direct = (updater: () => void) => sig.direct(updater)
57
57
  storageSig.debug = () => sig.debug()
58
58
 
59
- Object.defineProperty(storageSig, "label", {
59
+ Object.defineProperty(storageSig, 'label', {
60
60
  get: () => sig.label,
61
61
  set: (v: string | undefined) => {
62
62
  sig.label = v
@@ -115,5 +115,5 @@ export const useMemoryStorage = createStorage(
115
115
  remove: (key: string) => store.delete(key),
116
116
  }
117
117
  })(),
118
- "memory",
118
+ 'memory',
119
119
  )
package/src/index.ts CHANGED
@@ -24,15 +24,15 @@
24
24
 
25
25
  // ─── Hooks ───────────────────────────────────────────────────────────────────
26
26
 
27
- export { setCookieSource, useCookie } from "./cookie"
28
- export { createStorage, useMemoryStorage } from "./custom"
29
- export { useIndexedDB } from "./indexed-db"
30
- export { useStorage } from "./local"
31
- export { useSessionStorage } from "./session"
27
+ export { setCookieSource, useCookie } from './cookie'
28
+ export { createStorage, useMemoryStorage } from './custom'
29
+ export { useIndexedDB } from './indexed-db'
30
+ export { useStorage } from './local'
31
+ export { useSessionStorage } from './session'
32
32
 
33
33
  // ─── Utilities ───────────────────────────────────────────────────────────────
34
34
 
35
- export { clearStorage, removeStorage } from "./clear"
35
+ export { clearStorage, removeStorage } from './clear'
36
36
 
37
37
  // ─── Types ───────────────────────────────────────────────────────────────────
38
38
 
@@ -43,9 +43,9 @@ export type {
43
43
  StorageBackend,
44
44
  StorageOptions,
45
45
  StorageSignal,
46
- } from "./types"
46
+ } from './types'
47
47
 
48
48
  // ─── Testing ─────────────────────────────────────────────────────────────────
49
49
 
50
- export { _resetDBCache } from "./indexed-db"
51
- export { _resetRegistry } from "./registry"
50
+ export { _resetDBCache } from './indexed-db'
51
+ export { _resetRegistry } from './registry'
package/src/indexed-db.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { signal } from "@pyreon/reactivity"
2
- import { getEntry, removeEntry, setEntry } from "./registry"
3
- import type { IndexedDBOptions, StorageSignal } from "./types"
4
- import { deserialize, isBrowser, serialize } from "./utils"
1
+ import { signal } from '@pyreon/reactivity'
2
+ import { getEntry, removeEntry, setEntry } from './registry'
3
+ import type { IndexedDBOptions, StorageSignal } from './types'
4
+ import { deserialize, isBrowser, serialize } from './utils'
5
5
 
6
6
  // ─── Database management ─────────────────────────────────────────────────────
7
7
 
@@ -32,7 +32,7 @@ function openDB(dbName: string, storeName: string): Promise<IDBDatabase> {
32
32
 
33
33
  function idbGet(db: IDBDatabase, storeName: string, key: string): Promise<string | null> {
34
34
  return new Promise((resolve, reject) => {
35
- const tx = db.transaction(storeName, "readonly")
35
+ const tx = db.transaction(storeName, 'readonly')
36
36
  const store = tx.objectStore(storeName)
37
37
  const request = store.get(key)
38
38
  request.onsuccess = () =>
@@ -43,7 +43,7 @@ function idbGet(db: IDBDatabase, storeName: string, key: string): Promise<string
43
43
 
44
44
  function idbSet(db: IDBDatabase, storeName: string, key: string, value: string): Promise<void> {
45
45
  return new Promise((resolve, reject) => {
46
- const tx = db.transaction(storeName, "readwrite")
46
+ const tx = db.transaction(storeName, 'readwrite')
47
47
  const store = tx.objectStore(storeName)
48
48
  const request = store.put(value, key)
49
49
  request.onsuccess = () => resolve()
@@ -53,7 +53,7 @@ function idbSet(db: IDBDatabase, storeName: string, key: string, value: string):
53
53
 
54
54
  function idbDelete(db: IDBDatabase, storeName: string, key: string): Promise<void> {
55
55
  return new Promise((resolve, reject) => {
56
- const tx = db.transaction(storeName, "readwrite")
56
+ const tx = db.transaction(storeName, 'readwrite')
57
57
  const store = tx.objectStore(storeName)
58
58
  const request = store.delete(key)
59
59
  request.onsuccess = () => resolve()
@@ -83,17 +83,17 @@ export function useIndexedDB<T>(
83
83
  options: IndexedDBOptions<T> = {},
84
84
  ): StorageSignal<T> {
85
85
  // Return existing signal if already registered
86
- const existing = getEntry<T>("indexeddb", key)
86
+ const existing = getEntry<T>('indexeddb', key)
87
87
  if (existing) return existing.signal
88
88
 
89
- const dbName = options.dbName ?? "pyreon-storage"
90
- const storeName = options.storeName ?? "kv"
89
+ const dbName = options.dbName ?? 'pyreon-storage'
90
+ const storeName = options.storeName ?? 'kv'
91
91
  const debounceMs = options.debounceMs ?? 100
92
92
 
93
93
  const sig = signal<T>(defaultValue)
94
94
 
95
95
  // Async initial load
96
- if (isBrowser() && typeof indexedDB !== "undefined") {
96
+ if (isBrowser() && typeof indexedDB !== 'undefined') {
97
97
  openDB(dbName, storeName)
98
98
  .then((db) => idbGet(db, storeName, key))
99
99
  .then((raw) => {
@@ -116,7 +116,7 @@ export function useIndexedDB<T>(
116
116
  const value = pendingValue
117
117
  pendingValue = undefined
118
118
 
119
- if (!isBrowser() || typeof indexedDB === "undefined") return
119
+ if (!isBrowser() || typeof indexedDB === 'undefined') return
120
120
 
121
121
  openDB(dbName, storeName)
122
122
  .then((db) => idbSet(db, storeName, key, serialize(value, options.serializer)))
@@ -139,7 +139,7 @@ export function useIndexedDB<T>(
139
139
  storageSig.direct = (updater: () => void) => sig.direct(updater)
140
140
  storageSig.debug = () => sig.debug()
141
141
 
142
- Object.defineProperty(storageSig, "label", {
142
+ Object.defineProperty(storageSig, 'label', {
143
143
  get: () => sig.label,
144
144
  set: (v: string | undefined) => {
145
145
  sig.label = v
@@ -161,7 +161,7 @@ export function useIndexedDB<T>(
161
161
  pendingValue = undefined
162
162
  if (writeTimer !== null) clearTimeout(writeTimer)
163
163
 
164
- if (isBrowser() && typeof indexedDB !== "undefined") {
164
+ if (isBrowser() && typeof indexedDB !== 'undefined') {
165
165
  openDB(dbName, storeName)
166
166
  .then((db) => idbDelete(db, storeName, key))
167
167
  .catch(() => {
@@ -169,10 +169,10 @@ export function useIndexedDB<T>(
169
169
  })
170
170
  }
171
171
 
172
- removeEntry("indexeddb", key)
172
+ removeEntry('indexeddb', key)
173
173
  }
174
174
 
175
- setEntry("indexeddb", key, storageSig, defaultValue)
175
+ setEntry('indexeddb', key, storageSig, defaultValue)
176
176
 
177
177
  return storageSig
178
178
  }