API At a Glance
| Symbol | Purpose | Execution mode | Common gotcha |
|---|---|---|---|
createLocalStorage(dbName, schema) | Local browser storage adapter | Sync factory, async methods | Requires localStorage availability |
createSessionStorage(dbName, schema) | Tab-scoped browser storage adapter | Sync factory, async methods | Requires sessionStorage availability |
createCookie(dbName, schema, options?) | Cookie-backed browser storage adapter | Sync factory, async methods | Requires document and is browser-only |
createIndexedDB() | IndexedDB adapter with transactions | Sync factory, async methods | schemaVersion must increase to run migrations |
createMemory(schema) | In-memory adapter for tests and SSR | Sync factory, async methods | State is scoped to the instance; not persisted |
table<T>(key) | Creates a typed schema entry | Sync | — |
query(table) | Build chainable in-memory queries | Async execution | Filters run over fetched records |
Package Entry Points
@vielzeug/deposit
Exports
createLocalStoragecreateSessionStoragecreateCookiecreateIndexedDBcreateMemoryQueryBuildertablettl- Types:
Adapter,IndexedDBHandle,TransactionContext,RecordOf,KeyOf,MigrationContext,MigrationFn,Observer,TtlMs
Core Types
RecordOf and KeyOf
type RecordOf<S, K extends keyof S> = /* record type for table K */;
type KeyOf<S, K extends keyof S> = /* key value type for table K */;Example:
type User = { id: number; name: string };
const schema = {
users: table<User>('id'),
};
type UserRecord = RecordOf<typeof schema, 'users'>; // User
type UserKey = KeyOf<typeof schema, 'users'>; // numberSchema Helper
table
table<T extends Record<string, unknown>>(key: keyof T & string)Creates a typed schema entry. The generic T carries the record type; key is the primary key field.
const schema = {
users: table<User>('id'),
posts: table<Post>('id'),
};
// typeof schema inferred — no Schema<{...}> annotation needed
type UserRecord = RecordOf<typeof schema, 'users'>; // User
type UserKey = KeyOf<typeof schema, 'users'>; // numberFactories
createLocalStorage
createLocalStorage<S>(dbName: string, schema: S): Adapter<S>Creates a LocalStorage-backed adapter.
createSessionStorage
createSessionStorage<S>(dbName: string, schema: S): Adapter<S>Creates a SessionStorage-backed adapter.
createCookie
createCookie<S>(
dbName: string,
schema: S,
options?: {
path?: string;
sameSite?: 'Lax' | 'None' | 'Strict';
secure?: boolean;
},
): Adapter<S>Creates a cookie-backed adapter for browser environments.
Records are stored under cookie names derived from the database, table, and record key. TTL is encoded in the stored payload and also mapped to cookie Max-Age when provided.
Cookie options:
pathdefaults to'/'sameSitedefaults to'Strict'securedefaults tofalse
createIndexedDB
createIndexedDB<S>(options: {
dbName: string;
schema: S;
schemaVersion: number;
migrate?: (ctx: {
db: IDBDatabase;
oldVersion: number;
newVersion: number | null;
tx: IDBTransaction;
}) => void;
}): IndexedDBHandle<S>Creates an IndexedDB-backed adapter with transactions and migration support.
migrate runs during IDB upgrade (onupgradeneeded) before deposit ensures declared object stores exist.
createMemory
createMemory<S>(schema: S): Adapter<S>Creates an in-memory adapter backed by a Map. No dbName required; each call returns an isolated instance.
The memory adapter fully implements Adapter<S> including TTL: expired records are removed lazily on read, identical to the other adapters. Use this in tests and server-side rendering environments.
Migration Types
type MigrationContext = {
db: IDBDatabase;
newVersion: number | null;
oldVersion: number;
tx: IDBTransaction;
};
type MigrationFn = (ctx: MigrationContext) => void;Adapter Interface
interface Adapter<S> {
get<K extends keyof S>(table: K, key: KeyOf<S, K>): Promise<RecordOf<S, K> | undefined>;
getAll<K extends keyof S>(table: K): Promise<RecordOf<S, K>[]>;
iterate<K extends keyof S>(table: K): AsyncIterable<RecordOf<S, K>>;
forEach<K extends keyof S>(table: K, fn: (value: RecordOf<S, K>) => void | Promise<void>): Promise<void>;
has<K extends keyof S>(table: K, key: KeyOf<S, K>): Promise<boolean>;
getOrPut<K extends keyof S>(table: K, value: RecordOf<S, K>, ttl?: TtlMs): Promise<RecordOf<S, K>>;
put<K extends keyof S>(table: K, value: RecordOf<S, K>, ttl?: TtlMs): Promise<void>;
putAll<K extends keyof S>(table: K, values: RecordOf<S, K>[], ttl?: TtlMs): Promise<void>;
update<K extends keyof S>(
table: K,
key: KeyOf<S, K>,
changes: Partial<RecordOf<S, K>>,
ttl?: TtlMs,
): Promise<RecordOf<S, K> | undefined>;
delete<K extends keyof S>(table: K, key: KeyOf<S, K>): Promise<boolean>;
deleteWhere<K extends keyof S>(table: K, predicate: (value: RecordOf<S, K>) => boolean): Promise<number>;
deleteAll<K extends keyof S>(table: K): Promise<number>;
count<K extends keyof S>(table: K): Promise<number>;
query<K extends keyof S>(table: K): QueryBuilder<RecordOf<S, K>>;
dispose(): void;
observe<K extends keyof S>(
table: K,
listener: (value: RecordOf<S, K>[]) => void,
options?: { immediate?: boolean },
): () => void;
}The common adapter contract shared by all Deposit adapters.
IndexedDBHandle
interface IndexedDBHandle<S> extends Adapter<S> {
transaction<K extends keyof S, R>(tables: readonly K[], fn: (tx: TransactionContext<S, K>) => Promise<R>): Promise<R>;
}Extends Adapter with transaction support. Cleanup is handled through the shared dispose() method.
TransactionContext
type TransactionContext<S, K extends keyof S> = {
get<T extends K>(table: T, key: KeyOf<S, T>): Promise<RecordOf<S, T> | undefined>;
getAll<T extends K>(table: T): Promise<RecordOf<S, T>[]>;
iterate<T extends K>(table: T): AsyncIterable<RecordOf<S, T>>;
forEach<T extends K>(table: T, fn: (value: RecordOf<S, T>) => void | Promise<void>): Promise<void>;
has<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;
getOrPut<T extends K>(table: T, value: RecordOf<S, T>, ttl?: TtlMs): Promise<RecordOf<S, T>>;
put<T extends K>(table: T, value: RecordOf<S, T>, ttl?: TtlMs): Promise<void>;
putAll<T extends K>(table: T, values: RecordOf<S, T>[], ttl?: TtlMs): Promise<void>;
update<T extends K>(
table: T,
key: KeyOf<S, T>,
changes: Partial<RecordOf<S, T>>,
ttl?: TtlMs,
): Promise<RecordOf<S, T> | undefined>;
delete<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;
deleteWhere<T extends K>(table: T, predicate: (value: RecordOf<S, T>) => boolean): Promise<number>;
deleteAll<T extends K>(table: T): Promise<number>;
count<T extends K>(table: T): Promise<number>;
query<T extends K>(table: T): QueryBuilder<RecordOf<S, T>>;
};This context mirrors adapter methods and is scoped to the current transaction.
QueryBuilder
class QueryBuilder<T extends Record<string, unknown>> {
filter(fn: (value: T, index: number, array: T[]) => boolean): QueryBuilder<T>;
equals<K extends keyof T>(field: K, value: T[K]): QueryBuilder<T>;
between<K extends keyof T>(
field: K,
lower: Extract<NonNullable<T[K]>, number | string>,
upper: Extract<NonNullable<T[K]>, number | string>,
): QueryBuilder<T>;
startsWith<K extends keyof T>(field: K, prefix: string, options?: { ignoreCase?: boolean }): QueryBuilder<T>;
orderBy<K extends keyof T>(field: K, direction?: 'asc' | 'desc'): QueryBuilder<T>;
limit(n: number): QueryBuilder<T>;
offset(n: number): QueryBuilder<T>;
toArray(): Promise<T[]>;
count(): Promise<number>;
first(): Promise<T | undefined>;
}Query pipelines are lazy and execute only on toArray() / count() / first().
Validation rules:
limit(n)requiresnto be a non-negative integer.offset(n)requiresnto be a non-negative integer.count()returns the size of the fully transformed query result.
TTL Helper
type TtlMs = number & { readonly __brand: 'TtlMs' };
const ttl = {
ms(n: number): TtlMs;
seconds(n: number): TtlMs;
minutes(n: number): TtlMs;
hours(n: number): TtlMs;
days(n: number): TtlMs;
}Use TTL by passing one of these values as the third argument to put(table, value, ttl).
Validation rules:
- All TTL helper inputs must be finite and non-negative.
- Invalid TTL input throws synchronously from
ttl.*(...). - Passing an invalid
ttlvalue directly to write methods also throws.
Cookie observers are local to the current adapter instance. Cookie changes made in other tabs are not observable.
Error Behavior
createLocalStoragemethods throw when browser storage is unavailable.createSessionStoragemethods throw when browser storage is unavailable.- LocalStorage/SessionStorage writes throw on quota exceed (
QuotaExceededError) with a descriptive message. - IndexedDB open/transaction failures throw adapter-scoped errors with
causeattached when available.