Browser Key-Value Store with TTL
ts
import { s } from '@vielzeug/spell';
import { table, ttl } from '@vielzeug/vault';
import { createLocalStorage } from '@vielzeug/vault/local-storage';
interface Session {
id: string;
token: string;
}
const SessionSchema = s.object({ id: s.string(), token: s.string() });
const store = createLocalStorage({
name: 'app-v2',
schema: { sessions: table<Session>('id', { defaultTtl: ttl.hours(1) }) },
codecs: { sessions: SessionSchema },
});
await store.put('sessions', { id: 'current', token: 'FAKE-TOKEN-FOR-DOCS' });
console.log(await store.get('sessions', 'current'));IndexedDB with Transactions and Iteration
ts
import { s } from '@vielzeug/spell';
import { table } from '@vielzeug/vault';
import { createIndexedDB } from '@vielzeug/vault/indexeddb';
interface Event {
id: number;
type: string;
createdAt: number;
}
const EventSchema = s.object({ createdAt: s.number(), id: s.number(), type: s.string() });
const db = createIndexedDB({
name: 'events',
schema: { events: table<Event>('id', { indexes: ['type'] }) },
codecs: { events: EventSchema },
});
await db.batch(['events'], async (tx) => {
await tx.put('events', { id: 1, type: 'opened', createdAt: Date.now() });
await tx.put('events', { id: 2, type: 'saved', createdAt: Date.now() });
});
for await (const event of db.iterate('events')) {
console.log(event.type);
}SQLite Document Store
ts
import { DatabaseSync } from 'node:sqlite';
import { s } from '@vielzeug/spell';
import { table } from '@vielzeug/vault';
import { createSQLite } from '@vielzeug/vault/sqlite';
interface Job {
id: string;
status: 'queued' | 'running' | 'done';
}
const JobSchema = s.object({ id: s.string(), status: s.union('queued', 'running', 'done') });
const database = new DatabaseSync(':memory:');
const store = createSQLite({
database,
name: 'app',
schema: { jobs: table<Job>('id') },
codecs: { jobs: JobSchema },
});
await store.batch(['jobs'], async (tx) => {
await tx.put('jobs', { id: 'job-1', status: 'queued' });
await tx.put('jobs', { id: 'job-2', status: 'running' });
});
for await (const job of store.iterate('jobs')) {
console.log(job.id, job.status);
}Observe Table Changes
ts
const stop = store.observe('sessions', (sessions) => {
console.log(`Active sessions: ${sessions.length}`);
});
store.disposalSignal.addEventListener('abort', stop);Bound Helpers and Queries
ts
console.log(await store.has('sessions', 'current'));
console.log(await store.count('sessions'));
console.log(await store.isEmpty('sessions'));
const [a, b] = await store.getMany('sessions', ['current', 'old']);
console.log(await store.keys('sessions'));
console.log(await store.deleteMany('sessions', ['old', 'expired']));
await store.update('sessions', 'current', { token: 'FAKE-UPDATED-TOKEN' });
await store.upsert('sessions', 'temp', (existing) => ({
id: 'temp',
token: existing?.token ?? 'generated',
}));
const current = await store.query('sessions').equals('id', 'current').first();
(void a, b, current);React Hook
tsx
import { useEffect, useState } from 'react';
import type { AnySchema, KeyValueVaultStore, RecordOf } from '@vielzeug/vault';
export function useTable<S extends AnySchema, K extends keyof S & string>(
store: KeyValueVaultStore<S>,
table: K,
): RecordOf<S, K>[] {
const [rows, setRows] = useState<RecordOf<S, K>[]>([]);
useEffect(() => store.observe(table, setRows), [store, table]);
return rows;
}Migration Example
ts
import { s } from '@vielzeug/spell';
import { table } from '@vielzeug/vault';
import { createIndexedDB, defineMigration } from '@vielzeug/vault/indexeddb';
const UserSchema = s.object({ email: s.string().email(), id: s.number() });
const migrate = defineMigration([
{ field: 'email', table: 'users', type: 'addIndex' },
]);
const db = createIndexedDB({
name: 'app-v2',
version: 2,
migrate,
schema: { users: table<{ id: number; email: string }>('id', { indexes: ['email'] }) },
codecs: { users: UserSchema },
});