@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.
@@ -1,739 +0,0 @@
1
- import type { ContractInterface, ContractShape, FieldPath, Infer, JSONSchema } from '@orkestrel/contract';
2
- import type { EmitterErrorHandler, EmitterHooks, EmitterInterface } from '@orkestrel/emitter';
3
- /**
4
- * A primary key — the value identifying a row within its table.
5
- *
6
- * @remarks
7
- * `string | number` is the intersection of what IndexedDB key ranges and SQL
8
- * primary keys both express without coercion. Auto-generated keys are UUID
9
- * strings; supply your own to use numeric keys.
10
- */
11
- export type Key = string | number;
12
- /**
13
- * A caller-supplied key minting function.
14
- *
15
- * @remarks
16
- * Environment surfaces provide implementations (the server's `node:crypto`-backed
17
- * `generateKey`); the core mints no keys itself. Supplied via
18
- * {@link DatabaseOptions.key} and used by a table when a written row lacks its
19
- * primary key. Without one, writing a keyless row is a `VALIDATION` error.
20
- */
21
- export type KeyFunction = () => Key;
22
- /** A table row — a plain record of column values keyed by column name. */
23
- export type Row = Record<string, unknown>;
24
- /**
25
- * A WHERE operator — the comparison a single {@link Condition} applies.
26
- *
27
- * @remarks
28
- * Each maps to a SQL operator and an IndexedDB read strategy (a key range where
29
- * the bounds allow it, a scanned predicate otherwise). The set is closed to
30
- * comparisons expressible on both backends.
31
- */
32
- export type ConditionOperator = 'equals' | 'not' | 'above' | 'below' | 'from' | 'to' | 'between' | 'like' | 'glob' | 'starts' | 'ends' | 'any' | 'none' | 'absent' | 'present';
33
- /** How a {@link Condition} joins to the running result of the conditions before it. */
34
- export type Connector = 'and' | 'or';
35
- /**
36
- * One compiled WHERE condition.
37
- *
38
- * @remarks
39
- * `values` carries the operands the operator needs — none for `absent` /
40
- * `present`, one for most, two for `between`, a list for `any` / `none`.
41
- * `connector` folds this condition into the accumulated result left-to-right;
42
- * the first condition's connector seeds the fold and is otherwise ignored.
43
- * `column` is a {@link FieldPath}: a single string is ONE column (never split on
44
- * `.`), an array descends into a nested (object/`json`) value.
45
- */
46
- export interface Condition {
47
- readonly column: FieldPath;
48
- readonly operator: ConditionOperator;
49
- readonly values: readonly unknown[];
50
- readonly connector: Connector;
51
- }
52
- /** A sort direction. */
53
- export type Direction = 'ascending' | 'descending';
54
- /** One ordering term — a column ({@link FieldPath}, flat or nested) and its direction. */
55
- export interface Order {
56
- readonly column: FieldPath;
57
- readonly direction: Direction;
58
- }
59
- /**
60
- * A serializable read specification — everything a backend needs to compile one
61
- * read, free of JS callbacks so any backend can honor it.
62
- *
63
- * @remarks
64
- * The post-fetch `filter` predicate lives on {@link QueryInterface}, never here,
65
- * so `Criteria` stays portable across backends.
66
- */
67
- export interface Criteria {
68
- readonly conditions?: readonly Condition[];
69
- readonly order?: readonly Order[];
70
- readonly limit?: number;
71
- readonly offset?: number;
72
- }
73
- /** An aggregate computed over a numeric column. */
74
- export type AggregateFunction = 'count' | 'sum' | 'average' | 'minimum' | 'maximum';
75
- /**
76
- * Options for a cancellable read / iteration operation.
77
- *
78
- * @remarks
79
- * When `signal` aborts, the operation throws a {@link DatabaseError} with code
80
- * `ABORTED` carrying `signal.reason` in `context`. `TableInterface.scan` and
81
- * `QueryInterface.stream` check the signal before each yield; other read
82
- * methods check it at entry.
83
- */
84
- export interface ReadOptions {
85
- readonly signal?: AbortSignal;
86
- }
87
- /** The lifecycle state of a {@link DatabaseInterface}. */
88
- export type DatabaseStatus = 'idle' | 'open' | 'closed';
89
- /** A machine-readable {@link DatabaseError} code. */
90
- export type DatabaseErrorCode = 'CLOSED' | 'NOT_FOUND' | 'CONFLICT' | 'VALIDATION' | 'ABORTED' | 'MIGRATION' | 'CONFORMANCE' | 'DRIVER';
91
- /**
92
- * One violated invariant from the driver-conformance battery.
93
- *
94
- * @remarks
95
- * Mirrors the payload shape of a `DatabaseError` `CONFORMANCE` `context` —
96
- * `check` names the invariant, `message` describes the violation, and
97
- * `context` carries the offending table / key / value that failed it.
98
- */
99
- export interface ConformanceFinding {
100
- readonly check: string;
101
- readonly message: string;
102
- readonly context: Readonly<Record<string, unknown>>;
103
- }
104
- /**
105
- * The push observation surface of a {@link DatabaseInterface} (AGENTS §13) — the
106
- * connection + transaction lifecycle a fire-and-forget observer (logging, metrics,
107
- * tracing, cache invalidation) subscribes to.
108
- *
109
- * @remarks
110
- * Pure signals carrying no row data — these are the database-level (not per-row)
111
- * moments, so a non-generic map stays lean (per-row writes are {@link TableEventMap}).
112
- * Listener isolation is the emitter's (AGENTS §13): every event is emitted directly and a
113
- * listener throw is routed to the emitter's OWN `error` handler (the `error` option), never
114
- * onto this domain map and never into the snapshot / commit / rollback flow — so a buggy
115
- * observer can never reorder, throw into, or corrupt a transaction. Every emit sits AFTER the
116
- * relevant transition: `commit` only after the scope succeeds, `rollback` only after every
117
- * table has been restored (it OBSERVES the propagated error; the original throw still
118
- * propagates exactly as before). Subscribe via `database.emitter.on(...)`.
119
- *
120
- * Declared as a `type` alias (not `interface extends EventMap`, §4.5 — `EventMap` is a
121
- * `type` kind): a type-literal satisfies the `EventMap` constraint
122
- * (`Record<string, readonly unknown[]>`) structurally, whereas an interface lacks the
123
- * required index signature.
124
- */
125
- export type DatabaseEventMap = {
126
- /** The driver connected (`open`, or the lazy first-use connect completed). */
127
- readonly open: readonly [];
128
- /** The database was closed (the driver released). */
129
- readonly close: readonly [];
130
- /** A transaction scope began — the store was snapshotted, the scope is about to run. */
131
- readonly transaction: readonly [];
132
- /** A transaction scope completed successfully (no rollback). */
133
- readonly commit: readonly [];
134
- /** A transaction scope threw and every table was rolled back — the propagated error. */
135
- readonly rollback: readonly [error: unknown];
136
- /** A {@link Migration} plan was applied via `migrate` — the applied plan. */
137
- readonly migrate: readonly [migration: Migration];
138
- };
139
- /**
140
- * The push observation surface of a {@link TableInterface} (AGENTS §13) — the per-row
141
- * mutation moments a fire-and-forget observer (cache invalidation, sync, an audit log)
142
- * subscribes to, ALONGSIDE the database-level {@link DatabaseEventMap}.
143
- *
144
- * @typeParam TKey - The table's primary-key type (a {@link Key}); the events carry the
145
- * affected key so the map is `TableEventMap<TKey>`.
146
- *
147
- * @remarks
148
- * Events carry the affected KEY only — never the row value — to keep fan-out lean and
149
- * avoid leaking row data through the observation channel; a consumer that needs the
150
- * value re-reads it by key. Any row put — `set`, `add`, or `update` — emits a single
151
- * `write` (the consumer re-reads if it needs to know what changed); a delete emits
152
- * `remove`; emptying the table emits `clear`. Reads / queries / counts are NOT emitted
153
- * (too hot, and a reader does not mutate). Listener isolation is the emitter's (AGENTS §13):
154
- * every event is emitted directly and a listener throw is routed to the emitter's `error`
155
- * handler (the `error` option), never onto this map, and sits AFTER the driver write / delete
156
- * / clear has completed — so a throwing observer can never corrupt a write or perturb a
157
- * transaction. Subscribe via `table.emitter.on(...)`. Declared as a `type` alias (§4.5 —
158
- * `EventMap` is a `type` kind).
159
- */
160
- export type TableEventMap<TKey extends Key = Key> = {
161
- /** A row was written (set / added / updated) — the affected key (no value payload). */
162
- readonly write: readonly [key: TKey];
163
- /** A row was removed — the affected key. */
164
- readonly remove: readonly [key: TKey];
165
- /** The table was cleared (every row removed). */
166
- readonly clear: readonly [];
167
- };
168
- /**
169
- * A portable storage type for a column — the backend maps it to its native type
170
- * (SQLite affinity, an IndexedDB value). Derived from a column's `ContractShape`
171
- * by `shapeToColumnType`; `json` covers object/array/union/raw values a backend stores
172
- * as JSON text and can `json_extract` for nested-field queries.
173
- */
174
- export type ColumnType = 'text' | 'integer' | 'real' | 'boolean' | 'json' | 'blob';
175
- /**
176
- * One column of a {@link TableSchema} — its name, portable {@link ColumnType}, and
177
- * whether it is nullable (its shape is `optionalShape` / `nullableShape`).
178
- */
179
- export interface ColumnSchema {
180
- readonly name: string;
181
- readonly type: ColumnType;
182
- readonly nullable: boolean;
183
- }
184
- /**
185
- * Persisted schema metadata a versioning driver stores verbatim and returns on
186
- * demand.
187
- *
188
- * @remarks
189
- * The driver never introspects this payload — it hands back exactly what was
190
- * last stamped via {@link DriverInterface.stamp}. `meta()` returning `undefined`
191
- * is how a fresh store is distinguished from an upgradable one: it means the
192
- * store has never been stamped, not that it is at version zero.
193
- */
194
- export interface DriverMeta {
195
- readonly version: number;
196
- readonly schema: readonly TableSchema[];
197
- }
198
- /**
199
- * A backend-agnostic description of one table — what `open` hands each driver so a
200
- * native backend can create real tables and indexes.
201
- *
202
- * @remarks
203
- * Derived by the database from its `tables` contract shapes ({@link ColumnSchema}
204
- * per column, via `shapeToColumnType`), its `keys` (`primary`), and its `indexes` option
205
- * (`indexes`, each entry one possibly-compound index of column names). A scan-only
206
- * backend (the reference `MemoryDriver`) ignores everything but `name`.
207
- */
208
- export interface TableSchema {
209
- readonly name: string;
210
- readonly primary: string;
211
- readonly columns: readonly ColumnSchema[];
212
- readonly indexes: readonly (readonly string[])[];
213
- }
214
- /**
215
- * One step of a {@link Migration} plan — a single schema change applied to one
216
- * table.
217
- *
218
- * @remarks
219
- * `operation` names the axis it splits on (AGENTS §4.4): adding / removing a
220
- * whole table, a column, or an index. A driver's optional `migrate` applies each
221
- * step natively; a step referencing an unknown table throws `DatabaseError`
222
- * `MIGRATION`.
223
- */
224
- export type MigrationStep = {
225
- readonly operation: 'table.add';
226
- readonly table: TableSchema;
227
- } | {
228
- readonly operation: 'table.remove';
229
- readonly table: string;
230
- } | {
231
- readonly operation: 'column.add';
232
- readonly table: string;
233
- readonly column: ColumnSchema;
234
- } | {
235
- readonly operation: 'column.remove';
236
- readonly table: string;
237
- readonly column: string;
238
- } | {
239
- readonly operation: 'index.add';
240
- readonly table: string;
241
- readonly index: readonly string[];
242
- } | {
243
- readonly operation: 'index.remove';
244
- readonly table: string;
245
- readonly index: readonly string[];
246
- };
247
- /**
248
- * A schema migration plan — an ordered set of {@link MigrationStep}s moving a
249
- * database from one schema version to another.
250
- *
251
- * @remarks
252
- * `from` / `to` are the source and target schema versions; `steps` runs in
253
- * order. Applied natively via {@link DriverInterface.migrate} when a driver
254
- * implements it.
255
- */
256
- export interface Migration {
257
- readonly from: number;
258
- readonly to: number;
259
- readonly steps: readonly MigrationStep[];
260
- }
261
- /**
262
- * The handle a driver's native `transaction` hook returns.
263
- *
264
- * @remarks
265
- * `commit` finalizes the native BEGIN; `rollback` undoes it. When a driver
266
- * implements {@link DriverInterface.transaction}, the engine uses this handle
267
- * instead of the snapshot-based rollback floor.
268
- */
269
- export interface TransactionInterface {
270
- commit(): Promise<void>;
271
- rollback(): Promise<void>;
272
- }
273
- /**
274
- * The storage primitive every backend implements — the whole of the bridge.
275
- *
276
- * @remarks
277
- * The REQUIRED surface is deliberately minimal: keyed read / write / delete, an
278
- * ordered `scan`, a key listing, and a `snapshot` that backs transactions — the
279
- * irreducible primitive. There is **no** required query, count, or aggregate
280
- * here: all of that is one query engine in the core (`helpers.ts`) running over
281
- * `scan`, so a new backend implements a handful of tiny methods rather than
282
- * re-deriving WHERE compilation. `open` now receives a derived
283
- * {@link TableSchema}`[]` (columns, types, primary, indexes) so a native backend
284
- * can build real tables and indexes; a scan-only backend reads only `name`. The
285
- * optional `records?` / `count?` / `aggregate?` are native overrides the engine
286
- * falls back from (AGENTS §21). The API is async (Promises) because IndexedDB is; synchronous
287
- * backends resolve immediately. Lookups that may miss return `undefined` /
288
- * `false` rather than throwing (AGENTS §12).
289
- */
290
- export interface DriverInterface {
291
- open(schema: readonly TableSchema[]): Promise<void>;
292
- close(): Promise<void>;
293
- read(table: string, key: Key): Promise<Row | undefined>;
294
- write(table: string, key: Key, row: Row): Promise<void>;
295
- delete(table: string, key: Key): Promise<boolean>;
296
- keys(table: string): Promise<readonly Key[]>;
297
- scan(table: string): AsyncIterable<Row>;
298
- clear(table: string): Promise<void>;
299
- /**
300
- * Capture the current state and return a thunk that rolls every table back to
301
- * it — the primitive transactions are built on (SQL `SAVEPOINT`, an IndexedDB
302
- * key buffer, a cloned map).
303
- *
304
- * @remarks
305
- * `tables` omitted captures/rolls back the WHOLE store (existing behavior).
306
- * `tables` provided captures/restores ONLY the named tables — the returned
307
- * rollback thunk leaves every other table untouched.
308
- */
309
- snapshot(tables?: readonly string[]): Promise<() => Promise<void>>;
310
- /**
311
- * Optional native filtered read (AGENTS §21). A backend that can evaluate a
312
- * {@link Criteria} natively (SQL `WHERE` + `ORDER`/`LIMIT`, an index range)
313
- * implements this; `Table` prefers it and falls back to `applyCriteria` over
314
- * `scan` when it is absent. Must honor the full criteria (filter, order, page).
315
- */
316
- records?(table: string, criteria: Criteria): Promise<readonly Row[]>;
317
- /**
318
- * Optional native count (AGENTS §21). Counts rows matching the criteria's
319
- * conditions (paging is irrelevant to a count); `Table` falls back to counting
320
- * the engine-filtered `scan` when absent.
321
- */
322
- count?(table: string, criteria: Criteria): Promise<number>;
323
- /**
324
- * Optional native aggregate (AGENTS §21). A backend that can compute an
325
- * aggregate natively (SQL `COUNT`/`SUM`/`AVG`/`MIN`/`MAX`, an indexed count)
326
- * implements this; `Table.aggregate` prefers it and otherwise falls back to
327
- * `computeAggregate` over the native-filtered (or scanned) rows. Aggregates
328
- * ignore paging, so `criteria` carries only conditions.
329
- */
330
- aggregate?(table: string, operation: AggregateFunction, column: FieldPath, criteria: Criteria): Promise<number | undefined>;
331
- /**
332
- * Optional native transaction (BEGIN). When present, the engine uses the
333
- * returned {@link TransactionInterface}'s `commit` / `rollback` instead of
334
- * the snapshot-based rollback floor.
335
- */
336
- transaction?(): Promise<TransactionInterface>;
337
- /**
338
- * Optional natively filtered lazy iteration — a {@link Criteria}-aware
339
- * streaming read. Drivers without it are served by the core scan fallback
340
- * (filtering `scan` lazily).
341
- */
342
- stream?(table: string, criteria: Criteria): AsyncIterable<Row>;
343
- /**
344
- * Optional native migration — applies a {@link Migration} plan directly.
345
- * Throws `DatabaseError` `MIGRATION` when a step references an unknown
346
- * table.
347
- */
348
- migrate?(plan: Migration): Promise<void>;
349
- /**
350
- * Optional persisted-metadata read (PAIRED with {@link stamp} — a driver
351
- * implements both or neither). Returns the {@link DriverMeta} last stamped,
352
- * or `undefined` when the store has never been stamped.
353
- */
354
- meta?(): Promise<DriverMeta | undefined>;
355
- /**
356
- * Optional persisted-metadata write (PAIRED with {@link meta} — a driver
357
- * implements both or neither). Persists `meta` verbatim for a later `meta()`
358
- * to return.
359
- */
360
- stamp?(meta: DriverMeta): Promise<void>;
361
- }
362
- /**
363
- * One table's columns — a map of column name to its value {@link ContractShape}.
364
- *
365
- * @remarks
366
- * This is exactly the property map an `objectShape` takes. A table row is always
367
- * an object, so you write the columns directly (`{ id: stringShape(), … }`) and
368
- * the database wraps them in an `objectShape` for you — no redundant `objectShape`
369
- * at the table level. (Nested object *columns* still use `objectShape`, since a
370
- * column is not always an object.)
371
- */
372
- export type Columns = Readonly<Record<string, ContractShape>>;
373
- /**
374
- * A database's table schema — a map of table name to its {@link Columns}.
375
- *
376
- * @remarks
377
- * Each table's row type is `Infer` of its columns (see {@link RowOf}); primary-key
378
- * columns are named separately via {@link TableKeys}.
379
- */
380
- export type TablesShape = Readonly<Record<string, Columns>>;
381
- /**
382
- * The row type a table's {@link Columns} describe — `Infer` of its `objectShape`.
383
- *
384
- * @remarks
385
- * The broad `Columns` (an open `column → shape` map, e.g. when a database is held
386
- * at its default type) short-circuits to {@link Row}: there is nothing concrete to
387
- * infer, and expanding `Infer` over the open shape would trip TS's
388
- * instantiation-depth guard. Concrete column maps infer their exact row.
389
- */
390
- export type RowOf<C extends Columns> = [Columns] extends [C] ? Row : Infer<{
391
- readonly type: 'object';
392
- readonly properties: C;
393
- }>;
394
- /**
395
- * Per-table primary-key column overrides — `{ [table]: column }`.
396
- *
397
- * @remarks
398
- * A table absent from this map keys its rows by {@link DEFAULT_PRIMARY} (`id`).
399
- * Kept separate from {@link TablesShape} so the table map stays purely columns.
400
- */
401
- export type TableKeys = Readonly<Record<string, string>>;
402
- /**
403
- * Per-table secondary indexes — `{ [table]: groups }`, each group one
404
- * (possibly compound) index of column names.
405
- *
406
- * @remarks
407
- * Contracts don't express indexes, so they're declared here on `createDatabase`
408
- * and flow into each {@link TableSchema}'s `indexes` (SQLite `CREATE INDEX`,
409
- * IndexedDB `createIndex`). Mirrors {@link TableKeys}.
410
- */
411
- export type TableIndexes = Readonly<Record<string, readonly (readonly string[])[]>>;
412
- /**
413
- * Options for `createDatabase`.
414
- *
415
- * @remarks
416
- * `driver` is the storage backend; `tables` declares each table's columns;
417
- * `keys` overrides the primary-key column per table ({@link DEFAULT_PRIMARY}
418
- * otherwise); `indexes` declares secondary indexes per table (contracts don't
419
- * express them) that flow into each derived {@link TableSchema}; `name` labels
420
- * the database; `on` wires initial {@link DatabaseEventMap} listeners (§8); `error`
421
- * is the emitter's listener-error handler (§13 — a listener throw routes here);
422
- * `key` is the key factory a table uses when a written row lacks its primary
423
- * key — without one, writing a keyless row is a `VALIDATION` error (the core
424
- * mints no keys itself).
425
- */
426
- export interface DatabaseOptions<T extends TablesShape = TablesShape> {
427
- readonly on?: EmitterHooks<DatabaseEventMap>;
428
- /** The emitter's listener-error handler (AGENTS §13) — a listener throw routes here, not to a domain event. */
429
- readonly error?: EmitterErrorHandler;
430
- readonly driver: DriverInterface;
431
- readonly tables: T;
432
- readonly keys?: TableKeys;
433
- readonly indexes?: TableIndexes;
434
- readonly name?: string;
435
- readonly key?: KeyFunction;
436
- /**
437
- * The declared schema version.
438
- *
439
- * @remarks
440
- * Only meaningful when the driver implements BOTH {@link DriverInterface.meta}
441
- * and {@link DriverInterface.stamp} (a versioning driver); unset, or a
442
- * non-versioning driver, leaves `open()` unchanged from today's behavior.
443
- * When set and the driver versions, `open()` reconciles against the
444
- * driver's persisted {@link DriverMeta}:
445
- * - **Fresh store** (`meta()` returns `undefined`) — no migration is
446
- * possible (there is nothing deployed to diff against), so `open()`
447
- * simply `stamp`s `{ version, schema }` for next time.
448
- * - **Stored version < `version`** — `planMigration(stored.schema, declared
449
- * schema)` computes the upgrade plan, applied via the driver's optional
450
- * `migrate` hook. If `migrate` is absent and the plan is non-empty,
451
- * `open()` throws `DatabaseError` `MIGRATION`. On success, `open()`
452
- * `stamp`s the new `{ version, schema }` and emits the `migrate` event.
453
- * - **Stored version > `version`** — the store is newer than the declared
454
- * schema; `open()` throws `DatabaseError` `MIGRATION`.
455
- * - **Stored version === `version`** — no-op.
456
- *
457
- * When the driver ALSO implements {@link DriverInterface.transaction}, the
458
- * `migrate` + `stamp` pair applies atomically through that native handle
459
- * (all-or-nothing, rolled back cleanly on a mid-plan failure); otherwise the
460
- * pair applies sequentially, with a small documented window in which a `stamp`
461
- * failure after a successful `migrate` can leave new data under old meta.
462
- */
463
- readonly version?: number;
464
- }
465
- /**
466
- * One table's portable definition, produced by `export` — the unit of schema /
467
- * migration exchange across environments.
468
- *
469
- * @remarks
470
- * `schema` is the JSON Schema (universally portable, serializable); `columns` is
471
- * the source column map, which re-imports losslessly via `import` within a
472
- * TypeScript environment. `key` is the primary-key column.
473
- */
474
- export interface TableExport {
475
- readonly key: string;
476
- readonly columns: Columns;
477
- readonly schema: JSONSchema;
478
- }
479
- /**
480
- * A database — the ergonomic entry point that owns the driver and its tables.
481
- *
482
- * @remarks
483
- * A database is a typed view over a set of tables on one driver. Tables are
484
- * declared up front in `createDatabase({ tables })` and reached, fully typed,
485
- * with `table(name)`. The driver connects lazily on first use, so a freshly
486
- * created database is immediately usable. `import` defines more than one table
487
- * from a shape map and returns a new typed view of **those** tables over the
488
- * same driver and storage (so views can be split by concern and still share
489
- * data); `export` produces a portable {@link TableExport} per table for moving a
490
- * schema between databases or environments. `transaction` snapshots the store,
491
- * runs the scope, and rolls every table back if it throws — an optimistic model
492
- * that works uniformly across backends rather than reconciling SQL's and
493
- * IndexedDB's incompatible native transactions.
494
- */
495
- export interface DatabaseInterface<T extends TablesShape = TablesShape> {
496
- readonly emitter: EmitterInterface<DatabaseEventMap>;
497
- readonly name: string;
498
- readonly status: DatabaseStatus;
499
- table<K extends keyof T & string>(name: K): TableInterface<RowOf<T[K]>>;
500
- import<U extends TablesShape>(tables: U, keys?: TableKeys): DatabaseInterface<U>;
501
- export(): Readonly<Record<string, TableExport>>;
502
- open(): Promise<void>;
503
- close(): Promise<void>;
504
- transaction<R>(scope: () => Promise<R>, options?: ReadOptions): Promise<R>;
505
- /**
506
- * Diff a caller-supplied deployed schema against this database's declared
507
- * schema (its `tables`, as configured) via `planMigration`, apply the
508
- * resulting plan through the driver's optional `migrate` hook, and return
509
- * the applied plan.
510
- *
511
- * @param deployed - The schema currently deployed, as {@link TableSchema}s
512
- * @param options - Optional abort signal, checked at entry
513
- * @returns The applied {@link Migration} plan
514
- *
515
- * @remarks
516
- * Throws `DatabaseError` `MIGRATION` when the driver does not implement
517
- * `migrate`, or when a step references an unknown table (propagated from
518
- * the driver). Throws `ABORTED` when `options.signal` has already fired at
519
- * entry. Emits the `migrate` event after a successful apply. Version
520
- * TRACKING (persisting `from` / `to`) remains deferred to persistent
521
- * backends — the caller owns knowing what was deployed.
522
- */
523
- migrate(deployed: readonly TableSchema[], options?: ReadOptions): Promise<Migration>;
524
- }
525
- /**
526
- * A table — typed keyed CRUD plus fluent query and cursor access.
527
- *
528
- * @remarks
529
- * Writes are coerced through the table's contract: a string input to a numeric
530
- * column is normalized, and a row that cannot be coerced throws `VALIDATION`. A
531
- * row missing its key is assigned a generated UUID. `get` returns `undefined`
532
- * when a key is absent; `resolve` throws `NOT_FOUND`. `set` upserts; `add`
533
- * inserts and throws `CONFLICT` on a duplicate key. `contract` exposes the
534
- * compiled contract for introspection (`schema`) and fixtures (`generate`).
535
- *
536
- * The keyed methods batch by overload (AGENTS §9.2): pass one key/row for one
537
- * result, or an array for an array of results in the same order — a single verb,
538
- * never `getMany` / `setAll`. Batches run as independent sequential operations;
539
- * wrap them in `transaction` for atomicity.
540
- */
541
- export interface TableInterface<T = Row> {
542
- readonly emitter: EmitterInterface<TableEventMap>;
543
- readonly name: string;
544
- readonly primary: string;
545
- readonly contract: ContractInterface<T>;
546
- get(key: Key): Promise<T | undefined>;
547
- get(keys: readonly Key[]): Promise<readonly (T | undefined)[]>;
548
- resolve(key: Key): Promise<T>;
549
- resolve(keys: readonly Key[]): Promise<readonly T[]>;
550
- has(key: Key): Promise<boolean>;
551
- has(keys: readonly Key[]): Promise<readonly boolean[]>;
552
- keys(): Promise<readonly Key[]>;
553
- records(criteria?: Criteria, options?: ReadOptions): Promise<readonly T[]>;
554
- count(criteria?: Criteria, options?: ReadOptions): Promise<number>;
555
- aggregate(operation: AggregateFunction, column: FieldPath, criteria?: Criteria, options?: ReadOptions): Promise<number | undefined>;
556
- /**
557
- * Lazy filtered iteration over the table's rows.
558
- *
559
- * @remarks
560
- * `criteria`'s `conditions` / `offset` / `limit` are honored lazily as rows
561
- * stream; `order` is intentionally IGNORED — streaming yields driver
562
- * key-order, sorted output is `records()`'s job. Breaking out of the
563
- * iteration early closes the underlying source. The signal (if any) is
564
- * checked before each yield.
565
- */
566
- scan(criteria?: Criteria, options?: ReadOptions): AsyncIterable<T>;
567
- /**
568
- * Upsert one or more rows.
569
- *
570
- * @param row - The row to upsert
571
- * @param options - Optional abort signal
572
- * @returns The row's key
573
- */
574
- set(row: T, options?: ReadOptions): Promise<Key>;
575
- /**
576
- * Upsert one or more rows.
577
- *
578
- * @param rows - The rows to upsert
579
- * @param options - Optional abort signal, checked at entry and between items
580
- * @returns Each row's key, in order
581
- *
582
- * @remarks
583
- * The signal (if any) is checked at entry and between items; an abort
584
- * surfaces as `DatabaseError` `ABORTED`. Already-applied items stay
585
- * applied — there is no rollback. Wrap in `transaction()` for atomicity.
586
- */
587
- set(rows: readonly T[], options?: ReadOptions): Promise<readonly Key[]>;
588
- /**
589
- * Insert one or more rows, throwing `CONFLICT` on a duplicate key.
590
- *
591
- * @param row - The row to insert
592
- * @param options - Optional abort signal
593
- * @returns The row's key
594
- */
595
- add(row: T, options?: ReadOptions): Promise<Key>;
596
- /**
597
- * Insert one or more rows, throwing `CONFLICT` on a duplicate key.
598
- *
599
- * @param rows - The rows to insert
600
- * @param options - Optional abort signal, checked at entry and between items
601
- * @returns Each row's key, in order
602
- *
603
- * @remarks
604
- * The signal (if any) is checked at entry and between items; an abort
605
- * surfaces as `DatabaseError` `ABORTED`. Already-applied items stay
606
- * applied — there is no rollback. Wrap in `transaction()` for atomicity.
607
- */
608
- add(rows: readonly T[], options?: ReadOptions): Promise<readonly Key[]>;
609
- /**
610
- * Apply a partial change to one or more rows.
611
- *
612
- * @param key - The key of the row to update
613
- * @param changes - The partial changes to apply
614
- * @param options - Optional abort signal
615
- * @returns `true` when the row existed and was updated
616
- */
617
- update(key: Key, changes: Partial<T>, options?: ReadOptions): Promise<boolean>;
618
- /**
619
- * Apply a partial change to one or more rows.
620
- *
621
- * @param keys - The keys of the rows to update
622
- * @param changes - The partial changes to apply to each row
623
- * @param options - Optional abort signal, checked at entry and between items
624
- * @returns Each row's update result, in order
625
- *
626
- * @remarks
627
- * The signal (if any) is checked at entry and between items; an abort
628
- * surfaces as `DatabaseError` `ABORTED`. Already-applied items stay
629
- * applied — there is no rollback. Wrap in `transaction()` for atomicity.
630
- */
631
- update(keys: readonly Key[], changes: Partial<T>, options?: ReadOptions): Promise<readonly boolean[]>;
632
- /**
633
- * Delete one or more rows.
634
- *
635
- * @param key - The key of the row to remove
636
- * @param options - Optional abort signal
637
- * @returns `true` when the row existed and was removed
638
- */
639
- remove(key: Key, options?: ReadOptions): Promise<boolean>;
640
- /**
641
- * Delete one or more rows.
642
- *
643
- * @param keys - The keys of the rows to remove
644
- * @param options - Optional abort signal, checked at entry and between items
645
- * @returns Each row's removal result, in order
646
- *
647
- * @remarks
648
- * The signal (if any) is checked at entry and between items; an abort
649
- * surfaces as `DatabaseError` `ABORTED`. Already-applied items stay
650
- * applied — there is no rollback. Wrap in `transaction()` for atomicity.
651
- */
652
- remove(keys: readonly Key[], options?: ReadOptions): Promise<readonly boolean[]>;
653
- clear(): Promise<void>;
654
- query(): QueryInterface<T>;
655
- cursor(): Promise<CursorInterface<T>>;
656
- }
657
- /**
658
- * A fluent query builder.
659
- *
660
- * @remarks
661
- * `where` / `and` / `or` open a {@link ClauseInterface} whose operator
662
- * closes the condition and returns the query. `filter` adds a post-fetch JS
663
- * predicate (applied after the backend read, before paging). The terminals
664
- * (`all` / `first` / `count` / the aggregates) execute against the table; each
665
- * call mutates and returns the same builder, so a chain reads as one statement.
666
- * Every `column` is a {@link FieldPath} — a string is one column, an array
667
- * descends a nested value.
668
- */
669
- export interface QueryInterface<T = Row> {
670
- where(column: FieldPath): ClauseInterface<T>;
671
- and(column: FieldPath): ClauseInterface<T>;
672
- or(column: FieldPath): ClauseInterface<T>;
673
- filter(predicate: (row: T) => boolean): QueryInterface<T>;
674
- ascending(column: FieldPath): QueryInterface<T>;
675
- descending(column: FieldPath): QueryInterface<T>;
676
- limit(count: number): QueryInterface<T>;
677
- offset(count: number): QueryInterface<T>;
678
- all(): Promise<readonly T[]>;
679
- first(): Promise<T | undefined>;
680
- count(): Promise<number>;
681
- /**
682
- * Lazy per-row evaluation of this query's conditions / filters / offset /
683
- * limit.
684
- *
685
- * @remarks
686
- * `order` and its comparators are IGNORED (streaming yields unsorted, as
687
- * rows are evaluated one at a time). Same abort semantics as
688
- * {@link TableInterface.scan}: the signal (if any) is checked before each
689
- * yield, and breaking out early closes the underlying source.
690
- */
691
- stream(options?: ReadOptions): AsyncIterable<T>;
692
- sum(column: FieldPath): Promise<number | undefined>;
693
- average(column: FieldPath): Promise<number | undefined>;
694
- minimum(column: FieldPath): Promise<number | undefined>;
695
- maximum(column: FieldPath): Promise<number | undefined>;
696
- aggregate(operation: AggregateFunction, column: FieldPath): Promise<number | undefined>;
697
- }
698
- /**
699
- * A pending condition opened by `where` / `and` / `or`.
700
- *
701
- * @remarks
702
- * Each operator records its condition against the query and returns the query,
703
- * so the chain continues fluently. `absent` / `present` take no operand;
704
- * `between` takes two; `any` / `none` take a list.
705
- */
706
- export interface ClauseInterface<T = Row> {
707
- equals(value: unknown): QueryInterface<T>;
708
- not(value: unknown): QueryInterface<T>;
709
- above(value: unknown): QueryInterface<T>;
710
- below(value: unknown): QueryInterface<T>;
711
- from(value: unknown): QueryInterface<T>;
712
- to(value: unknown): QueryInterface<T>;
713
- between(lower: unknown, upper: unknown): QueryInterface<T>;
714
- like(pattern: string): QueryInterface<T>;
715
- glob(pattern: string): QueryInterface<T>;
716
- starts(prefix: string): QueryInterface<T>;
717
- ends(suffix: string): QueryInterface<T>;
718
- any(values: readonly unknown[]): QueryInterface<T>;
719
- none(values: readonly unknown[]): QueryInterface<T>;
720
- absent(): QueryInterface<T>;
721
- present(): QueryInterface<T>;
722
- }
723
- /**
724
- * A forward row cursor for bulk in-place mutation.
725
- *
726
- * @remarks
727
- * Iterates a snapshot of the table's keys taken at creation; `update` and
728
- * `remove` act on the row at the current position through the owning table.
729
- * `done` is `true` once iteration has advanced past the last key.
730
- */
731
- export interface CursorInterface<T = Row> {
732
- readonly value: T | undefined;
733
- readonly index: number;
734
- readonly done: boolean;
735
- next(): Promise<void>;
736
- update(changes: Partial<T>): Promise<void>;
737
- remove(): Promise<void>;
738
- close(): void;
739
- }