seitu
Version:
137 lines (136 loc) • 4.37 kB
JavaScript
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 };