web-universal-storage
v0.2.0
Published
Unified, type-safe API for localStorage, sessionStorage, cookies, and IndexedDB with SSR-safe fallbacks.
Maintainers
Readme
web-universal-storage
Unified, type-safe API for browser storage:
localStorage(sync)sessionStorage(sync)document.cookie(sync)IndexedDB(async)
SSR-safe: in non-browser environments it automatically falls back to in-memory storage.
Install
npm i web-universal-storageBasic Usage
All storages support generics and get<T>() returns T | null (never undefined).
Default instance
The default export is a singleton instance created with default options:
import storage from "web-universal-storage";localStorage (sync)
import storage from "web-universal-storage";
type User = { id: string; name: string };
storage.local.set<User>("user", { id: "1", name: "Ada" });
const user = storage.local.get<User>("user");
storage.local.remove("user");sessionStorage (sync)
import storage from "web-universal-storage";
storage.session.set("token", "abc");
const token = storage.session.get<string>("token");
storage.session.remove("token");Cookies (sync)
import storage from "web-universal-storage";
storage.cookie.set("token", "abc", { expires: 7, sameSite: "lax", secure: true });
const token = storage.cookie.get<string>("token");
storage.cookie.remove("token");IndexedDB (async)
import storage from "web-universal-storage";
await storage.db.set("large_data", { ok: true });
const large = await storage.db.get<{ ok: boolean }>("large_data");
await storage.db.remove("large_data");createStorage(options?)
Create a custom instance when you need namespacing, default TTL, custom IndexedDB names, or change events.
import { createStorage } from "web-universal-storage";
const storage = createStorage({ namespace: "app" });One instance per namespace
If you want to ensure a single instance per namespace (singleton per config), use getStorage(...):
import { getStorage } from "web-universal-storage";
const storage = getStorage({ namespace: "app" });Options
type CreateStorageOptions = {
namespace?: string;
/**
* Default TTL (ms) applied when `set()` is called without `ttlMs`.
* TTL is implemented via metadata wrapper (not native storage expiration).
*/
defaultTtlMs?: number;
/**
* Default cookie attributes for this storage instance.
* Note: `ttlMs` is per-call (not a default).
*/
cookieDefaults?: {
expires?: number | Date;
path?: string;
secure?: boolean;
sameSite?: "strict" | "lax" | "none";
};
/**
* IndexedDB database name.
* Default: "universal-storage"
*/
dbName?: string;
/**
* IndexedDB object store name.
* Default: "keyval"
*/
dbStoreName?: string;
/**
* In-process events for this instance only (set/remove/clear).
*/
onChange?: (event: StorageChangeEvent) => void;
/**
* Optional secure (encryption) layer configuration.
* Sync APIs remain unencrypted; use `storage.secure.*` for encryption.
*/
encryption?: {
enabled?: boolean;
secret?: string; // defaults to "khid" when enabled
local?: { enabled?: boolean };
session?: { enabled?: boolean };
cookie?: { enabled?: boolean };
db?: { enabled?: boolean };
};
};Features
Namespacing
import { createStorage } from "web-universal-storage";
const s1 = createStorage({ namespace: "app" });
const s2 = createStorage({ namespace: "admin" });
s1.local.set("key", 1);
s2.local.set("key", 2);TTL (expiration)
TTL works for all storage types. Expired values return null and are removed on read.
import storage from "web-universal-storage";
storage.local.set("temp", { ok: true }, { ttlMs: 5_000 });Change events
import { createStorage } from "web-universal-storage";
const storage = createStorage({
onChange(e) {
console.log(e.storage, e.action, e.key);
},
});Global defaults (for default instance)
You can configure defaults used by the default singleton and by any instance that does not override those defaults.
import storage from "web-universal-storage";
storage.configure({
cookieDefaults: { sameSite: "lax", secure: true, path: "/" },
});
storage.cookie.set("token", "abc"); // uses configured cookie defaultsAPI Reference
Sync storages: local, session, cookie
storage.local.get<T>(key): T | null
storage.local.set<T>(key, value, { ttlMs? }): void
storage.local.remove(key): void
storage.local.clear(): voidAsync storage: db (IndexedDB)
await storage.db.get<T>(key): Promise<T | null>
await storage.db.set<T>(key, value, { ttlMs? }): Promise<void>
await storage.db.remove(key): Promise<void>
await storage.db.clear(): Promise<void>Cookies
storage.cookie.set(key, value, {
expires?: number | Date,
path?: string,
secure?: boolean,
sameSite?: "strict" | "lax" | "none",
ttlMs?: number
})SSR / Disabled storage
- If
typeof window === "undefined"or a storage is unavailable/throws, the library uses in-memory storage. get()never throws and never returnsundefined(returnsnull).
Fallback behavior
- Reads: primary storage → memory fallback
- Writes: try primary storage → memory fallback (best-effort; e.g. quota exceeded / storage blocked)
Reset
reset() clears storages for a specific instance (namespace-aware). fullReset() is a more destructive variant that clears the underlying storages broadly and is only available on the default import.
Instance reset (default import or createStorage)
import storage, { createStorage } from "web-universal-storage";
await storage.reset(); // local + session + cookie + db (default instance)
await storage.resetType("cookie");
const s = createStorage({ namespace: "app" });
await s.reset(); // clears only "app:" keysFull reset (default import only)
import storage from "web-universal-storage";
await storage.fullReset();
await storage.fullResetType("local");Encryption (secure layer)
Encryption is optional and disabled by default. Existing sync APIs remain unchanged and store plain values.
Use the async secure wrapper:
import storage from "web-universal-storage";
await storage.secure.local.set("user", { id: 1 });
const user = await storage.secure.local.get<{ id: number }>("user");Enable encryption per instance
import { createStorage } from "web-universal-storage";
const storage = createStorage({
namespace: "app",
encryption: { enabled: true, secret: "my-secret" },
});
await storage.secure.db.set("profile", { ok: true });If secret is not provided and encryption is enabled, it defaults to "khid".
Per-storage enable/disable
import { createStorage } from "web-universal-storage";
const storage = createStorage({
encryption: {
enabled: true,
session: { enabled: false },
cookie: { enabled: false },
},
});Per-call override
import { createStorage } from "web-universal-storage";
const storage = createStorage({ encryption: { enabled: true } });
await storage.secure.local.set("tmp", { ok: true }, { encryption: false });SSR safety
If window or Web Crypto is unavailable, encryption is skipped silently and secure APIs fall back to plain storage.
Example (JS)
import storage from "web-universal-storage";
storage.local.set("count", 1);
console.log(storage.local.get("count")); // 1