TTL and Pruning
Problem
You need records to expire automatically after a fixed duration. You also want to reclaim storage by removing expired records that have not yet been evicted by a read.
Solution
Pass a TtlMs value as the third argument to put() or putAll(). Use ttl.* helpers to create the value — raw numbers are rejected by the type system. For per-table defaults, chain .ttl() on the table() call. For explicit cleanup, call pruneExpired() or schedule it with scheduleExpiredPrune.
ts
import { createMemory, scheduleExpiredPrune, table, ttl } from '@vielzeug/vault';
type Session = { id: string; userId: number };
// Per-table default: every write to sessions uses 30 minutes unless overridden
const schema = {
sessions: table<Session>('id').ttl(ttl.minutes(30)),
};
const db = createMemory({ schema });
// Write with default TTL (30 minutes, from the schema)
await db.put('sessions', { id: 's1', userId: 1 });
// Override the table default at the call site
await db.put('sessions', { id: 's2', userId: 2 }, ttl.hours(8));
// All ttl.* helpers
await db.put('sessions', { id: 's3', userId: 3 }, ttl.ms(500));
await db.put('sessions', { id: 's4', userId: 4 }, ttl.seconds(30));
await db.put('sessions', { id: 's5', userId: 5 }, ttl.days(7));
// Expired records are excluded from reads automatically:
// db.get(), db.getAll(), db.getMany(), db.count(), db.query() all skip expired records.
// Explicit pruning — sweeps all tables, returns count per table
const pruned = await db.pruneExpired();
console.log(pruned); // { sessions: 0 } — none have expired yet
// Prune only specific tables
const partial = await db.pruneExpired(['sessions']);
console.log(partial); // { sessions: 0 }
// Schedule periodic pruning for write-heavy tables
const stop = scheduleExpiredPrune(db, { interval: ttl.hours(1) });
// On app teardown (before dispose)
stop();
db.dispose();Checking Expired Record Counts
debug() reports live versus expired record counts per table without evicting anything.
ts
const info = await db.debug();
for (const t of info.tables) {
console.log(`${t.name}: ${t.recordCount} live, ${t.expiredCount} expired`);
}Pitfalls
- Expired records are evicted lazily on the next read to that key. If a table is written to frequently but rarely read, expired records accumulate. Call
pruneExpired()orscheduleExpiredPruneto reclaim storage proactively. ttl.hours(0)is valid and means the record expires immediately. The record may still be readable within the same synchronous tick, but will be treated as expired on the next async read.- On IndexedDB,
pruneExpireduses a cursor-based pass — expired records are deleted without loading their values into memory. On LocalStorage / SessionStorage and Memory, each key is checked in sequence. scheduleExpiredPruneusessetIntervalinternally. Call the returnedstop()function before callingdb.dispose()when a manual stop is needed. Whendispose()is called without stopping the schedule first, the next interval tick will receive aVaultDisposedErrorand automatically clear the timer — so no dangling timer will fire after the adapter is torn down.