@orkestrel/database 0.0.1 → 0.0.2

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