@vielzeug/vault 2.0.1 → 2.1.0

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.
Files changed (61) hide show
  1. package/README.md +42 -6
  2. package/dist/adapter-core.cjs +1 -1
  3. package/dist/adapter-core.cjs.map +1 -1
  4. package/dist/adapter-core.d.ts +1 -0
  5. package/dist/adapter-core.d.ts.map +1 -1
  6. package/dist/adapter-core.js +98 -108
  7. package/dist/adapter-core.js.map +1 -1
  8. package/dist/adapters/indexeddb.cjs +1 -1
  9. package/dist/adapters/indexeddb.cjs.map +1 -1
  10. package/dist/adapters/indexeddb.d.ts.map +1 -1
  11. package/dist/adapters/indexeddb.js +114 -113
  12. package/dist/adapters/indexeddb.js.map +1 -1
  13. package/dist/adapters/memory.cjs +1 -1
  14. package/dist/adapters/memory.js +59 -59
  15. package/dist/adapters/sqlite.cjs +42 -0
  16. package/dist/adapters/sqlite.cjs.map +1 -0
  17. package/dist/adapters/sqlite.d.ts +43 -0
  18. package/dist/adapters/sqlite.d.ts.map +1 -0
  19. package/dist/adapters/sqlite.js +401 -0
  20. package/dist/adapters/sqlite.js.map +1 -0
  21. package/dist/adapters/webstorage.cjs +1 -1
  22. package/dist/adapters/webstorage.js +25 -25
  23. package/dist/index.cjs +1 -1
  24. package/dist/index.d.ts +1 -6
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +4 -8
  27. package/dist/indexeddb.cjs +1 -0
  28. package/dist/indexeddb.d.ts +5 -0
  29. package/dist/indexeddb.d.ts.map +1 -0
  30. package/dist/indexeddb.js +3 -0
  31. package/dist/local-storage.cjs +1 -0
  32. package/dist/local-storage.d.ts +2 -0
  33. package/dist/local-storage.d.ts.map +1 -0
  34. package/dist/local-storage.js +2 -0
  35. package/dist/memory.cjs +1 -0
  36. package/dist/memory.d.ts +2 -0
  37. package/dist/memory.d.ts.map +1 -0
  38. package/dist/memory.js +2 -0
  39. package/dist/session-storage.cjs +1 -0
  40. package/dist/session-storage.d.ts +2 -0
  41. package/dist/session-storage.d.ts.map +1 -0
  42. package/dist/session-storage.js +2 -0
  43. package/dist/sqlite.cjs +1 -0
  44. package/dist/sqlite.d.ts +3 -0
  45. package/dist/sqlite.d.ts.map +1 -0
  46. package/dist/sqlite.js +2 -0
  47. package/dist/types.cjs.map +1 -1
  48. package/dist/types.d.ts +8 -2
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/types.js.map +1 -1
  51. package/dist/vault.cjs +1 -1
  52. package/dist/vault.cjs.map +1 -1
  53. package/dist/vault.iife.js +1 -1
  54. package/dist/vault.iife.js.map +1 -1
  55. package/dist/vault.js +1 -1
  56. package/dist/vault.js.map +1 -1
  57. package/package.json +48 -2
  58. package/dist/streaming.d.ts +0 -2
  59. package/dist/streaming.d.ts.map +0 -1
  60. package/dist/versioned-codec.d.ts +0 -2
  61. package/dist/versioned-codec.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"vault.cjs","names":[],"sources":["../src/errors.ts","../src/_dev.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/types.ts"],"sourcesContent":["/**\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","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","import type { AnySchema, KeyOf, RecordOf, VaultKey } from './types';\n\nimport { error as logError } from './_dev';\nimport { VaultDisposedError, VaultError } from './errors';\n\ntype ObserverListener<T> = (records: T[]) => void;\n\nconst SEP = '\\x00';\n\n/**\n * Type-tagged keys make `1` and `'1'` distinct in string-keyed adapters while\n * keeping the same portable primary-key contract as IndexedDB.\n */\nexport function encodeVaultKey(key: VaultKey): string {\n if (typeof key === 'number') {\n if (!Number.isFinite(key)) throw new VaultError(`primary key must be a finite number, received ${String(key)}`);\n\n return `n:${String(key)}`;\n }\n\n return `s:${encodeURIComponent(key)}`;\n}\n\nexport function encodeDbPrefix(dbName: string): string {\n return `${encodeURIComponent(dbName)}${SEP}`;\n}\n\nexport function encodeStorageKey(dbName: string, table: string, key: VaultKey): string {\n return `${encodeURIComponent(dbName)}${SEP}${encodeURIComponent(table)}${SEP}${encodeVaultKey(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\n/** `observe()` is Vault's single reactivity primitive: a snapshot now, then on mutations. */\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) onError(error);\n else logError('observer notification failed', error);\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(reportObserverError);\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) throw new VaultDisposedError('observer hub is disposed');\n\n if (signal?.aborted) return () => {};\n\n const wrapped = listener as ObserverListener<unknown>;\n const listeners = observers.get(table) ?? new Set<ObserverListener<unknown>>();\n\n observers.set(table, listeners);\n listeners.add(wrapped);\n\n if (immediate) notify(table);\n\n const stop = (): void => {\n const current = observers.get(table);\n\n if (!current) return;\n\n current.delete(wrapped);\n\n if (current.size === 0) observers.delete(table);\n };\n\n signal?.addEventListener('abort', stop, { once: true });\n\n return stop;\n };\n\n return {\n dispose: () => {\n disposed = true;\n observers.clear();\n },\n notify,\n observe,\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 (typeof keyValue !== 'number' && typeof keyValue !== 'string') {\n throw new VaultError(`record in table \"${String(table)}\" must have a string or finite-number key at \"${keyField}\"`);\n }\n\n if (typeof keyValue === 'number' && !Number.isFinite(keyValue)) {\n throw new VaultError(`record in table \"${String(table)}\" must have a finite-number key at \"${keyField}\"`);\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 object> = {\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 object> = {\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 object, 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 stores and IndexedDB transaction callbacks. */\nexport interface QueryBuilder<T extends object, N extends T = T> extends ChainedQuery<T, N, QueryBuilder<T, N>> {\n delete(): Promise<number>;\n}\n\n/* -------------------- Helpers -------------------- */\n\nasync function applyOps<T extends object>(ctx: QueryContext<T>, ops: readonly QueryOp<T>[]): 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 object>(ctx: QueryContext<T>, newSource: () => Promise<T[]>): QueryContext<T> {\n return { deleteMany: ctx.deleteMany, source: newSource };\n}\n\n/* -------------------- Factory -------------------- */\n\nexport function createQueryBuilder<T extends object, 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\ndeclare const ttlMsBrand: unique symbol;\n/** A duration in milliseconds, produced by the `ttl.*` helpers. */\nexport type TtlMs = number & { readonly [ttlMsBrand]: never };\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/** Fixed storage envelope shared by every adapter, so data and indexes stay portable. */\nexport type StoredRecord<T> = { expiresAt?: number; value: T };\n\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 return ttlMs === undefined ? { value } : { expiresAt: Date.now() + ttlMs, value };\n}\n\nexport function unwrapStored<T>(raw: StoredRecord<T>): T | undefined {\n return isExpired(raw.expiresAt) ? undefined : 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\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","import type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n IndexedDbVaultStore,\n KeyOf,\n MetricsEvent,\n RecordOf,\n TransactionContext,\n TtlMs,\n VaultStore,\n} from './types';\n\nimport { VaultDisposedError, VaultError, VaultScopeError } from './errors';\nimport { createObserverHub, 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 await core.clear(table);\n\n if (live > 0) 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\n/** Builds the portable surface first; IndexedDB opts into transactions at its factory boundary. */\nexport function buildAdapterOps<S extends AnySchema>(\n schema: S,\n core: StorageBackend<S>,\n options?: BaseAdapterOptions<S> & {\n onCrossTabMessage?: (notify: (table: keyof S & string) => void) => (() => void) | undefined;\n onMutation?: (table: keyof S & string) => void;\n onTransactions?: (deps: BatchDeps<S>) => void;\n },\n): VaultStore<S> {\n const { logger, onMetrics, 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 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 options?.onTransactions?.({ notifyMutation, validate });\n\n const txCtx = buildTxContext<S, keyof S & string>(schema, core, notifyMutation, validate);\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: VaultStore<S> = {\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 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\n return adapter;\n}\n\n/** IndexedDB is the only backend that can bind this callback to one native transaction. */\nexport function withIndexedDbTransactions<S extends AnySchema>(\n store: VaultStore<S>,\n batch: BatchImpl<S>,\n): IndexedDbVaultStore<S> {\n return Object.assign(store, { batch }) as IndexedDbVaultStore<S>;\n}\n","import type {\n AnySchema,\n BaseAdapterOptions,\n IndexedDbVaultStore,\n KeyOf,\n MigrationFn,\n RecordOf,\n TransactionContext,\n TtlMs,\n} from '../types';\n\nimport {\n buildAdapterOps,\n buildTxContext,\n withIndexedDbTransactions,\n type BatchImpl,\n type StorageBackend,\n} from '../adapter-core';\nimport { VaultDisposedError, VaultError, VaultMigrationError } from '../errors';\nimport { encodeVaultKey, getRecordKey } from '../internal';\nimport { type NativeRange } from '../query';\nimport { isExpired, parseStored, type StoredRecord } 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 object>(\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 object>(\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 storeGet<T extends object>(\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 object>(\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 object>(\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 object>(\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): 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 = parseStored(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 object>(\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 object>(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), encodeVaultKey(key), decode),\n deleteMany: (table, keys) => storeDeleteMany<RecordOf<S, K>>(storeOf(table), keys.map(encodeVaultKey), decode),\n get: (table, key) => storeGet<RecordOf<S, typeof table>>(storeOf(table), encodeVaultKey(key), 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 getMany: (table, keys) =>\n Promise.all(keys.map((key) => storeGet<RecordOf<S, typeof table>>(storeOf(table), encodeVaultKey(key), decode))),\n has: (table, key) => storeHas<RecordOf<S, typeof table>>(storeOf(table), encodeVaultKey(key), decode),\n pruneExpiredInTable: (table) => pruneExpiredInStore(storeOf(table)),\n put(table, value, ttl) {\n return storePutAt(storeOf(table), encodeVaultKey(getRecordKey(schema, table, value)), value, encode, ttl);\n },\n putAll(table, values, ttl) {\n return Promise.all(\n values.map((v) => storePutAt(storeOf(table), encodeVaultKey(getRecordKey(schema, table, v)), 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 function createIndexedDB<S extends AnySchema>(options: IndexedDbOptions<S>): IndexedDbVaultStore<S> {\n const { logger, migrate, name, onMetrics, schema, validators, version = 1 } = 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 // Fixed envelopes keep IndexedDB records and `value.<field>` indexes portable across adapters.\n const decode = <T extends object>(raw: unknown): T | undefined => {\n const stored = parseStored<T>(raw);\n\n return !stored || isExpired(stored.expiresAt) ? undefined : stored.value;\n };\n\n const encode = <T>(value: T, ttl?: TtlMs): StoredRecord<T> => {\n return ttl === undefined ? { value } : { expiresAt: Date.now() + ttl, value };\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, encodeVaultKey(key), 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.map(encodeVaultKey), 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, encodeVaultKey(key), 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 getMany: (table, keys) =>\n keys.length === 0\n ? Promise.resolve([])\n : withStore(table, 'readonly', (s) =>\n Promise.all(keys.map((key) => storeGet<RecordOf<S, typeof table>>(s, encodeVaultKey(key), 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, encodeVaultKey(key), 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))] as const)),\n );\n\n return Object.fromEntries(results);\n },\n\n pruneExpiredInTable: (table) => withStore(table, 'readwrite', (s) => pruneExpiredInStore(s)),\n\n put(table, value, ttl) {\n const key = encodeVaultKey(getRecordKey(schema, table, value));\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, encodeVaultKey(getRecordKey(schema, table, v)), 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 let batch: BatchImpl<S> | undefined;\n const adapter = buildAdapterOps(schema, core, {\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 onTransactions: (deps) => {\n batch = (tables, fn) => idbBatch(tables, fn, deps.notifyMutation, deps.validate);\n },\n schema,\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 const store = {\n ...adapter,\n iterate<K extends keyof S & string>(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 if (!batch) throw new VaultError('IndexedDB transaction capability was not initialized');\n\n return withIndexedDbTransactions(store, batch);\n}\n","import type { AnySchema, BaseAdapterOptions, KeyOf, RecordOf, VaultStore } from '../types';\n\nimport { buildAdapterOps, type StorageBackend } from '../adapter-core';\nimport { encodeVaultKey, getRecordKey } from '../internal';\nimport { isExpired, parseStored, 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\ntype MemoryOptions<S extends AnySchema> = BaseAdapterOptions<S> & { name?: string };\n\nfunction isBroadcastMessage(value: unknown): value is MemoryBroadcastMsg {\n if (typeof value !== 'object' || value === null) return false;\n\n const message = value as Record<string, unknown>;\n\n if (typeof message['table'] !== 'string' || typeof message['type'] !== 'string') return false;\n\n if (message['table'] === '__proto__' || message['table'] === 'constructor' || message['table'] === 'prototype') {\n return false;\n }\n\n switch (message['type']) {\n case 'clear':\n return true;\n case 'delete':\n return typeof message['key'] === 'string';\n case 'deleteMany':\n return Array.isArray(message['keys']) && message['keys'].every((key) => typeof key === 'string');\n case 'put':\n return typeof message['key'] === 'string' && parseStored(message['stored']) !== undefined;\n case 'putAll':\n return (\n Array.isArray(message['entries']) &&\n message['entries'].every(\n (entry) =>\n typeof entry === 'object' &&\n entry !== null &&\n typeof (entry as Record<string, unknown>)['key'] === 'string' &&\n parseStored((entry as Record<string, unknown>)['stored']) !== undefined,\n )\n );\n default:\n return false;\n }\n}\n\n/** Memory uses tagged Map keys so numeric and string primary keys never collide. */\nexport function createMemory<S extends AnySchema>(options: MemoryOptions<S>): VaultStore<S> {\n const { logger, name, onMetrics, schema, validators } = options;\n const tables = new Map(Object.keys(schema).map((table) => [table, new Map<string, StoredRecord<unknown>>()]));\n const getTable = (table: string): Map<string, StoredRecord<unknown>> => tables.get(table)!;\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(table) {\n getTable(table).clear();\n channel?.postMessage({ table, type: 'clear' });\n },\n async count(table) {\n const store = getTable(table);\n let count = 0;\n\n for (const [key, stored] of store) {\n const record = parseStored(stored);\n\n if (!record || isExpired(record.expiresAt)) store.delete(key);\n else count += 1;\n }\n\n return count;\n },\n async delete(table, key) {\n const store = getTable(table);\n const encodedKey = encodeVaultKey(key);\n const record = parseStored(store.get(encodedKey));\n\n store.delete(encodedKey);\n\n const deleted = record !== undefined && !isExpired(record.expiresAt);\n\n if (deleted) channel?.postMessage({ key: encodedKey, table, type: 'delete' });\n\n return deleted;\n },\n async deleteMany(table, keys) {\n let deleted = 0;\n const deletedKeys: string[] = [];\n\n for (const key of keys) {\n const encodedKey = encodeVaultKey(key);\n const record = parseStored(getTable(table).get(encodedKey));\n\n getTable(table).delete(encodedKey);\n\n if (record !== undefined && !isExpired(record.expiresAt)) {\n deleted += 1;\n deletedKeys.push(encodedKey);\n }\n }\n\n if (deletedKeys.length > 0) channel?.postMessage({ keys: deletedKeys, table, type: 'deleteMany' });\n\n return deleted;\n },\n dispose: channel ? async () => channel.close() : undefined,\n async get(table, key) {\n const store = getTable(table);\n const encodedKey = encodeVaultKey(key);\n const record = parseStored<RecordOf<S, typeof table>>(store.get(encodedKey));\n\n if (!record || isExpired(record.expiresAt)) {\n store.delete(encodedKey);\n\n return undefined;\n }\n\n return record.value;\n },\n async getAll(table) {\n const store = getTable(table);\n const values: RecordOf<S, typeof table>[] = [];\n\n for (const [key, stored] of store) {\n const record = parseStored<RecordOf<S, typeof table>>(stored);\n\n if (!record || isExpired(record.expiresAt)) store.delete(key);\n else values.push(record.value);\n }\n\n return values;\n },\n async getRawCount(table) {\n return getTable(table).size;\n },\n async has(table, key) {\n return (await core.get(table, key)) !== undefined;\n },\n async pruneExpiredInTable(table) {\n const store = getTable(table);\n let pruned = 0;\n\n for (const [key, stored] of store) {\n const record = parseStored(stored);\n\n if (!record || isExpired(record.expiresAt)) {\n store.delete(key);\n pruned += 1;\n }\n }\n\n return pruned;\n },\n async put(table, value, ttl) {\n const key = encodeVaultKey(getRecordKey(schema, table, value));\n const stored: StoredRecord<unknown> = ttl === undefined ? { value } : { expiresAt: Date.now() + ttl, value };\n\n getTable(table).set(key, stored);\n channel?.postMessage({ key, stored, table, type: 'put' });\n },\n async putAll(table, values, ttl) {\n const expiresAt = ttl === undefined ? undefined : Date.now() + ttl;\n const entries: Array<{ key: string; stored: StoredRecord<unknown> }> = [];\n\n for (const value of values) {\n const key = encodeVaultKey(getRecordKey(schema, table, value));\n const stored: StoredRecord<unknown> = expiresAt === undefined ? { value } : { expiresAt, value };\n\n getTable(table).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 return buildAdapterOps(schema, core, {\n logger,\n onCrossTabMessage: channel\n ? (notify) => {\n channel.onmessage = (event: MessageEvent<unknown>) => {\n if (!isBroadcastMessage(event.data)) return;\n\n const message = event.data;\n\n if (!tables.has(message.table)) return;\n\n const store = getTable(message.table);\n const validateStored = (key: string, stored: StoredRecord<unknown>): boolean => {\n const parsed = parseStored(stored);\n const keyField = schema[message.table as keyof S & string]?.key;\n const validator = validators?.[message.table as keyof S];\n\n if (!parsed || !keyField) return false;\n\n try {\n const value = validator ? validator.parse(parsed.value) : parsed.value;\n\n if (encodeVaultKey((value as Record<string, unknown>)[keyField] as KeyOf<S, keyof S>) !== key)\n return false;\n\n store.set(key, parsed.expiresAt === undefined ? { value } : { expiresAt: parsed.expiresAt, value });\n\n return true;\n } catch {\n return false;\n }\n };\n\n switch (message.type) {\n case 'clear':\n store.clear();\n notify(message.table as keyof S & string);\n break;\n case 'delete':\n store.delete(message.key);\n notify(message.table as keyof S & string);\n break;\n case 'deleteMany':\n for (const key of message.keys) store.delete(key);\n notify(message.table as keyof S & string);\n break;\n case 'put':\n if (validateStored(message.key, message.stored)) notify(message.table as keyof S & string);\n\n break;\n case 'putAll':\n if (message.entries.some((entry) => validateStored(entry.key, entry.stored))) {\n notify(message.table as keyof S & string);\n }\n\n break;\n }\n };\n\n return () => {\n channel.onmessage = null;\n };\n }\n : undefined,\n onMetrics,\n schema,\n validators,\n });\n}\n","import type { AnySchema, BaseAdapterOptions, KeyOf, RecordOf, TtlMs, VaultStore } 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 { isExpired, parseStored } 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): VaultStore<S> {\n const { getStorage, logger, name, onMetrics, onQuotaExceeded, schema, storageLabel, validators } = 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 object>(storageKey: string): T | undefined => {\n const raw = storage().getItem(storageKey);\n\n if (!raw) return undefined;\n\n try {\n const stored = parseStored<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, 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, 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, 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, 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 = parseStored(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, getRecordKey(schema, table, value));\n const expiresAt = ttl !== undefined ? Date.now() + ttl : undefined;\n\n writeItem(table, storageKey, expiresAt === undefined ? { value } : { expiresAt, value });\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, getRecordKey(schema, table, value));\n\n writeItem(table, storageKey, expiresAt === undefined ? { value } : { expiresAt, value });\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 validators,\n });\n}\n\nexport function createLocalStorage<S extends AnySchema>(options: WebStorageOptions<S>): VaultStore<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>): VaultStore<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 { AnySchema, VaultStore } 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<VaultStore<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","import type { QueryBuilder } from './query';\n\nimport { VaultError } from './errors';\nimport { assertTtlMs, type TtlMs } from './ttl';\n\nexport type { TtlMs };\n\n/** Portable primary-key values. Vault preserves their type and encodes them distinctly at rest. */\nexport type VaultKey = number | string;\n\ndeclare const schemaEntryBrand: unique symbol;\n\n/** A typed table definition whose primary-key field must hold a portable Vault key. */\nexport type SchemaEntry<T extends object, Key extends keyof T & string = keyof T & string> = T[Key] extends VaultKey\n ? {\n defaultTtl?: TtlMs;\n /** IndexedDB creates these as `value.<field>` indexes; other stores filter in memory. */\n indexes?: readonly (keyof T & string)[];\n key: Key;\n readonly [schemaEntryBrand]?: T;\n }\n : never;\n\nexport type AnySchema = Record<string, { defaultTtl?: TtlMs; indexes?: readonly string[]; key: string }>;\n\nexport type TableBuilder<T extends object, Key extends keyof T & string = keyof T & string> = SchemaEntry<T, Key> & {\n index: <F extends keyof T & string>(field: F) => TableBuilder<T, Key>;\n ttl: (ms: TtlMs) => TableBuilder<T, Key>;\n};\n\n/** Define a table once so all stores share the same record and portable-key contract. */\nexport function table<T extends object, Key extends keyof T & string = keyof T & string>(\n key: Key & (T[Key] extends VaultKey ? unknown : never),\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 } as unknown as SchemaEntry<T, Key>);\n}\n\nexport type RecordOf<S extends AnySchema, K extends keyof S> =\n S[K] extends SchemaEntry<infer R, infer _Key> ? R : never;\nexport type KeyOf<S extends AnySchema, K extends keyof S> = Extract<\n S[K] extends SchemaEntry<infer R, infer Key> ? R[Key] : never,\n VaultKey\n>;\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\nexport interface VaultLogger {\n error(messageOrContext?: Record<string, unknown> | Error | string, message?: string): void;\n}\n\nexport interface RecordValidator<T> {\n parse(value: unknown): T;\n}\n\nexport type TableValidators<S extends AnySchema> = {\n [K in keyof S]?: RecordValidator<RecordOf<S, K>>;\n};\n\nexport type Observer<T> = (records: T[]) => void;\nexport type Unsubscribe = () => void;\n\n/** Shared factory options. Values always use Vault's fixed `{ value, expiresAt? }` envelope. */\nexport type BaseAdapterOptions<S extends AnySchema> = {\n logger?: VaultLogger;\n onMetrics?: (event: MetricsEvent) => void;\n schema: S;\n validators?: TableValidators<S>;\n};\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 table: string;\n};\n\nexport type DebugStats = { expiredCount: number; recordCount: number };\nexport type DebugInfo<S extends AnySchema> = { tables: Array<{ name: keyof S & string } & DebugStats> };\n\n/** Methods available within an IndexedDB transaction. */\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 deleteMany<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<number>;\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 getAll<T extends K>(table: T): Promise<RecordOf<S, T>[]>;\n getMany<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<Array<RecordOf<S, T> | undefined>>;\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 isEmpty<T extends K>(table: T): Promise<boolean>;\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 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/** Portable API returned by memory and Web Storage factories. */\nexport interface VaultStore<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 deleteMany<K extends keyof S & string>(table: K, keys: KeyOf<S, K>[]): Promise<number>;\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 getAll<K extends keyof S & string>(table: K): Promise<RecordOf<S, K>[]>;\n getMany<K extends keyof S & string>(table: K, keys: KeyOf<S, K>[]): Promise<Array<RecordOf<S, K> | undefined>>;\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 isEmpty<K extends keyof S & string>(table: K): Promise<boolean>;\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 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 pruneExpired(): Promise<Record<keyof S & string, number>>;\n debug(): Promise<DebugInfo<S>>;\n observe<K extends keyof S & string>(\n table: K,\n listener: Observer<RecordOf<S, K>>,\n options?: { immediate?: boolean; signal?: AbortSignal },\n ): Unsubscribe;\n dispose(): Promise<void>;\n readonly disposed: boolean;\n readonly disposalSignal: AbortSignal;\n [Symbol.asyncDispose](): Promise<void>;\n}\n\n/** IndexedDB-only guarantees: cursor iteration and atomic, scoped transactions. */\nexport interface IndexedDbVaultStore<S extends AnySchema> extends VaultStore<S> {\n batch<K extends keyof S & string, R>(\n tables: readonly K[],\n fn: (tx: TransactionContext<S, K>) => Promise<R>,\n ): Promise<R>;\n iterate<K extends keyof S & string>(table: K): AsyncIterable<RecordOf<S, K>>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"vault.cjs","names":[],"sources":["../src/errors.ts","../src/_dev.ts","../src/prune.ts","../src/ttl.ts","../src/types.ts"],"sourcesContent":["/**\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","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","import type { AnySchema, VaultStore } 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<VaultStore<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","import { VaultError } from './errors';\n\ndeclare const ttlMsBrand: unique symbol;\n/** A duration in milliseconds, produced by the `ttl.*` helpers. */\nexport type TtlMs = number & { readonly [ttlMsBrand]: never };\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/** Fixed storage envelope shared by every adapter, so data and indexes stay portable. */\nexport type StoredRecord<T> = { expiresAt?: number; value: T };\n\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 return ttlMs === undefined ? { value } : { expiresAt: Date.now() + ttlMs, value };\n}\n\nexport function unwrapStored<T>(raw: StoredRecord<T>): T | undefined {\n return isExpired(raw.expiresAt) ? undefined : 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\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","/// <reference lib=\"dom\" />\n\nimport type { QueryBuilder } from './query';\n\nimport { VaultError } from './errors';\nimport { assertTtlMs, type TtlMs } from './ttl';\n\nexport type { TtlMs };\n\n/** Portable primary-key values. Vault preserves their type and encodes them distinctly at rest. */\nexport type VaultKey = number | string;\n\ndeclare const schemaEntryBrand: unique symbol;\n\n/** A typed table definition whose primary-key field must hold a portable Vault key. */\nexport type SchemaEntry<T extends object, Key extends keyof T & string = keyof T & string> = T[Key] extends VaultKey\n ? {\n defaultTtl?: TtlMs;\n /** IndexedDB creates these as `value.<field>` indexes; other stores filter in memory. */\n indexes?: readonly (keyof T & string)[];\n key: Key;\n readonly [schemaEntryBrand]?: T;\n }\n : never;\n\nexport type AnySchema = Record<string, { defaultTtl?: TtlMs; indexes?: readonly string[]; key: string }>;\n\nexport type TableBuilder<T extends object, Key extends keyof T & string = keyof T & string> = SchemaEntry<T, Key> & {\n index: <F extends keyof T & string>(field: F) => TableBuilder<T, Key>;\n ttl: (ms: TtlMs) => TableBuilder<T, Key>;\n};\n\n/** Define a table once so all stores share the same record and portable-key contract. */\nexport function table<T extends object, Key extends keyof T & string = keyof T & string>(\n key: Key & (T[Key] extends VaultKey ? unknown : never),\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 } as unknown as SchemaEntry<T, Key>);\n}\n\nexport type RecordOf<S extends AnySchema, K extends keyof S> =\n S[K] extends SchemaEntry<infer R, infer _Key> ? R : never;\nexport type KeyOf<S extends AnySchema, K extends keyof S> = Extract<\n S[K] extends SchemaEntry<infer R, infer Key> ? R[Key] : never,\n VaultKey\n>;\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\nexport interface VaultLogger {\n error(messageOrContext?: Record<string, unknown> | Error | string, message?: string): void;\n}\n\nexport interface RecordValidator<T> {\n parse(value: unknown): T;\n}\n\nexport type TableValidators<S extends AnySchema> = {\n [K in keyof S]?: RecordValidator<RecordOf<S, K>>;\n};\n\nexport type Observer<T> = (records: T[]) => void;\nexport type Unsubscribe = () => void;\n\n/** Shared factory options. Values always use Vault's fixed `{ value, expiresAt? }` envelope. */\nexport type BaseAdapterOptions<S extends AnySchema> = {\n logger?: VaultLogger;\n onMetrics?: (event: MetricsEvent) => void;\n schema: S;\n validators?: TableValidators<S>;\n};\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 table: string;\n};\n\nexport type DebugStats = { expiredCount: number; recordCount: number };\nexport type DebugInfo<S extends AnySchema> = { tables: Array<{ name: keyof S & string } & DebugStats> };\n\n/** Methods available within an IndexedDB transaction. */\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 deleteMany<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<number>;\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 getAll<T extends K>(table: T): Promise<RecordOf<S, T>[]>;\n getMany<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<Array<RecordOf<S, T> | undefined>>;\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 isEmpty<T extends K>(table: T): Promise<boolean>;\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 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/** Portable API returned by memory and Web Storage factories. */\nexport interface VaultStore<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 deleteMany<K extends keyof S & string>(table: K, keys: KeyOf<S, K>[]): Promise<number>;\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 getAll<K extends keyof S & string>(table: K): Promise<RecordOf<S, K>[]>;\n getMany<K extends keyof S & string>(table: K, keys: KeyOf<S, K>[]): Promise<Array<RecordOf<S, K> | undefined>>;\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 isEmpty<K extends keyof S & string>(table: K): Promise<boolean>;\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 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 pruneExpired(): Promise<Record<keyof S & string, number>>;\n debug(): Promise<DebugInfo<S>>;\n observe<K extends keyof S & string>(\n table: K,\n listener: Observer<RecordOf<S, K>>,\n options?: { immediate?: boolean; signal?: AbortSignal },\n ): Unsubscribe;\n dispose(): Promise<void>;\n readonly disposed: boolean;\n readonly disposalSignal: AbortSignal;\n [Symbol.asyncDispose](): Promise<void>;\n}\n\n/** Atomic, scoped transactions supplied by storage engines that support them. */\nexport interface TransactionalVaultStore<S extends AnySchema> extends VaultStore<S> {\n batch<K extends keyof S & string, R>(\n tables: readonly K[],\n fn: (tx: TransactionContext<S, K>) => Promise<R>,\n ): Promise<R>;\n}\n\n/** Lazy record iteration supplied by storage engines that support it. */\nexport interface IterableVaultStore<S extends AnySchema> extends VaultStore<S> {\n iterate<K extends keyof S & string>(table: K): AsyncIterable<RecordOf<S, K>>;\n}\n\n/** IndexedDB-only guarantees: cursor iteration and atomic, scoped transactions. */\nexport interface IndexedDbVaultStore<S extends AnySchema> extends TransactionalVaultStore<S>, IterableVaultStore<S> {}\n"],"mappings":"mEAIA,IAAa,EAAb,MAAa,UAAmB,KAAM,CACpC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KAEvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CAEA,OAAO,GAAG,EAAiC,CACzC,OAAO,aAAe,CACxB,CACF,EAGa,EAAb,cAAwC,CAAW,CACjD,YAAY,EAAU,sBAAuB,EAAqB,CAChE,MAAM,EAAS,CAAI,CACrB,CACF,EAGa,EAAb,cAAqC,CAAW,CAAC,EAGpC,EAAb,cAAqC,CAAW,CAAC,EAGpC,EAAb,cAAyC,CAAW,CAAC,EEbrD,SAAgB,EACd,EACA,EAiBY,CACZ,GAAI,CAAC,OAAO,SAAS,EAAQ,QAAQ,GAAK,EAAQ,UAAY,EAC5D,MAAM,IAAI,EAAW,iEAAiE,EAGxF,IAAI,EAAS,GAEP,MAAmB,CACvB,EAAS,GACT,cAAc,CAAE,CAClB,EAEA,EAAQ,QAAQ,iBAAiB,QAAS,EAAM,CAAE,KAAM,EAAK,CAAC,EAE9D,IAAM,EAAK,gBAAkB,CACtB,GAEL,EAAa,aAAa,CAAC,CAAC,MAAO,GAAQ,CACrC,aAAe,GACjB,EAAS,GACT,cAAc,CAAE,GACP,EAAQ,QACjB,EAAQ,QAAQ,CAAG,EAEd,GAA6E,OAAO,CAAG,EAAvF,CAET,CAAC,CACH,EAAG,EAAQ,QAAQ,EAEnB,OAAO,CACT,CC7DA,IAAa,EAAM,CACjB,KAAO,GAAc,EAAY,EAAY,EAAG,UAAU,EAAI,MAAY,mBAAmB,EAC7F,MAAQ,GAAc,EAAY,EAAY,EAAG,WAAW,EAAI,KAAW,oBAAoB,EAC/F,QAAU,GAAc,EAAY,EAAY,EAAG,aAAa,EAAI,IAAQ,sBAAsB,EAClG,GAAK,GAAc,EAAY,EAAG,QAAQ,EAC1C,QAAU,GAAc,EAAY,EAAY,EAAG,aAAa,EAAI,IAAM,sBAAsB,CAClG,EAKA,SAAgB,EAAU,EAAwC,CAChE,OAAO,IAAc,IAAA,IAAa,KAAK,IAAI,GAAK,CAClD,CAEA,SAAgB,EAAY,EAAe,EAAuB,CAChE,GAAI,CAAC,OAAO,SAAS,CAAK,GAAK,GAAS,EACtC,MAAM,IAAI,EAAW,GAAG,EAAO,+CAA+C,OAAO,CAAK,GAAG,EAG/F,OAAO,CACT,CCMA,SAAgB,EACd,EACsB,CACtB,SAAS,EAAY,EAAkD,CACrE,MAAO,CACL,GAAG,EACH,MAAoC,GAAmC,CACrE,IAAM,EAAW,EAAM,SAAW,CAAC,EAEnC,GAAI,EAAQ,SAAS,CAAK,EACxB,MAAM,IAAI,EAAW,gBAAgB,EAAM,wBAAwB,EAGrE,OAAO,EAAY,CAAE,GAAG,EAAO,QAAS,CAAC,GAAG,EAAS,CAAK,CAAE,CAAC,CAC/D,EACA,IAAM,IACJ,EAAY,EAAI,WAAW,EAEpB,EAAY,CAAE,GAAG,EAAO,WAAY,CAAG,CAAC,EAEnD,CACF,CAEA,OAAO,EAAY,CAAE,KAAI,CAAmC,CAC9D"}
@@ -1,2 +1,2 @@
1
- var Vault=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},n=class extends t{constructor(e=`adapter is disposed`,t){super(e,t)}},r=class extends t{},i=class extends t{},a=class extends t{},o=`\0`;function s(e){if(typeof e==`number`){if(!Number.isFinite(e))throw new t(`primary key must be a finite number, received ${String(e)}`);return`n:${String(e)}`}return`s:${encodeURIComponent(e)}`}function c(e){return`${encodeURIComponent(e)}${o}`}function l(e,t,n){return`${encodeURIComponent(e)}${o}${encodeURIComponent(t)}${o}${s(n)}`}function u(e,t){return`${encodeURIComponent(e)}${o}${encodeURIComponent(t)}${o}`}function d(e,t){if(!t)return;let n=c(e);if(!t.startsWith(n))return;let r=t.slice(n.length),i=r.indexOf(o);if(i!==-1)try{return decodeURIComponent(r.slice(0,i))}catch{return}}function f(e,t){let r=new Map,i=!1,a=e=>{t&&t(e)},o=t=>{if(i)return;let n=r.get(t);!n||n.size===0||e(t).then(e=>{if(i)return;let n=r.get(t);if(!(!n||n.size===0))for(let t of n)try{t(e)}catch(e){a(e)}}).catch(a)};return{dispose:()=>{i=!0,r.clear()},notify:o,observe:(e,t,{immediate:a=!0,signal:s}={})=>{if(i)throw new n(`observer hub is disposed`);if(s?.aborted)return()=>{};let c=t,l=r.get(e)??new Set;r.set(e,l),l.add(c),a&&o(e);let u=()=>{let t=r.get(e);t&&(t.delete(c),t.size===0&&r.delete(e))};return s?.addEventListener(`abort`,u,{once:!0}),u}}}function p(e,n,r){let i=String(e[n].key),a=r[i];if(typeof a!=`number`&&typeof a!=`string`)throw new t(`record in table "${String(n)}" must have a string or finite-number key at "${i}"`);if(typeof a==`number`&&!Number.isFinite(a))throw new t(`record in table "${String(n)}" must have a finite-number key at "${i}"`);return a}async function m(e,t){let n=await e.source();for(let e of t)n=e.apply(n);return n}function h(e,n){if(!Number.isInteger(e)||e<0)throw new t(`${n} must be a non-negative integer`);return e}function g(e,t){return{deleteMany:e.deleteMany,source:t}}function _(e,n=[]){let r=t=>_(e,[...n,t]);return{between(t,i,a){let o=String(t);if(n.length===0){if(e.getRange&&e.keyField===o)return _(g(e,()=>e.getRange({lower:i,type:`between`,upper:a})),n);if(e.getIndexRange&&e.indexedFields?.has(o))return _(g(e,()=>e.getIndexRange(o,{lower:i,type:`between`,upper:a})),n)}return r({apply:e=>e.filter(e=>{let n=e[t];return n>=i&&n<=a})})},count(){return m(e,n).then(e=>e.length)},async delete(){if(!e.deleteMany)throw new t(`query.delete is not available for this adapter context`);let r=await m(e,n);return e.deleteMany(r)},equals(t,r){let i=String(t);if(n.length===0){if(e.getRange&&e.keyField===i)return _(g(e,()=>e.getRange({type:`eq`,value:r})));if(e.getIndexRange&&e.indexedFields?.has(i))return _(g(e,()=>e.getIndexRange(i,{type:`eq`,value:r})))}return _(e,[...n,{apply:e=>e.filter(e=>e[t]===r)}])},async exists(){return n.length===0?e.source().then(e=>e.length>0):m(e,n).then(e=>e.length>0)},filter(e){return r({apply:t=>t.filter(e)})},first(){return n.length===0?e.source().then(e=>e[0]):m(e,n).then(e=>e[0])},limit(e){let t=h(e,`query.limit`);return r({apply:e=>e.slice(0,t),isNonFilter:!0})},offset(e){let t=h(e,`query.offset`);return r({apply:e=>e.slice(t),isNonFilter:!0})},orderBy(e,t=`asc`){return r({apply:n=>{let r=t===`asc`?1:-1;return[...n].sort((t,n)=>{let i=t[e],a=n[e];return i===a?0:i>a?r:-r})},isNonFilter:!0})},startsWith(t,i,{ignoreCase:a=!1}={}){let o=String(t);if(!a&&i.length>0&&n.length===0){if(e.getRange&&e.keyField===o)return _(g(e,()=>e.getRange({prefix:i,type:`starts`})),n);if(e.getIndexRange&&e.indexedFields?.has(o))return _(g(e,()=>e.getIndexRange(o,{prefix:i,type:`starts`})),n)}let s=a?i.toLowerCase():i;return r({apply:e=>e.filter(e=>{let n=e[t];return typeof n==`string`&&(a?n.toLowerCase():n).startsWith(s)})})},toArray(){return m(e,n)},totalCount(){return m(e,n.filter(e=>!e.isNonFilter)).then(e=>e.length)}}}var v={days:e=>b(b(e,`ttl.days`)*864e5,`ttl.days (result)`),hours:e=>b(b(e,`ttl.hours`)*36e5,`ttl.hours (result)`),minutes:e=>b(b(e,`ttl.minutes`)*6e4,`ttl.minutes (result)`),ms:e=>b(e,`ttl.ms`),seconds:e=>b(b(e,`ttl.seconds`)*1e3,`ttl.seconds (result)`)};function y(e){return e!==void 0&&Date.now()>=e}function b(e,n){if(!Number.isFinite(e)||e<=0)throw new t(`${n} expected a finite positive number, received ${String(e)}`);return e}function x(e){if(typeof e!=`object`||!e||!(`value`in e))return;let t=e;if(!(t.expiresAt!==void 0&&(typeof t.expiresAt!=`number`||!Number.isFinite(t.expiresAt))))return t}var S=typeof performance<`u`?()=>performance.now():()=>Date.now();function C(e,t,n){return n!==void 0&&b(n,`put/putAll`),n??e[t].defaultTtl}function w(e,n,r,i,a){let o=p(e,n,i);if(o!==r)throw new t(`${a}: key field "${e[n].key}" must be "${String(r)}" but got "${String(o)}" in table "${n}"`)}function T(e,t,n){return e.getMany?e.getMany(t,n):Promise.all(n.map(n=>e.get(t,n)))}function E(e,t,n,r){let i=n[e],a=i.key,o=i.indexes?new Set(i.indexes):void 0;return{deleteMany:async i=>{if(i.length===0)return 0;let a=i.map(t=>p(n,e,t)),o=await t.deleteMany(e,a);return o>0&&r(e),o},getIndexRange:t.getByIndexRange?(n,r)=>t.getByIndexRange(e,n,r):void 0,getRange:t.getByKeyRange?n=>t.getByKeyRange(e,n):void 0,indexedFields:o,keyField:a,source:()=>t.getAll(e)}}function D(e,t,n,i,a){let o=i??((e,t)=>t),s=a?e=>{if(!a.has(e))throw new r(`table "${e}" is not part of this batch scope`)}:e=>{};return{async clear(e){s(e);let r=await t.count(e);await t.clear(e),r>0&&n(e)},async count(e){return s(e),t.count(e)},async delete(e,r){s(e);let i=await t.delete(e,r);return i&&n(e),i},async deleteMany(e,r){s(e);let i=await t.deleteMany(e,r);return i>0&&n(e),i},async entries(n){s(n);let r=await t.getAll(n),i=e[n].key;return r.map(e=>[e[i],e])},async get(e,n){return s(e),t.get(e,n)},async getAll(e){return s(e),t.getAll(e)},async getMany(e,n){return s(e),T(t,e,n)},async getOrDefault(r,i,a,c){s(r);let l=await t.get(r,i);if(l!==void 0)return l;let u=a();return w(e,r,i,u,`getOrDefault: defaultFn()`),await t.put(r,o(r,u),C(e,r,c)),n(r),u},async has(e,n){return s(e),t.has(e,n)},async isEmpty(e){return s(e),await t.count(e)===0},async keys(n,r){if(s(n),r){let i=await t.getAll(n),a=e[n].key;return i.filter(r).map(e=>e[a])}if(t.getAllKeys)return t.getAllKeys(n);let i=await t.getAll(n),a=e[n].key;return i.map(e=>e[a])},async put(r,i,a){s(r),await t.put(r,o(r,i),C(e,r,a)),n(r)},async putAll(r,i,a){if(s(r),i.length===0)return;let c=i.map(e=>o(r,e));await t.putAll(r,c,C(e,r,a)),n(r)},query(r){return s(r),_(E(r,t,e,n))},async update(r,i,a,c){s(r);let l=await t.get(r,i);if(!l)return;let u={...l,...a};return w(e,r,i,u,`update`),await t.put(r,o(r,u),C(e,r,c)),n(r),u},async upsert(r,i,a,c){s(r);let l=a(await t.get(r,i));return w(e,r,i,l,`upsert: fn()`),await t.put(r,o(r,l),C(e,r,c)),n(r),l}}}function O(e,r,i){let{logger:a,onMetrics:o,validators:s}=i??{},c=f(e=>r.getAll(e),a?e=>a.error(e instanceof Error?e:Error(String(e)),`[vault] observer notification failed`):void 0),l=new Map,u=async e=>{if(l.has(e))return l.get(e);let t=await r.count(e);return l.set(e,t),t},d=e=>{l.delete(e),c.notify(e),i?.onMutation?.(e)},p=e=>{l.delete(e),c.notify(e)},m=r.pruneAllExpired?.bind(r),h=r.getRawCount?.bind(r),g=i?.onCrossTabMessage?.(p)??void 0,v=(e,n)=>{let r=s?.[e];if(!r)return n;try{return r.parse(n)}catch(n){throw new t(`validation failed for table "${e}"`,{cause:n})}},y=(e,t,n)=>{if(!o)return n();let r=S();return n().finally(()=>o({duration:S()-r,operation:t,table:e}))};i?.onTransactions?.({notifyMutation:d,validate:v});let b=D(e,r,d,v),x=new AbortController,C=!1,w=()=>{if(C)throw new n},T={async clear(e){w(),await y(e,`clear`,()=>b.clear(e)),l.set(e,0)},async count(e){return w(),y(e,`count`,()=>u(e))},async debug(){w();let t=Object.keys(e);return{tables:await Promise.all(t.map(async e=>{let t=h?await h(e):void 0,n=await r.count(e);return l.set(e,n),{expiredCount:t===void 0?0:Math.max(0,t-n),name:e,recordCount:n}}))}},async delete(e,t){return w(),y(e,`delete`,()=>b.delete(e,t))},async deleteMany(e,t){return w(),y(e,`deleteMany`,()=>b.deleteMany(e,t))},get disposalSignal(){return x.signal},async dispose(){C||(C=!0,x.abort(),g?.(),c.dispose(),await r.dispose?.())},get disposed(){return C},async entries(e){return w(),y(e,`entries`,()=>b.entries(e))},async get(e,t){return w(),y(e,`get`,()=>b.get(e,t))},async getAll(e){return w(),y(e,`getAll`,()=>b.getAll(e))},async getMany(e,t){return w(),y(e,`getMany`,()=>b.getMany(e,t))},async getOrDefault(e,t,n,r){return w(),y(e,`getOrDefault`,()=>b.getOrDefault(e,t,n,r))},async has(e,t){return w(),y(e,`has`,()=>b.has(e,t))},async isEmpty(e){return w(),y(e,`isEmpty`,async()=>await u(e)===0)},async keys(e,t){return w(),y(e,`keys`,()=>b.keys(e,t))},observe(e,t,n){return w(),c.observe(e,t,n)},async pruneExpired(t){w();let n=t??Object.keys(e);if(!t&&m){let e=await m();for(let[t,n]of Object.entries(e))n>0&&l.delete(t);return e}let i=await Promise.all(n.map(async e=>{let t=await r.pruneExpiredInTable(e);return t>0&&l.delete(e),[e,t]}));return Object.fromEntries(i)},async put(e,t,n){w(),await y(e,`put`,()=>b.put(e,t,n))},async putAll(e,t,n){w(),await y(e,`putAll`,()=>b.putAll(e,t,n))},query(t){w();let n=E(t,r,e,d);return _(o?{...n,deleteMany:n.deleteMany?e=>y(t,`queryDelete`,()=>n.deleteMany(e)):void 0,source:()=>y(t,`query`,()=>r.getAll(t))}:n)},async[Symbol.asyncDispose](){await T.dispose()},async update(e,t,n,r){return w(),y(e,`update`,()=>b.update(e,t,n,r))},async upsert(e,t,n,r){return w(),y(e,`upsert`,()=>b.upsert(e,t,n,r))}};return T}function k(e,t){return Object.assign(e,{batch:t})}function A(e){return new Promise((n,r)=>{e.onsuccess=()=>n(e.result),e.onerror=()=>r(e.error??new t(`IndexedDB request failed`))})}function j(e,n,r){return new t(`${n} on "${e}"${r instanceof Error&&r.message?`: ${r.message}`:``}`,{cause:r})}function M(e,t,n){return new Promise((r,i)=>{let a,o;Promise.resolve().then(n).then(e=>{a=e}).catch(t=>{o=t;try{e.abort()}catch{}});let s=(e,n=`transaction failed`)=>{o instanceof Error?i(o):i(j(t,n,o??e))};e.oncomplete=()=>{if(o){s(void 0);return}r(a)},e.onerror=()=>i(j(t,`transaction error`,e.error)),e.onabort=()=>s(e.error,`transaction aborted`)})}async function N(e,t){let n=await A(e.getAll()),r=[];for(let e of n){let n=t(e);n!==void 0&&r.push(n)}return r}async function P(e,t,n,r){let i=e.index(t),a=n.type===`eq`?IDBKeyRange.only(n.value):n.type===`between`?IDBKeyRange.bound(n.lower,n.upper):IDBKeyRange.bound(n.prefix,n.prefix+`￿`),o=await A(i.getAll(a)),s=[];for(let e of o){let t=r(e);t!==void 0&&s.push(t)}return s}async function F(e,t,n){let r=await A(e.get(t));if(r!=null)return n(r)}async function I(e,t,n){return await F(e,t,n)!==void 0}async function L(e,t,n){let r=await I(e,t,n);return await A(e.delete(t)),r}async function R(e,t,n){return t.length===0?0:(await Promise.all(t.map(t=>L(e,t,n)))).filter(Boolean).length}async function z(e,t,n,r,i){await A(e.put(r(n,i),t))}function B(e){return new Promise((n,r)=>{let i=0,a=e.openCursor();a.onerror=()=>r(a.error??new t(`IndexedDB cursor failed during prune`)),a.onsuccess=()=>{let e=a.result;if(!e){n(i);return}let t=x(e.value);(!t||y(t.expiresAt))&&(e.delete(),i+=1),e.continue()}})}function V(e,n){return{[Symbol.asyncIterator](){let r=e.openCursor(),i={type:`idle`},a=e=>{if(i.type===`waiting`){let{resolve:t}=i;i=e.done?{type:`done`}:{type:`idle`},t(e)}else i={result:e,type:`buffered`}};return r.onerror=()=>{let e=r.error??new t(`IndexedDB cursor iteration failed`);if(i.type===`waiting`){let{reject:t}=i;i={type:`done`},t(e)}else i={error:e,type:`error`}},r.onsuccess=()=>{let e=r.result;if(!e){a({done:!0,value:void 0});return}let t=n(e.value);e.continue(),t!==void 0&&a({done:!1,value:t})},{next(){if(i.type===`error`){let{error:e}=i;return i={type:`done`},Promise.reject(e)}if(i.type===`buffered`){let{result:e}=i;return i=e.done?{type:`done`}:{type:`idle`},Promise.resolve(e)}return i.type===`done`?Promise.resolve({done:!0,value:void 0}):new Promise((e,t)=>{i={reject:t,resolve:e,type:`waiting`}})},return(e){return i.type===`waiting`&&i.resolve({done:!0,value:void 0}),i={type:`done`},Promise.resolve({done:!0,value:e})},throw(e){return i.type===`waiting`&&i.reject(e),i={type:`done`},Promise.reject(e)}}}}}function H(e,t,n,r){let i=e=>t.objectStore(e);return{clear:async e=>{await A(i(e).clear())},count:async e=>(await A(i(e).getAll())).filter(e=>n(e)!==void 0).length,delete:(e,t)=>L(i(e),s(t),n),deleteMany:(e,t)=>R(i(e),t.map(s),n),get:(e,t)=>F(i(e),s(t),n),getAll:e=>N(i(e),n),getByIndexRange:(e,t,r)=>P(i(e),t,r,n),getMany:(e,t)=>Promise.all(t.map(t=>F(i(e),s(t),n))),has:(e,t)=>I(i(e),s(t),n),pruneExpiredInTable:e=>B(i(e)),put(t,n,a){return z(i(t),s(p(e,t,n)),n,r,a)},putAll(t,n,a){return Promise.all(n.map(n=>z(i(t),s(p(e,t,n)),n,r,a))).then(()=>void 0)}}}function U(e){let{logger:r,migrate:i,name:o,onMetrics:c,schema:l,validators:u,version:d=1}=e;if(!Number.isInteger(d)||d<1)throw new t(`createIndexedDB: version must be a positive integer, got ${String(d)}`);let f=e=>{let t=x(e);return!t||y(t.expiresAt)?void 0:t.value},m=(e,t)=>t===void 0?{value:e}:{expiresAt:Date.now()+t,value:e},h=typeof BroadcastChannel<`u`?new BroadcastChannel(`vault:${o}`):void 0;!h&&r&&r.error(`[vault] BroadcastChannel unavailable — cross-tab sync disabled for "${o}"`);let g=null,_=null,v=!1,b=(e,t)=>{for(let[n,r]of Object.entries(l)){let i;i=e.objectStoreNames.contains(n)?t.objectStore(n):e.createObjectStore(n);let a=r.indexes??[];for(let e of a)i.indexNames.contains(e)||i.createIndex(e,`value.${e}`)}},S=async()=>(_||=new Promise((e,n)=>{let r=indexedDB.open(o,d);r.onupgradeneeded=e=>{let t=r.result,s=r.transaction;if(i)try{i({db:t,newVersion:e.newVersion??null,oldVersion:e.oldVersion,tx:s})}catch(e){try{s.abort()}catch{}n(new a(`migration failed for "${o}"`,{cause:e}));return}b(t,s)},r.onsuccess=()=>{if(v){r.result.close(),e();return}let t=r.result;t.onversionchange=()=>{t.close(),g=null,_=null},g=t,e()},r.onerror=()=>{_=null,n(new t(`failed to open "${o}"`,{cause:r.error}))}}),_),C=async(e,t,r)=>{if(v||(g||await S(),!g))throw new n(`"${o}" is disposed`);let i=String(e),a=g.transaction(i,t);return M(a,`${o}/${i}`,()=>r(a.objectStore(i)))},w=async()=>{if(v||(g||await S(),!g))throw new n(`"${o}" is disposed`);return g},T=e=>{h?.postMessage({table:String(e)})},E={clear:e=>C(e,`readwrite`,e=>A(e.clear()).then(()=>void 0)),count:e=>C(e,`readonly`,async e=>(await A(e.getAll())).filter(e=>f(e)!==void 0).length),delete:(e,t)=>C(e,`readwrite`,e=>L(e,s(t),f)),deleteMany:(e,t)=>t.length===0?Promise.resolve(0):C(e,`readwrite`,e=>R(e,t.map(s),f)),async dispose(){v=!0,h?.close(),_&&await _.catch(()=>{}),g?.close(),g=null,_=null},get:(e,t)=>C(e,`readonly`,e=>F(e,s(t),f)),getAll:e=>C(e,`readonly`,e=>N(e,f)),getAllKeys:e=>C(e,`readonly`,async t=>{let n=await N(t,f),r=l[e].key;return n.map(e=>e[r])}),getByIndexRange:(e,t,n)=>C(e,`readonly`,e=>P(e,t,n,f)),getMany:(e,t)=>t.length===0?Promise.resolve([]):C(e,`readonly`,e=>Promise.all(t.map(t=>F(e,s(t),f)))),getRawCount:e=>C(e,`readonly`,e=>A(e.count())),has:(e,t)=>C(e,`readonly`,e=>I(e,s(t),f)),async pruneAllExpired(){let e=await w(),t=Object.keys(l),n=e.transaction(t,`readwrite`),r=await M(n,`${o}/pruneAll`,()=>Promise.all(t.map(async e=>[e,await B(n.objectStore(e))])));return Object.fromEntries(r)},pruneExpiredInTable:e=>C(e,`readwrite`,e=>B(e)),put(e,t,n){let r=s(p(l,e,t));return C(e,`readwrite`,e=>z(e,r,t,m,n))},putAll(e,t,n){return C(e,`readwrite`,r=>Promise.all(t.map(t=>z(r,s(p(l,e,t)),t,m,n))).then(()=>void 0))}},j=async(e,t,n,r)=>{let i=(await w()).transaction([...e],`readwrite`),a=new Set,s=H(l,i,f,m),c=new Set(e),u=D(l,s,e=>a.add(e),r,c),d=await M(i,o,()=>t(u));for(let e of a)n(e);return d},U,W={...O(l,E,{logger:r,onCrossTabMessage(e){if(h)return h.onmessage=t=>{let n=t.data?.table;!n||!Object.hasOwn(l,n)||e(n)},()=>{h.onmessage=null}},onMetrics:c,onMutation:T,onTransactions:e=>{U=(t,n)=>j(t,n,e.notifyMutation,e.validate)},schema:l,validators:u}),iterate(e){if(v)throw new n(`"${o}" is disposed`);let t=async()=>{if(g||await S(),!g||v)throw new n(`"${o}" is disposed`);return V(g.transaction(String(e),`readonly`).objectStore(String(e)),f)},r,i=()=>t().then(e=>(r=e[Symbol.asyncIterator](),r));return{[Symbol.asyncIterator](){return{next(){return r?r.next():i().then(e=>e.next())},return(e){return r?r.return?.(e)??Promise.resolve({done:!0,value:e}):Promise.resolve({done:!0,value:e})},throw(e){return r?r.throw?.(e)??Promise.reject(e):Promise.reject(e)}}}}}};if(!U)throw new t(`IndexedDB transaction capability was not initialized`);return k(W,U)}function W(e){if(typeof e!=`object`||!e)return!1;let t=e;if(typeof t.table!=`string`||typeof t.type!=`string`||t.table===`__proto__`||t.table===`constructor`||t.table===`prototype`)return!1;switch(t.type){case`clear`:return!0;case`delete`:return typeof t.key==`string`;case`deleteMany`:return Array.isArray(t.keys)&&t.keys.every(e=>typeof e==`string`);case`put`:return typeof t.key==`string`&&x(t.stored)!==void 0;case`putAll`:return Array.isArray(t.entries)&&t.entries.every(e=>typeof e==`object`&&!!e&&typeof e.key==`string`&&x(e.stored)!==void 0);default:return!1}}function G(e){let{logger:t,name:n,onMetrics:r,schema:i,validators:a}=e,o=new Map(Object.keys(i).map(e=>[e,new Map])),c=e=>o.get(e),l=n!==void 0&&typeof BroadcastChannel<`u`?new BroadcastChannel(`vault-memory:${n}`):void 0,u={async clear(e){c(e).clear(),l?.postMessage({table:e,type:`clear`})},async count(e){let t=c(e),n=0;for(let[e,r]of t){let i=x(r);!i||y(i.expiresAt)?t.delete(e):n+=1}return n},async delete(e,t){let n=c(e),r=s(t),i=x(n.get(r));n.delete(r);let a=i!==void 0&&!y(i.expiresAt);return a&&l?.postMessage({key:r,table:e,type:`delete`}),a},async deleteMany(e,t){let n=0,r=[];for(let i of t){let t=s(i),a=x(c(e).get(t));c(e).delete(t),a!==void 0&&!y(a.expiresAt)&&(n+=1,r.push(t))}return r.length>0&&l?.postMessage({keys:r,table:e,type:`deleteMany`}),n},dispose:l?async()=>l.close():void 0,async get(e,t){let n=c(e),r=s(t),i=x(n.get(r));if(!i||y(i.expiresAt)){n.delete(r);return}return i.value},async getAll(e){let t=c(e),n=[];for(let[e,r]of t){let i=x(r);!i||y(i.expiresAt)?t.delete(e):n.push(i.value)}return n},async getRawCount(e){return c(e).size},async has(e,t){return await u.get(e,t)!==void 0},async pruneExpiredInTable(e){let t=c(e),n=0;for(let[e,r]of t){let i=x(r);(!i||y(i.expiresAt))&&(t.delete(e),n+=1)}return n},async put(e,t,n){let r=s(p(i,e,t)),a=n===void 0?{value:t}:{expiresAt:Date.now()+n,value:t};c(e).set(r,a),l?.postMessage({key:r,stored:a,table:e,type:`put`})},async putAll(e,t,n){let r=n===void 0?void 0:Date.now()+n,a=[];for(let n of t){let t=s(p(i,e,n)),o=r===void 0?{value:n}:{expiresAt:r,value:n};c(e).set(t,o),a.push({key:t,stored:o})}a.length>0&&l?.postMessage({entries:a,table:e,type:`putAll`})}};return O(i,u,{logger:t,onCrossTabMessage:l?e=>(l.onmessage=t=>{if(!W(t.data))return;let n=t.data;if(!o.has(n.table))return;let r=c(n.table),l=(e,t)=>{let o=x(t),c=i[n.table]?.key,l=a?.[n.table];if(!o||!c)return!1;try{let t=l?l.parse(o.value):o.value;return s(t[c])===e&&(r.set(e,o.expiresAt===void 0?{value:t}:{expiresAt:o.expiresAt,value:t}),!0)}catch{return!1}};switch(n.type){case`clear`:r.clear(),e(n.table);break;case`delete`:r.delete(n.key),e(n.table);break;case`deleteMany`:for(let e of n.keys)r.delete(e);e(n.table);break;case`put`:l(n.key,n.stored)&&e(n.table);break;case`putAll`:n.entries.some(e=>l(e.key,e.stored))&&e(n.table)}},()=>{l.onmessage=null}):void 0,onMetrics:r,schema:i,validators:a})}var K=new Set([`QuotaExceededError`,`NS_ERROR_DOM_QUOTA_REACHED`]);function q(e){let{getStorage:n,logger:r,name:a,onMetrics:o,onQuotaExceeded:s,schema:f,storageLabel:m,validators:h}=e,g;try{g=n()}catch(e){throw new t(`${m} is not available in this environment (private browsing or sandboxed iframe?)`,{cause:e})}let _=()=>g,v=new Map(Object.keys(f).map(e=>[e,u(a,e)])),b=e=>{let n=v.get(e);if(!n)throw new t(`table "${e}" not in schema`);return n},S=(e,t,n)=>{try{_().setItem(t,JSON.stringify(n))}catch(t){if(t instanceof DOMException&&K.has(t.name)){let n=new i(`${m} quota exceeded while writing record`,{cause:t});if(s?.(e,n)===`ignore`)return;throw n}throw t}},C=new Set;(()=>{let e=c(a);for(let t=0;t<g.length;t++){let n=g.key(t);n!==null&&n.startsWith(e)&&C.add(n)}})();let w=e=>{let t=_().getItem(e);if(t)try{let e=x(JSON.parse(t));return!e||y(e.expiresAt)?void 0:e.value}catch{return}},T=e=>{_().removeItem(e),C.delete(e)};return O(f,{async clear(e){let t=_(),n=b(e),r=[];for(let e of C)e.startsWith(n)&&r.push(e);for(let e of r)t.removeItem(e),C.delete(e)},async count(e){let t=b(e),n=[],r=0;for(let e of C)e.startsWith(t)&&(w(e)===void 0?n.push(e):r+=1);for(let e of n)T(e);return r},async delete(e,t){let n=l(a,e,t);return w(n)===void 0?(C.has(n)&&T(n),!1):(T(n),!0)},async deleteMany(e,t){let n=0;for(let r of t){let t=l(a,e,r);w(t)===void 0?C.has(t)&&T(t):(T(t),n+=1)}return n},async get(e,t){let n=l(a,e,t),r=w(n);return r===void 0&&C.has(n)&&T(n),r},async getAll(e){let t=[],n=[],r=b(e);for(let e of C){if(!e.startsWith(r))continue;let i=w(e);if(i===void 0){n.push(e);continue}t.push(i)}for(let e of n)T(e);return t},async getAllKeys(e){let t=b(e),n=[],r=[];for(let i of C){if(!i.startsWith(t))continue;let a=w(i);if(a===void 0){r.push(i);continue}n.push(a[f[e].key])}for(let e of r)T(e);return n},async getRawCount(e){let t=b(e),n=0;for(let e of C)e.startsWith(t)&&(n+=1);return n},async has(e,t){let n=l(a,e,t),r=w(n);return r===void 0&&C.has(n)&&T(n),r!==void 0},async pruneExpiredInTable(e){let t=b(e),n=[];for(let e of C){if(!e.startsWith(t))continue;let r=_().getItem(e);if(r===null){n.push(e);continue}try{let t=x(JSON.parse(r));(!t||y(t.expiresAt))&&n.push(e)}catch{n.push(e)}}for(let e of n)T(e);return n.length},async put(e,t,n){let r=l(a,e,p(f,e,t)),i=n===void 0?void 0:Date.now()+n;S(e,r,i===void 0?{value:t}:{expiresAt:i,value:t}),C.add(r)},async putAll(e,t,n){let r=n===void 0?void 0:Date.now()+n;for(let n of t){let t=l(a,e,p(f,e,n));S(e,t,r===void 0?{value:n}:{expiresAt:r,value:n}),C.add(t)}}},{logger:r,onCrossTabMessage(e){if(typeof window>`u`||typeof window.addEventListener!=`function`)return;let t=t=>{if(t.storageArea&&t.storageArea!==g)return;if(t.key===null){C.clear();for(let t of Object.keys(f))e(t);return}let n=d(a,t.key);n&&Object.hasOwn(f,n)&&(t.newValue===null?C.delete(t.key):C.add(t.key),e(n))};return window.addEventListener(`storage`,t),()=>window.removeEventListener(`storage`,t)},onMetrics:o,schema:f,validators:h})}function J(e){return q({...e,getStorage:()=>typeof window<`u`?window.localStorage:localStorage,storageLabel:`localStorage`})}function Y(e){return q({...e,getStorage:()=>typeof window<`u`?window.sessionStorage:sessionStorage,storageLabel:`sessionStorage`})}function X(e){return({db:t,tx:n})=>{for(let r of e)switch(r.type){case`addIndex`:{let e=n.objectStore(r.table);e.indexNames.contains(r.field)||e.createIndex(r.field,`value.${r.field}`);break}case`addTable`:t.objectStoreNames.contains(r.name)||t.createObjectStore(r.name);break;case`removeIndex`:{let e=n.objectStore(r.table);e.indexNames.contains(r.field)&&e.deleteIndex(r.field);break}case`removeTable`:t.objectStoreNames.contains(r.name)&&t.deleteObjectStore(r.name)}}}function Z(e,r){if(!Number.isFinite(r.interval)||r.interval<=0)throw new t(`scheduleExpiredPrune: interval must be a finite positive number`);let i=!0,a=()=>{i=!1,clearInterval(o)};r.signal?.addEventListener(`abort`,a,{once:!0});let o=setInterval(()=>{i&&e.pruneExpired().catch(e=>{e instanceof n?(i=!1,clearInterval(o)):r.onError?r.onError(e):`${String(e)}`})},r.interval);return a}function Q(e){function n(e){return{...e,index:r=>{let i=e.indexes??[];if(i.includes(r))throw new t(`table index "${r}" is already registered`);return n({...e,indexes:[...i,r]})},ttl:t=>(b(t,`table.ttl`),n({...e,defaultTtl:t}))}}return n({key:e})}return e.VaultDisposedError=n,e.VaultError=t,e.VaultMigrationError=a,e.VaultQuotaError=i,e.VaultScopeError=r,e.createIndexedDB=U,e.createLocalStorage=J,e.createMemory=G,e.createSessionStorage=Y,e.defineMigration=X,e.isExpired=y,e.scheduleExpiredPrune=Z,e.table=Q,e.ttl=v,e})({});
1
+ var Vault=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},n=class extends t{constructor(e=`adapter is disposed`,t){super(e,t)}},r=class extends t{},i=class extends t{},a=class extends t{};function o(e,r){if(!Number.isFinite(r.interval)||r.interval<=0)throw new t(`scheduleExpiredPrune: interval must be a finite positive number`);let i=!0,a=()=>{i=!1,clearInterval(o)};r.signal?.addEventListener(`abort`,a,{once:!0});let o=setInterval(()=>{i&&e.pruneExpired().catch(e=>{e instanceof n?(i=!1,clearInterval(o)):r.onError?r.onError(e):`${String(e)}`})},r.interval);return a}var s={days:e=>l(l(e,`ttl.days`)*864e5,`ttl.days (result)`),hours:e=>l(l(e,`ttl.hours`)*36e5,`ttl.hours (result)`),minutes:e=>l(l(e,`ttl.minutes`)*6e4,`ttl.minutes (result)`),ms:e=>l(e,`ttl.ms`),seconds:e=>l(l(e,`ttl.seconds`)*1e3,`ttl.seconds (result)`)};function c(e){return e!==void 0&&Date.now()>=e}function l(e,n){if(!Number.isFinite(e)||e<=0)throw new t(`${n} expected a finite positive number, received ${String(e)}`);return e}function u(e){function n(e){return{...e,index:r=>{let i=e.indexes??[];if(i.includes(r))throw new t(`table index "${r}" is already registered`);return n({...e,indexes:[...i,r]})},ttl:t=>(l(t,`table.ttl`),n({...e,defaultTtl:t}))}}return n({key:e})}return e.VaultDisposedError=n,e.VaultError=t,e.VaultMigrationError=a,e.VaultQuotaError=i,e.VaultScopeError=r,e.isExpired=c,e.scheduleExpiredPrune=o,e.table=u,e.ttl=s,e})({});
2
2
  //# sourceMappingURL=vault.iife.js.map