@vielzeug/vault 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -0
- package/dist/_dev.cjs +2 -0
- package/dist/_dev.cjs.map +1 -0
- package/dist/_dev.d.ts +2 -0
- package/dist/_dev.d.ts.map +1 -0
- package/dist/_dev.js +12 -0
- package/dist/_dev.js.map +1 -0
- package/dist/adapter-core.cjs +2 -0
- package/dist/adapter-core.cjs.map +1 -0
- package/dist/adapter-core.d.ts +8 -0
- package/dist/adapter-core.d.ts.map +1 -0
- package/dist/adapter-core.js +269 -0
- package/dist/adapter-core.js.map +1 -0
- package/dist/adapters/indexeddb.cjs +2 -0
- package/dist/adapters/indexeddb.cjs.map +1 -0
- package/dist/adapters/indexeddb.d.ts +13 -0
- package/dist/adapters/indexeddb.d.ts.map +1 -0
- package/dist/adapters/indexeddb.js +336 -0
- package/dist/adapters/indexeddb.js.map +1 -0
- package/dist/adapters/memory.cjs +2 -0
- package/dist/adapters/memory.cjs.map +1 -0
- package/dist/adapters/memory.d.ts +14 -0
- package/dist/adapters/memory.d.ts.map +1 -0
- package/dist/adapters/memory.js +200 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/webstorage.cjs +2 -0
- package/dist/adapters/webstorage.cjs.map +1 -0
- package/dist/adapters/webstorage.d.ts +14 -0
- package/dist/adapters/webstorage.d.ts.map +1 -0
- package/dist/adapters/webstorage.js +182 -0
- package/dist/adapters/webstorage.js.map +1 -0
- package/dist/errors.cjs +2 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +17 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/internal.cjs +2 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.ts +50 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +125 -0
- package/dist/internal.js.map +1 -0
- package/dist/migration.cjs +2 -0
- package/dist/migration.cjs.map +1 -0
- package/dist/migration.d.ts +37 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +27 -0
- package/dist/migration.js.map +1 -0
- package/dist/prune.cjs +2 -0
- package/dist/prune.cjs.map +1 -0
- package/dist/prune.d.ts +32 -0
- package/dist/prune.d.ts.map +1 -0
- package/dist/prune.js +20 -0
- package/dist/prune.js.map +1 -0
- package/dist/query.cjs +2 -0
- package/dist/query.cjs.map +1 -0
- package/dist/query.d.ts +113 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +126 -0
- package/dist/query.js.map +1 -0
- package/dist/streaming.cjs +2 -0
- package/dist/streaming.cjs.map +1 -0
- package/dist/streaming.d.ts +22 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +21 -0
- package/dist/streaming.js.map +1 -0
- package/dist/ttl.cjs +2 -0
- package/dist/ttl.cjs.map +1 -0
- package/dist/ttl.d.ts +74 -0
- package/dist/ttl.d.ts.map +1 -0
- package/dist/ttl.js +32 -0
- package/dist/ttl.js.map +1 -0
- package/dist/types.cjs +2 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.ts +409 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +27 -0
- package/dist/types.js.map +1 -0
- package/dist/vault.cjs +2 -0
- package/dist/vault.cjs.map +1 -0
- package/dist/vault.iife.js +2 -0
- package/dist/vault.iife.js.map +1 -0
- package/dist/vault.js +2 -0
- package/dist/vault.js.map +1 -0
- package/dist/versioned-codec.cjs +2 -0
- package/dist/versioned-codec.cjs.map +1 -0
- package/dist/versioned-codec.d.ts +44 -0
- package/dist/versioned-codec.d.ts.map +1 -0
- package/dist/versioned-codec.js +32 -0
- package/dist/versioned-codec.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault.js","names":[],"sources":["../src/_dev.ts","../src/errors.ts","../src/internal.ts","../src/query.ts","../src/ttl.ts","../src/adapter-core.ts","../src/adapters/indexeddb.ts","../src/adapters/memory.ts","../src/adapters/webstorage.ts","../src/migration.ts","../src/prune.ts","../src/streaming.ts","../src/versioned-codec.ts","../src/types.ts"],"sourcesContent":["const isDev = !(globalThis as { __VAULT_PROD__?: boolean }).__VAULT_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/vault] ${msg}`);\n}\n\n/** @internal */\nexport function error(msg: string, ...args: unknown[]): void {\n if (isDev) console.error(`[@vielzeug/vault] ${msg}`, ...args);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","/**\n * Base class for all vault errors. Catch with `instanceof VaultError` to\n * handle any vault-originated error regardless of its specific subtype.\n */\nexport class VaultError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n // Ensures `instanceof` works correctly when transpiled to ES5.\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is VaultError {\n return err instanceof VaultError;\n }\n}\n\n/** Thrown when an operation is attempted on a disposed adapter or observer hub. */\nexport class VaultDisposedError extends VaultError {\n constructor(message = 'adapter is disposed', opts?: ErrorOptions) {\n super(message, opts);\n }\n}\n\n/** Thrown when a `batch()` callback accesses a table not declared in the scope. */\nexport class VaultScopeError extends VaultError {}\n\n/** Thrown when a WebStorage write exceeds the storage quota. */\nexport class VaultQuotaError extends VaultError {}\n\n/** Thrown when an IndexedDB `onupgradeneeded` migration callback throws. */\nexport class VaultMigrationError extends VaultError {}\n","import type { AnySchema, KeyOf, RecordOf } from './types';\n\nimport { error as logError, warn } from './_dev';\nimport { VaultDisposedError, VaultError, VaultScopeError } from './errors';\n\ntype ObserverListener<T> = (records: T[]) => void;\n\n/**\n * Separator used between encoded key segments. The null byte (\\x00) is safe\n * because `encodeURIComponent` always percent-encodes it to `%00` (a 3-character\n * string), so the raw `\\x00` byte can never appear inside an encoded segment —\n * eliminating key-collision risk that a URL-safe character like `~` would carry.\n */\nconst SEP = '\\x00';\n\nexport function encodeDbPrefix(dbName: string): string {\n return `${encodeURIComponent(dbName)}${SEP}`;\n}\n\nexport function encodeStorageKey(dbName: string, table: string, key: string): string {\n return `${encodeURIComponent(dbName)}${SEP}${encodeURIComponent(table)}${SEP}${encodeURIComponent(key)}`;\n}\n\nexport function encodeStorageTablePrefix(dbName: string, table: string): string {\n return `${encodeURIComponent(dbName)}${SEP}${encodeURIComponent(table)}${SEP}`;\n}\n\nexport function decodeStorageTableFromKey(dbName: string, storageKey: string | null): string | undefined {\n if (!storageKey) return undefined;\n\n const prefix = encodeDbPrefix(dbName);\n\n if (!storageKey.startsWith(prefix)) return undefined;\n\n const tail = storageKey.slice(prefix.length);\n const end = tail.indexOf(SEP);\n\n if (end === -1) return undefined;\n\n try {\n return decodeURIComponent(tail.slice(0, end));\n } catch {\n return undefined;\n }\n}\n\nexport function createObserverHub<S extends AnySchema>(\n getAll: <K extends keyof S & string>(table: K) => Promise<RecordOf<S, K>[]>,\n onError?: (error: unknown) => void,\n) {\n const observers = new Map<string, Set<ObserverListener<unknown>>>();\n let disposed = false;\n\n const reportObserverError = (error: unknown): void => {\n if (onError) {\n onError(error);\n } else {\n logError('observer notification failed', error);\n }\n };\n\n const notify = <K extends keyof S & string>(table: K): void => {\n if (disposed) return;\n\n const listeners = observers.get(table);\n\n if (!listeners || listeners.size === 0) return;\n\n void getAll(table)\n .then((records) => {\n if (disposed) return;\n\n const current = observers.get(table);\n\n if (!current || current.size === 0) return;\n\n for (const listener of current) {\n try {\n listener(records as unknown[]);\n } catch (error) {\n reportObserverError(error);\n }\n }\n })\n .catch((error) => reportObserverError(error));\n };\n\n const observe = <K extends keyof S & string>(\n table: K,\n listener: (records: RecordOf<S, K>[]) => void,\n { immediate = true, signal }: { immediate?: boolean; signal?: AbortSignal } = {},\n ): (() => void) => {\n if (disposed) {\n throw new VaultDisposedError('observer hub is disposed');\n }\n\n if (signal?.aborted) return () => {};\n\n const key = table;\n const wrapped = listener as ObserverListener<unknown>;\n let listeners = observers.get(key);\n\n if (!listeners) {\n listeners = new Set();\n observers.set(key, listeners);\n }\n\n listeners.add(wrapped);\n\n // Fire an initial snapshot unless the caller opted out.\n if (immediate) notify(table);\n\n const stop = (): void => {\n const current = observers.get(key);\n\n if (!current) return;\n\n current.delete(wrapped);\n\n if (current.size === 0) observers.delete(key);\n };\n\n signal?.addEventListener('abort', stop, { once: true });\n\n return stop;\n };\n\n const dispose = (): void => {\n disposed = true;\n observers.clear();\n };\n\n return { dispose, notify, observe };\n}\n\n/**\n * Creates an AsyncIterable that yields a snapshot on every mutation.\n * The first snapshot is emitted immediately (on the first `next()` call).\n *\n * `mode` controls back-pressure:\n * - `'latest'` (default): if the consumer lags, only the most recent snapshot is retained.\n * Intermediate states are discarded. Best for rendering/display use-cases.\n * - `'all'`: every snapshot is queued and delivered in order. Use when every intermediate\n * state matters (audit trails, animation).\n *\n * Pass an `AbortSignal` to cancel the iteration externally:\n * ```ts\n * const controller = new AbortController();\n * for await (const users of db.watch('users', { signal: controller.signal })) { ... }\n * controller.abort(); // stops the loop\n * ```\n *\n * **Resource note:** The observer subscription is created eagerly on `[Symbol.asyncIterator]()` to\n * avoid missing mutations before the first `next()` call. Always consume the iterator to completion,\n * `break`/`return` out of `for await`, or pass an `AbortSignal` — otherwise the subscription\n * will remain active until the adapter is disposed.\n */\nexport function createWatchIterable<T>(\n subscribe: (listener: (snapshot: T[]) => void, signal?: AbortSignal) => () => void,\n mode: 'all' | 'latest' = 'latest',\n signal?: AbortSignal,\n): AsyncIterable<T[]> {\n return {\n [Symbol.asyncIterator]() {\n let pending: T[][] = [];\n let waiting: ((value: IteratorResult<T[]>) => void) | null = null;\n let done = signal?.aborted ?? false;\n let unsubscribe: (() => void) | null = null;\n\n const finish = (): void => {\n if (done) return;\n\n done = true;\n unsubscribe?.();\n unsubscribe = null;\n\n if (waiting) {\n waiting({ done: true, value: undefined });\n waiting = null;\n }\n };\n\n signal?.addEventListener('abort', finish, { once: true });\n\n // Eager registration: subscribe immediately so mutations between iterator\n // creation and first next() are not silently lost.\n if (!done) {\n unsubscribe = subscribe((snapshot) => {\n if (done) return;\n\n if (waiting) {\n const resolve = waiting;\n\n waiting = null;\n resolve({ done: false, value: snapshot });\n } else if (mode === 'all') {\n pending.push(snapshot);\n } else {\n pending = [snapshot];\n }\n });\n }\n\n return {\n async next(): Promise<IteratorResult<T[]>> {\n if (done) return { done: true, value: undefined };\n\n if (pending.length > 0) {\n const value = pending.shift()!;\n\n return { done: false, value };\n }\n\n return new Promise<IteratorResult<T[]>>((resolve) => {\n waiting = resolve;\n });\n },\n\n async return(): Promise<IteratorResult<T[]>> {\n finish();\n\n return { done: true, value: undefined };\n },\n\n async throw(err?: unknown): Promise<IteratorResult<T[]>> {\n finish();\n\n return Promise.reject(err);\n },\n };\n },\n };\n}\n\n/**\n * Observes multiple tables simultaneously. The listener receives a combined snapshot\n * `{ [tableName]: RecordOf<S, T>[] }` and fires once after all tables have delivered\n * their first snapshot. Subsequent firings are coalesced per microtask.\n *\n * Since `observe()` always fires immediately, each per-table observer populates the\n * snapshotMap on registration. No separate prefetch step is needed.\n */\nexport function createObserveMany<S extends AnySchema>(hub: ReturnType<typeof createObserverHub<S>>) {\n return function observeMany<K extends keyof S & string>(\n tables: readonly K[],\n listener: (snapshots: { [T in K]: RecordOf<S, T>[] }) => void,\n { eager = false, signal }: { eager?: boolean; signal?: AbortSignal } = {},\n ): () => void {\n const distinctTables = [...new Set(tables)] as K[];\n\n if (distinctTables.length === 0) throw new VaultScopeError('observeMany requires at least one table');\n\n if (distinctTables.length < tables.length) {\n warn('observeMany: duplicate table names were ignored');\n }\n\n if (signal?.aborted) return () => {};\n\n const snapshotMap = new Map<string, RecordOf<S, K>[]>();\n let microtaskQueued = false;\n let stopped = false;\n\n const buildCombined = (): { [T in K]: RecordOf<S, T>[] } =>\n Object.fromEntries(tables.map((t) => [t, snapshotMap.get(t) ?? []])) as { [T in K]: RecordOf<S, T>[] };\n\n const scheduleFlush = (): void => {\n // In eager mode fire as soon as any table has delivered; in default mode wait for all.\n if (microtaskQueued || (!eager && snapshotMap.size < distinctTables.length)) return;\n\n microtaskQueued = true;\n queueMicrotask(() => {\n microtaskQueued = false;\n\n if (!stopped) listener(buildCombined());\n });\n };\n\n // Register observers — each fires an immediate snapshot that populates snapshotMap.\n // Once all tables have delivered, scheduleFlush emits the first combined snapshot.\n // Signal is NOT passed to inner hub.observe() calls — the outer stop() is the single\n // cleanup path, avoiding O(tables) redundant abort listeners per signal.\n const stopFns = distinctTables.map((t) =>\n hub.observe(t, (records) => {\n snapshotMap.set(t, records as RecordOf<S, K>[]);\n scheduleFlush();\n }),\n );\n\n const stop = (): void => {\n stopped = true;\n\n for (const s of stopFns) s();\n };\n\n signal?.addEventListener('abort', stop, { once: true });\n\n return stop;\n };\n}\n\nexport function getRecordKey<S extends AnySchema, K extends keyof S>(\n schema: S,\n table: K,\n value: RecordOf<S, K>,\n): KeyOf<S, K> {\n const keyField = String(schema[table].key);\n const keyValue = (value as Record<string, unknown>)[keyField];\n\n if (keyValue === undefined || keyValue === null) {\n throw new VaultError(\n `key field \"${keyField}\" in table \"${String(table)}\" must be a non-null value, got ${String(keyValue)}`,\n );\n }\n\n return keyValue as KeyOf<S, K>;\n}\n","import { VaultError } from './errors';\n\ntype Predicate<T> = (value: T, index: number, array: T[]) => boolean;\n/**\n * `isNonFilter`: when true this op is excluded from `totalCount()` — it does not restrict\n * *which* records match (limit, offset, orderBy), only how results are presented.\n */\ntype QueryOp<T> = { apply: (data: T[]) => T[]; isNonFilter?: boolean };\ntype ComparableFieldKeys<T extends Record<string, unknown>> = {\n [K in keyof T]-?: Extract<NonNullable<T[K]>, number | string> extends never ? never : K;\n}[keyof T];\n\n/**\n * A primary-key range hint that can be pushed down to native storage backends (e.g. IndexedDB).\n * When `QueryContext.getRange` and `keyField` are present, a matching first filter op replaces\n * the full-table `source()` scan with a targeted range fetch.\n */\nexport type NativeRange =\n | { type: 'eq'; value: unknown }\n | { lower: unknown; type: 'between'; upper: unknown }\n | { prefix: string; type: 'starts' };\n\nexport type QueryContext<T extends Record<string, unknown>> = {\n deleteMany?: (records: T[]) => Promise<number>;\n /**\n * When present alongside `indexedFields`, replaces `source()` for secondary-index filter ops.\n * Only activated when the first op is `equals`, `between`, or `startsWith` on a field that\n * has an index registered in the schema. IndexedDB uses `IDBIndex.getAll(range)` under the hood.\n */\n getIndexRange?: (field: string, range: NativeRange) => Promise<T[]>;\n /**\n * When present alongside `keyField`, replaces `source()` for primary-key filter ops.\n * Only activated when the first op is an `equals`, `between`, or case-sensitive `startsWith`\n * on `keyField`. All remaining ops still run in-memory against the range result.\n */\n getRange?: (range: NativeRange) => Promise<T[]>;\n /** Fields that have secondary indexes — used to detect when a filter op can use `getIndexRange`. */\n indexedFields?: ReadonlySet<string>;\n /** Primary key field name — used to detect when a filter op can be pushed to `getRange`. */\n keyField?: string;\n source: () => Promise<T[]>;\n};\n\n/* -------------------- Public interfaces -------------------- */\n\n/**\n * Shared query methods. `T` is the base record type; `N` is the progressively-narrowed type\n * accumulated by `equals()` calls.\n */\ntype ChainedQuery<T extends Record<string, unknown>, N extends T, Self extends ChainedQuery<T, N, Self>> = {\n /**\n * Filter records where `field` is between `lower` and `upper` (inclusive).\n * Preserves any type narrowing already accumulated by prior `equals()` calls.\n */\n between<K extends ComparableFieldKeys<T>>(\n field: K,\n lower: Extract<NonNullable<T[K]>, number | string>,\n upper: Extract<NonNullable<T[K]>, number | string>,\n ): QueryBuilder<T, N>;\n /**\n * Returns the number of records matching all applied operations, including `limit` and `offset`.\n * To get the full filtered set size regardless of pagination (e.g. for \"page X of N\" UIs),\n * use `totalCount()` instead.\n */\n count(): Promise<number>;\n /**\n * Filter records where `field` exactly equals `value`.\n * The return type is narrowed to `QueryBuilder<T & Record<K, V>>` so that subsequent\n * `.toArray()`, `.first()`, etc. reflect the equality constraint in their result type.\n *\n * ```ts\n * // result: (User & { role: 'admin' })[]\n * const admins = await db.query('users').equals('role', 'admin').toArray();\n * ```\n */\n equals<K extends keyof T & string, V extends T[K]>(field: K, value: V): QueryBuilder<T & Record<K, V>>;\n /**\n * Returns `true` if at least one record matches all applied filter operations.\n * Equivalent to `(await query.first()) !== undefined` but makes the intent explicit.\n * Presentation-only ops (`limit`, `offset`, `orderBy`) are respected before checking.\n */\n exists(): Promise<boolean>;\n filter(fn: Predicate<N>): Self;\n first(): Promise<N | undefined>;\n limit(n: number): Self;\n offset(n: number): Self;\n orderBy<K extends keyof T>(field: K, direction?: 'asc' | 'desc'): Self;\n /**\n * Filter records where `field` starts with `prefix`.\n * Preserves any type narrowing already accumulated by prior `equals()` calls.\n *\n * **Index push-down** is only active when `ignoreCase` is `false` (default) and this\n * is the first operation in the chain. With `ignoreCase: true`, a full-table scan is\n * always performed regardless of whether the field has an index.\n */\n startsWith<K extends keyof T>(field: K, prefix: string, options?: { ignoreCase?: boolean }): QueryBuilder<T, N>;\n toArray(): Promise<N[]>;\n /**\n * Returns the number of records matching the applied filter predicates.\n * Presentation-only ops (`limit`, `offset`, `orderBy`) are intentionally ignored — this\n * always counts the full filtered set, making paginated total-count queries possible\n * without a second query.\n */\n totalCount(): Promise<number>;\n};\n\n/** Extends the shared query API with `delete()`. Available on both `Adapter.query()` and inside `batch()` callbacks. */\nexport interface QueryBuilder<T extends Record<string, unknown>, N extends T = T> extends ChainedQuery<\n T,\n N,\n QueryBuilder<T, N>\n> {\n delete(): Promise<number>;\n}\n\n/* -------------------- Helpers -------------------- */\n\nasync function applyOps<T extends Record<string, unknown>>(\n ctx: QueryContext<T>,\n ops: readonly QueryOp<T>[],\n): Promise<T[]> {\n let data = await ctx.source();\n\n for (const op of ops) {\n data = op.apply(data);\n }\n\n return data;\n}\n\nfunction assertNonNegativeInteger(value: number, name: string): number {\n if (!Number.isInteger(value) || value < 0) {\n throw new VaultError(`${name} must be a non-negative integer`);\n }\n\n return value;\n}\n\n/* -------------------- Push-down helpers -------------------- */\n\n/** Build a new QueryContext with source replaced by a range/index fetch. Drops range hints since push-down is done. */\nfunction pushDownContext<T extends Record<string, unknown>>(\n ctx: QueryContext<T>,\n newSource: () => Promise<T[]>,\n): QueryContext<T> {\n return { deleteMany: ctx.deleteMany, source: newSource };\n}\n\n/* -------------------- Factory -------------------- */\n\nexport function createQueryBuilder<T extends Record<string, unknown>, N extends T = T>(\n ctx: QueryContext<T>,\n ops: readonly QueryOp<T>[] = [],\n): QueryBuilder<T, N> {\n const append = (op: QueryOp<T>): QueryBuilder<T, N> => createQueryBuilder<T, N>(ctx, [...ops, op]);\n\n return {\n between(field, lower, upper) {\n const fieldStr = String(field);\n\n if (ops.length === 0) {\n if (ctx.getRange && ctx.keyField === fieldStr) {\n return createQueryBuilder(\n pushDownContext(ctx, () => ctx.getRange!({ lower, type: 'between', upper })),\n ops,\n );\n }\n\n if (ctx.getIndexRange && ctx.indexedFields?.has(fieldStr)) {\n return createQueryBuilder(\n pushDownContext(ctx, () => ctx.getIndexRange!(fieldStr, { lower, type: 'between', upper })),\n ops,\n );\n }\n }\n\n return append({\n apply: (data) =>\n data.filter((r) => {\n const v = r[field] as number | string;\n\n return v >= lower && v <= upper;\n }),\n }) as unknown as QueryBuilder<T, N>;\n },\n count(): Promise<number> {\n return applyOps(ctx, ops).then((r) => r.length);\n },\n async delete(): Promise<number> {\n if (!ctx.deleteMany) {\n throw new VaultError('query.delete is not available for this adapter context');\n }\n\n const records = await applyOps(ctx, ops);\n\n return ctx.deleteMany(records);\n },\n equals<K extends keyof T & string, V extends T[K]>(field: K, value: V): QueryBuilder<T & Record<K, V>> {\n const fieldStr = String(field);\n\n if (ops.length === 0) {\n if (ctx.getRange && ctx.keyField === fieldStr) {\n return createQueryBuilder<T & Record<K, V>>(\n pushDownContext(\n ctx as unknown as QueryContext<T & Record<K, V>>,\n () => ctx.getRange!({ type: 'eq', value }) as Promise<(T & Record<K, V>)[]>,\n ),\n ) as QueryBuilder<T & Record<K, V>>;\n }\n\n if (ctx.getIndexRange && ctx.indexedFields?.has(fieldStr)) {\n return createQueryBuilder<T & Record<K, V>>(\n pushDownContext(\n ctx as unknown as QueryContext<T & Record<K, V>>,\n () => ctx.getIndexRange!(fieldStr, { type: 'eq', value }) as Promise<(T & Record<K, V>)[]>,\n ),\n ) as QueryBuilder<T & Record<K, V>>;\n }\n }\n\n return createQueryBuilder<T & Record<K, V>>(ctx as unknown as QueryContext<T & Record<K, V>>, [\n ...(ops as unknown as QueryOp<T & Record<K, V>>[]),\n { apply: (data) => data.filter((r) => r[field] === value) as (T & Record<K, V>)[] },\n ]);\n },\n async exists(): Promise<boolean> {\n if (ops.length === 0) return ctx.source().then((r) => r.length > 0);\n\n return applyOps(ctx, ops).then((r) => r.length > 0);\n },\n filter(fn) {\n return append({ apply: (data) => data.filter(fn as Predicate<T>) });\n },\n first(): Promise<N | undefined> {\n if (ops.length === 0) return ctx.source().then((r) => r[0] as N | undefined);\n\n return applyOps(ctx, ops).then((r) => r[0] as N | undefined);\n },\n limit(n) {\n const safeN = assertNonNegativeInteger(n, 'query.limit');\n\n return append({ apply: (data) => data.slice(0, safeN), isNonFilter: true });\n },\n offset(n) {\n const safeN = assertNonNegativeInteger(n, 'query.offset');\n\n return append({ apply: (data) => data.slice(safeN), isNonFilter: true });\n },\n orderBy(field, direction = 'asc') {\n return append({\n apply: (data) => {\n const sign = direction === 'asc' ? 1 : -1;\n\n return [...data].sort((a, b) => {\n const av = a[field] as number | string;\n const bv = b[field] as number | string;\n\n if (av === bv) return 0;\n\n return av > bv ? sign : -sign;\n });\n },\n isNonFilter: true,\n });\n },\n startsWith(field, prefix, { ignoreCase = false } = {}) {\n const fieldStr = String(field);\n\n if (!ignoreCase && prefix.length > 0 && ops.length === 0) {\n if (ctx.getRange && ctx.keyField === fieldStr) {\n return createQueryBuilder(\n pushDownContext(ctx, () => ctx.getRange!({ prefix, type: 'starts' })),\n ops,\n );\n }\n\n if (ctx.getIndexRange && ctx.indexedFields?.has(fieldStr)) {\n return createQueryBuilder(\n pushDownContext(ctx, () => ctx.getIndexRange!(fieldStr, { prefix, type: 'starts' })),\n ops,\n );\n }\n }\n\n const needle = ignoreCase ? prefix.toLowerCase() : prefix;\n\n return append({\n apply: (data) =>\n data.filter((r) => {\n const v = r[field];\n\n if (typeof v !== 'string') return false;\n\n const haystack = ignoreCase ? v.toLowerCase() : v;\n\n return haystack.startsWith(needle);\n }),\n }) as unknown as QueryBuilder<T, N>;\n },\n toArray(): Promise<N[]> {\n return applyOps(ctx, ops) as Promise<N[]>;\n },\n totalCount(): Promise<number> {\n const filterOps = ops.filter((op) => !op.isNonFilter);\n\n return applyOps(ctx, filterOps).then((r) => r.length);\n },\n };\n}\n","import { VaultError } from './errors';\n\n/* -------------------- TtlMs (owns here to avoid circular deps with types.ts) -------------------- */\n\ndeclare const ttlMsBrand: unique symbol;\n/** A duration in milliseconds, produced by the `ttl.*` helpers. Branded to prevent accidental raw numbers. */\nexport type TtlMs = number & { readonly [ttlMsBrand]: never };\n\n/* -------------------- Codec -------------------- */\n\n/**\n * Pluggable serialization contract. Implement to change how vault stores values at rest\n * (e.g. compressed JSON, msgpack, encrypted envelopes).\n *\n * The codec translates between the vault-internal TTL envelope `{ value, expiresAt? }` and\n * whatever format is actually written to the underlying storage backend.\n *\n * The default codec stores `{ value: T, expiresAt?: number }` verbatim — identical to the\n * previous behaviour.\n *\n * ```ts\n * const compactCodec: VaultCodec = {\n * encode: (value, expiresAt) => expiresAt ? { v: value, e: expiresAt } : { v: value },\n * decode: (raw) => {\n * if (typeof raw !== 'object' || raw === null || !('v' in raw)) return undefined;\n * const { v, e } = raw as { v: unknown; e?: unknown };\n * return { value: v as any, expiresAt: typeof e === 'number' ? e : undefined };\n * },\n * };\n * ```\n */\nexport type VaultCodec = {\n /**\n * Parse a raw stored value into the internal TTL envelope.\n * Return `undefined` for any unrecognized or corrupt data.\n */\n decode<T>(raw: unknown): { expiresAt?: number; value: T } | undefined;\n /**\n * Encode a value (and optional absolute expiry timestamp) into the storage format.\n * `expiresAt` is an epoch-ms timestamp — use `Date.now() + ttlMs` to compute it.\n */\n encode<T>(value: T, expiresAt?: number): unknown;\n};\n\n/* -------------------- Duration helpers -------------------- */\n\nexport const ttl = {\n days: (n: number) => assertTtlMs(assertTtlMs(n, 'ttl.days') * 86_400_000, 'ttl.days (result)'),\n hours: (n: number) => assertTtlMs(assertTtlMs(n, 'ttl.hours') * 3_600_000, 'ttl.hours (result)'),\n minutes: (n: number) => assertTtlMs(assertTtlMs(n, 'ttl.minutes') * 60_000, 'ttl.minutes (result)'),\n ms: (n: number) => assertTtlMs(n, 'ttl.ms'),\n seconds: (n: number) => assertTtlMs(assertTtlMs(n, 'ttl.seconds') * 1000, 'ttl.seconds (result)'),\n} as const;\n\n/* -------------------- Storage record -------------------- */\n\n/** Internal envelope used by all storage backends. `expiresAt` is an epoch timestamp in ms. */\nexport type StoredRecord<T> = {\n expiresAt?: number;\n value: T;\n};\n\n/** Returns true when an epoch-ms expiry timestamp has passed. Safe to call with `undefined`. */\nexport function isExpired(expiresAt: number | undefined): boolean {\n return expiresAt !== undefined && Date.now() >= expiresAt;\n}\n\nexport function assertTtlMs(ttlMs: number, source: string): TtlMs {\n if (!Number.isFinite(ttlMs) || ttlMs <= 0) {\n throw new VaultError(`${source} expected a finite positive number, received ${String(ttlMs)}`);\n }\n\n return ttlMs as TtlMs;\n}\n\nexport function wrapStored<T>(value: T, ttlMs?: TtlMs): StoredRecord<T> {\n if (ttlMs === undefined) return { value };\n\n return { expiresAt: Date.now() + ttlMs, value };\n}\n\nexport function unwrapStored<T>(raw: StoredRecord<T>): T | undefined {\n if (isExpired(raw.expiresAt)) return undefined;\n\n return raw.value;\n}\n\nexport function parseStored<T>(raw: unknown): StoredRecord<T> | undefined {\n if (typeof raw !== 'object' || raw === null || !('value' in raw)) return undefined;\n\n const record = raw as { expiresAt?: unknown; value: unknown };\n\n if (record.expiresAt !== undefined && (typeof record.expiresAt !== 'number' || !Number.isFinite(record.expiresAt))) {\n return undefined;\n }\n\n return record as StoredRecord<T>;\n}\n\n/**\n * Parse a raw stored value and determine its TTL status in one call.\n * Combines `parseStored` + `unwrapStored` with an explicit `expired` flag so callers\n * can distinguish between \"key not found\" (`found: false`) and \"key is TTL-expired\"\n * (`found: true, expired: true, value: undefined`).\n */\nexport function readWithTtl<T>(raw: unknown): { expired: boolean; found: boolean; value: T | undefined } {\n const parsed = parseStored<T>(raw);\n\n if (!parsed) return { expired: false, found: false, value: undefined };\n\n const value = unwrapStored(parsed);\n\n return { expired: value === undefined, found: true, value };\n}\n\n/** Default codec — stores `{ value, expiresAt? }` verbatim. Zero overhead vs. previous behaviour. */\nexport const defaultCodec: VaultCodec = {\n decode: parseStored as VaultCodec['decode'],\n encode: <T>(value: T, expiresAt?: number): unknown => (expiresAt !== undefined ? { expiresAt, value } : { value }),\n};\n","import type {\n Adapter,\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n KeyOf,\n MetricsEvent,\n RecordOf,\n TransactionContext,\n TtlMs,\n} from './types';\n\nimport { warn } from './_dev';\nimport { VaultDisposedError, VaultError, VaultScopeError } from './errors';\nimport { createObserveMany, createObserverHub, createWatchIterable, getRecordKey } from './internal';\nimport { createQueryBuilder, type NativeRange, type QueryContext } from './query';\nimport { assertTtlMs } from './ttl';\n\n/* -------------------- Internal backend protocol -------------------- */\n\n/** @internal Full backend protocol implemented by each adapter. A single flat interface — no StorageCore/StorageBackend split. */\nexport type StorageBackend<S extends AnySchema, K extends keyof S & string = keyof S & string> = {\n clear<T extends K>(table: T): Promise<void>;\n /** Returns live (non-expired) record count for the table. */\n count<T extends K>(table: T): Promise<number>;\n delete<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;\n deleteMany<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<number>;\n dispose?(): Promise<void>;\n get<T extends K>(table: T, key: KeyOf<S, T>): Promise<RecordOf<S, T> | undefined>;\n getAll<T extends K>(table: T): Promise<RecordOf<S, T>[]>;\n /**\n * Optional: fetch all live primary keys without materialising full records.\n * When present, `keys()` uses this instead of `getAll()` + key extraction.\n * For tables with TTL, implementations may still fall back to `getAll()` to\n * filter expired keys accurately.\n */\n getAllKeys?<T extends K>(table: T): Promise<KeyOf<S, T>[]>;\n /**\n * Optional secondary-index range fetch. When present, `query()` activates `getIndexRange` in\n * the QueryContext so that a leading `equals`, `between`, or `startsWith` on an indexed field\n * uses an IDB index instead of a full-table scan.\n */\n getByIndexRange?<T extends K>(table: T, field: string, range: NativeRange): Promise<RecordOf<S, T>[]>;\n /**\n * Optional primary-key range fetch for push-down queries. When present, `query()` activates\n * `getRange` in the QueryContext so that a leading `equals`, `between`, or `startsWith` on the\n * key field avoids a full-table scan. Falls back to `getAll` when absent.\n */\n getByKeyRange?<T extends K>(table: T, range: NativeRange): Promise<RecordOf<S, T>[]>;\n /**\n * Optional: fetch multiple records by key in a single operation. Preserves key order; missing keys yield\n * `undefined`. Falls back to N individual `get` calls when absent.\n */\n getMany?<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<Array<RecordOf<S, T> | undefined>>;\n /** Raw record count including TTL-expired entries. Used only for debug(). */\n getRawCount?<T extends K>(table: T): Promise<number>;\n has<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;\n /**\n * Optional: prune all expired records across all tables in a single atomic operation.\n * When present, `pruneExpired()` delegates here instead of calling `pruneExpiredInTable` N times.\n */\n pruneAllExpired?(): Promise<Record<string, number>>;\n pruneExpiredInTable<T extends K>(table: T): Promise<number>;\n put<T extends K>(table: T, value: RecordOf<S, T>, ttl?: TtlMs): Promise<void>;\n putAll<T extends K>(table: T, values: RecordOf<S, T>[], ttl?: TtlMs): Promise<void>;\n};\n\n/** @internal */\nexport type BatchDeps<S extends AnySchema> = {\n notifyMutation: (table: keyof S & string) => void;\n validate: <K extends keyof S & string>(table: K, value: RecordOf<S, K>) => RecordOf<S, K>;\n};\n\n/** @internal */\nexport type BatchImpl<S extends AnySchema> = <K extends keyof S & string, R>(\n tables: readonly K[],\n fn: (tx: TransactionContext<S, K>) => Promise<R>,\n) => Promise<R>;\n\n/* -------------------- Helpers -------------------- */\n\nconst getTimestamp: () => number = typeof performance !== 'undefined' ? () => performance.now() : () => Date.now();\n\nfunction resolveTtl<S extends AnySchema, K extends keyof S & string>(\n schema: S,\n table: K,\n ttl?: TtlMs,\n): TtlMs | undefined {\n if (ttl !== undefined) assertTtlMs(ttl, 'put/putAll');\n\n return ttl ?? schema[table].defaultTtl;\n}\n\nfunction verifyKey<S extends AnySchema, K extends keyof S & string>(\n schema: S,\n table: K,\n expected: KeyOf<S, K>,\n value: RecordOf<S, K>,\n op: string,\n): void {\n const actual = getRecordKey(schema, table, value);\n\n if (actual !== expected) {\n throw new VaultError(\n `${op}: key field \"${schema[table].key}\" must be \"${String(expected)}\" but got \"${String(actual)}\" in table \"${table}\"`,\n );\n }\n}\n\nfunction getManyWithFallback<S extends AnySchema, K extends keyof S & string>(\n core: Pick<StorageBackend<S, K>, 'get' | 'getMany'>,\n table: K,\n keys: KeyOf<S, K>[],\n): Promise<Array<RecordOf<S, K> | undefined>> {\n if (core.getMany) return core.getMany(table, keys);\n\n return Promise.all(keys.map((k) => core.get(table, k)));\n}\n\nfunction buildQueryCtx<S extends AnySchema, K extends keyof S & string>(\n table: K,\n core: Pick<StorageBackend<S, K>, 'deleteMany' | 'getAll' | 'getByIndexRange' | 'getByKeyRange'>,\n schema: S,\n onMutate: (t: K) => void,\n): QueryContext<RecordOf<S, K>> {\n const entry = schema[table];\n const keyField = entry.key;\n const indexedFields = entry.indexes ? new Set(entry.indexes) : undefined;\n\n const deleteManyFn = async (records: RecordOf<S, K>[]): Promise<number> => {\n if (records.length === 0) return 0;\n\n const keys = records.map((r) => getRecordKey(schema, table, r));\n const deleted = await core.deleteMany(table, keys);\n\n if (deleted > 0) onMutate(table);\n\n return deleted;\n };\n\n return {\n deleteMany: deleteManyFn,\n getIndexRange: core.getByIndexRange\n ? (field, range) => core.getByIndexRange!(table, field, range) as Promise<RecordOf<S, K>[]>\n : undefined,\n getRange: core.getByKeyRange\n ? (range) => core.getByKeyRange!(table, range) as Promise<RecordOf<S, K>[]>\n : undefined,\n indexedFields,\n keyField,\n source: () => core.getAll(table),\n };\n}\n\n/* -------------------- buildTxContext -------------------- */\n\nexport function buildTxContext<S extends AnySchema, K extends keyof S & string>(\n schema: S,\n core: StorageBackend<S, K>,\n onMutate: (table: K) => void,\n validate?: <T extends K>(table: T, value: RecordOf<S, T>) => RecordOf<S, T>,\n scope?: ReadonlySet<string>,\n): TransactionContext<S, K> {\n const applyValidation = validate ?? ((_: K, v: RecordOf<S, K>) => v);\n\n const checkScope = scope\n ? (t: K): void => {\n if (!scope.has(t)) {\n throw new VaultScopeError(`table \"${t}\" is not part of this batch scope`);\n }\n }\n : (_t: K): void => {};\n\n return {\n async clear(table) {\n checkScope(table);\n\n const live = await core.count(table);\n\n if (live === 0) return;\n\n await core.clear(table);\n onMutate(table);\n },\n async count(table) {\n checkScope(table);\n\n return core.count(table);\n },\n async delete(table, key) {\n checkScope(table);\n\n const deleted = await core.delete(table, key);\n\n if (deleted) onMutate(table);\n\n return deleted;\n },\n async deleteMany(table, keys) {\n checkScope(table);\n\n const deleted = await core.deleteMany(table, keys);\n\n if (deleted > 0) onMutate(table);\n\n return deleted;\n },\n async entries(table) {\n checkScope(table);\n\n const records = await core.getAll(table);\n const keyField = schema[table].key;\n\n return records.map((r) => [(r as Record<string, unknown>)[keyField] as KeyOf<S, typeof table>, r]);\n },\n async get(table, key) {\n checkScope(table);\n\n return core.get(table, key);\n },\n async getAll(table) {\n checkScope(table);\n\n return core.getAll(table);\n },\n async getMany(table, keys) {\n checkScope(table);\n\n return getManyWithFallback(core, table, keys);\n },\n async getOrDefault(table, key, defaultFn, ttl) {\n checkScope(table);\n\n const existing = await core.get(table, key);\n\n if (existing !== undefined) return existing;\n\n const value = defaultFn();\n\n verifyKey(schema, table, key, value, 'getOrDefault: defaultFn()');\n await core.put(table, applyValidation(table, value), resolveTtl(schema, table, ttl));\n onMutate(table);\n\n return value;\n },\n async has(table, key) {\n checkScope(table);\n\n return core.has(table, key);\n },\n async isEmpty(table) {\n checkScope(table);\n\n return (await core.count(table)) === 0;\n },\n async keys(table, filter) {\n checkScope(table);\n\n if (filter) {\n const records = await core.getAll(table);\n const keyField = schema[table].key;\n\n return records.filter(filter).map((r) => (r as Record<string, unknown>)[keyField] as KeyOf<S, typeof table>);\n }\n\n // R1: prefer native getAllKeys when the backend supports it (e.g. IDB store.getAllKeys).\n if (core.getAllKeys) {\n return core.getAllKeys(table);\n }\n\n const records = await core.getAll(table);\n const keyField = schema[table].key;\n\n return records.map((r) => (r as Record<string, unknown>)[keyField] as KeyOf<S, typeof table>);\n },\n async put(table, value, ttl) {\n checkScope(table);\n\n await core.put(table, applyValidation(table, value), resolveTtl(schema, table, ttl));\n onMutate(table);\n },\n async putAll(table, values, ttl) {\n checkScope(table);\n\n if (values.length === 0) return;\n\n const toWrite = values.map((v) => applyValidation(table, v));\n\n await core.putAll(table, toWrite, resolveTtl(schema, table, ttl));\n onMutate(table);\n },\n query(table) {\n checkScope(table);\n\n return createQueryBuilder(buildQueryCtx(table, core, schema, onMutate));\n },\n async update(table, key, changes, ttl) {\n checkScope(table);\n\n const current = await core.get(table, key);\n\n if (!current) return undefined;\n\n const merged = { ...current, ...changes } as RecordOf<S, typeof table>;\n\n verifyKey(schema, table, key, merged, 'update');\n await core.put(table, applyValidation(table, merged), resolveTtl(schema, table, ttl));\n onMutate(table);\n\n return merged;\n },\n async upsert(table, key, fn, ttl) {\n checkScope(table);\n\n const existing = await core.get(table, key);\n const value = fn(existing);\n\n verifyKey(schema, table, key, value, 'upsert: fn()');\n await core.put(table, applyValidation(table, value), resolveTtl(schema, table, ttl));\n onMutate(table);\n\n return value;\n },\n };\n}\n\n/* -------------------- buildAdapterOps -------------------- */\n\nexport function buildAdapterOps<S extends AnySchema>(\n schema: S,\n core: StorageBackend<S>,\n options?: BaseAdapterOptions<S> & {\n buildBatch?: (deps: BatchDeps<S>) => BatchImpl<S>;\n onCrossTabMessage?: (notify: (table: keyof S & string) => void) => (() => void) | undefined;\n onMutation?: (table: keyof S & string) => void;\n },\n): Adapter<S> {\n const { logger, onMetrics, signals, validators } = options ?? {};\n\n const observers = createObserverHub<S>(\n (table) => core.getAll(table),\n logger\n ? (err) =>\n logger.error(err instanceof Error ? err : new Error(String(err)), '[vault] observer notification failed')\n : undefined,\n );\n\n // Per-table live-count cache. Populated lazily on first count() call.\n // Invalidated on every mutation via notifyMutation.\n const countCache = new Map<string, number>();\n\n const getCachedCount = async (table: keyof S & string): Promise<number> => {\n if (countCache.has(table)) return countCache.get(table)!;\n\n const n = await core.count(table);\n\n countCache.set(table, n);\n\n return n;\n };\n\n const notifyMutation = (table: keyof S & string): void => {\n countCache.delete(table); // invalidate on any mutation\n observers.notify(table);\n options?.onMutation?.(table);\n };\n\n // Cross-tab notifications must NOT call onMutation (which would re-publish to BroadcastChannel,\n // creating an infinite loop). They only need to invalidate the count cache and notify observers.\n const notifyExternal = (table: keyof S & string): void => {\n countCache.delete(table);\n observers.notify(table);\n };\n\n // R6: Resolve optional backend capabilities once at construction — no per-call checks.\n const pruneAll = core.pruneAllExpired?.bind(core);\n const rawCountFn = core.getRawCount?.bind(core);\n\n const disconnectExternal = options?.onCrossTabMessage?.(notifyExternal) ?? undefined;\n\n if (signals) {\n for (const [tableName, signal] of Object.entries(signals)) {\n if (signal) {\n observers.observe(tableName as keyof S & string, (records) => {\n signal.update(() => records as RecordOf<S, keyof S>[]);\n });\n }\n }\n }\n\n const validate = <K extends keyof S & string>(table: K, value: RecordOf<S, K>): RecordOf<S, K> => {\n const validator = validators?.[table];\n\n if (!validator) return value;\n\n try {\n return validator.parse(value) as RecordOf<S, K>;\n } catch (err) {\n throw new VaultError(`validation failed for table \"${table}\"`, { cause: err });\n }\n };\n\n /* Timed wrapper — only wraps when onMetrics is present, otherwise zero overhead. */\n const timed = <T>(table: string, op: MetricsEvent['operation'], fn: () => Promise<T>): Promise<T> => {\n if (!onMetrics) return fn();\n\n const start = getTimestamp();\n\n return fn().finally(() => onMetrics({ duration: getTimestamp() - start, operation: op, table }));\n };\n\n const deferredBatch = async <K extends keyof S & string, R>(\n fn: (tx: TransactionContext<S, K>) => Promise<R>,\n scope: ReadonlySet<string>,\n ): Promise<R> => {\n const dirty = new Set<K>();\n const tx = buildTxContext<S, K>(schema, core as StorageBackend<S, K>, (t) => dirty.add(t), validate, scope);\n const result = await fn(tx);\n\n for (const t of dirty) {\n notifyMutation(t);\n }\n\n return result;\n };\n\n const nativeBatch = options?.buildBatch?.({ notifyMutation, validate });\n\n let warnedNonAtomic = false;\n\n const txCtx = buildTxContext<S, keyof S & string>(schema, core, notifyMutation, validate);\n\n const observeMany = createObserveMany<S>(observers);\n\n const disposeController = new AbortController();\n let disposed = false;\n\n const checkDisposed = (): void => {\n if (disposed) throw new VaultDisposedError();\n };\n\n const adapter: Adapter<S> = {\n async batch<K extends keyof S & string, R>(tables: readonly K[], fn: (tx: TransactionContext<S, K>) => Promise<R>) {\n checkDisposed();\n\n if (tables.length === 0) throw new VaultScopeError('batch requires at least one table');\n\n if (!nativeBatch && !warnedNonAtomic) {\n warnedNonAtomic = true;\n warn(\n 'batch() on this adapter is not atomic — concurrent mutations may interleave. Use createIndexedDB for atomic batches.',\n );\n }\n\n return timed('*', 'batch', () => (nativeBatch ? nativeBatch(tables, fn) : deferredBatch(fn, new Set(tables))));\n },\n\n async clear(table) {\n checkDisposed();\n await timed(table, 'clear', () => txCtx.clear(table));\n countCache.set(table, 0); // we know count is 0 after clear\n },\n\n async count(table) {\n checkDisposed();\n\n return timed(table, 'count', () => getCachedCount(table));\n },\n\n async debug() {\n checkDisposed();\n\n const tableNames = Object.keys(schema) as Array<keyof S & string>;\n const entries = await Promise.all(\n tableNames.map(async (name) => {\n // getRawCount must be called before core.count() because count() has\n // lazy-eviction side effects (deletes expired entries from the store).\n const raw = rawCountFn ? await rawCountFn(name) : undefined;\n const live = await core.count(name);\n\n // Warm the count cache so subsequent adapter.count() calls don't re-hit the backend.\n countCache.set(name, live);\n\n const expiredCount = raw !== undefined ? Math.max(0, raw - live) : 0;\n\n return { expiredCount, name, recordCount: live };\n }),\n );\n\n return { tables: entries } as DebugInfo<S>;\n },\n\n async delete(table, key) {\n checkDisposed();\n\n return timed(table, 'delete', () => txCtx.delete(table, key));\n // count cache invalidated by notifyMutation inside txCtx.delete\n },\n\n async deleteMany(table, keys) {\n checkDisposed();\n\n return timed(table, 'deleteMany', () => txCtx.deleteMany(table, keys));\n // count cache invalidated by notifyMutation inside txCtx.deleteMany\n },\n\n get disposalSignal(): AbortSignal {\n return disposeController.signal;\n },\n\n async dispose() {\n if (disposed) return;\n\n disposed = true;\n disposeController.abort();\n disconnectExternal?.();\n observers.dispose();\n await core.dispose?.();\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n async entries(table) {\n checkDisposed();\n\n return timed(table, 'entries', () => txCtx.entries(table));\n },\n\n async get(table, key) {\n checkDisposed();\n\n return timed(table, 'get', () => txCtx.get(table, key));\n },\n\n async getAll(table) {\n checkDisposed();\n\n return timed(table, 'getAll', () => txCtx.getAll(table));\n },\n\n async getMany(table, keys) {\n checkDisposed();\n\n return timed(table, 'getMany', () => txCtx.getMany(table, keys));\n },\n\n async getOrDefault(table, key, defaultFn, ttl) {\n checkDisposed();\n\n return timed(table, 'getOrDefault', () => txCtx.getOrDefault(table, key, defaultFn, ttl));\n },\n\n async has(table, key) {\n checkDisposed();\n\n return timed(table, 'has', () => txCtx.has(table, key));\n },\n\n async isEmpty(table) {\n checkDisposed();\n\n return timed(table, 'isEmpty', async () => (await getCachedCount(table)) === 0);\n },\n\n async keys(table, filter) {\n checkDisposed();\n\n return timed(table, 'keys', () => txCtx.keys(table, filter));\n },\n\n observe(table, listener, opts) {\n checkDisposed();\n\n return observers.observe(table, listener, opts);\n },\n\n observeMany: (tables, listener, opts) => {\n checkDisposed();\n\n return observeMany(tables, listener, opts);\n },\n\n async pruneExpired(tables?: readonly (keyof S & string)[]) {\n checkDisposed();\n\n const tableNames = tables ?? (Object.keys(schema) as Array<keyof S & string>);\n\n if (!tables && pruneAll) {\n const result = await pruneAll();\n\n for (const [name, count] of Object.entries(result)) {\n if (count > 0) countCache.delete(name);\n }\n\n return result as { [K in keyof S & string]: number };\n }\n\n const pairs = await Promise.all(\n tableNames.map(async (name) => {\n const pruned = await core.pruneExpiredInTable(name);\n\n if (pruned > 0) countCache.delete(name);\n\n return [name, pruned] as const;\n }),\n );\n\n return Object.fromEntries(pairs) as { [K in keyof S & string]: number };\n },\n\n async put(table, value, ttl) {\n checkDisposed();\n await timed(table, 'put', () => txCtx.put(table, value, ttl));\n // count cache invalidated via notifyMutation inside txCtx.put\n },\n\n async putAll(table, values, ttl) {\n checkDisposed();\n await timed(table, 'putAll', () => txCtx.putAll(table, values, ttl));\n // count cache invalidated via notifyMutation inside txCtx.putAll\n },\n\n query(table) {\n checkDisposed();\n\n const ctx = buildQueryCtx(table, core, schema, notifyMutation);\n\n if (onMetrics) {\n return createQueryBuilder({\n ...ctx,\n deleteMany: ctx.deleteMany\n ? (records) => timed(table, 'queryDelete', () => ctx.deleteMany!(records))\n : undefined,\n source: () => timed(table, 'query', () => core.getAll(table)),\n });\n }\n\n return createQueryBuilder(ctx);\n },\n\n async [Symbol.asyncDispose]() {\n await adapter.dispose();\n },\n\n async update(table, key, changes, ttl) {\n checkDisposed();\n\n return timed(table, 'update', () => txCtx.update(table, key, changes, ttl));\n },\n\n async upsert(table, key, fn, ttl) {\n checkDisposed();\n\n return timed(table, 'upsert', () => txCtx.upsert(table, key, fn, ttl));\n },\n\n watch(table, options) {\n checkDisposed();\n\n return createWatchIterable<RecordOf<S, typeof table>>(\n (listener) => observers.observe(table, listener),\n options?.mode ?? 'latest',\n options?.signal,\n );\n },\n };\n\n return adapter;\n}\n","import type {\n Adapter,\n AnySchema,\n BaseAdapterOptions,\n KeyOf,\n MigrationFn,\n RecordOf,\n TransactionContext,\n TtlMs,\n} from '../types';\n\nimport { buildAdapterOps, buildTxContext, type StorageBackend } from '../adapter-core';\nimport { VaultDisposedError, VaultError, VaultMigrationError } from '../errors';\nimport { getRecordKey } from '../internal';\nimport { type NativeRange } from '../query';\nimport { defaultCodec, isExpired, type VaultCodec } from '../ttl';\n\nfunction idbReq<R>(request: IDBRequest<R>): Promise<R> {\n return new Promise<R>((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error ?? new VaultError('IndexedDB request failed'));\n });\n}\n\nfunction wrapTxError(scope: string, message: string, cause: unknown): VaultError {\n const causeMessage = cause instanceof Error && cause.message ? `: ${cause.message}` : '';\n\n return new VaultError(`${message} on \"${scope}\"${causeMessage}`, { cause });\n}\n\nfunction runIdbTx<T>(tx: IDBTransaction, scope: string, work: () => Promise<T>): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n let result: T | undefined;\n let callbackError: unknown;\n\n Promise.resolve()\n .then(work)\n .then((value) => {\n result = value;\n })\n .catch((error) => {\n callbackError = error;\n\n try {\n tx.abort();\n } catch {\n /* ignore */\n }\n });\n\n const rejectWithCallbackError = (fallbackCause: unknown, message = 'transaction failed'): void => {\n if (callbackError instanceof Error) {\n reject(callbackError);\n } else {\n reject(wrapTxError(scope, message, callbackError ?? fallbackCause));\n }\n };\n\n tx.oncomplete = () => {\n if (callbackError) {\n rejectWithCallbackError(undefined);\n\n return;\n }\n\n resolve(result as T);\n };\n tx.onerror = () => reject(wrapTxError(scope, 'transaction error', tx.error));\n tx.onabort = () => rejectWithCallbackError(tx.error, 'transaction aborted');\n });\n}\n\nasync function getAllFromStore<T extends Record<string, unknown>>(\n store: IDBObjectStore,\n decode: (raw: unknown) => T | undefined,\n): Promise<T[]> {\n const rawRecords = await idbReq<unknown[]>(store.getAll());\n const records: T[] = [];\n\n for (const raw of rawRecords) {\n const value = decode(raw);\n\n if (value !== undefined) records.push(value);\n }\n\n return records;\n}\n\nasync function getAllFromStoreByIndex<T extends Record<string, unknown>>(\n store: IDBObjectStore,\n indexName: string,\n range: NativeRange,\n decode: (raw: unknown) => T | undefined,\n): Promise<T[]> {\n const index = store.index(indexName);\n const idbRange =\n range.type === 'eq'\n ? IDBKeyRange.only(range.value as IDBValidKey)\n : range.type === 'between'\n ? IDBKeyRange.bound(range.lower as IDBValidKey, range.upper as IDBValidKey)\n : IDBKeyRange.bound(range.prefix, range.prefix + '\\uffff');\n const rawRecords = await idbReq<unknown[]>(index.getAll(idbRange));\n const records: T[] = [];\n\n for (const raw of rawRecords) {\n const value = decode(raw);\n\n if (value !== undefined) records.push(value);\n }\n\n return records;\n}\n\nasync function getAllFromStoreWithRange<T extends Record<string, unknown>>(\n store: IDBObjectStore,\n range: NativeRange,\n decode: (raw: unknown) => T | undefined,\n): Promise<T[]> {\n if (range.type === 'eq') {\n const record = await storeGet<T>(store, range.value as IDBValidKey, decode);\n\n return record !== undefined ? [record] : [];\n }\n\n const idbRange =\n range.type === 'between'\n ? IDBKeyRange.bound(range.lower as IDBValidKey, range.upper as IDBValidKey)\n : // 'starts': bound from prefix to prefix\\uffff (covers all BMP strings starting with prefix)\n IDBKeyRange.bound(range.prefix, range.prefix + '\\uffff');\n\n const rawRecords = await idbReq<unknown[]>(store.getAll(idbRange));\n const records: T[] = [];\n\n for (const raw of rawRecords) {\n const value = decode(raw);\n\n if (value !== undefined) records.push(value);\n }\n\n return records;\n}\n\nasync function storeGet<T extends Record<string, unknown>>(\n store: IDBObjectStore,\n key: IDBValidKey,\n decode: (raw: unknown) => T | undefined,\n): Promise<T | undefined> {\n const raw = await idbReq<unknown>(store.get(key));\n\n if (raw == null) return undefined;\n\n return decode(raw);\n}\n\nasync function storeHas<T extends Record<string, unknown>>(\n store: IDBObjectStore,\n key: IDBValidKey,\n decode: (raw: unknown) => T | undefined,\n): Promise<boolean> {\n return (await storeGet<T>(store, key, decode)) !== undefined;\n}\n\nasync function storeDelete<T extends Record<string, unknown>>(\n store: IDBObjectStore,\n key: IDBValidKey,\n decode: (raw: unknown) => T | undefined,\n): Promise<boolean> {\n const live = await storeHas<T>(store, key, decode);\n\n await idbReq(store.delete(key));\n\n return live;\n}\n\nasync function storeDeleteMany<T extends Record<string, unknown>>(\n store: IDBObjectStore,\n keys: IDBValidKey[],\n decode: (raw: unknown) => T | undefined,\n): Promise<number> {\n if (keys.length === 0) return 0;\n\n const results = await Promise.all(keys.map((k) => storeDelete<T>(store, k, decode)));\n\n return results.filter(Boolean).length;\n}\n\nasync function storePutAt<T>(\n store: IDBObjectStore,\n key: IDBValidKey,\n value: T,\n encode: (v: T, ttl?: TtlMs) => unknown,\n ttl?: TtlMs,\n): Promise<void> {\n await idbReq(store.put(encode(value, ttl), key));\n}\n\nfunction pruneExpiredInStore(store: IDBObjectStore, codec: VaultCodec): Promise<number> {\n return new Promise<number>((resolve, reject) => {\n let deleted = 0;\n const request = store.openCursor();\n\n request.onerror = () => reject(request.error ?? new VaultError('IndexedDB cursor failed during prune'));\n request.onsuccess = () => {\n const cursor = request.result;\n\n if (!cursor) {\n resolve(deleted);\n\n return;\n }\n\n const stored = codec.decode(cursor.value as unknown);\n\n if (!stored || isExpired(stored.expiresAt)) {\n cursor.delete();\n deleted += 1;\n }\n\n cursor.continue();\n };\n });\n}\n\n/**\n * Cursor state machine — a single discriminated union replaces five boolean/nullable variables.\n * Transitions: idle → waiting (next() before cursor fires) | buffered (cursor fires first) | done | error\n */\ntype CursorState<T> =\n | { type: 'idle' }\n | { reject: (e: unknown) => void; resolve: (r: IteratorResult<T>) => void; type: 'waiting' }\n | { result: IteratorResult<T>; type: 'buffered' }\n | { error: unknown; type: 'error' }\n | { type: 'done' };\n\n/**\n * F1: True cursor-based iteration for IndexedDB.\n * Yields live records one-by-one using an IDB cursor, avoiding materializing the full table.\n * This is memory-efficient for large tables — the cursor walks the store incrementally.\n *\n * Design: the cursor is opened *synchronously* in [Symbol.asyncIterator]() so that event\n * handlers are wired immediately (no queueMicrotask races). The cursor is advanced *eagerly*\n * before yielding — this keeps the IDB readonly transaction alive between consumer awaits,\n * because IDB auto-commits when there are no pending requests.\n */\nfunction iterateStoreWithCursor<T extends Record<string, unknown>>(\n store: IDBObjectStore,\n decode: (raw: unknown) => T | undefined,\n): AsyncIterable<T> {\n return {\n [Symbol.asyncIterator](): AsyncIterator<T> {\n const cursorRequest = store.openCursor();\n let state: CursorState<T> = { type: 'idle' };\n\n const deliver = (result: IteratorResult<T>): void => {\n if (state.type === 'waiting') {\n const { resolve } = state;\n\n state = result.done ? { type: 'done' } : { type: 'idle' };\n resolve(result);\n } else {\n state = { result, type: 'buffered' };\n }\n };\n\n cursorRequest.onerror = () => {\n const err = cursorRequest.error ?? new VaultError('IndexedDB cursor iteration failed');\n\n if (state.type === 'waiting') {\n const { reject } = state;\n\n state = { type: 'done' };\n reject(err);\n } else {\n state = { error: err, type: 'error' };\n }\n };\n\n cursorRequest.onsuccess = () => {\n const cursor = cursorRequest.result;\n\n if (!cursor) {\n deliver({ done: true, value: undefined });\n\n return;\n }\n\n const value = decode(cursor.value as unknown);\n\n // Advance eagerly BEFORE yielding to keep the IDB transaction alive.\n cursor.continue();\n\n if (value !== undefined) deliver({ done: false, value });\n };\n\n return {\n next(): Promise<IteratorResult<T>> {\n if (state.type === 'error') {\n const { error } = state;\n\n state = { type: 'done' };\n\n return Promise.reject(error);\n }\n\n if (state.type === 'buffered') {\n const { result } = state;\n\n state = result.done ? { type: 'done' } : { type: 'idle' };\n\n return Promise.resolve(result);\n }\n\n if (state.type === 'done') return Promise.resolve({ done: true, value: undefined });\n\n return new Promise<IteratorResult<T>>((resolve, reject) => {\n state = { reject, resolve, type: 'waiting' };\n });\n },\n\n return(value?: unknown): Promise<IteratorResult<T>> {\n if (state.type === 'waiting') state.resolve({ done: true, value: undefined });\n\n state = { type: 'done' };\n\n return Promise.resolve({ done: true, value });\n },\n\n throw(err?: unknown): Promise<IteratorResult<T>> {\n if (state.type === 'waiting') state.reject(err);\n\n state = { type: 'done' };\n\n return Promise.reject(err);\n },\n };\n },\n };\n}\n\n/**\n * R3: Extracted IDB batch core — builds a StorageBackend that operates within\n * an existing IDBTransaction, shared by all tables in the batch.\n * Eliminates the duplicated `txCore` block that was previously inlined in `idbBatch`.\n */\nfunction buildIdbBatchCore<S extends AnySchema, K extends keyof S & string>(\n schema: S,\n idbTx: IDBTransaction,\n decode: <T extends Record<string, unknown>>(raw: unknown) => T | undefined,\n encode: <T>(value: T, ttl?: TtlMs) => unknown,\n): StorageBackend<S, K> {\n const storeOf = (table: K): IDBObjectStore => idbTx.objectStore(table);\n\n return {\n clear: async (table) => {\n await idbReq(storeOf(table).clear());\n },\n count: async (table) => {\n // Must inspect each stored record to exclude TTL-expired entries.\n // Individual put() calls can attach a TTL even when the schema has no defaultTtl,\n // so schema[table].defaultTtl being absent does not guarantee a clean count.\n // This matches the top-level core.count() behaviour in the IDB adapter.\n const all = await idbReq<unknown[]>(storeOf(table).getAll());\n\n return all.filter((r) => decode(r) !== undefined).length;\n },\n delete: (table, key) => storeDelete<RecordOf<S, K>>(storeOf(table), key as IDBValidKey, decode),\n deleteMany: (table, keys) => storeDeleteMany<RecordOf<S, K>>(storeOf(table), keys as IDBValidKey[], decode),\n get: (table, key) => storeGet<RecordOf<S, typeof table>>(storeOf(table), key as IDBValidKey, decode),\n getAll: (table) => getAllFromStore<RecordOf<S, typeof table>>(storeOf(table), decode),\n getByIndexRange: (table, field, range) =>\n getAllFromStoreByIndex<RecordOf<S, typeof table>>(storeOf(table), field, range, decode),\n getByKeyRange: (table, range) => getAllFromStoreWithRange<RecordOf<S, typeof table>>(storeOf(table), range, decode),\n getMany: (table, keys) =>\n Promise.all(keys.map((k) => storeGet<RecordOf<S, typeof table>>(storeOf(table), k as IDBValidKey, decode))),\n has: (table, key) => storeHas<RecordOf<S, typeof table>>(storeOf(table), key as IDBValidKey, decode),\n pruneExpiredInTable: (table) => pruneExpiredInStore(storeOf(table), { decode, encode }),\n put(table, value, ttl) {\n return storePutAt(storeOf(table), getRecordKey(schema, table, value) as IDBValidKey, value, encode, ttl);\n },\n putAll(table, values, ttl) {\n return Promise.all(\n values.map((v) => storePutAt(storeOf(table), getRecordKey(schema, table, v) as IDBValidKey, v, encode, ttl)),\n ).then(() => undefined);\n },\n };\n}\n\ntype IndexedDbOptions<S extends AnySchema> = BaseAdapterOptions<S> & {\n migrate?: MigrationFn;\n name: string;\n /** Schema version. Must be a positive integer. Increment when adding tables or changing the schema, then provide `migrate`. Defaults to 1. */\n version?: number;\n};\n\nexport type IndexedDbAdapter<S extends AnySchema> = Adapter<S> & {\n iterate<K extends keyof S>(table: K): AsyncIterable<RecordOf<S, K>>;\n};\n\nexport function createIndexedDB<S extends AnySchema>(options: IndexedDbOptions<S>): IndexedDbAdapter<S> {\n const {\n codec: userCodec = defaultCodec,\n logger,\n migrate,\n name,\n onMetrics,\n schema,\n signals,\n validators,\n version = 1,\n } = options;\n\n if (!Number.isInteger(version) || version < 1) {\n throw new VaultError(`createIndexedDB: version must be a positive integer, got ${String(version)}`);\n }\n\n // F4: Codec-bound encode/decode used throughout the IDB adapter.\n const decode = <T extends Record<string, unknown>>(raw: unknown): T | undefined => {\n const stored = userCodec.decode<T>(raw);\n\n if (!stored) return undefined;\n\n if (isExpired(stored.expiresAt)) return undefined;\n\n return stored.value;\n };\n\n const encode = <T>(value: T, ttl?: TtlMs): unknown => {\n const expiresAt = ttl !== undefined ? Date.now() + ttl : undefined;\n\n return userCodec.encode(value, expiresAt);\n };\n\n const channel = typeof BroadcastChannel !== 'undefined' ? new BroadcastChannel(`vault:${name}`) : undefined;\n\n if (!channel && logger) {\n logger.error(`[vault] BroadcastChannel unavailable — cross-tab sync disabled for \"${name}\"`);\n }\n\n let db: IDBDatabase | null = null;\n let connectPromise: Promise<void> | null = null;\n let disposed = false;\n\n const createObjectStores = (target: IDBDatabase, tx: IDBTransaction): void => {\n for (const [tableName, entry] of Object.entries(schema)) {\n let store: IDBObjectStore;\n\n if (!target.objectStoreNames.contains(tableName)) {\n store = target.createObjectStore(tableName);\n } else {\n store = tx.objectStore(tableName);\n }\n\n // F5: Create secondary indexes for fields declared via .index() on the table() builder.\n // Stored format is { value: T, expiresAt?: number } so IDB keyPath is `value.<field>`.\n const indexes = (entry as { indexes?: readonly string[] }).indexes ?? [];\n\n for (const field of indexes) {\n if (!store.indexNames.contains(field)) {\n store.createIndex(field, `value.${field}`);\n }\n }\n }\n };\n\n const connect = async (): Promise<void> => {\n if (!connectPromise) {\n connectPromise = new Promise((resolve, reject) => {\n const request = indexedDB.open(name, version);\n\n request.onupgradeneeded = (event) => {\n const target = request.result;\n const tx = request.transaction!;\n\n if (migrate) {\n try {\n migrate({\n db: target,\n newVersion: (event as IDBVersionChangeEvent).newVersion ?? null,\n oldVersion: event.oldVersion,\n tx,\n });\n } catch (error) {\n try {\n tx.abort();\n } catch {\n /* ignore */\n }\n\n reject(new VaultMigrationError(`migration failed for \"${name}\"`, { cause: error }));\n\n return;\n }\n }\n\n createObjectStores(target, tx);\n };\n\n request.onsuccess = () => {\n if (disposed) {\n request.result.close();\n resolve();\n\n return;\n }\n\n const connection = request.result;\n\n connection.onversionchange = () => {\n connection.close();\n db = null;\n connectPromise = null;\n };\n\n db = connection;\n resolve();\n };\n request.onerror = () => {\n connectPromise = null;\n reject(new VaultError(`failed to open \"${name}\"`, { cause: request.error }));\n };\n });\n }\n\n return connectPromise;\n };\n\n const withStore = async <T>(\n table: keyof S,\n mode: 'readonly' | 'readwrite',\n fn: (store: IDBObjectStore) => Promise<T>,\n ): Promise<T> => {\n if (disposed) throw new VaultDisposedError(`\"${name}\" is disposed`);\n\n if (!db) await connect();\n\n if (!db) throw new VaultDisposedError(`\"${name}\" is disposed`);\n\n const tableName = String(table);\n const tx = db.transaction(tableName, mode);\n\n return runIdbTx(tx, `${name}/${tableName}`, () => fn(tx.objectStore(tableName)));\n };\n\n const requireDb = async (): Promise<IDBDatabase> => {\n if (disposed) throw new VaultDisposedError(`\"${name}\" is disposed`);\n\n if (!db) await connect();\n\n if (!db) throw new VaultDisposedError(`\"${name}\" is disposed`);\n\n return db;\n };\n\n const publish = <K extends keyof S>(table: K): void => {\n channel?.postMessage({ table: String(table) });\n };\n\n const core: StorageBackend<S> = {\n clear: (table) => withStore(table, 'readwrite', (s) => idbReq(s.clear()).then(() => undefined)),\n\n count: (table) =>\n withStore(table, 'readonly', async (s) => {\n // Must inspect each stored record to exclude TTL-expired entries.\n // Individual put() calls can attach a TTL even when the schema has no defaultTtl,\n // so schema[table].defaultTtl being absent does not guarantee a clean count.\n const all = await idbReq<unknown[]>(s.getAll());\n\n return all.filter((r) => decode(r) !== undefined).length;\n }),\n\n delete: (table, key) =>\n withStore(table, 'readwrite', (s) => storeDelete<RecordOf<S, typeof table>>(s, key as IDBValidKey, decode)),\n\n deleteMany: (table, keys) =>\n keys.length === 0\n ? Promise.resolve(0)\n : withStore(table, 'readwrite', (s) =>\n storeDeleteMany<RecordOf<S, typeof table>>(s, keys as IDBValidKey[], decode),\n ),\n\n async dispose(): Promise<void> {\n disposed = true;\n channel?.close();\n\n // F7: Wait for any in-progress connect before closing the DB to avoid\n // \"database connection is closing\" errors on in-flight requests.\n if (connectPromise) await connectPromise.catch(() => {});\n\n db?.close();\n db = null;\n connectPromise = null;\n },\n\n get: (table, key) =>\n withStore(table, 'readonly', (s) => storeGet<RecordOf<S, typeof table>>(s, key as IDBValidKey, decode)),\n\n getAll: (table) => withStore(table, 'readonly', (s) => getAllFromStore<RecordOf<S, typeof table>>(s, decode)),\n\n // Ad-hoc per-put TTLs can be attached regardless of schema-level defaultTtl (same caveat as\n // count()), so we cannot take the O(1) store.getAllKeys() shortcut unconditionally — every\n // record must be decoded to exclude TTL-expired entries correctly.\n getAllKeys: (table) =>\n withStore(table, 'readonly', async (s) => {\n const records = await getAllFromStore<RecordOf<S, typeof table>>(s, decode);\n const keyField = schema[table].key;\n\n return records.map((r) => (r as Record<string, unknown>)[keyField] as KeyOf<S, typeof table>);\n }),\n\n getByIndexRange: (table, field, range) =>\n withStore(table, 'readonly', (s) => getAllFromStoreByIndex<RecordOf<S, typeof table>>(s, field, range, decode)),\n\n getByKeyRange: (table, range) =>\n withStore(table, 'readonly', (s) => getAllFromStoreWithRange<RecordOf<S, typeof table>>(s, range, decode)),\n\n getMany: (table, keys) =>\n keys.length === 0\n ? Promise.resolve([])\n : withStore(table, 'readonly', (s) =>\n Promise.all(keys.map((k) => storeGet<RecordOf<S, typeof table>>(s, k as IDBValidKey, decode))),\n ),\n\n getRawCount: (table) => withStore(table, 'readonly', (s) => idbReq(s.count())),\n\n has: (table, key) =>\n withStore(table, 'readonly', (s) => storeHas<RecordOf<S, typeof table>>(s, key as IDBValidKey, decode)),\n\n async pruneAllExpired() {\n const idb = await requireDb();\n const tableNames = Object.keys(schema);\n const tx = idb.transaction(tableNames, 'readwrite');\n const results = await runIdbTx(tx, `${name}/pruneAll`, () =>\n Promise.all(tableNames.map(async (t) => [t, await pruneExpiredInStore(tx.objectStore(t), userCodec)] as const)),\n );\n\n return Object.fromEntries(results);\n },\n\n pruneExpiredInTable: (table) => withStore(table, 'readwrite', (s) => pruneExpiredInStore(s, userCodec)),\n\n put(table, value, ttl) {\n const key = getRecordKey(schema, table, value) as IDBValidKey;\n\n return withStore(table, 'readwrite', (s) => storePutAt(s, key, value, encode, ttl));\n },\n\n putAll(table, values, ttl) {\n return withStore(table, 'readwrite', (s) =>\n Promise.all(\n values.map((v) => storePutAt(s, getRecordKey(schema, table, v) as IDBValidKey, v, encode, ttl)),\n ).then(() => undefined),\n );\n },\n };\n\n const idbBatch = async <K extends keyof S & string, R>(\n tables: readonly K[],\n fn: (tx: TransactionContext<S, K>) => Promise<R>,\n notifyMutation: (table: K) => void,\n validateFn: <T extends K>(table: T, value: RecordOf<S, T>) => RecordOf<S, T>,\n ): Promise<R> => {\n const idb = await requireDb();\n const idbTx = idb.transaction([...tables] as string[], 'readwrite');\n const dirtyTables = new Set<K>();\n\n const txCore = buildIdbBatchCore<S, K>(schema, idbTx, decode, encode);\n const scope = new Set<string>(tables);\n const tx = buildTxContext<S, K>(schema, txCore, (t) => dirtyTables.add(t), validateFn, scope);\n const result = await runIdbTx(idbTx, name, () => fn(tx));\n\n for (const table of dirtyTables) {\n notifyMutation(table);\n }\n\n return result;\n };\n\n const adapter = buildAdapterOps(schema, core, {\n buildBatch:\n ({ notifyMutation, validate: validateFn }) =>\n (tables, fn) =>\n idbBatch(tables, fn, notifyMutation, validateFn),\n logger,\n onCrossTabMessage(notify) {\n if (!channel) {\n return undefined;\n }\n\n channel.onmessage = (event: MessageEvent<{ table?: string }>) => {\n const tableName = event.data?.table;\n\n if (!tableName || !Object.hasOwn(schema, tableName)) return;\n\n notify(tableName as keyof S & string);\n };\n\n return () => {\n channel.onmessage = null;\n };\n },\n onMetrics,\n onMutation: publish,\n schema,\n signals,\n validators,\n });\n\n /**\n * F1: Attach cursor-based `iterate()` on top of the adapter.\n * Opens a dedicated readonly transaction per call and streams records via IDB cursor —\n * genuinely memory-efficient for large tables unlike the getAll()-then-yield pattern.\n */\n return {\n ...adapter,\n iterate<K extends keyof S>(table: K): AsyncIterable<RecordOf<S, K>> {\n if (disposed) throw new VaultDisposedError(`\"${name}\" is disposed`);\n\n // Each call opens a fresh transaction so iteration doesn't hold locks across awaits.\n // We need to ensure the DB is connected before opening the transaction.\n const getIterable = async (): Promise<AsyncIterable<RecordOf<S, K>>> => {\n if (!db) await connect();\n\n if (!db || disposed) throw new VaultDisposedError(`\"${name}\" is disposed`);\n\n const idb = db;\n const tx = idb.transaction(String(table), 'readonly');\n const store = tx.objectStore(String(table));\n\n return iterateStoreWithCursor<RecordOf<S, K>>(store, decode as (raw: unknown) => RecordOf<S, K> | undefined);\n };\n\n let inner: AsyncIterator<RecordOf<S, K>> | undefined;\n\n const initInner = (): Promise<AsyncIterator<RecordOf<S, K>>> =>\n getIterable().then((iterable) => {\n inner = iterable[Symbol.asyncIterator]();\n\n return inner;\n });\n\n return {\n [Symbol.asyncIterator](): AsyncIterator<RecordOf<S, K>> {\n return {\n next(): Promise<IteratorResult<RecordOf<S, K>>> {\n // Sync check avoids an extra Promise allocation on every iteration after the first.\n if (inner) return inner.next();\n\n return initInner().then((it) => it.next());\n },\n return(value?: unknown): Promise<IteratorResult<RecordOf<S, K>>> {\n if (inner) return inner.return?.(value) ?? Promise.resolve({ done: true, value });\n\n return Promise.resolve({ done: true, value });\n },\n throw(err?: unknown): Promise<IteratorResult<RecordOf<S, K>>> {\n if (inner) return inner.throw?.(err) ?? Promise.reject(err);\n\n return Promise.reject(err);\n },\n };\n },\n };\n },\n };\n}\n","import type { Adapter, AnySchema, BaseAdapterOptions, KeyOf, RecordOf, TtlMs } from '../types';\n\nimport { buildAdapterOps, type StorageBackend } from '../adapter-core';\nimport { VaultDisposedError } from '../errors';\nimport { getRecordKey } from '../internal';\nimport { defaultCodec, isExpired, type StoredRecord } from '../ttl';\n\ntype MemoryBroadcastMsg =\n | { table: string; type: 'clear' }\n | { key: string; table: string; type: 'delete' }\n | { keys: string[]; table: string; type: 'deleteMany' }\n | { key: string; stored: StoredRecord<unknown>; table: string; type: 'put' }\n | { entries: Array<{ key: string; stored: StoredRecord<unknown> }>; table: string; type: 'putAll' };\n\n/** Runtime type guard — rejects malformed or unknown BroadcastChannel messages. */\nfunction makeBroadcastMsgGuard(codec: { decode<T>(raw: unknown): (StoredRecord<T> & { value: T }) | undefined }) {\n return function isBroadcastMsg(data: unknown): data is MemoryBroadcastMsg {\n if (typeof data !== 'object' || data === null) return false;\n\n const d = data as Record<string, unknown>;\n\n if (typeof d['table'] !== 'string' || typeof d['type'] !== 'string') return false;\n\n // Guard against prototype-polluting table names from crafted cross-tab messages.\n if (d['table'] === '__proto__' || d['table'] === 'constructor' || d['table'] === 'prototype') return false;\n\n switch (d['type']) {\n case 'clear':\n return true;\n case 'delete':\n return typeof d['key'] === 'string';\n case 'deleteMany':\n return Array.isArray(d['keys']) && (d['keys'] as unknown[]).every((k) => typeof k === 'string');\n case 'put':\n return typeof d['key'] === 'string' && codec.decode(d['stored']) !== undefined;\n case 'putAll':\n return (\n Array.isArray(d['entries']) &&\n (d['entries'] as unknown[]).every(\n (e) =>\n typeof e === 'object' &&\n e !== null &&\n typeof (e as Record<string, unknown>)['key'] === 'string' &&\n codec.decode((e as Record<string, unknown>)['stored']) !== undefined,\n )\n );\n default:\n return false;\n }\n };\n}\n\ntype MemoryOptions<S extends AnySchema> = BaseAdapterOptions<S> & {\n /**\n * When provided and BroadcastChannel is available, enables cross-tab state sync.\n * All `createMemory` instances with the same `name` will replicate mutations to each other.\n */\n name?: string;\n};\n\nexport type MemoryAdapter<S extends AnySchema> = Adapter<S> & {\n iterate<K extends keyof S>(table: K): AsyncIterable<RecordOf<S, K>>;\n};\n\nexport function createMemory<S extends AnySchema>(options: MemoryOptions<S>): MemoryAdapter<S> {\n const { codec = defaultCodec, logger, name, onMetrics, schema, signals, validators } = options;\n const tables = new Map(Object.keys(schema).map((k) => [k, new Map<string, StoredRecord<unknown>>()]));\n const getTableStore = (table: string) => tables.get(table)!;\n const isBroadcastMsg = makeBroadcastMsgGuard(codec);\n\n const channel =\n name !== undefined && typeof BroadcastChannel !== 'undefined'\n ? new BroadcastChannel(`vault-memory:${name}`)\n : undefined;\n\n const core: StorageBackend<S> = {\n async clear<K extends keyof S & string>(table: K): Promise<void> {\n getTableStore(table).clear();\n channel?.postMessage({ table, type: 'clear' });\n },\n\n async count<K extends keyof S & string>(table: K): Promise<number> {\n const store = getTableStore(table);\n let liveCount = 0;\n const expiredKeys: string[] = [];\n\n for (const [key, raw] of store) {\n const decoded = codec.decode<RecordOf<S, K>>(raw);\n\n if (!decoded || isExpired(decoded.expiresAt)) {\n expiredKeys.push(key);\n } else {\n liveCount += 1;\n }\n }\n\n for (const key of expiredKeys) store.delete(key);\n\n return liveCount;\n },\n\n async delete<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<boolean> {\n const store = getTableStore(table);\n const raw = store.get(String(key));\n\n if (!raw) return false;\n\n const decoded = codec.decode<RecordOf<S, K>>(raw);\n const isLive = decoded !== undefined && !isExpired(decoded.expiresAt);\n\n store.delete(String(key));\n\n if (isLive) channel?.postMessage({ key: String(key), table, type: 'delete' });\n\n return isLive;\n },\n\n async deleteMany<K extends keyof S & string>(table: K, keys: KeyOf<S, K>[]): Promise<number> {\n const store = getTableStore(table);\n const deletedKeys: string[] = [];\n\n for (const key of keys) {\n const strKey = String(key);\n const raw = store.get(strKey);\n\n if (raw) {\n const decoded = codec.decode<RecordOf<S, K>>(raw);\n\n if (decoded !== undefined && !isExpired(decoded.expiresAt)) deletedKeys.push(strKey);\n\n store.delete(strKey);\n }\n }\n\n if (deletedKeys.length > 0) channel?.postMessage({ keys: deletedKeys, table, type: 'deleteMany' });\n\n return deletedKeys.length;\n },\n\n dispose: channel ? async () => channel.close() : undefined,\n\n async get<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<RecordOf<S, K> | undefined> {\n const store = getTableStore(table);\n const raw = store.get(String(key));\n\n if (!raw) return undefined;\n\n const decoded = codec.decode<RecordOf<S, K>>(raw);\n\n if (!decoded || isExpired(decoded.expiresAt)) {\n store.delete(String(key));\n\n return undefined;\n }\n\n return decoded.value;\n },\n\n async getAll<K extends keyof S & string>(table: K): Promise<RecordOf<S, K>[]> {\n const store = getTableStore(table);\n const records: RecordOf<S, K>[] = [];\n const expiredKeys: string[] = [];\n\n for (const [key, raw] of store) {\n const decoded = codec.decode<RecordOf<S, K>>(raw);\n\n if (!decoded || isExpired(decoded.expiresAt)) {\n expiredKeys.push(key);\n } else {\n records.push(decoded.value);\n }\n }\n\n for (const key of expiredKeys) store.delete(key);\n\n return records;\n },\n\n async getRawCount<K extends keyof S & string>(table: K): Promise<number> {\n return getTableStore(table).size;\n },\n\n async has<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<boolean> {\n const store = getTableStore(table);\n const raw = store.get(String(key));\n\n if (!raw) return false;\n\n const decoded = codec.decode<RecordOf<S, K>>(raw);\n const live = decoded !== undefined && !isExpired(decoded.expiresAt);\n\n if (!live) store.delete(String(key));\n\n return live;\n },\n\n async pruneExpiredInTable<K extends keyof S & string>(table: K): Promise<number> {\n const store = getTableStore(table);\n let pruned = 0;\n\n for (const [key, raw] of store) {\n const decoded = codec.decode<RecordOf<S, K>>(raw);\n\n if (!decoded || isExpired(decoded.expiresAt)) {\n store.delete(key);\n pruned += 1;\n }\n }\n\n return pruned;\n },\n\n async put<K extends keyof S & string>(table: K, value: RecordOf<S, K>, ttl?: TtlMs): Promise<void> {\n const key = String(getRecordKey(schema, table, value));\n const expiresAt = ttl !== undefined ? Date.now() + ttl : undefined;\n const stored = codec.encode(value, expiresAt) as StoredRecord<unknown>;\n\n getTableStore(table).set(key, stored);\n channel?.postMessage({ key, stored, table, type: 'put' });\n },\n\n async putAll<K extends keyof S & string>(table: K, values: RecordOf<S, K>[], ttl?: TtlMs): Promise<void> {\n const store = getTableStore(table);\n const entries: Array<{ key: string; stored: StoredRecord<unknown> }> = [];\n const expiresAt = ttl !== undefined ? Date.now() + ttl : undefined;\n\n for (const value of values) {\n const key = String(getRecordKey(schema, table, value));\n const stored = codec.encode(value, expiresAt) as StoredRecord<unknown>;\n\n store.set(key, stored);\n entries.push({ key, stored });\n }\n\n if (entries.length > 0) channel?.postMessage({ entries, table, type: 'putAll' });\n },\n };\n\n const adapter = buildAdapterOps(schema, core, {\n logger,\n onCrossTabMessage: channel\n ? (notify) => {\n channel.onmessage = (event: MessageEvent<unknown>) => {\n let msg: MemoryBroadcastMsg;\n\n try {\n if (!isBroadcastMsg(event.data)) return;\n\n msg = event.data;\n } catch {\n return;\n }\n\n const store = tables.get(msg.table);\n\n if (!store) return;\n\n const validator = validators?.[msg.table as keyof S];\n\n const keyField = schema[msg.table as keyof S & string]?.key;\n\n const applyStoredWithValidation = (key: string, stored: StoredRecord<unknown>): boolean => {\n try {\n const parsed = codec.decode(stored as unknown);\n\n if (!parsed) return false;\n\n const rawValue = validator ? validator.parse(parsed.value) : parsed.value;\n\n // Security: verify the record's own primary key matches the broadcast envelope key.\n // Prevents a rogue same-origin sender from inserting records with mismatched keys.\n if (keyField && String((rawValue as Record<string, unknown>)[keyField]) !== key) return false;\n\n const validated: StoredRecord<unknown> =\n parsed.expiresAt !== undefined\n ? { expiresAt: parsed.expiresAt, value: rawValue }\n : { value: rawValue };\n\n store.set(key, validated);\n\n return true;\n } catch {\n return false;\n }\n };\n\n switch (msg.type) {\n case 'clear':\n store.clear();\n notify(msg.table as keyof S & string);\n\n return;\n case 'delete':\n store.delete(msg.key);\n notify(msg.table as keyof S & string);\n\n return;\n case 'deleteMany':\n for (const key of msg.keys) store.delete(key);\n notify(msg.table as keyof S & string);\n\n return;\n case 'put':\n if (applyStoredWithValidation(msg.key, msg.stored)) {\n notify(msg.table as keyof S & string);\n }\n\n return;\n case 'putAll':\n {\n let anyApplied = false;\n\n for (const { key, stored } of msg.entries) {\n if (applyStoredWithValidation(key, stored)) anyApplied = true;\n }\n\n if (anyApplied) notify(msg.table as keyof S & string);\n }\n\n return;\n }\n };\n\n return () => {\n channel.onmessage = null;\n };\n }\n : undefined,\n onMetrics,\n schema,\n signals,\n validators,\n });\n\n return {\n ...adapter,\n iterate<K extends keyof S>(table: K): AsyncIterable<RecordOf<S, K>> {\n const store = getTableStore(String(table));\n\n return {\n async *[Symbol.asyncIterator]() {\n if (adapter.disposed) throw new VaultDisposedError();\n\n const expiredKeys: string[] = [];\n\n for (const [key, raw] of store) {\n const decoded = codec.decode<RecordOf<S, K>>(raw);\n\n if (!decoded || isExpired(decoded.expiresAt)) {\n expiredKeys.push(key);\n } else {\n yield decoded.value;\n }\n }\n\n for (const key of expiredKeys) store.delete(key);\n },\n };\n },\n };\n}\n","import type { Adapter, AnySchema, BaseAdapterOptions, KeyOf, RecordOf, TtlMs } from '../types';\n\nimport { buildAdapterOps, type StorageBackend } from '../adapter-core';\nimport { VaultError, VaultQuotaError } from '../errors';\nimport {\n decodeStorageTableFromKey,\n encodeDbPrefix,\n encodeStorageKey,\n encodeStorageTablePrefix,\n getRecordKey,\n} from '../internal';\nimport { defaultCodec, isExpired } from '../ttl';\n\n// Firefox historically threw 'NS_ERROR_DOM_QUOTA_REACHED'; modern browsers use the standard name.\nconst QUOTA_ERROR_NAMES = new Set(['QuotaExceededError', 'NS_ERROR_DOM_QUOTA_REACHED']);\n\ntype WebStorageOptions<S extends AnySchema> = BaseAdapterOptions<S> & {\n name: string;\n /**\n * Called when localStorage/sessionStorage quota is exceeded on a write.\n * Return `'ignore'` to silently drop the write, or `'throw'` (default) to rethrow the error.\n */\n onQuotaExceeded?: (table: keyof S, error: VaultQuotaError) => 'ignore' | 'throw';\n};\n\nfunction createWebStorageAdapter<S extends AnySchema>(\n options: WebStorageOptions<S> & {\n getStorage: () => Storage;\n storageLabel: string;\n },\n): Adapter<S> {\n const {\n codec = defaultCodec,\n getStorage,\n logger,\n name,\n onMetrics,\n onQuotaExceeded,\n schema,\n signals,\n storageLabel,\n validators,\n } = options;\n\n let resolvedStorage: Storage;\n\n try {\n resolvedStorage = getStorage();\n } catch (cause) {\n throw new VaultError(\n `${storageLabel} is not available in this environment (private browsing or sandboxed iframe?)`,\n {\n cause,\n },\n );\n }\n\n const storage = (): Storage => resolvedStorage;\n\n const prefixMap = new Map(Object.keys(schema).map((table) => [table, encodeStorageTablePrefix(name, table)]));\n const getPrefix = (table: string): string => {\n const cached = prefixMap.get(table);\n\n if (!cached) throw new VaultError(`table \"${table}\" not in schema`);\n\n return cached;\n };\n\n const writeItem = (table: keyof S, storageKey: string, value: unknown): void => {\n try {\n storage().setItem(storageKey, JSON.stringify(value));\n } catch (error) {\n if (error instanceof DOMException && QUOTA_ERROR_NAMES.has(error.name)) {\n const wrappedError = new VaultQuotaError(`${storageLabel} quota exceeded while writing record`, {\n cause: error,\n });\n\n if (onQuotaExceeded?.(table, wrappedError) === 'ignore') return;\n\n throw wrappedError;\n }\n\n throw error;\n }\n };\n\n // Per-instance registry of all storage keys owned by this adapter instance.\n // Populated once at construction; kept current by every mutation.\n const ownedKeys = new Set<string>();\n\n const initOwnedKeys = (): void => {\n const dbPrefix = encodeDbPrefix(name);\n\n for (let i = 0; i < resolvedStorage.length; i++) {\n const key = resolvedStorage.key(i);\n\n if (key !== null && key.startsWith(dbPrefix)) ownedKeys.add(key);\n }\n };\n\n initOwnedKeys();\n\n /**\n * Reads a single entry without side effects. Returns the live value,\n * or `undefined` if missing/expired/corrupt.\n *\n * Callers that want to evict stale entries must call `evict(storageKey)` explicitly.\n * This design avoids the `{ cleanup?: boolean }` flag that required callers to remember\n * to pass `{ cleanup: false }` inside loops.\n */\n const parseEntry = <T extends Record<string, unknown>>(storageKey: string): T | undefined => {\n const raw = storage().getItem(storageKey);\n\n if (!raw) return undefined;\n\n try {\n const stored = codec.decode<T>(JSON.parse(raw) as unknown);\n\n if (!stored || isExpired(stored.expiresAt)) return undefined;\n\n return stored.value;\n } catch {\n return undefined;\n }\n };\n\n /** Removes a stale/expired/corrupt entry from storage and the owned-keys registry. */\n const evict = (storageKey: string): void => {\n storage().removeItem(storageKey);\n ownedKeys.delete(storageKey);\n };\n\n const core: StorageBackend<S> = {\n async clear<K extends keyof S & string>(table: K): Promise<void> {\n const target = storage();\n const prefix = getPrefix(table);\n const toRemove: string[] = [];\n\n for (const key of ownedKeys) {\n if (key.startsWith(prefix)) toRemove.push(key);\n }\n\n for (const key of toRemove) {\n target.removeItem(key);\n ownedKeys.delete(key);\n }\n },\n\n async count<K extends keyof S & string>(table: K): Promise<number> {\n const prefix = getPrefix(table);\n const expiredKeys: string[] = [];\n let liveCount = 0;\n\n for (const storageKey of ownedKeys) {\n if (!storageKey.startsWith(prefix)) continue;\n\n const value = parseEntry<RecordOf<S, K>>(storageKey);\n\n if (value === undefined) {\n expiredKeys.push(storageKey);\n } else {\n liveCount += 1;\n }\n }\n\n for (const storageKey of expiredKeys) {\n evict(storageKey);\n }\n\n return liveCount;\n },\n\n async delete<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<boolean> {\n const storageKey = encodeStorageKey(name, table, String(key));\n const value = parseEntry<RecordOf<S, K>>(storageKey);\n\n if (value !== undefined) {\n evict(storageKey);\n\n return true;\n }\n\n if (ownedKeys.has(storageKey)) evict(storageKey);\n\n return false;\n },\n\n async deleteMany<K extends keyof S & string>(table: K, keys: KeyOf<S, K>[]): Promise<number> {\n let deleted = 0;\n\n for (const key of keys) {\n const storageKey = encodeStorageKey(name, table, String(key));\n const value = parseEntry<RecordOf<S, K>>(storageKey);\n\n if (value !== undefined) {\n evict(storageKey);\n deleted += 1;\n } else if (ownedKeys.has(storageKey)) {\n evict(storageKey);\n }\n }\n\n return deleted;\n },\n\n async get<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<RecordOf<S, K> | undefined> {\n const storageKey = encodeStorageKey(name, table, String(key));\n const value = parseEntry<RecordOf<S, K>>(storageKey);\n\n if (value === undefined && ownedKeys.has(storageKey)) evict(storageKey);\n\n return value;\n },\n\n async getAll<K extends keyof S & string>(table: K): Promise<RecordOf<S, K>[]> {\n const records: RecordOf<S, K>[] = [];\n const expiredKeys: string[] = [];\n const prefix = getPrefix(table);\n\n for (const storageKey of ownedKeys) {\n if (!storageKey.startsWith(prefix)) continue;\n\n const value = parseEntry<RecordOf<S, K>>(storageKey);\n\n if (value === undefined) {\n expiredKeys.push(storageKey);\n continue;\n }\n\n records.push(value);\n }\n\n for (const storageKey of expiredKeys) {\n evict(storageKey);\n }\n\n return records;\n },\n\n async getAllKeys<K extends keyof S & string>(table: K): Promise<KeyOf<S, K>[]> {\n const prefix = getPrefix(table);\n const keys: KeyOf<S, K>[] = [];\n const expiredStorageKeys: string[] = [];\n\n for (const storageKey of ownedKeys) {\n if (!storageKey.startsWith(prefix)) continue;\n\n const value = parseEntry<RecordOf<S, K>>(storageKey);\n\n if (value === undefined) {\n expiredStorageKeys.push(storageKey);\n continue;\n }\n\n // Extract the record key from the already-decoded value (avoids a second parse).\n keys.push((value as Record<string, unknown>)[schema[table].key] as KeyOf<S, K>);\n }\n\n for (const storageKey of expiredStorageKeys) {\n evict(storageKey);\n }\n\n return keys;\n },\n\n async getRawCount<K extends keyof S & string>(table: K): Promise<number> {\n const prefix = getPrefix(table);\n let count = 0;\n\n for (const key of ownedKeys) {\n if (key.startsWith(prefix)) count += 1;\n }\n\n return count;\n },\n\n async has<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<boolean> {\n const storageKey = encodeStorageKey(name, table, String(key));\n const value = parseEntry<RecordOf<S, K>>(storageKey);\n\n if (value === undefined && ownedKeys.has(storageKey)) evict(storageKey);\n\n return value !== undefined;\n },\n\n async pruneExpiredInTable<K extends keyof S & string>(table: K): Promise<number> {\n const prefix = getPrefix(table);\n const expiredKeys: string[] = [];\n\n for (const storageKey of ownedKeys) {\n if (!storageKey.startsWith(prefix)) continue;\n\n const raw = storage().getItem(storageKey);\n\n if (raw === null) {\n expiredKeys.push(storageKey);\n continue;\n }\n\n try {\n const stored = codec.decode(JSON.parse(raw) as unknown);\n\n if (!stored || isExpired(stored.expiresAt)) {\n expiredKeys.push(storageKey);\n }\n } catch {\n expiredKeys.push(storageKey);\n }\n }\n\n for (const storageKey of expiredKeys) {\n evict(storageKey);\n }\n\n return expiredKeys.length;\n },\n\n async put<K extends keyof S & string>(table: K, value: RecordOf<S, K>, ttl?: TtlMs): Promise<void> {\n const storageKey = encodeStorageKey(name, table, String(getRecordKey(schema, table, value)));\n const expiresAt = ttl !== undefined ? Date.now() + ttl : undefined;\n\n writeItem(table, storageKey, codec.encode(value, expiresAt));\n ownedKeys.add(storageKey);\n },\n\n async putAll<K extends keyof S & string>(table: K, values: RecordOf<S, K>[], ttl?: TtlMs): Promise<void> {\n const expiresAt = ttl !== undefined ? Date.now() + ttl : undefined;\n\n for (const value of values) {\n const storageKey = encodeStorageKey(name, table, String(getRecordKey(schema, table, value)));\n\n writeItem(table, storageKey, codec.encode(value, expiresAt));\n ownedKeys.add(storageKey);\n }\n },\n };\n\n return buildAdapterOps(schema, core, {\n logger,\n onCrossTabMessage(notify) {\n if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') {\n return undefined;\n }\n\n const listener = (event: StorageEvent) => {\n if (event.storageArea && event.storageArea !== resolvedStorage) return;\n\n if (event.key === null) {\n // storage.clear() from another tab — all keys are gone; purge ownedKeys\n ownedKeys.clear();\n\n for (const table of Object.keys(schema)) {\n notify(table as keyof S & string);\n }\n\n return;\n }\n\n const tableName = decodeStorageTableFromKey(name, event.key);\n\n if (tableName && Object.hasOwn(schema, tableName)) {\n if (event.newValue === null) {\n ownedKeys.delete(event.key);\n } else {\n ownedKeys.add(event.key);\n }\n\n notify(tableName as keyof S & string);\n }\n };\n\n window.addEventListener('storage', listener);\n\n return () => window.removeEventListener('storage', listener);\n },\n onMetrics,\n schema,\n signals,\n validators,\n });\n}\n\nexport function createLocalStorage<S extends AnySchema>(options: WebStorageOptions<S>): Adapter<S> {\n return createWebStorageAdapter({\n ...options,\n getStorage: () => (typeof window !== 'undefined' ? window.localStorage : localStorage),\n storageLabel: 'localStorage',\n });\n}\n\nexport function createSessionStorage<S extends AnySchema>(options: WebStorageOptions<S>): Adapter<S> {\n return createWebStorageAdapter({\n ...options,\n getStorage: () => (typeof window !== 'undefined' ? window.sessionStorage : sessionStorage),\n storageLabel: 'sessionStorage',\n });\n}\n","import type { MigrationFn } from './types';\n\n/**\n * A single step in a typed migration definition.\n * Compose multiple steps to describe the full schema change between two versions.\n */\nexport type MigrationStep =\n | { field: string; table: string; type: 'addIndex' }\n | { field: string; table: string; type: 'removeIndex' }\n | { name: string; type: 'addTable' }\n | { name: string; type: 'removeTable' };\n\n/**\n * Builds a typed `MigrationFn` from a declarative list of schema change steps.\n * Each step is applied in order and is idempotent (safe to run when the target\n * already exists or has already been removed).\n *\n * ```ts\n * const migrate = defineMigration([\n * { type: 'addTable', name: 'sessions' },\n * { type: 'addIndex', table: 'users', field: 'email' },\n * { type: 'removeTable', name: 'legacyTokens' },\n * ]);\n *\n * const db = createIndexedDB({ name: 'app', version: 2, schema, migrate });\n * ```\n */\nexport function defineMigration(steps: MigrationStep[]): MigrationFn {\n return ({ db, tx }) => {\n for (const step of steps) {\n switch (step.type) {\n case 'addIndex': {\n const store = tx.objectStore(step.table);\n\n // keyPath mirrors the vault storage envelope: { value: T, expiresAt?: number }\n if (!store.indexNames.contains(step.field)) {\n store.createIndex(step.field, `value.${step.field}`);\n }\n\n break;\n }\n case 'addTable':\n if (!db.objectStoreNames.contains(step.name)) {\n db.createObjectStore(step.name);\n }\n\n break;\n case 'removeIndex': {\n const store = tx.objectStore(step.table);\n\n if (store.indexNames.contains(step.field)) {\n store.deleteIndex(step.field);\n }\n\n break;\n }\n case 'removeTable':\n if (db.objectStoreNames.contains(step.name)) {\n db.deleteObjectStore(step.name);\n }\n\n break;\n }\n }\n };\n}\n","import type { Adapter, AnySchema } from './types';\n\nimport { warn } from './_dev';\nimport { VaultDisposedError, VaultError } from './errors';\n\n/**\n * Schedules periodic calls to `adapter.pruneExpired()` using `setInterval`.\n * Returns a `stop` function to cancel the schedule.\n *\n * The schedule auto-cancels if the adapter is disposed — no need to call `stop()`\n * in a dispose handler when the adapter lifetime matches the schedule lifetime.\n *\n * ```ts\n * const stopPrune = scheduleExpiredPrune(db, { interval: ttl.hours(1) });\n * // later...\n * stopPrune();\n * ```\n */\nexport function scheduleExpiredPrune<S extends AnySchema>(\n adapter: Pick<Adapter<S>, 'pruneExpired'>,\n options: {\n interval: number;\n /**\n * Called when `pruneExpired()` throws an error that is NOT a `VaultDisposedError`.\n * `VaultDisposedError` always stops the schedule automatically.\n * Without this callback, non-disposal errors are silently swallowed.\n */\n onError?: (err: unknown) => void;\n /**\n * When aborted, stops the schedule. Useful for tying the schedule lifetime\n * to an adapter's `disposalSignal`:\n * ```ts\n * scheduleExpiredPrune(db, { interval: ttl.hours(1), signal: db.disposalSignal });\n * ```\n */\n signal?: AbortSignal;\n },\n): () => void {\n if (!Number.isFinite(options.interval) || options.interval <= 0) {\n throw new VaultError('scheduleExpiredPrune: interval must be a finite positive number');\n }\n\n let active = true;\n\n const stop = (): void => {\n active = false;\n clearInterval(id);\n };\n\n options.signal?.addEventListener('abort', stop, { once: true });\n\n const id = setInterval(() => {\n if (!active) return;\n\n void adapter.pruneExpired().catch((err) => {\n if (err instanceof VaultDisposedError) {\n active = false;\n clearInterval(id);\n } else if (options.onError) {\n options.onError(err);\n } else {\n warn(`scheduleExpiredPrune: pruneExpired() threw — pass onError to handle this. ${String(err)}`);\n }\n });\n }, options.interval);\n\n return stop;\n}\n","/**\n * Converts an `AsyncIterable<T>` into a Web Standard `ReadableStream<T>`.\n *\n * Use this with `db.watch()` when you need a `ReadableStream` instead of an async iterator —\n * for example, to `pipeTo()` a `WritableStream` or `pipeThrough()` a `TransformStream`.\n *\n * ```ts\n * const stream = toReadableStream(db.watch('users'));\n * await stream.pipeTo(new WritableStream({ write: (users) => render(users) }));\n * ```\n *\n * The stream closes when the iterable is exhausted or when the stream is cancelled.\n * Pass an `AbortSignal` to `db.watch()` to cancel from outside:\n *\n * ```ts\n * const controller = new AbortController();\n * const stream = toReadableStream(db.watch('users', { signal: controller.signal }));\n * controller.abort(); // closes the stream\n * ```\n */\nexport function toReadableStream<T>(iterable: AsyncIterable<T>): ReadableStream<T> {\n const iter = iterable[Symbol.asyncIterator]();\n\n return new ReadableStream<T>({\n async cancel() {\n await iter.return?.();\n },\n async pull(controller) {\n try {\n const { done, value } = await iter.next();\n\n if (done) {\n controller.close();\n } else {\n controller.enqueue(value);\n }\n } catch (err) {\n controller.error(err);\n }\n },\n });\n}\n","import type { VaultCodec } from './ttl';\n\nimport { VaultError } from './errors';\n\n/**\n * A single version entry in a `createVersionedCodec` definition.\n * `version` must be a non-negative integer. `codec` is the codec responsible for\n * encoding/decoding records that carry this version number.\n */\nexport type CodecVersion = {\n codec: VaultCodec;\n version: number;\n};\n\n/**\n * Creates a versioned codec that prepends a `__v` field to every encoded envelope.\n * When decoding, the `__v` field selects the matching codec from `versions`.\n * This allows safe codec upgrades: old records encoded with a previous codec continue\n * to decode correctly as long as the old codec remains in `versions`.\n *\n * ```ts\n * const v1Codec: VaultCodec = { ... };\n * const v2Codec: VaultCodec = { ... }; // newer, more compact format\n *\n * const codec = createVersionedCodec([\n * { version: 1, codec: v1Codec },\n * { version: 2, codec: v2Codec },\n * ], 2); // write new records with version 2\n *\n * const db = createMemory({ schema, codec });\n * // Old records (version 1) are decoded by v1Codec; new records use v2Codec.\n * ```\n *\n * **Rules:**\n * - `currentVersion` must be one of the versions listed in `versions`.\n * - Version numbers must be unique non-negative integers.\n * - The current version's codec is used for all new writes.\n * - Unrecognised `__v` values cause `decode` to return `undefined` (treated as corrupt).\n * - **Migration note:** records written by any other codec (including `defaultCodec`) lack\n * the `__v` field and will decode as `undefined` — they will be treated as missing/expired.\n * Perform a data migration or clear the store before switching to a versioned codec.\n * - **Secondary index caveat:** the `{ __d, __v }` envelope replaces the default\n * `{ value, expiresAt? }` shape, so IndexedDB secondary indexes (created via `.index()`\n * with keyPath `value.<field>`) silently stop matching. Don't combine a versioned codec\n * with `.index()` on the same table.\n */\nexport function createVersionedCodec(versions: CodecVersion[], currentVersion: number): VaultCodec {\n if (versions.length === 0) {\n throw new VaultError('createVersionedCodec: at least one version entry is required');\n }\n\n const byVersion = new Map<number, VaultCodec>();\n\n for (const entry of versions) {\n if (!Number.isInteger(entry.version) || entry.version < 0) {\n throw new VaultError(\n `createVersionedCodec: version must be a non-negative integer, got ${String(entry.version)}`,\n );\n }\n\n if (byVersion.has(entry.version)) {\n throw new VaultError(`createVersionedCodec: duplicate version ${String(entry.version)}`);\n }\n\n byVersion.set(entry.version, entry.codec);\n }\n\n if (!byVersion.has(currentVersion)) {\n throw new VaultError(`createVersionedCodec: currentVersion ${String(currentVersion)} is not listed in versions`);\n }\n\n const writeCodec = byVersion.get(currentVersion)!;\n\n return {\n decode<T>(raw: unknown): { expiresAt?: number; value: T } | undefined {\n if (typeof raw !== 'object' || raw === null || !('__v' in raw)) {\n return undefined;\n }\n\n const wrapper = raw as { __d: unknown; __v: unknown };\n const version = wrapper.__v;\n\n if (typeof version !== 'number') return undefined;\n\n const codec = byVersion.get(version);\n\n if (!codec) return undefined;\n\n return codec.decode<T>(wrapper.__d);\n },\n\n encode<T>(value: T, expiresAt?: number): unknown {\n return { __d: writeCodec.encode(value, expiresAt), __v: currentVersion };\n },\n };\n}\n","import type { QueryBuilder } from './query';\n\nimport { VaultError } from './errors';\nimport { assertTtlMs, type TtlMs, type VaultCodec } from './ttl';\n\n/* -------------------- Re-export TtlMs and VaultCodec for public API -------------------- */\n\nexport type { TtlMs, VaultCodec };\n\n/* -------------------- Schema types -------------------- */\n\n/** @internal Unique symbol used as a phantom type brand — never appears at runtime. */\ndeclare const schemaEntryBrand: unique symbol;\n\n/**\n * Schema entry for a single table.\n * `T` is the record type; `Key` is the primary key field name.\n *\n * The phantom brand `[schemaEntryBrand]` holds `T` in a directly inferable position so\n * TypeScript can recover `T` in `RecordOf` conditional types. It uses a unique symbol key\n * so it is invisible in IDE autocompletion and cannot be set accidentally.\n */\nexport type SchemaEntry<T extends Record<string, unknown>, Key extends keyof T & string = keyof T & string> = {\n defaultTtl?: TtlMs;\n /**\n * Secondary index field names. The IndexedDB adapter creates an IDB index for each field,\n * enabling push-down optimisation for `equals`, `between`, and `startsWith` queries on those\n * fields — avoiding a full-table scan.\n *\n * ```ts\n * const schema = {\n * users: table<User>('id').index('email').index('city'),\n * };\n * // db.query('users').equals('email', 'alice@example.com') → uses IDB index\n * ```\n */\n indexes?: readonly (keyof T & string)[];\n key: Key;\n /** @internal Phantom brand — enables TypeScript to infer T. Never has a runtime value. */\n readonly [schemaEntryBrand]?: T;\n};\n\n/** A schema is any record of `SchemaEntry`-compatible values. Checked structurally so that\n * concrete `SchemaEntry<T, Key>` values satisfy it without covariance constraints. */\nexport type AnySchema = Record<string, { defaultTtl?: TtlMs; indexes?: readonly string[]; key: string }>;\n\n/**\n * Fluent builder returned by `table()` — satisfies `SchemaEntry` and adds `.ttl()` and `.index()` chaining.\n * Export this type to annotate schema entry variables without using `ReturnType<typeof table<T, K>>`.\n */\nexport type TableBuilder<\n T extends Record<string, unknown>,\n Key extends keyof T & string = keyof T & string,\n> = SchemaEntry<T, Key> & {\n /**\n * Register a secondary index on the given field. Can be chained multiple times.\n * Only used by the IndexedDB adapter; other adapters fall back to in-memory filtering.\n *\n * **Custom codec caveat:** the IndexedDB adapter creates the index with keyPath\n * `value.<field>`, which assumes the default `{ value, expiresAt? }` storage envelope\n * (see `VaultCodec`). A custom codec that changes the top-level shape (e.g.\n * `createVersionedCodec`, or a compact/encrypted format) breaks index push-down silently —\n * queries on the indexed field return empty results instead of throwing. Avoid combining\n * `.index()` with a non-default codec, or design the custom codec to preserve `value.<field>`.\n */\n index: <F extends keyof T & string>(field: F) => TableBuilder<T, Key>;\n /** Set a default TTL (ms) applied to all `put`/`putAll` calls that don't specify one explicitly. */\n ttl: (ms: TtlMs) => TableBuilder<T, Key>;\n};\n\nexport function table<T extends Record<string, unknown>, Key extends keyof T & string = keyof T & string>(\n key: Key,\n): TableBuilder<T, Key> {\n function makeBuilder(entry: SchemaEntry<T, Key>): TableBuilder<T, Key> {\n return {\n ...entry,\n index: <F extends keyof T & string>(field: F): TableBuilder<T, Key> => {\n const current = (entry.indexes ?? []) as readonly (keyof T & string)[];\n\n if (current.includes(field)) {\n throw new VaultError(`table index \"${field}\" is already registered`);\n }\n\n return makeBuilder({ ...entry, indexes: [...current, field] });\n },\n ttl: (ms: TtlMs): TableBuilder<T, Key> => {\n assertTtlMs(ms, 'table.ttl');\n\n return makeBuilder({ ...entry, defaultTtl: ms });\n },\n };\n }\n\n return makeBuilder({ key });\n}\n\n/** Extracts the record type from a schema table entry. */\nexport type RecordOf<S extends AnySchema, K extends keyof S> = S[K] extends SchemaEntry<infer R, string> ? R : never;\n\n/** Extracts the primary key value type from a schema table entry. */\nexport type KeyOf<S extends AnySchema, K extends keyof S> =\n S[K] extends SchemaEntry<infer R, infer Key> ? R[Key] : never;\n\n/* -------------------- Migration -------------------- */\n\nexport type MigrationContext = {\n db: IDBDatabase;\n newVersion: number | null;\n oldVersion: number;\n tx: IDBTransaction;\n};\n\nexport type MigrationFn = (ctx: MigrationContext) => void;\n\n/* -------------------- Plugin / integration types -------------------- */\n\n/**\n * Minimal logger interface satisfied structurally by `/rune` Logger.\n * Pass a rune Logger instance directly — no adapter needed:\n *\n * ```ts\n * import { createLogger } from '@vielzeug/rune';\n * const db = createMemory({ schema, logger: createLogger('db') });\n * ```\n *\n * Vault only emits error-level logs, so a single rune-compatible `error`\n * method is enough.\n */\nexport interface VaultLogger {\n error(messageOrContext?: Record<string, unknown> | Error | string, message?: string): void;\n}\n\n/**\n * Minimal synchronous validator interface satisfied structurally by\n * `/sieve` Schema.\n * Pass a sieve schema directly — no adapter needed:\n *\n * ```ts\n * import { s } from '@vielzeug/spell';\n * const db = createMemory({\n * schema: { users: table<User>('id') },\n * validators: { users: v.object({ id: v.number(), name: v.string() }) },\n * });\n * ```\n *\n * Vault requires synchronous validation because writes may execute inside a\n * live IndexedDB transaction. Any object with a `parse(value: unknown): T`\n * method works.\n */\nexport interface RecordValidator<T> {\n parse(value: unknown): T;\n}\n\n/**\n * Per-table record parsers. Keys match your vault schema table names.\n * Validators run before every `put`, `putAll`, and inside `update`/`upsert`.\n */\nexport type TableValidators<S extends AnySchema> = {\n [K in keyof S]?: RecordValidator<RecordOf<S, K>>;\n};\n\n/**\n * Minimal writable-signal interface satisfied structurally by\n * `/ripple` `Signal<T>` and `Store<T>`.\n * Pass a ripple signal directly — no adapter needed:\n *\n * ```ts\n * import { signal } from '@vielzeug/ripple';\n * const usersSignal = signal<User[]>([]);\n *\n * const db = createMemory({\n * schema: { users: table<User>('id') },\n * signals: { users: usersSignal },\n * });\n *\n * // usersSignal.value is now always in sync with the users table.\n * ```\n *\n * Any object with an `update(fn: (current: T) => T): void` method satisfies\n * this interface. Vault calls `signal.update(() => snapshot)` on each change.\n */\nexport interface ReactiveSignal<T> {\n update(fn: (current: T) => T): void;\n}\n\n/**\n * Per-table reactive signals. Keys match your vault schema table names.\n * Each signal is automatically kept in sync with the table via `observe()`.\n * Signals are wired at construction time and cleaned up on `dispose()`.\n */\nexport type TableSignals<S extends AnySchema> = {\n [K in keyof S]?: ReactiveSignal<RecordOf<S, K>[]>;\n};\n\n/* -------------------- Observer -------------------- */\n\nexport type Observer<T> = (records: T[]) => void;\n\n/** A function that cancels an active subscription. Returned by `observe()` and `observeMany()`. */\nexport type Unsubscribe = () => void;\n\n/* -------------------- Shared adapter options -------------------- */\n\n/**\n * Common options shared by all adapter factories.\n * Individual adapters extend this with adapter-specific fields.\n */\nexport type BaseAdapterOptions<S extends AnySchema> = {\n /**\n * Pluggable serialization codec. Provide a custom implementation to change how values\n * are encoded at rest (e.g. compact keys, encryption, msgpack).\n * Defaults to the standard `{ value, expiresAt? }` JSON envelope.\n *\n * **IndexedDB + `.index()` caveat:** secondary indexes are created with keyPath\n * `value.<field>`, which assumes the default envelope shape. A custom codec that changes\n * the top-level shape breaks index push-down silently (queries return empty results\n * instead of throwing) — see `TableBuilder.index`.\n */\n codec?: VaultCodec;\n /** Structured logger. A /rune Logger satisfies VaultLogger directly. */\n logger?: VaultLogger;\n /** Performance monitoring hook. Called after every operation with duration in ms. */\n onMetrics?: (event: MetricsEvent) => void;\n schema: S;\n /**\n * Per-table reactive signals. A /ripple Signal<T[]> satisfies ReactiveSignal directly.\n * Each signal is kept in sync with its table automatically via observe().\n */\n signals?: TableSignals<S>;\n /** Per-table validators. A /sieve Schema satisfies this directly via `parse()`. */\n validators?: TableValidators<S>;\n};\n\n/* -------------------- Metrics -------------------- */\n\nexport type MetricsEvent = {\n duration: number;\n operation:\n | 'batch'\n | 'clear'\n | 'count'\n | 'delete'\n | 'deleteMany'\n | 'entries'\n | 'get'\n | 'getAll'\n | 'getMany'\n | 'getOrDefault'\n | 'has'\n | 'isEmpty'\n | 'keys'\n | 'put'\n | 'putAll'\n | 'query'\n | 'queryDelete'\n | 'update'\n | 'upsert';\n /**\n * The table name. For `batch` operations this is `'*'` because a batch may\n * span multiple tables and there is no single canonical table name.\n */\n table: string;\n};\n\n/* -------------------- Debug info -------------------- */\n\nexport type DebugStats = {\n /** Number of TTL-expired records still physically in the store (not yet lazily evicted). */\n expiredCount: number;\n /** Number of live (non-expired) records. */\n recordCount: number;\n};\n\nexport type DebugInfo<S extends AnySchema> = {\n tables: Array<{ name: keyof S & string } & DebugStats>;\n};\n\n/* -------------------- Transaction context -------------------- */\n\n/**\n * Available inside `batch()` callbacks. For IndexedDB, operations run in a real atomic IDB transaction.\n * The batch scope restricts accessed tables to those declared in `batch(tables, fn)` — accessing\n * others throws `VaultScopeError`.\n */\nexport type TransactionContext<S extends AnySchema, K extends keyof S & string = keyof S & string> = {\n clear<T extends K>(table: T): Promise<void>;\n count<T extends K>(table: T): Promise<number>;\n delete<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;\n /** Delete multiple records by key in a single operation. Returns the number of records removed. */\n deleteMany<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<number>;\n /**\n * Returns all `[key, record]` pairs in the table.\n * Useful for cache-warming, migration scripts, and debugging.\n */\n entries<T extends K>(table: T): Promise<Array<[KeyOf<S, T>, RecordOf<S, T>]>>;\n get<T extends K>(table: T, key: KeyOf<S, T>): Promise<RecordOf<S, T> | undefined>;\n /** Fetch all records in the table. */\n getAll<T extends K>(table: T): Promise<RecordOf<S, T>[]>;\n /** Fetch multiple records by key in a single operation. Preserves key order; missing keys yield `undefined`. */\n getMany<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<Array<RecordOf<S, T> | undefined>>;\n /**\n * Read-or-insert: returns the existing record if present, otherwise calls `defaultFn()`,\n * writes the result, and returns it. Equivalent to an `upsert` that never overwrites.\n *\n * **Not atomic for memory and WebStorage adapters.** Two concurrent calls may both observe\n * a missing record and both invoke `defaultFn()`, writing twice. For guaranteed atomicity,\n * wrap in `batch(['table'], tx => tx.getOrDefault(...))` with the IndexedDB adapter.\n */\n getOrDefault<T extends K>(\n table: T,\n key: KeyOf<S, T>,\n defaultFn: () => RecordOf<S, T>,\n ttl?: TtlMs,\n ): Promise<RecordOf<S, T>>;\n has<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;\n /** Returns `true` if the table contains no live records. Equivalent to `(await count(table)) === 0`. */\n isEmpty<T extends K>(table: T): Promise<boolean>;\n /**\n * Returns the primary key of every live record in the table.\n * Without a `filter`, uses a key-only backend path (no full records fetched).\n * Useful for existence checks, diffing, and cache-invalidation.\n *\n * Pass an optional `filter` predicate to restrict results — when provided, all records are\n * fetched internally and the predicate is applied before key extraction.\n */\n keys<T extends K>(table: T, filter?: (record: RecordOf<S, T>) => boolean): Promise<KeyOf<S, T>[]>;\n put<T extends K>(table: T, value: RecordOf<S, T>, ttl?: TtlMs): Promise<void>;\n putAll<T extends K>(table: T, values: RecordOf<S, T>[], ttl?: TtlMs): Promise<void>;\n query<T extends K>(table: T): QueryBuilder<RecordOf<S, T>>;\n /**\n * Merge `changes` into the existing record identified by `key` and persist the result.\n * Returns `undefined` when the key does not exist — use `upsert` for insert-or-update semantics.\n */\n update<T extends K>(\n table: T,\n key: KeyOf<S, T>,\n changes: Partial<RecordOf<S, T>>,\n ttl?: TtlMs,\n ): Promise<RecordOf<S, T> | undefined>;\n upsert<T extends K>(\n table: T,\n key: KeyOf<S, T>,\n fn: (existing: RecordOf<S, T> | undefined) => RecordOf<S, T>,\n ttl?: TtlMs,\n ): Promise<RecordOf<S, T>>;\n};\n\n/* -------------------- Adapter interface -------------------- */\n\n/** Full client API for vault adapters. */\nexport interface Adapter<S extends AnySchema> {\n clear<K extends keyof S & string>(table: K): Promise<void>;\n count<K extends keyof S & string>(table: K): Promise<number>;\n delete<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<boolean>;\n /** Delete multiple records by key in a single operation. Returns the number of records removed. */\n deleteMany<K extends keyof S & string>(table: K, keys: KeyOf<S, K>[]): Promise<number>;\n /**\n * Returns all `[key, record]` pairs in the table.\n * Useful for cache-warming, migration scripts, and debugging.\n */\n entries<K extends keyof S & string>(table: K): Promise<Array<[KeyOf<S, K>, RecordOf<S, K>]>>;\n get<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<RecordOf<S, K> | undefined>;\n /** Fetch all records in the table. */\n getAll<K extends keyof S & string>(table: K): Promise<RecordOf<S, K>[]>;\n /** Fetch multiple records by key in a single operation. Preserves key order; missing keys yield `undefined`. */\n getMany<K extends keyof S & string>(table: K, keys: KeyOf<S, K>[]): Promise<Array<RecordOf<S, K> | undefined>>;\n /**\n * Read-or-insert: returns the existing record if present, otherwise calls `defaultFn()`,\n * writes the result, and returns it. Equivalent to an `upsert` that never overwrites.\n *\n * **Not atomic for memory and WebStorage adapters.** Two concurrent calls may both observe\n * a missing record and both invoke `defaultFn()`, writing twice. For guaranteed atomicity,\n * wrap in `batch(['table'], tx => tx.getOrDefault(...))` with the IndexedDB adapter.\n */\n getOrDefault<K extends keyof S & string>(\n table: K,\n key: KeyOf<S, K>,\n defaultFn: () => RecordOf<S, K>,\n ttl?: TtlMs,\n ): Promise<RecordOf<S, K>>;\n has<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<boolean>;\n /** Returns `true` if the table contains no live records. Equivalent to `(await count(table)) === 0`. */\n isEmpty<K extends keyof S & string>(table: K): Promise<boolean>;\n /**\n * Returns the primary key of every live record in the table.\n * Without a `filter`, uses a key-only backend path (no full records fetched).\n * Useful for existence checks, diffing, and cache-invalidation.\n *\n * Pass an optional `filter` predicate to restrict results — when provided, all records are\n * fetched internally and the predicate is applied before key extraction.\n */\n keys<K extends keyof S & string>(table: K, filter?: (record: RecordOf<S, K>) => boolean): Promise<KeyOf<S, K>[]>;\n put<K extends keyof S & string>(table: K, value: RecordOf<S, K>, ttl?: TtlMs): Promise<void>;\n putAll<K extends keyof S & string>(table: K, values: RecordOf<S, K>[], ttl?: TtlMs): Promise<void>;\n query<K extends keyof S & string>(table: K): QueryBuilder<RecordOf<S, K>>;\n /**\n * Merge `changes` into the existing record identified by `key` and persist the result.\n * Returns `undefined` when the key does not exist — use `upsert` for insert-or-update semantics.\n */\n update<K extends keyof S & string>(\n table: K,\n key: KeyOf<S, K>,\n changes: Partial<RecordOf<S, K>>,\n ttl?: TtlMs,\n ): Promise<RecordOf<S, K> | undefined>;\n upsert<K extends keyof S & string>(\n table: K,\n key: KeyOf<S, K>,\n fn: (existing: RecordOf<S, K> | undefined) => RecordOf<S, K>,\n ttl?: TtlMs,\n ): Promise<RecordOf<S, K>>;\n /**\n * Execute multiple operations against a set of tables with deferred observer notifications.\n *\n * All observer callbacks fire once per dirty table after `fn` resolves, instead of after\n * each individual write. Inside `fn`, only the tables declared in `tables` may be accessed.\n *\n * **Atomicity:** For the IndexedDB adapter this runs as a real IDB transaction — all writes\n * are atomic and rolled back on error. For the memory and WebStorage adapters the operation\n * is **not atomic** — concurrent `batch()` calls or concurrent mutations may interleave.\n */\n batch<K extends keyof S & string, R>(\n tables: readonly K[],\n fn: (tx: TransactionContext<S, K>) => Promise<R>,\n ): Promise<R>;\n /** Returns live record counts and expired-but-not-yet-evicted counts per table. */\n debug(): Promise<DebugInfo<S>>;\n /** `AbortSignal` aborted when `dispose()` is called. Use to tie external lifetimes to this adapter. */\n readonly disposalSignal: AbortSignal;\n /** Releases all resources (observers, cross-tab channel, DB connection). */\n dispose(): Promise<void>;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** Delegates to `dispose()`. Enables `await using` declarations. */\n [Symbol.asyncDispose](): Promise<void>;\n observe<K extends keyof S & string>(\n table: K,\n listener: Observer<RecordOf<S, K>>,\n options?: {\n /**\n * When `false`, skips the automatic initial snapshot fired on registration.\n * Useful when the caller already has the current table state (e.g. from a\n * preceding `getAll()` call) and only wants change notifications.\n * Defaults to `true`.\n */\n immediate?: boolean;\n /**\n * An `AbortSignal` that, when aborted, automatically unsubscribes this observer.\n * Aligns with the platform pattern used by `fetch`, `addEventListener`, etc.\n *\n * ```ts\n * const controller = new AbortController();\n * db.observe('users', (users) => render(users), { signal: controller.signal });\n * // later:\n * controller.abort(); // stops the observer\n * ```\n */\n signal?: AbortSignal;\n },\n ): Unsubscribe;\n /**\n * Observe multiple tables simultaneously. The listener receives a combined snapshot\n * `{ [tableName]: RecordOf<S, T>[] }` and fires once after all tables have delivered\n * their first snapshot (initial load). Subsequent firings are coalesced per microtask,\n * so a batch that writes to multiple observed tables triggers exactly one combined callback.\n *\n * @param tables - The tables to observe. Must be non-empty.\n * @param listener - Called with a snapshot map keyed by table name.\n * @param options.signal - An `AbortSignal` that automatically unsubscribes all observers.\n * @returns An unsubscribe function that stops all underlying observers.\n */\n observeMany<K extends keyof S & string>(\n tables: readonly K[],\n listener: (snapshots: { [T in K]: RecordOf<S, T>[] }) => void,\n options?: {\n /**\n * When `true`, fires the listener as soon as any table delivers its first snapshot,\n * using empty arrays for tables not yet resolved. Defaults to `false` (wait for all tables).\n *\n * Useful when some tables may be large and you want to render partial data immediately.\n */\n eager?: boolean;\n signal?: AbortSignal;\n },\n ): Unsubscribe;\n /**\n * Explicitly delete all TTL-expired records from the specified tables (or all tables when\n * no filter is provided). Returns the number of records pruned per table.\n *\n * Useful as a scheduled maintenance task for write-heavy tables that are rarely\n * read (lazy eviction would not reclaim storage otherwise).\n *\n * ```ts\n * // Prune all tables\n * await db.pruneExpired();\n *\n * // Prune only TTL-bearing tables\n * await db.pruneExpired(['sessions', 'tokens']);\n * ```\n *\n * **Does not trigger observer callbacks.** TTL-expired records are already logically\n * absent, so their physical removal does not change observable state.\n */\n pruneExpired(tables?: readonly (keyof S & string)[]): Promise<{ [K in keyof S & string]: number }>;\n /**\n * An AsyncIterable that yields a fresh snapshot of the table on every change.\n * The first value is emitted immediately.\n *\n * ```ts\n * for await (const users of db.watch('users')) {\n * render(users);\n * }\n * ```\n *\n * Pass an `AbortSignal` to stop iteration from outside the loop:\n * ```ts\n * const controller = new AbortController();\n * for await (const users of db.watch('users', { signal: controller.signal })) {\n * render(users);\n * }\n * controller.abort(); // stops the iteration\n * ```\n *\n * @param options.mode\n * - `'latest'` (default): if the consumer lags, intermediate snapshots are dropped and\n * only the most recent one is retained. Best for rendering/display use-cases.\n * - `'all'`: every snapshot is queued and delivered in order. Use when every intermediate\n * state matters (audit trails, animation frames).\n * @param options.signal - An `AbortSignal` that stops the iteration.\n */\n watch<K extends keyof S & string>(\n table: K,\n options?: { mode?: 'all' | 'latest'; signal?: AbortSignal },\n ): AsyncIterable<RecordOf<S, K>[]>;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./errors.cjs");function t(t,n){if(t.length===0)throw new e.VaultError(`createVersionedCodec: at least one version entry is required`);let r=new Map;for(let n of t){if(!Number.isInteger(n.version)||n.version<0)throw new e.VaultError(`createVersionedCodec: version must be a non-negative integer, got ${String(n.version)}`);if(r.has(n.version))throw new e.VaultError(`createVersionedCodec: duplicate version ${String(n.version)}`);r.set(n.version,n.codec)}if(!r.has(n))throw new e.VaultError(`createVersionedCodec: currentVersion ${String(n)} is not listed in versions`);let i=r.get(n);return{decode(e){if(typeof e!=`object`||!e||!(`__v`in e))return;let t=e,n=t.__v;if(typeof n!=`number`)return;let i=r.get(n);if(i)return i.decode(t.__d)},encode(e,t){return{__d:i.encode(e,t),__v:n}}}}exports.createVersionedCodec=t;
|
|
2
|
+
//# sourceMappingURL=versioned-codec.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"versioned-codec.cjs","names":[],"sources":["../src/versioned-codec.ts"],"sourcesContent":["import type { VaultCodec } from './ttl';\n\nimport { VaultError } from './errors';\n\n/**\n * A single version entry in a `createVersionedCodec` definition.\n * `version` must be a non-negative integer. `codec` is the codec responsible for\n * encoding/decoding records that carry this version number.\n */\nexport type CodecVersion = {\n codec: VaultCodec;\n version: number;\n};\n\n/**\n * Creates a versioned codec that prepends a `__v` field to every encoded envelope.\n * When decoding, the `__v` field selects the matching codec from `versions`.\n * This allows safe codec upgrades: old records encoded with a previous codec continue\n * to decode correctly as long as the old codec remains in `versions`.\n *\n * ```ts\n * const v1Codec: VaultCodec = { ... };\n * const v2Codec: VaultCodec = { ... }; // newer, more compact format\n *\n * const codec = createVersionedCodec([\n * { version: 1, codec: v1Codec },\n * { version: 2, codec: v2Codec },\n * ], 2); // write new records with version 2\n *\n * const db = createMemory({ schema, codec });\n * // Old records (version 1) are decoded by v1Codec; new records use v2Codec.\n * ```\n *\n * **Rules:**\n * - `currentVersion` must be one of the versions listed in `versions`.\n * - Version numbers must be unique non-negative integers.\n * - The current version's codec is used for all new writes.\n * - Unrecognised `__v` values cause `decode` to return `undefined` (treated as corrupt).\n * - **Migration note:** records written by any other codec (including `defaultCodec`) lack\n * the `__v` field and will decode as `undefined` — they will be treated as missing/expired.\n * Perform a data migration or clear the store before switching to a versioned codec.\n * - **Secondary index caveat:** the `{ __d, __v }` envelope replaces the default\n * `{ value, expiresAt? }` shape, so IndexedDB secondary indexes (created via `.index()`\n * with keyPath `value.<field>`) silently stop matching. Don't combine a versioned codec\n * with `.index()` on the same table.\n */\nexport function createVersionedCodec(versions: CodecVersion[], currentVersion: number): VaultCodec {\n if (versions.length === 0) {\n throw new VaultError('createVersionedCodec: at least one version entry is required');\n }\n\n const byVersion = new Map<number, VaultCodec>();\n\n for (const entry of versions) {\n if (!Number.isInteger(entry.version) || entry.version < 0) {\n throw new VaultError(\n `createVersionedCodec: version must be a non-negative integer, got ${String(entry.version)}`,\n );\n }\n\n if (byVersion.has(entry.version)) {\n throw new VaultError(`createVersionedCodec: duplicate version ${String(entry.version)}`);\n }\n\n byVersion.set(entry.version, entry.codec);\n }\n\n if (!byVersion.has(currentVersion)) {\n throw new VaultError(`createVersionedCodec: currentVersion ${String(currentVersion)} is not listed in versions`);\n }\n\n const writeCodec = byVersion.get(currentVersion)!;\n\n return {\n decode<T>(raw: unknown): { expiresAt?: number; value: T } | undefined {\n if (typeof raw !== 'object' || raw === null || !('__v' in raw)) {\n return undefined;\n }\n\n const wrapper = raw as { __d: unknown; __v: unknown };\n const version = wrapper.__v;\n\n if (typeof version !== 'number') return undefined;\n\n const codec = byVersion.get(version);\n\n if (!codec) return undefined;\n\n return codec.decode<T>(wrapper.__d);\n },\n\n encode<T>(value: T, expiresAt?: number): unknown {\n return { __d: writeCodec.encode(value, expiresAt), __v: currentVersion };\n },\n };\n}\n"],"mappings":"gCA8CA,SAAgB,EAAqB,EAA0B,EAAoC,CACjG,GAAI,EAAS,SAAW,EACtB,MAAM,IAAI,EAAA,WAAW,8DAA8D,EAGrF,IAAM,EAAY,IAAI,IAEtB,IAAK,IAAM,KAAS,EAAU,CAC5B,GAAI,CAAC,OAAO,UAAU,EAAM,OAAO,GAAK,EAAM,QAAU,EACtD,MAAM,IAAI,EAAA,WACR,qEAAqE,OAAO,EAAM,OAAO,GAC3F,EAGF,GAAI,EAAU,IAAI,EAAM,OAAO,EAC7B,MAAM,IAAI,EAAA,WAAW,2CAA2C,OAAO,EAAM,OAAO,GAAG,EAGzF,EAAU,IAAI,EAAM,QAAS,EAAM,KAAK,CAC1C,CAEA,GAAI,CAAC,EAAU,IAAI,CAAc,EAC/B,MAAM,IAAI,EAAA,WAAW,wCAAwC,OAAO,CAAc,EAAE,2BAA2B,EAGjH,IAAM,EAAa,EAAU,IAAI,CAAc,EAE/C,MAAO,CACL,OAAU,EAA4D,CACpE,GAAI,OAAO,GAAQ,WAAY,GAAgB,EAAE,QAAS,GACxD,OAGF,IAAM,EAAU,EACV,EAAU,EAAQ,IAExB,GAAI,OAAO,GAAY,SAAU,OAEjC,IAAM,EAAQ,EAAU,IAAI,CAAO,EAE9B,KAEL,OAAO,EAAM,OAAU,EAAQ,GAAG,CACpC,EAEA,OAAU,EAAU,EAA6B,CAC/C,MAAO,CAAE,IAAK,EAAW,OAAO,EAAO,CAAS,EAAG,IAAK,CAAe,CACzE,CACF,CACF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { VaultCodec } from './ttl';
|
|
2
|
+
/**
|
|
3
|
+
* A single version entry in a `createVersionedCodec` definition.
|
|
4
|
+
* `version` must be a non-negative integer. `codec` is the codec responsible for
|
|
5
|
+
* encoding/decoding records that carry this version number.
|
|
6
|
+
*/
|
|
7
|
+
export type CodecVersion = {
|
|
8
|
+
codec: VaultCodec;
|
|
9
|
+
version: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Creates a versioned codec that prepends a `__v` field to every encoded envelope.
|
|
13
|
+
* When decoding, the `__v` field selects the matching codec from `versions`.
|
|
14
|
+
* This allows safe codec upgrades: old records encoded with a previous codec continue
|
|
15
|
+
* to decode correctly as long as the old codec remains in `versions`.
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* const v1Codec: VaultCodec = { ... };
|
|
19
|
+
* const v2Codec: VaultCodec = { ... }; // newer, more compact format
|
|
20
|
+
*
|
|
21
|
+
* const codec = createVersionedCodec([
|
|
22
|
+
* { version: 1, codec: v1Codec },
|
|
23
|
+
* { version: 2, codec: v2Codec },
|
|
24
|
+
* ], 2); // write new records with version 2
|
|
25
|
+
*
|
|
26
|
+
* const db = createMemory({ schema, codec });
|
|
27
|
+
* // Old records (version 1) are decoded by v1Codec; new records use v2Codec.
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* **Rules:**
|
|
31
|
+
* - `currentVersion` must be one of the versions listed in `versions`.
|
|
32
|
+
* - Version numbers must be unique non-negative integers.
|
|
33
|
+
* - The current version's codec is used for all new writes.
|
|
34
|
+
* - Unrecognised `__v` values cause `decode` to return `undefined` (treated as corrupt).
|
|
35
|
+
* - **Migration note:** records written by any other codec (including `defaultCodec`) lack
|
|
36
|
+
* the `__v` field and will decode as `undefined` — they will be treated as missing/expired.
|
|
37
|
+
* Perform a data migration or clear the store before switching to a versioned codec.
|
|
38
|
+
* - **Secondary index caveat:** the `{ __d, __v }` envelope replaces the default
|
|
39
|
+
* `{ value, expiresAt? }` shape, so IndexedDB secondary indexes (created via `.index()`
|
|
40
|
+
* with keyPath `value.<field>`) silently stop matching. Don't combine a versioned codec
|
|
41
|
+
* with `.index()` on the same table.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createVersionedCodec(versions: CodecVersion[], currentVersion: number): VaultCodec;
|
|
44
|
+
//# sourceMappingURL=versioned-codec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"versioned-codec.d.ts","sourceRoot":"","sources":["../src/versioned-codec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAIxC;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,cAAc,EAAE,MAAM,GAAG,UAAU,CAiDjG"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { VaultError as e } from "./errors.js";
|
|
2
|
+
//#region src/versioned-codec.ts
|
|
3
|
+
function t(t, n) {
|
|
4
|
+
if (t.length === 0) throw new e("createVersionedCodec: at least one version entry is required");
|
|
5
|
+
let r = /* @__PURE__ */ new Map();
|
|
6
|
+
for (let n of t) {
|
|
7
|
+
if (!Number.isInteger(n.version) || n.version < 0) throw new e(`createVersionedCodec: version must be a non-negative integer, got ${String(n.version)}`);
|
|
8
|
+
if (r.has(n.version)) throw new e(`createVersionedCodec: duplicate version ${String(n.version)}`);
|
|
9
|
+
r.set(n.version, n.codec);
|
|
10
|
+
}
|
|
11
|
+
if (!r.has(n)) throw new e(`createVersionedCodec: currentVersion ${String(n)} is not listed in versions`);
|
|
12
|
+
let i = r.get(n);
|
|
13
|
+
return {
|
|
14
|
+
decode(e) {
|
|
15
|
+
if (typeof e != "object" || !e || !("__v" in e)) return;
|
|
16
|
+
let t = e, n = t.__v;
|
|
17
|
+
if (typeof n != "number") return;
|
|
18
|
+
let i = r.get(n);
|
|
19
|
+
if (i) return i.decode(t.__d);
|
|
20
|
+
},
|
|
21
|
+
encode(e, t) {
|
|
22
|
+
return {
|
|
23
|
+
__d: i.encode(e, t),
|
|
24
|
+
__v: n
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { t as createVersionedCodec };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=versioned-codec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"versioned-codec.js","names":[],"sources":["../src/versioned-codec.ts"],"sourcesContent":["import type { VaultCodec } from './ttl';\n\nimport { VaultError } from './errors';\n\n/**\n * A single version entry in a `createVersionedCodec` definition.\n * `version` must be a non-negative integer. `codec` is the codec responsible for\n * encoding/decoding records that carry this version number.\n */\nexport type CodecVersion = {\n codec: VaultCodec;\n version: number;\n};\n\n/**\n * Creates a versioned codec that prepends a `__v` field to every encoded envelope.\n * When decoding, the `__v` field selects the matching codec from `versions`.\n * This allows safe codec upgrades: old records encoded with a previous codec continue\n * to decode correctly as long as the old codec remains in `versions`.\n *\n * ```ts\n * const v1Codec: VaultCodec = { ... };\n * const v2Codec: VaultCodec = { ... }; // newer, more compact format\n *\n * const codec = createVersionedCodec([\n * { version: 1, codec: v1Codec },\n * { version: 2, codec: v2Codec },\n * ], 2); // write new records with version 2\n *\n * const db = createMemory({ schema, codec });\n * // Old records (version 1) are decoded by v1Codec; new records use v2Codec.\n * ```\n *\n * **Rules:**\n * - `currentVersion` must be one of the versions listed in `versions`.\n * - Version numbers must be unique non-negative integers.\n * - The current version's codec is used for all new writes.\n * - Unrecognised `__v` values cause `decode` to return `undefined` (treated as corrupt).\n * - **Migration note:** records written by any other codec (including `defaultCodec`) lack\n * the `__v` field and will decode as `undefined` — they will be treated as missing/expired.\n * Perform a data migration or clear the store before switching to a versioned codec.\n * - **Secondary index caveat:** the `{ __d, __v }` envelope replaces the default\n * `{ value, expiresAt? }` shape, so IndexedDB secondary indexes (created via `.index()`\n * with keyPath `value.<field>`) silently stop matching. Don't combine a versioned codec\n * with `.index()` on the same table.\n */\nexport function createVersionedCodec(versions: CodecVersion[], currentVersion: number): VaultCodec {\n if (versions.length === 0) {\n throw new VaultError('createVersionedCodec: at least one version entry is required');\n }\n\n const byVersion = new Map<number, VaultCodec>();\n\n for (const entry of versions) {\n if (!Number.isInteger(entry.version) || entry.version < 0) {\n throw new VaultError(\n `createVersionedCodec: version must be a non-negative integer, got ${String(entry.version)}`,\n );\n }\n\n if (byVersion.has(entry.version)) {\n throw new VaultError(`createVersionedCodec: duplicate version ${String(entry.version)}`);\n }\n\n byVersion.set(entry.version, entry.codec);\n }\n\n if (!byVersion.has(currentVersion)) {\n throw new VaultError(`createVersionedCodec: currentVersion ${String(currentVersion)} is not listed in versions`);\n }\n\n const writeCodec = byVersion.get(currentVersion)!;\n\n return {\n decode<T>(raw: unknown): { expiresAt?: number; value: T } | undefined {\n if (typeof raw !== 'object' || raw === null || !('__v' in raw)) {\n return undefined;\n }\n\n const wrapper = raw as { __d: unknown; __v: unknown };\n const version = wrapper.__v;\n\n if (typeof version !== 'number') return undefined;\n\n const codec = byVersion.get(version);\n\n if (!codec) return undefined;\n\n return codec.decode<T>(wrapper.__d);\n },\n\n encode<T>(value: T, expiresAt?: number): unknown {\n return { __d: writeCodec.encode(value, expiresAt), __v: currentVersion };\n },\n };\n}\n"],"mappings":";;AA8CA,SAAgB,EAAqB,GAA0B,GAAoC;CACjG,IAAI,EAAS,WAAW,GACtB,MAAM,IAAI,EAAW,8DAA8D;CAGrF,IAAM,oBAAY,IAAI,IAAwB;CAE9C,KAAK,IAAM,KAAS,GAAU;EAC5B,IAAI,CAAC,OAAO,UAAU,EAAM,OAAO,KAAK,EAAM,UAAU,GACtD,MAAM,IAAI,EACR,qEAAqE,OAAO,EAAM,OAAO,GAC3F;EAGF,IAAI,EAAU,IAAI,EAAM,OAAO,GAC7B,MAAM,IAAI,EAAW,2CAA2C,OAAO,EAAM,OAAO,GAAG;EAGzF,EAAU,IAAI,EAAM,SAAS,EAAM,KAAK;CAC1C;CAEA,IAAI,CAAC,EAAU,IAAI,CAAc,GAC/B,MAAM,IAAI,EAAW,wCAAwC,OAAO,CAAc,EAAE,2BAA2B;CAGjH,IAAM,IAAa,EAAU,IAAI,CAAc;CAE/C,OAAO;EACL,OAAU,GAA4D;GACpE,IAAI,OAAO,KAAQ,aAAY,KAAgB,EAAE,SAAS,IACxD;GAGF,IAAM,IAAU,GACV,IAAU,EAAQ;GAExB,IAAI,OAAO,KAAY,UAAU;GAEjC,IAAM,IAAQ,EAAU,IAAI,CAAO;GAE9B,OAEL,OAAO,EAAM,OAAU,EAAQ,GAAG;EACpC;EAEA,OAAU,GAAU,GAA6B;GAC/C,OAAO;IAAE,KAAK,EAAW,OAAO,GAAO,CAAS;IAAG,KAAK;GAAe;EACzE;CACF;AACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vielzeug/vault",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Storage adapter for IndexedDB and localStorage with TTL, reactive signals, and schema validation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"main": "./dist/index.cjs",
|
|
10
|
+
"module": "./dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"source": "./src/index.ts",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"require": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "vite build && pnpm run build:bundle && pnpm run build:types",
|
|
22
|
+
"build:types": "tsc -p tsconfig.declarations.json",
|
|
23
|
+
"fix": "eslint --fix src",
|
|
24
|
+
"lint": "eslint src",
|
|
25
|
+
"prepublishOnly": "pnpm run build",
|
|
26
|
+
"preview": "vite preview",
|
|
27
|
+
"test": "vitest",
|
|
28
|
+
"build:bundle": "vite build --config vite.bundle.config.ts"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public",
|
|
32
|
+
"registry": "https://registry.npmjs.org/"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^26.1.0",
|
|
36
|
+
"fake-indexeddb": "^6.2.5",
|
|
37
|
+
"typescript": "~6.0.3",
|
|
38
|
+
"vite": "^8.1.3",
|
|
39
|
+
"vitest": "^4.1.9"
|
|
40
|
+
}
|
|
41
|
+
}
|