@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.
@@ -6,25 +6,24 @@ import { EmitterInterface } from '@orkestrel/emitter';
6
6
  import { FieldPath } from '@orkestrel/contract';
7
7
  import { Infer } from '@orkestrel/contract';
8
8
  import { JSONSchema } from '@orkestrel/contract';
9
- import { RandomFunction } from '@orkestrel/contract';
10
9
 
11
10
  /** An aggregate computed over a numeric column. */
12
- export declare type AggregateFunction = 'count' | 'sum' | 'average' | 'minimum' | 'maximum';
11
+ export declare type AggregateOperation = 'count' | 'sum' | 'average' | 'minimum' | 'maximum';
13
12
 
14
13
  /**
15
- * Apply a {@link Criteria} to rows — filter, then sort, then page.
14
+ * Apply a {@link QueryInput} to rows — filter, then sort, then page.
16
15
  *
17
16
  * @remarks
18
17
  * The whole portable read pipeline in one place: conditions filter, `order`
19
18
  * sorts, and `offset` / `limit` window the result. Each step is skipped when its
20
- * part of the criteria is absent. The reference {@link DriverInterface} backends
19
+ * part of the input is absent. The reference {@link DriverInterface} backends
21
20
  * lean on this rather than each re-deriving it.
22
21
  *
23
22
  * @param rows - The rows to process (typically a table's full `scan`)
24
- * @param criteria - The read specification, or `undefined` for all rows as-is
23
+ * @param input - The read specification, or `undefined` for all rows as-is
25
24
  * @returns The filtered, sorted, paged rows
26
25
  */
27
- export declare function applyCriteria(rows: readonly Row[], criteria?: Criteria): readonly Row[];
26
+ export declare function applyQuery(rows: readonly Row[], input?: QueryInput): readonly Row[];
28
27
 
29
28
  /**
30
29
  * Run the FULL driver-conformance battery and collect every violation — the
@@ -50,15 +49,25 @@ export declare function applyCriteria(rows: readonly Row[], criteria?: Criteria)
50
49
  export declare function auditDriver(factory: () => DriverInterface): Promise<readonly ConformanceFinding[]>;
51
50
 
52
51
  /**
53
- * Throw when an {@link ReadOptions.signal | AbortSignal} has fired the shared
54
- * cancellation gate checked at operation boundaries and between streamed rows.
52
+ * Return a fresh row whose primary column is authoritatively bound to its storage key.
53
+ *
54
+ * @param row - The caller row
55
+ * @param primary - The primary column
56
+ * @param key - The authoritative storage key
57
+ * @returns A fresh row with the bound primary
58
+ */
59
+ export declare function bindRowKey(row: Row, primary: string, key: Key): Row;
60
+
61
+ /**
62
+ * Throw when an {@link OperationOptions.signal | AbortSignal} has fired — the shared
63
+ * abort gate checked at operation boundaries and between streamed rows.
55
64
  *
56
65
  * @remarks
57
66
  * A no-op for `undefined` or a live signal, so callers thread `options?.signal`
58
67
  * straight through. When the signal has aborted, throws an `ABORTED`
59
68
  * {@link DatabaseError} carrying the signal's `reason` in its context — callers
60
- * mint signals with whatever tool they like (`AbortSignal.timeout(ms)`,
61
- * `new AbortController()`, `@orkestrel/abort`).
69
+ * mint signals with native APIs such as `AbortSignal.timeout(ms)` or
70
+ * `new AbortController()`.
62
71
  *
63
72
  * @param signal - The signal to check, if any
64
73
  * @returns Nothing — returns normally while the signal is live
@@ -77,59 +86,28 @@ export declare function auditDriver(factory: () => DriverInterface): Promise<rea
77
86
  export declare function checkAbort(signal: AbortSignal | undefined): void;
78
87
 
79
88
  /**
80
- * A pending condition opened by a query's `where` / `and` / `or`.
89
+ * Clone unknown driver metadata into a distinct deeply frozen snapshot.
81
90
  *
82
- * @remarks
83
- * Holds the column, the connector that will join this condition to the ones
84
- * before it, and a recorder the owning query supplies. Each operator builds the
85
- * {@link Condition}, hands it to the recorder, and returns the query — so the
86
- * fluent chain flows straight back into the builder without exposing a mutator.
91
+ * @param value - Unknown metadata
92
+ * @returns Owned driver metadata
87
93
  */
88
- export declare class Clause<T = Record<string, unknown>> implements ClauseInterface<T> {
89
- #private;
90
- constructor(record: (condition: Condition) => QueryInterface<T>, column: FieldPath, connector: Connector);
91
- equals(value: unknown): QueryInterface<T>;
92
- not(value: unknown): QueryInterface<T>;
93
- above(value: unknown): QueryInterface<T>;
94
- below(value: unknown): QueryInterface<T>;
95
- from(value: unknown): QueryInterface<T>;
96
- to(value: unknown): QueryInterface<T>;
97
- between(lower: unknown, upper: unknown): QueryInterface<T>;
98
- like(pattern: string): QueryInterface<T>;
99
- glob(pattern: string): QueryInterface<T>;
100
- starts(prefix: string): QueryInterface<T>;
101
- ends(suffix: string): QueryInterface<T>;
102
- any(values: readonly unknown[]): QueryInterface<T>;
103
- none(values: readonly unknown[]): QueryInterface<T>;
104
- absent(): QueryInterface<T>;
105
- present(): QueryInterface<T>;
106
- }
94
+ export declare function cloneDriverMetadata(value: unknown): DriverMetadata;
107
95
 
108
96
  /**
109
- * A pending condition opened by `where` / `and` / `or`.
97
+ * Clone unknown driver schema into a distinct deeply frozen snapshot.
110
98
  *
111
- * @remarks
112
- * Each operator records its condition against the query and returns the query,
113
- * so the chain continues fluently. `absent` / `present` take no operand;
114
- * `between` takes two; `any` / `none` take a list.
115
- */
116
- export declare interface ClauseInterface<T = Row> {
117
- equals(value: unknown): QueryInterface<T>;
118
- not(value: unknown): QueryInterface<T>;
119
- above(value: unknown): QueryInterface<T>;
120
- below(value: unknown): QueryInterface<T>;
121
- from(value: unknown): QueryInterface<T>;
122
- to(value: unknown): QueryInterface<T>;
123
- between(lower: unknown, upper: unknown): QueryInterface<T>;
124
- like(pattern: string): QueryInterface<T>;
125
- glob(pattern: string): QueryInterface<T>;
126
- starts(prefix: string): QueryInterface<T>;
127
- ends(suffix: string): QueryInterface<T>;
128
- any(values: readonly unknown[]): QueryInterface<T>;
129
- none(values: readonly unknown[]): QueryInterface<T>;
130
- absent(): QueryInterface<T>;
131
- present(): QueryInterface<T>;
132
- }
99
+ * @param value - Unknown table schema collection
100
+ * @returns Owned driver schema
101
+ */
102
+ export declare function cloneDriverSchema(value: unknown): readonly TableSchema[];
103
+
104
+ /**
105
+ * Clone unknown migration input into a distinct deeply frozen snapshot.
106
+ *
107
+ * @param value - Unknown migration input
108
+ * @returns Owned migration input
109
+ */
110
+ export declare function cloneMigrationInput(value: unknown): MigrationInput;
133
111
 
134
112
  /**
135
113
  * One table's columns — a map of column name to its value {@link ContractShape}.
@@ -141,25 +119,27 @@ export declare interface ClauseInterface<T = Row> {
141
119
  * at the table level. (Nested object *columns* still use `objectShape`, since a
142
120
  * column is not always an object.)
143
121
  */
144
- export declare type Columns = Readonly<Record<string, ContractShape>>;
122
+ export declare type ColumnMap = Readonly<Record<string, ContractShape>>;
145
123
 
146
124
  /**
147
- * One column of a {@link TableSchema} — its name, portable {@link ColumnType}, and
148
- * whether it is nullable (its shape is `optionalShape` / `nullableShape`).
125
+ * One column of a {@link TableSchema} — its name, portable {@link ColumnStorage}, and
126
+ * whether it independently accepts absence (`optional`) and explicit `null`
127
+ * (`nullable`).
149
128
  */
150
129
  export declare interface ColumnSchema {
151
130
  readonly name: string;
152
- readonly type: ColumnType;
131
+ readonly storage: ColumnStorage;
132
+ readonly optional: boolean;
153
133
  readonly nullable: boolean;
154
134
  }
155
135
 
156
136
  /**
157
137
  * A portable storage type for a column — the backend maps it to its native type
158
138
  * (SQLite affinity, an IndexedDB value). Derived from a column's `ContractShape`
159
- * by `shapeToColumnType`; `json` covers object/array/union/raw values a backend stores
139
+ * by `shapeToColumnStorage`; `json` covers object/array/union/raw values a backend stores
160
140
  * as JSON text and can `json_extract` for nested-field queries.
161
141
  */
162
- export declare type ColumnType = 'text' | 'integer' | 'real' | 'boolean' | 'json' | 'blob';
142
+ export declare type ColumnStorage = 'text' | 'integer' | 'real' | 'boolean' | 'json' | 'blob';
163
143
 
164
144
  /**
165
145
  * A total ordering over arbitrary values — the comparator behind sorting and the
@@ -191,7 +171,7 @@ export declare function compareValues(left: unknown, right: unknown): number;
191
171
  * @param column - The column to aggregate
192
172
  * @returns The aggregate value, or `undefined` when undefined for the inputs
193
173
  */
194
- export declare function computeAggregate(rows: readonly unknown[], operation: AggregateFunction, column: FieldPath): number | undefined;
174
+ export declare function computeAggregate(rows: readonly unknown[], operation: AggregateOperation, column: FieldPath): number | undefined;
195
175
 
196
176
  /**
197
177
  * One compiled WHERE condition.
@@ -208,9 +188,12 @@ export declare interface Condition {
208
188
  readonly column: FieldPath;
209
189
  readonly operator: ConditionOperator;
210
190
  readonly values: readonly unknown[];
211
- readonly connector: Connector;
191
+ readonly connector: ConditionConnector;
212
192
  }
213
193
 
194
+ /** How a {@link Condition} joins to the running result of the conditions before it. */
195
+ export declare type ConditionConnector = 'and' | 'or';
196
+
214
197
  /**
215
198
  * A WHERE operator — the comparison a single {@link Condition} applies.
216
199
  *
@@ -261,9 +244,6 @@ export declare interface ConformanceFinding {
261
244
  */
262
245
  export declare function conformDriver(factory: () => DriverInterface): Promise<void>;
263
246
 
264
- /** How a {@link Condition} joins to the running result of the conditions before it. */
265
- export declare type Connector = 'and' | 'or';
266
-
267
247
  /**
268
248
  * Create a database over a driver and a declared `tables` schema.
269
249
  *
@@ -273,10 +253,10 @@ export declare type Connector = 'and' | 'or';
273
253
  * level. The `const` type parameter captures the literal names and columns, so
274
254
  * `db.table('users')` is checked against the schema and typed by `Infer` of its
275
255
  * columns — no annotations. Name a non-`id` primary-key column per table via the
276
- * optional `keys` map.
256
+ * optional `primary` and `indexes` maps.
277
257
  *
278
- * @param options - The driver, the `tables` column map, optional `keys`, and an
279
- * optional `name`
258
+ * @param options - The driver, `tables`, and optional `primary`, `indexes`,
259
+ * `name`, `generator`, `version`, and emitter hooks
280
260
  * @returns A typed {@link DatabaseInterface}
281
261
  *
282
262
  * @example
@@ -290,12 +270,12 @@ export declare type Connector = 'and' | 'or';
290
270
  * users: { id: stringShape(), age: integerShape() },
291
271
  * posts: { slug: stringShape(), title: stringShape() },
292
272
  * },
293
- * keys: { posts: 'slug' },
273
+ * primary: { posts: 'slug' },
294
274
  * })
295
275
  * await db.table('users').set({ id: 'u1', age: 36 }) // typed; coerced + validated
296
276
  * ```
297
277
  */
298
- export declare function createDatabase<const T extends TablesShape>(options: DatabaseOptions<T>): DatabaseInterface<T>;
278
+ export declare function createDatabase<const T extends TableMap>(options: DatabaseOptions<T>): DatabaseInterface<T>;
299
279
 
300
280
  /**
301
281
  * Create the in-memory reference {@link DriverInterface}.
@@ -308,49 +288,17 @@ export declare function createDatabase<const T extends TablesShape>(options: Dat
308
288
  */
309
289
  export declare function createMemoryDriver(): DriverInterface;
310
290
 
311
- /**
312
- * A serializable read specification — everything a backend needs to compile one
313
- * read, free of JS callbacks so any backend can honor it.
314
- *
315
- * @remarks
316
- * The post-fetch `filter` predicate lives on {@link QueryInterface}, never here,
317
- * so `Criteria` stays portable across backends.
318
- */
319
- export declare interface Criteria {
320
- readonly conditions?: readonly Condition[];
321
- readonly order?: readonly Order[];
322
- readonly limit?: number;
323
- readonly offset?: number;
324
- }
325
-
326
- /**
327
- * A forward row cursor for bulk in-place mutation.
328
- *
329
- * @remarks
330
- * Iterates a snapshot of the table's keys captured when the cursor was opened,
331
- * reading each row lazily through the owning table — so a mutation made during
332
- * iteration cannot corrupt the walk, and a key removed mid-iteration is simply
333
- * skipped. `update` and `remove` act on the row at the current position.
334
- */
335
- export declare class Cursor<T = Record<string, unknown>> implements CursorInterface<T> {
336
- #private;
337
- constructor(table: TableInterface<T>, keys: readonly Key[]);
338
- get value(): T | undefined;
339
- get index(): number;
340
- get done(): boolean;
341
- next(): Promise<void>;
342
- update(changes: Partial<T>): Promise<void>;
343
- remove(): Promise<void>;
344
- close(): void;
345
- }
346
-
347
291
  /**
348
292
  * A forward row cursor for bulk in-place mutation.
349
293
  *
350
294
  * @remarks
351
295
  * Iterates a snapshot of the table's keys taken at creation; `update` and
352
296
  * `remove` act on the row at the current position through the owning table.
353
- * `done` is `true` once iteration has advanced past the last key.
297
+ * Promise operations execute serially in invocation order, and one rejection
298
+ * does not prevent later admitted work. `done` is `true` once iteration has
299
+ * advanced past the last key. `close` is synchronous and terminal: it clears
300
+ * the current value, queued work becomes a no-op, and in-flight work never
301
+ * republishes a value after settling.
354
302
  */
355
303
  export declare interface CursorInterface<T = Row> {
356
304
  readonly value: T | undefined;
@@ -363,82 +311,27 @@ export declare interface CursorInterface<T = Row> {
363
311
  }
364
312
 
365
313
  /**
366
- * A database the ergonomic entry point over a {@link DriverInterface}.
367
- *
368
- * @remarks
369
- * Owns the driver and a `tables` shape map, connecting the driver lazily on first
370
- * use so a freshly created database is immediately usable. `table(name)` returns
371
- * a table typed by that table's shape `Infer`. `import` registers more tables and
372
- * returns a database re-typed with them over the **same** driver and storage;
373
- * `export` emits a portable {@link TableExport} per table. `transaction` snapshots
374
- * the driver, runs the scope, and rolls every table back if it throws — an
375
- * optimistic model that works uniformly across backends rather than reconciling
376
- * SQL's and IndexedDB's incompatible native transactions.
314
+ * A typed database view over one shared internal lifecycle and storage context.
377
315
  *
378
316
  * @remarks
379
- * - **Versioned (optional).** When {@link DatabaseOptions.version} is set and the driver
380
- * implements both {@link DriverInterface.meta} and {@link DriverInterface.stamp},
381
- * `open()` reconciles the driver's persisted {@link DriverMeta} against the declared
382
- * version INSIDE the same lazy-connect chain, AFTER the `open` event fires — see
383
- * {@link DatabaseOptions.version} for the full reconciliation contract.
384
- * - **Observable (§13).** The owned {@link emitter} ({@link DatabaseEventMap}) carries the
385
- * connection + transaction lifecycle — `open` / `close` / `transaction` / `commit` /
386
- * `rollback` — for fire-and-forget observers, ALONGSIDE each table's per-row events. Every
387
- * event is emitted directly, strictly AFTER the relevant transition: `commit` only after
388
- * the scope succeeds, `rollback` only after every table is restored. The `rollback` emit
389
- * OBSERVES the propagated error — it never swallows it (the original throw propagates
390
- * exactly as before). The emitter isolates a listener throw and routes it to its `error`
391
- * handler (the `error` option), so observation can never reorder, throw into, or corrupt
392
- * the snapshot / commit / rollback flow.
393
- */
394
- export declare class Database<T extends TablesShape = TablesShape> implements DatabaseInterface<T> {
317
+ * Each view owns only its table contracts, primary columns, indexes, and key
318
+ * generator. Imported views register their physical schemas with the same
319
+ * internal context before opening begins, so every view observes one driver,
320
+ * merged schema, emitter, status, transaction boundary, and terminal close.
321
+ */
322
+ export declare class Database<T extends TableMap = TableMap> implements DatabaseInterface<T> {
395
323
  #private;
396
324
  constructor(options: DatabaseOptions<T>);
397
325
  get emitter(): EmitterInterface<DatabaseEventMap>;
398
326
  get name(): string;
399
327
  get status(): DatabaseStatus;
400
328
  table<K extends keyof T & string>(name: K): TableInterface<RowOf<T[K]>>;
401
- import<U extends TablesShape>(tables: U, keys?: TableKeys): DatabaseInterface<U>;
402
- export(): Readonly<Record<string, TableExport>>;
329
+ import<U extends TableMap>(tables: U, primary?: PrimaryMap): DatabaseInterface<U>;
330
+ export(): Readonly<Record<string, TableDefinition>>;
403
331
  open(): Promise<void>;
404
332
  close(): Promise<void>;
405
- /**
406
- * Run `scope` transactionally: commit its writes on success, roll every table
407
- * back if it throws.
408
- *
409
- * @remarks
410
- * When the driver implements the optional native {@link DriverInterface.transaction}
411
- * hook, that native `commit` / `rollback` handle drives the transaction; otherwise
412
- * the universal snapshot floor (`driver.snapshot()`) runs unchanged. Either path
413
- * emits the same `transaction` / `commit` / `rollback` lifecycle (AGENTS §13).
414
- * `options.signal` is checked ONCE at entry, before connecting or starting any
415
- * transactional work — an already-aborted signal throws `ABORTED` and neither the
416
- * native hook nor the snapshot floor is invoked. Nesting is unguarded and
417
- * unsupported exactly as before: this is a single-writer model, not reentrant.
418
- * On the native path, a `scope` throw rolls back via the native handle; a
419
- * native `commit` failure propagates as-is with no rollback attempt — the
420
- * engine owns transaction state after a failed COMMIT.
421
- *
422
- * @param scope - The transactional work to run
423
- * @param options - `{ signal }` to abort before the transaction starts
424
- * @returns The scope's resolved value
425
- * @throws An `ABORTED` {@link DatabaseError} when `options.signal` has already fired
426
- */
427
- transaction<R>(scope: () => Promise<R>, options?: ReadOptions): Promise<R>;
428
- /**
429
- * Diff `deployed` against this database's declared schema and apply the
430
- * resulting plan through the driver's optional `migrate` hook.
431
- *
432
- * @param deployed - The schema currently deployed, as {@link TableSchema}s
433
- * @param options - `{ signal }` to abort before the migration starts
434
- * @returns The applied {@link Migration} plan
435
- * @throws A `MIGRATION` {@link DatabaseError} when the driver does not
436
- * implement `migrate`, or when a step references an unknown table
437
- * (propagated from the driver)
438
- * @throws An `ABORTED` {@link DatabaseError} when `options.signal` has
439
- * already fired at entry
440
- */
441
- migrate(deployed: readonly TableSchema[], options?: ReadOptions): Promise<Migration>;
333
+ transaction<R>(scope: (transaction: DatabaseStorageInterface<T>) => Promise<R>, options?: OperationOptions): Promise<R>;
334
+ migrate(deployed: readonly TableSchema[], options?: OperationOptions): Promise<Migration>;
442
335
  }
443
336
 
444
337
  /**
@@ -448,8 +341,8 @@ export declare class Database<T extends TablesShape = TablesShape> implements Da
448
341
  * Carries a {@link DatabaseErrorCode} and an optional `context` bag naming the
449
342
  * offending table / key. Thrown for: operating on a closed database (`CLOSED`), a
450
343
  * `resolve` miss (`NOT_FOUND`), an `add` onto an existing key (`CONFLICT`), a
451
- * row that fails its table's contract (`VALIDATION`), a cancelled operation whose
452
- * {@link ReadOptions.signal} aborted (`ABORTED`, carrying `signal.reason` in
344
+ * row that fails its table's contract (`VALIDATION`), an aborted operation whose
345
+ * {@link OperationOptions.signal} aborted (`ABORTED`, carrying `signal.reason` in
453
346
  * `context`), an inapplicable {@link Migration} plan (`MIGRATION`), a
454
347
  * driver that violates a {@link DriverInterface} invariant, thrown by the
455
348
  * `conformDriver` helper (`CONFORMANCE`), and an unexpected infrastructure
@@ -478,9 +371,10 @@ export declare type DatabaseErrorCode = 'CLOSED' | 'NOT_FOUND' | 'CONFLICT' | 'V
478
371
  * listener throw is routed to the emitter's OWN `error` handler (the `error` option), never
479
372
  * onto this domain map and never into the snapshot / commit / rollback flow — so a buggy
480
373
  * observer can never reorder, throw into, or corrupt a transaction. Every emit sits AFTER the
481
- * relevant transition: `commit` only after the scope succeeds, `rollback` only after every
482
- * table has been restored (it OBSERVES the propagated error; the original throw still
483
- * propagates exactly as before). Subscribe via `database.emitter.on(...)`.
374
+ * relevant transition: `commit` only after the scope succeeds, `rollback` only after the
375
+ * rollback operation completes (it OBSERVES the propagated scope error; that exact reason
376
+ * still propagates). A rollback failure propagates instead and emits no misleading
377
+ * `rollback` event. Subscribe via `database.emitter.on(...)`.
484
378
  *
485
379
  * Declared as a `type` alias (not `interface extends EventMap`, §4.5 — `EventMap` is a
486
380
  * `type` kind): a type-literal satisfies the `EventMap` constraint
@@ -492,11 +386,11 @@ export declare type DatabaseEventMap = {
492
386
  readonly open: readonly [];
493
387
  /** The database was closed (the driver released). */
494
388
  readonly close: readonly [];
495
- /** A transaction scope began the store was snapshotted, the scope is about to run. */
389
+ /** A transaction scope began after its native boundary or fallback snapshot was acquired. */
496
390
  readonly transaction: readonly [];
497
391
  /** A transaction scope completed successfully (no rollback). */
498
392
  readonly commit: readonly [];
499
- /** A transaction scope threw and every table was rolled back — the propagated error. */
393
+ /** A transaction scope failed and rollback completed — the exact propagated scope error. */
500
394
  readonly rollback: readonly [error: unknown];
501
395
  /** A {@link Migration} plan was applied via `migrate` — the applied plan. */
502
396
  readonly migrate: readonly [migration: Migration];
@@ -512,22 +406,21 @@ export declare type DatabaseEventMap = {
512
406
  * created database is immediately usable. `import` defines more than one table
513
407
  * from a shape map and returns a new typed view of **those** tables over the
514
408
  * same driver and storage (so views can be split by concern and still share
515
- * data); `export` produces a portable {@link TableExport} per table for moving a
516
- * schema between databases or environments. `transaction` snapshots the store,
517
- * runs the scope, and rolls every table back if it throws — an optimistic model
518
- * that works uniformly across backends rather than reconciling SQL's and
519
- * IndexedDB's incompatible native transactions.
409
+ * data); `export` produces a portable {@link TableDefinition} per table for moving a
410
+ * schema between databases or environments. `transaction` runs a table-only
411
+ * scoped callback through a native driver transaction when available, otherwise
412
+ * through the universal whole-store snapshot floor.
520
413
  */
521
- export declare interface DatabaseInterface<T extends TablesShape = TablesShape> {
414
+ export declare interface DatabaseInterface<T extends TableMap = TableMap> {
522
415
  readonly emitter: EmitterInterface<DatabaseEventMap>;
523
416
  readonly name: string;
524
417
  readonly status: DatabaseStatus;
525
418
  table<K extends keyof T & string>(name: K): TableInterface<RowOf<T[K]>>;
526
- import<U extends TablesShape>(tables: U, keys?: TableKeys): DatabaseInterface<U>;
527
- export(): Readonly<Record<string, TableExport>>;
419
+ import<U extends TableMap>(tables: U, primary?: PrimaryMap): DatabaseInterface<U>;
420
+ export(): Readonly<Record<string, TableDefinition>>;
528
421
  open(): Promise<void>;
529
422
  close(): Promise<void>;
530
- transaction<R>(scope: () => Promise<R>, options?: ReadOptions): Promise<R>;
423
+ transaction<R>(scope: (transaction: DatabaseStorageInterface<T>) => Promise<R>, options?: OperationOptions): Promise<R>;
531
424
  /**
532
425
  * Diff a caller-supplied deployed schema against this database's declared
533
426
  * schema (its `tables`, as configured) via `planMigration`, apply the
@@ -542,11 +435,20 @@ export declare interface DatabaseInterface<T extends TablesShape = TablesShape>
542
435
  * Throws `DatabaseError` `MIGRATION` when the driver does not implement
543
436
  * `migrate`, or when a step references an unknown table (propagated from
544
437
  * the driver). Throws `ABORTED` when `options.signal` has already fired at
545
- * entry. Emits the `migrate` event after a successful apply. Version
546
- * TRACKING (persisting `from` / `to`) remains deferred to persistent
547
- * backends the caller owns knowing what was deployed.
438
+ * entry. For explicit `migrate`, driver open and migration apply form one
439
+ * readiness transition: status and the `open` event publish only after apply
440
+ * succeeds. A failed explicit apply blocks ordinary open/table work with that
441
+ * exact failure until a later explicit `migrate` succeeds; `close` remains
442
+ * available. Automatic versioned open has a separate lifecycle: successful
443
+ * physical driver open publishes `open` status and one `open` event before
444
+ * reconciliation. If reconciliation fails, its readiness Promise and table
445
+ * work reject until a later automatic retry succeeds on the same physical
446
+ * handle. Emits the `migrate` event after a successful apply. Explicit
447
+ * migration still accepts the caller's deployed schema; versioned open uses
448
+ * {@link DatabaseOptions.version} with a driver's paired `metadata` / `stamp`
449
+ * capabilities to persist and reconcile deployed version metadata.
548
450
  */
549
- migrate(deployed: readonly TableSchema[], options?: ReadOptions): Promise<Migration>;
451
+ migrate(deployed: readonly TableSchema[], options?: OperationOptions): Promise<Migration>;
550
452
  }
551
453
 
552
454
  /**
@@ -554,37 +456,55 @@ export declare interface DatabaseInterface<T extends TablesShape = TablesShape>
554
456
  *
555
457
  * @remarks
556
458
  * `driver` is the storage backend; `tables` declares each table's columns;
557
- * `keys` overrides the primary-key column per table ({@link DEFAULT_PRIMARY}
459
+ * `primary` overrides the primary-key column per table ({@link DEFAULT_PRIMARY}
558
460
  * otherwise); `indexes` declares secondary indexes per table (contracts don't
559
461
  * express them) that flow into each derived {@link TableSchema}; `name` labels
560
462
  * the database; `on` wires initial {@link DatabaseEventMap} listeners (§8); `error`
561
463
  * is the emitter's listener-error handler (§13 — a listener throw routes here);
562
- * `key` is the key factory a table uses when a written row lacks its primary
563
- * key without one, writing a keyless row is a `VALIDATION` error (the core
564
- * mints no keys itself).
464
+ * `generator` is the authoritative key-generation override a table uses when a
465
+ * written row's primary is exactly `undefined`. When omitted, the table uses
466
+ * global `crypto.randomUUID()`; numeric primary keys require a custom generator.
565
467
  */
566
- export declare interface DatabaseOptions<T extends TablesShape = TablesShape> {
468
+ export declare interface DatabaseOptions<T extends TableMap = TableMap> {
567
469
  readonly on?: EmitterHooks<DatabaseEventMap>;
568
- /** The emitter's listener-error handler (AGENTS §13) — a listener throw routes here, not to a domain event. */
470
+ /**
471
+ * The listener-error handler shared by the database and every table emitter.
472
+ *
473
+ * @remarks
474
+ * Listener throws from root, imported, and transaction-scoped handles route
475
+ * here as `(error, event)`, never to a domain event and never into the
476
+ * completed operation.
477
+ */
569
478
  readonly error?: EmitterErrorHandler;
570
479
  readonly driver: DriverInterface;
571
480
  readonly tables: T;
572
- readonly keys?: TableKeys;
573
- readonly indexes?: TableIndexes;
481
+ readonly primary?: PrimaryMap;
482
+ readonly indexes?: IndexMap;
574
483
  readonly name?: string;
575
- readonly key?: KeyFunction;
484
+ /**
485
+ * The authoritative key-generation override for a keyless write.
486
+ *
487
+ * @remarks
488
+ * Omit it to use global `crypto.randomUUID()`. A numeric primary requires a
489
+ * custom generator. Explicit primary values never invoke this function. A
490
+ * custom generator throw is `VALIDATION`; a host
491
+ * `crypto.randomUUID()` failure is `DRIVER`. An invalid returned key is
492
+ * `VALIDATION`; neither branch falls back or retries.
493
+ */
494
+ readonly generator?: KeyFunction;
576
495
  /**
577
496
  * The declared schema version.
578
497
  *
579
498
  * @remarks
580
- * Only meaningful when the driver implements BOTH {@link DriverInterface.meta}
499
+ * Only meaningful when the driver implements BOTH {@link DriverInterface.metadata}
581
500
  * and {@link DriverInterface.stamp} (a versioning driver); unset, or a
582
501
  * non-versioning driver, leaves `open()` unchanged from today's behavior.
583
502
  * When set and the driver versions, `open()` reconciles against the
584
- * driver's persisted {@link DriverMeta}:
585
- * - **Fresh store** (`meta()` returns `undefined`) no migration is
586
- * possible (there is nothing deployed to diff against), so `open()`
587
- * simply `stamp`s `{ version, schema }` for next time.
503
+ * driver's persisted {@link DriverMetadata}:
504
+ * - **Fresh store** (`metadata()` returns `undefined` after the durable
505
+ * driver proves absence) no migration is possible (there is nothing
506
+ * deployed to diff against), so `open()` simply `stamp`s
507
+ * `{ version, schema }` for next time.
588
508
  * - **Stored version < `version`** — `planMigration(stored.schema, declared
589
509
  * schema)` computes the upgrade plan, applied via the driver's optional
590
510
  * `migrate` hook. If `migrate` is absent and the plan is non-empty,
@@ -592,13 +512,14 @@ export declare interface DatabaseOptions<T extends TablesShape = TablesShape> {
592
512
  * `stamp`s the new `{ version, schema }` and emits the `migrate` event.
593
513
  * - **Stored version > `version`** — the store is newer than the declared
594
514
  * schema; `open()` throws `DatabaseError` `MIGRATION`.
595
- * - **Stored version === `version`** — no-op.
515
+ * - **Stored version === `version`** — the persisted and declared schemas
516
+ * must still match. Schema drift throws `DatabaseError` `MIGRATION`;
517
+ * otherwise `open()` is a no-op.
596
518
  *
597
- * When the driver ALSO implements {@link DriverInterface.transaction}, the
598
- * `migrate` + `stamp` pair applies atomically through that native handle
599
- * (all-or-nothing, rolled back cleanly on a mid-plan failure); otherwise the
600
- * pair applies sequentially, with a small documented window in which a `stamp`
601
- * failure after a successful `migrate` can leave new data under old meta.
519
+ * A non-empty version upgrade passes both the plan and target metadata to
520
+ * {@link DriverInterface.migrate} as one {@link MigrationInput}; the driver
521
+ * settles both atomically. A zero-step version transition may use `stamp`
522
+ * directly because no schema or rows change.
602
523
  */
603
524
  readonly version?: number;
604
525
  }
@@ -607,45 +528,28 @@ export declare interface DatabaseOptions<T extends TablesShape = TablesShape> {
607
528
  export declare type DatabaseStatus = 'idle' | 'open' | 'closed';
608
529
 
609
530
  /**
610
- * Structural equality by SameValueZero leaves the comparator behind conformance
611
- * checks and any test/fixture that needs "same data", not "same reference".
531
+ * A database view valid only inside one {@link DatabaseInterface.transaction}
532
+ * scope.
612
533
  *
613
534
  * @remarks
614
- * Primitives compare by SameValueZero (`NaN` equals itself; `+0` equals `-0`).
615
- * Arrays compare by index (same length, every element `deepEqual`). Plain
616
- * records (via `isRecord`) compare by their OWN enumerable keys: same key
617
- * COUNT and, for every key in `left`, `right` has that key (`Object.hasOwn`)
618
- * with a `deepEqual` value — so a key present with value `undefined` is NOT
619
- * equal to that key being absent (both differ in `Object.keys` membership).
620
- * Anything else (functions, class instances, mismatched shapes) falls through
621
- * to `false`. There is no cycle detection — a cyclic input recurses forever;
622
- * callers pass acyclic data (rows, plans, config).
623
- *
624
- * @param left - The left value
625
- * @param right - The right value
626
- * @returns Whether `left` and `right` are structurally equal
627
- *
628
- * @example
629
- * ```ts
630
- * deepEqual(Number.NaN, Number.NaN) // true
631
- * deepEqual({ a: [1, { b: 2 }] }, { a: [1, { b: 2 }] }) // true
632
- * deepEqual({ a: undefined }, {}) // false — present-undefined ≠ absent
633
- * ```
535
+ * The view exposes only declared tables backed by the driver's scoped
536
+ * {@link StorageInterface}. It cannot open, close, import, migrate, or nest
537
+ * a transaction. The view and every table captured from it throw `CONFLICT`
538
+ * after the scope settles.
634
539
  */
635
- export declare function deepEqual(left: unknown, right: unknown): boolean;
540
+ export declare interface DatabaseStorageInterface<T extends TableMap = TableMap> {
541
+ table<K extends keyof T & string>(name: K): TableInterface<RowOf<T[K]>>;
542
+ }
636
543
 
637
544
  /**
638
- * The primary-key column assumed when {@link TableKeys} does not name one.
545
+ * The primary-key column assumed when {@link PrimaryMap} does not name one.
639
546
  *
640
547
  * @remarks
641
548
  * `id` is the convention IndexedDB (`keyPath: 'id'`) and SQL (`id` / rowid) both
642
- * lean on, so a table that omits `key` keys its rows by `id`.
549
+ * lean on, so a table without a `primary` override keys its rows by `id`.
643
550
  */
644
551
  export declare const DEFAULT_PRIMARY = "id";
645
552
 
646
- /** A sort direction. */
647
- export declare type Direction = 'ascending' | 'descending';
648
-
649
553
  /**
650
554
  * Run the driver-conformance battery against a fresh {@link DriverInterface}
651
555
  * per phase, yielding one {@link ConformanceFinding} per violated invariant —
@@ -661,21 +565,24 @@ export declare type Direction = 'ascending' | 'descending';
661
565
  * `read` of a missing key returns `undefined`; `write`/`read` round-trip with
662
566
  * DEEP copy-in/copy-out isolation (mutating the caller's row — including a
663
567
  * NESTED field — after `write`, or a row `read` returns, never perturbs
664
- * stored state) and upsert-overwrite; `delete` returns `true` then `false`;
568
+ * stored state) and upsert-overwrite; simultaneous same-key `insert` calls
569
+ * produce exactly one commit and one `CONFLICT`; pre-aborted `write`,
570
+ * `insert`, and `delete` calls leave storage unchanged; `delete` returns
571
+ * `true` then `false`;
665
572
  * `keys`/`scan` yield in ascending key order; `clear` empties only its target
666
573
  * table; `snapshot`'s rollback thunk restores pre-snapshot state, including a
667
574
  * NESTED field mutated in place on a read-back row between capture and
668
575
  * restore; a scoped `snapshot(['users'])` rolls back only the named table,
669
576
  * leaving a concurrent mutation to another table intact; a
670
577
  * non-`id` primary key (`posts.slug`) round-trips; a nested-object row
671
- * round-trips structurally (via {@link deepEqual}). The optional surface is
578
+ * round-trips structurally (via {@link equalsValue}). The optional surface is
672
579
  * presence-gated: when `migrate` exists, a `column.remove` plan strips the
673
580
  * column from stored rows and a plan referencing an unknown table throws
674
581
  * `DatabaseError` `MIGRATION`; when `stream` exists, it yields only
675
582
  * condition-matching rows and honors `offset`/`limit`; when `transaction`
676
- * exists, `commit` persists and `rollback` restores; when both `meta` and
677
- * `stamp` exist, a fresh store's `meta()` is `undefined`, and after
678
- * `stamp({ version, schema })`, `meta()` returns the exact stamped value.
583
+ * exists, `commit` persists and `rollback` restores; when both `metadata` and
584
+ * `stamp` exist, a fresh store's `metadata()` is `undefined`, and after
585
+ * `stamp({ version, schema })`, `metadata()` returns the exact stamped value.
679
586
  *
680
587
  * Each phase runs within a `try`/`catch`: an EXPECTED mismatch yields a
681
588
  * finding built from the assertion, while an UNEXPECTED throw (a driver
@@ -706,107 +613,92 @@ export declare function driverFindings(factory: () => DriverInterface): AsyncIte
706
613
  * The storage primitive every backend implements — the whole of the bridge.
707
614
  *
708
615
  * @remarks
709
- * The REQUIRED surface is deliberately minimal: keyed read / write / delete, an
710
- * ordered `scan`, a key listing, and a `snapshot` that backs transactions — the
711
- * irreducible primitive. There is **no** required query, count, or aggregate
616
+ * The REQUIRED surface is deliberately minimal: keyed read / write / atomic
617
+ * insert / delete, an ordered `scan`, a key listing, and a `snapshot` that backs
618
+ * transactions — the irreducible primitive. There is **no** required query,
619
+ * count, or aggregate
712
620
  * here: all of that is one query engine in the core (`helpers.ts`) running over
713
621
  * `scan`, so a new backend implements a handful of tiny methods rather than
714
622
  * re-deriving WHERE compilation. `open` now receives a derived
715
623
  * {@link TableSchema}`[]` (columns, types, primary, indexes) so a native backend
716
624
  * can build real tables and indexes; a scan-only backend reads only `name`. The
717
- * optional `records?` / `count?` / `aggregate?` are native overrides the engine
625
+ * optional `records?` / `aggregate?` are native overrides the engine
718
626
  * falls back from (AGENTS §21). The API is async (Promises) because IndexedDB is; synchronous
719
627
  * backends resolve immediately. Lookups that may miss return `undefined` /
720
- * `false` rather than throwing (AGENTS §12).
628
+ * `false` rather than throwing (AGENTS §12). Metadata has the same ownership
629
+ * boundary across every implementation: `stamp` and `migrate` snapshot
630
+ * {@link DriverMetadata} at entry, while `metadata` returns a distinct deeply frozen
631
+ * snapshot. A durable driver returns `undefined` only when it proves the
632
+ * metadata record or durable store is absent. Existing unreadable or malformed
633
+ * durable state fails `open` / `metadata` closed; it is never treated as fresh,
634
+ * rewritten, or repaired automatically.
721
635
  */
722
- export declare interface DriverInterface {
636
+ export declare interface DriverInterface extends StorageInterface {
723
637
  open(schema: readonly TableSchema[]): Promise<void>;
724
638
  close(): Promise<void>;
725
- read(table: string, key: Key): Promise<Row | undefined>;
726
- write(table: string, key: Key, row: Row): Promise<void>;
727
- delete(table: string, key: Key): Promise<boolean>;
728
- keys(table: string): Promise<readonly Key[]>;
729
- scan(table: string): AsyncIterable<Row>;
730
- clear(table: string): Promise<void>;
731
639
  /**
732
- * Capture the current state and return a thunk that rolls every table back to
733
- * it — the primitive transactions are built on (SQL `SAVEPOINT`, an IndexedDB
734
- * key buffer, a cloned map).
640
+ * Capture table rows and return a repeatable thunk that restores those rows
641
+ * the primitive transactions are built on.
735
642
  *
736
643
  * @remarks
737
- * `tables` omitted captures/rolls back the WHOLE store (existing behavior).
738
- * `tables` provided captures/restores ONLY the named tables the returned
739
- * rollback thunk leaves every other table untouched.
644
+ * Snapshots are row-only: they never restore schema or driver metadata.
645
+ * `tables` omitted captures every current table. `tables` provided captures
646
+ * only existing named tables. Rollback skips captured tables removed since
647
+ * capture and leaves every uncaptured or later-added table untouched.
740
648
  */
741
649
  snapshot(tables?: readonly string[]): Promise<() => Promise<void>>;
742
650
  /**
743
- * Optional native filtered read (AGENTS §21). A backend that can evaluate a
744
- * {@link Criteria} natively (SQL `WHERE` + `ORDER`/`LIMIT`, an index range)
745
- * implements this; `Table` prefers it and falls back to `applyCriteria` over
746
- * `scan` when it is absent. Must honor the full criteria (filter, order, page).
747
- */
748
- records?(table: string, criteria: Criteria): Promise<readonly Row[]>;
749
- /**
750
- * Optional native count (AGENTS §21). Counts rows matching the criteria's
751
- * conditions (paging is irrelevant to a count); `Table` falls back to counting
752
- * the engine-filtered `scan` when absent.
753
- */
754
- count?(table: string, criteria: Criteria): Promise<number>;
755
- /**
756
- * Optional native aggregate (AGENTS §21). A backend that can compute an
757
- * aggregate natively (SQL `COUNT`/`SUM`/`AVG`/`MIN`/`MAX`, an indexed count)
758
- * implements this; `Table.aggregate` prefers it and otherwise falls back to
759
- * `computeAggregate` over the native-filtered (or scanned) rows. Aggregates
760
- * ignore paging, so `criteria` carries only conditions.
761
- */
762
- aggregate?(table: string, operation: AggregateFunction, column: FieldPath, criteria: Criteria): Promise<number | undefined>;
763
- /**
764
- * Optional native transaction (BEGIN). When present, the engine uses the
765
- * returned {@link TransactionInterface}'s `commit` / `rollback` instead of
766
- * the snapshot-based rollback floor.
651
+ * Optional native transaction scope. The driver owns acquisition, commit or
652
+ * rollback, release, and invalidation of the scoped capability.
767
653
  */
768
- transaction?(): Promise<TransactionInterface>;
769
- /**
770
- * Optional natively filtered lazy iteration — a {@link Criteria}-aware
771
- * streaming read. Drivers without it are served by the core scan fallback
772
- * (filtering `scan` lazily).
773
- */
774
- stream?(table: string, criteria: Criteria): AsyncIterable<Row>;
775
- /**
776
- * Optional native migration — applies a {@link Migration} plan directly.
777
- * Throws `DatabaseError` `MIGRATION` when a step references an unknown
778
- * table.
779
- */
780
- migrate?(plan: Migration): Promise<void>;
781
- /**
782
- * Optional persisted-metadata read (PAIRED with {@link stamp} — a driver
783
- * implements both or neither). Returns the {@link DriverMeta} last stamped,
784
- * or `undefined` when the store has never been stamped.
785
- */
786
- meta?(): Promise<DriverMeta | undefined>;
787
- /**
788
- * Optional persisted-metadata write (PAIRED with {@link meta} — a driver
789
- * implements both or neither). Persists `meta` verbatim for a later `meta()`
790
- * to return.
791
- */
792
- stamp?(meta: DriverMeta): Promise<void>;
654
+ transaction?<R>(scope: (storage: StorageInterface) => Promise<R>): Promise<R>;
793
655
  }
794
656
 
795
657
  /**
796
- * Persisted schema metadata a versioning driver stores verbatim and returns on
797
- * demand.
658
+ * Persisted schema metadata a versioning driver owns as an immutable snapshot.
798
659
  *
799
660
  * @remarks
800
- * The driver never introspects this payload — it hands back exactly what was
801
- * last stamped via {@link DriverInterface.stamp}. `meta()` returning `undefined`
802
- * is how a fresh store is distinguished from an upgradable one: it means the
803
- * store has never been stamped, not that it is at version zero.
661
+ * A driver snapshots metadata when it enters through `stamp` or a
662
+ * {@link MigrationInput}, so later caller mutation cannot alter stored version
663
+ * state. `metadata()` returns a distinct deeply frozen owned snapshot, never the
664
+ * driver's internal reference. `undefined` distinguishes a fresh store from an
665
+ * upgradable one: it means the store has never been stamped, not that it is at
666
+ * version zero.
804
667
  */
805
- export declare interface DriverMeta {
668
+ export declare interface DriverMetadata {
806
669
  readonly version: number;
807
670
  readonly schema: readonly TableSchema[];
808
671
  }
809
672
 
673
+ /**
674
+ * Structural equality by SameValueZero leaves — the comparator behind conformance
675
+ * checks and any test/fixture that needs "same data", not "same reference".
676
+ *
677
+ * @remarks
678
+ * Primitives compare by SameValueZero (`NaN` equals itself; `+0` equals `-0`).
679
+ * Arrays compare by index (same length, every element `equalsValue`). Plain
680
+ * records (via `isRecord`) compare by their OWN enumerable keys: same key
681
+ * COUNT and, for every key in `left`, `right` has that key (`Object.hasOwn`)
682
+ * with a `equalsValue` value — so a key present with value `undefined` is NOT
683
+ * equal to that key being absent (both differ in `Object.keys` membership).
684
+ * Anything else (functions, class instances, mismatched shapes) falls through
685
+ * to `false`. Container pairs are tracked iteratively, so self-referential and
686
+ * mutually cyclic arrays/records terminate without consuming the call stack.
687
+ * Hostile proxy traps and accessors are contained as a non-match.
688
+ *
689
+ * @param left - The left value
690
+ * @param right - The right value
691
+ * @returns Whether `left` and `right` are structurally equal
692
+ *
693
+ * @example
694
+ * ```ts
695
+ * equalsValue(Number.NaN, Number.NaN) // true
696
+ * equalsValue({ a: [1, { b: 2 }] }, { a: [1, { b: 2 }] }) // true
697
+ * equalsValue({ a: undefined }, {}) // false — present-undefined ≠ absent
698
+ * ```
699
+ */
700
+ export declare function equalsValue(left: unknown, right: unknown): boolean;
701
+
810
702
  /**
811
703
  * Read a row's primary key from a column, when it is a usable {@link Key}.
812
704
  *
@@ -818,11 +710,11 @@ export declare function extractKey(row: Row, column: string): Key | undefined;
818
710
 
819
711
  /**
820
712
  * Filter rows by a list of conditions — the shared basis for a table's count
821
- * and aggregate paths (no sort/page, unlike {@link applyCriteria}).
713
+ * and aggregate paths (no sort/page, unlike {@link applyQuery}).
822
714
  *
823
715
  * @remarks
824
716
  * An empty condition list matches every row (returned as-is, no copy). Folds
825
- * each row through {@link matchesCriteria}.
717
+ * each row through {@link matchesQuery}.
826
718
  *
827
719
  * @param rows - The rows to filter
828
720
  * @param conditions - The conditions to apply (empty matches everything)
@@ -839,35 +731,23 @@ export declare function extractKey(row: Row, column: string): Key | undefined;
839
731
  export declare function filterRows(rows: readonly Row[], conditions: readonly Condition[]): readonly Row[];
840
732
 
841
733
  /**
842
- * Generate an RFC 4122 version 4 UUID from a number source — no host crypto global.
734
+ * Per-table secondary indexes `{ [table]: groups }`, each group one
735
+ * (possibly compound) index of column names.
843
736
  *
844
737
  * @remarks
845
- * Draws exactly {@link UUID_BYTE_COUNT} values from `random`, one per byte, then
846
- * forces the version (`4`) and variant (`10xx`) bits. The default source is
847
- * `Math.random` a pure-ECMAScript intrinsic, so generation works on every host;
848
- * pass a seeded source (`seededRandom` from `@orkestrel/contract`) and reuse it
849
- * across calls for reproducible sequences in tests and fixtures — production
850
- * identifiers should keep the default source, whose engine entropy is far larger
851
- * than a 32-bit seed. Each byte is floored and masked, so a source straying
852
- * outside `[0, 1)` (negative, `>= 1`, `NaN`, `Infinity`) can never yield a
853
- * malformed UUID. Suitable as a collision-resistant record identifier — not a
854
- * cryptographic token; never use one as a secret.
855
- *
856
- * @param random - A number source returning values in the half-open range `[0, 1)` (defaults to `Math.random`)
857
- * @returns A lowercase RFC 4122 version 4 UUID
858
- *
859
- * @example
860
- * ```ts
861
- * import { generateUUID } from '@orkestrel/database'
862
- * import { seededRandom } from '@orkestrel/contract'
863
- *
864
- * generateUUID() // e.g. '9b2f7c1e-3d4a-4f6b-8e2d-5a1c0b9f8e7d'
865
- * generateUUID(seededRandom(42)) // the same UUID on every run
866
- * ```
738
+ * Contracts don't express indexes, so they're declared here on `createDatabase`
739
+ * and flow into each {@link TableSchema}'s `indexes` (SQLite `CREATE INDEX`,
740
+ * IndexedDB `createIndex`). Mirrors {@link PrimaryMap}.
867
741
  */
868
- export declare function generateUUID(random?: RandomFunction): string;
742
+ export declare type IndexMap = Readonly<Record<string, readonly (readonly string[])[]>>;
869
743
 
870
- export declare function globMatch(value: string, pattern: string): boolean;
744
+ /**
745
+ * Test whether a value is a portable column schema.
746
+ *
747
+ * @param value - The value to test
748
+ * @returns Whether `value` is a complete {@link ColumnSchema}
749
+ */
750
+ export declare function isColumnSchema(value: unknown): value is ColumnSchema;
871
751
 
872
752
  /**
873
753
  * Narrow an unknown caught value to a {@link DatabaseError}.
@@ -887,53 +767,82 @@ export declare function globMatch(value: string, pattern: string): boolean;
887
767
  export declare function isDatabaseError(value: unknown): value is DatabaseError;
888
768
 
889
769
  /**
890
- * Whether a value is a well-formed {@link DriverMeta} — the boundary guard a
891
- * versioning driver's `meta()` narrows a stored (structured-clone or
892
- * `JSON.parse`d) value through before trusting it, replacing the per-driver
893
- * duplicated narrowing every backend used to hand-roll (AGENTS §14: never `as`).
770
+ * Test whether a value is persisted driver metadata.
894
771
  *
895
- * @remarks
896
- * Total and total-recursive over the whole shape: a finite `version`, and a
897
- * `schema` array of well-formed {@link TableSchema} entries — each a `name` /
898
- * `primary` string pair, a `columns` array of well-formed {@link ColumnSchema}
899
- * entries (a `name` string, a {@link ColumnType} literal, a `nullable`
900
- * boolean), and an `indexes` array of string arrays. Anything off-shape
901
- * (including a non-record) returns `false` rather than throwing.
772
+ * @param value - The value to test
773
+ * @returns Whether `value` is complete {@link DriverMetadata}
774
+ */
775
+ export declare function isDriverMetadata(value: unknown): value is DriverMetadata;
776
+
777
+ /**
778
+ * Test whether a value is a complete portable driver schema.
902
779
  *
903
780
  * @param value - The value to test
904
- * @returns `true` when `value` is a well-formed `DriverMeta`
781
+ * @returns Whether `value` is a table-schema collection with unique table names
782
+ */
783
+ export declare function isDriverSchema(value: unknown): value is readonly TableSchema[];
784
+
785
+ /**
786
+ * Test whether a value is a usable database key.
905
787
  *
906
- * @example
907
- * ```ts
908
- * isDriverMeta({ version: 1, schema: [] }) // true
909
- * isDriverMeta({ version: 1, schema: [{ name: 'users' }] }) // false
910
- * ```
788
+ * @param value - The value to test
789
+ * @returns Whether `value` is a string or finite number
790
+ */
791
+ export declare function isKey(value: unknown): value is Key;
792
+
793
+ /**
794
+ * Test whether a value is an ordered migration plan.
795
+ *
796
+ * @param value - The value to test
797
+ * @returns Whether `value` is a complete {@link Migration}
798
+ */
799
+ export declare function isMigration(value: unknown): value is Migration;
800
+
801
+ /**
802
+ * Test whether a value is one atomic migration request.
803
+ *
804
+ * @param value - The value to test
805
+ * @returns Whether `value` is a complete {@link MigrationInput}
806
+ */
807
+ export declare function isMigrationInput(value: unknown): value is MigrationInput;
808
+
809
+ /**
810
+ * Test whether a value is one ordered migration step.
811
+ *
812
+ * @param value - The value to test
813
+ * @returns Whether `value` is a complete {@link MigrationStep}
814
+ */
815
+ export declare function isMigrationStep(value: unknown): value is MigrationStep;
816
+
817
+ /**
818
+ * Test whether a value is a portable table schema.
819
+ *
820
+ * @param value - The value to test
821
+ * @returns Whether `value` is a complete {@link TableSchema}
911
822
  */
912
- export declare function isDriverMeta(value: unknown): value is DriverMeta;
823
+ export declare function isTableSchema(value: unknown): value is TableSchema;
913
824
 
914
825
  /**
915
826
  * A primary key — the value identifying a row within its table.
916
827
  *
917
828
  * @remarks
918
829
  * `string | number` is the intersection of what IndexedDB key ranges and SQL
919
- * primary keys both express without coercion. Auto-generated keys are UUID
920
- * strings; supply your own to use numeric keys.
830
+ * primary keys both express without coercion. The default generated key is a
831
+ * UUID string; configure a custom generator for numeric primary keys.
921
832
  */
922
833
  export declare type Key = string | number;
923
834
 
924
835
  /**
925
- * A caller-supplied key minting function.
836
+ * A key-generating function.
926
837
  *
927
838
  * @remarks
928
- * Environment surfaces provide implementations (the server's `node:crypto`-backed
929
- * `generateKey`); the core mints no keys itself. Supplied via
930
- * {@link DatabaseOptions.key} and used by a table when a written row lacks its
931
- * primary key. Without one, writing a keyless row is a `VALIDATION` error.
839
+ * Supplied through {@link DatabaseOptions.generator} as an authoritative
840
+ * override when an application needs a non-UUID key or controlled generation.
841
+ * When omitted, a keyless write uses the global `crypto.randomUUID()`. Numeric
842
+ * primary keys therefore require a custom generator.
932
843
  */
933
844
  export declare type KeyFunction = () => Key;
934
845
 
935
- export declare function likeMatch(value: string, pattern: string): boolean;
936
-
937
846
  /**
938
847
  * Evaluate one {@link Condition} against a row — the per-operator predicate.
939
848
  *
@@ -942,10 +851,10 @@ export declare function likeMatch(value: string, pattern: string): boolean;
942
851
  * string is one column; an array descends a nested value) — and applies the
943
852
  * operator. Range operators (`above` / `below` / `from` / `to` / `between`) use
944
853
  * {@link compareValues}, the total order; the equality family (`equals` / `not`
945
- * / `any` / `none`) uses {@link deepEqual} — STRUCTURAL equality, not the total
854
+ * / `any` / `none`) uses {@link equalsValue} — STRUCTURAL equality, not the total
946
855
  * order's rank-5-collapses-all-objects behavior, so `equals` on an object/array
947
856
  * operand only matches a structurally-equal value, never every row holding any
948
- * object. This is a semantics change from ranking: `deepEqual` is SameValueZero
857
+ * object. This is a semantics change from ranking: `equalsValue` is SameValueZero
949
858
  * on leaves, so `NaN` now equals `NaN` under `equals` / `any` (it never matched
950
859
  * anything under the old rank-based comparison). `like` / `glob` / `starts` /
951
860
  * `ends` match only strings; `absent` / `present` test nullishness. Total — a
@@ -957,6 +866,10 @@ export declare function likeMatch(value: string, pattern: string): boolean;
957
866
  */
958
867
  export declare function matchesCondition(row: Row, condition: Condition): boolean;
959
868
 
869
+ export declare function matchesGlobPattern(value: string, pattern: string): boolean;
870
+
871
+ export declare function matchesLikePattern(value: string, pattern: string): boolean;
872
+
960
873
  /**
961
874
  * Fold a row through a list of conditions, joining each by its connector.
962
875
  *
@@ -970,14 +883,47 @@ export declare function matchesCondition(row: Row, condition: Condition): boolea
970
883
  * @param conditions - The conditions to fold
971
884
  * @returns Whether the row satisfies the combined conditions
972
885
  */
973
- export declare function matchesCriteria(row: Row, conditions: readonly Condition[]): boolean;
886
+ export declare function matchesQuery(row: Row, conditions: readonly Condition[]): boolean;
887
+
888
+ /**
889
+ * Match a value against a wildcard pattern in LINEAR time — the shared, ReDoS-SAFE
890
+ * engine behind {@link matchesLikePattern} and {@link matchesGlobPattern}.
891
+ *
892
+ * @remarks
893
+ * A backtracking RegExp (`a%b%c` → `^a.*b.*c$`) is CATASTROPHIC on a hostile pattern:
894
+ * `.*` segments separated by literals, matched against a long non-matching input, blow
895
+ * up super-linearly — and JS has no atomic groups / possessive quantifiers to bound it
896
+ * (AGENTS §6.5, now that the authed server runs model-supplied `list` input over the
897
+ * wire). So this builds NO regex. It runs the classic GREEDY TWO-POINTER wildcard match:
898
+ * the `any` wildcard records its position and, on a later mismatch, backtracks ONLY to
899
+ * that last `any` (letting it absorb one more char) — so the work is O(value × pattern),
900
+ * never the exponential / polynomial backtracking a regex would do. The pattern length
901
+ * is capped at {@link MAX_PATTERN_LENGTH} (a `VALIDATION` {@link DatabaseError} over it),
902
+ * bounding the pattern factor so a match stays linear in the value length whatever the
903
+ * pattern.
904
+ *
905
+ * The `any` wildcard matches any run (including empty); `single` matches exactly one
906
+ * char; every other pattern char matches itself LITERALLY (a pattern `.` / `(` / `\` is
907
+ * a literal — the regex-metacharacter hazard is gone with the regex). `any` is tested
908
+ * BEFORE a literal match, so a value that literally contains the wildcard char never
909
+ * shadows the wildcard. Case folding is applied to BOTH sides when `fold` is set.
910
+ *
911
+ * @param value - The value to test
912
+ * @param pattern - The wildcard pattern
913
+ * @param any - The any-run wildcard char (`%` for `LIKE`, `*` for `GLOB`)
914
+ * @param single - The single-char wildcard char (`_` for `LIKE`, `?` for `GLOB`)
915
+ * @param fold - Whether to match case-INSENSITIVELY (`LIKE` folds; `GLOB` does not)
916
+ * @returns Whether `value` matches `pattern`
917
+ * @throws A `VALIDATION` {@link DatabaseError} when `pattern` exceeds {@link MAX_PATTERN_LENGTH}
918
+ */
919
+ export declare function matchesWildcardPattern(value: string, pattern: string, any: string, single: string, fold: boolean): boolean;
974
920
 
975
921
  /**
976
922
  * The longest `LIKE` / `GLOB` pattern the wildcard matcher accepts before rejecting it.
977
923
  *
978
924
  * @remarks
979
925
  * A ReDoS bound (AGENTS §6.5): the SA1–SA4 migration lets a model supply `list`
980
- * criteria over the wire, so `likeMatch` / `globMatch` run attacker-controlled
926
+ * input over the wire, so `matchesLikePattern` / `matchesGlobPattern` run attacker-controlled
981
927
  * patterns. The matcher is the LINEAR greedy two-pointer wildcard match — never a
982
928
  * backtracking regex (`.*`-segments-separated-by-literals against a long input is the
983
929
  * catastrophic shape JS cannot bound without atomic groups), so it is O(value ×
@@ -998,21 +944,24 @@ export declare const MAX_PATTERN_LENGTH = 1024;
998
944
  * snapshot capture and restore — so a caller mutating a nested field of an input
999
945
  * row, a returned row, or a row mutated in place between snapshot and rollback
1000
946
  * can never perturb stored state (AGENTS §11); a shallow `{ ...row }` spread
1001
- * would still share nested object/array references. `snapshot`
947
+ * would still share nested object/array references. Metadata instead routes
948
+ * through `cloneDriverMetadata`: `stamp` and migration snapshot exact JSON at
949
+ * ingress, and `metadata` returns a distinct deeply frozen owned copy. `snapshot`
1002
950
  * clones every table to give transactions an exact rollback point. `scan` and
1003
951
  * `keys` yield in key order — sorted by the core {@link compareValues} total
1004
952
  * order, the same contract the SQLite (`ORDER BY`) and IndexedDB (key-ordered
1005
953
  * reads) backends honor, so an unordered read agrees across every backend rather
1006
954
  * than leaking Map insertion order. A persistent backend (IndexedDB, SQLite)
1007
- * implements the same nine methods over real storage.
955
+ * implements the same required methods over real storage.
1008
956
  */
1009
957
  export declare class MemoryDriver implements DriverInterface {
1010
958
  #private;
1011
959
  open(schema: readonly TableSchema[]): Promise<void>;
1012
960
  close(): Promise<void>;
1013
961
  read(table: string, key: Key): Promise<Row | undefined>;
1014
- write(table: string, key: Key, row: Row): Promise<void>;
1015
- delete(table: string, key: Key): Promise<boolean>;
962
+ write(table: string, key: Key, row: Row, options?: OperationOptions): Promise<void>;
963
+ insert(table: string, key: Key, row: Row, options?: OperationOptions): Promise<void>;
964
+ delete(table: string, key: Key, options?: OperationOptions): Promise<boolean>;
1016
965
  keys(table: string): Promise<readonly Key[]>;
1017
966
  scan(table: string): AsyncIterable<Row>;
1018
967
  /**
@@ -1021,17 +970,17 @@ export declare class MemoryDriver implements DriverInterface {
1021
970
  * @remarks
1022
971
  * Iterates the table's keys in the same key order `scan` and `keys` yield
1023
972
  * (sorted by {@link compareValues}), testing each row against
1024
- * `criteria.conditions` (via {@link matchesCriteria}) before counting it
973
+ * `input.conditions` (via {@link matchesQuery}) before counting it
1025
974
  * toward `offset` / `limit`. Both are applied lazily as matches are found —
1026
975
  * `offset` matches are skipped without being yielded, and iteration stops the
1027
976
  * instant `limit` yields have been produced, so a large table is never fully
1028
- * walked for a small page. `criteria.order` is IGNORED (the same contract as
977
+ * walked for a small page. `input.order` is IGNORED (the same contract as
1029
978
  * `TableInterface.scan` and `QueryInterface.stream`): streaming yields key
1030
979
  * order, sorted output is `records()`'s job. Rows yield copy-out (AGENTS
1031
980
  * §11), and an unknown table mirrors `scan`'s empty-yield behavior.
1032
981
  *
1033
982
  * @param table - The table to stream
1034
- * @param criteria - The filter / offset / limit to apply lazily
983
+ * @param input - The filter / offset / limit to apply lazily
1035
984
  *
1036
985
  * @example
1037
986
  * ```ts
@@ -1040,49 +989,51 @@ export declare class MemoryDriver implements DriverInterface {
1040
989
  * }
1041
990
  * ```
1042
991
  */
1043
- stream(table: string, criteria: Criteria): AsyncIterable<Row>;
992
+ stream(table: string, input: QueryInput): AsyncIterable<Row>;
1044
993
  clear(table: string): Promise<void>;
1045
994
  /**
1046
995
  * Capture the current state and return a thunk that rolls back to it.
1047
996
  *
1048
997
  * @remarks
1049
- * `tables` omitted clones and restores the WHOLE store, byte-identical to the
1050
- * prior whole-store behavior. `tables` provided clones ONLY the named tables,
1051
- * and the returned thunk restores ONLY those every other table keeps
1052
- * whatever it was mutated to after the snapshot was taken.
998
+ * Capture owns rows, schema, and one session-local table identity. Replay
999
+ * adapts rows to each surviving same-identity table's current schema before
1000
+ * changing storage. Removed or replaced tables are skipped; uncaptured and
1001
+ * later-added tables retain their current rows. Schema and metadata are never
1002
+ * restored.
1053
1003
  *
1054
1004
  * @param tables - The table names to scope the snapshot to; omitted captures every table
1055
1005
  * @returns A thunk that restores the captured tables
1056
1006
  */
1057
1007
  snapshot(tables?: readonly string[]): Promise<() => Promise<void>>;
1058
1008
  /**
1059
- * Return the persisted {@link DriverMeta}, or `undefined` when the store has
1009
+ * Return the persisted {@link DriverMetadata}, or `undefined` when the store has
1060
1010
  * never been stamped.
1061
1011
  *
1062
1012
  * @remarks
1063
1013
  * In-process only — the metadata lives in this instance's memory, exactly
1064
- * like the rest of this driver's storage. A driver-conformance-valid
1065
- * implementation of the optional `meta` / `stamp` pair.
1014
+ * like the rest of this driver's storage. The returned value is a distinct
1015
+ * deeply frozen owned snapshot. A driver-conformance-valid implementation of
1016
+ * the optional `metadata` / `stamp` pair.
1066
1017
  *
1067
- * @returns The last-stamped {@link DriverMeta}, or `undefined`
1018
+ * @returns The last-stamped {@link DriverMetadata}, or `undefined`
1068
1019
  */
1069
- meta(): Promise<DriverMeta | undefined>;
1020
+ metadata(): Promise<DriverMetadata | undefined>;
1070
1021
  /**
1071
- * Persist `meta` verbatim for a later `meta()` to return.
1022
+ * Persist an owned snapshot for a later `metadata()` to return.
1072
1023
  *
1073
- * @param meta - The {@link DriverMeta} to persist
1024
+ * @param metadata - The {@link DriverMetadata} to persist
1074
1025
  */
1075
- stamp(meta: DriverMeta): Promise<void>;
1026
+ stamp(metadata: DriverMetadata): Promise<void>;
1076
1027
  /**
1077
1028
  * Apply a {@link Migration} plan's steps against the in-memory store.
1078
1029
  *
1079
1030
  * @remarks
1080
- * A multi-step plan applies its steps sequentially and is NOT atomic — a
1081
- * failure partway through a plan leaves the earlier steps already applied.
1031
+ * Steps apply against an isolated candidate. Rows, schema changes, and
1032
+ * optional metadata publish together only after the whole request succeeds.
1082
1033
  *
1083
- * @param plan - The migration plan to apply
1034
+ * @param input - The migration plan and optional metadata to settle atomically
1084
1035
  */
1085
- migrate(plan: Migration): Promise<void>;
1036
+ migrate(input: MigrationInput): Promise<void>;
1086
1037
  }
1087
1038
 
1088
1039
  /**
@@ -1124,6 +1075,19 @@ export declare interface Migration {
1124
1075
  readonly steps: readonly MigrationStep[];
1125
1076
  }
1126
1077
 
1078
+ /**
1079
+ * One atomic migration request.
1080
+ *
1081
+ * @remarks
1082
+ * `plan` carries the schema changes. `metadata`, when present, is the snapshot that
1083
+ * must settle in the same atomic unit as those changes so a failed migration
1084
+ * cannot expose a new schema under stale version metadata.
1085
+ */
1086
+ export declare interface MigrationInput {
1087
+ readonly plan: Migration;
1088
+ readonly metadata?: DriverMetadata;
1089
+ }
1090
+
1127
1091
  /**
1128
1092
  * One step of a {@link Migration} plan — a single schema change applied to one
1129
1093
  * table.
@@ -1158,12 +1122,42 @@ export declare type MigrationStep = {
1158
1122
  readonly index: readonly string[];
1159
1123
  };
1160
1124
 
1125
+ /**
1126
+ * Canonicalize an unknown driver schema into a distinct deeply frozen snapshot.
1127
+ *
1128
+ * @remarks
1129
+ * Table and column lists are sorted by name. The index list is sorted by the
1130
+ * complete serialized tuple while column order inside each compound index is
1131
+ * preserved because it carries index semantics. Validation and ownership flow
1132
+ * through {@link cloneDriverSchema} before and after projection.
1133
+ *
1134
+ * @param value - Unknown driver schema
1135
+ * @returns A validated, owned canonical schema
1136
+ */
1137
+ export declare function normalizeDriverSchema(value: unknown): readonly TableSchema[];
1138
+
1139
+ /**
1140
+ * Options for an abortable operation.
1141
+ *
1142
+ * @remarks
1143
+ * When `signal` aborts, the operation throws a {@link DatabaseError} with code
1144
+ * `ABORTED` carrying `signal.reason` in `context`. Reads check at their
1145
+ * documented boundaries; point mutations propagate the signal through the
1146
+ * driver to the backend commit point.
1147
+ */
1148
+ export declare interface OperationOptions {
1149
+ readonly signal?: AbortSignal;
1150
+ }
1151
+
1161
1152
  /** One ordering term — a column ({@link FieldPath}, flat or nested) and its direction. */
1162
1153
  export declare interface Order {
1163
1154
  readonly column: FieldPath;
1164
- readonly direction: Direction;
1155
+ readonly direction: OrderDirection;
1165
1156
  }
1166
1157
 
1158
+ /** A sort direction. */
1159
+ export declare type OrderDirection = 'ascending' | 'descending';
1160
+
1167
1161
  /**
1168
1162
  * Structurally diff a deployed and a declared table set into a {@link Migration}
1169
1163
  * plan.
@@ -1177,11 +1171,12 @@ export declare interface Order {
1177
1171
  * `column.remove` / `index.add` / `index.remove` steps. Step order is
1178
1172
  * deterministic: every `table.remove`, then every `table.add`, then each
1179
1173
  * shared table's column/index changes in `declared` order. `from` / `to` are
1180
- * plan labels only version tracking itself is deferred to persistent
1181
- * backends.
1174
+ * plan labels only; versioning drivers persist and reconcile them through
1175
+ * {@link DriverMetadata}.
1182
1176
  *
1183
1177
  * A column present in BOTH schemas under the same name but with a different
1184
- * `type` or `nullable` throws a `MIGRATION` {@link DatabaseError} naming the
1178
+ * `storage`, `optional`, or `nullable` value throws a `MIGRATION`
1179
+ * {@link DatabaseError} naming the
1185
1180
  * table, the column, and the from→to difference — a name-only diff would
1186
1181
  * otherwise silently produce NO step for the drift, and versioned
1187
1182
  * reconciliation would stamp over it. There is no automatic in-place
@@ -1194,14 +1189,16 @@ export declare interface Order {
1194
1189
  * @param from - The plan's source version label (defaults to `0`)
1195
1190
  * @param to - The plan's target version label (defaults to `1`)
1196
1191
  * @returns The migration plan moving `deployed` toward `declared`
1197
- * @throws A `MIGRATION` {@link DatabaseError} when a shared column's `type` or
1198
- * `nullable` differs between `deployed` and `declared`
1192
+ * @throws A `MIGRATION` {@link DatabaseError} when a shared table's primary or
1193
+ * a shared column's `storage`, `optional`, or `nullable` differs, or when a
1194
+ * required non-null column would be added to an existing table without a
1195
+ * portable backfill
1199
1196
  *
1200
1197
  * @example
1201
1198
  * ```ts
1202
1199
  * const plan = planMigration(
1203
- * [{ name: 'users', primary: 'id', columns: [], indexes: [] }],
1204
- * [{ name: 'users', primary: 'id', columns: [{ name: 'age', type: 'integer', nullable: false }], indexes: [] }],
1200
+ * [{ name: 'users', primary: 'id', columns: [{ name: 'id', storage: 'text', optional: false, nullable: false }], indexes: [] }],
1201
+ * [{ name: 'users', primary: 'id', columns: [{ name: 'id', storage: 'text', optional: false, nullable: false }, { name: 'age', storage: 'integer', optional: true, nullable: false }], indexes: [] }],
1205
1202
  * )
1206
1203
  * // plan.steps === [{ operation: 'column.add', table: 'users', column: { name: 'age', ... } }]
1207
1204
  * ```
@@ -1209,74 +1206,62 @@ export declare interface Order {
1209
1206
  export declare function planMigration(deployed: readonly TableSchema[], declared: readonly TableSchema[], from?: number, to?: number): Migration;
1210
1207
 
1211
1208
  /**
1212
- * A fluent query builder bound to one table.
1209
+ * Per-table primary-key column overrides `{ [table]: column }`.
1213
1210
  *
1214
1211
  * @remarks
1215
- * Accumulates conditions, ordering, JS filters, and a page; each builder method
1216
- * mutates and returns the same instance, so a chain reads as one statement. The
1217
- * portable parts (conditions, order, page) compile into a {@link Criteria} the
1218
- * table resolves; a `filter` predicate is applied in memory after the read and
1219
- * before paging, so it composes with the rest without a backend ever seeing a
1220
- * JS callback.
1221
- */
1222
- export declare class Query<T = Record<string, unknown>> implements QueryInterface<T> {
1223
- #private;
1224
- constructor(table: TableInterface<T>);
1225
- where(column: FieldPath): ClauseInterface<T>;
1226
- and(column: FieldPath): ClauseInterface<T>;
1227
- or(column: FieldPath): ClauseInterface<T>;
1228
- filter(predicate: (row: T) => boolean): QueryInterface<T>;
1229
- ascending(column: FieldPath): QueryInterface<T>;
1230
- descending(column: FieldPath): QueryInterface<T>;
1231
- limit(count: number): QueryInterface<T>;
1232
- offset(count: number): QueryInterface<T>;
1233
- all(): Promise<readonly T[]>;
1234
- first(): Promise<T | undefined>;
1235
- count(): Promise<number>;
1236
- /**
1237
- * Lazy per-row evaluation of this query's conditions / filters / offset /
1238
- * limit.
1239
- *
1240
- * @remarks
1241
- * `order` and its comparators are IGNORED (streaming yields unsorted, as rows
1242
- * are evaluated one at a time). Same abort semantics as
1243
- * `TableInterface.scan`: the signal (if any) is checked before each yield,
1244
- * and breaking out early closes the underlying source.
1245
- *
1246
- * @param options - `signal` to cancel the iteration; checked before each yield
1247
- * @returns An async iterable of matching rows
1248
- */
1249
- stream(options?: ReadOptions): AsyncIterable<T>;
1250
- aggregate(operation: AggregateFunction, column: FieldPath): Promise<number | undefined>;
1251
- sum(column: FieldPath): Promise<number | undefined>;
1252
- average(column: FieldPath): Promise<number | undefined>;
1253
- minimum(column: FieldPath): Promise<number | undefined>;
1254
- maximum(column: FieldPath): Promise<number | undefined>;
1212
+ * A table absent from this map keys its rows by {@link DEFAULT_PRIMARY} (`id`).
1213
+ * Kept separate from {@link TableMap} so the table map stays purely columns.
1214
+ */
1215
+ export declare type PrimaryMap = Readonly<Record<string, string>>;
1216
+
1217
+ /**
1218
+ * Sequentially project migration steps over a canonical validated owned schema.
1219
+ * Adding a required non-null column to an existing table rejects with
1220
+ * `MIGRATION`; optional-only and nullable-only additions remain portable.
1221
+ *
1222
+ * @param schema - The initial deployed schema
1223
+ * @param steps - The ordered migration steps
1224
+ * @returns A fresh owned final schema
1225
+ */
1226
+ export declare function projectMigrationSchema(schema: readonly TableSchema[], steps: readonly MigrationStep[]): readonly TableSchema[];
1227
+
1228
+ /**
1229
+ * A serializable read specification — everything a backend needs to compile one
1230
+ * read, free of JS callbacks so any backend can honor it.
1231
+ *
1232
+ * @remarks
1233
+ * The post-fetch `filter` predicate lives on {@link QueryInterface}, never here,
1234
+ * so `QueryInput` stays portable across backends. When present, `limit` and
1235
+ * `offset` must be finite nonnegative integers. Zero is valid: `limit: 0`
1236
+ * selects an empty page and `offset: 0` skips nothing.
1237
+ */
1238
+ export declare interface QueryInput {
1239
+ readonly conditions?: readonly Condition[];
1240
+ readonly order?: readonly Order[];
1241
+ readonly limit?: number;
1242
+ readonly offset?: number;
1255
1243
  }
1256
1244
 
1257
1245
  /**
1258
1246
  * A fluent query builder.
1259
1247
  *
1260
1248
  * @remarks
1261
- * `where` / `and` / `or` open a {@link ClauseInterface} whose operator
1262
- * closes the condition and returns the query. `filter` adds a post-fetch JS
1263
- * predicate (applied after the backend read, before paging). The terminals
1264
- * (`all` / `first` / `count` / the aggregates) execute against the table; each
1249
+ * `condition` appends one portable condition and `order` appends one portable
1250
+ * ordering term. `filter` adds a post-fetch JavaScript predicate (applied after
1251
+ * the backend read, before paging). The terminals (`collect` / `find` / `count`
1252
+ * / `aggregate`) execute against the table; each
1265
1253
  * call mutates and returns the same builder, so a chain reads as one statement.
1266
1254
  * Every `column` is a {@link FieldPath} — a string is one column, an array
1267
1255
  * descends a nested value.
1268
1256
  */
1269
1257
  export declare interface QueryInterface<T = Row> {
1270
- where(column: FieldPath): ClauseInterface<T>;
1271
- and(column: FieldPath): ClauseInterface<T>;
1272
- or(column: FieldPath): ClauseInterface<T>;
1258
+ condition(input: Condition): QueryInterface<T>;
1259
+ order(input: Order): QueryInterface<T>;
1273
1260
  filter(predicate: (row: T) => boolean): QueryInterface<T>;
1274
- ascending(column: FieldPath): QueryInterface<T>;
1275
- descending(column: FieldPath): QueryInterface<T>;
1276
1261
  limit(count: number): QueryInterface<T>;
1277
1262
  offset(count: number): QueryInterface<T>;
1278
- all(): Promise<readonly T[]>;
1279
- first(): Promise<T | undefined>;
1263
+ collect(): Promise<readonly T[]>;
1264
+ find(): Promise<T | undefined>;
1280
1265
  count(): Promise<number>;
1281
1266
  /**
1282
1267
  * Lazy per-row evaluation of this query's conditions / filters / offset /
@@ -1288,51 +1273,43 @@ export declare interface QueryInterface<T = Row> {
1288
1273
  * {@link TableInterface.scan}: the signal (if any) is checked before each
1289
1274
  * yield, and breaking out early closes the underlying source.
1290
1275
  */
1291
- stream(options?: ReadOptions): AsyncIterable<T>;
1292
- sum(column: FieldPath): Promise<number | undefined>;
1293
- average(column: FieldPath): Promise<number | undefined>;
1294
- minimum(column: FieldPath): Promise<number | undefined>;
1295
- maximum(column: FieldPath): Promise<number | undefined>;
1296
- aggregate(operation: AggregateFunction, column: FieldPath): Promise<number | undefined>;
1297
- }
1298
-
1299
- /**
1300
- * Options for a cancellable read / iteration operation.
1301
- *
1302
- * @remarks
1303
- * When `signal` aborts, the operation throws a {@link DatabaseError} with code
1304
- * `ABORTED` carrying `signal.reason` in `context`. `TableInterface.scan` and
1305
- * `QueryInterface.stream` check the signal before each yield; other read
1306
- * methods check it at entry.
1307
- */
1308
- export declare interface ReadOptions {
1309
- readonly signal?: AbortSignal;
1276
+ stream(options?: OperationOptions): AsyncIterable<T>;
1277
+ aggregate(operation: AggregateOperation, column: FieldPath): Promise<number | undefined>;
1310
1278
  }
1311
1279
 
1312
1280
  /** A table row — a plain record of column values keyed by column name. */
1313
1281
  export declare type Row = Record<string, unknown>;
1314
1282
 
1315
1283
  /**
1316
- * The row type a table's {@link Columns} describe — `Infer` of the `objectShape`
1284
+ * The row type a table's {@link ColumnMap} describe — `Infer` of the `objectShape`
1317
1285
  * the database wraps them in.
1318
1286
  *
1319
1287
  * @remarks
1320
1288
  * Contract 0.0.4's non-distributive `Infer` resolves the OPEN case (the broad
1321
- * `Columns` — e.g. when a database is held at its default type) directly:
1322
- * `RowOf<Columns>` and {@link Row} are mutually assignable, so no short-circuit
1289
+ * `ColumnMap` — e.g. when a database is held at its default type) directly:
1290
+ * `RowOf<ColumnMap>` and {@link Row} are mutually assignable, so no short-circuit
1323
1291
  * to `Row` and no `additionalProperties: false` pin are needed — `Infer` no
1324
1292
  * longer trips TS's instantiation-depth guard over the open shape, and the
1325
1293
  * inferred row matches the CLOSED object `objectShape(columns)` builds at
1326
1294
  * runtime (its additional-properties parameter defaults to `false`) for every
1327
1295
  * concrete column map.
1328
1296
  */
1329
- export declare type RowOf<C extends Columns> = Infer<{
1297
+ export declare type RowOf<C extends ColumnMap> = Infer<{
1330
1298
  readonly type: 'object';
1331
1299
  readonly properties: C;
1332
1300
  }>;
1333
1301
 
1334
1302
  /**
1335
- * Map a column's {@link ContractShape} to its portable {@link ColumnType} — the
1303
+ * Project one contract shape into a portable column schema.
1304
+ *
1305
+ * @param name - The column name
1306
+ * @param shape - The column contract shape
1307
+ * @returns The portable storage and independent absence/null acceptance
1308
+ */
1309
+ export declare function shapeToColumnSchema(name: string, shape: ContractShape): ColumnSchema;
1310
+
1311
+ /**
1312
+ * Map a column's {@link ContractShape} to its portable {@link ColumnStorage} — the
1336
1313
  * value a `TableSchema` carries so a native backend can declare a real column.
1337
1314
  *
1338
1315
  * @remarks
@@ -1349,13 +1326,13 @@ export declare type RowOf<C extends Columns> = Infer<{
1349
1326
  *
1350
1327
  * @example
1351
1328
  * ```ts
1352
- * shapeToColumnType(stringShape()) // 'text'
1353
- * shapeToColumnType(integerShape()) // 'integer'
1354
- * shapeToColumnType(optionalShape(integerShape())) // 'integer'
1355
- * shapeToColumnType(objectShape({ a: stringShape() })) // 'json'
1329
+ * shapeToColumnStorage(stringShape()) // 'text'
1330
+ * shapeToColumnStorage(integerShape()) // 'integer'
1331
+ * shapeToColumnStorage(optionalShape(integerShape())) // 'integer'
1332
+ * shapeToColumnStorage(objectShape({ a: stringShape() })) // 'json'
1356
1333
  * ```
1357
1334
  */
1358
- export declare function shapeToColumnType(shape: ContractShape): ColumnType;
1335
+ export declare function shapeToColumnStorage(shape: ContractShape): ColumnStorage;
1359
1336
 
1360
1337
  /**
1361
1338
  * Sort rows by an ordering specification, leaving the input untouched.
@@ -1371,102 +1348,44 @@ export declare function shapeToColumnType(shape: ContractShape): ColumnType;
1371
1348
  export declare function sortRows(rows: readonly Row[], order: readonly Order[]): readonly Row[];
1372
1349
 
1373
1350
  /**
1374
- * A table typed keyed CRUD plus fluent query and cursor access over a driver.
1351
+ * The storage operations available only inside a driver's transaction scope.
1375
1352
  *
1376
1353
  * @remarks
1377
- * The table's contract is the load-bearing piece: writes go through `parse`
1378
- * (coercing inputs and rejecting rows that don't fit with a `VALIDATION` throw),
1379
- * reads come back through the contract guard (narrowing a stored {@link Row} to
1380
- * the table's type no assertion, AGENTS §1), and `contract` is exposed for
1381
- * introspection and seeding. The driver only stores and scans; all querying is
1382
- * the shared core engine in `helpers.ts`.
1354
+ * A driver owns acquisition, commit or rollback, release, and lifetime. This
1355
+ * capability exposes storage work only: it has no public `commit` / `rollback`,
1356
+ * cannot start a nested transaction, and throws `CONFLICT` after its scope
1357
+ * settles. Optional native read and migration hooks mirror the owning driver;
1358
+ * callers fall back to `scan` when a native read hook is absent.
1359
+ */
1360
+ export declare interface StorageInterface {
1361
+ read(table: string, key: Key): Promise<Row | undefined>;
1362
+ write(table: string, key: Key, row: Row, options?: OperationOptions): Promise<void>;
1363
+ insert(table: string, key: Key, row: Row, options?: OperationOptions): Promise<void>;
1364
+ delete(table: string, key: Key, options?: OperationOptions): Promise<boolean>;
1365
+ keys(table: string): Promise<readonly Key[]>;
1366
+ scan(table: string): AsyncIterable<Row>;
1367
+ clear(table: string): Promise<void>;
1368
+ records?(table: string, input: QueryInput): Promise<readonly Row[]>;
1369
+ aggregate?(table: string, operation: AggregateOperation, column: FieldPath, input: QueryInput): Promise<number | undefined>;
1370
+ stream?(table: string, input: QueryInput): AsyncIterable<Row>;
1371
+ migrate?(input: MigrationInput): Promise<void>;
1372
+ metadata?(): Promise<DriverMetadata | undefined>;
1373
+ stamp?(metadata: DriverMetadata): Promise<void>;
1374
+ }
1375
+
1376
+ /**
1377
+ * One table's portable definition, produced by `export` — the unit of schema /
1378
+ * migration exchange across environments.
1383
1379
  *
1384
1380
  * @remarks
1385
- * - **Observable (§13).** The owned {@link emitter} ({@link TableEventMap}) carries the
1386
- * per-row mutation moments `write` (set / add / update), `remove`, `clear` for
1387
- * fire-and-forget observers (cache invalidation, sync, an audit log), ALONGSIDE the
1388
- * database-level lifecycle. Events carry the affected KEY only (no value payload, to
1389
- * keep fan-out lean); reads / queries / counts are not emitted. Every event is emitted
1390
- * directly, strictly AFTER the driver write / delete / clear completes; the emitter
1391
- * isolates a listener throw and routes it to its `error` handler (the `error` option),
1392
- * so a buggy observer can never corrupt a write or perturb a transaction.
1393
- */
1394
- export declare class Table<T = Row> implements TableInterface<T> {
1395
- #private;
1396
- constructor(ready: () => Promise<void>, driver: DriverInterface, name: string, key: string, contract: ContractInterface<T>, generate?: KeyFunction, on?: EmitterHooks<TableEventMap>, error?: EmitterErrorHandler);
1397
- get emitter(): EmitterInterface<TableEventMap>;
1398
- get name(): string;
1399
- get primary(): string;
1400
- get contract(): ContractInterface<T>;
1401
- get(key: Key): Promise<T | undefined>;
1402
- get(keys: readonly Key[]): Promise<readonly (T | undefined)[]>;
1403
- resolve(key: Key): Promise<T>;
1404
- resolve(keys: readonly Key[]): Promise<readonly T[]>;
1405
- has(key: Key): Promise<boolean>;
1406
- has(keys: readonly Key[]): Promise<readonly boolean[]>;
1407
- keys(): Promise<readonly Key[]>;
1408
- records(criteria?: Criteria, options?: ReadOptions): Promise<readonly T[]>;
1409
- /**
1410
- * Count rows matching `criteria`'s conditions.
1411
- *
1412
- * @remarks
1413
- * Unlike {@link records}, which narrows every row through the table's
1414
- * contract guard before returning it, `count` operates on STORED rows
1415
- * WITHOUT that guard (both the native `driver.count` hook and the
1416
- * `filterRows`-over-`#collect()` fallback count raw storage) — so it can
1417
- * exceed `(await records(criteria)).length` when storage holds rows that
1418
- * no longer conform to the table's contract (legacy or migrated data).
1419
- *
1420
- * @param criteria - Optional conditions to filter by (paging is ignored)
1421
- * @param options - `{ signal }` to abort
1422
- * @returns The count of matching stored rows
1423
- */
1424
- count(criteria?: Criteria, options?: ReadOptions): Promise<number>;
1425
- /**
1426
- * Compute an aggregate over `column` across rows matching `criteria`'s
1427
- * conditions.
1428
- *
1429
- * @remarks
1430
- * Like {@link count}, `aggregate` operates on STORED rows WITHOUT the
1431
- * contract guard {@link records} / {@link scan} apply — a non-conforming
1432
- * stored row still contributes to the computed aggregate when it matches
1433
- * the conditions, even though it would never appear in `records()`'s
1434
- * output.
1435
- *
1436
- * @param operation - The aggregate to compute
1437
- * @param column - The column to aggregate
1438
- * @param criteria - Optional conditions to filter by (paging is ignored)
1439
- * @param options - `{ signal }` to abort
1440
- * @returns The aggregate value, or `undefined` when undefined for the inputs
1441
- */
1442
- aggregate(operation: AggregateFunction, column: FieldPath, criteria?: Criteria, options?: ReadOptions): Promise<number | undefined>;
1443
- /**
1444
- * Stream the table's rows matching `criteria`, applying offset/limit paging.
1445
- *
1446
- * @remarks
1447
- * `criteria.limit` counts rows that pass BOTH the criteria conditions AND the
1448
- * table's contract guard (a stored row that fails the guard is skipped and
1449
- * does not count toward `limit`) — this can differ from {@link records}'s
1450
- * `limit`, which a driver's optional native `records` hook applies BEFORE
1451
- * the contract guard runs, when storage holds rows that no longer conform
1452
- * to the table's contract.
1453
- *
1454
- * @param criteria - Optional conditions plus offset/limit paging
1455
- * @param options - `{ signal }` to abort mid-stream
1456
- * @returns An async iterable of matching, guard-conforming rows
1457
- */
1458
- scan(criteria?: Criteria, options?: ReadOptions): AsyncIterable<T>;
1459
- set(row: T, options?: ReadOptions): Promise<Key>;
1460
- set(rows: readonly T[], options?: ReadOptions): Promise<readonly Key[]>;
1461
- add(row: T, options?: ReadOptions): Promise<Key>;
1462
- add(rows: readonly T[], options?: ReadOptions): Promise<readonly Key[]>;
1463
- update(key: Key, changes: Partial<T>, options?: ReadOptions): Promise<boolean>;
1464
- update(keys: readonly Key[], changes: Partial<T>, options?: ReadOptions): Promise<readonly boolean[]>;
1465
- remove(key: Key, options?: ReadOptions): Promise<boolean>;
1466
- remove(keys: readonly Key[], options?: ReadOptions): Promise<readonly boolean[]>;
1467
- clear(): Promise<void>;
1468
- query(): QueryInterface<T>;
1469
- cursor(): Promise<CursorInterface<T>>;
1381
+ * `schema` is the JSON Schema (universally portable, serializable); `columns` is
1382
+ * the source column map, which re-imports losslessly via `import` within a
1383
+ * TypeScript environment. `primary` is the primary-key column.
1384
+ */
1385
+ export declare interface TableDefinition {
1386
+ readonly primary: string;
1387
+ readonly columns: ColumnMap;
1388
+ readonly schema: JSONSchema;
1470
1389
  }
1471
1390
 
1472
1391
  /**
@@ -1474,9 +1393,6 @@ export declare class Table<T = Row> implements TableInterface<T> {
1474
1393
  * mutation moments a fire-and-forget observer (cache invalidation, sync, an audit log)
1475
1394
  * subscribes to, ALONGSIDE the database-level {@link DatabaseEventMap}.
1476
1395
  *
1477
- * @typeParam TKey - The table's primary-key type (a {@link Key}); the events carry the
1478
- * affected key so the map is `TableEventMap<TKey>`.
1479
- *
1480
1396
  * @remarks
1481
1397
  * Events carry the affected KEY only — never the row value — to keep fan-out lean and
1482
1398
  * avoid leaking row data through the observation channel; a consumer that needs the
@@ -1490,48 +1406,22 @@ export declare class Table<T = Row> implements TableInterface<T> {
1490
1406
  * transaction. Subscribe via `table.emitter.on(...)`. Declared as a `type` alias (§4.5 —
1491
1407
  * `EventMap` is a `type` kind).
1492
1408
  */
1493
- export declare type TableEventMap<TKey extends Key = Key> = {
1409
+ export declare type TableEventMap = {
1494
1410
  /** A row was written (set / added / updated) — the affected key (no value payload). */
1495
- readonly write: readonly [key: TKey];
1411
+ readonly write: readonly [key: Key];
1496
1412
  /** A row was removed — the affected key. */
1497
- readonly remove: readonly [key: TKey];
1413
+ readonly remove: readonly [key: Key];
1498
1414
  /** The table was cleared (every row removed). */
1499
1415
  readonly clear: readonly [];
1500
1416
  };
1501
1417
 
1502
- /**
1503
- * One table's portable definition, produced by `export` — the unit of schema /
1504
- * migration exchange across environments.
1505
- *
1506
- * @remarks
1507
- * `schema` is the JSON Schema (universally portable, serializable); `columns` is
1508
- * the source column map, which re-imports losslessly via `import` within a
1509
- * TypeScript environment. `key` is the primary-key column.
1510
- */
1511
- export declare interface TableExport {
1512
- readonly key: string;
1513
- readonly columns: Columns;
1514
- readonly schema: JSONSchema;
1515
- }
1516
-
1517
- /**
1518
- * Per-table secondary indexes — `{ [table]: groups }`, each group one
1519
- * (possibly compound) index of column names.
1520
- *
1521
- * @remarks
1522
- * Contracts don't express indexes, so they're declared here on `createDatabase`
1523
- * and flow into each {@link TableSchema}'s `indexes` (SQLite `CREATE INDEX`,
1524
- * IndexedDB `createIndex`). Mirrors {@link TableKeys}.
1525
- */
1526
- export declare type TableIndexes = Readonly<Record<string, readonly (readonly string[])[]>>;
1527
-
1528
1418
  /**
1529
1419
  * A table — typed keyed CRUD plus fluent query and cursor access.
1530
1420
  *
1531
1421
  * @remarks
1532
1422
  * Writes are coerced through the table's contract: a string input to a numeric
1533
1423
  * column is normalized, and a row that cannot be coerced throws `VALIDATION`. A
1534
- * row missing its key is assigned a generated UUID. `get` returns `undefined`
1424
+ * row whose primary is `undefined` receives a generated key. `get` returns `undefined`
1535
1425
  * when a key is absent; `resolve` throws `NOT_FOUND`. `set` upserts; `add`
1536
1426
  * inserts and throws `CONFLICT` on a duplicate key. `contract` exposes the
1537
1427
  * compiled contract for introspection (`schema`) and fixtures (`generate`).
@@ -1553,43 +1443,39 @@ export declare interface TableInterface<T = Row> {
1553
1443
  has(key: Key): Promise<boolean>;
1554
1444
  has(keys: readonly Key[]): Promise<readonly boolean[]>;
1555
1445
  keys(): Promise<readonly Key[]>;
1556
- records(criteria?: Criteria, options?: ReadOptions): Promise<readonly T[]>;
1446
+ records(input?: QueryInput, options?: OperationOptions): Promise<readonly T[]>;
1557
1447
  /**
1558
- * Count rows matching `criteria`'s conditions.
1448
+ * Count contract-valid rows matching `input`'s conditions.
1559
1449
  *
1560
1450
  * @remarks
1561
- * `records()` / `scan()` narrow every row through the table's contract
1562
- * guard before returning it, so a non-conforming stored row (legacy data,
1563
- * a row from before a migration) never appears in their results. `count`
1564
- * operates on STORED rows WITHOUT that guard — it counts whatever
1565
- * conditions-matches in storage, guard-conforming or not. This means
1566
- * `count()` CAN exceed `(await records(criteria)).length` when storage
1567
- * holds rows that no longer conform to the table's contract.
1451
+ * Paging is ignored. Like `records()` / `scan()`, `count()` narrows every
1452
+ * candidate through the table contract, so a non-conforming stored row does
1453
+ * not consume the count.
1568
1454
  */
1569
- count(criteria?: Criteria, options?: ReadOptions): Promise<number>;
1455
+ count(input?: QueryInput, options?: OperationOptions): Promise<number>;
1570
1456
  /**
1571
- * Compute an aggregate over `column` across rows matching `criteria`'s
1457
+ * Compute an aggregate over `column` across rows matching `input`'s
1572
1458
  * conditions.
1573
1459
  *
1574
1460
  * @remarks
1575
- * Like {@link TableInterface.count}, `aggregate` operates on STORED rows
1576
- * WITHOUT the contract guard that `records()` / `scan()` apply — a
1461
+ * Unlike {@link TableInterface.count}, `aggregate` operates on STORED rows
1462
+ * without the contract guard that `records()` / `scan()` apply — a
1577
1463
  * non-conforming stored row still contributes to the aggregate (or to the
1578
1464
  * `count` operation's tally) when it matches the conditions, even though
1579
1465
  * it would never appear in `records()`'s output.
1580
1466
  */
1581
- aggregate(operation: AggregateFunction, column: FieldPath, criteria?: Criteria, options?: ReadOptions): Promise<number | undefined>;
1467
+ aggregate(operation: AggregateOperation, column: FieldPath, input?: QueryInput, options?: OperationOptions): Promise<number | undefined>;
1582
1468
  /**
1583
1469
  * Lazy filtered iteration over the table's rows.
1584
1470
  *
1585
1471
  * @remarks
1586
- * `criteria`'s `conditions` / `offset` / `limit` are honored lazily as rows
1472
+ * `input`'s `conditions` / `offset` / `limit` are honored lazily as rows
1587
1473
  * stream; `order` is intentionally IGNORED — streaming yields driver
1588
1474
  * key-order, sorted output is `records()`'s job. Breaking out of the
1589
1475
  * iteration early closes the underlying source. The signal (if any) is
1590
1476
  * checked before each yield.
1591
1477
  */
1592
- scan(criteria?: Criteria, options?: ReadOptions): AsyncIterable<T>;
1478
+ scan(input?: QueryInput, options?: OperationOptions): AsyncIterable<T>;
1593
1479
  /**
1594
1480
  * Upsert one or more rows.
1595
1481
  *
@@ -1597,7 +1483,7 @@ export declare interface TableInterface<T = Row> {
1597
1483
  * @param options - Optional abort signal
1598
1484
  * @returns The row's key
1599
1485
  */
1600
- set(row: T, options?: ReadOptions): Promise<Key>;
1486
+ set(row: T, options?: OperationOptions): Promise<Key>;
1601
1487
  /**
1602
1488
  * Upsert one or more rows.
1603
1489
  *
@@ -1610,7 +1496,7 @@ export declare interface TableInterface<T = Row> {
1610
1496
  * surfaces as `DatabaseError` `ABORTED`. Already-applied items stay
1611
1497
  * applied — there is no rollback. Wrap in `transaction()` for atomicity.
1612
1498
  */
1613
- set(rows: readonly T[], options?: ReadOptions): Promise<readonly Key[]>;
1499
+ set(rows: readonly T[], options?: OperationOptions): Promise<readonly Key[]>;
1614
1500
  /**
1615
1501
  * Insert one or more rows, throwing `CONFLICT` on a duplicate key.
1616
1502
  *
@@ -1618,7 +1504,7 @@ export declare interface TableInterface<T = Row> {
1618
1504
  * @param options - Optional abort signal
1619
1505
  * @returns The row's key
1620
1506
  */
1621
- add(row: T, options?: ReadOptions): Promise<Key>;
1507
+ add(row: T, options?: OperationOptions): Promise<Key>;
1622
1508
  /**
1623
1509
  * Insert one or more rows, throwing `CONFLICT` on a duplicate key.
1624
1510
  *
@@ -1631,7 +1517,7 @@ export declare interface TableInterface<T = Row> {
1631
1517
  * surfaces as `DatabaseError` `ABORTED`. Already-applied items stay
1632
1518
  * applied — there is no rollback. Wrap in `transaction()` for atomicity.
1633
1519
  */
1634
- add(rows: readonly T[], options?: ReadOptions): Promise<readonly Key[]>;
1520
+ add(rows: readonly T[], options?: OperationOptions): Promise<readonly Key[]>;
1635
1521
  /**
1636
1522
  * Apply a partial change to one or more rows.
1637
1523
  *
@@ -1640,7 +1526,7 @@ export declare interface TableInterface<T = Row> {
1640
1526
  * @param options - Optional abort signal
1641
1527
  * @returns `true` when the row existed and was updated
1642
1528
  */
1643
- update(key: Key, changes: Partial<T>, options?: ReadOptions): Promise<boolean>;
1529
+ update(key: Key, changes: Partial<T>, options?: OperationOptions): Promise<boolean>;
1644
1530
  /**
1645
1531
  * Apply a partial change to one or more rows.
1646
1532
  *
@@ -1654,7 +1540,7 @@ export declare interface TableInterface<T = Row> {
1654
1540
  * surfaces as `DatabaseError` `ABORTED`. Already-applied items stay
1655
1541
  * applied — there is no rollback. Wrap in `transaction()` for atomicity.
1656
1542
  */
1657
- update(keys: readonly Key[], changes: Partial<T>, options?: ReadOptions): Promise<readonly boolean[]>;
1543
+ update(keys: readonly Key[], changes: Partial<T>, options?: OperationOptions): Promise<readonly boolean[]>;
1658
1544
  /**
1659
1545
  * Delete one or more rows.
1660
1546
  *
@@ -1662,7 +1548,7 @@ export declare interface TableInterface<T = Row> {
1662
1548
  * @param options - Optional abort signal
1663
1549
  * @returns `true` when the row existed and was removed
1664
1550
  */
1665
- remove(key: Key, options?: ReadOptions): Promise<boolean>;
1551
+ remove(key: Key, options?: OperationOptions): Promise<boolean>;
1666
1552
  /**
1667
1553
  * Delete one or more rows.
1668
1554
  *
@@ -1675,20 +1561,20 @@ export declare interface TableInterface<T = Row> {
1675
1561
  * surfaces as `DatabaseError` `ABORTED`. Already-applied items stay
1676
1562
  * applied — there is no rollback. Wrap in `transaction()` for atomicity.
1677
1563
  */
1678
- remove(keys: readonly Key[], options?: ReadOptions): Promise<readonly boolean[]>;
1564
+ remove(keys: readonly Key[], options?: OperationOptions): Promise<readonly boolean[]>;
1679
1565
  clear(): Promise<void>;
1680
1566
  query(): QueryInterface<T>;
1681
1567
  cursor(): Promise<CursorInterface<T>>;
1682
1568
  }
1683
1569
 
1684
1570
  /**
1685
- * Per-table primary-key column overrides`{ [table]: column }`.
1571
+ * A database's table schemaa map of table name to its {@link ColumnMap}.
1686
1572
  *
1687
1573
  * @remarks
1688
- * A table absent from this map keys its rows by {@link DEFAULT_PRIMARY} (`id`).
1689
- * Kept separate from {@link TablesShape} so the table map stays purely columns.
1574
+ * Each table's row type is `Infer` of its columns (see {@link RowOf}); primary-key
1575
+ * columns are named separately via {@link PrimaryMap}.
1690
1576
  */
1691
- export declare type TableKeys = Readonly<Record<string, string>>;
1577
+ export declare type TableMap = Readonly<Record<string, ColumnMap>>;
1692
1578
 
1693
1579
  /**
1694
1580
  * A backend-agnostic description of one table — what `open` hands each driver so a
@@ -1696,7 +1582,7 @@ export declare type TableKeys = Readonly<Record<string, string>>;
1696
1582
  *
1697
1583
  * @remarks
1698
1584
  * Derived by the database from its `tables` contract shapes ({@link ColumnSchema}
1699
- * per column, via `shapeToColumnType`), its `keys` (`primary`), and its `indexes` option
1585
+ * per column, via `shapeToColumnStorage`), its `primary`, and its `indexes` option
1700
1586
  * (`indexes`, each entry one possibly-compound index of column names). A scan-only
1701
1587
  * backend (the reference `MemoryDriver`) ignores everything but `name`.
1702
1588
  */
@@ -1708,64 +1594,17 @@ export declare interface TableSchema {
1708
1594
  }
1709
1595
 
1710
1596
  /**
1711
- * A database's table schema a map of table name to its {@link Columns}.
1712
- *
1713
- * @remarks
1714
- * Each table's row type is `Infer` of its columns (see {@link RowOf}); primary-key
1715
- * columns are named separately via {@link TableKeys}.
1716
- */
1717
- export declare type TablesShape = Readonly<Record<string, Columns>>;
1718
-
1719
- /**
1720
- * The handle a driver's native `transaction` hook returns.
1597
+ * Validate the paging fields of a portable query.
1721
1598
  *
1722
1599
  * @remarks
1723
- * `commit` finalizes the native BEGIN; `rollback` undoes it. When a driver
1724
- * implements {@link DriverInterface.transaction}, the engine uses this handle
1725
- * instead of the snapshot-based rollback floor.
1726
- */
1727
- export declare interface TransactionInterface {
1728
- commit(): Promise<void>;
1729
- rollback(): Promise<void>;
1730
- }
1731
-
1732
- /** The number of bytes encoded by an RFC 4122 UUID. */
1733
- export declare const UUID_BYTE_COUNT = 16;
1734
-
1735
- /** The number of distinct values one UUID byte may hold. */
1736
- export declare const UUID_BYTE_RANGE = 256;
1737
-
1738
- /**
1739
- * Match a value against a wildcard pattern in LINEAR time — the shared, ReDoS-SAFE
1740
- * engine behind {@link likeMatch} and {@link globMatch}.
1741
- *
1742
- * @remarks
1743
- * A backtracking RegExp (`a%b%c` → `^a.*b.*c$`) is CATASTROPHIC on a hostile pattern:
1744
- * `.*` segments separated by literals, matched against a long non-matching input, blow
1745
- * up super-linearly — and JS has no atomic groups / possessive quantifiers to bound it
1746
- * (AGENTS §6.5, now that the authed server runs model-supplied `list` criteria over the
1747
- * wire). So this builds NO regex. It runs the classic GREEDY TWO-POINTER wildcard match:
1748
- * the `any` wildcard records its position and, on a later mismatch, backtracks ONLY to
1749
- * that last `any` (letting it absorb one more char) — so the work is O(value × pattern),
1750
- * never the exponential / polynomial backtracking a regex would do. The pattern length
1751
- * is capped at {@link MAX_PATTERN_LENGTH} (a `VALIDATION` {@link DatabaseError} over it),
1752
- * bounding the pattern factor so a match stays linear in the value length whatever the
1753
- * pattern.
1600
+ * A present `limit` or `offset` must be a finite nonnegative integer; zero is
1601
+ * valid. Validation is deterministic (`limit` before `offset`). Non-finite
1602
+ * values are rendered as strings in error context so JSON serialization cannot
1603
+ * collapse `NaN` or infinity to `null`.
1754
1604
  *
1755
- * The `any` wildcard matches any run (including empty); `single` matches exactly one
1756
- * char; every other pattern char matches itself LITERALLY (a pattern `.` / `(` / `\` is
1757
- * a literal — the regex-metacharacter hazard is gone with the regex). `any` is tested
1758
- * BEFORE a literal match, so a value that literally contains the wildcard char never
1759
- * shadows the wildcard. Case folding is applied to BOTH sides when `fold` is set.
1760
- *
1761
- * @param value - The value to test
1762
- * @param pattern - The wildcard pattern
1763
- * @param any - The any-run wildcard char (`%` for `LIKE`, `*` for `GLOB`)
1764
- * @param single - The single-char wildcard char (`_` for `LIKE`, `?` for `GLOB`)
1765
- * @param fold - Whether to match case-INSENSITIVELY (`LIKE` folds; `GLOB` does not)
1766
- * @returns Whether `value` matches `pattern`
1767
- * @throws A `VALIDATION` {@link DatabaseError} when `pattern` exceeds {@link MAX_PATTERN_LENGTH}
1768
- */
1769
- export declare function wildcardMatch(value: string, pattern: string, any: string, single: string, fold: boolean): boolean;
1605
+ * @param input - The portable query whose paging fields to validate
1606
+ * @throws {@link DatabaseError} `VALIDATION` when a paging field is invalid
1607
+ */
1608
+ export declare function validatePage(input?: QueryInput): void;
1770
1609
 
1771
- export { }
1610
+ export { }