@orkestrel/database 0.0.1 → 0.0.3

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.
@@ -1,11 +1,1766 @@
1
- export type * from './types.js';
2
- export * from './constants.js';
3
- export * from './errors.js';
4
- export * from './helpers.js';
5
- export * from './factories.js';
6
- export * from './Database.js';
7
- export * from './drivers/MemoryDriver.js';
8
- export * from './Table.js';
9
- export * from './Query.js';
10
- export * from './Clause.js';
11
- export * from './Cursor.js';
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
+ import { RandomFunction } from '@orkestrel/contract';
10
+
11
+ /** An aggregate computed over a numeric column. */
12
+ export declare type AggregateFunction = 'count' | 'sum' | 'average' | 'minimum' | 'maximum';
13
+
14
+ /**
15
+ * Apply a {@link Criteria} to rows — filter, then sort, then page.
16
+ *
17
+ * @remarks
18
+ * The whole portable read pipeline in one place: conditions filter, `order`
19
+ * 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
21
+ * lean on this rather than each re-deriving it.
22
+ *
23
+ * @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
25
+ * @returns The filtered, sorted, paged rows
26
+ */
27
+ export declare function applyCriteria(rows: readonly Row[], criteria?: Criteria): readonly Row[];
28
+
29
+ /**
30
+ * Run the FULL driver-conformance battery and collect every violation — the
31
+ * audit entry point for a driver author who wants a complete report rather
32
+ * than a single fail-fast throw.
33
+ *
34
+ * @remarks
35
+ * Drains {@link driverFindings} to completion: every phase runs regardless
36
+ * of earlier violations, so a driver breaking two independent invariants
37
+ * reports both. An empty array means the driver is fully conformant.
38
+ *
39
+ * @param factory - Mints a fresh, unopened driver instance (called once per phase)
40
+ * @returns Every violated invariant found, in phase order (empty when fully conformant)
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * import { auditDriver, createMemoryDriver } from '@orkestrel/database'
45
+ *
46
+ * const findings = await auditDriver(() => createMemoryDriver())
47
+ * for (const finding of findings) console.log(`${finding.check}: ${finding.message}`)
48
+ * ```
49
+ */
50
+ export declare function auditDriver(factory: () => DriverInterface): Promise<readonly ConformanceFinding[]>;
51
+
52
+ /**
53
+ * Throw when an {@link ReadOptions.signal | AbortSignal} has fired — the shared
54
+ * cancellation gate checked at operation boundaries and between streamed rows.
55
+ *
56
+ * @remarks
57
+ * A no-op for `undefined` or a live signal, so callers thread `options?.signal`
58
+ * straight through. When the signal has aborted, throws an `ABORTED`
59
+ * {@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`).
62
+ *
63
+ * @param signal - The signal to check, if any
64
+ * @returns Nothing — returns normally while the signal is live
65
+ * @throws An `ABORTED` {@link DatabaseError} when the signal has aborted
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * import { checkAbort } from '@orkestrel/database'
70
+ *
71
+ * const controller = new AbortController()
72
+ * checkAbort(controller.signal) // returns
73
+ * controller.abort('too slow')
74
+ * checkAbort(controller.signal) // throws DatabaseError('ABORTED', …)
75
+ * ```
76
+ */
77
+ export declare function checkAbort(signal: AbortSignal | undefined): void;
78
+
79
+ /**
80
+ * A pending condition opened by a query's `where` / `and` / `or`.
81
+ *
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.
87
+ */
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
+ }
107
+
108
+ /**
109
+ * A pending condition opened by `where` / `and` / `or`.
110
+ *
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
+ }
133
+
134
+ /**
135
+ * One table's columns — a map of column name to its value {@link ContractShape}.
136
+ *
137
+ * @remarks
138
+ * This is exactly the property map an `objectShape` takes. A table row is always
139
+ * an object, so you write the columns directly (`{ id: stringShape(), … }`) and
140
+ * the database wraps them in an `objectShape` for you — no redundant `objectShape`
141
+ * at the table level. (Nested object *columns* still use `objectShape`, since a
142
+ * column is not always an object.)
143
+ */
144
+ export declare type Columns = Readonly<Record<string, ContractShape>>;
145
+
146
+ /**
147
+ * One column of a {@link TableSchema} — its name, portable {@link ColumnType}, and
148
+ * whether it is nullable (its shape is `optionalShape` / `nullableShape`).
149
+ */
150
+ export declare interface ColumnSchema {
151
+ readonly name: string;
152
+ readonly type: ColumnType;
153
+ readonly nullable: boolean;
154
+ }
155
+
156
+ /**
157
+ * A portable storage type for a column — the backend maps it to its native type
158
+ * (SQLite affinity, an IndexedDB value). Derived from a column's `ContractShape`
159
+ * by `shapeToColumnType`; `json` covers object/array/union/raw values a backend stores
160
+ * as JSON text and can `json_extract` for nested-field queries.
161
+ */
162
+ export declare type ColumnType = 'text' | 'integer' | 'real' | 'boolean' | 'json' | 'blob';
163
+
164
+ /**
165
+ * A total ordering over arbitrary values — the comparator behind sorting and the
166
+ * range operators.
167
+ *
168
+ * @remarks
169
+ * Values of different types order by a fixed type rank (`undefined` < `null` <
170
+ * boolean < number < string < other); same-typed values compare naturally.
171
+ * `NaN` sorts after every other number and equal to itself, so the comparator
172
+ * is total and never returns `NaN`.
173
+ *
174
+ * @param left - The left value
175
+ * @param right - The right value
176
+ * @returns `-1`, `0`, or `1`
177
+ */
178
+ export declare function compareValues(left: unknown, right: unknown): number;
179
+
180
+ /**
181
+ * Compute an aggregate over a column across rows.
182
+ *
183
+ * @remarks
184
+ * `count` returns the row count. The numeric aggregates coerce each cell with
185
+ * the contracts `parseNumber` (so `'42'` counts) and ignore non-numeric cells;
186
+ * over zero numeric values they return `undefined` — the SQL `NULL` of an empty
187
+ * aggregate.
188
+ *
189
+ * @param rows - The rows to aggregate (non-record entries are ignored)
190
+ * @param operation - The aggregate to compute
191
+ * @param column - The column to aggregate
192
+ * @returns The aggregate value, or `undefined` when undefined for the inputs
193
+ */
194
+ export declare function computeAggregate(rows: readonly unknown[], operation: AggregateFunction, column: FieldPath): number | undefined;
195
+
196
+ /**
197
+ * One compiled WHERE condition.
198
+ *
199
+ * @remarks
200
+ * `values` carries the operands the operator needs — none for `absent` /
201
+ * `present`, one for most, two for `between`, a list for `any` / `none`.
202
+ * `connector` folds this condition into the accumulated result left-to-right;
203
+ * the first condition's connector seeds the fold and is otherwise ignored.
204
+ * `column` is a {@link FieldPath}: a single string is ONE column (never split on
205
+ * `.`), an array descends into a nested (object/`json`) value.
206
+ */
207
+ export declare interface Condition {
208
+ readonly column: FieldPath;
209
+ readonly operator: ConditionOperator;
210
+ readonly values: readonly unknown[];
211
+ readonly connector: Connector;
212
+ }
213
+
214
+ /**
215
+ * A WHERE operator — the comparison a single {@link Condition} applies.
216
+ *
217
+ * @remarks
218
+ * Each maps to a SQL operator and an IndexedDB read strategy (a key range where
219
+ * the bounds allow it, a scanned predicate otherwise). The set is closed to
220
+ * comparisons expressible on both backends.
221
+ */
222
+ export declare type ConditionOperator = 'equals' | 'not' | 'above' | 'below' | 'from' | 'to' | 'between' | 'like' | 'glob' | 'starts' | 'ends' | 'any' | 'none' | 'absent' | 'present';
223
+
224
+ /**
225
+ * One violated invariant from the driver-conformance battery.
226
+ *
227
+ * @remarks
228
+ * Mirrors the payload shape of a `DatabaseError` `CONFORMANCE` `context` —
229
+ * `check` names the invariant, `message` describes the violation, and
230
+ * `context` carries the offending table / key / value that failed it.
231
+ */
232
+ export declare interface ConformanceFinding {
233
+ readonly check: string;
234
+ readonly message: string;
235
+ readonly context: Readonly<Record<string, unknown>>;
236
+ }
237
+
238
+ /**
239
+ * Run the driver-conformance battery, throwing on the first violated
240
+ * invariant — the fail-fast entry point most callers (test setup, CI smoke
241
+ * checks) want.
242
+ *
243
+ * @remarks
244
+ * A thin driver over {@link driverFindings}: because that generator is
245
+ * lazy, consuming only its first yielded value means every LATER phase
246
+ * never runs — true fail-fast, not merely "report only the first". The
247
+ * thrown error is byte-compatible with the historical shape: a
248
+ * `CONFORMANCE` {@link DatabaseError} whose `message` is the finding's
249
+ * `message` and whose `context` is `{ check, ...finding.context }`.
250
+ *
251
+ * @param factory - Mints a fresh, unopened driver instance (called once per phase)
252
+ * @returns Nothing — resolves once every phase has passed
253
+ * @throws A `CONFORMANCE` {@link DatabaseError} on the first violated invariant
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * import { conformDriver, createMemoryDriver } from '@orkestrel/database'
258
+ *
259
+ * await conformDriver(() => createMemoryDriver()) // resolves when every invariant holds
260
+ * ```
261
+ */
262
+ export declare function conformDriver(factory: () => DriverInterface): Promise<void>;
263
+
264
+ /** How a {@link Condition} joins to the running result of the conditions before it. */
265
+ export declare type Connector = 'and' | 'or';
266
+
267
+ /**
268
+ * Create a database over a driver and a declared `tables` schema.
269
+ *
270
+ * @remarks
271
+ * `tables` maps each name to its columns (a `column → shape` map); the database
272
+ * wraps each in an `objectShape`, so you never write `objectShape` at the table
273
+ * level. The `const` type parameter captures the literal names and columns, so
274
+ * `db.table('users')` is checked against the schema and typed by `Infer` of its
275
+ * columns — no annotations. Name a non-`id` primary-key column per table via the
276
+ * optional `keys` map.
277
+ *
278
+ * @param options - The driver, the `tables` column map, optional `keys`, and an
279
+ * optional `name`
280
+ * @returns A typed {@link DatabaseInterface}
281
+ *
282
+ * @example
283
+ * ```ts
284
+ * import { createDatabase, createMemoryDriver } from '@orkestrel/database'
285
+ * import { integerShape, stringShape } from '@orkestrel/contract'
286
+ *
287
+ * const db = createDatabase({
288
+ * driver: createMemoryDriver(),
289
+ * tables: {
290
+ * users: { id: stringShape(), age: integerShape() },
291
+ * posts: { slug: stringShape(), title: stringShape() },
292
+ * },
293
+ * keys: { posts: 'slug' },
294
+ * })
295
+ * await db.table('users').set({ id: 'u1', age: 36 }) // typed; coerced + validated
296
+ * ```
297
+ */
298
+ export declare function createDatabase<const T extends TablesShape>(options: DatabaseOptions<T>): DatabaseInterface<T>;
299
+
300
+ /**
301
+ * Create the in-memory reference {@link DriverInterface}.
302
+ *
303
+ * @remarks
304
+ * Backed by nested maps with no I/O — the same driver runs in a browser or on a
305
+ * server, making it the natural choice for tests and ephemeral storage.
306
+ *
307
+ * @returns A fresh in-memory driver
308
+ */
309
+ export declare function createMemoryDriver(): DriverInterface;
310
+
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
+ /**
348
+ * A forward row cursor for bulk in-place mutation.
349
+ *
350
+ * @remarks
351
+ * Iterates a snapshot of the table's keys taken at creation; `update` and
352
+ * `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.
354
+ */
355
+ export declare interface CursorInterface<T = Row> {
356
+ readonly value: T | undefined;
357
+ readonly index: number;
358
+ readonly done: boolean;
359
+ next(): Promise<void>;
360
+ update(changes: Partial<T>): Promise<void>;
361
+ remove(): Promise<void>;
362
+ close(): void;
363
+ }
364
+
365
+ /**
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.
377
+ *
378
+ * @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> {
395
+ #private;
396
+ constructor(options: DatabaseOptions<T>);
397
+ get emitter(): EmitterInterface<DatabaseEventMap>;
398
+ get name(): string;
399
+ get status(): DatabaseStatus;
400
+ 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>>;
403
+ open(): Promise<void>;
404
+ 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>;
442
+ }
443
+
444
+ /**
445
+ * An error thrown by the database layer.
446
+ *
447
+ * @remarks
448
+ * Carries a {@link DatabaseErrorCode} and an optional `context` bag naming the
449
+ * offending table / key. Thrown for: operating on a closed database (`CLOSED`), a
450
+ * `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
453
+ * `context`), an inapplicable {@link Migration} plan (`MIGRATION`), a
454
+ * driver that violates a {@link DriverInterface} invariant, thrown by the
455
+ * `conformDriver` helper (`CONFORMANCE`), and an unexpected infrastructure
456
+ * fault surfaced by a driver seam — e.g. a filesystem failure while
457
+ * persisting (`DRIVER`) — as opposed to expected domain conditions, which
458
+ * keep their specific codes.
459
+ */
460
+ export declare class DatabaseError extends Error {
461
+ readonly code: DatabaseErrorCode;
462
+ readonly context?: Readonly<Record<string, unknown>>;
463
+ constructor(code: DatabaseErrorCode, message: string, context?: Readonly<Record<string, unknown>>);
464
+ }
465
+
466
+ /** A machine-readable {@link DatabaseError} code. */
467
+ export declare type DatabaseErrorCode = 'CLOSED' | 'NOT_FOUND' | 'CONFLICT' | 'VALIDATION' | 'ABORTED' | 'MIGRATION' | 'CONFORMANCE' | 'DRIVER';
468
+
469
+ /**
470
+ * The push observation surface of a {@link DatabaseInterface} (AGENTS §13) — the
471
+ * connection + transaction lifecycle a fire-and-forget observer (logging, metrics,
472
+ * tracing, cache invalidation) subscribes to.
473
+ *
474
+ * @remarks
475
+ * Pure signals carrying no row data — these are the database-level (not per-row)
476
+ * moments, so a non-generic map stays lean (per-row writes are {@link TableEventMap}).
477
+ * Listener isolation is the emitter's (AGENTS §13): every event is emitted directly and a
478
+ * listener throw is routed to the emitter's OWN `error` handler (the `error` option), never
479
+ * onto this domain map and never into the snapshot / commit / rollback flow — so a buggy
480
+ * 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(...)`.
484
+ *
485
+ * Declared as a `type` alias (not `interface extends EventMap`, §4.5 — `EventMap` is a
486
+ * `type` kind): a type-literal satisfies the `EventMap` constraint
487
+ * (`Record<string, readonly unknown[]>`) structurally, whereas an interface lacks the
488
+ * required index signature.
489
+ */
490
+ export declare type DatabaseEventMap = {
491
+ /** The driver connected (`open`, or the lazy first-use connect completed). */
492
+ readonly open: readonly [];
493
+ /** The database was closed (the driver released). */
494
+ readonly close: readonly [];
495
+ /** A transaction scope began — the store was snapshotted, the scope is about to run. */
496
+ readonly transaction: readonly [];
497
+ /** A transaction scope completed successfully (no rollback). */
498
+ readonly commit: readonly [];
499
+ /** A transaction scope threw and every table was rolled back — the propagated error. */
500
+ readonly rollback: readonly [error: unknown];
501
+ /** A {@link Migration} plan was applied via `migrate` — the applied plan. */
502
+ readonly migrate: readonly [migration: Migration];
503
+ };
504
+
505
+ /**
506
+ * A database — the ergonomic entry point that owns the driver and its tables.
507
+ *
508
+ * @remarks
509
+ * A database is a typed view over a set of tables on one driver. Tables are
510
+ * declared up front in `createDatabase({ tables })` and reached, fully typed,
511
+ * with `table(name)`. The driver connects lazily on first use, so a freshly
512
+ * created database is immediately usable. `import` defines more than one table
513
+ * from a shape map and returns a new typed view of **those** tables over the
514
+ * 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.
520
+ */
521
+ export declare interface DatabaseInterface<T extends TablesShape = TablesShape> {
522
+ readonly emitter: EmitterInterface<DatabaseEventMap>;
523
+ readonly name: string;
524
+ readonly status: DatabaseStatus;
525
+ 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>>;
528
+ open(): Promise<void>;
529
+ close(): Promise<void>;
530
+ transaction<R>(scope: () => Promise<R>, options?: ReadOptions): Promise<R>;
531
+ /**
532
+ * Diff a caller-supplied deployed schema against this database's declared
533
+ * schema (its `tables`, as configured) via `planMigration`, apply the
534
+ * resulting plan through the driver's optional `migrate` hook, and return
535
+ * the applied plan.
536
+ *
537
+ * @param deployed - The schema currently deployed, as {@link TableSchema}s
538
+ * @param options - Optional abort signal, checked at entry
539
+ * @returns The applied {@link Migration} plan
540
+ *
541
+ * @remarks
542
+ * Throws `DatabaseError` `MIGRATION` when the driver does not implement
543
+ * `migrate`, or when a step references an unknown table (propagated from
544
+ * 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.
548
+ */
549
+ migrate(deployed: readonly TableSchema[], options?: ReadOptions): Promise<Migration>;
550
+ }
551
+
552
+ /**
553
+ * Options for `createDatabase`.
554
+ *
555
+ * @remarks
556
+ * `driver` is the storage backend; `tables` declares each table's columns;
557
+ * `keys` overrides the primary-key column per table ({@link DEFAULT_PRIMARY}
558
+ * otherwise); `indexes` declares secondary indexes per table (contracts don't
559
+ * express them) that flow into each derived {@link TableSchema}; `name` labels
560
+ * the database; `on` wires initial {@link DatabaseEventMap} listeners (§8); `error`
561
+ * 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).
565
+ */
566
+ export declare interface DatabaseOptions<T extends TablesShape = TablesShape> {
567
+ readonly on?: EmitterHooks<DatabaseEventMap>;
568
+ /** The emitter's listener-error handler (AGENTS §13) — a listener throw routes here, not to a domain event. */
569
+ readonly error?: EmitterErrorHandler;
570
+ readonly driver: DriverInterface;
571
+ readonly tables: T;
572
+ readonly keys?: TableKeys;
573
+ readonly indexes?: TableIndexes;
574
+ readonly name?: string;
575
+ readonly key?: KeyFunction;
576
+ /**
577
+ * The declared schema version.
578
+ *
579
+ * @remarks
580
+ * Only meaningful when the driver implements BOTH {@link DriverInterface.meta}
581
+ * and {@link DriverInterface.stamp} (a versioning driver); unset, or a
582
+ * non-versioning driver, leaves `open()` unchanged from today's behavior.
583
+ * 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.
588
+ * - **Stored version < `version`** — `planMigration(stored.schema, declared
589
+ * schema)` computes the upgrade plan, applied via the driver's optional
590
+ * `migrate` hook. If `migrate` is absent and the plan is non-empty,
591
+ * `open()` throws `DatabaseError` `MIGRATION`. On success, `open()`
592
+ * `stamp`s the new `{ version, schema }` and emits the `migrate` event.
593
+ * - **Stored version > `version`** — the store is newer than the declared
594
+ * schema; `open()` throws `DatabaseError` `MIGRATION`.
595
+ * - **Stored version === `version`** — no-op.
596
+ *
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.
602
+ */
603
+ readonly version?: number;
604
+ }
605
+
606
+ /** The lifecycle state of a {@link DatabaseInterface}. */
607
+ export declare type DatabaseStatus = 'idle' | 'open' | 'closed';
608
+
609
+ /**
610
+ * Structural equality by SameValueZero leaves — the comparator behind conformance
611
+ * checks and any test/fixture that needs "same data", not "same reference".
612
+ *
613
+ * @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
+ * ```
634
+ */
635
+ export declare function deepEqual(left: unknown, right: unknown): boolean;
636
+
637
+ /**
638
+ * The primary-key column assumed when {@link TableKeys} does not name one.
639
+ *
640
+ * @remarks
641
+ * `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`.
643
+ */
644
+ export declare const DEFAULT_PRIMARY = "id";
645
+
646
+ /** A sort direction. */
647
+ export declare type Direction = 'ascending' | 'descending';
648
+
649
+ /**
650
+ * Run the driver-conformance battery against a fresh {@link DriverInterface}
651
+ * per phase, yielding one {@link ConformanceFinding} per violated invariant —
652
+ * the shared invariant suite every backend (in-memory, SQLite, IndexedDB)
653
+ * must uphold to be a drop-in {@link DriverInterface}.
654
+ *
655
+ * @remarks
656
+ * Framework-agnostic: no test-runner or Node imports, only sibling core
657
+ * modules — so it runs equally from a unit test, a smoke script, or a new
658
+ * driver's own README. Opens a fixed two-table schema (`users` keyed by the
659
+ * default `id`, `posts` keyed by a non-id `slug`) and, calling `factory()`
660
+ * fresh for each phase so failures stay isolated, verifies: `open`/`close`;
661
+ * `read` of a missing key returns `undefined`; `write`/`read` round-trip with
662
+ * DEEP copy-in/copy-out isolation (mutating the caller's row — including a
663
+ * NESTED field — after `write`, or a row `read` returns, never perturbs
664
+ * stored state) and upsert-overwrite; `delete` returns `true` then `false`;
665
+ * `keys`/`scan` yield in ascending key order; `clear` empties only its target
666
+ * table; `snapshot`'s rollback thunk restores pre-snapshot state, including a
667
+ * NESTED field mutated in place on a read-back row between capture and
668
+ * restore; a scoped `snapshot(['users'])` rolls back only the named table,
669
+ * leaving a concurrent mutation to another table intact; a
670
+ * non-`id` primary key (`posts.slug`) round-trips; a nested-object row
671
+ * round-trips structurally (via {@link deepEqual}). The optional surface is
672
+ * presence-gated: when `migrate` exists, a `column.remove` plan strips the
673
+ * column from stored rows and a plan referencing an unknown table throws
674
+ * `DatabaseError` `MIGRATION`; when `stream` exists, it yields only
675
+ * 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.
679
+ *
680
+ * Each phase runs within a `try`/`catch`: an EXPECTED mismatch yields a
681
+ * finding built from the assertion, while an UNEXPECTED throw (a driver
682
+ * crash mid-phase) is caught and yielded as a finding too, naming the phase
683
+ * as `check` and carrying the caught error in `context.error` — a broken
684
+ * driver can never escape the battery as an unhandled rejection. Within a
685
+ * phase, the FIRST violated assertion yields and the phase stops (matching
686
+ * the historical fail-fast shape at phase granularity); the generator then
687
+ * moves on to the next phase regardless. Because this is a **generator**,
688
+ * consuming only the first yielded value reproduces true fail-fast (later
689
+ * phases never run) — that is exactly what {@link conformDriver} does.
690
+ *
691
+ * @param factory - Mints a fresh, unopened driver instance (called once per phase)
692
+ * @yields One {@link ConformanceFinding} per violated invariant, in phase order
693
+ *
694
+ * @example
695
+ * ```ts
696
+ * import { createMemoryDriver, driverFindings } from '@orkestrel/database'
697
+ *
698
+ * for await (const finding of driverFindings(() => createMemoryDriver())) {
699
+ * console.log(finding.check, finding.message)
700
+ * }
701
+ * ```
702
+ */
703
+ export declare function driverFindings(factory: () => DriverInterface): AsyncIterable<ConformanceFinding>;
704
+
705
+ /**
706
+ * The storage primitive every backend implements — the whole of the bridge.
707
+ *
708
+ * @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
712
+ * here: all of that is one query engine in the core (`helpers.ts`) running over
713
+ * `scan`, so a new backend implements a handful of tiny methods rather than
714
+ * re-deriving WHERE compilation. `open` now receives a derived
715
+ * {@link TableSchema}`[]` (columns, types, primary, indexes) so a native backend
716
+ * can build real tables and indexes; a scan-only backend reads only `name`. The
717
+ * optional `records?` / `count?` / `aggregate?` are native overrides the engine
718
+ * falls back from (AGENTS §21). The API is async (Promises) because IndexedDB is; synchronous
719
+ * backends resolve immediately. Lookups that may miss return `undefined` /
720
+ * `false` rather than throwing (AGENTS §12).
721
+ */
722
+ export declare interface DriverInterface {
723
+ open(schema: readonly TableSchema[]): Promise<void>;
724
+ 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
+ /**
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).
735
+ *
736
+ * @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.
740
+ */
741
+ snapshot(tables?: readonly string[]): Promise<() => Promise<void>>;
742
+ /**
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.
767
+ */
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>;
793
+ }
794
+
795
+ /**
796
+ * Persisted schema metadata a versioning driver stores verbatim and returns on
797
+ * demand.
798
+ *
799
+ * @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.
804
+ */
805
+ export declare interface DriverMeta {
806
+ readonly version: number;
807
+ readonly schema: readonly TableSchema[];
808
+ }
809
+
810
+ /**
811
+ * Read a row's primary key from a column, when it is a usable {@link Key}.
812
+ *
813
+ * @param row - The row to read
814
+ * @param column - The primary-key column name
815
+ * @returns The key (a string or finite number), or `undefined`
816
+ */
817
+ export declare function extractKey(row: Row, column: string): Key | undefined;
818
+
819
+ /**
820
+ * 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}).
822
+ *
823
+ * @remarks
824
+ * An empty condition list matches every row (returned as-is, no copy). Folds
825
+ * each row through {@link matchesCriteria}.
826
+ *
827
+ * @param rows - The rows to filter
828
+ * @param conditions - The conditions to apply (empty matches everything)
829
+ * @returns The matching rows
830
+ *
831
+ * @example
832
+ * ```ts
833
+ * filterRows(
834
+ * [{ age: 30 }, { age: 12 }],
835
+ * [{ column: 'age', operator: 'above', values: [18], connector: 'and' }],
836
+ * ) // => [{ age: 30 }]
837
+ * ```
838
+ */
839
+ export declare function filterRows(rows: readonly Row[], conditions: readonly Condition[]): readonly Row[];
840
+
841
+ /**
842
+ * Generate an RFC 4122 version 4 UUID from a number source — no host crypto global.
843
+ *
844
+ * @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
+ * ```
867
+ */
868
+ export declare function generateUUID(random?: RandomFunction): string;
869
+
870
+ export declare function globMatch(value: string, pattern: string): boolean;
871
+
872
+ /**
873
+ * Narrow an unknown caught value to a {@link DatabaseError}.
874
+ *
875
+ * @param value - The value to test (typically a `catch` binding)
876
+ * @returns `true` when `value` is a {@link DatabaseError}
877
+ *
878
+ * @example
879
+ * ```ts
880
+ * try {
881
+ * await users.add(row)
882
+ * } catch (error) {
883
+ * if (isDatabaseError(error) && error.code === 'CONFLICT') await users.set(row)
884
+ * }
885
+ * ```
886
+ */
887
+ export declare function isDatabaseError(value: unknown): value is DatabaseError;
888
+
889
+ /**
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`).
894
+ *
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.
902
+ *
903
+ * @param value - The value to test
904
+ * @returns `true` when `value` is a well-formed `DriverMeta`
905
+ *
906
+ * @example
907
+ * ```ts
908
+ * isDriverMeta({ version: 1, schema: [] }) // true
909
+ * isDriverMeta({ version: 1, schema: [{ name: 'users' }] }) // false
910
+ * ```
911
+ */
912
+ export declare function isDriverMeta(value: unknown): value is DriverMeta;
913
+
914
+ /**
915
+ * A primary key — the value identifying a row within its table.
916
+ *
917
+ * @remarks
918
+ * `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.
921
+ */
922
+ export declare type Key = string | number;
923
+
924
+ /**
925
+ * A caller-supplied key minting function.
926
+ *
927
+ * @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.
932
+ */
933
+ export declare type KeyFunction = () => Key;
934
+
935
+ export declare function likeMatch(value: string, pattern: string): boolean;
936
+
937
+ /**
938
+ * Evaluate one {@link Condition} against a row — the per-operator predicate.
939
+ *
940
+ * @remarks
941
+ * Reads the condition's column — a `FieldPath`, resolved with `resolveField` (a
942
+ * string is one column; an array descends a nested value) — and applies the
943
+ * operator. Range operators (`above` / `below` / `from` / `to` / `between`) use
944
+ * {@link compareValues}, the total order; the equality family (`equals` / `not`
945
+ * / `any` / `none`) uses {@link deepEqual} — STRUCTURAL equality, not the total
946
+ * order's rank-5-collapses-all-objects behavior, so `equals` on an object/array
947
+ * operand only matches a structurally-equal value, never every row holding any
948
+ * object. This is a semantics change from ranking: `deepEqual` is SameValueZero
949
+ * on leaves, so `NaN` now equals `NaN` under `equals` / `any` (it never matched
950
+ * anything under the old rank-based comparison). `like` / `glob` / `starts` /
951
+ * `ends` match only strings; `absent` / `present` test nullishness. Total — a
952
+ * type mismatch is simply a non-match.
953
+ *
954
+ * @param row - The row to test
955
+ * @param condition - The condition to apply
956
+ * @returns Whether the row satisfies the condition
957
+ */
958
+ export declare function matchesCondition(row: Row, condition: Condition): boolean;
959
+
960
+ /**
961
+ * Fold a row through a list of conditions, joining each by its connector.
962
+ *
963
+ * @remarks
964
+ * Evaluated left-to-right: the first condition seeds the result, and each later
965
+ * condition combines with `&&` (`and`) or `||` (`or`). An empty list matches
966
+ * every row. There is no operator precedence — conditions combine in the order
967
+ * the query builder recorded them.
968
+ *
969
+ * @param row - The row to test
970
+ * @param conditions - The conditions to fold
971
+ * @returns Whether the row satisfies the combined conditions
972
+ */
973
+ export declare function matchesCriteria(row: Row, conditions: readonly Condition[]): boolean;
974
+
975
+ /**
976
+ * The longest `LIKE` / `GLOB` pattern the wildcard matcher accepts before rejecting it.
977
+ *
978
+ * @remarks
979
+ * 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
981
+ * patterns. The matcher is the LINEAR greedy two-pointer wildcard match — never a
982
+ * backtracking regex (`.*`-segments-separated-by-literals against a long input is the
983
+ * catastrophic shape JS cannot bound without atomic groups), so it is O(value ×
984
+ * pattern). Capping the pattern length bounds that pattern factor, leaving a match
985
+ * linear in the value length whatever the pattern. A longer pattern throws a
986
+ * `VALIDATION` {@link DatabaseError}; the cap is generous for any legitimate search.
987
+ */
988
+ export declare const MAX_PATTERN_LENGTH = 1024;
989
+
990
+ /**
991
+ * The reference {@link DriverInterface} — nested maps, no I/O.
992
+ *
993
+ * @remarks
994
+ * The in-between made concrete: it runs identically in a browser or on a server,
995
+ * so it is the storage behind tests, ephemeral caches, and any code that wants
996
+ * the database API without a persistent backend. Rows are DEEP-copied (via
997
+ * `structuredClone`) in and out — at `write`, `read`, `scan`, `stream`, and both
998
+ * snapshot capture and restore — so a caller mutating a nested field of an input
999
+ * row, a returned row, or a row mutated in place between snapshot and rollback
1000
+ * can never perturb stored state (AGENTS §11); a shallow `{ ...row }` spread
1001
+ * would still share nested object/array references. `snapshot`
1002
+ * clones every table to give transactions an exact rollback point. `scan` and
1003
+ * `keys` yield in key order — sorted by the core {@link compareValues} total
1004
+ * order, the same contract the SQLite (`ORDER BY`) and IndexedDB (key-ordered
1005
+ * reads) backends honor, so an unordered read agrees across every backend rather
1006
+ * than leaking Map insertion order. A persistent backend (IndexedDB, SQLite)
1007
+ * implements the same nine methods over real storage.
1008
+ */
1009
+ export declare class MemoryDriver implements DriverInterface {
1010
+ #private;
1011
+ open(schema: readonly TableSchema[]): Promise<void>;
1012
+ close(): Promise<void>;
1013
+ 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>;
1016
+ keys(table: string): Promise<readonly Key[]>;
1017
+ scan(table: string): AsyncIterable<Row>;
1018
+ /**
1019
+ * Natively filtered lazy iteration — the {@link DriverInterface.stream} hook.
1020
+ *
1021
+ * @remarks
1022
+ * Iterates the table's keys in the same key order `scan` and `keys` yield
1023
+ * (sorted by {@link compareValues}), testing each row against
1024
+ * `criteria.conditions` (via {@link matchesCriteria}) before counting it
1025
+ * toward `offset` / `limit`. Both are applied lazily as matches are found —
1026
+ * `offset` matches are skipped without being yielded, and iteration stops the
1027
+ * 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
1029
+ * `TableInterface.scan` and `QueryInterface.stream`): streaming yields key
1030
+ * order, sorted output is `records()`'s job. Rows yield copy-out (AGENTS
1031
+ * §11), and an unknown table mirrors `scan`'s empty-yield behavior.
1032
+ *
1033
+ * @param table - The table to stream
1034
+ * @param criteria - The filter / offset / limit to apply lazily
1035
+ *
1036
+ * @example
1037
+ * ```ts
1038
+ * for await (const row of driver.stream('users', { conditions, limit: 10 })) {
1039
+ * // one matched row at a time, in key order
1040
+ * }
1041
+ * ```
1042
+ */
1043
+ stream(table: string, criteria: Criteria): AsyncIterable<Row>;
1044
+ clear(table: string): Promise<void>;
1045
+ /**
1046
+ * Capture the current state and return a thunk that rolls back to it.
1047
+ *
1048
+ * @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.
1053
+ *
1054
+ * @param tables - The table names to scope the snapshot to; omitted captures every table
1055
+ * @returns A thunk that restores the captured tables
1056
+ */
1057
+ snapshot(tables?: readonly string[]): Promise<() => Promise<void>>;
1058
+ /**
1059
+ * Return the persisted {@link DriverMeta}, or `undefined` when the store has
1060
+ * never been stamped.
1061
+ *
1062
+ * @remarks
1063
+ * 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.
1066
+ *
1067
+ * @returns The last-stamped {@link DriverMeta}, or `undefined`
1068
+ */
1069
+ meta(): Promise<DriverMeta | undefined>;
1070
+ /**
1071
+ * Persist `meta` verbatim for a later `meta()` to return.
1072
+ *
1073
+ * @param meta - The {@link DriverMeta} to persist
1074
+ */
1075
+ stamp(meta: DriverMeta): Promise<void>;
1076
+ /**
1077
+ * Apply a {@link Migration} plan's steps against the in-memory store.
1078
+ *
1079
+ * @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.
1082
+ *
1083
+ * @param plan - The migration plan to apply
1084
+ */
1085
+ migrate(plan: Migration): Promise<void>;
1086
+ }
1087
+
1088
+ /**
1089
+ * Apply one table's {@link MigrationStep}s to its rows — a pure row transform.
1090
+ *
1091
+ * @remarks
1092
+ * `column.remove` drops that field from every row (a fresh copy — inputs are
1093
+ * never mutated, AGENTS §11); `column.add` leaves rows as-is (an absent field
1094
+ * reads as `undefined`, backfill is application policy). `table.add` /
1095
+ * `table.remove` / `index.add` / `index.remove` are no-ops here (they operate
1096
+ * on storage shape, not row shape). Steps for tables other than the one
1097
+ * `rows` belongs to are ignored — pass only the steps relevant to this table.
1098
+ *
1099
+ * @param rows - The table's current rows
1100
+ * @param steps - The migration steps to apply (typically one table's slice of a {@link Migration})
1101
+ * @returns A new array of transformed rows; `rows` is never mutated
1102
+ *
1103
+ * @example
1104
+ * ```ts
1105
+ * const rows = [{ id: 'a', name: 'Ada', legacy: true }]
1106
+ * migrateRows(rows, [{ operation: 'column.remove', table: 'users', column: 'legacy' }])
1107
+ * // => [{ id: 'a', name: 'Ada' }]
1108
+ * ```
1109
+ */
1110
+ export declare function migrateRows(rows: readonly Row[], steps: readonly MigrationStep[]): readonly Row[];
1111
+
1112
+ /**
1113
+ * A schema migration plan — an ordered set of {@link MigrationStep}s moving a
1114
+ * database from one schema version to another.
1115
+ *
1116
+ * @remarks
1117
+ * `from` / `to` are the source and target schema versions; `steps` runs in
1118
+ * order. Applied natively via {@link DriverInterface.migrate} when a driver
1119
+ * implements it.
1120
+ */
1121
+ export declare interface Migration {
1122
+ readonly from: number;
1123
+ readonly to: number;
1124
+ readonly steps: readonly MigrationStep[];
1125
+ }
1126
+
1127
+ /**
1128
+ * One step of a {@link Migration} plan — a single schema change applied to one
1129
+ * table.
1130
+ *
1131
+ * @remarks
1132
+ * `operation` names the axis it splits on (AGENTS §4.4): adding / removing a
1133
+ * whole table, a column, or an index. A driver's optional `migrate` applies each
1134
+ * step natively; a step referencing an unknown table throws `DatabaseError`
1135
+ * `MIGRATION`.
1136
+ */
1137
+ export declare type MigrationStep = {
1138
+ readonly operation: 'table.add';
1139
+ readonly table: TableSchema;
1140
+ } | {
1141
+ readonly operation: 'table.remove';
1142
+ readonly table: string;
1143
+ } | {
1144
+ readonly operation: 'column.add';
1145
+ readonly table: string;
1146
+ readonly column: ColumnSchema;
1147
+ } | {
1148
+ readonly operation: 'column.remove';
1149
+ readonly table: string;
1150
+ readonly column: string;
1151
+ } | {
1152
+ readonly operation: 'index.add';
1153
+ readonly table: string;
1154
+ readonly index: readonly string[];
1155
+ } | {
1156
+ readonly operation: 'index.remove';
1157
+ readonly table: string;
1158
+ readonly index: readonly string[];
1159
+ };
1160
+
1161
+ /** One ordering term — a column ({@link FieldPath}, flat or nested) and its direction. */
1162
+ export declare interface Order {
1163
+ readonly column: FieldPath;
1164
+ readonly direction: Direction;
1165
+ }
1166
+
1167
+ /**
1168
+ * Structurally diff a deployed and a declared table set into a {@link Migration}
1169
+ * plan.
1170
+ *
1171
+ * @remarks
1172
+ * Tables present in `declared` but not `deployed` become `table.add` steps
1173
+ * (carrying the full declared {@link TableSchema}); tables present in
1174
+ * `deployed` but not `declared` become `table.remove` steps. Tables present in
1175
+ * both are diffed column-by-column (by name) and index-group-by-index-group
1176
+ * (by deep equality of the column-name array), each producing `column.add` /
1177
+ * `column.remove` / `index.add` / `index.remove` steps. Step order is
1178
+ * deterministic: every `table.remove`, then every `table.add`, then each
1179
+ * 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.
1182
+ *
1183
+ * 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
1185
+ * table, the column, and the from→to difference — a name-only diff would
1186
+ * otherwise silently produce NO step for the drift, and versioned
1187
+ * reconciliation would stamp over it. There is no automatic in-place
1188
+ * type-change step: the manual path is to add a new column, copy/convert the
1189
+ * data at the application layer, then remove the old column — two separate
1190
+ * plans, never a single implicit "alter" step.
1191
+ *
1192
+ * @param deployed - The table schemas currently applied
1193
+ * @param declared - The table schemas the caller wants applied
1194
+ * @param from - The plan's source version label (defaults to `0`)
1195
+ * @param to - The plan's target version label (defaults to `1`)
1196
+ * @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`
1199
+ *
1200
+ * @example
1201
+ * ```ts
1202
+ * const plan = planMigration(
1203
+ * [{ name: 'users', primary: 'id', columns: [], indexes: [] }],
1204
+ * [{ name: 'users', primary: 'id', columns: [{ name: 'age', type: 'integer', nullable: false }], indexes: [] }],
1205
+ * )
1206
+ * // plan.steps === [{ operation: 'column.add', table: 'users', column: { name: 'age', ... } }]
1207
+ * ```
1208
+ */
1209
+ export declare function planMigration(deployed: readonly TableSchema[], declared: readonly TableSchema[], from?: number, to?: number): Migration;
1210
+
1211
+ /**
1212
+ * A fluent query builder bound to one table.
1213
+ *
1214
+ * @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>;
1255
+ }
1256
+
1257
+ /**
1258
+ * A fluent query builder.
1259
+ *
1260
+ * @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
1265
+ * call mutates and returns the same builder, so a chain reads as one statement.
1266
+ * Every `column` is a {@link FieldPath} — a string is one column, an array
1267
+ * descends a nested value.
1268
+ */
1269
+ export declare interface QueryInterface<T = Row> {
1270
+ where(column: FieldPath): ClauseInterface<T>;
1271
+ and(column: FieldPath): ClauseInterface<T>;
1272
+ or(column: FieldPath): ClauseInterface<T>;
1273
+ filter(predicate: (row: T) => boolean): QueryInterface<T>;
1274
+ ascending(column: FieldPath): QueryInterface<T>;
1275
+ descending(column: FieldPath): QueryInterface<T>;
1276
+ limit(count: number): QueryInterface<T>;
1277
+ offset(count: number): QueryInterface<T>;
1278
+ all(): Promise<readonly T[]>;
1279
+ first(): Promise<T | undefined>;
1280
+ count(): Promise<number>;
1281
+ /**
1282
+ * Lazy per-row evaluation of this query's conditions / filters / offset /
1283
+ * limit.
1284
+ *
1285
+ * @remarks
1286
+ * `order` and its comparators are IGNORED (streaming yields unsorted, as
1287
+ * rows are evaluated one at a time). Same abort semantics as
1288
+ * {@link TableInterface.scan}: the signal (if any) is checked before each
1289
+ * yield, and breaking out early closes the underlying source.
1290
+ */
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;
1310
+ }
1311
+
1312
+ /** A table row — a plain record of column values keyed by column name. */
1313
+ export declare type Row = Record<string, unknown>;
1314
+
1315
+ /**
1316
+ * The row type a table's {@link Columns} describe — `Infer` of its `objectShape`.
1317
+ *
1318
+ * @remarks
1319
+ * The broad `Columns` (an open `column → shape` map, e.g. when a database is held
1320
+ * at its default type) short-circuits to {@link Row}: there is nothing concrete to
1321
+ * infer, and expanding `Infer` over the open shape would trip TS's
1322
+ * instantiation-depth guard. Concrete column maps infer their exact row.
1323
+ */
1324
+ export declare type RowOf<C extends Columns> = [Columns] extends [C] ? Row : Infer<{
1325
+ readonly type: 'object';
1326
+ readonly properties: C;
1327
+ }>;
1328
+
1329
+ /**
1330
+ * Map a column's {@link ContractShape} to its portable {@link ColumnType} — the
1331
+ * value a `TableSchema` carries so a native backend can declare a real column.
1332
+ *
1333
+ * @remarks
1334
+ * `string` → `text`; `number` → `integer` when the shape is integer-only, else
1335
+ * `real`; `boolean` → `boolean`. A `literal` takes the type of its values
1336
+ * (all-boolean → `boolean`, all-integer → `integer`, mixed/fractional numbers →
1337
+ * `real`, anything else → `text`). `optional` / `nullable` unwrap to their inner
1338
+ * type (nullability is tracked separately). `null` / `object` / `array` / `union` /
1339
+ * `json` / `raw` → `json`: a backend stores them as JSON text and can `json_extract`
1340
+ * for nested `FieldPath` queries. A scan-only backend ignores the result.
1341
+ *
1342
+ * @param shape - The column's contract shape
1343
+ * @returns The portable column type
1344
+ *
1345
+ * @example
1346
+ * ```ts
1347
+ * shapeToColumnType(stringShape()) // 'text'
1348
+ * shapeToColumnType(integerShape()) // 'integer'
1349
+ * shapeToColumnType(optionalShape(integerShape())) // 'integer'
1350
+ * shapeToColumnType(objectShape({ a: stringShape() })) // 'json'
1351
+ * ```
1352
+ */
1353
+ export declare function shapeToColumnType(shape: ContractShape): ColumnType;
1354
+
1355
+ /**
1356
+ * Sort rows by an ordering specification, leaving the input untouched.
1357
+ *
1358
+ * @remarks
1359
+ * Applies the terms in priority order — the first term that distinguishes two
1360
+ * rows decides — using {@link compareValues}, reversing for `descending`.
1361
+ *
1362
+ * @param rows - The rows to sort
1363
+ * @param order - The ordering terms in priority order
1364
+ * @returns A new, sorted array
1365
+ */
1366
+ export declare function sortRows(rows: readonly Row[], order: readonly Order[]): readonly Row[];
1367
+
1368
+ /**
1369
+ * A table — typed keyed CRUD plus fluent query and cursor access over a driver.
1370
+ *
1371
+ * @remarks
1372
+ * The table's contract is the load-bearing piece: writes go through `parse`
1373
+ * (coercing inputs and rejecting rows that don't fit with a `VALIDATION` throw),
1374
+ * reads come back through the contract guard (narrowing a stored {@link Row} to
1375
+ * the table's type — no assertion, AGENTS §1), and `contract` is exposed for
1376
+ * introspection and seeding. The driver only stores and scans; all querying is
1377
+ * the shared core engine in `helpers.ts`.
1378
+ *
1379
+ * @remarks
1380
+ * - **Observable (§13).** The owned {@link emitter} ({@link TableEventMap}) carries the
1381
+ * per-row mutation moments — `write` (set / add / update), `remove`, `clear` — for
1382
+ * fire-and-forget observers (cache invalidation, sync, an audit log), ALONGSIDE the
1383
+ * database-level lifecycle. Events carry the affected KEY only (no value payload, to
1384
+ * keep fan-out lean); reads / queries / counts are not emitted. Every event is emitted
1385
+ * directly, strictly AFTER the driver write / delete / clear completes; the emitter
1386
+ * isolates a listener throw and routes it to its `error` handler (the `error` option),
1387
+ * so a buggy observer can never corrupt a write or perturb a transaction.
1388
+ */
1389
+ export declare class Table<T = Row> implements TableInterface<T> {
1390
+ #private;
1391
+ constructor(ready: () => Promise<void>, driver: DriverInterface, name: string, key: string, contract: ContractInterface<T>, generate?: KeyFunction, on?: EmitterHooks<TableEventMap>, error?: EmitterErrorHandler);
1392
+ get emitter(): EmitterInterface<TableEventMap>;
1393
+ get name(): string;
1394
+ get primary(): string;
1395
+ get contract(): ContractInterface<T>;
1396
+ get(key: Key): Promise<T | undefined>;
1397
+ get(keys: readonly Key[]): Promise<readonly (T | undefined)[]>;
1398
+ resolve(key: Key): Promise<T>;
1399
+ resolve(keys: readonly Key[]): Promise<readonly T[]>;
1400
+ has(key: Key): Promise<boolean>;
1401
+ has(keys: readonly Key[]): Promise<readonly boolean[]>;
1402
+ keys(): Promise<readonly Key[]>;
1403
+ records(criteria?: Criteria, options?: ReadOptions): Promise<readonly T[]>;
1404
+ /**
1405
+ * Count rows matching `criteria`'s conditions.
1406
+ *
1407
+ * @remarks
1408
+ * Unlike {@link records}, which narrows every row through the table's
1409
+ * contract guard before returning it, `count` operates on STORED rows
1410
+ * WITHOUT that guard (both the native `driver.count` hook and the
1411
+ * `filterRows`-over-`#collect()` fallback count raw storage) — so it can
1412
+ * exceed `(await records(criteria)).length` when storage holds rows that
1413
+ * no longer conform to the table's contract (legacy or migrated data).
1414
+ *
1415
+ * @param criteria - Optional conditions to filter by (paging is ignored)
1416
+ * @param options - `{ signal }` to abort
1417
+ * @returns The count of matching stored rows
1418
+ */
1419
+ count(criteria?: Criteria, options?: ReadOptions): Promise<number>;
1420
+ /**
1421
+ * Compute an aggregate over `column` across rows matching `criteria`'s
1422
+ * conditions.
1423
+ *
1424
+ * @remarks
1425
+ * Like {@link count}, `aggregate` operates on STORED rows WITHOUT the
1426
+ * contract guard {@link records} / {@link scan} apply — a non-conforming
1427
+ * stored row still contributes to the computed aggregate when it matches
1428
+ * the conditions, even though it would never appear in `records()`'s
1429
+ * output.
1430
+ *
1431
+ * @param operation - The aggregate to compute
1432
+ * @param column - The column to aggregate
1433
+ * @param criteria - Optional conditions to filter by (paging is ignored)
1434
+ * @param options - `{ signal }` to abort
1435
+ * @returns The aggregate value, or `undefined` when undefined for the inputs
1436
+ */
1437
+ aggregate(operation: AggregateFunction, column: FieldPath, criteria?: Criteria, options?: ReadOptions): Promise<number | undefined>;
1438
+ /**
1439
+ * Stream the table's rows matching `criteria`, applying offset/limit paging.
1440
+ *
1441
+ * @remarks
1442
+ * `criteria.limit` counts rows that pass BOTH the criteria conditions AND the
1443
+ * table's contract guard (a stored row that fails the guard is skipped and
1444
+ * does not count toward `limit`) — this can differ from {@link records}'s
1445
+ * `limit`, which a driver's optional native `records` hook applies BEFORE
1446
+ * the contract guard runs, when storage holds rows that no longer conform
1447
+ * to the table's contract.
1448
+ *
1449
+ * @param criteria - Optional conditions plus offset/limit paging
1450
+ * @param options - `{ signal }` to abort mid-stream
1451
+ * @returns An async iterable of matching, guard-conforming rows
1452
+ */
1453
+ scan(criteria?: Criteria, options?: ReadOptions): AsyncIterable<T>;
1454
+ set(row: T, options?: ReadOptions): Promise<Key>;
1455
+ set(rows: readonly T[], options?: ReadOptions): Promise<readonly Key[]>;
1456
+ add(row: T, options?: ReadOptions): Promise<Key>;
1457
+ add(rows: readonly T[], options?: ReadOptions): Promise<readonly Key[]>;
1458
+ update(key: Key, changes: Partial<T>, options?: ReadOptions): Promise<boolean>;
1459
+ update(keys: readonly Key[], changes: Partial<T>, options?: ReadOptions): Promise<readonly boolean[]>;
1460
+ remove(key: Key, options?: ReadOptions): Promise<boolean>;
1461
+ remove(keys: readonly Key[], options?: ReadOptions): Promise<readonly boolean[]>;
1462
+ clear(): Promise<void>;
1463
+ query(): QueryInterface<T>;
1464
+ cursor(): Promise<CursorInterface<T>>;
1465
+ }
1466
+
1467
+ /**
1468
+ * The push observation surface of a {@link TableInterface} (AGENTS §13) — the per-row
1469
+ * mutation moments a fire-and-forget observer (cache invalidation, sync, an audit log)
1470
+ * subscribes to, ALONGSIDE the database-level {@link DatabaseEventMap}.
1471
+ *
1472
+ * @typeParam TKey - The table's primary-key type (a {@link Key}); the events carry the
1473
+ * affected key so the map is `TableEventMap<TKey>`.
1474
+ *
1475
+ * @remarks
1476
+ * Events carry the affected KEY only — never the row value — to keep fan-out lean and
1477
+ * avoid leaking row data through the observation channel; a consumer that needs the
1478
+ * value re-reads it by key. Any row put — `set`, `add`, or `update` — emits a single
1479
+ * `write` (the consumer re-reads if it needs to know what changed); a delete emits
1480
+ * `remove`; emptying the table emits `clear`. Reads / queries / counts are NOT emitted
1481
+ * (too hot, and a reader does not mutate). Listener isolation is the emitter's (AGENTS §13):
1482
+ * every event is emitted directly and a listener throw is routed to the emitter's `error`
1483
+ * handler (the `error` option), never onto this map, and sits AFTER the driver write / delete
1484
+ * / clear has completed — so a throwing observer can never corrupt a write or perturb a
1485
+ * transaction. Subscribe via `table.emitter.on(...)`. Declared as a `type` alias (§4.5 —
1486
+ * `EventMap` is a `type` kind).
1487
+ */
1488
+ export declare type TableEventMap<TKey extends Key = Key> = {
1489
+ /** A row was written (set / added / updated) — the affected key (no value payload). */
1490
+ readonly write: readonly [key: TKey];
1491
+ /** A row was removed — the affected key. */
1492
+ readonly remove: readonly [key: TKey];
1493
+ /** The table was cleared (every row removed). */
1494
+ readonly clear: readonly [];
1495
+ };
1496
+
1497
+ /**
1498
+ * One table's portable definition, produced by `export` — the unit of schema /
1499
+ * migration exchange across environments.
1500
+ *
1501
+ * @remarks
1502
+ * `schema` is the JSON Schema (universally portable, serializable); `columns` is
1503
+ * the source column map, which re-imports losslessly via `import` within a
1504
+ * TypeScript environment. `key` is the primary-key column.
1505
+ */
1506
+ export declare interface TableExport {
1507
+ readonly key: string;
1508
+ readonly columns: Columns;
1509
+ readonly schema: JSONSchema;
1510
+ }
1511
+
1512
+ /**
1513
+ * Per-table secondary indexes — `{ [table]: groups }`, each group one
1514
+ * (possibly compound) index of column names.
1515
+ *
1516
+ * @remarks
1517
+ * Contracts don't express indexes, so they're declared here on `createDatabase`
1518
+ * and flow into each {@link TableSchema}'s `indexes` (SQLite `CREATE INDEX`,
1519
+ * IndexedDB `createIndex`). Mirrors {@link TableKeys}.
1520
+ */
1521
+ export declare type TableIndexes = Readonly<Record<string, readonly (readonly string[])[]>>;
1522
+
1523
+ /**
1524
+ * A table — typed keyed CRUD plus fluent query and cursor access.
1525
+ *
1526
+ * @remarks
1527
+ * Writes are coerced through the table's contract: a string input to a numeric
1528
+ * column is normalized, and a row that cannot be coerced throws `VALIDATION`. A
1529
+ * row missing its key is assigned a generated UUID. `get` returns `undefined`
1530
+ * when a key is absent; `resolve` throws `NOT_FOUND`. `set` upserts; `add`
1531
+ * inserts and throws `CONFLICT` on a duplicate key. `contract` exposes the
1532
+ * compiled contract for introspection (`schema`) and fixtures (`generate`).
1533
+ *
1534
+ * The keyed methods batch by overload (AGENTS §9.2): pass one key/row for one
1535
+ * result, or an array for an array of results in the same order — a single verb,
1536
+ * never `getMany` / `setAll`. Batches run as independent sequential operations;
1537
+ * wrap them in `transaction` for atomicity.
1538
+ */
1539
+ export declare interface TableInterface<T = Row> {
1540
+ readonly emitter: EmitterInterface<TableEventMap>;
1541
+ readonly name: string;
1542
+ readonly primary: string;
1543
+ readonly contract: ContractInterface<T>;
1544
+ get(key: Key): Promise<T | undefined>;
1545
+ get(keys: readonly Key[]): Promise<readonly (T | undefined)[]>;
1546
+ resolve(key: Key): Promise<T>;
1547
+ resolve(keys: readonly Key[]): Promise<readonly T[]>;
1548
+ has(key: Key): Promise<boolean>;
1549
+ has(keys: readonly Key[]): Promise<readonly boolean[]>;
1550
+ keys(): Promise<readonly Key[]>;
1551
+ records(criteria?: Criteria, options?: ReadOptions): Promise<readonly T[]>;
1552
+ /**
1553
+ * Count rows matching `criteria`'s conditions.
1554
+ *
1555
+ * @remarks
1556
+ * `records()` / `scan()` narrow every row through the table's contract
1557
+ * guard before returning it, so a non-conforming stored row (legacy data,
1558
+ * a row from before a migration) never appears in their results. `count`
1559
+ * operates on STORED rows WITHOUT that guard — it counts whatever
1560
+ * conditions-matches in storage, guard-conforming or not. This means
1561
+ * `count()` CAN exceed `(await records(criteria)).length` when storage
1562
+ * holds rows that no longer conform to the table's contract.
1563
+ */
1564
+ count(criteria?: Criteria, options?: ReadOptions): Promise<number>;
1565
+ /**
1566
+ * Compute an aggregate over `column` across rows matching `criteria`'s
1567
+ * conditions.
1568
+ *
1569
+ * @remarks
1570
+ * Like {@link TableInterface.count}, `aggregate` operates on STORED rows
1571
+ * WITHOUT the contract guard that `records()` / `scan()` apply — a
1572
+ * non-conforming stored row still contributes to the aggregate (or to the
1573
+ * `count` operation's tally) when it matches the conditions, even though
1574
+ * it would never appear in `records()`'s output.
1575
+ */
1576
+ aggregate(operation: AggregateFunction, column: FieldPath, criteria?: Criteria, options?: ReadOptions): Promise<number | undefined>;
1577
+ /**
1578
+ * Lazy filtered iteration over the table's rows.
1579
+ *
1580
+ * @remarks
1581
+ * `criteria`'s `conditions` / `offset` / `limit` are honored lazily as rows
1582
+ * stream; `order` is intentionally IGNORED — streaming yields driver
1583
+ * key-order, sorted output is `records()`'s job. Breaking out of the
1584
+ * iteration early closes the underlying source. The signal (if any) is
1585
+ * checked before each yield.
1586
+ */
1587
+ scan(criteria?: Criteria, options?: ReadOptions): AsyncIterable<T>;
1588
+ /**
1589
+ * Upsert one or more rows.
1590
+ *
1591
+ * @param row - The row to upsert
1592
+ * @param options - Optional abort signal
1593
+ * @returns The row's key
1594
+ */
1595
+ set(row: T, options?: ReadOptions): Promise<Key>;
1596
+ /**
1597
+ * Upsert one or more rows.
1598
+ *
1599
+ * @param rows - The rows to upsert
1600
+ * @param options - Optional abort signal, checked at entry and between items
1601
+ * @returns Each row's key, in order
1602
+ *
1603
+ * @remarks
1604
+ * The signal (if any) is checked at entry and between items; an abort
1605
+ * surfaces as `DatabaseError` `ABORTED`. Already-applied items stay
1606
+ * applied — there is no rollback. Wrap in `transaction()` for atomicity.
1607
+ */
1608
+ set(rows: readonly T[], options?: ReadOptions): Promise<readonly Key[]>;
1609
+ /**
1610
+ * Insert one or more rows, throwing `CONFLICT` on a duplicate key.
1611
+ *
1612
+ * @param row - The row to insert
1613
+ * @param options - Optional abort signal
1614
+ * @returns The row's key
1615
+ */
1616
+ add(row: T, options?: ReadOptions): Promise<Key>;
1617
+ /**
1618
+ * Insert one or more rows, throwing `CONFLICT` on a duplicate key.
1619
+ *
1620
+ * @param rows - The rows to insert
1621
+ * @param options - Optional abort signal, checked at entry and between items
1622
+ * @returns Each row's key, in order
1623
+ *
1624
+ * @remarks
1625
+ * The signal (if any) is checked at entry and between items; an abort
1626
+ * surfaces as `DatabaseError` `ABORTED`. Already-applied items stay
1627
+ * applied — there is no rollback. Wrap in `transaction()` for atomicity.
1628
+ */
1629
+ add(rows: readonly T[], options?: ReadOptions): Promise<readonly Key[]>;
1630
+ /**
1631
+ * Apply a partial change to one or more rows.
1632
+ *
1633
+ * @param key - The key of the row to update
1634
+ * @param changes - The partial changes to apply
1635
+ * @param options - Optional abort signal
1636
+ * @returns `true` when the row existed and was updated
1637
+ */
1638
+ update(key: Key, changes: Partial<T>, options?: ReadOptions): Promise<boolean>;
1639
+ /**
1640
+ * Apply a partial change to one or more rows.
1641
+ *
1642
+ * @param keys - The keys of the rows to update
1643
+ * @param changes - The partial changes to apply to each row
1644
+ * @param options - Optional abort signal, checked at entry and between items
1645
+ * @returns Each row's update result, in order
1646
+ *
1647
+ * @remarks
1648
+ * The signal (if any) is checked at entry and between items; an abort
1649
+ * surfaces as `DatabaseError` `ABORTED`. Already-applied items stay
1650
+ * applied — there is no rollback. Wrap in `transaction()` for atomicity.
1651
+ */
1652
+ update(keys: readonly Key[], changes: Partial<T>, options?: ReadOptions): Promise<readonly boolean[]>;
1653
+ /**
1654
+ * Delete one or more rows.
1655
+ *
1656
+ * @param key - The key of the row to remove
1657
+ * @param options - Optional abort signal
1658
+ * @returns `true` when the row existed and was removed
1659
+ */
1660
+ remove(key: Key, options?: ReadOptions): Promise<boolean>;
1661
+ /**
1662
+ * Delete one or more rows.
1663
+ *
1664
+ * @param keys - The keys of the rows to remove
1665
+ * @param options - Optional abort signal, checked at entry and between items
1666
+ * @returns Each row's removal result, in order
1667
+ *
1668
+ * @remarks
1669
+ * The signal (if any) is checked at entry and between items; an abort
1670
+ * surfaces as `DatabaseError` `ABORTED`. Already-applied items stay
1671
+ * applied — there is no rollback. Wrap in `transaction()` for atomicity.
1672
+ */
1673
+ remove(keys: readonly Key[], options?: ReadOptions): Promise<readonly boolean[]>;
1674
+ clear(): Promise<void>;
1675
+ query(): QueryInterface<T>;
1676
+ cursor(): Promise<CursorInterface<T>>;
1677
+ }
1678
+
1679
+ /**
1680
+ * Per-table primary-key column overrides — `{ [table]: column }`.
1681
+ *
1682
+ * @remarks
1683
+ * A table absent from this map keys its rows by {@link DEFAULT_PRIMARY} (`id`).
1684
+ * Kept separate from {@link TablesShape} so the table map stays purely columns.
1685
+ */
1686
+ export declare type TableKeys = Readonly<Record<string, string>>;
1687
+
1688
+ /**
1689
+ * A backend-agnostic description of one table — what `open` hands each driver so a
1690
+ * native backend can create real tables and indexes.
1691
+ *
1692
+ * @remarks
1693
+ * Derived by the database from its `tables` contract shapes ({@link ColumnSchema}
1694
+ * per column, via `shapeToColumnType`), its `keys` (`primary`), and its `indexes` option
1695
+ * (`indexes`, each entry one possibly-compound index of column names). A scan-only
1696
+ * backend (the reference `MemoryDriver`) ignores everything but `name`.
1697
+ */
1698
+ export declare interface TableSchema {
1699
+ readonly name: string;
1700
+ readonly primary: string;
1701
+ readonly columns: readonly ColumnSchema[];
1702
+ readonly indexes: readonly (readonly string[])[];
1703
+ }
1704
+
1705
+ /**
1706
+ * A database's table schema — a map of table name to its {@link Columns}.
1707
+ *
1708
+ * @remarks
1709
+ * Each table's row type is `Infer` of its columns (see {@link RowOf}); primary-key
1710
+ * columns are named separately via {@link TableKeys}.
1711
+ */
1712
+ export declare type TablesShape = Readonly<Record<string, Columns>>;
1713
+
1714
+ /**
1715
+ * The handle a driver's native `transaction` hook returns.
1716
+ *
1717
+ * @remarks
1718
+ * `commit` finalizes the native BEGIN; `rollback` undoes it. When a driver
1719
+ * implements {@link DriverInterface.transaction}, the engine uses this handle
1720
+ * instead of the snapshot-based rollback floor.
1721
+ */
1722
+ export declare interface TransactionInterface {
1723
+ commit(): Promise<void>;
1724
+ rollback(): Promise<void>;
1725
+ }
1726
+
1727
+ /** The number of bytes encoded by an RFC 4122 UUID. */
1728
+ export declare const UUID_BYTE_COUNT = 16;
1729
+
1730
+ /** The number of distinct values one UUID byte may hold. */
1731
+ export declare const UUID_BYTE_RANGE = 256;
1732
+
1733
+ /**
1734
+ * Match a value against a wildcard pattern in LINEAR time — the shared, ReDoS-SAFE
1735
+ * engine behind {@link likeMatch} and {@link globMatch}.
1736
+ *
1737
+ * @remarks
1738
+ * A backtracking RegExp (`a%b%c` → `^a.*b.*c$`) is CATASTROPHIC on a hostile pattern:
1739
+ * `.*` segments separated by literals, matched against a long non-matching input, blow
1740
+ * up super-linearly — and JS has no atomic groups / possessive quantifiers to bound it
1741
+ * (AGENTS §6.5, now that the authed server runs model-supplied `list` criteria over the
1742
+ * wire). So this builds NO regex. It runs the classic GREEDY TWO-POINTER wildcard match:
1743
+ * the `any` wildcard records its position and, on a later mismatch, backtracks ONLY to
1744
+ * that last `any` (letting it absorb one more char) — so the work is O(value × pattern),
1745
+ * never the exponential / polynomial backtracking a regex would do. The pattern length
1746
+ * is capped at {@link MAX_PATTERN_LENGTH} (a `VALIDATION` {@link DatabaseError} over it),
1747
+ * bounding the pattern factor so a match stays linear in the value length whatever the
1748
+ * pattern.
1749
+ *
1750
+ * The `any` wildcard matches any run (including empty); `single` matches exactly one
1751
+ * char; every other pattern char matches itself LITERALLY (a pattern `.` / `(` / `\` is
1752
+ * a literal — the regex-metacharacter hazard is gone with the regex). `any` is tested
1753
+ * BEFORE a literal match, so a value that literally contains the wildcard char never
1754
+ * shadows the wildcard. Case folding is applied to BOTH sides when `fold` is set.
1755
+ *
1756
+ * @param value - The value to test
1757
+ * @param pattern - The wildcard pattern
1758
+ * @param any - The any-run wildcard char (`%` for `LIKE`, `*` for `GLOB`)
1759
+ * @param single - The single-char wildcard char (`_` for `LIKE`, `?` for `GLOB`)
1760
+ * @param fold - Whether to match case-INSENSITIVELY (`LIKE` folds; `GLOB` does not)
1761
+ * @returns Whether `value` matches `pattern`
1762
+ * @throws A `VALIDATION` {@link DatabaseError} when `pattern` exceeds {@link MAX_PATTERN_LENGTH}
1763
+ */
1764
+ export declare function wildcardMatch(value: string, pattern: string, any: string, single: string, fold: boolean): boolean;
1765
+
1766
+ export { }