Skip to content

Typed Storage

Fully typed, async, key-prefixed storage over any backend.

@nice-code/util provides typed storage adapters and WebCrypto utilities.

Terminal window
bun add @nice-code/util

ITypedStorage<T> gives you fully typed, async, key-prefixed storage over any backend.

import { createTypedWebLocalStorage } from "@nice-code/util";
interface IAppStorage {
user_id: string;
theme: "light" | "dark";
recent_searches: string[];
}
const storage = createTypedWebLocalStorage<IAppStorage>({
localStorage,
keyPrefix: "app:",
});
// All keys autocomplete; values are typed
await storage.setJson("theme", "dark");
const theme = await storage.getJson("theme"); // "light" | "dark" | undefined
const userId = await storage.getJsonOrDef("user_id", "guest"); // string
// Read-modify-write in one call
await storage.updateJsonWithDef("recent_searches", [], (cur) => [...cur, "query"]);
await storage.removeItem("theme");
await storage.clearAll(); // removes only keys this storage has written
import {
createTypedWebLocalStorage,
createTypedWebSessionStorage,
createDurableObjectTypedStorage,
createTypedMemoryStorage_string,
createTypedMemoryStorage_json,
} from "@nice-code/util";
// Browser
const local = createTypedWebLocalStorage<IAppStorage>({ localStorage, keyPrefix: "app:" });
const session = createTypedWebSessionStorage<IAppStorage>({ sessionStorage });
// Cloudflare Durable Objects (inside a DO class)
const doStorage = createDurableObjectTypedStorage<IDOStorage>({
durableObjectStorage: ctx.storage,
keyPrefix: "do:",
});
// In-memory (testing / SSR) — string-serialized or JSON-native
const mem = createTypedMemoryStorage_string<IAppStorage>();
const memJson = createTypedMemoryStorage_json<IAppStorage>();
// Share state between instances by passing the same Map
const shared = new Map<string, string>();
const a = createTypedMemoryStorage_string<IAppStorage>({ memoryStorageMap: shared });
const b = createTypedMemoryStorage_string<IAppStorage>({ memoryStorageMap: shared });
interface ITypedStorage<T extends Record<string, any>> {
getJson<K>(key: K): Promise<T[K] | undefined>;
getJsonOrDef<K>(key: K, defVal: T[K]): Promise<T[K]>;
setJson<K>(key: K, val: T[K]): Promise<void>;
updateJson<K>(key: K, updater: (cur: T[K] | undefined) => T[K]): Promise<void>;
updateJsonWithDef<K>(key: K, defVal: T[K], updater: (cur: T[K]) => T[K]): Promise<void>;
removeItem<K>(key: K): Promise<void>;
clearAll(): Promise<void>;
}

Implement the methods interface to wrap any storage (Redis, KV, …):

import {
createTypedStorage,
EStorageAdapterType,
StorageAdapter,
type IStorageAdapterMethods_String,
} from "@nice-code/util";
const redisMethods: IStorageAdapterMethods_String = {
type: EStorageAdapterType.string,
getItem: async (key) => redis.get(key),
setItem: async (key, value) => { await redis.set(key, value); },
removeItem: async (key) => { await redis.del(key); },
};
const storage = createTypedStorage<IMySchema>({
storageAdapter: new StorageAdapter({ methods: redisMethods, keyPrefix: "app:" }),
});

The lower-level StorageAdapter can also be used directly (untyped keys), including createJsonGetterSetter<T>(key) for a single-key { get, set } pair.

Next: Crypto →