UNPKG

seitu

Version:
137 lines (136 loc) • 4.37 kB
import { createReadableSubscription, createSubscription } from "../../core/subscription/index.mjs"; import { n as validateSchema } from "../../schema-store-Dfbr678J.mjs"; import { tryParseJson } from "../../utils/json/index.mjs"; import { n as listenStorage, t as dispatchStorageEvent } from "../../storage-event-D-A553TB.mjs"; //#region src/web/web-storage/index.ts /** * Reactive handle for `localStorage` or `sessionStorage`. On the server, `get()` * and the SSR snapshot are `defaultValues`. * * @example Vanilla * ```ts twoslash title="session-storage.ts" * import { createWebStorage } from 'seitu/web' * import * as z from 'zod' * * const sessionStorage = createWebStorage({ * type: 'sessionStorage', * schemas: { * token: z.string().nullable(), * preferences: z.object({ theme: z.enum(['light', 'dark']) }), * }, * defaultValues: { token: null, preferences: { theme: 'light' } }, * }) * * sessionStorage.get() * sessionStorage.set({ token: 'abc' }) * sessionStorage.get() // { token: 'abc', preferences: { theme: 'light' } } * sessionStorage.subscribe(console.log) * ``` * * @example React * ```tsx twoslash title="page.tsx" * 'use client' * * import { createWebStorage } from 'seitu/web' * import { useSubscription } from 'seitu/react' * import * as z from 'zod' * * const sessionStorage = createWebStorage({ * type: 'sessionStorage', * schemas: { count: z.number(), name: z.string() }, * defaultValues: { count: 0, name: '' }, * }) * * export default function Page() { * const value = useSubscription(sessionStorage) * return ( * <div> * <span>{value.count}</span> * <span>{value.name}</span> * </div> * ) * } * ``` */ function createWebStorage(options) { const defaultValues = { ...options.defaultValues }; const keys = Object.keys(defaultValues); const resolveKey = (key) => String(options.keyTransform ? options.keyTransform(key) : key); const storageKeys = new Set(keys.map(resolveKey)); let isWriting = false; const { subscribe, notify } = createSubscription({ onFirstSubscribe: () => listenStorage((event) => !isWriting && (event.key === null || storageKeys.has(event.key)), notify) }); const cachedRaws = /* @__PURE__ */ new Map(); let cachedOutput; const get = () => { if (typeof window === "undefined") return defaultValues; const storage = window[options.type]; let hasCache = cachedOutput !== void 0; const currentRaws = {}; for (const key of keys) { const raw = storage.getItem(resolveKey(key)); currentRaws[key] = raw; if (hasCache && cachedRaws.get(key) !== raw) hasCache = false; } if (hasCache) return cachedOutput; const output = { ...defaultValues }; for (const key of keys) { const raw = currentRaws[key]; if (raw === null) output[key] = defaultValues[key]; else output[key] = validateSchema(options.schemas[key], tryParseJson(raw), { defaultValue: defaultValues[key], label: `createWebStorage:${String(key)}`, key, onError: options.onValidationError }); cachedRaws.set(key, raw); } cachedOutput = output; return output; }; const readable = createReadableSubscription(get, subscribe, notify, () => defaultValues); const write = (mutate) => { if (typeof window === "undefined") return; isWriting = true; try { mutate(window[options.type]); } finally { isWriting = false; } cachedOutput = void 0; notify(); }; return { ...readable, set: (value) => { write((storage) => { const resolvedValue = typeof value === "function" ? value(get()) : value; for (const [key, entry] of Object.entries(resolvedValue)) { const storageKey = resolveKey(key); const oldValue = storage.getItem(storageKey); const newValue = JSON.stringify(entry); storage.setItem(storageKey, newValue); dispatchStorageEvent(storage, storageKey, oldValue, newValue); } }); }, clear: () => { write((storage) => { for (const key of keys) { const storageKey = resolveKey(key); const oldValue = storage.getItem(storageKey); storage.removeItem(storageKey); dispatchStorageEvent(storage, storageKey, oldValue, null); } }); }, "~": { ...readable["~"], getDefaultValue: (key) => defaultValues[key], getSchema: (key) => options.schemas[key], transformKey: resolveKey, type: options.type } }; } //#endregion export { createWebStorage };