@orkestrel/database 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -35
- package/dist/src/browser/index.d.ts +92 -79
- package/dist/src/browser/index.js +357 -220
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +1796 -803
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +545 -706
- package/dist/src/core/index.d.ts +545 -706
- package/dist/src/core/index.js +1775 -790
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +1508 -757
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +443 -569
- package/dist/src/server/index.d.ts +443 -569
- package/dist/src/server/index.js +1492 -741
- package/dist/src/server/index.js.map +1 -1
- package/package.json +9 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["#name","#database","#stores","#schema","#wrap","#store","#table","#candidates","#require","#applySteps","#upgrade","#reopen"],"sources":["../../../src/browser/constants.ts","../../../src/browser/helpers.ts","../../../src/browser/drivers/IndexedDBDriver.ts","../../../src/browser/factories.ts"],"sourcesContent":["import type { ColumnType } from '@src/core'\n\n// The column types that are valid, orderable IndexedDB keys (string / number key\n// space). `boolean` / `json` / `blob` are not valid `IDBValidKey`s and would make\n// a range silently miss rows, so they are never pushed down.\nexport const INDEXABLE_TYPES: ReadonlySet<ColumnType> = new Set<ColumnType>([\n\t'text',\n\t'integer',\n\t'real',\n])\n\n// The reserved out-of-line store the driver stamps its DriverMeta into\n// (`meta` / `stamp`). A user table declared with this exact name would\n// collide with the driver's own bookkeeping — callers must avoid it.\nexport const META_STORE = '__meta__'\n","import type { Condition, Criteria, TableSchema } from '@src/core'\nimport type { IndexedDBError } from '@orkestrel/indexeddb'\nimport type { QueryPlan } from './types.js'\nimport { compareValues, DatabaseError } from '@src/core'\nimport { range } from '@orkestrel/indexeddb'\nimport { INDEXABLE_TYPES } from './constants.js'\n\n// The IndexedDB driver's pushdown planner. A pure function over the portable\n// `Criteria`: it decides which index (or the primary key) a read can narrow on\n// and the `IDBKeyRange` to use, so the driver fetches a candidate SUPERSET rather\n// than every row. The core engine then refines that superset to the exact result\n// — so a plan is only ever allowed to over-fetch, never to drop a matching row.\n// Anything it cannot prove range-exact (a non-comparison operator, a non-orderable\n// column type, a nested path, a non-scalar operand) falls through to a full scan.\n\n/**\n * The `IDBKeyRange` a single {@link Condition} maps to, when its operator is one\n * of the six exact key comparisons over scalar operands — else `null`.\n *\n * @remarks\n * Only the comparison operators (`equals`/`above`/`below`/`from`/`to`/`between`)\n * translate to a key range that a typed (string/number) column can back with an\n * IndexedDB store/index read — see {@link selectPlan} for the caveats that\n * decide WHICH of `below`/`to` may drive a SECONDARY-index read versus the\n * primary store only (a column-type / absent-row concern, not a range-shape\n * one). `starts` is excluded — its prefix range can miss strings past U+FFFF;\n * the membership / negation / pattern / existence operators (`not`/`like`/`glob`/\n * `ends`/`any`/`none`/`absent`/`present`) have no single exact range. The operand\n * guard (`typeof` string/number) rejects a non-scalar value (e.g. an array, a\n * boolean) that is not a usable key. `between` additionally guards against a\n * REVERSED pair (`first > second`): native `IDBKeyRange.bound` throws a raw\n * `DataError` `DOMException` for a lower bound above the upper bound, so a\n * reversed pair returns `null` here (falls back to a full scan, which the\n * engine then correctly resolves to an empty result) rather than letting a\n * native exception escape untyped — the same defensive posture as every other\n * backend, which returns empty for a reversed/empty range instead of throwing.\n * The switch is exhaustive over every {@link ConditionOperator}, so a new\n * operator forces a deliberate decision here rather than silently defaulting\n * to a (possibly lossy) range.\n *\n * @param condition - The condition to translate\n * @returns Its exact key range, or `null` when the operator/operands cannot push\n */\nexport function conditionRange(condition: Condition): IDBKeyRange | null {\n\tconst first = condition.values[0]\n\tconst second = condition.values[1]\n\tswitch (condition.operator) {\n\t\tcase 'equals':\n\t\t\treturn isKey(first) ? range.only(first) : null\n\t\tcase 'above':\n\t\t\treturn isKey(first) ? range.above(first) : null\n\t\tcase 'below':\n\t\t\treturn isKey(first) ? range.below(first) : null\n\t\tcase 'from':\n\t\t\treturn isKey(first) ? range.from(first) : null\n\t\tcase 'to':\n\t\t\treturn isKey(first) ? range.to(first) : null\n\t\tcase 'between':\n\t\t\t// A reversed pair (`first > second`) has no valid IDBKeyRange — native\n\t\t\t// `bound` throws rather than returning empty. Fall back to a full scan\n\t\t\t// instead of letting that DOMException escape (the engine then yields\n\t\t\t// the correct, empty result over `compareValues(value, first) >= 0 &&\n\t\t\t// compareValues(value, second) <= 0`, which no row can satisfy).\n\t\t\treturn isKey(first) && isKey(second) && compareValues(first, second) <= 0\n\t\t\t\t? range.between(first, second)\n\t\t\t\t: null\n\t\tcase 'not':\n\t\tcase 'like':\n\t\tcase 'glob':\n\t\tcase 'starts':\n\t\tcase 'ends':\n\t\tcase 'any':\n\t\tcase 'none':\n\t\tcase 'absent':\n\t\tcase 'present':\n\t\t\treturn null\n\t}\n}\n\n// A scalar IndexedDB key operand — a string or number (the core `Key` space).\nexport function isKey(value: unknown): value is string | number {\n\treturn typeof value === 'string' || typeof value === 'number'\n}\n\n/**\n * Plan an IndexedDB read for a {@link Criteria} — pick the index (or the primary\n * store) and {@link IDBKeyRange} to narrow by, falling back to a full scan.\n *\n * @remarks\n * Pushdown is sound ONLY when every condition is `and`-joined: the engine folds\n * conditions left-to-right (`c1 && c2 && … && cn`), so the result is a subset of\n * each — narrowing on any one is then a valid superset. A single `or` breaks that\n * (a row can match through a later condition the range would exclude), so any `or`\n * forces a full scan. Otherwise it scans the conditions in order and selects the\n * **first** one that is provably range-exact and backed by a key: a comparison\n * operator (`conditionRange`) over a single, orderable (`text`/`integer`/`real`)\n * column that is either the table's primary key (read the store directly, `index:\n * null`) or has a single-column secondary index (named exactly the column — read\n * that index). A condition whose column is a nested {@link FieldPath} array\n * (descends a json value, not a key), is absent from the schema, is a non-orderable\n * type (`boolean`/`json`/`blob`), uses a non-comparison operator, or has a\n * non-scalar operand cannot push and is skipped.\n *\n * **`below`/`to` may drive a SECONDARY-index range only when the column has NO\n * absent/null rows to lose — which this planner cannot verify from the schema\n * alone, so it restricts them to the PRIMARY store, where that is always true.**\n * The engine's total order (`compareValues`, see `@src/core`) ranks\n * `undefined` (absent) and `null` BELOW every number/string, so\n * `matchesCondition('below' | 'to', …)` is TRUE for a row whose field is absent\n * or `null` — but a secondary IndexedDB index has NO ENTRY for a row whose\n * indexed field is absent/`null`, so a `below`/`to` range read against that\n * index would SILENTLY DROP those rows (they can never be over-fetched, only\n * missed — the one shape of lossiness this planner must never produce). The\n * table's PRIMARY key is exempt: a row's primary-key value is always present\n * and never `null` (it is the row's identity, enforced at write time), so a\n * `below`/`to` range against the primary store can never exclude an\n * absent/null-keyed row because no such row exists. `equals`/`above`/`from`/\n * `between` stay index-eligible on ANY orderable column, primary or secondary:\n * each is bounded below by a scalar (`equals`/`between`'s lower bound, `above`/\n * `from`'s lower bound), and every scalar strictly out-ranks `undefined`/`null`\n * in the total order, so an absent/null-valued row can never satisfy them — the\n * index's silence on such a row is harmless (it was never going to match).\n * **Declared-type trust caveat:** this reasoning holds under the contract that\n * an {@link INDEXABLE_TYPES} column, once contract-validated at write time,\n * holds only `string | number | null` (or is absent) — never some other\n * runtime value that could rank differently; a driver bypassing the write\n * contract (writing raw rows directly to the store) could defeat this\n * argument, but that is out of scope for a planner reading validated schema\n * metadata.\n *\n * When no condition qualifies the plan is a full scan (`{ index: null, range:\n * null }`) and the engine does everything. The plan is always a SUPERSET of the\n * matching rows — the only correctness contract — so the driver may safely run\n * the exact engine over it.\n *\n * @param criteria - The read specification (its `conditions` drive the plan), or\n * `undefined` for an unconditional read\n * @param schema - The table's schema — its `primary` key and column types\n * @param available - The secondary-index names that physically exist on the store\n * (`store.indexes`); a single-column index is named exactly its column\n * @returns The index + range to read, narrowing to a superset (never lossy)\n *\n * @example\n * ```ts\n * selectPlan({ conditions: [eq('id', 'u1')] }, schema, []) // { index: null, range: only('u1') }\n * selectPlan({ conditions: [from('age', 18)] }, schema, ['age']) // { index: 'age', range: from(18) }\n * selectPlan({ conditions: [contains('name', 'a')] }, schema, []) // { index: null, range: null }\n * ```\n */\nexport function selectPlan(\n\tcriteria: Criteria | undefined,\n\tschema: TableSchema,\n\tavailable: readonly string[],\n): QueryPlan {\n\tconst conditions = criteria?.conditions ?? []\n\t// A single condition's range is a SUPERSET of the result only when the result\n\t// implies that condition — which holds iff every condition is `and`-joined (the\n\t// fold is `c1 && c2 && … && cn`, so the result is a subset of each). A single\n\t// `or` breaks that (a row can match via a later condition the range excludes),\n\t// so any `or` forces a full scan. The first condition's connector only seeds the\n\t// fold and is ignored (AGENTS — the `Condition.connector` contract).\n\tif (conditions.slice(1).some((condition) => condition.connector === 'or')) {\n\t\treturn { index: null, range: null }\n\t}\n\tfor (const condition of conditions) {\n\t\t// An array column is a nested FieldPath into a json value — not a key.\n\t\tif (typeof condition.column !== 'string') continue\n\t\tconst column = schema.columns.find((candidate) => candidate.name === condition.column)\n\t\tif (column === undefined || !INDEXABLE_TYPES.has(column.type)) continue\n\t\tconst keyRange = conditionRange(condition)\n\t\tif (keyRange === null) continue\n\t\tif (condition.column === schema.primary) return { index: null, range: keyRange }\n\t\t// `below`/`to` can silently drop an absent/null-valued row from a SECONDARY\n\t\t// index (see @remarks) — only the primary store (handled above) is safe.\n\t\t// Keep scanning: a later condition may still qualify.\n\t\tif (condition.operator === 'below' || condition.operator === 'to') continue\n\t\tif (available.includes(condition.column)) return { index: condition.column, range: keyRange }\n\t\t// The column is range-exact but has no usable index — keep looking.\n\t}\n\treturn { index: null, range: null }\n}\n\n/**\n * Map a backend {@link IndexedDBError} to the portable `DatabaseError` taxonomy\n * — the default mapping used everywhere except inside `migrate()`.\n *\n * @remarks\n * No backend fault may leak through `DriverInterface` as a raw `IndexedDBError`.\n * `CONSTRAINT` (a unique-key violation) is a `CONFLICT` — the same code every\n * other backend uses for a duplicate key. `CLOSED`/`NOT_OPEN`/`INVALID` (the\n * connection is gone, never opened, or the native handle is stale) collapse to\n * `CLOSED`. `QUOTA` and `BLOCKED` are genuine infrastructure faults (`DRIVER`),\n * carrying a machine-readable `context.code` (`'QUOTA'` / `'BLOCKED'`) so a\n * caller can branch without parsing the message; `BLOCKED` additionally marks\n * `context.retryable: true` — a concurrent connection holding the database open\n * is a transient condition, not a permanent one. Every other code (`UPGRADE`\n * here — see {@link mapMigrationError} for the `migrate()`-only remapping to\n * `MIGRATION` — `ABORTED`, `NOT_FOUND`, `DATA`, `OPEN`, `INACTIVE`, `READONLY`,\n * `UNKNOWN`) is an unexpected infrastructure fault and maps to `DRIVER` — the\n * driver opens its own readwrite transactions, so a `READONLY` fault can only\n * mean the backend behaved unexpectedly. The original error is always\n * preserved as `context.cause` for diagnostics.\n *\n * @param error - The backend error to translate\n * @returns The portable `DatabaseError`\n */\nexport function mapIndexedDBError(error: IndexedDBError): DatabaseError {\n\tswitch (error.code) {\n\t\tcase 'CONSTRAINT':\n\t\t\treturn new DatabaseError('CONFLICT', error.message, { cause: error })\n\t\tcase 'CLOSED':\n\t\tcase 'NOT_OPEN':\n\t\tcase 'INVALID':\n\t\t\treturn new DatabaseError('CLOSED', error.message, { cause: error })\n\t\tcase 'QUOTA':\n\t\t\treturn new DatabaseError('DRIVER', error.message, { cause: error, code: 'QUOTA' })\n\t\tcase 'BLOCKED':\n\t\t\treturn new DatabaseError('DRIVER', error.message, {\n\t\t\t\tcause: error,\n\t\t\t\tcode: 'BLOCKED',\n\t\t\t\tretryable: true,\n\t\t\t})\n\t\tcase 'UPGRADE':\n\t\tcase 'ABORTED':\n\t\tcase 'NOT_FOUND':\n\t\tcase 'DATA':\n\t\tcase 'OPEN':\n\t\tcase 'INACTIVE':\n\t\tcase 'READONLY':\n\t\tcase 'UNKNOWN':\n\t\t\treturn new DatabaseError('DRIVER', error.message, { cause: error })\n\t}\n}\n\n/**\n * Map a backend {@link IndexedDBError} to the portable `DatabaseError` taxonomy\n * for use INSIDE `migrate()` — the one context where `UPGRADE` means the\n * migration itself failed, not a generic driver fault.\n *\n * @remarks\n * `migrate()` reconnects at a bumped version inside `onupgradeneeded`; a\n * rejection there (an inapplicable step, a native `ConstraintError` from a\n * duplicate index, …) surfaces as `IndexedDBError` `UPGRADE` and must become a\n * `MIGRATION` `DatabaseError` so a caller can distinguish \"this migration plan\n * failed\" from \"the driver hit an unrelated infrastructure fault\". Every other\n * code defers to {@link mapIndexedDBError} unchanged.\n *\n * @param error - The backend error to translate\n * @returns The portable `DatabaseError`\n */\nexport function mapMigrationError(error: IndexedDBError): DatabaseError {\n\tif (error.code === 'UPGRADE') {\n\t\treturn new DatabaseError('MIGRATION', error.message, { cause: error })\n\t}\n\treturn mapIndexedDBError(error)\n}\n\n/**\n * Derive an IndexedDB index name for a declared column group — a bare column\n * name for a single-column index, a deterministic collision-free encoding for a\n * compound one.\n *\n * @remarks\n * Naming a compound index by joining its columns with `_` (`['a', 'b'] →\n * 'a_b'`) collides with a single-column index over a column LITERALLY named\n * `'a_b'` — the same name, two different key paths (`'a_b'` vs `['a', 'b']`),\n * which either throws a native `ConstraintError` from a duplicate\n * `createIndex` call at open, or (worse) lets {@link selectPlan}'s name-based\n * lookup match the wrong index. A single-column index keeps the BARE column\n * name — {@link selectPlan} matches `available.includes(condition.column)` by\n * that exact name, so a single-column index must stay named after its column\n * verbatim. A compound index instead encodes each column as a LENGTH-PREFIXED\n * segment (`'2#1:a1:b'`), so the boundary between columns is self-describing\n * and cannot be reconstructed by any other column list — including one\n * containing a column that happens to look like an encoded segment.\n *\n * @param columns - The index's column group, in declared order\n * @returns The index name to pass to `createIndex` / read back from `indexNames`\n *\n * @example\n * ```ts\n * deriveIndexName(['age']) // 'age'\n * deriveIndexName(['a', 'b']) // '2#1:a1:b'\n * ```\n */\nexport function deriveIndexName(columns: readonly string[]): string {\n\tconst [column] = columns\n\tif (columns.length === 1 && column !== undefined) return column\n\treturn `${columns.length}#${columns.map((part) => `${part.length}:${part}`).join('')}`\n}\n","import type {\n\tCriteria,\n\tDriverInterface,\n\tDriverMeta,\n\tKey,\n\tMigration,\n\tMigrationStep,\n\tRow,\n\tTableSchema,\n} from '@src/core'\nimport {\n\tapplyCriteria,\n\tcompareValues,\n\tDatabaseError,\n\tdeepEqual,\n\textractKey,\n\tisDriverMeta,\n\tmatchesCriteria,\n\tmigrateRows,\n} from '@src/core'\nimport type {\n\tIndexedDBDatabaseInterface,\n\tIndexedDBStoreInterface,\n\tIndexedDBUpgradeContext,\n\tStoreDefinition,\n} from '@orkestrel/indexeddb'\nimport { createIndexedDBDatabase, isIndexedDBError } from '@orkestrel/indexeddb'\nimport type { QueryPlan } from '../types.js'\nimport { deriveIndexName, mapIndexedDBError, mapMigrationError, selectPlan } from '../helpers.js'\nimport { META_STORE } from '../constants.js'\n\n/**\n * The IndexedDB {@link DriverInterface} — the persistent browser backend, built on\n * the published `@orkestrel/indexeddb` wrapper.\n *\n * @remarks\n * A thin adapter: it implements the storage primitives the core database layer\n * needs (`open` / `close` / `read` / `write` / `delete` / `keys` / `scan` / `clear`\n * / `snapshot`) by delegating to the wrapper's typed store operations — it never\n * touches raw IndexedDB. Rows are stored with **out-of-line keys** (the database\n * passes the key explicitly, `store.set(row, key)`), so each table is declared as a\n * key-path-less store. The wrapper opens in **auto-managed** mode (no fixed\n * version), creating any missing store on demand, so a table added to the schema is\n * created on the next open with no manual version bump. The driver's bulk reads\n * (`scan` / `keys`) use the wrapper's native `getAll` / `getAllKeys`, and `snapshot`\n * rolls back through one atomic wrapper transaction.\n *\n * It also implements the optional native `records` / `count` / `stream` hooks\n * (AGENTS §21): `selectPlan` ({@link selectPlan}) turns the {@link Criteria} into a\n * key-range pushdown over the primary key or a single-column secondary index,\n * fetching a candidate **superset** that the core engine (`applyCriteria` /\n * `matchesCriteria`) then refines — so a native read is byte-identical to a full\n * scan, just cheaper. Pushdown is conservative: only the exact-comparison\n * operators over orderable columns narrow to a range; everything else falls back\n * to a full scan + the engine.\n *\n * @remarks\n * This driver also implements `migrate` / `meta` / `stamp`. `meta` / `stamp`\n * persist the {@link DriverMeta} in a reserved out-of-line store,\n * {@link META_STORE} (`__meta__`) — excluded from a whole-store `snapshot`\n * capture, since it is driver bookkeeping, not caller data. `migrate` applies a\n * {@link Migration} plan natively: IndexedDB schema DDL (creating/dropping a\n * store, creating/dropping an index) is legal only inside a versionchange\n * transaction (`onupgradeneeded`), so `migrate` closes the current connection\n * and opens a FRESH one at `version + 1` with an `upgrade` hook that walks the\n * plan's steps — dropping stores, adding/removing indexes on the raw\n * `IDBTransaction`, and rewriting rows for `column.remove` via a cursor walk\n * (the one step needing to touch existing data; `column.add` is a no-op — this\n * driver stores whatever a row carries, so there is nothing to backfill). A\n * step referencing an unknown table is validated BEFORE the reconnect, so a\n * `MIGRATION` `DatabaseError` never wastes a version bump.\n *\n * @remarks\n * This unit deliberately OMITS `aggregate` / `transaction`. There is no native\n * `aggregate` (IndexedDB has no native SUM/AVG); the engine over the narrowed\n * `records` covers it. `transaction` is impossible here: the wrapper auto-commits\n * an `IDBTransaction` the moment control yields to a non-IDB `await`, so a\n * BEGIN-now / commit-or-rollback-later handle spanning arbitrary caller code\n * cannot be built on top of it — every atomic multi-op sequence in this driver\n * (`snapshot`'s rollback) instead runs entirely inside ONE `db.write(...)` scope.\n */\nexport class IndexedDBDriver implements DriverInterface {\n\treadonly #name: string\n\t#schema = new Map<string, TableSchema>()\n\t#database: IndexedDBDatabaseInterface | undefined\n\n\tconstructor(name: string) {\n\t\tthis.#name = name\n\t}\n\n\tasync open(schema: readonly TableSchema[]): Promise<void> {\n\t\t// The reserved meta store name may never collide with a caller-declared\n\t\t// table — it would silently corrupt this driver's own `meta`/`stamp`\n\t\t// bookkeeping (AGENTS §12 — a programmer error throws).\n\t\tif (schema.some((table) => table.name === META_STORE)) {\n\t\t\tthrow new DatabaseError(\n\t\t\t\t'VALIDATION',\n\t\t\t\t`open: table name '${META_STORE}' is reserved for driver metadata`,\n\t\t\t\t{ table: META_STORE },\n\t\t\t)\n\t\t}\n\t\ttry {\n\t\t\t// Reconnect cleanly so an auto-managed version bump (to create new\n\t\t\t// stores) is never blocked by this driver's own open handle.\n\t\t\tthis.#database?.close()\n\t\t\t// Build the new schema into a LOCAL map first — never mutate `#schema`\n\t\t\t// in place — so a reopen with a REDUCED schema replaces the map\n\t\t\t// wholesale instead of retaining ghost tables the caller no longer\n\t\t\t// declared.\n\t\t\tconst map = new Map<string, TableSchema>()\n\t\t\tfor (const table of schema) map.set(table.name, table)\n\t\t\tconst database = createIndexedDBDatabase({ name: this.#name, stores: this.#stores(map) })\n\t\t\tawait database.connect()\n\t\t\tthis.#database = database\n\t\t\t// Remember the schema so the native `records` / `count` / `stream` hooks\n\t\t\t// can plan a key-range pushdown (the primary key, column types, secondary\n\t\t\t// indexes).\n\t\t\tthis.#schema = map\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.#database?.close()\n\t\tthis.#database = undefined\n\t}\n\n\tasync read(table: string, key: Key): Promise<Row | undefined> {\n\t\ttry {\n\t\t\treturn await this.#store(table).get(key)\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\tasync write(table: string, key: Key, row: Row): Promise<void> {\n\t\ttry {\n\t\t\tawait this.#store(table).set(row, key)\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\tasync delete(table: string, key: Key): Promise<boolean> {\n\t\ttry {\n\t\t\tconst store = this.#store(table)\n\t\t\tconst present = await store.has(key)\n\t\t\tawait store.remove(key)\n\t\t\treturn present\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\tasync keys(table: string): Promise<readonly Key[]> {\n\t\ttry {\n\t\t\tconst keys = await this.#store(table).keys()\n\t\t\treturn keys.filter((key): key is Key => typeof key === 'string' || typeof key === 'number')\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\tasync *scan(table: string): AsyncIterable<Row> {\n\t\ttry {\n\t\t\tfor (const row of await this.#store(table).records()) yield row\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\tasync clear(table: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.#store(table).clear()\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\tasync records(table: string, criteria: Criteria): Promise<readonly Row[]> {\n\t\ttry {\n\t\t\tconst schema = this.#table(table)\n\t\t\tconst store = this.#store(table)\n\t\t\tconst plan = selectPlan(criteria, schema, store.indexes)\n\t\t\treturn applyCriteria(await this.#candidates(store, schema, plan), criteria)\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\t// A single-condition native count is exact — never a superset needing a\n\t// re-filter — because EVERY range `selectPlan` can produce excludes an\n\t// absent/null-valued row by construction: `equals`/`above`/`from`/`between`\n\t// are bounded below by a scalar (out-ranking absent/null in the engine's\n\t// total order, @src/core `compareValues`), and `below`/`to` are restricted by\n\t// `selectPlan` to the PRIMARY store, whose key is never absent/null. So a\n\t// native range count over a single condition already equals the engine's\n\t// `matchesCriteria` count for that condition — no row the range returns can\n\t// fail the condition, and no row the range omits could have passed it.\n\tasync count(table: string, criteria: Criteria): Promise<number> {\n\t\ttry {\n\t\t\tconst schema = this.#table(table)\n\t\t\tconst store = this.#store(table)\n\t\t\tconst conditions = criteria.conditions ?? []\n\t\t\tif (conditions.length === 0) return await store.count()\n\t\t\tconst plan = selectPlan(criteria, schema, store.indexes)\n\t\t\t// A single pushable condition is fully expressed by its range → native count.\n\t\t\tif (conditions.length === 1 && plan.range !== null) {\n\t\t\t\treturn plan.index === null\n\t\t\t\t\t? await store.count(plan.range)\n\t\t\t\t\t: await store.index(plan.index).count(plan.range)\n\t\t\t}\n\t\t\t// Otherwise the range is a superset (or a full scan) → engine filters exactly.\n\t\t\t// Order is irrelevant to a count, so the candidates need no re-sort.\n\t\t\tconst candidates =\n\t\t\t\tplan.index === null\n\t\t\t\t\t? await store.records(plan.range)\n\t\t\t\t\t: await store.index(plan.index).records(plan.range)\n\t\t\treturn candidates.reduce(\n\t\t\t\t(total, row) => (matchesCriteria(row, conditions) ? total + 1 : total),\n\t\t\t\t0,\n\t\t\t)\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\tasync *stream(table: string, criteria: Criteria): AsyncIterable<Row> {\n\t\ttry {\n\t\t\tconst schema = this.#table(table)\n\t\t\tconst store = this.#store(table)\n\t\t\tconst plan = selectPlan(criteria, schema, store.indexes)\n\t\t\tconst conditions = criteria.conditions ?? []\n\t\t\tconst offset = criteria.offset ?? 0\n\t\t\tconst limit = criteria.limit\n\t\t\tlet skipped = 0\n\t\t\tlet yielded = 0\n\t\t\tfor (const row of await this.#candidates(store, schema, plan)) {\n\t\t\t\tif (limit !== undefined && yielded >= limit) break\n\t\t\t\tif (!matchesCriteria(row, conditions)) continue\n\t\t\t\tif (skipped < offset) {\n\t\t\t\t\tskipped += 1\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tyielded += 1\n\t\t\t\tyield row\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\tasync snapshot(tables?: readonly string[]): Promise<() => Promise<void>> {\n\t\ttry {\n\t\t\tconst database = this.#require()\n\t\t\t// A whole-store capture excludes the reserved meta store — it is driver\n\t\t\t// bookkeeping, not caller data, and rolling it back would undo a `stamp`\n\t\t\t// unrelated to the caller's snapshot scope. An explicit `tables` list is\n\t\t\t// caller-scoped already and passes through untouched.\n\t\t\tconst names = tables ?? database.stores.filter((name) => name !== META_STORE)\n\t\t\tconst captured = new Map<\n\t\t\t\tstring,\n\t\t\t\t{ readonly keys: readonly IDBValidKey[]; readonly rows: readonly Row[] }\n\t\t\t>()\n\t\t\tif (names.length > 0) {\n\t\t\t\t// Capture EVERY store inside ONE read transaction, so the snapshot is a\n\t\t\t\t// single point-in-time view — a concurrent writer can never leave the\n\t\t\t\t// capture straddling two different states (each store's keys/records\n\t\t\t\t// call previously ran in its OWN implicit transaction).\n\t\t\t\tawait database.read(names, async (transaction) => {\n\t\t\t\t\tfor (const name of names) {\n\t\t\t\t\t\tconst store = transaction.store(name)\n\t\t\t\t\t\tcaptured.set(name, { keys: await store.keys(), rows: await store.records() })\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst current = this.#require()\n\t\t\t\t\tconst restorable = names.filter((name) => current.stores.includes(name))\n\t\t\t\t\tif (restorable.length === 0) return\n\t\t\t\t\t// Restore every captured store in one transaction, so a rollback is atomic.\n\t\t\t\t\tawait current.write(restorable, async (transaction) => {\n\t\t\t\t\t\tfor (const name of restorable) {\n\t\t\t\t\t\t\tconst snapshot = captured.get(name)\n\t\t\t\t\t\t\tif (snapshot === undefined) continue\n\t\t\t\t\t\t\tconst store = transaction.store(name)\n\t\t\t\t\t\t\tawait store.clear()\n\t\t\t\t\t\t\tfor (let index = 0; index < snapshot.keys.length; index += 1) {\n\t\t\t\t\t\t\t\tconst row = snapshot.rows[index]\n\t\t\t\t\t\t\t\tconst key = snapshot.keys[index]\n\t\t\t\t\t\t\t\tif (row === undefined || key === undefined) {\n\t\t\t\t\t\t\t\t\tthrow new DatabaseError('DRIVER', 'IndexedDB snapshot entry is incomplete', {\n\t\t\t\t\t\t\t\t\t\ttable: name,\n\t\t\t\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tawait store.set(row, key)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthrow this.#wrap(error)\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\t/**\n\t * Return the persisted {@link DriverMeta}, or `undefined` when the store has\n\t * never been stamped.\n\t *\n\t * @remarks\n\t * Reads `'meta'` from the reserved {@link META_STORE}, narrowing the\n\t * structured-clone value with the core {@link isDriverMeta} guard (never\n\t * asserted, AGENTS §14) — a missing or malformed record returns `undefined`,\n\t * exactly like a fresh, never-stamped store.\n\t *\n\t * @returns The last-stamped {@link DriverMeta}, or `undefined`\n\t */\n\tasync meta(): Promise<DriverMeta | undefined> {\n\t\ttry {\n\t\t\tconst record = await this.#require().store(META_STORE).get('meta')\n\t\t\tif (!isDriverMeta(record)) return undefined\n\t\t\treturn record\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\t/**\n\t * Persist `meta` verbatim for a later `meta()` to return.\n\t *\n\t * @param meta - The {@link DriverMeta} to persist\n\t */\n\tasync stamp(meta: DriverMeta): Promise<void> {\n\t\ttry {\n\t\t\tawait this.#require()\n\t\t\t\t.store(META_STORE)\n\t\t\t\t.set({ ...meta }, 'meta')\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\t/**\n\t * Apply a {@link Migration} plan by reconnecting at a bumped version and\n\t * running the plan's steps inside the wrapper's `upgrade` hook.\n\t *\n\t * @remarks\n\t * IndexedDB schema DDL is legal only inside `onupgradeneeded`, so this closes\n\t * the current connection and opens a FRESH one at `version + 1`, declaring\n\t * every currently-known store (plus {@link META_STORE}) so nothing is lost,\n\t * and applying `table.remove` / `index.add` / `index.remove` /\n\t * `column.remove` inside `upgrade`. Every step's `table` is validated against\n\t * the driver's own `#schema` BEFORE the reconnect — an unknown-table step\n\t * throws `DatabaseError` `MIGRATION` without ever bumping the version.\n\t * `table.add` / `column.add` need no upgrade-time action: `table.add` is\n\t * created by the wrapper's built-in create-missing-stores pass (its\n\t * definition is already in the declared `stores`), and this driver stores\n\t * whatever a row carries — there is nothing to backfill for a new column.\n\t * `#schema` bookkeeping is updated to match the applied plan, mirroring what\n\t * `open` tracks, so subsequent pushdown planning and a later `migrate` /\n\t * `open` see the new shape.\n\t *\n\t * @param plan - The migration plan to apply\n\t */\n\tasync migrate(plan: Migration): Promise<void> {\n\t\tfor (const step of plan.steps) {\n\t\t\tif (step.operation !== 'table.add' && !this.#schema.has(step.table)) {\n\t\t\t\tthrow new DatabaseError('MIGRATION', `migrate: unknown table '${step.table}'`, {\n\t\t\t\t\ttable: step.table,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tconst current = this.#require()\n\t\tconst version = current.version\n\t\t// Project the post-migration shape into a LOCAL copy first — `#schema`\n\t\t// stays untouched until the upgrade actually commits, so a mid-upgrade\n\t\t// failure never leaves the driver's bookkeeping ahead of the real database.\n\t\tconst schema = new Map(this.#schema)\n\t\tthis.#applySteps(schema, plan.steps)\n\t\tcurrent.close()\n\t\ttry {\n\t\t\tconst database = createIndexedDBDatabase({\n\t\t\t\tname: this.#name,\n\t\t\t\tversion: version + 1,\n\t\t\t\tstores: this.#stores(schema),\n\t\t\t\tupgrade: this.#upgrade.bind(this, plan.steps),\n\t\t\t})\n\t\t\tawait database.connect()\n\t\t\t// Only on success: adopt the connection AND commit the local map.\n\t\t\tthis.#database = database\n\t\t\tthis.#schema = schema\n\t\t} catch (error) {\n\t\t\t// The old connection was closed to allow the versionchange attempt;\n\t\t\t// reconnect at the PRE-migration schema/version so the driver is left\n\t\t\t// usable, with `#schema` (and the real database) unchanged.\n\t\t\tawait this.#reopen()\n\t\t\t// Inside `migrate()`, an `UPGRADE` fault means THIS migration failed —\n\t\t\t// map it to `MIGRATION`, not the generic `DRIVER` every other caller gets\n\t\t\t// (see `mapMigrationError` @remarks).\n\t\t\tthrow isIndexedDBError(error) ? mapMigrationError(error) : error\n\t\t}\n\t}\n\n\t// === Private\n\n\t#require(): IndexedDBDatabaseInterface {\n\t\tif (this.#database === undefined) {\n\t\t\tthrow new DatabaseError('CLOSED', `IndexedDB database '${this.#name}' is not open`, {\n\t\t\t\tname: this.#name,\n\t\t\t})\n\t\t}\n\t\treturn this.#database\n\t}\n\n\t// The shared backend-fault boundary: no `IndexedDBError` may leak through\n\t// `DriverInterface` — every public method's `catch` routes here. A\n\t// `DatabaseError` this driver threw itself (the `CLOSED` gate, the\n\t// `NOT_FOUND` table guard, `migrate`'s own `MIGRATION` validation) passes\n\t// through unchanged; only a genuine backend `IndexedDBError` is remapped.\n\t#wrap(error: unknown): unknown {\n\t\treturn isIndexedDBError(error) ? mapIndexedDBError(error) : error\n\t}\n\n\t#store(table: string) {\n\t\treturn this.#require().store(table)\n\t}\n\n\t// Project a schema map into the wrapper's declared-stores shape — the\n\t// reserved meta store is always declared alongside every table, out-of-line\n\t// (keys are passed explicitly), with the declared secondary indexes becoming\n\t// each store's `createIndex` definitions. Shared by `open`, `migrate`, and\n\t// `#reopen` so the projection never drifts between them.\n\t#stores(schema: ReadonlyMap<string, TableSchema>): Record<string, StoreDefinition> {\n\t\tconst stores: Record<string, StoreDefinition> = { [META_STORE]: {} }\n\t\tfor (const table of schema.values()) {\n\t\t\tstores[table.name] = {\n\t\t\t\tindexes: table.indexes.map((columns) => {\n\t\t\t\t\tconst [column] = columns\n\t\t\t\t\treturn {\n\t\t\t\t\t\tname: deriveIndexName(columns),\n\t\t\t\t\t\tpath: columns.length === 1 && column !== undefined ? column : [...columns],\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t}\n\t\t}\n\t\treturn stores\n\t}\n\n\t// Reconnect at the CURRENT `#schema` with no version bump (auto-managed\n\t// mode, mirroring `open`) — used to restore a working connection after a\n\t// failed `migrate` left the prior connection closed.\n\tasync #reopen(): Promise<void> {\n\t\tconst database = createIndexedDBDatabase({\n\t\t\tname: this.#name,\n\t\t\tstores: this.#stores(this.#schema),\n\t\t})\n\t\tawait database.connect()\n\t\tthis.#database = database\n\t}\n\n\t// The candidate-superset read for a plan. The primary store already returns rows\n\t// in primary-key order — the same order `scan` yields, which `applyCriteria`\n\t// preserves for an unordered query. A secondary index returns them in INDEX-key\n\t// order, so re-sort by the primary key to reproduce scan order; the engine then\n\t// filters / orders / pages exactly, so a native read equals the scan path.\n\tasync #candidates(\n\t\tstore: IndexedDBStoreInterface,\n\t\tschema: TableSchema,\n\t\tplan: QueryPlan,\n\t): Promise<readonly Row[]> {\n\t\tif (plan.index === null) return store.records(plan.range)\n\t\tconst rows = [...(await store.index(plan.index).records(plan.range))]\n\t\trows.sort((left, right) =>\n\t\t\tcompareValues(extractKey(left, schema.primary), extractKey(right, schema.primary)),\n\t\t)\n\t\treturn rows\n\t}\n\n\t#table(name: string): TableSchema {\n\t\tconst schema = this.#schema.get(name)\n\t\tif (schema === undefined) {\n\t\t\tthrow new DatabaseError('NOT_FOUND', `table '${name}' is not declared`, { table: name })\n\t\t}\n\t\treturn schema\n\t}\n\n\t// Mirror a migration plan's steps onto a LOCAL schema map — the same\n\t// bookkeeping `open` does for a freshly declared schema — without touching\n\t// `#schema`, so a failed migrate never leaves the driver's bookkeeping ahead\n\t// of the real database. The caller commits the map into `#schema` only after\n\t// the upgrade connects successfully.\n\t#applySteps(schema: Map<string, TableSchema>, steps: readonly MigrationStep[]): void {\n\t\tfor (const step of steps) {\n\t\t\tswitch (step.operation) {\n\t\t\t\tcase 'table.add':\n\t\t\t\t\tif (!schema.has(step.table.name)) schema.set(step.table.name, step.table)\n\t\t\t\t\tbreak\n\t\t\t\tcase 'table.remove':\n\t\t\t\t\tschema.delete(step.table)\n\t\t\t\t\tbreak\n\t\t\t\tcase 'column.add': {\n\t\t\t\t\tconst table = schema.get(step.table)\n\t\t\t\t\tif (\n\t\t\t\t\t\ttable !== undefined &&\n\t\t\t\t\t\t!table.columns.some((column) => column.name === step.column.name)\n\t\t\t\t\t) {\n\t\t\t\t\t\tschema.set(step.table, { ...table, columns: [...table.columns, step.column] })\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcase 'column.remove': {\n\t\t\t\t\tconst table = schema.get(step.table)\n\t\t\t\t\tif (table !== undefined) {\n\t\t\t\t\t\tschema.set(step.table, {\n\t\t\t\t\t\t\t...table,\n\t\t\t\t\t\t\tcolumns: table.columns.filter((column) => column.name !== step.column),\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcase 'index.add': {\n\t\t\t\t\tconst table = schema.get(step.table)\n\t\t\t\t\tif (table !== undefined) {\n\t\t\t\t\t\tschema.set(step.table, { ...table, indexes: [...table.indexes, step.index] })\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcase 'index.remove': {\n\t\t\t\t\tconst table = schema.get(step.table)\n\t\t\t\t\tif (table !== undefined) {\n\t\t\t\t\t\tschema.set(step.table, {\n\t\t\t\t\t\t\t...table,\n\t\t\t\t\t\t\tindexes: table.indexes.filter((index) => !deepEqual(index, step.index)),\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Runs INSIDE the wrapper's versionchange transaction (see `migrate`\n\t// @remarks). `table.add` / `column.add` are no-ops here — see `migrate`\n\t// @remarks for why. `column.remove` is the one step touching existing rows:\n\t// it walks a live cursor and rewrites each row through the core\n\t// `migrateRows`, updating in place — the only IDB-await-only work permitted\n\t// inside an upgrade transaction.\n\tasync #upgrade(steps: readonly MigrationStep[], context: IndexedDBUpgradeContext): Promise<void> {\n\t\tfor (const step of steps) {\n\t\t\tswitch (step.operation) {\n\t\t\t\tcase 'table.remove':\n\t\t\t\t\tcontext.drop(step.table)\n\t\t\t\t\tbreak\n\t\t\t\tcase 'index.add': {\n\t\t\t\t\tconst name = deriveIndexName(step.index)\n\t\t\t\t\tconst [column] = step.index\n\t\t\t\t\tconst path = step.index.length === 1 && column !== undefined ? column : [...step.index]\n\t\t\t\t\tcontext.transaction.objectStore(step.table).createIndex(name, path)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcase 'index.remove':\n\t\t\t\t\tcontext.transaction.objectStore(step.table).deleteIndex(deriveIndexName(step.index))\n\t\t\t\t\tbreak\n\t\t\t\tcase 'column.remove': {\n\t\t\t\t\tconst store = context.store(step.table)\n\t\t\t\t\tlet cursor = await store.cursor()\n\t\t\t\t\twhile (cursor !== null) {\n\t\t\t\t\t\tconst [migrated] = migrateRows([cursor.value], [step])\n\t\t\t\t\t\tif (migrated === undefined) {\n\t\t\t\t\t\t\tthrow new DatabaseError('MIGRATION', 'migrate: transformed row is missing', {\n\t\t\t\t\t\t\t\ttable: step.table,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait cursor.update(migrated)\n\t\t\t\t\t\tcursor = await cursor.continue()\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcase 'table.add':\n\t\t\t\tcase 'column.add':\n\t\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n","import type { DriverInterface } from '@src/core'\nimport { IndexedDBDriver } from './drivers/IndexedDBDriver.js'\n\n/**\n * Create a persistent IndexedDB {@link DriverInterface} for the core database layer.\n *\n * @remarks\n * Pass it to `createDatabase` from `@orkestrel/database` to run the whole typed database +\n * relations stack against IndexedDB instead of memory — the `Database` / `Table` /\n * `Query` / relations API is unchanged; only where the bytes live changes. The\n * driver is built on the published `@orkestrel/indexeddb` wrapper in auto-managed\n * mode, so a table added to the `tables` map is created on the next open with no\n * version bump. This unit omits `transaction` / `aggregate` (see\n * {@link IndexedDBDriver} `@remarks`).\n *\n * @param name - The IndexedDB database name to open or create\n * @returns A {@link DriverInterface} backed by IndexedDB\n *\n * @example\n * ```ts\n * import { createDatabase } from '@orkestrel/database'\n * import { stringShape } from '@orkestrel/contract'\n * import { createIndexedDBDriver } from '@orkestrel/database/browser'\n *\n * const db = createDatabase({\n * \tdriver: createIndexedDBDriver('app'),\n * \ttables: { users: { id: stringShape(), name: stringShape() } },\n * })\n * await db.table('users').set({ id: 'u1', name: 'Ada' }) // persisted to IndexedDB\n * ```\n */\nexport function createIndexedDBDriver(name: string): DriverInterface {\n\treturn new IndexedDBDriver(name)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["#name","#database","#schema","#load","#stores","#alignIdentities","#identities","#wrap","#store","#table","#mutate","#candidates","#stream","#require","#projectIdentities","#upgrade","#migrationError","#reopen","#recoveryError"],"sources":["../../../src/browser/constants.ts","../../../src/browser/helpers.ts","../../../src/browser/drivers/IndexedDBDriver.ts","../../../src/browser/factories.ts"],"sourcesContent":["import type { ColumnStorage } from '@src/core'\n\n// The column types that are valid, orderable IndexedDB keys (string / number key\n// space). `boolean` / `json` / `blob` are not valid `IDBValidKey`s and would make\n// a range silently miss rows, so they are never pushed down.\nexport const INDEXABLE_STORAGE: ReadonlySet<ColumnStorage> = new Set<ColumnStorage>([\n\t'text',\n\t'integer',\n\t'real',\n])\n\n// The reserved out-of-line store the driver stamps its DriverMetadata into\n// (`metadata` / `stamp`). A user table declared with this exact name would\n// collide with the driver's own bookkeeping — callers must avoid it.\nexport const METADATA_STORE = '__metadata__'\n","import type { Condition, QueryInput, TableSchema } from '@src/core'\nimport type { IndexedDBError } from '@orkestrel/indexeddb'\nimport type { StoreDefinition } from '@orkestrel/indexeddb'\nimport type { QueryPlan } from './types.js'\nimport { compareValues, DatabaseError, isKey } from '@src/core'\nimport {\n\trangeAboveKey,\n\trangeBelowKey,\n\trangeBetweenKeys,\n\trangeExactKey,\n\trangeFromKey,\n\trangeToKey,\n} from '@orkestrel/indexeddb'\nimport { INDEXABLE_STORAGE } from './constants.js'\n\n// The IndexedDB driver's pushdown planner. A pure function over the portable\n// `QueryInput`: it decides which index (or the primary key) a read can narrow on\n// and the `IDBKeyRange` to use, so the driver fetches a candidate SUPERSET rather\n// than every row. The core engine then refines that superset to the exact result\n// — so a plan is only ever allowed to over-fetch, never to drop a matching row.\n// Anything it cannot prove range-exact (a non-comparison operator, a non-orderable\n// column type, a nested path, a non-scalar operand) falls through to a full scan.\n\n/**\n * The `IDBKeyRange` a single {@link Condition} maps to, when its operator is one\n * of the six exact key comparisons over scalar operands; otherwise\n * `undefined`.\n *\n * @remarks\n * Only the comparison operators (`equals`/`above`/`below`/`from`/`to`/`between`)\n * translate to a key range that a typed (string/number) column can back with an\n * IndexedDB store/index read — see {@link selectPlan} for the caveats that\n * decide WHICH of `below`/`to` may drive a SECONDARY-index read versus the\n * primary store only (a column-type / absent-row concern, not a range-shape\n * one). `starts` is excluded — its prefix range can miss strings past U+FFFF;\n * the membership / negation / pattern / existence operators (`not`/`like`/`glob`/\n * `ends`/`any`/`none`/`absent`/`present`) have no single exact range. The operand\n * guard (`typeof` string/number) rejects a non-scalar value (e.g. an array, a\n * boolean) that is not a usable key. `between` additionally guards against a\n * REVERSED pair (`first > second`): native `IDBKeyRange.bound` throws a raw\n * `DataError` `DOMException` for a lower bound above the upper bound, so a\n * reversed pair returns `undefined` here (falls back to a full scan, which the\n * engine then correctly resolves to an empty result) rather than letting a\n * native exception escape untyped — the same defensive posture as every other\n * backend, which returns empty for a reversed/empty range instead of throwing.\n * The switch is exhaustive over every {@link ConditionOperator}, so a new\n * operator forces a deliberate decision here rather than silently defaulting\n * to a (possibly lossy) range.\n *\n * @param condition - The condition to translate\n * @returns Its exact key range, or `undefined` when the operator/operands cannot push\n */\nexport function conditionToRange(condition: Condition): IDBKeyRange | undefined {\n\tconst first = condition.values[0]\n\tconst second = condition.values[1]\n\tswitch (condition.operator) {\n\t\tcase 'equals':\n\t\t\treturn isKey(first) ? rangeExactKey(first) : undefined\n\t\tcase 'above':\n\t\t\treturn isKey(first) ? rangeAboveKey(first) : undefined\n\t\tcase 'below':\n\t\t\treturn isKey(first) ? rangeBelowKey(first) : undefined\n\t\tcase 'from':\n\t\t\treturn isKey(first) ? rangeFromKey(first) : undefined\n\t\tcase 'to':\n\t\t\treturn isKey(first) ? rangeToKey(first) : undefined\n\t\tcase 'between':\n\t\t\t// A reversed pair (`first > second`) has no valid IDBKeyRange — native\n\t\t\t// `bound` throws rather than returning empty. Fall back to a full scan\n\t\t\t// instead of letting that DOMException escape (the engine then yields\n\t\t\t// the correct, empty result over `compareValues(value, first) >= 0 &&\n\t\t\t// compareValues(value, second) <= 0`, which no row can satisfy).\n\t\t\treturn isKey(first) && isKey(second) && compareValues(first, second) <= 0\n\t\t\t\t? rangeBetweenKeys(first, second)\n\t\t\t\t: undefined\n\t\tcase 'not':\n\t\tcase 'like':\n\t\tcase 'glob':\n\t\tcase 'starts':\n\t\tcase 'ends':\n\t\tcase 'any':\n\t\tcase 'none':\n\t\tcase 'absent':\n\t\tcase 'present':\n\t\t\treturn undefined\n\t}\n}\n\n/**\n * Plan an IndexedDB read for a {@link QueryInput} — pick the index (or the primary\n * store) and {@link IDBKeyRange} to narrow by, falling back to a full scan.\n *\n * @remarks\n * Pushdown is sound ONLY when every condition is `and`-joined: the engine folds\n * conditions left-to-right (`c1 && c2 && … && cn`), so the result is a subset of\n * each — narrowing on any one is then a valid superset. A single `or` breaks that\n * (a row can match through a later condition the range would exclude), so any `or`\n * forces a full scan. Otherwise it scans the conditions in order and selects the\n * **first** one that is provably range-exact and backed by a key: a comparison\n * operator (`conditionToRange`) over a single, orderable (`text`/`integer`/`real`)\n * column that is either the table's primary key (read the store directly with\n * `index` omitted) or has a single-column secondary index (named exactly the column — read\n * that index). A condition whose column is a nested {@link FieldPath} array\n * (descends a json value, not a key), is absent from the schema, is a non-orderable\n * type (`boolean`/`json`/`blob`), uses a non-comparison operator, or has a\n * non-scalar operand cannot push and is skipped.\n *\n * **`below`/`to` may drive a SECONDARY-index range only when the column has NO\n * absent/null rows to lose — which this planner cannot verify from the schema\n * alone, so it restricts them to the PRIMARY store, where that is always true.**\n * The engine's total order (`compareValues`, see `@src/core`) ranks\n * `undefined` (absent) and `null` BELOW every number/string, so\n * `matchesCondition('below' | 'to', …)` is TRUE for a row whose field is absent\n * or `null` — but a secondary IndexedDB index has NO ENTRY for a row whose\n * indexed field is absent/`null`, so a `below`/`to` range read against that\n * index would SILENTLY DROP those rows (they can never be over-fetched, only\n * missed — the one shape of lossiness this planner must never produce). The\n * table's PRIMARY key is exempt: a row's primary-key value is always present\n * and never `null` (it is the row's identity, enforced at write time), so a\n * `below`/`to` range against the primary store can never exclude an\n * absent/null-keyed row because no such row exists. `equals`/`above`/`from`/\n * `between` stay index-eligible on ANY orderable column, primary or secondary:\n * each is bounded below by a scalar (`equals`/`between`'s lower bound, `above`/\n * `from`'s lower bound), and every scalar strictly out-ranks `undefined`/`null`\n * in the total order, so an absent/null-valued row can never satisfy them — the\n * index's silence on such a row is harmless (it was never going to match).\n * **Declared-type trust caveat:** this reasoning holds under the contract that\n * an {@link INDEXABLE_STORAGE} column, once contract-validated at write time,\n * holds only `string | number | null` (or is absent) — never some other\n * runtime value that could rank differently; a driver bypassing the write\n * contract (writing raw rows directly to the store) could defeat this\n * argument, but that is out of scope for a planner reading validated schema\n * metadata.\n *\n * When no condition qualifies the plan is a full scan (`{}`) and the engine\n * does everything. The plan is always a SUPERSET of the\n * matching rows — the only correctness contract — so the driver may safely run\n * the exact engine over it.\n *\n * @param input - The read specification (its `conditions` drive the plan), or\n * `undefined` for an unconditional read\n * @param schema - The table's schema — its `primary` key and column types\n * @param available - The secondary-index names that physically exist on the store\n * (`store.indexes`); a single-column index is named exactly its column\n * @returns The index + range to read, narrowing to a superset (never lossy)\n *\n * @example\n * ```ts\n * selectPlan({ conditions: [eq('id', 'u1')] }, schema, []) // { range: only('u1') }\n * selectPlan({ conditions: [from('age', 18)] }, schema, ['age']) // { index: 'age', range: from(18) }\n * selectPlan({ conditions: [contains('name', 'a')] }, schema, []) // {}\n * ```\n */\nexport function selectPlan(\n\tinput: QueryInput | undefined,\n\tschema: TableSchema,\n\tavailable: readonly string[],\n): QueryPlan {\n\tconst conditions = input?.conditions ?? []\n\t// A single condition's range is a SUPERSET of the result only when the result\n\t// implies that condition — which holds iff every condition is `and`-joined (the\n\t// fold is `c1 && c2 && … && cn`, so the result is a subset of each). A single\n\t// `or` breaks that (a row can match via a later condition the range excludes),\n\t// so any `or` forces a full scan. The first condition's connector only seeds the\n\t// fold and is ignored (AGENTS — the `Condition.connector` contract).\n\tif (conditions.slice(1).some((condition) => condition.connector === 'or')) {\n\t\treturn {}\n\t}\n\tfor (const condition of conditions) {\n\t\t// An array column is a nested FieldPath into a json value — not a key.\n\t\tif (typeof condition.column !== 'string') continue\n\t\tconst column = schema.columns.find((candidate) => candidate.name === condition.column)\n\t\tif (column === undefined || !INDEXABLE_STORAGE.has(column.storage)) continue\n\t\tconst range = conditionToRange(condition)\n\t\tif (range === undefined) continue\n\t\tif (condition.column === schema.primary) return { range }\n\t\t// `below`/`to` can silently drop an absent/null-valued row from a SECONDARY\n\t\t// index (see @remarks) — only the primary store (handled above) is safe.\n\t\t// Keep scanning: a later condition may still qualify.\n\t\tif (condition.operator === 'below' || condition.operator === 'to') continue\n\t\tif (available.includes(condition.column)) {\n\t\t\treturn { index: condition.column, range }\n\t\t}\n\t\t// The column is range-exact but has no usable index — keep looking.\n\t}\n\treturn {}\n}\n\n/**\n * Map a backend {@link IndexedDBError} to the portable `DatabaseError` taxonomy\n * — the default mapping used everywhere except inside `migrate()`.\n *\n * @remarks\n * No backend fault may leak through `DriverInterface` as a raw `IndexedDBError`.\n * `CONSTRAINT` (a unique-key violation) is a `CONFLICT` — the same code every\n * other backend uses for a duplicate key. `CLOSED`/`NOT_OPEN`/`INVALID` (the\n * connection is gone, never opened, or the native handle is stale) collapse to\n * `CLOSED`. `QUOTA` is a genuine infrastructure fault (`DRIVER`) carrying a\n * machine-readable `context.code` so a caller can branch without parsing the\n * message. A blocked open or versionchange is nonterminal in the backend and\n * remains pending until the competing connection closes, so it never reaches\n * this error mapper. Every other code (`UPGRADE` here — see\n * {@link mapMigrationError} for the `migrate()`-only remapping to `MIGRATION` —\n * `ABORTED`, `NOT_FOUND`, `DATA`, `OPEN`, `INACTIVE`, `READONLY`, `UNKNOWN`) is\n * an unexpected infrastructure fault and maps to `DRIVER` — the driver opens\n * its own readwrite transactions, so a `READONLY` fault can only mean the\n * backend behaved unexpectedly. The original error is always preserved as\n * `context.cause` for diagnostics.\n *\n * @param error - The backend error to translate\n * @returns The portable `DatabaseError`\n */\nexport function mapIndexedDBError(error: IndexedDBError): DatabaseError {\n\tswitch (error.code) {\n\t\tcase 'CONSTRAINT':\n\t\t\treturn new DatabaseError('CONFLICT', error.message, { cause: error })\n\t\tcase 'CLOSED':\n\t\tcase 'NOT_OPEN':\n\t\tcase 'INVALID':\n\t\t\treturn new DatabaseError('CLOSED', error.message, { cause: error })\n\t\tcase 'QUOTA':\n\t\t\treturn new DatabaseError('DRIVER', error.message, { cause: error, code: 'QUOTA' })\n\t\tcase 'UPGRADE':\n\t\tcase 'ABORTED':\n\t\tcase 'NOT_FOUND':\n\t\tcase 'DATA':\n\t\tcase 'OPEN':\n\t\tcase 'INACTIVE':\n\t\tcase 'READONLY':\n\t\tcase 'UNKNOWN':\n\t\t\treturn new DatabaseError('DRIVER', error.message, { cause: error })\n\t}\n}\n\n/**\n * Map a backend {@link IndexedDBError} to the portable `DatabaseError` taxonomy\n * for use INSIDE `migrate()` — the one context where `UPGRADE` means the\n * migration itself failed, not a generic driver fault.\n *\n * @remarks\n * `migrate()` reconnects at a bumped version inside `onupgradeneeded`; a\n * rejection there (an inapplicable step, a native `ConstraintError` from a\n * duplicate index, …) surfaces as `IndexedDBError` `UPGRADE` and must become a\n * `MIGRATION` `DatabaseError` so a caller can distinguish \"this migration plan\n * failed\" from \"the driver hit an unrelated infrastructure fault\". Every other\n * code defers to {@link mapIndexedDBError} unchanged.\n *\n * @param error - The backend error to translate\n * @returns The portable `DatabaseError`\n */\nexport function mapMigrationError(error: IndexedDBError): DatabaseError {\n\tif (error.code === 'UPGRADE') {\n\t\treturn new DatabaseError('MIGRATION', error.message, { cause: error })\n\t}\n\treturn mapIndexedDBError(error)\n}\n\n/**\n * Derive an IndexedDB index name for a declared column group — a bare column\n * name for a single-column index, a deterministic collision-free encoding for a\n * compound one.\n *\n * @remarks\n * Naming a compound index by joining its columns with `_` (`['a', 'b'] →\n * 'a_b'`) collides with a single-column index over a column LITERALLY named\n * `'a_b'` — the same name, two different key paths (`'a_b'` vs `['a', 'b']`),\n * which either throws a native `ConstraintError` from a duplicate\n * `createIndex` call at open, or (worse) lets {@link selectPlan}'s name-based\n * lookup match the wrong index. A single-column index keeps the BARE column\n * name — {@link selectPlan} matches `available.includes(condition.column)` by\n * that exact name, so a single-column index must stay named after its column\n * verbatim. A compound index instead encodes each column as a LENGTH-PREFIXED\n * segment (`'2#1:a1:b'`), so the boundary between columns is self-describing\n * and cannot be reconstructed by any other column list — including one\n * containing a column that happens to look like an encoded segment.\n *\n * @param columns - The index's column group, in declared order\n * @returns The index name to pass to `createIndex` / read back from `indexNames`\n *\n * @example\n * ```ts\n * deriveIndexedDBIndexName(['age']) // 'age'\n * deriveIndexedDBIndexName(['a', 'b']) // '2#1:a1:b'\n * ```\n */\nexport function deriveIndexedDBIndexName(columns: readonly string[]): string {\n\tconst [column] = columns\n\tif (columns.length === 1 && column !== undefined) return column\n\treturn `${columns.length}#${columns.map((part) => `${part.length}:${part}`).join('')}`\n}\n\n/**\n * Project a table schema into the IndexedDB wrapper's store definition.\n *\n * @param schema - Portable table schema\n * @returns Store definition with declared indexes\n */\nexport function schemaToStore(schema: TableSchema): StoreDefinition {\n\treturn {\n\t\tindexes: schema.indexes.map((columns) => {\n\t\t\tconst [column] = columns\n\t\t\treturn {\n\t\t\t\tname: deriveIndexedDBIndexName(columns),\n\t\t\t\tpath: columns.length === 1 && column !== undefined ? column : [...columns],\n\t\t\t}\n\t\t}),\n\t}\n}\n","import type {\n\tQueryInput,\n\tDriverInterface,\n\tDriverMetadata,\n\tKey,\n\tMigrationInput,\n\tMigrationStep,\n\tOperationOptions,\n\tRow,\n\tTableSchema,\n} from '@src/core'\nimport {\n\tapplyQuery,\n\tbindRowKey,\n\tcheckAbort,\n\tcloneDriverMetadata,\n\tcloneMigrationInput,\n\tcompareValues,\n\tDatabaseError,\n\tequalsValue,\n\textractKey,\n\tisKey,\n\tisDatabaseError,\n\tmatchesQuery,\n\tmigrateRows,\n\tnormalizeDriverSchema,\n\tplanMigration,\n\tprojectMigrationSchema,\n\tvalidatePage,\n} from '@src/core'\nimport type {\n\tIndexedDBDatabaseInterface,\n\tIndexedDBStoreInterface,\n\tIndexedDBTransactionStoreInterface,\n\tIndexedDBUpgradeContext,\n\tStoreDefinition,\n} from '@orkestrel/indexeddb'\nimport { createIndexedDBDatabase, isIndexedDBError } from '@orkestrel/indexeddb'\nimport type { QueryPlan } from '../types.js'\nimport {\n\tderiveIndexedDBIndexName,\n\tmapIndexedDBError,\n\tmapMigrationError,\n\tschemaToStore,\n\tselectPlan,\n} from '../helpers.js'\nimport { METADATA_STORE } from '../constants.js'\n\n/**\n * The IndexedDB {@link DriverInterface} — the persistent browser backend, built on\n * the published `@orkestrel/indexeddb` wrapper.\n *\n * @remarks\n * A thin adapter: it implements the storage primitives the core database layer\n * needs (`open` / `close` / `read` / `write` / `delete` / `keys` / `scan` / `clear`\n * / `snapshot`) by delegating to the wrapper's typed store operations — it never\n * touches raw IndexedDB. Rows are stored with **out-of-line keys** (the database\n * passes the key explicitly, `store.set(row, key)`), so each table is declared as a\n * key-path-less store. A fresh database opens in **auto-managed** mode (no fixed\n * version), creating missing declared stores on demand. Once metadata is persisted,\n * bootstrap captures the live stores and version, rejects a missing persisted store,\n * and pins the final open to that version so a competing versionchange cannot\n * silently recreate lost storage. The driver's bulk reads (`scan` / `keys`) use the\n * wrapper's native `getAll` / `getAllKeys`, and `snapshot` rolls back through one\n * atomic wrapper transaction.\n *\n * It also implements the optional native `records` / `stream` hooks\n * (AGENTS §21): `selectPlan` ({@link selectPlan}) turns the {@link QueryInput} into a\n * key-range pushdown over the primary key or a single-column secondary index,\n * fetching a candidate **superset** that the core engine (`applyQuery` /\n * `matchesQuery`) then refines — so a native read is byte-identical to a full\n * scan, just cheaper. Pushdown is conservative: only the exact-comparison\n * operators over orderable columns narrow to a range; everything else falls back\n * to a full scan + the engine.\n *\n * @remarks\n * This driver also implements `migrate` / `metadata` / `stamp`. `metadata` / `stamp`\n * persist the {@link DriverMetadata} in a reserved out-of-line store,\n * {@link METADATA_STORE} (`__metadata__`) — excluded from a whole-store `snapshot`\n * capture, since it is driver bookkeeping, not caller data. `migrate` applies a\n * {@link Migration} plan natively: IndexedDB schema DDL (creating/dropping a\n * store, creating/dropping an index) is legal only inside a versionchange\n * transaction (`onupgradeneeded`), so `migrate` closes the current connection\n * and opens a FRESH one at `version + 1` with an `upgrade` hook that walks the\n * plan's steps — dropping stores, adding/removing indexes on the raw\n * `IDBTransaction`, and rewriting rows for `column.remove` via a cursor walk\n * (the one step needing to touch existing data; `column.add` is a no-op — this\n * driver stores whatever a row carries, so there is nothing to backfill). A\n * step referencing an unknown table is validated BEFORE the reconnect, so a\n * `MIGRATION` `DatabaseError` never wastes a version bump.\n *\n * @remarks\n * This unit deliberately OMITS `aggregate` / `transaction`. There is no native\n * `aggregate` (IndexedDB has no native SUM/AVG); the engine over the narrowed\n * `records` covers it. `transaction` is impossible here: the wrapper auto-commits\n * an `IDBTransaction` when control yields outside its request chain, so arbitrary\n * callback awaits cannot remain inside one native transaction. Every atomic\n * multi-operation sequence in this driver\n * (`snapshot`'s rollback) instead runs entirely inside ONE `db.write(...)` scope.\n */\nexport class IndexedDBDriver implements DriverInterface {\n\treadonly #name: string\n\t#identities = new Map<string, object>()\n\t#schema = new Map<string, TableSchema>()\n\t#database: IndexedDBDatabaseInterface | undefined\n\n\tconstructor(name: string) {\n\t\tthis.#name = name\n\t}\n\n\tasync open(schema: readonly TableSchema[]): Promise<void> {\n\t\tconst owned = normalizeDriverSchema(schema)\n\t\t// The reserved metadata store name may never collide with a caller-declared\n\t\t// table — it would silently corrupt this driver's own `metadata`/`stamp`\n\t\t// bookkeeping (AGENTS §12 — a programmer error throws).\n\t\tif (owned.some((table) => table.name === METADATA_STORE)) {\n\t\t\tthrow new DatabaseError(\n\t\t\t\t'VALIDATION',\n\t\t\t\t`open: table name '${METADATA_STORE}' is reserved for driver metadata`,\n\t\t\t\t{ table: METADATA_STORE },\n\t\t\t)\n\t\t}\n\t\tthis.#database?.close()\n\t\tthis.#database = undefined\n\t\tthis.#schema = new Map()\n\t\ttry {\n\t\t\t// Reconnect cleanly so the auto-managed bootstrap can ensure the\n\t\t\t// metadata store exists without being blocked by this driver's own\n\t\t\t// open handle. The final persisted open is version-pinned below.\n\t\t\t// Build the new schema into a LOCAL map first — never mutate `#schema`\n\t\t\t// in place — so a reopen with a REDUCED schema replaces the map\n\t\t\t// wholesale instead of retaining ghost tables the caller no longer\n\t\t\t// declared.\n\t\t\tconst bootstrap = createIndexedDBDatabase({\n\t\t\t\tname: this.#name,\n\t\t\t\tstores: { [METADATA_STORE]: {} },\n\t\t\t})\n\t\t\tlet persisted: DriverMetadata | undefined\n\t\t\tlet stores: readonly string[] = []\n\t\t\tlet version = 0\n\t\t\ttry {\n\t\t\t\tawait bootstrap.connect()\n\t\t\t\tstores = bootstrap.stores\n\t\t\t\tversion = bootstrap.version\n\t\t\t\tpersisted = await this.#load(bootstrap)\n\t\t\t} finally {\n\t\t\t\tbootstrap.close()\n\t\t\t}\n\t\t\tif (persisted !== undefined) {\n\t\t\t\tfor (const table of persisted.schema) {\n\t\t\t\t\tif (!stores.includes(table.name)) {\n\t\t\t\t\t\tthrow new DatabaseError('DRIVER', 'Stored IndexedDB store is missing', {\n\t\t\t\t\t\t\tname: this.#name,\n\t\t\t\t\t\t\tstore: table.name,\n\t\t\t\t\t\t\taspect: 'missing',\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst map = new Map<string, TableSchema>()\n\t\t\tfor (const table of normalizeDriverSchema(persisted?.schema ?? owned)) {\n\t\t\t\tmap.set(table.name, table)\n\t\t\t}\n\t\t\tconst database = createIndexedDBDatabase({\n\t\t\t\tname: this.#name,\n\t\t\t\t...(persisted === undefined ? {} : { version }),\n\t\t\t\tstores: this.#stores(map),\n\t\t\t})\n\t\t\ttry {\n\t\t\t\tawait database.connect()\n\t\t\t} catch (error) {\n\t\t\t\tdatabase.close()\n\t\t\t\tthrow error\n\t\t\t}\n\t\t\t// Remember the schema so the native `records` / `stream` hooks\n\t\t\t// can plan a key-range pushdown (the primary key, column types, secondary\n\t\t\t// indexes).\n\t\t\tconst identities = this.#alignIdentities(map)\n\t\t\tthis.#schema = map\n\t\t\tthis.#identities = identities\n\t\t\tthis.#database = database\n\t\t} catch (error) {\n\t\t\tthis.#identities = new Map()\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.#database?.close()\n\t\tthis.#database = undefined\n\t}\n\n\tasync read(table: string, key: Key): Promise<Row | undefined> {\n\t\ttry {\n\t\t\treturn await this.#store(table).get(key)\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\tasync write(table: string, key: Key, row: Row, options?: OperationOptions): Promise<void> {\n\t\tconst bound = bindRowKey(row, this.#table(table).primary, key)\n\t\tawait this.#mutate(table, options, async (store) => {\n\t\t\tawait store.set(bound, key)\n\t\t})\n\t}\n\n\tasync insert(table: string, key: Key, row: Row, options?: OperationOptions): Promise<void> {\n\t\tconst bound = bindRowKey(row, this.#table(table).primary, key)\n\t\tawait this.#mutate(table, options, async (store) => {\n\t\t\tawait store.add(bound, key)\n\t\t})\n\t}\n\n\tasync delete(table: string, key: Key, options?: OperationOptions): Promise<boolean> {\n\t\tlet present = false\n\t\tawait this.#mutate(table, options, async (store) => {\n\t\t\tpresent = await store.has(key)\n\t\t\tawait store.remove(key)\n\t\t})\n\t\treturn present\n\t}\n\n\tasync keys(table: string): Promise<readonly Key[]> {\n\t\ttry {\n\t\t\tconst keys = await this.#store(table).keys()\n\t\t\treturn keys.filter(isKey)\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\tasync *scan(table: string): AsyncIterable<Row> {\n\t\ttry {\n\t\t\tfor (const row of await this.#store(table).records()) yield row\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\tasync clear(table: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.#store(table).clear()\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\tasync records(table: string, input: QueryInput): Promise<readonly Row[]> {\n\t\tvalidatePage(input)\n\t\ttry {\n\t\t\tconst schema = this.#table(table)\n\t\t\tconst store = this.#store(table)\n\t\t\tconst plan = selectPlan(input, schema, store.indexes)\n\t\t\treturn applyQuery(await this.#candidates(store, schema, plan), input)\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\tstream(table: string, input: QueryInput): AsyncIterable<Row> {\n\t\tvalidatePage(input)\n\t\treturn this.#stream(table, input)\n\t}\n\n\tasync *#stream(table: string, input: QueryInput): AsyncIterable<Row> {\n\t\ttry {\n\t\t\tconst schema = this.#table(table)\n\t\t\tconst store = this.#store(table)\n\t\t\tconst plan = selectPlan(input, schema, store.indexes)\n\t\t\tconst conditions = input.conditions ?? []\n\t\t\tconst offset = input.offset ?? 0\n\t\t\tconst limit = input.limit\n\t\t\tlet skipped = 0\n\t\t\tlet yielded = 0\n\t\t\tfor (const row of await this.#candidates(store, schema, plan)) {\n\t\t\t\tif (limit !== undefined && yielded >= limit) break\n\t\t\t\tif (!matchesQuery(row, conditions)) continue\n\t\t\t\tif (skipped < offset) {\n\t\t\t\t\tskipped += 1\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tyielded += 1\n\t\t\t\tyield row\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\tasync snapshot(tables?: readonly string[]): Promise<() => Promise<void>> {\n\t\ttry {\n\t\t\tconst database = this.#require()\n\t\t\tconst requested = tables ?? [...this.#schema.keys()]\n\t\t\tconst names = [...new Set(requested)].filter(\n\t\t\t\t(name) =>\n\t\t\t\t\tname !== METADATA_STORE && this.#schema.has(name) && database.stores.includes(name),\n\t\t\t)\n\t\t\tconst captured = new Map<\n\t\t\t\tstring,\n\t\t\t\t{\n\t\t\t\t\treadonly identity: object\n\t\t\t\t\treadonly keys: readonly IDBValidKey[]\n\t\t\t\t\treadonly rows: readonly Row[]\n\t\t\t\t\treadonly schema: TableSchema\n\t\t\t\t}\n\t\t\t>()\n\t\t\tif (names.length > 0) {\n\t\t\t\tawait database.read(names, async (transaction) => {\n\t\t\t\t\tfor (const name of names) {\n\t\t\t\t\t\tconst schema = this.#schema.get(name)\n\t\t\t\t\t\tconst identity = this.#identities.get(name)\n\t\t\t\t\t\tif (schema === undefined || identity === undefined) continue\n\t\t\t\t\t\tconst store = transaction.store(name)\n\t\t\t\t\t\tcaptured.set(name, {\n\t\t\t\t\t\t\tidentity,\n\t\t\t\t\t\t\tkeys: await store.keys(),\n\t\t\t\t\t\t\trows: await store.records(),\n\t\t\t\t\t\t\tschema,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst current = this.#require()\n\t\t\t\t\tconst replacements = new Map<\n\t\t\t\t\t\tstring,\n\t\t\t\t\t\t{ readonly keys: readonly Key[]; readonly rows: readonly Row[] }\n\t\t\t\t\t>()\n\t\t\t\t\tfor (const [name, snapshot] of captured) {\n\t\t\t\t\t\tconst schema = this.#schema.get(name)\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tschema === undefined ||\n\t\t\t\t\t\t\tthis.#identities.get(name) !== snapshot.identity ||\n\t\t\t\t\t\t\t!current.stores.includes(name)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst plan = planMigration([snapshot.schema], [schema])\n\t\t\t\t\t\tconst migrated = migrateRows(snapshot.rows, plan.steps)\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tsnapshot.keys.length !== snapshot.rows.length ||\n\t\t\t\t\t\t\tmigrated.length !== snapshot.rows.length\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tthrow new DatabaseError(\n\t\t\t\t\t\t\t\t'MIGRATION',\n\t\t\t\t\t\t\t\t'IndexedDB snapshot keys and rows have different cardinality',\n\t\t\t\t\t\t\t\t{ table: name },\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst keys: Key[] = []\n\t\t\t\t\t\tconst rows: Row[] = []\n\t\t\t\t\t\tfor (const [index, key] of snapshot.keys.entries()) {\n\t\t\t\t\t\t\tconst row = migrated[index]\n\t\t\t\t\t\t\tif (!isKey(key) || row === undefined) {\n\t\t\t\t\t\t\t\tthrow new DatabaseError(\n\t\t\t\t\t\t\t\t\t'MIGRATION',\n\t\t\t\t\t\t\t\t\t'IndexedDB snapshot row has no usable primary key',\n\t\t\t\t\t\t\t\t\t{ table: name, column: schema.primary, index },\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tkeys.push(key)\n\t\t\t\t\t\t\trows.push(bindRowKey(row, schema.primary, key))\n\t\t\t\t\t\t}\n\t\t\t\t\t\treplacements.set(name, { keys, rows })\n\t\t\t\t\t}\n\t\t\t\t\tif (replacements.size === 0) return\n\t\t\t\t\tawait current.write([...replacements.keys()], async (transaction) => {\n\t\t\t\t\t\tfor (const [name, replacement] of replacements) {\n\t\t\t\t\t\t\tconst store = transaction.store(name)\n\t\t\t\t\t\t\tawait store.clear()\n\t\t\t\t\t\t\tfor (const [index, key] of replacement.keys.entries()) {\n\t\t\t\t\t\t\t\tconst row = replacement.rows[index]\n\t\t\t\t\t\t\t\tif (row === undefined || key === undefined) {\n\t\t\t\t\t\t\t\t\tthrow new DatabaseError('DRIVER', 'IndexedDB snapshot entry is incomplete', {\n\t\t\t\t\t\t\t\t\t\ttable: name,\n\t\t\t\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tawait store.set(row, key)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthrow this.#wrap(error)\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\t/**\n\t * Return the persisted {@link DriverMetadata}, or `undefined` when the store has\n\t * never been stamped.\n\t *\n\t * @remarks\n\t * Reads `'metadata'` from the reserved {@link METADATA_STORE} in one readonly\n\t * transaction that distinguishes key absence from a present `undefined`\n\t * value. Only absence returns `undefined`; present malformed state fails\n\t * closed with a payload-safe `DRIVER` error.\n\t *\n\t * @returns The last-stamped {@link DriverMetadata}, or `undefined`\n\t */\n\tasync metadata(): Promise<DriverMetadata | undefined> {\n\t\ttry {\n\t\t\treturn await this.#load(this.#require())\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\t/**\n\t * Persist an owned metadata snapshot for a later `metadata()` to return.\n\t *\n\t * @param metadata - The {@link DriverMetadata} to persist\n\t */\n\tasync stamp(metadata: DriverMetadata): Promise<void> {\n\t\tconst database = this.#require()\n\t\tconst owned = cloneDriverMetadata(metadata)\n\t\ttry {\n\t\t\tawait database\n\t\t\t\t.store(METADATA_STORE)\n\t\t\t\t.set({ version: owned.version, schema: owned.schema }, 'metadata')\n\t\t} catch (error) {\n\t\t\tthrow this.#wrap(error)\n\t\t}\n\t}\n\n\t/**\n\t * Apply a {@link Migration} plan by reconnecting at a bumped version and\n\t * running the plan's steps inside the wrapper's `upgrade` hook.\n\t *\n\t * @remarks\n\t * IndexedDB schema DDL is legal only inside `onupgradeneeded`, so this closes\n\t * the current connection and opens a FRESH one at `version + 1`, declaring\n\t * every currently-known store (plus {@link METADATA_STORE}) so nothing is lost,\n\t * and applying `table.remove` / `index.add` / `index.remove` /\n\t * `column.remove` inside `upgrade`. Every step's `table` is validated against\n\t * the driver's own `#schema` BEFORE the reconnect — an unknown-table step\n\t * throws `DatabaseError` `MIGRATION` without ever bumping the version.\n\t * `table.add` / `column.add` need no upgrade-time action: `table.add` is\n\t * created by the wrapper's built-in create-missing-stores pass (its\n\t * definition is already in the declared `stores`), and this driver stores\n\t * whatever a row carries — there is nothing to backfill for a new column.\n\t * `#schema` bookkeeping is updated to match the applied plan, mirroring what\n\t * `open` tracks, so subsequent pushdown planning and a later `migrate` /\n\t * `open` see the new shape.\n\t *\n\t * @param input - The migration plan and optional metadata stamp to apply atomically\n\t */\n\tasync migrate(input: MigrationInput): Promise<void> {\n\t\tconst current = this.#require()\n\t\tconst owned = cloneMigrationInput(input)\n\t\tconst projected = projectMigrationSchema([...this.#schema.values()], owned.plan.steps)\n\t\tif (\n\t\t\towned.metadata !== undefined &&\n\t\t\t!equalsValue(normalizeDriverSchema(owned.metadata.schema), projected)\n\t\t) {\n\t\t\tthrow new DatabaseError('MIGRATION', 'Migration metadata schema does not match the plan', {\n\t\t\t\tprojected,\n\t\t\t\tmetadata: owned.metadata.schema,\n\t\t\t})\n\t\t}\n\t\tconst schema = new Map(projected.map((table) => [table.name, table]))\n\t\tconst identities = this.#projectIdentities(this.#identities, owned.plan.steps)\n\t\tif (owned.plan.steps.length === 0) {\n\t\t\tconst metadata = owned.metadata\n\t\t\tif (metadata !== undefined) {\n\t\t\t\ttry {\n\t\t\t\t\tawait current.write(METADATA_STORE, async (transaction) => {\n\t\t\t\t\t\tawait transaction.store(METADATA_STORE).set(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tversion: metadata.version,\n\t\t\t\t\t\t\t\tschema: metadata.schema,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t'metadata',\n\t\t\t\t\t\t)\n\t\t\t\t\t})\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthrow this.#wrap(error)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\t// Project the post-migration shape into a LOCAL copy first — `#schema`\n\t\t// stays untouched until the upgrade actually commits, so a mid-upgrade\n\t\t// failure never leaves the driver's bookkeeping ahead of the real database.\n\t\ttry {\n\t\t\tawait current.connect()\n\t\t\tconst version = current.version\n\t\t\tcurrent.close()\n\t\t\tthis.#database = undefined\n\t\t\tconst added = new Set([...schema.keys()].filter((name) => !this.#schema.has(name)))\n\t\t\tconst database = createIndexedDBDatabase({\n\t\t\t\tname: this.#name,\n\t\t\t\tversion: version + 1,\n\t\t\t\tstores: this.#stores(schema),\n\t\t\t\tupgrade: this.#upgrade.bind(this, owned, added),\n\t\t\t})\n\t\t\ttry {\n\t\t\t\tawait database.connect()\n\t\t\t} catch (error) {\n\t\t\t\tdatabase.close()\n\t\t\t\tthrow error\n\t\t\t}\n\t\t\t// Only on success: adopt the connection AND commit the local map.\n\t\t\tthis.#database = database\n\t\t\tthis.#schema = schema\n\t\t\tthis.#identities = identities\n\t\t} catch (error) {\n\t\t\tcurrent.close()\n\t\t\tthis.#database = undefined\n\t\t\tconst cause = this.#migrationError(error)\n\t\t\ttry {\n\t\t\t\tawait this.#reopen()\n\t\t\t} catch (recoveryError) {\n\t\t\t\tthis.#database = undefined\n\t\t\t\tthrow new DatabaseError('DRIVER', 'IndexedDB migration and recovery failed', {\n\t\t\t\t\tcause,\n\t\t\t\t\trecovery: this.#recoveryError(recoveryError),\n\t\t\t\t})\n\t\t\t}\n\t\t\tthrow cause\n\t\t}\n\t}\n\n\t// === Private\n\n\t// Run one point mutation inside an explicit readwrite transaction. The\n\t// signal can abort that transaction only while it is active; native\n\t// completion is the commit boundary, so a late abort cannot rewrite a\n\t// completed success. A signal-driven rollback is translated to the core\n\t// ABORTED error after the wrapper has observed transaction settlement.\n\tasync #mutate(\n\t\ttable: string,\n\t\toptions: OperationOptions | undefined,\n\t\tscope: (store: IndexedDBTransactionStoreInterface) => Promise<void>,\n\t): Promise<void> {\n\t\tconst signal = options?.signal\n\t\tcheckAbort(signal)\n\t\tconst cleanup = new AbortController()\n\t\tlet aborted = false\n\t\ttry {\n\t\t\tawait this.#require().write(table, async (transaction) => {\n\t\t\t\tcheckAbort(signal)\n\t\t\t\tsignal?.addEventListener(\n\t\t\t\t\t'abort',\n\t\t\t\t\t() => {\n\t\t\t\t\t\tif (!transaction.active) return\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\ttransaction.abort()\n\t\t\t\t\t\t\taborted = true\n\t\t\t\t\t\t} catch {}\n\t\t\t\t\t},\n\t\t\t\t\t{ once: true, signal: cleanup.signal },\n\t\t\t\t)\n\t\t\t\tcheckAbort(signal)\n\t\t\t\tawait scope(transaction.store(table))\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tif (aborted) checkAbort(signal)\n\t\t\tthrow this.#wrap(error)\n\t\t} finally {\n\t\t\tcleanup.abort()\n\t\t}\n\t}\n\n\t#require(): IndexedDBDatabaseInterface {\n\t\tif (this.#database === undefined) {\n\t\t\tthrow new DatabaseError('CLOSED', `IndexedDB database '${this.#name}' is not open`, {\n\t\t\t\tname: this.#name,\n\t\t\t})\n\t\t}\n\t\treturn this.#database\n\t}\n\n\tasync #load(database: IndexedDBDatabaseInterface): Promise<DriverMetadata | undefined> {\n\t\tlet present = false\n\t\tlet value: unknown\n\t\tawait database.read(METADATA_STORE, async (transaction) => {\n\t\t\tconst store = transaction.store(METADATA_STORE)\n\t\t\tpresent = await store.has('metadata')\n\t\t\tif (present) value = await store.get('metadata')\n\t\t})\n\t\tif (!present) return undefined\n\t\ttry {\n\t\t\treturn cloneDriverMetadata(value)\n\t\t} catch {\n\t\t\tconst cause = new DatabaseError('VALIDATION', 'Stored IndexedDB metadata failed validation', {\n\t\t\t\tpath: 'metadata',\n\t\t\t})\n\t\t\tthrow new DatabaseError('DRIVER', 'Stored IndexedDB metadata is invalid', {\n\t\t\t\tname: this.#name,\n\t\t\t\tstore: METADATA_STORE,\n\t\t\t\tkey: 'metadata',\n\t\t\t\tcause,\n\t\t\t})\n\t\t}\n\t}\n\n\t#migrationError(error: unknown): DatabaseError {\n\t\tif (isDatabaseError(error)) return error\n\t\tif (isIndexedDBError(error)) return mapMigrationError(error)\n\t\treturn new DatabaseError('DRIVER', 'IndexedDB migration failed', { cause: error })\n\t}\n\n\t#recoveryError(error: unknown): DatabaseError {\n\t\tif (isDatabaseError(error)) return error\n\t\tif (isIndexedDBError(error)) return mapIndexedDBError(error)\n\t\treturn new DatabaseError('DRIVER', 'IndexedDB recovery failed', { cause: error })\n\t}\n\n\t// The shared ordinary CRUD/query backend-fault boundary: no `IndexedDBError`\n\t// may leak through those `DriverInterface` operations. A `DatabaseError` this\n\t// driver threw itself (such as the `CLOSED` gate or `NOT_FOUND` table guard)\n\t// passes through unchanged; only a genuine backend `IndexedDBError` is\n\t// remapped. Migration and recovery instead use `#migrationError` and\n\t// `#recoveryError`.\n\t#wrap(error: unknown): unknown {\n\t\treturn isIndexedDBError(error) ? mapIndexedDBError(error) : error\n\t}\n\n\t#store(table: string) {\n\t\treturn this.#require().store(table)\n\t}\n\n\t#alignIdentities(schema: ReadonlyMap<string, TableSchema>): Map<string, object> {\n\t\tconst aligned = new Map<string, object>()\n\t\tfor (const table of schema.values()) {\n\t\t\taligned.set(table.name, this.#identities.get(table.name) ?? {})\n\t\t}\n\t\treturn aligned\n\t}\n\n\t#projectIdentities(\n\t\tidentities: ReadonlyMap<string, object>,\n\t\tsteps: readonly MigrationStep[],\n\t): Map<string, object> {\n\t\tconst projected = new Map(identities)\n\t\tfor (const step of steps) {\n\t\t\tif (step.operation === 'table.add') projected.set(step.table.name, {})\n\t\t\tif (step.operation === 'table.remove') projected.delete(step.table)\n\t\t}\n\t\treturn projected\n\t}\n\n\t// Project a schema map into the wrapper's declared-stores shape — the\n\t// reserved metadata store is always declared alongside every table, out-of-line\n\t// (keys are passed explicitly), with the declared secondary indexes becoming\n\t// each store's `createIndex` definitions. Shared by `open`, `migrate`, and\n\t// `#reopen` so the projection never drifts between them.\n\t#stores(schema: ReadonlyMap<string, TableSchema>): Record<string, StoreDefinition> {\n\t\tconst stores: Record<string, StoreDefinition> = {\n\t\t\t[METADATA_STORE]: {},\n\t\t}\n\t\tfor (const table of schema.values()) {\n\t\t\tstores[table.name] = schemaToStore(table)\n\t\t}\n\t\treturn stores\n\t}\n\n\t// Reconnect at the CURRENT `#schema` with no version bump (auto-managed\n\t// mode, mirroring `open`) — used to restore a working connection after a\n\t// failed `migrate` left the prior connection closed.\n\tasync #reopen(): Promise<void> {\n\t\tconst database = createIndexedDBDatabase({\n\t\t\tname: this.#name,\n\t\t\tstores: this.#stores(this.#schema),\n\t\t})\n\t\ttry {\n\t\t\tawait database.connect()\n\t\t\tthis.#database = database\n\t\t} catch (error) {\n\t\t\tdatabase.close()\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t// The candidate-superset read for a plan. The primary store already returns rows\n\t// in primary-key order — the same order `scan` yields, which `applyQuery`\n\t// preserves for an unordered query. A secondary index returns them in INDEX-key\n\t// order, so re-sort by the primary key to reproduce scan order; the engine then\n\t// filters / orders / pages exactly, so a native read equals the scan path.\n\tasync #candidates(\n\t\tstore: IndexedDBStoreInterface,\n\t\tschema: TableSchema,\n\t\tplan: QueryPlan,\n\t): Promise<readonly Row[]> {\n\t\tif (plan.index === undefined) return store.records(plan.range)\n\t\tconst rows = [...(await store.index(plan.index).records(plan.range))]\n\t\trows.sort((left, right) =>\n\t\t\tcompareValues(extractKey(left, schema.primary), extractKey(right, schema.primary)),\n\t\t)\n\t\treturn rows\n\t}\n\n\t#table(name: string): TableSchema {\n\t\tconst schema = this.#schema.get(name)\n\t\tif (schema === undefined) {\n\t\t\tthrow new DatabaseError('NOT_FOUND', `table '${name}' is not declared`, { table: name })\n\t\t}\n\t\treturn schema\n\t}\n\n\t// Mirror a migration plan's steps onto a LOCAL schema map — the same\n\t// bookkeeping `open` does for a freshly declared schema — without touching\n\t// `#schema`, so a failed migrate never leaves the driver's bookkeeping ahead\n\t// of the real database. The caller commits the map into `#schema` only after\n\t// the upgrade connects successfully.\n\t// Runs INSIDE the wrapper's versionchange transaction (see `migrate`\n\t// @remarks). `table.add` / `column.add` are no-ops here — see `migrate`\n\t// @remarks for why. `column.remove` is the one step touching existing rows:\n\t// it walks a live cursor and rewrites each row through the core\n\t// `migrateRows`, updating in place — the only IDB-await-only work permitted\n\t// inside an upgrade transaction.\n\tasync #upgrade(\n\t\tinput: MigrationInput,\n\t\tadded: ReadonlySet<string>,\n\t\tcontext: IndexedDBUpgradeContext,\n\t): Promise<void> {\n\t\tfor (const name of added) {\n\t\t\tif (name !== METADATA_STORE && context.stores.includes(name)) {\n\t\t\t\tcontext.drop(name)\n\t\t\t}\n\t\t}\n\t\tfor (const step of input.plan.steps) {\n\t\t\tswitch (step.operation) {\n\t\t\t\tcase 'table.add':\n\t\t\t\t\tcontext.create(step.table.name, schemaToStore(step.table))\n\t\t\t\t\tbreak\n\t\t\t\tcase 'table.remove':\n\t\t\t\t\tcontext.drop(step.table)\n\t\t\t\t\tbreak\n\t\t\t\tcase 'index.add': {\n\t\t\t\t\tconst name = deriveIndexedDBIndexName(step.index)\n\t\t\t\t\tconst [column] = step.index\n\t\t\t\t\tconst path = step.index.length === 1 && column !== undefined ? column : [...step.index]\n\t\t\t\t\tcontext.index(step.table, { name, path })\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcase 'index.remove':\n\t\t\t\t\tcontext.deindex(step.table, deriveIndexedDBIndexName(step.index))\n\t\t\t\t\tbreak\n\t\t\t\tcase 'column.remove': {\n\t\t\t\t\tconst store = context.store(step.table)\n\t\t\t\t\tlet cursor = await store.cursor()\n\t\t\t\t\twhile (cursor !== null) {\n\t\t\t\t\t\tconst [migrated] = migrateRows([cursor.value], [step])\n\t\t\t\t\t\tif (migrated === undefined) {\n\t\t\t\t\t\t\tthrow new DatabaseError('MIGRATION', 'migrate: transformed row is missing', {\n\t\t\t\t\t\t\t\ttable: step.table,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait cursor.update(migrated)\n\t\t\t\t\t\tcursor = await cursor.continue()\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcase 'column.add':\n\t\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif (input.metadata !== undefined) {\n\t\t\tawait context.store(METADATA_STORE).set(\n\t\t\t\t{\n\t\t\t\t\tversion: input.metadata.version,\n\t\t\t\t\tschema: input.metadata.schema,\n\t\t\t\t},\n\t\t\t\t'metadata',\n\t\t\t)\n\t\t}\n\t}\n}\n","import type { DriverInterface } from '@src/core'\nimport { IndexedDBDriver } from './drivers/IndexedDBDriver.js'\n\n/**\n * Create a persistent IndexedDB {@link DriverInterface} for the core database layer.\n *\n * @remarks\n * Pass it to `createDatabase` from `@orkestrel/database` to run the typed database\n * layer against IndexedDB instead of memory — the `Database` / `Table` / `Query`\n * API is unchanged; only where the bytes live changes. The\n * driver is built on the published `@orkestrel/indexeddb` wrapper in auto-managed\n * mode, so a table added to the `tables` map is created on the next open with no\n * version bump. This unit omits `transaction` / `aggregate` (see\n * {@link IndexedDBDriver} `@remarks`).\n *\n * @param name - The IndexedDB database name to open or create\n * @returns A {@link DriverInterface} backed by IndexedDB\n *\n * @example\n * ```ts\n * import { createDatabase } from '@orkestrel/database'\n * import { stringShape } from '@orkestrel/contract'\n * import { createIndexedDBDriver } from '@orkestrel/database/browser'\n *\n * const db = createDatabase({\n * \tdriver: createIndexedDBDriver('app'),\n * \ttables: { users: { id: stringShape(), name: stringShape() } },\n * })\n * await db.table('users').set({ id: 'u1', name: 'Ada' }) // persisted to IndexedDB\n * ```\n */\nexport function createIndexedDBDriver(name: string): DriverInterface {\n\treturn new IndexedDBDriver(name)\n}\n"],"mappings":"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"}
|