@orkestrel/database 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1054 @@
1
+ import { AggregateFunction } from '../core/index.js';
2
+ import { AggregateFunction as AggregateFunction_2 } from '../core/index.js';
3
+ import { ColumnType } from '../core/index.js';
4
+ import { Condition } from '../core/index.js';
5
+ import { Criteria } from '../core/index.js';
6
+ import { Criteria as Criteria_2 } from '../core/index.js';
7
+ import { DriverInterface } from '../core/index.js';
8
+ import { DriverInterface as DriverInterface_2 } from '../core/index.js';
9
+ import { DriverMeta } from '../core/index.js';
10
+ import { FieldPath } from '@orkestrel/contract';
11
+ import { Key } from '../core/index.js';
12
+ import { Migration } from '../core/index.js';
13
+ import { MigrationStep } from '../core/index.js';
14
+ import { Order } from '../core/index.js';
15
+ import { Row } from '../core/index.js';
16
+ import { Row as Row_2 } from '../core/index.js';
17
+ import { TableSchema } from '../core/index.js';
18
+ import { TableSchema as TableSchema_2 } from '../core/index.js';
19
+ import { TransactionInterface } from '../core/index.js';
20
+
21
+ /**
22
+ * Compile an {@link AggregateFunction} over a {@link FieldPath} to its SQL
23
+ * aggregate expression — the SELECT body the SQLite driver's native `aggregate`
24
+ * runs.
25
+ *
26
+ * @remarks
27
+ * `count` → `COUNT(*)` (counting all matched ROWS, not non-null column values —
28
+ * mirroring the engine's `computeAggregate('count')`, which is `rows.length`); the
29
+ * numeric aggregates wrap the column's read expression (a flat column, or a nested
30
+ * `json_extract` path) in `SUM` / `AVG` / `MIN` / `MAX`. Over zero matched rows
31
+ * `COUNT(*)` is `0` and the numeric aggregates are SQL `NULL` (→ `undefined`),
32
+ * matching the engine.
33
+ *
34
+ * @param operation - The aggregate to compute
35
+ * @param column - The column (or nested path) to aggregate
36
+ * @returns The SQL aggregate expression
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * aggregateSQL('count', 'age') // 'COUNT(*)'
41
+ * aggregateSQL('sum', 'age') // 'SUM("age")'
42
+ * aggregateSQL('average', ['payload', 'score']) // 'AVG(json_extract("payload", \'$.score\'))'
43
+ * ```
44
+ */
45
+ export declare function aggregateSQL(operation: AggregateFunction, column: FieldPath): string;
46
+
47
+ /**
48
+ * Map a portable {@link ColumnType} to its SQLite column type.
49
+ *
50
+ * @remarks
51
+ * `text` / `json` → `TEXT` (JSON is stored as text and read back with
52
+ * `json_extract` for nested-field queries); `integer` / `boolean` → `INTEGER`
53
+ * (a boolean stores `1` / `0`); `real` → `REAL`; `blob` → `BLOB`. No `NOT NULL`
54
+ * is ever emitted — the contract validates required-ness; the database is just
55
+ * storage (AGENTS §14, the typed layer above imposes the shape).
56
+ *
57
+ * @param type - The portable column type
58
+ * @returns The SQLite column type keyword
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * columnSQL('integer') // 'INTEGER'
63
+ * columnSQL('json') // 'TEXT'
64
+ * ```
65
+ */
66
+ export declare function columnSQL(type: ColumnType): string;
67
+
68
+ /**
69
+ * Compile a {@link Criteria} into the SQL clause that follows a table name, with
70
+ * its bound parameters in clause order.
71
+ *
72
+ * @remarks
73
+ * The driver's native `records` / `count` path: it assembles
74
+ * `[where, orderBy, limitOffset]` (each possibly empty) into one clause so a
75
+ * `SELECT * FROM <table> <clause>` runs the whole read in the engine instead of
76
+ * over a JS `scan`. The WHERE fold is parenthesized **left-to-right** to mirror
77
+ * the core engine's `matchesCriteria` (not SQL's native AND-over-OR precedence),
78
+ * so a native and an engine read return identical rows. Each operand is encoded
79
+ * via `encodeValue`: a flat column uses its declared schema type, while a nested
80
+ * `FieldPath` (a `json_extract` read) encodes each operand as the native scalar
81
+ * the extract returns — derived from the operand's runtime type — so it compares.
82
+ * The 15 operators map per the databases guide's operator table, with
83
+ * `starts` / `ends` using `LIKE … ESCAPE '\'` and an empty `any` / `none` list
84
+ * collapsing to a constant. A `undefined` criteria (or one with no parts)
85
+ * compiles to an empty clause.
86
+ *
87
+ * @param criteria - The read specification, or `undefined` for all rows
88
+ * @param schema - The table's schema (column types for operand encoding)
89
+ * @returns The SQL tail and its bound parameters
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * compileCriteria({ conditions: [{ column: 'age', operator: 'from', values: [18], connector: 'and' }] }, schema)
94
+ * // { sql: 'WHERE "age" >= ? ORDER BY "id"', params: [18] }
95
+ * ```
96
+ */
97
+ export declare function compileCriteria(criteria: Criteria | undefined, schema: TableSchema): CompiledSQL;
98
+
99
+ /**
100
+ * A parameterized SQL fragment or statement plus its bind values.
101
+ *
102
+ * @remarks
103
+ * Produced by the pure SQL compilers (`compilers.ts`) that turn a core
104
+ * `Criteria` (or a table definition) into SQL text with `?` placeholders, and
105
+ * consumed by the SQLite driver, which runs `sql` with `params` bound in
106
+ * order — no further assembly. Keeping `sql` and `params` together prevents
107
+ * the two from drifting apart across compile and execute.
108
+ */
109
+ export declare interface CompiledSQL {
110
+ readonly sql: string;
111
+ readonly params: readonly SQLiteValue[];
112
+ }
113
+
114
+ /**
115
+ * Compile the ORDER BY clause from the order terms, always ending with the
116
+ * primary key as the final determinant.
117
+ *
118
+ * @remarks
119
+ * The native `records` read then resolves ties in key order, matching a
120
+ * primary-key-ordered `scan` and the core engine's stable `sortRows` over a
121
+ * key-ordered scan (and IndexedDB's key-ordered reads), so a native read equals
122
+ * the scan path (AGENTS §21 / §22 native ↔ engine parity). SQLite without an
123
+ * `ORDER BY` returns rowid (insertion) order, and an explicit order alone breaks
124
+ * ties by rowid too — both diverge from every key-ordered backend. The
125
+ * tie-breaker is ASCENDING regardless of the explicit directions: the engine's
126
+ * stable sort runs over key-ascending input, so equal rows stay in
127
+ * ascending-key order whichever way the explicit terms point. Skipped when the
128
+ * primary is already an explicit order term (no double-append).
129
+ *
130
+ * @param order - The explicit order terms, or `undefined`
131
+ * @param schema - The table's schema (for the primary key)
132
+ * @returns The `ORDER BY …` clause, or an empty string when there is nothing to order by
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * compileOrder([{ column: 'age', direction: 'descending' }], schema)
137
+ * // 'ORDER BY "age" DESC, "id"'
138
+ * ```
139
+ */
140
+ export declare function compileOrder(order: readonly Order[] | undefined, schema: TableSchema): string;
141
+
142
+ /**
143
+ * Compile the LIMIT / OFFSET clause.
144
+ *
145
+ * @remarks
146
+ * An offset without a limit uses `LIMIT -1` (SQLite's "no limit") so OFFSET is
147
+ * still honored.
148
+ *
149
+ * @param limit - The maximum row count, or `undefined`
150
+ * @param offset - The row count to skip, or `undefined`
151
+ * @returns The `LIMIT …` clause and its bound parameters, or an empty clause when neither is set
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * compilePage(undefined, 5) // { sql: 'LIMIT -1 OFFSET ?', params: [5] }
156
+ * ```
157
+ */
158
+ export declare function compilePage(limit: number | undefined, offset: number | undefined): CompiledSQL;
159
+
160
+ /**
161
+ * Fold the conditions into one WHERE clause, parenthesizing progressively
162
+ * left-to-right so the grouping matches the engine's `matchesCriteria` fold.
163
+ *
164
+ * @remarks
165
+ * The first condition's connector is ignored, per the {@link Condition} types.
166
+ * Every fragment (see {@link fragment}'s truth table) replicates the core
167
+ * engine's total order EXACTLY under SQL's three-valued NULL logic, so this
168
+ * clause matches `applyCriteria` row-for-row over the same table — a native
169
+ * `records` / `count` read never disagrees with a scan-and-filter fallback.
170
+ *
171
+ * @param conditions - The conditions to fold
172
+ * @param schema - The table's schema
173
+ * @returns The `WHERE …` clause and its bound parameters, or an empty clause for zero conditions
174
+ *
175
+ * @example
176
+ * ```ts
177
+ * compileWhere([{ column: 'age', operator: 'from', values: [18], connector: 'and' }], schema)
178
+ * // { sql: 'WHERE "age" >= ?', params: [18] }
179
+ * ```
180
+ */
181
+ export declare function compileWhere(conditions: readonly Condition[], schema: TableSchema): CompiledSQL;
182
+
183
+ /**
184
+ * Create a persistent JSON-file {@link DriverInterface} for the core database layer.
185
+ *
186
+ * @remarks
187
+ * Pass it to `createDatabase` from `@orkestrel/database` to run the whole typed database +
188
+ * relations stack against a single JSON file instead of memory — the `Database` /
189
+ * `Table` / `Query` / relations API is unchanged; only where the bytes live changes.
190
+ * The driver is the reference `MemoryDriver` plus JSON-file persistence: `open` loads
191
+ * the file, every mutation flushes the whole store back, and querying runs through
192
+ * the core engine over `scan` (it is scan-only — no native `records` / `count` /
193
+ * `aggregate`). A missing, corrupt, or wrong-shaped file starts empty rather than
194
+ * throwing.
195
+ *
196
+ * @param path - The JSON file path data is loaded from and flushed to
197
+ * @returns A {@link DriverInterface} backed by a JSON file
198
+ *
199
+ * @example
200
+ * ```ts
201
+ * import { createDatabase } from '@orkestrel/database'
202
+ * import { stringShape } from '@orkestrel/contract'
203
+ * import { createJSONDriver } from '@orkestrel/database/server'
204
+ *
205
+ * const db = createDatabase({
206
+ * driver: createJSONDriver('data/app.json'),
207
+ * tables: { users: { id: stringShape(), name: stringShape() } },
208
+ * })
209
+ * await db.table('users').set({ id: 'u1', name: 'Ada' }) // persisted to app.json
210
+ * ```
211
+ */
212
+ export declare function createJSONDriver(path: string): DriverInterface;
213
+
214
+ /**
215
+ * Create a trusted-mode SQLite {@link DriverInterface} for the core database layer.
216
+ *
217
+ * @remarks
218
+ * Pass it to `createDatabase` from `@orkestrel/database` to run the whole typed database +
219
+ * relations stack against a real SQLite database — the `Database` / `Table` /
220
+ * `Query` / relations API is unchanged; only where the bytes live changes. Built
221
+ * on the published `@orkestrel/sqlite` synchronous wrapper: `open` issues real
222
+ * typed `CREATE TABLE` / `CREATE INDEX` statements (reopen-safe) plus a reserved
223
+ * `_meta` table for `meta()` / `stamp()` — avoid naming a table `_meta`.
224
+ * Querying, paging, and aggregation run natively (`records` / `count` /
225
+ * `aggregate` / `stream`); `transaction` and `migrate` use real `BEGIN` /
226
+ * `COMMIT` / `ROLLBACK`, so `migrate` is atomic even mid-plan.
227
+ *
228
+ * @param options - A bare database file path (`':memory:'` by default, for
229
+ * back-compat), or a full {@link SQLiteDriverOptions} bag (`path`,
230
+ * `readonly`, `timeout`, `foreignKeys`, `pragmas`)
231
+ * @returns A {@link DriverInterface} backed by SQLite
232
+ *
233
+ * @example
234
+ * ```ts
235
+ * import { createDatabase } from '@orkestrel/database'
236
+ * import { stringShape } from '@orkestrel/contract'
237
+ * import { createSQLiteDriver } from '@orkestrel/database/server'
238
+ *
239
+ * const db = createDatabase({
240
+ * driver: createSQLiteDriver('data/app.sqlite'),
241
+ * tables: { users: { id: stringShape(), name: stringShape() } },
242
+ * })
243
+ * await db.table('users').set({ id: 'u1', name: 'Ada' }) // persisted to app.sqlite
244
+ *
245
+ * // Or with options:
246
+ * createSQLiteDriver({ path: 'data/app.sqlite', pragmas: { journal_mode: 'WAL' } })
247
+ * ```
248
+ */
249
+ export declare function createSQLiteDriver(options?: string | SQLiteDriverOptions): DriverInterface;
250
+
251
+ /**
252
+ * The declared storage type of a flat (string) column, read from the schema.
253
+ *
254
+ * @param column - The column name
255
+ * @param schema - The table's schema
256
+ * @returns The column's {@link ColumnType}, or `undefined` if the schema does not carry it
257
+ *
258
+ * @example
259
+ * ```ts
260
+ * declaredType('age', schema) // 'integer'
261
+ * ```
262
+ */
263
+ export declare function declaredType(column: string, schema: TableSchema): ColumnType | undefined;
264
+
265
+ /**
266
+ * Decode a stored {@link SQLiteRow} back to a {@link Row} by its table's schema.
267
+ *
268
+ * @remarks
269
+ * Decodes each declared column with {@link decodeValue} and **omits** any column
270
+ * whose decoded value is `undefined` — so an absent / `NULL` optional column does
271
+ * not surface as `{ bio: undefined }`, matching how the contract's optional
272
+ * columns expect absence. A known, documented edge: a non-optional `nullableShape`
273
+ * column storing `null` round-trips to absent (a `null` cell decodes to
274
+ * `undefined`, and an `undefined` value is omitted).
275
+ *
276
+ * @param row - The stored SQLite row
277
+ * @param schema - The table's schema
278
+ * @returns The decoded JS row (absent columns omitted)
279
+ *
280
+ * @example
281
+ * ```ts
282
+ * decodeRow({ id: 'u1', active: 1, bio: null }, schema) // { id: 'u1', active: true }
283
+ * ```
284
+ */
285
+ export declare function decodeRow(row: SQLiteRow, schema: TableSchema): Row;
286
+
287
+ /**
288
+ * Decode a stored {@link SQLiteValue} back to its JS value for a column's type —
289
+ * the exact inverse of {@link encodeValue}.
290
+ *
291
+ * @remarks
292
+ * A `boolean` column reads `1` / `0` back to `true` / `false` (a stored `null`
293
+ * → `undefined`); a `json` column `JSON.parse`s a string (anything else →
294
+ * `undefined`); every other type passes the value through, mapping a stored
295
+ * `NULL` to `undefined`. NULL decodes to `undefined` so {@link decodeRow} can
296
+ * omit absent columns.
297
+ *
298
+ * @param value - The stored SQLite value
299
+ * @param type - The column's portable storage type
300
+ * @returns The decoded JS value (`undefined` for a stored `NULL`)
301
+ *
302
+ * @example
303
+ * ```ts
304
+ * decodeValue(1, 'boolean') // true
305
+ * decodeValue('{"a":1}', 'json') // { a: 1 }
306
+ * ```
307
+ */
308
+ export declare function decodeValue(value: SQLiteValue, type: ColumnType): unknown;
309
+
310
+ /**
311
+ * Encode a whole {@link Row} to a {@link SQLiteRow} by its table's schema.
312
+ *
313
+ * @remarks
314
+ * Encodes each declared column's value with {@link encodeValue}; columns the row
315
+ * does not carry encode from `undefined` (so they store `null`). Only the
316
+ * schema's columns appear in the result — an extra row key is dropped.
317
+ *
318
+ * @param row - The JS row to store
319
+ * @param schema - The table's schema
320
+ * @returns The storable SQLite row
321
+ *
322
+ * @example
323
+ * ```ts
324
+ * encodeRow({ id: 'u1', active: true }, schema) // { id: 'u1', active: 1, ... }
325
+ * ```
326
+ */
327
+ export declare function encodeRow(row: Row, schema: TableSchema): SQLiteRow;
328
+
329
+ /**
330
+ * Encode a JS value to its stored {@link SQLiteValue} for a column's type.
331
+ *
332
+ * @remarks
333
+ * The forward half of the bridge, total (AGENTS §14): a value that does not fit
334
+ * its column's storage type encodes to `null` rather than throwing. A `boolean`
335
+ * column stores `1` / `0` (and `null` / `undefined` → `null`); a `json` column
336
+ * stores `JSON.stringify` (or `null` for `null` / `undefined`); `integer` /
337
+ * `real` keep a `number` / `bigint`, else `null`; `text` keeps a `string`, else
338
+ * `null`; `blob` keeps a `Uint8Array`, else `null`. Narrowed with `typeof` /
339
+ * `instanceof`, never `as`.
340
+ *
341
+ * @param value - The JS value to store
342
+ * @param type - The column's portable storage type
343
+ * @returns The value SQLite stores
344
+ *
345
+ * @example
346
+ * ```ts
347
+ * encodeValue(true, 'boolean') // 1
348
+ * encodeValue({ a: 1 }, 'json') // '{"a":1}'
349
+ * ```
350
+ */
351
+ export declare function encodeValue(value: unknown, type: ColumnType): SQLiteValue;
352
+
353
+ /**
354
+ * Escape `\`, `%`, and `_` (each with a leading `\`) so a `starts` / `ends`
355
+ * operand is matched literally under the `LIKE … ESCAPE '\'` clause.
356
+ *
357
+ * @param text - The raw operand text
358
+ * @returns The text with LIKE metacharacters escaped
359
+ *
360
+ * @example
361
+ * ```ts
362
+ * escapeLike('50%_off') // '50\\%\\_off'
363
+ * ```
364
+ */
365
+ export declare function escapeLike(text: string): string;
366
+
367
+ /**
368
+ * The declared {@link ColumnType}s whose SQL EQUALITY comparisons (`equals` /
369
+ * `not` / `any` / `none`) and `starts` / `ends` compiles are provably
370
+ * engine-exact under declared-type trust — `text` / `integer` / `real` /
371
+ * `boolean`; a `json` or `blob` column always refines instead.
372
+ *
373
+ * @remarks
374
+ * This set governs equality and prefix/suffix matching only. RANGE
375
+ * comparisons (`above` / `below` / `from` / `to` / `between`) and `ORDER BY`
376
+ * are exact for `integer` / `real` / `boolean` but NOT for `text`: compiled
377
+ * SQL orders/ranges under SQLite's default BINARY collation, which compares
378
+ * TEXT byte-for-byte as UTF-8 — equivalent to Unicode CODE-POINT order —
379
+ * while the core engine's `compareValues` orders JS strings with `<`, which
380
+ * compares UTF-16 CODE-UNIT order. The two orders diverge for supplementary-
381
+ * plane characters (code points ≥ U+10000, e.g. many emoji): a lead surrogate
382
+ * (`\uD800`–`\uDBFF`) sorts BELOW ``–`￿` in code-unit order, while
383
+ * its code point sorts ABOVE them. So `isExactCondition`'s range family and
384
+ * `isExactOrder` exclude `text`, refining through the core engine instead. A
385
+ * future opt-in "trusted collation" mode (the caller vouches the column's
386
+ * values are BMP-only, or a custom SQLite collation matching `compareValues`
387
+ * is registered) could restore native text ranges/ordering.
388
+ */
389
+ export declare const EXACT_COLUMN_TYPES: readonly ColumnType[];
390
+
391
+ /**
392
+ * The declared {@link ColumnType}s whose SQL RANGE comparisons
393
+ * (`above` / `below` / `from` / `to` / `between`) and `ORDER BY` compiles are
394
+ * provably engine-exact — `integer` / `real` / `boolean` only. `text` is
395
+ * excluded: see {@link EXACT_COLUMN_TYPES}'s remarks for the BINARY-collation
396
+ * (code-point) vs. JS `<` (code-unit) divergence on supplementary-plane
397
+ * characters.
398
+ */
399
+ export declare const EXACT_RANGE_COLUMN_TYPES: readonly ColumnType[];
400
+
401
+ /**
402
+ * Compile a {@link FieldPath} to the SQL expression that reads it.
403
+ *
404
+ * @remarks
405
+ * A single string is ONE column — `quote(path)`. An array descends a JSON column:
406
+ * the first element is the (quoted) column, the rest a `json_extract` path
407
+ * (`json_extract("payload", '$.user.id')`), matching the guide's nested-field
408
+ * examples (simple identifier keys). The string's value is never split on `.`
409
+ * (AGENTS — `FieldPath` semantics): a dotted string is one column literally.
410
+ *
411
+ * @param path - The field path (a column, or a column + nested keys)
412
+ * @returns The SQL expression selecting the value
413
+ *
414
+ * @example
415
+ * ```ts
416
+ * fieldColumn('payload') // '"payload"'
417
+ * fieldColumn(['payload', 'user', 'id']) // 'json_extract("payload", \'$.user.id\')'
418
+ * ```
419
+ */
420
+ export declare function fieldColumn(path: FieldPath): string;
421
+
422
+ /**
423
+ * Compile one condition to its `<column> <operator>` SQL fragment and the params
424
+ * it binds — engine-exact under SQL's three-valued NULL logic.
425
+ *
426
+ * @remarks
427
+ * Every operand is run through `encodeValue`, so a bound value matches the SQL
428
+ * the column side compiles to. A flat column encodes operands with its DECLARED
429
+ * schema type (a flat `json` column → `JSON.stringify`); a nested `FieldPath`
430
+ * encodes each operand as the NATIVE scalar `json_extract` returns, derived from
431
+ * the operand's runtime type (per-operand, since `between` / `any` / `none` can
432
+ * mix types). `any` / `none` collapse an empty list to a constant (`0` matches
433
+ * nothing, `1` matches all) with no params.
434
+ *
435
+ * The core engine's total order ranks `undefined` (rank 0) BELOW `null`
436
+ * (rank 1) (see `compareValues`), so a MISSING/`NULL` column MATCHES
437
+ * `below` / `to` / a scalar `not` / `none` — the opposite of raw SQL, where a
438
+ * comparison against `NULL` is `NULL` (excluded). This fragment replicates the
439
+ * engine exactly. Truth table (`value` = the engine's decoded field read; a
440
+ * FLAT column's stored `NULL` decodes to `undefined` per `decodeRow`, so a
441
+ * flat `value` is NEVER a present `null` — only a NESTED path can be
442
+ * present-but-`null`):
443
+ *
444
+ * ```text
445
+ * operator | value=undefined (absent) | value=null (nested only) | value=scalar
446
+ * --------------------|--------------------------------|---------------------------|-------------
447
+ * equals, first=null | no match | MATCH | no match
448
+ * equals, first=X | no match | no match | value===X
449
+ * not, first=null | MATCH (flat: unconditionally; | no match | MATCH
450
+ * | nested: absent still matches)| |
451
+ * not, first=X | MATCH | MATCH | value!==X
452
+ * below/to, first=X | MATCH (rank 0 < rank(X)) | MATCH (rank 1 < rank(X)) | rank compare
453
+ * none, list=[…] | MATCH (no scalar rank-equal) | MATCH | not-in-list
454
+ * any, list=[…] | no match | no match | in-list
455
+ * above/from/between | no match | no match | rank compare
456
+ * like/glob/starts/… | no match (not a string) | no match | string test
457
+ * present | false | false | true
458
+ * absent | true | true | false
459
+ * ```
460
+ *
461
+ * Because a flat column's `NULL` always decodes to `undefined`, `equals`
462
+ * against a `null` operand needs no special flat compilation (`col = ?`
463
+ * binding a `NULL` param is already always-false in SQL, matching "no match"
464
+ * above) — but flat `not` against `null` must match EVERY row (both the
465
+ * absent and the scalar rows), which `col != ? OR col IS NULL` cannot express
466
+ * (it only catches the `IS NULL` row), so a flat `not`-with-`null`-operand
467
+ * compiles to the constant `1`.
468
+ *
469
+ * A NESTED path can be present-but-`null` (a stored JSON `null`), which
470
+ * `json_extract` reads back as SQL `NULL` — indistinguishable from an ABSENT
471
+ * path. `json_type(col, path)` disambiguates them (`'null'` for present-null,
472
+ * SQL `NULL` for absent), so nested `equals` / `not` against a `null` operand
473
+ * compile through `json_type` instead of `IS NULL` / `IS NOT NULL`.
474
+ *
475
+ * Every other MATCH-on-null-or-absent row is expressed uniformly (flat and
476
+ * nested alike) as `(<column> <op> ? OR <column> IS NULL)` — for a nested
477
+ * path, `json_extract` already collapses BOTH absent and present-null to SQL
478
+ * `NULL`, so `IS NULL` catches both in one clause; for a flat column there is
479
+ * only the absent case to catch.
480
+ *
481
+ * @param condition - The condition to compile
482
+ * @param schema - The table's schema (for declared column types)
483
+ * @returns The SQL fragment and its bound parameters
484
+ *
485
+ * @example
486
+ * ```ts
487
+ * fragment({ column: 'age', operator: 'above', values: [18], connector: 'and' }, schema)
488
+ * // { sql: '"age" > ?', params: [18] }
489
+ * fragment({ column: 'age', operator: 'below', values: [18], connector: 'and' }, schema)
490
+ * // { sql: '("age" < ? OR "age" IS NULL)', params: [18] }
491
+ * ```
492
+ */
493
+ export declare function fragment(condition: Condition, schema: TableSchema): CompiledSQL;
494
+
495
+ /**
496
+ * Generate a fresh unique key — a v4 UUID string, backed by `node:crypto`.
497
+ *
498
+ * @remarks
499
+ * Supply this as {@link import('@orkestrel/database').DatabaseOptions.key} so a table mints
500
+ * a key when a written row lacks its primary-key value. Strings work as keys on
501
+ * every backend; supply your own key values directly to use numeric keys instead.
502
+ *
503
+ * @returns A new UUID string
504
+ *
505
+ * @example
506
+ * ```ts
507
+ * const db = createDatabase({ driver, tables, key: generateKey })
508
+ * ```
509
+ */
510
+ export declare function generateKey(): string;
511
+
512
+ /**
513
+ * Build a collision-free SQL index name for a table + column-group index —
514
+ * shared by {@link schemaToIndexes} (an `open`-time `CREATE INDEX`) and
515
+ * {@link stepToSQL}'s `index.add` / `index.remove` (a migration-time DDL),
516
+ * so a plan-built index name always matches one `open` would have created.
517
+ *
518
+ * @remarks
519
+ * A naive `idx_<table>_<cols joined by _>` is AMBIGUOUS: table `'a_b'` with
520
+ * column `'c'` and table `'a'` with columns `['b', 'c']` both produce
521
+ * `idx_a_b_c`. This encodes each part (the table name, then each column name)
522
+ * length-prefixed (`<len>_<part>`) so the boundary between parts is always
523
+ * unambiguous, however the names themselves are punctuated.
524
+ *
525
+ * @param table - The table name
526
+ * @param columns - The index's column names, in order
527
+ * @returns The deterministic, collision-free index identifier (unquoted)
528
+ *
529
+ * @example
530
+ * ```ts
531
+ * indexName('users', ['name']) // 'idx_5_users_4_name'
532
+ * indexName('a_b', ['c']) // 'idx_3_a_b_1_c'
533
+ * indexName('a', ['b', 'c']) // 'idx_1_a_1_b_1_c'
534
+ * ```
535
+ */
536
+ export declare function indexName(table: string, columns: readonly string[]): string;
537
+
538
+ /**
539
+ * Whether one {@link Condition} compiles to SQL that is PROVABLY identical to
540
+ * the core engine's `matchesCondition` for every value its column's declared
541
+ * type can store.
542
+ *
543
+ * @remarks
544
+ * `false` for a nested `FieldPath` (an array), a column absent from `schema`,
545
+ * or a column whose declared type is not `text` / `integer` / `real` /
546
+ * `boolean` (a `json` / `blob` column) — EXCEPT `absent` / `present`, which
547
+ * compile to `IS NULL` / `IS NOT NULL` and match `decodeRow`'s "a stored NULL
548
+ * decodes to `undefined`" rule for every column type, so they are exact
549
+ * regardless of declared type. `equals` / `not` require a operand matching the
550
+ * column's declared type (a `null` / `undefined` operand is never exact here —
551
+ * `encodeRow` stores both an explicit `null` and an absent field as SQL NULL,
552
+ * so native `IS NULL` semantics cannot match the engine's `deepEqual`-over-
553
+ * decoded-rows truth). `above` / `below` / `from` / `to` / `between` are exact
554
+ * ONLY for a declared type in {@link EXACT_RANGE_COLUMN_TYPES} (`integer` /
555
+ * `real` / `boolean`) — a `text` column's range conditions REFINE, because
556
+ * SQLite's default BINARY collation orders TEXT by Unicode CODE POINT while
557
+ * the core engine's `compareValues` orders JS strings by UTF-16 CODE UNIT,
558
+ * and the two diverge for supplementary-plane characters (see
559
+ * {@link EXACT_COLUMN_TYPES}'s remarks for the full rationale).
560
+ * `any` / `none` require a NON-EMPTY list where every element matches (an empty
561
+ * list is exact under neither: the engine's `any([])` matches nothing while
562
+ * `none([])` matches everything, and SQL `IN ()` is a syntax error) — these
563
+ * stay exact on `text` (byte equality is collation-independent and engine-
564
+ * identical). `starts` / `ends` are exact only on a `text` column with a
565
+ * string operand (case-sensitive `substr` compile, see {@link fragment}) —
566
+ * likewise collation-independent. `like` / `glob` are NEVER exact — SQLite
567
+ * `LIKE` folds case ASCII-only against the engine's Unicode fold, and `GLOB`
568
+ * has character classes the engine treats literally.
569
+ *
570
+ * @param condition - The condition to test
571
+ * @param schema - The table's schema
572
+ * @returns Whether `condition` is exact
573
+ */
574
+ export declare function isExactCondition(condition: Condition, schema: TableSchema): boolean;
575
+
576
+ /**
577
+ * Whether a whole {@link Criteria} is exact — every condition and every order
578
+ * term is exact. `limit` / `offset` never affect exactness (SQL `LIMIT` /
579
+ * `OFFSET` are always engine-identical).
580
+ *
581
+ * @param criteria - The criteria to test
582
+ * @param schema - The table's schema
583
+ * @returns Whether every part of `criteria` is exact
584
+ */
585
+ export declare function isExactCriteria(criteria: Criteria, schema: TableSchema): boolean;
586
+
587
+ /**
588
+ * Whether one {@link Order} term's column compiles to an `ORDER BY` that
589
+ * matches the engine's {@link import('../core/index.js').sortRows} exactly.
590
+ *
591
+ * @remarks
592
+ * `false` for a nested `FieldPath`, a column absent from `schema`, or a
593
+ * declared type outside {@link EXACT_RANGE_COLUMN_TYPES} (`integer` / `real` /
594
+ * `boolean`). `text` is NOT exact here: SQLite's default BINARY collation
595
+ * orders TEXT by Unicode code point while the core engine's `compareValues`
596
+ * orders JS strings by UTF-16 code unit, and the two diverge for
597
+ * supplementary-plane characters (see {@link EXACT_COLUMN_TYPES}'s remarks) —
598
+ * a `text` order term REFINES through the core engine instead.
599
+ *
600
+ * @param order - The order term to test
601
+ * @param schema - The table's schema
602
+ * @returns Whether `order` is exact
603
+ */
604
+ export declare function isExactOrder(order: Order, schema: TableSchema): boolean;
605
+
606
+ /**
607
+ * A persistent {@link DriverInterface} backed by a single JSON file — the
608
+ * reference {@link MemoryDriver} plus file load / flush.
609
+ *
610
+ * @remarks
611
+ * A decorator, not a reimplementation: every primitive delegates to an inner
612
+ * {@link MemoryDriver}, so querying, key-order `scan` / `keys`, and capture-replay
613
+ * `snapshot` are inherited unchanged — this layer adds only persistence. `open`
614
+ * loads the file into memory; every mutation (`write` / `delete` / `clear`) flushes
615
+ * the whole store back. The file is one JSON object, `{ meta?: DriverMeta, tables: {
616
+ * [name]: rows } }` — `meta` is present only once the store has been `stamp`ed
617
+ * (an unstamped store serializes the old `{ tables }` shape, preserving
618
+ * backward compatibility); a per-table array of rows, each row carrying its own
619
+ * primary (the table contract), so the key is recovered on load with
620
+ * {@link extractKey} and the file need not store it. The parsed JSON crosses the
621
+ * boundary as `unknown` and is narrowed with {@link isRecord} / {@link extractKey},
622
+ * never asserted (AGENTS §14): a missing, corrupt, or wrong-shaped file starts
623
+ * empty rather than throwing, and a malformed row (or malformed `meta`) is
624
+ * skipped/dropped rather than thrown on. It is scan-only — it implements none of
625
+ * the optional native `records` / `count` / `aggregate` hooks, so the core engine
626
+ * over `scan` answers every query. For development, small datasets, and portable /
627
+ * inspectable data; for large or concurrent workloads reach for a SQLite-backed
628
+ * driver.
629
+ *
630
+ * A failure in the write path ({@link JSONDriver.#serialize} — `mkdir` /
631
+ * `writeFile` / `rename`) is wrapped and rethrown as `DatabaseError` `DRIVER`,
632
+ * carrying the target `path` in its context; the read path ({@link
633
+ * JSONDriver.#load}) tolerance above is a separate, deliberate contract and is
634
+ * never touched by this wrapping.
635
+ */
636
+ export declare class JSONDriver implements DriverInterface_2 {
637
+ #private;
638
+ constructor(path: string);
639
+ open(schema: readonly TableSchema_2[]): Promise<void>;
640
+ close(): Promise<void>;
641
+ read(table: string, key: Key): Promise<Row_2 | undefined>;
642
+ write(table: string, key: Key, row: Row_2): Promise<void>;
643
+ delete(table: string, key: Key): Promise<boolean>;
644
+ keys(table: string): Promise<readonly Key[]>;
645
+ scan(table: string): AsyncIterable<Row_2>;
646
+ /**
647
+ * Natively filtered lazy iteration — delegates to the inner {@link MemoryDriver}.
648
+ *
649
+ * @remarks
650
+ * Semantics are the memory driver's own: `criteria.conditions` filters, `offset`
651
+ * / `limit` page lazily, and `criteria.order` is ignored (streaming yields key
652
+ * order; sorted output is `records()`'s job).
653
+ *
654
+ * @param table - The table to stream
655
+ * @param criteria - The filter / offset / limit to apply lazily
656
+ */
657
+ stream(table: string, criteria: Criteria_2): AsyncIterable<Row_2>;
658
+ clear(table: string): Promise<void>;
659
+ /**
660
+ * Begin a native transaction — flush-coalescing over the inner {@link MemoryDriver}.
661
+ *
662
+ * @remarks
663
+ * Single-writer: throws `DatabaseError` `CONFLICT` if a transaction is already
664
+ * active — this driver does not support nesting. On begin, captures the inner
665
+ * memory rollback thunk via `#memory.snapshot()` and suppresses per-mutation
666
+ * `#flush` — `write` / `delete` / `clear` still mutate memory but no longer
667
+ * touch the file, so N mutations under the handle cost ONE file write instead
668
+ * of N. `commit()` releases the suppression and performs that one atomic
669
+ * `#flush()`, persisting the transaction's net state. `rollback()` restores
670
+ * memory via the captured snapshot thunk, then `#flush()`s so the file reflects
671
+ * the restored state. Outside a transaction, behavior is unchanged — every
672
+ * mutation flushes on its own. Calling `commit` / `rollback` a second time (on
673
+ * either method, in either order) throws `DatabaseError` `CONFLICT`.
674
+ *
675
+ * @returns A {@link TransactionInterface} handle to `commit` or `rollback`
676
+ */
677
+ transaction(): Promise<TransactionInterface>;
678
+ snapshot(tables?: readonly string[]): Promise<() => Promise<void>>;
679
+ meta(): Promise<DriverMeta | undefined>;
680
+ /**
681
+ * Persist `meta` verbatim for a later `meta()` to return.
682
+ *
683
+ * @remarks
684
+ * Respects the same defer-flush suppression as `write` / `delete` / `clear`
685
+ * (see {@link JSONDriver.transaction} @remarks) — stamping inside an active
686
+ * transaction updates memory but does not flush until the transaction settles.
687
+ *
688
+ * @param meta - The {@link DriverMeta} to persist
689
+ */
690
+ stamp(meta: DriverMeta): Promise<void>;
691
+ /**
692
+ * Apply a {@link Migration} plan by delegating to the inner {@link MemoryDriver},
693
+ * then persist the migrated state.
694
+ *
695
+ * @remarks
696
+ * The inner `MemoryDriver.migrate` applies each step (adding/removing tables,
697
+ * adding/removing columns from stored rows, no-op index steps) and throws
698
+ * `DatabaseError` `MIGRATION` for a step referencing an unknown table — that
699
+ * error propagates untouched. `table.add` / `table.remove` steps also update
700
+ * this driver's own declared `#schema`, mirroring the bookkeeping `open` does,
701
+ * so a subsequent `#flush` / `#load` round-trip includes (or drops) the table.
702
+ * A successful migration ends with one atomic `#flush()` so the new state
703
+ * survives a close and reopen. A multi-step plan applies its steps
704
+ * sequentially and is NOT atomic — a failure partway through a plan leaves
705
+ * the earlier steps already applied.
706
+ *
707
+ * @param plan - The migration plan to apply
708
+ */
709
+ migrate(plan: Migration): Promise<void>;
710
+ }
711
+
712
+ /**
713
+ * Compile a NESTED {@link FieldPath} to the `json_type(<col>, <path>)` SQL
714
+ * expression — the {@link fieldColumn} `json_extract` sibling used to tell a
715
+ * PRESENT JSON `null` apart from an ABSENT path (both read back as SQL `NULL`
716
+ * through `json_extract`, but `json_type` reports `'null'` for the former and
717
+ * SQL `NULL` for the latter).
718
+ *
719
+ * @param path - The nested field path (a column plus its JSON keys)
720
+ * @returns The SQL expression reading the value's JSON type
721
+ *
722
+ * @example
723
+ * ```ts
724
+ * jsonTypeColumn(['payload', 'user', 'id']) // "json_type(\"payload\", '$.user.id')"
725
+ * ```
726
+ */
727
+ export declare function jsonTypeColumn(path: readonly string[]): string;
728
+
729
+ /**
730
+ * Whether a value's runtime type matches a column's declared exact type —
731
+ * the operand side of the declared-type-trust proof.
732
+ *
733
+ * @remarks
734
+ * `text` ↔ string, `integer` / `real` ↔ FINITE number (`NaN` / `±Infinity`
735
+ * fail), `boolean` ↔ boolean. Backs {@link isExactCondition}'s operand checks.
736
+ *
737
+ * @param value - The condition operand to test
738
+ * @param type - The column's declared portable type
739
+ * @returns `true` when the operand's runtime type matches the declared type
740
+ *
741
+ * @example
742
+ * ```ts
743
+ * matchesDeclaredType('Ada', 'text') // true
744
+ * matchesDeclaredType(Number.NaN, 'integer') // false — only finite numbers
745
+ * ```
746
+ */
747
+ export declare function matchesDeclaredType(value: unknown, type: ColumnType): boolean;
748
+
749
+ /**
750
+ * The reserved metadata table the {@link SQLiteDriver} creates on `open` to
751
+ * persist its stamped `DriverMeta` (`version` + declared schema JSON) — the
752
+ * SQLite realization of the `meta` / `stamp` driver hooks.
753
+ *
754
+ * @remarks
755
+ * A single-row table (`id = 1`). A user table named `_meta` collides with the
756
+ * reservation — the caller's concern to avoid, documented on the driver class.
757
+ */
758
+ export declare const META_TABLE = "_meta";
759
+
760
+ /**
761
+ * Quote a SQL identifier (a table or column name) so any characters are literal.
762
+ *
763
+ * @remarks
764
+ * Wraps the name in double quotes and doubles any embedded quote — the standard
765
+ * SQL identifier-quoting that lets a column named `order` or `from` be referenced
766
+ * safely. Identifiers cannot be bound as parameters, so they are quoted instead.
767
+ *
768
+ * @param identifier - The raw identifier
769
+ * @returns The double-quoted identifier
770
+ *
771
+ * @example
772
+ * ```ts
773
+ * quote('order') // '"order"'
774
+ * ```
775
+ */
776
+ export declare function quote(identifier: string): string;
777
+
778
+ /**
779
+ * Project a {@link TableSchema} to the `CREATE INDEX IF NOT EXISTS` statements a
780
+ * SQLite driver's `open` issues for its declared indexes.
781
+ *
782
+ * @remarks
783
+ * One statement per index group; the index name is built by {@link indexName}
784
+ * (collision-free and deterministic), matching the driver's naming so a
785
+ * repeated `open` is idempotent.
786
+ *
787
+ * @param schema - The table's schema
788
+ * @returns One `CREATE INDEX IF NOT EXISTS …` statement per declared index
789
+ *
790
+ * @example
791
+ * ```ts
792
+ * schemaToIndexes(schema)
793
+ * // ['CREATE INDEX IF NOT EXISTS "idx_5_users_4_name" ON "users" ("name")']
794
+ * ```
795
+ */
796
+ export declare function schemaToIndexes(schema: TableSchema): readonly string[];
797
+
798
+ /**
799
+ * Project a {@link TableSchema} to the `CREATE TABLE IF NOT EXISTS` statement a
800
+ * SQLite driver's `open` issues for it.
801
+ *
802
+ * @remarks
803
+ * Each column compiles to `<quoted name> <columnSQL(type)>`; the statement ends
804
+ * with `PRIMARY KEY (<quoted primary>)`. No `NOT NULL` is emitted — the contract
805
+ * validates required-ness, the database is just storage (AGENTS §14).
806
+ *
807
+ * @param schema - The table's schema
808
+ * @returns The `CREATE TABLE IF NOT EXISTS …` statement
809
+ *
810
+ * @example
811
+ * ```ts
812
+ * schemaToTable(schema)
813
+ * // 'CREATE TABLE IF NOT EXISTS "users" ("id" TEXT, "age" INTEGER, PRIMARY KEY ("id"))'
814
+ * ```
815
+ */
816
+ export declare function schemaToTable(schema: TableSchema): string;
817
+
818
+ /**
819
+ * The SQLite {@link DriverInterface} — the server-native, trusted-mode backend
820
+ * built on the published `@orkestrel/sqlite` synchronous wrapper.
821
+ *
822
+ * @remarks
823
+ * A thin adapter: it implements the storage primitives the core database layer
824
+ * needs by delegating to the wrapper's prepared statements — it never touches
825
+ * raw `node:sqlite`. `open` issues `CREATE TABLE IF NOT EXISTS` with real typed
826
+ * columns (mapped from each {@link TableSchema}'s portable column types) and a
827
+ * `PRIMARY KEY`, plus a `CREATE INDEX IF NOT EXISTS` per declared index (both
828
+ * reopen-safe), and readies a reserved `_meta` single-row table `meta()` /
829
+ * `stamp()` read and write — **a user table named `_meta` collides with it**;
830
+ * avoid the name. Rows cross the boundary through the codecs in `helpers.ts`
831
+ * (`json` columns store / parse JSON text, a `boolean` stores `1` / `0`), so the
832
+ * typed layer above imposes the exact shape (AGENTS §14). `write` is an
833
+ * `INSERT OR REPLACE` upsert — the `Table` layer detects a `CONFLICT` via a
834
+ * prior `has`, so this never translates a constraint error; a backend
835
+ * `SQLiteError` otherwise propagates unchanged. Querying, ordering, paging, and
836
+ * aggregation are native: `records` / `count` / `stream` compile a `Criteria`
837
+ * to SQL with `compileCriteria`, and `aggregate` runs a SQL
838
+ * `COUNT`/`SUM`/`AVG`/`MIN`/`MAX` (via `aggregateSQL`) over the same compiled
839
+ * WHERE. `transaction` wraps native `BEGIN` / `COMMIT` / `ROLLBACK` with
840
+ * double-settle guards. `migrate` runs the plan's projected DDL
841
+ * ({@link import('../helpers.js').stepToSQL}) inside whichever native
842
+ * transaction is active: joined into an already-open `transaction()` handle
843
+ * when one exists (the core's versioned reconcile path wraps migrate + stamp
844
+ * in one native `BEGIN`, and node:sqlite rejects a nested `BEGIN`), or inside
845
+ * its own `database.transaction` otherwise — a mid-plan failure rolls back
846
+ * atomically either way, an improvement over the non-atomic `MemoryDriver` /
847
+ * `JSONDriver` migrate; a step referencing an undeclared table throws
848
+ * `DatabaseError` `MIGRATION` before any DDL for that step runs. `snapshot` is
849
+ * capture-replay (SELECT the
850
+ * named tables' rows, replay via DELETE + INSERT OR REPLACE inside a native
851
+ * transaction on rollback) rather than a SQL `SAVEPOINT`, since the core
852
+ * `transaction` calls the rollback thunk only on failure with no commit-on-
853
+ * success signal — a long-lived `SAVEPOINT` would leave the connection
854
+ * uncommitted (lost on close). Every backend interaction runs through `#guard`,
855
+ * which maps a thrown backend `SQLiteError` (or any unexpected non-`SQLiteError`
856
+ * throw) to a typed {@link DatabaseError} — never a raw backend error escapes
857
+ * `DriverInterface`: `CONSTRAINT` → `CONFLICT`, the wrapper's own `CLOSED` →
858
+ * `CLOSED`, `BUSY` (a locked database that outlasted the configured `timeout`)
859
+ * → a retryable `DRIVER` (`context.retryable` is `true`), and `UNKNOWN` / any
860
+ * other throw → `DRIVER`. The original error is preserved as `context.cause`.
861
+ * A `DatabaseError` this driver throws directly (`CLOSED` from the `#require`
862
+ * gate, `NOT_FOUND` from `#table`, `MIGRATION` from a migration-plan fault)
863
+ * passes through `#guard` unchanged, never re-wrapped.
864
+ */
865
+ export declare class SQLiteDriver implements DriverInterface_2 {
866
+ #private;
867
+ constructor(path: string, options?: SQLiteDriverOptions);
868
+ open(schema: readonly TableSchema_2[]): Promise<void>;
869
+ close(): Promise<void>;
870
+ read(table: string, key: Key): Promise<Row_2 | undefined>;
871
+ write(table: string, key: Key, row: Row_2): Promise<void>;
872
+ delete(table: string, key: Key): Promise<boolean>;
873
+ keys(table: string): Promise<readonly Key[]>;
874
+ scan(table: string): AsyncIterable<Row_2>;
875
+ clear(table: string): Promise<void>;
876
+ records(table: string, criteria: Criteria_2): Promise<readonly Row_2[]>;
877
+ count(table: string, criteria: Criteria_2): Promise<number>;
878
+ aggregate(table: string, operation: AggregateFunction_2, column: FieldPath, criteria: Criteria_2): Promise<number | undefined>;
879
+ stream(table: string, criteria: Criteria_2): AsyncIterable<Row_2>;
880
+ /**
881
+ * Begin a native transaction — real `BEGIN`, `COMMIT`, `ROLLBACK`.
882
+ *
883
+ * @remarks
884
+ * Calling `commit` or `rollback` a second time (on either method, in either
885
+ * order) throws `DatabaseError` `CONFLICT`.
886
+ *
887
+ * @returns A {@link TransactionInterface} handle to `commit` or `rollback`
888
+ */
889
+ transaction(): Promise<TransactionInterface>;
890
+ /**
891
+ * Apply a {@link Migration} plan by executing each step's projected DDL
892
+ * ({@link import('../helpers.js').stepToSQL}).
893
+ *
894
+ * @remarks
895
+ * Atomicity is provided by whichever native transaction is active: when
896
+ * this driver's own `transaction()` hook already has a handle open (the
897
+ * core's versioned reconcile / migrate path joins migrate + stamp under
898
+ * one native `BEGIN`), the plan's DDL runs directly inside that enclosing
899
+ * transaction — a mid-plan failure propagates out and the CALLER's
900
+ * `commit`/`rollback` provides atomicity. node:sqlite (and SQLite
901
+ * generally) rejects a nested `BEGIN`, so this driver must never open a
902
+ * second native transaction while one is already open. Otherwise (no
903
+ * enclosing transaction), `migrate` wraps the plan in its own native
904
+ * `database.transaction` — atomic on its own: a mid-plan failure rolls
905
+ * back every DDL statement already applied by the plan. A step
906
+ * referencing a table not in this driver's declared schema (and that is
907
+ * not itself a `table.add`) throws `DatabaseError` `MIGRATION` before any
908
+ * DDL for that step runs, propagating out of whichever transaction is
909
+ * active (which rolls back on a throw).
910
+ *
911
+ * @param plan - The migration plan to apply
912
+ */
913
+ migrate(plan: Migration): Promise<void>;
914
+ /**
915
+ * Read the persisted {@link DriverMeta} from the reserved `_meta` table.
916
+ *
917
+ * @returns The last-stamped `DriverMeta`, or `undefined` when never stamped
918
+ * (or the stored row is malformed)
919
+ */
920
+ meta(): Promise<DriverMeta | undefined>;
921
+ /**
922
+ * Persist `meta` verbatim (as JSON) into the reserved `_meta` table's single
923
+ * row.
924
+ *
925
+ * @param meta - The {@link DriverMeta} to persist
926
+ */
927
+ stamp(meta: DriverMeta): Promise<void>;
928
+ snapshot(tables?: readonly string[]): Promise<() => Promise<void>>;
929
+ }
930
+
931
+ /**
932
+ * Options for {@link import('./factories.js').createSQLiteDriver}.
933
+ *
934
+ * @remarks
935
+ * Threaded into the underlying `@orkestrel/sqlite` wrapper's connection.
936
+ * `path` is the database file path (`':memory:'` when omitted); `readonly`
937
+ * opens the connection read-only (a write then fails as a typed `DRIVER`
938
+ * {@link DatabaseError}); `timeout` is the busy-timeout in milliseconds before
939
+ * a locked database fails `BUSY`; `foreignKeys` enables foreign-key
940
+ * constraint enforcement. `pragmas` is an ordered record of PRAGMA name to
941
+ * value, applied via the wrapper's `pragma()` right after `connect()`, in
942
+ * insertion order (e.g. `{ journal_mode: 'WAL' }`). Core rows are
943
+ * number-typed — this driver never surfaces a `bigint`, so a stored integer
944
+ * beyond `Number.MAX_SAFE_INTEGER` reads back imprecisely (the wrapper's own
945
+ * `bigints` option is not exposed here).
946
+ */
947
+ export declare interface SQLiteDriverOptions {
948
+ readonly path?: string;
949
+ readonly readonly?: boolean;
950
+ readonly timeout?: number;
951
+ readonly foreignKeys?: boolean;
952
+ readonly pragmas?: Readonly<Record<string, string | number>>;
953
+ }
954
+
955
+ /**
956
+ * One row as a SQLite binding returns it — a plain object keyed by column name.
957
+ *
958
+ * @remarks
959
+ * Every column value is a {@link SQLiteValue}. The SQLite driver decodes each
960
+ * raw row into this shape before handing it to the core query engine; nothing
961
+ * above the driver ever sees SQLite's native row representation directly.
962
+ */
963
+ export declare type SQLiteRow = Record<string, SQLiteValue>;
964
+
965
+ /**
966
+ * The value domain a SQLite binding accepts as a bound parameter and returns
967
+ * from a row.
968
+ *
969
+ * @remarks
970
+ * Mirrors SQLite's storage classes (`NULL`, `INTEGER`, `REAL`, `TEXT`, `BLOB`)
971
+ * at the TypeScript boundary: `null`, `number` / `bigint` for integer and
972
+ * floating-point values, `string` for text, and `Uint8Array` for blobs. This
973
+ * type is pure — it names the shape a value must have to cross the binding,
974
+ * independent of any concrete sqlite package (`node:sqlite`, `better-sqlite3`,
975
+ * etc.), so a driver can encode/decode against it without importing one.
976
+ */
977
+ export declare type SQLiteValue = null | number | bigint | string | Uint8Array;
978
+
979
+ /**
980
+ * Project one {@link MigrationStep} onto its table's declared {@link TableSchema}
981
+ * — the bookkeeping counterpart to {@link stepToSQL} (which projects the DDL a
982
+ * driver's `migrate` runs against the live database).
983
+ *
984
+ * @remarks
985
+ * `column.add` / `column.remove` add / filter the named column;
986
+ * `index.add` / `index.remove` add / filter the matching index group (an exact
987
+ * ordered match on `index`). `table.add` / `table.remove` act on a WHOLE
988
+ * schema map rather than one table's shape, so they are the caller's concern
989
+ * (a driver's `migrate` applies them directly against its table map) — passed
990
+ * here, they return `schema` unchanged.
991
+ *
992
+ * @param schema - The table's current declared schema
993
+ * @param step - The migration step to project onto it
994
+ * @returns The table's schema after the step
995
+ *
996
+ * @example
997
+ * ```ts
998
+ * stepToSchema(schema, { operation: 'column.remove', table: 'users', column: 'legacy' })
999
+ * // schema with the 'legacy' column dropped from `columns`
1000
+ * ```
1001
+ */
1002
+ export declare function stepToSchema(schema: TableSchema, step: MigrationStep): TableSchema;
1003
+
1004
+ /**
1005
+ * Project one {@link MigrationStep} to the DDL statement(s) a SQLite driver's
1006
+ * `migrate` executes for it.
1007
+ *
1008
+ * @remarks
1009
+ * `table.add` emits the `CREATE TABLE` plus one `CREATE INDEX` per declared
1010
+ * index (via {@link schemaToTable} / {@link schemaToIndexes}); `table.remove`
1011
+ * emits `DROP TABLE IF EXISTS`; `column.add` / `column.remove` emit `ALTER
1012
+ * TABLE … ADD COLUMN` / `… DROP COLUMN`; `index.add` / `index.remove` emit
1013
+ * `CREATE INDEX IF NOT EXISTS` / `DROP INDEX IF EXISTS`, naming the index the
1014
+ * same way `schemaToIndexes` does (`idx_<table>_<columns joined by _>`) so a
1015
+ * plan-built index matches one `open` would have created. Whether the named
1016
+ * table actually exists is the caller's concern (a driver's `migrate` checks
1017
+ * its own declared schema before running these statements) — this projection
1018
+ * is pure and never inspects live state.
1019
+ *
1020
+ * @param step - The migration step to project
1021
+ * @returns The DDL statement(s) that apply the step
1022
+ *
1023
+ * @example
1024
+ * ```ts
1025
+ * stepToSQL({ operation: 'column.remove', table: 'users', column: 'legacy' })
1026
+ * // ['ALTER TABLE "users" DROP COLUMN "legacy"']
1027
+ * ```
1028
+ */
1029
+ export declare function stepToSQL(step: MigrationStep): readonly string[];
1030
+
1031
+ /**
1032
+ * The storage type a nested (`json_extract`) operand encodes as, derived from its
1033
+ * RUNTIME value — NOT `json`.
1034
+ *
1035
+ * @remarks
1036
+ * `json_extract` returns the unquoted, natively-typed scalar (a JSON boolean as
1037
+ * `1` / `0`, a number as-is, a string as-is), so the operand must encode to that
1038
+ * same scalar to compare. A boolean → `'boolean'` (→ `1` / `0`); a number →
1039
+ * `'integer'` / `'real'`; a bigint → `'integer'`; a string → `'text'`; `null` /
1040
+ * `undefined` → `'text'` (encodes to `null`); an object / array → `'json'` (the
1041
+ * edge of comparing against a json subtree).
1042
+ *
1043
+ * @param value - The runtime operand value
1044
+ * @returns The {@link ColumnType} to encode it as
1045
+ *
1046
+ * @example
1047
+ * ```ts
1048
+ * valueType(true) // 'boolean'
1049
+ * valueType(9) // 'integer'
1050
+ * ```
1051
+ */
1052
+ export declare function valueType(value: unknown): ColumnType;
1053
+
1054
+ export { }