@jayoncode/storage
v0.4.2
Published
Storage — Namespace it. Expire it. Upgrade it — without localStorage glue. Explicit adapters (memory / localStorage / sessionStorage / IndexedDB), TTL, migrations.
Maintainers
Readme
Storage — Typed, framework-agnostic client-side storage for modern web apps.
Namespace it. Expire it. Upgrade it — without localStorage glue.
@jayoncode/storage — policy-driven client persistence for modern web apps.
A small policy layer for browser persistence — namespaces, envelopes, TTL, migrations — on adapters you choose: memory, localStorage, sessionStorage, and IndexedDB.
Explicit adapters. Optional policy.
Soft quota and transforms hooks are opt-in — no silent encryption.
Persist → Policy → Adapt
createStorage({ namespace, adapter })
↓
TTL · schema version · migrations (optional)
↓
peek · quota · transforms · maintenance (optional)| Pillar | What you get |
| ----------- | ---------------------------------------------------------------------- |
| Persist | Typed set / get with namespaced keys |
| Policy | TTL, schema version, migrations, soft quota |
| Adapt | Memory · localStorage · sessionStorage · IndexedDB — never auto-picked |
Five capabilities
| Card | What it is | | --------------------- | --------------------------------------------------- | | createStorage() | Namespace + adapter + optional TTL / schema version | | Explicit adapters | Memory · localStorage · sessionStorage · IndexedDB | | Peek & TTL | See when a value was saved and when it expires | | Migrations | Schema upgrades without ad-hoc key rewrites | | Extra tools | Soft quota, transforms hooks, maintenance — opt-in |
Install
npm install @jayoncode/storagepnpm add @jayoncode/storageyarn add @jayoncode/storageThe problem it solves
Most apps slowly accumulate helpers like this:
const raw = localStorage.getItem("app:theme");
const parsed = raw ? JSON.parse(raw) : null;
// + manual expiry · version bumps · migration · quota try/catch · per-project key rules@jayoncode/storage replaces that sprawl with a namespaced, envelope-backed store you configure once.
Quick start — prefs that survive reload
import { createStorage, createLocalStorageAdapter } from "@jayoncode/storage";
const storage = createStorage({
namespace: "app",
adapter: createLocalStorageAdapter(), // or createMemoryAdapter() / createSessionStorageAdapter()
ttl: { hours: 1 },
schemaVersion: "1",
});
storage.set("theme", "dark");
storage.get("theme"); // "dark" | nullAdapters are explicit — the library never chooses a backend for you.
More problem → solution snippets
Named TTL policies
import { createStorage, createLocalStorageAdapter } from "@jayoncode/storage";
const storage = createStorage({
namespace: "app",
adapter: createLocalStorageAdapter(),
policies: {
preferences: { ttl: { days: 365 } },
cache: { ttl: { minutes: 15 } },
},
});
storage.set("theme", "dark", { policy: "preferences" });TTL resolution: per-write ttl → named policy → instance ttl.
Async IndexedDB
import { createAsyncStorage, createIndexedDbAdapter } from "@jayoncode/storage/async";
const storage = createAsyncStorage({
namespace: "app",
adapter: createIndexedDbAdapter(), // default DB: jayoncode-storage
});
await storage.set("theme", "dark");Cross-tab notify (no auto-merge)
import { enableCrossTabSync } from "@jayoncode/storage/cross-tab";
const { storage: synced, stop } = enableCrossTabSync(storage, {
onRemote: () => refreshUi(),
});Soft quota (approx bytes)
import { enableQuotaGuard } from "@jayoncode/storage/quota";
const { storage: guarded } = enableQuotaGuard(storage, {
maxApproxBytes: 50_000,
});Opt-in compress / encrypt hooks
import {
createStorage,
createMemoryAdapter,
defaultSerialize,
defaultDeserialize,
} from "@jayoncode/storage";
import { withPayloadTransforms } from "@jayoncode/storage/transforms";
const { serialize, deserialize } = withPayloadTransforms(
{ serialize: defaultSerialize, deserialize: defaultDeserialize },
{
// App-owned sync string → string hooks (Storage does not ship crypto algorithms)
compress: (plain) => plain,
decompress: (wire) => wire,
encrypt: (plain) => plain,
decrypt: (wire) => wire,
},
);
const storage = createStorage({
namespace: "app",
adapter: createMemoryAdapter(),
serialize,
deserialize,
});Capabilities (pay only when you import)
| Import | What it solves |
| --------------------------------- | ----------------------------------------------------------- |
| @jayoncode/storage | Sync createStorage, adapters, errors, default ser/de |
| @jayoncode/storage/async | Promise API + IndexedDB adapter |
| @jayoncode/storage/cross-tab | Notify other tabs (BroadcastChannel + optional storage) |
| @jayoncode/storage/quota | Soft max / warn on approx namespace bytes |
| @jayoncode/storage/transforms | Compose compress/encrypt around serialize |
| @jayoncode/storage/maintenance | Explicit expired-key cleanup |
| @jayoncode/storage/snapshots | Export / restore a namespace |
| @jayoncode/storage/observable | In-process on / watch |
| @jayoncode/storage/diagnostics | DEV report / activity |
| @jayoncode/storage/transactions | Same-tab journal + rollback |
Non-goals
- Does not own Form Intelligence drafts (keep IDB DBs separate:
jayoncode-storagevsjayoncode-form-intelligent-drafts) - Does not auto-select storage backends
- Cross-tab does not auto-merge remote values (notify-only)
- Soft quota uses approx payload bytes — not exact browser remaining space
- Does not ship silent encryption/compression or crypto algorithms — opt-in
/transformshooks only; keys stay app-owned
Documentation
Repository
https://github.com/itsjayoncode/joc · Package path: packages/storage
License
MIT © JayOnCode
