@orkestrel/database 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,116 +1,155 @@
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';
1
+ import { AggregateOperation } from '../core/index.ts';
2
+ import { AggregateOperation as AggregateOperation_2 } from '../../core/index.ts';
3
+ import { ColumnSchema } from '../core/index.ts';
4
+ import { ColumnStorage } from '../core/index.ts';
5
+ import { Condition } from '../core/index.ts';
6
+ import { DriverInterface } from '../core/index.ts';
7
+ import { DriverInterface as DriverInterface_2 } from '../../core/index.ts';
8
+ import { DriverMetadata } from '../../core/index.ts';
10
9
  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.
10
+ import { Key } from '../../core/index.ts';
11
+ import { MigrationInput } from '../../core/index.ts';
12
+ import { MigrationStep } from '../core/index.ts';
13
+ import { OperationOptions } from '../../core/index.ts';
14
+ import { Order } from '../core/index.ts';
15
+ import { QueryInput } from '../core/index.ts';
16
+ import { QueryInput as QueryInput_2 } from '../../core/index.ts';
17
+ import { Row } from '../core/index.ts';
18
+ import { Row as Row_2 } from '../../core/index.ts';
19
+ import { SQLiteRow } from '@orkestrel/sqlite';
20
+ import { SQLiteValue } from '@orkestrel/sqlite';
21
+ import { StorageInterface } from '../../core/index.ts';
22
+ import { TableSchema } from '../core/index.ts';
23
+ import { TableSchema as TableSchema_2 } from '../../core/index.ts';
24
+
25
+ /**
26
+ * Compile an {@link AggregateOperation} over a {@link FieldPath}.
33
27
  *
34
28
  * @param operation - The aggregate to compute
35
- * @param column - The column (or nested path) to aggregate
29
+ * @param column - The column or nested path to aggregate
36
30
  * @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
31
  */
45
- export declare function aggregateSQL(operation: AggregateFunction, column: FieldPath): string;
32
+ export declare function compileAggregateSQL(operation: AggregateOperation, column: FieldPath): string;
46
33
 
47
34
  /**
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).
35
+ * Map a portable {@link ColumnStorage} to its SQLite column type.
56
36
  *
57
- * @param type - The portable column type
37
+ * @param storage - The portable column type
58
38
  * @returns The SQLite column type keyword
59
- *
60
- * @example
61
- * ```ts
62
- * columnSQL('integer') // 'INTEGER'
63
- * columnSQL('json') // 'TEXT'
64
- * ```
65
39
  */
66
- export declare function columnSQL(type: ColumnType): string;
40
+ export declare function compileColumnSQL(storage: ColumnStorage): string;
67
41
 
68
42
  /**
69
- * Compile a {@link Criteria} into the SQL clause that follows a table name, with
70
- * its bound parameters in clause order.
43
+ * Compile one condition to its `<column> <operator>` SQL fragment and the parameters
44
+ * it binds engine-exact under SQL's three-valued NULL logic.
71
45
  *
72
46
  * @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.
47
+ * Every operand is run through `encodeValue`, so a bound value matches the SQL
48
+ * the column side compiles to. A flat column encodes operands with its DECLARED
49
+ * schema type (a flat `json` column `JSON.stringify`); a nested `FieldPath`
50
+ * encodes each operand as the NATIVE scalar `json_extract` returns, derived from
51
+ * the operand's runtime type (per-operand, since `between` / `any` / `none` can
52
+ * mix types). `any` / `none` collapse an empty list to a constant (`0` matches
53
+ * nothing, `1` matches all) with no parameters.
86
54
  *
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
55
+ * The core engine's total order ranks `undefined` (rank 0) BELOW `null`
56
+ * (rank 1) (see `compareValues`), so a MISSING/`NULL` column MATCHES
57
+ * `below` / `to` / a scalar `not` / `none` — the opposite of raw SQL, where a
58
+ * comparison against `NULL` is `NULL` (excluded). This fragment replicates the
59
+ * engine exactly. Truth table (`value` = the engine's decoded field read; a
60
+ * FLAT column's stored `NULL` decodes to `undefined` per `decodeRow`, so a
61
+ * flat `value` is NEVER a present `null` — only a NESTED path can be
62
+ * present-but-`null`):
63
+ *
64
+ * ```text
65
+ * operator | value=undefined (absent) | value=null (nested only) | value=scalar
66
+ * --------------------|--------------------------------|---------------------------|-------------
67
+ * equals, first=null | no match | MATCH | no match
68
+ * equals, first=X | no match | no match | value===X
69
+ * not, first=null | MATCH (flat: unconditionally; | no match | MATCH
70
+ * | nested: absent still matches)| |
71
+ * not, first=X | MATCH | MATCH | value!==X
72
+ * below/to, first=X | MATCH (rank 0 < rank(X)) | MATCH (rank 1 < rank(X)) | rank compare
73
+ * none, list=[…] | MATCH (no scalar rank-equal) | MATCH | not-in-list
74
+ * any, list=[…] | no match | no match | in-list
75
+ * above/from/between | no match | no match | rank compare
76
+ * like/glob/starts/… | no match (not a string) | no match | string test
77
+ * present | false | false | true
78
+ * absent | true | true | false
79
+ * ```
80
+ *
81
+ * A flat SQL `NULL` represents absence or explicit null according to its
82
+ * {@link ColumnSchema}; optional-and-nullable columns use a storage-class
83
+ * sentinel to distinguish the two. The native exactness gate therefore
84
+ * refines every optional or nullable scalar comparison through the core engine.
85
+ * This compiler still emits a total SQL fragment for direct consumers.
86
+ *
87
+ * A NESTED path can be present-but-`null` (a stored JSON `null`), which
88
+ * `json_extract` reads back as SQL `NULL` — indistinguishable from an ABSENT
89
+ * path. `json_type(col, path)` disambiguates them (`'null'` for present-null,
90
+ * SQL `NULL` for absent), so nested `equals` / `not` against a `null` operand
91
+ * compile through `json_type` instead of `IS NULL` / `IS NOT NULL`.
92
+ *
93
+ * Every other MATCH-on-null-or-absent row is expressed uniformly (flat and
94
+ * nested alike) as `(<column> <op> ? OR <column> IS NULL)` — for a nested
95
+ * path, `json_extract` already collapses BOTH absent and present-null to SQL
96
+ * `NULL`, so `IS NULL` catches both in one clause; for a flat column there is
97
+ * only the absent case to catch.
98
+ *
99
+ * @param condition - The condition to compile
100
+ * @param schema - The table's schema (for declared column types)
101
+ * @returns The SQL fragment and its bound parameters
90
102
  *
91
103
  * @example
92
104
  * ```ts
93
- * compileCriteria({ conditions: [{ column: 'age', operator: 'from', values: [18], connector: 'and' }] }, schema)
94
- * // { sql: 'WHERE "age" >= ? ORDER BY "id"', params: [18] }
105
+ * compileConditionSQL({ column: 'age', operator: 'above', values: [18], connector: 'and' }, schema)
106
+ * // { sql: '"age" > ?', parameters: [18] }
107
+ * compileConditionSQL({ column: 'age', operator: 'below', values: [18], connector: 'and' }, schema)
108
+ * // { sql: '("age" < ? OR "age" IS NULL)', parameters: [18] }
95
109
  * ```
96
110
  */
97
- export declare function compileCriteria(criteria: Criteria | undefined, schema: TableSchema): CompiledSQL;
111
+ export declare function compileConditionSQL(condition: Condition, schema: TableSchema): CompiledSQL;
98
112
 
99
113
  /**
100
114
  * A parameterized SQL fragment or statement plus its bind values.
101
115
  *
102
116
  * @remarks
103
117
  * 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
118
+ * `QueryInput` (or a table definition) into SQL text with `?` placeholders, and
119
+ * consumed by the SQLite driver, which runs `sql` with `parameters` bound in
120
+ * order — no further assembly. Keeping `sql` and `parameters` together prevents
107
121
  * the two from drifting apart across compile and execute.
108
122
  */
109
123
  export declare interface CompiledSQL {
110
124
  readonly sql: string;
111
- readonly params: readonly SQLiteValue[];
125
+ readonly parameters: readonly SQLiteValue[];
112
126
  }
113
127
 
128
+ /**
129
+ * Compile a {@link FieldPath} to the SQL expression that reads it.
130
+ *
131
+ * @param path - The field path
132
+ * @returns The SQL expression selecting the value
133
+ */
134
+ export declare function compileFieldSQL(path: FieldPath): string;
135
+
136
+ /**
137
+ * Compile a NESTED {@link FieldPath} to the `json_type(<col>, <path>)` SQL
138
+ * expression — the {@link compileFieldSQL} `json_extract` sibling used to tell a
139
+ * PRESENT JSON `null` apart from an ABSENT path (both read back as SQL `NULL`
140
+ * through `json_extract`, but `json_type` reports `'null'` for the former and
141
+ * SQL `NULL` for the latter).
142
+ *
143
+ * @param path - The nested field path (a column plus its JSON keys)
144
+ * @returns The SQL expression reading the value's JSON type
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * compileJSONTypeSQL(['payload', 'user', 'id']) // "json_type(\"payload\", '$.user.id')"
149
+ * ```
150
+ */
151
+ export declare function compileJSONTypeSQL(path: readonly string[]): string;
152
+
114
153
  /**
115
154
  * Compile the ORDER BY clause from the order terms, always ending with the
116
155
  * primary key as the final determinant.
@@ -152,20 +191,51 @@ export declare function compileOrder(order: readonly Order[] | undefined, schema
152
191
  *
153
192
  * @example
154
193
  * ```ts
155
- * compilePage(undefined, 5) // { sql: 'LIMIT -1 OFFSET ?', params: [5] }
194
+ * compilePage(undefined, 5) // { sql: 'LIMIT -1 OFFSET ?', parameters: [5] }
156
195
  * ```
157
196
  */
158
197
  export declare function compilePage(limit: number | undefined, offset: number | undefined): CompiledSQL;
159
198
 
199
+ /**
200
+ * Compile a {@link QueryInput} into the SQL clause that follows a table name, with
201
+ * its bound parameters in clause order.
202
+ *
203
+ * @remarks
204
+ * The driver's native `records` / `count` path: it assembles
205
+ * `[where, orderBy, limitOffset]` (each possibly empty) into one clause so a
206
+ * `SELECT * FROM <table> <clause>` runs the whole read in the engine instead of
207
+ * over a JS `scan`. The WHERE fold is parenthesized **left-to-right** to mirror
208
+ * the core engine's `matchesQuery` (not SQL's native AND-over-OR precedence),
209
+ * so a native and an engine read return identical rows. Each operand is encoded
210
+ * via `encodeValue`: a flat column uses its declared schema type, while a nested
211
+ * `FieldPath` (a `json_extract` read) encodes each operand as the native scalar
212
+ * the extract returns — derived from the operand's runtime type — so it compares.
213
+ * The 15 operators map per the databases guide's operator table, with
214
+ * `starts` / `ends` using `LIKE … ESCAPE '\'` and an empty `any` / `none` list
215
+ * collapsing to a constant. An `undefined` input (or one with no parts)
216
+ * compiles to an empty clause.
217
+ *
218
+ * @param input - The read specification, or `undefined` for all rows
219
+ * @param schema - The table's schema (column types for operand encoding)
220
+ * @returns The SQL tail and its bound parameters
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * compileQuerySQL({ conditions: [{ column: 'age', operator: 'from', values: [18], connector: 'and' }] }, schema)
225
+ * // { sql: 'WHERE "age" >= ? ORDER BY "id"', parameters: [18] }
226
+ * ```
227
+ */
228
+ export declare function compileQuerySQL(input: QueryInput | undefined, schema: TableSchema): CompiledSQL;
229
+
160
230
  /**
161
231
  * Fold the conditions into one WHERE clause, parenthesizing progressively
162
- * left-to-right so the grouping matches the engine's `matchesCriteria` fold.
232
+ * left-to-right so the grouping matches the engine's `matchesQuery` fold.
163
233
  *
164
234
  * @remarks
165
235
  * The first condition's connector is ignored, per the {@link Condition} types.
166
- * Every fragment (see {@link fragment}'s truth table) replicates the core
236
+ * Every fragment (see {@link compileConditionSQL}'s truth table) replicates the core
167
237
  * 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
238
+ * clause matches `applyQuery` row-for-row over the same table — a native
169
239
  * `records` / `count` read never disagrees with a scan-and-filter fallback.
170
240
  *
171
241
  * @param conditions - The conditions to fold
@@ -175,7 +245,7 @@ export declare function compilePage(limit: number | undefined, offset: number |
175
245
  * @example
176
246
  * ```ts
177
247
  * compileWhere([{ column: 'age', operator: 'from', values: [18], connector: 'and' }], schema)
178
- * // { sql: 'WHERE "age" >= ?', params: [18] }
248
+ * // { sql: 'WHERE "age" >= ?', parameters: [18] }
179
249
  * ```
180
250
  */
181
251
  export declare function compileWhere(conditions: readonly Condition[], schema: TableSchema): CompiledSQL;
@@ -184,9 +254,9 @@ export declare function compileWhere(conditions: readonly Condition[], schema: T
184
254
  * Create a persistent JSON-file {@link DriverInterface} for the core database layer.
185
255
  *
186
256
  * @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.
257
+ * Pass it to `createDatabase` from `@orkestrel/database` to run the typed
258
+ * database against a single JSON file instead of memory — the `Database` /
259
+ * `Table` / `Query` API is unchanged; only where the bytes live changes.
190
260
  * The driver is the reference `MemoryDriver` plus JSON-file persistence: `open` loads
191
261
  * the file, every mutation flushes the whole store back, and querying runs through
192
262
  * the core engine over `scan` (it is scan-only — no native `records` / `count` /
@@ -215,19 +285,20 @@ export declare function createJSONDriver(path: string): DriverInterface;
215
285
  * Create a trusted-mode SQLite {@link DriverInterface} for the core database layer.
216
286
  *
217
287
  * @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
288
+ * Pass it to `createDatabase` from `@orkestrel/database` to run the typed
289
+ * database against a real SQLite database — the `Database` / `Table` /
290
+ * `Query` API is unchanged; only where the bytes live changes. Built
221
291
  * on the published `@orkestrel/sqlite` synchronous wrapper: `open` issues real
222
292
  * typed `CREATE TABLE` / `CREATE INDEX` statements (reopen-safe) plus a reserved
223
- * `_meta` table for `meta()` / `stamp()` — avoid naming a table `_meta`.
293
+ * `_metadata` table for `metadata()` / `stamp()` — avoid naming a table `_metadata`.
224
294
  * Querying, paging, and aggregation run natively (`records` / `count` /
225
295
  * `aggregate` / `stream`); `transaction` and `migrate` use real `BEGIN` /
226
296
  * `COMMIT` / `ROLLBACK`, so `migrate` is atomic even mid-plan.
227
297
  *
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`)
298
+ * @param options - The {@link SQLiteDriverOptions} bag (`path`, `readonly`,
299
+ * `timeout`, `references`, `pragmas`); `references` directly enables or
300
+ * disables foreign-key enforcement, and omission retains the upstream
301
+ * default; omit the whole bag for an in-memory database
231
302
  * @returns A {@link DriverInterface} backed by SQLite
232
303
  *
233
304
  * @example
@@ -237,30 +308,16 @@ export declare function createJSONDriver(path: string): DriverInterface;
237
308
  * import { createSQLiteDriver } from '@orkestrel/database/server'
238
309
  *
239
310
  * const db = createDatabase({
240
- * driver: createSQLiteDriver('data/app.sqlite'),
311
+ * driver: createSQLiteDriver({ path: 'data/app.sqlite' }),
241
312
  * tables: { users: { id: stringShape(), name: stringShape() } },
242
313
  * })
243
314
  * await db.table('users').set({ id: 'u1', name: 'Ada' }) // persisted to app.sqlite
244
315
  *
245
- * // Or with options:
316
+ * // Or with additional options:
246
317
  * createSQLiteDriver({ path: 'data/app.sqlite', pragmas: { journal_mode: 'WAL' } })
247
318
  * ```
248
319
  */
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;
320
+ export declare function createSQLiteDriver(options?: SQLiteDriverOptions): DriverInterface;
264
321
 
265
322
  /**
266
323
  * Decode a stored {@link SQLiteRow} back to a {@link Row} by its table's schema.
@@ -269,9 +326,8 @@ export declare function declaredType(column: string, schema: TableSchema): Colum
269
326
  * Decodes each declared column with {@link decodeValue} and **omits** any column
270
327
  * whose decoded value is `undefined` — so an absent / `NULL` optional column does
271
328
  * 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).
329
+ * columns expect absence. Nullable-only SQL `NULL` cells remain explicit
330
+ * `null`; optional-and-nullable columns use their storage-class sentinel.
275
331
  *
276
332
  * @param row - The stored SQLite row
277
333
  * @param schema - The table's schema
@@ -285,27 +341,51 @@ export declare function declaredType(column: string, schema: TableSchema): Colum
285
341
  export declare function decodeRow(row: SQLiteRow, schema: TableSchema): Row;
286
342
 
287
343
  /**
288
- * Decode a stored {@link SQLiteValue} back to its JS value for a column's type
344
+ * Decode a stored {@link SQLiteValue} back to its JS value for a declared column —
289
345
  * the exact inverse of {@link encodeValue}.
290
346
  *
291
347
  * @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.
348
+ * Stored values must use the declared SQLite storage class. SQL `NULL` decodes
349
+ * to explicit `null` only for nullable-only columns and otherwise to absence.
350
+ * Optional-and-nullable sentinels decode to explicit `null`; malformed values
351
+ * decode to `undefined` so {@link decodeRow} omits them.
297
352
  *
298
353
  * @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`)
354
+ * @param column - The declared storage and absence/null contract
355
+ * @returns The decoded JS value, or `undefined` for absence/malformed storage
301
356
  *
302
357
  * @example
303
358
  * ```ts
304
- * decodeValue(1, 'boolean') // true
305
- * decodeValue('{"a":1}', 'json') // { a: 1 }
359
+ * decodeValue(1, booleanColumn) // true
360
+ * decodeValue('{"a":1}', jsonColumn) // { a: 1 }
306
361
  * ```
307
362
  */
308
- export declare function decodeValue(value: SQLiteValue, type: ColumnType): unknown;
363
+ export declare function decodeValue(value: SQLiteValue, column: ColumnSchema): unknown;
364
+
365
+ /**
366
+ * Build a collision-free SQL index name for a table + column-group index —
367
+ * shared by the compiler module's `schemaToIndexes` and `stepToSQL`,
368
+ * so a plan-built index name always matches one `open` would have created.
369
+ *
370
+ * @remarks
371
+ * A naive `idx_<table>_<cols joined by _>` is AMBIGUOUS: table `'a_b'` with
372
+ * column `'c'` and table `'a'` with columns `['b', 'c']` both produce
373
+ * `idx_a_b_c`. This encodes each part (the table name, then each column name)
374
+ * length-prefixed (`<len>_<part>`) so the boundary between parts is always
375
+ * unambiguous, however the names themselves are punctuated.
376
+ *
377
+ * @param table - The table name
378
+ * @param columns - The index's column names, in order
379
+ * @returns The deterministic, collision-free index identifier (unquoted)
380
+ *
381
+ * @example
382
+ * ```ts
383
+ * deriveSQLiteIndexName('users', ['name']) // 'idx_5_users_4_name'
384
+ * deriveSQLiteIndexName('a_b', ['c']) // 'idx_3_a_b_1_c'
385
+ * deriveSQLiteIndexName('a', ['b', 'c']) // 'idx_1_a_1_b_1_c'
386
+ * ```
387
+ */
388
+ export declare function deriveSQLiteIndexName(table: string, columns: readonly string[]): string;
309
389
 
310
390
  /**
311
391
  * Encode a whole {@link Row} to a {@link SQLiteRow} by its table's schema.
@@ -327,28 +407,25 @@ export declare function decodeValue(value: SQLiteValue, type: ColumnType): unkno
327
407
  export declare function encodeRow(row: Row, schema: TableSchema): SQLiteRow;
328
408
 
329
409
  /**
330
- * Encode a JS value to its stored {@link SQLiteValue} for a column's type.
410
+ * Encode a JS value to its stored {@link SQLiteValue} for a declared column.
331
411
  *
332
412
  * @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`.
413
+ * The codec is total: a malformed value encodes to SQL `NULL`. Absence always
414
+ * uses SQL `NULL`. A nullable-only column also uses SQL `NULL` for explicit
415
+ * `null`; an optional-and-nullable column uses a storage-class sentinel so
416
+ * absence and explicit `null` remain distinct.
340
417
  *
341
418
  * @param value - The JS value to store
342
- * @param type - The column's portable storage type
419
+ * @param column - The declared storage and absence/null contract
343
420
  * @returns The value SQLite stores
344
421
  *
345
422
  * @example
346
423
  * ```ts
347
- * encodeValue(true, 'boolean') // 1
348
- * encodeValue({ a: 1 }, 'json') // '{"a":1}'
424
+ * encodeValue(true, booleanColumn) // 1
425
+ * encodeValue({ a: 1 }, jsonColumn) // '{"a":1}'
349
426
  * ```
350
427
  */
351
- export declare function encodeValue(value: unknown, type: ColumnType): SQLiteValue;
428
+ export declare function encodeValue(value: unknown, column: ColumnSchema): SQLiteValue;
352
429
 
353
430
  /**
354
431
  * Escape `\`, `%`, and `_` (each with a leading `\`) so a `starts` / `ends`
@@ -365,7 +442,7 @@ export declare function encodeValue(value: unknown, type: ColumnType): SQLiteVal
365
442
  export declare function escapeLike(text: string): string;
366
443
 
367
444
  /**
368
- * The declared {@link ColumnType}s whose SQL EQUALITY comparisons (`equals` /
445
+ * The declared {@link ColumnStorage}s whose SQL EQUALITY comparisons (`equals` /
369
446
  * `not` / `any` / `none`) and `starts` / `ends` compiles are provably
370
447
  * engine-exact under declared-type trust — `text` / `integer` / `real` /
371
448
  * `boolean`; a `json` or `blob` column always refines instead.
@@ -380,258 +457,112 @@ export declare function escapeLike(text: string): string;
380
457
  * compares UTF-16 CODE-UNIT order. The two orders diverge for supplementary-
381
458
  * plane characters (code points ≥ U+10000, e.g. many emoji): a lead surrogate
382
459
  * (`\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.
460
+ * its code point sorts ABOVE them. So `matchesConditionExactly`'s range family and
461
+ * `matchesOrderExactly` exclude `text`, refining through the core engine instead.
388
462
  */
389
- export declare const EXACT_COLUMN_TYPES: readonly ColumnType[];
463
+ export declare const EXACT_COLUMN_STORAGE: readonly ColumnStorage[];
390
464
 
391
465
  /**
392
- * The declared {@link ColumnType}s whose SQL RANGE comparisons
466
+ * The declared {@link ColumnStorage}s whose SQL RANGE comparisons
393
467
  * (`above` / `below` / `from` / `to` / `between`) and `ORDER BY` compiles are
394
468
  * provably engine-exact — `integer` / `real` / `boolean` only. `text` is
395
- * excluded: see {@link EXACT_COLUMN_TYPES}'s remarks for the BINARY-collation
469
+ * excluded: see {@link EXACT_COLUMN_STORAGE}'s remarks for the BINARY-collation
396
470
  * (code-point) vs. JS `<` (code-unit) divergence on supplementary-plane
397
471
  * characters.
398
472
  */
399
- export declare const EXACT_RANGE_COLUMN_TYPES: readonly ColumnType[];
473
+ export declare const EXACT_RANGE_COLUMN_STORAGE: readonly ColumnStorage[];
400
474
 
401
475
  /**
402
- * Compile a {@link FieldPath} to the SQL expression that reads it.
476
+ * Extract a stored row's values in a declared positional order.
403
477
  *
404
478
  * @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.
479
+ * SQLite statements bind arrays positionally. Every requested column must be
480
+ * present in `row`; an incomplete backend row is a typed `DRIVER` fault carrying
481
+ * the table and missing column in its context.
410
482
  *
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
483
+ * @param row - The stored SQLite row
484
+ * @param names - The column names in binding order
485
+ * @param table - The owning table name for fault context
486
+ * @returns The row values in the same order as `names`
487
+ * @throws A `DRIVER` {@link DatabaseError} when a requested column is missing
484
488
  *
485
489
  * @example
486
490
  * ```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
+ * extractValues({ id: 'u1', age: 36 }, ['age', 'id'], 'users') // [36, 'u1']
491
492
  * ```
492
493
  */
493
- export declare function fragment(condition: Condition, schema: TableSchema): CompiledSQL;
494
+ export declare function extractValues(row: SQLiteRow, names: readonly string[], table: string): readonly SQLiteValue[];
494
495
 
495
496
  /**
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.
497
+ * The declared storage type of a flat (string) column, read from the schema.
502
498
  *
503
- * @returns A new UUID string
499
+ * @param column - The column name
500
+ * @param schema - The table's schema
501
+ * @returns The column's {@link ColumnStorage}, or `undefined` if the schema does not carry it
504
502
  *
505
503
  * @example
506
504
  * ```ts
507
- * const db = createDatabase({ driver, tables, key: generateKey })
505
+ * findColumnStorage('age', schema) // 'integer'
508
506
  * ```
509
507
  */
510
- export declare function generateKey(): string;
508
+ export declare function findColumnStorage(column: string, schema: TableSchema): ColumnStorage | undefined;
511
509
 
512
510
  /**
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.
511
+ * The storage type a nested (`json_extract`) operand encodes as, derived from its
512
+ * RUNTIME value NOT `json`.
517
513
  *
518
514
  * @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.
515
+ * `json_extract` returns the unquoted, natively-typed scalar (a JSON boolean as
516
+ * `1` / `0`, a number as-is, a string as-is), so the operand must encode to that
517
+ * same scalar to compare. A boolean → `'boolean'` ( `1` / `0`); a number
518
+ * `'integer'` / `'real'`; a bigint `'integer'`; a string → `'text'`; `null` /
519
+ * `undefined` `'text'` (encodes to `null`); an object / array → `'json'` (the
520
+ * edge of comparing against a json subtree).
524
521
  *
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)
522
+ * @param value - The runtime operand value
523
+ * @returns The {@link ColumnStorage} to encode it as
528
524
  *
529
525
  * @example
530
526
  * ```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'
527
+ * inferValueStorage(true) // 'boolean'
528
+ * inferValueStorage(9) // 'integer'
534
529
  * ```
535
530
  */
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;
531
+ export declare function inferValueStorage(value: unknown): ColumnStorage;
605
532
 
606
533
  /**
607
534
  * A persistent {@link DriverInterface} backed by a single JSON file — the
608
535
  * reference {@link MemoryDriver} plus file load / flush.
609
536
  *
610
537
  * @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`
538
+ * A decorator, not a reimplementation: storage primitives delegate to an inner
539
+ * {@link MemoryDriver}, while this layer owns persistence, writer ordering,
540
+ * isolated transactions, and queued row-snapshot restoration. `open`
614
541
  * 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
542
+ * the whole store back. The file is one JSON object, `{ metadata?: DriverMetadata, tables: {
543
+ * [name]: rows } }` — `metadata` is present only once the store has been `stamp`ed
544
+ * (an unstamped store omits `metadata`); a per-table array of rows, each row carrying its own
619
545
  * primary (the table contract), so the key is recovered on load with
620
546
  * {@link extractKey} and the file need not store it. The parsed JSON crosses the
621
547
  * 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
548
+ * never asserted (AGENTS §14). Only an `ENOENT` read starts empty; every other
549
+ * read failure or invalid existing document fails closed without publication,
550
+ * mutation, or automatic repair. It is scan-only — it implements none of
551
+ * the optional native `records` / `aggregate` hooks, so the core engine
626
552
  * over `scan` answers every query. For development, small datasets, and portable /
627
553
  * inspectable data; for large or concurrent workloads reach for a SQLite-backed
628
554
  * driver.
629
555
  *
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.
556
+ * Metadata crosses {@link cloneDriverMetadata} at parsed-file ingress, public and
557
+ * scoped write ingress, candidate/root publication, serialization, and copy-out.
558
+ * Callers therefore cannot mutate queued metadata, and `metadata()` always returns a
559
+ * distinct deeply frozen snapshot. A failure in the write path ({@link
560
+ * JSONDriver.#serialize} `mkdir` / `writeFile` / `rename`) is wrapped and
561
+ * rethrown as `DatabaseError` `DRIVER`, carrying the target `path` and native
562
+ * `cause` in its context. If temporary-file cleanup also fails, the top-level
563
+ * `DRIVER` context additionally carries `temp` and `cleanup`; a precommit abort
564
+ * remains an `ABORTED` `DatabaseError` in `context.cause`. The fail-closed read path
565
+ * ({@link JSONDriver.#document}) remains separate from this write-error contract.
635
566
  */
636
567
  export declare class JSONDriver implements DriverInterface_2 {
637
568
  #private;
@@ -639,92 +570,124 @@ export declare class JSONDriver implements DriverInterface_2 {
639
570
  open(schema: readonly TableSchema_2[]): Promise<void>;
640
571
  close(): Promise<void>;
641
572
  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>;
573
+ write(table: string, key: Key, row: Row_2, options?: OperationOptions): Promise<void>;
574
+ insert(table: string, key: Key, row: Row_2, options?: OperationOptions): Promise<void>;
575
+ delete(table: string, key: Key, options?: OperationOptions): Promise<boolean>;
644
576
  keys(table: string): Promise<readonly Key[]>;
645
577
  scan(table: string): AsyncIterable<Row_2>;
646
578
  /**
647
579
  * Natively filtered lazy iteration — delegates to the inner {@link MemoryDriver}.
648
580
  *
649
581
  * @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
582
+ * Semantics are the memory driver's own: `input.conditions` filters, `offset`
583
+ * / `limit` page lazily, and `input.order` is ignored (streaming yields key
652
584
  * order; sorted output is `records()`'s job).
653
585
  *
654
586
  * @param table - The table to stream
655
- * @param criteria - The filter / offset / limit to apply lazily
587
+ * @param input - The filter / offset / limit to apply lazily
656
588
  */
657
- stream(table: string, criteria: Criteria_2): AsyncIterable<Row_2>;
589
+ stream(table: string, input: QueryInput_2): AsyncIterable<Row_2>;
658
590
  clear(table: string): Promise<void>;
659
591
  /**
660
- * Begin a native transaction flush-coalescing over the inner {@link MemoryDriver}.
592
+ * Run an isolated native transaction callback over a candidate memory store.
661
593
  *
662
594
  * @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`.
595
+ * Single-writer: nesting and root operations while active throw `CONFLICT`.
596
+ * The callback receives a capability over cloned rows, schema, and metadata.
597
+ * Fulfillment atomically serializes that candidate and publishes it to root
598
+ * memory only after file replacement succeeds. Rejection or persistence
599
+ * failure discards the candidate, and every captured capability call after
600
+ * settlement throws `CONFLICT`.
674
601
  *
675
- * @returns A {@link TransactionInterface} handle to `commit` or `rollback`
602
+ * @returns The callback's resolved value
603
+ */
604
+ transaction<R>(scope: (storage: StorageInterface) => Promise<R>): Promise<R>;
605
+ /**
606
+ * Capture an owned row snapshot at an exact writer-queue position.
607
+ *
608
+ * @remarks
609
+ * Capture owns table names, schemas, rows, and one session-local identity per
610
+ * table. Rollback is repeatable: it clones the then-current root into a
611
+ * candidate, adapts captured rows to each surviving same-identity table
612
+ * through the portable migration engine, persists the candidate with current
613
+ * metadata, and publishes memory only after file replacement succeeds.
614
+ * Removed, replaced, uncaptured, and later-added tables remain untouched.
615
+ *
616
+ * @param tables - Existing tables to capture; omitted captures every current table
617
+ * @returns A repeatable rollback operation
676
618
  */
677
- transaction(): Promise<TransactionInterface>;
678
619
  snapshot(tables?: readonly string[]): Promise<() => Promise<void>>;
679
- meta(): Promise<DriverMeta | undefined>;
620
+ metadata(): Promise<DriverMetadata | undefined>;
680
621
  /**
681
- * Persist `meta` verbatim for a later `meta()` to return.
622
+ * Persist an owned metadata snapshot for a later `metadata()` to copy out.
682
623
  *
683
624
  * @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.
625
+ * Root stamping conflicts while a transaction is active. The scoped
626
+ * {@link StorageInterface.stamp} updates candidate metadata and publishes
627
+ * with the candidate rows on callback fulfillment.
687
628
  *
688
- * @param meta - The {@link DriverMeta} to persist
629
+ * @param metadata - The {@link DriverMetadata} to persist
689
630
  */
690
- stamp(meta: DriverMeta): Promise<void>;
631
+ stamp(metadata: DriverMetadata): Promise<void>;
691
632
  /**
692
- * Apply a {@link Migration} plan by delegating to the inner {@link MemoryDriver},
693
- * then persist the migrated state.
633
+ * Apply one atomic {@link MigrationInput} through an isolated candidate.
694
634
  *
695
635
  * @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.
636
+ * The candidate receives every plan step plus optional metadata. Its complete
637
+ * rows, derived schema, and metadata serialize through one atomic file
638
+ * replacement before root memory changes. Any migration or persistence failure
639
+ * therefore leaves root state and the prior file exact.
706
640
  *
707
- * @param plan - The migration plan to apply
641
+ * @param input - The plan and optional metadata to settle together
708
642
  */
709
- migrate(plan: Migration): Promise<void>;
643
+ migrate(input: MigrationInput): Promise<void>;
710
644
  }
711
645
 
712
646
  /**
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).
647
+ * Determine whether SQLite can execute an aggregate exactly like the core engine.
718
648
  *
719
- * @param path - The nested field path (a column plus its JSON keys)
720
- * @returns The SQL expression reading the value's JSON type
649
+ * @param operation - Aggregate operation
650
+ * @param column - Aggregate field
651
+ * @param schema - Current table schema
652
+ * @returns Whether native aggregation is exact
653
+ */
654
+ export declare function matchesAggregateExactly(operation: AggregateOperation, column: FieldPath, schema: TableSchema): boolean;
655
+
656
+ /**
657
+ * Whether one {@link Condition} compiles to SQL that is PROVABLY identical to
658
+ * the core engine's `matchesCondition` for every value its column's declared
659
+ * type can store.
721
660
  *
722
- * @example
723
- * ```ts
724
- * jsonTypeColumn(['payload', 'user', 'id']) // "json_type(\"payload\", '$.user.id')"
725
- * ```
661
+ * @remarks
662
+ * `false` for a nested `FieldPath` (an array) or a column absent from `schema`.
663
+ * `absent` / `present` are exact unless a column is both optional and nullable.
664
+ * In that combined case the storage sentinel for explicit `null` is not SQL
665
+ * `NULL`, while the core treats both absence and explicit `null` as absent.
666
+ * Every scalar operator refines when the column is optional
667
+ * OR nullable, because SQL null semantics and the core total order differ.
668
+ * Required non-null `equals` / `not` require an operand matching the declared
669
+ * storage and exclude `json` / `blob`. `above` / `below` / `from` / `to` /
670
+ * `between` are exact only for {@link EXACT_RANGE_COLUMN_STORAGE} (`integer` /
671
+ * `real` / `boolean`) — a `text` column's range conditions REFINE, because
672
+ * SQLite's default BINARY collation orders TEXT by Unicode CODE POINT while
673
+ * the core engine's `compareValues` orders JS strings by UTF-16 CODE UNIT,
674
+ * and the two diverge for supplementary-plane characters (see
675
+ * {@link EXACT_COLUMN_STORAGE}'s remarks for the full rationale).
676
+ * `any` / `none` require a NON-EMPTY list where every element matches (an empty
677
+ * list is exact under neither: the engine's `any([])` matches nothing while
678
+ * `none([])` matches everything, and SQL `IN ()` is a syntax error) — these
679
+ * stay exact on `text` (byte equality is collation-independent and engine-
680
+ * identical). `starts` / `ends` are exact only on a `text` column with a
681
+ * string operand (case-sensitive `substr` compile, see {@link compileConditionSQL}) —
682
+ * likewise collation-independent. `like` / `glob` are NEVER exact — SQLite
683
+ * `LIKE` folds case ASCII-only against the engine's Unicode fold, and `GLOB`
684
+ * has character classes the engine treats literally.
685
+ *
686
+ * @param condition - The condition to test
687
+ * @param schema - The table's schema
688
+ * @returns Whether `condition` is exact
726
689
  */
727
- export declare function jsonTypeColumn(path: readonly string[]): string;
690
+ export declare function matchesConditionExactly(condition: Condition, schema: TableSchema): boolean;
728
691
 
729
692
  /**
730
693
  * Whether a value's runtime type matches a column's declared exact type —
@@ -732,7 +695,7 @@ export declare function jsonTypeColumn(path: readonly string[]): string;
732
695
  *
733
696
  * @remarks
734
697
  * `text` ↔ string, `integer` / `real` ↔ FINITE number (`NaN` / `±Infinity`
735
- * fail), `boolean` ↔ boolean. Backs {@link isExactCondition}'s operand checks.
698
+ * fail), `boolean` ↔ boolean. Backs {@link matchesConditionExactly}'s operand checks.
736
699
  *
737
700
  * @param value - The condition operand to test
738
701
  * @param type - The column's declared portable type
@@ -744,18 +707,57 @@ export declare function jsonTypeColumn(path: readonly string[]): string;
744
707
  * matchesDeclaredType(Number.NaN, 'integer') // false — only finite numbers
745
708
  * ```
746
709
  */
747
- export declare function matchesDeclaredType(value: unknown, type: ColumnType): boolean;
710
+ export declare function matchesDeclaredStorage(value: unknown, storage: ColumnStorage): boolean;
711
+
712
+ /**
713
+ * Whether one {@link Order} term's column compiles to an `ORDER BY` that
714
+ * matches the engine's {@link import('@src/core').sortRows} exactly.
715
+ *
716
+ * @remarks
717
+ * `false` for a nested `FieldPath`, a column absent from `schema`, or a
718
+ * declared type outside {@link EXACT_RANGE_COLUMN_STORAGE} (`integer` / `real` /
719
+ * `boolean`). `text` is NOT exact here: SQLite's default BINARY collation
720
+ * orders TEXT by Unicode code point while the core engine's `compareValues`
721
+ * orders JS strings by UTF-16 code unit, and the two diverge for
722
+ * supplementary-plane characters (see {@link EXACT_COLUMN_STORAGE}'s remarks) —
723
+ * a `text` order term REFINES through the core engine instead.
724
+ *
725
+ * @param order - The order term to test
726
+ * @param schema - The table's schema
727
+ * @returns Whether `order` is exact
728
+ */
729
+ export declare function matchesOrderExactly(order: Order, schema: TableSchema): boolean;
730
+
731
+ /**
732
+ * Whether a whole {@link QueryInput} is exact — every condition and every order
733
+ * term is exact. `limit` / `offset` never affect exactness (SQL `LIMIT` /
734
+ * `OFFSET` are always engine-identical).
735
+ *
736
+ * @param input - The query input to test
737
+ * @param schema - The table's schema
738
+ * @returns Whether every part of `input` is exact
739
+ */
740
+ export declare function matchesQueryExactly(input: QueryInput, schema: TableSchema): boolean;
741
+
742
+ /**
743
+ * Test a declared SQLite type against a portable storage affinity.
744
+ *
745
+ * @param declared - Native declared type
746
+ * @param storage - Portable column storage
747
+ * @returns Whether SQLite's official affinity rules yield the expected affinity
748
+ */
749
+ export declare function matchesSQLiteAffinity(declared: unknown, storage: ColumnStorage): boolean;
748
750
 
749
751
  /**
750
752
  * 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
+ * persist its stamped `DriverMetadata` (`version` + declared schema JSON) — the
754
+ * SQLite realization of the `metadata` / `stamp` driver hooks.
753
755
  *
754
756
  * @remarks
755
- * A single-row table (`id = 1`). A user table named `_meta` collides with the
757
+ * A single-row table (`id = 1`). A user table named `_metadata` collides with the
756
758
  * reservation — the caller's concern to avoid, documented on the driver class.
757
759
  */
758
- export declare const META_TABLE = "_meta";
760
+ export declare const METADATA_TABLE = "_metadata";
759
761
 
760
762
  /**
761
763
  * Quote a SQL identifier (a table or column name) so any characters are literal.
@@ -770,48 +772,24 @@ export declare const META_TABLE = "_meta";
770
772
  *
771
773
  * @example
772
774
  * ```ts
773
- * quote('order') // '"order"'
775
+ * quoteIdentifier('order') // '"order"'
774
776
  * ```
775
777
  */
776
- export declare function quote(identifier: string): string;
778
+ export declare function quoteIdentifier(identifier: string): string;
777
779
 
778
780
  /**
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.
781
+ * Project a {@link TableSchema} to its declared SQLite indexes.
786
782
  *
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
- * ```
783
+ * @param schema - The table schema
784
+ * @returns One statement per declared index
795
785
  */
796
786
  export declare function schemaToIndexes(schema: TableSchema): readonly string[];
797
787
 
798
788
  /**
799
- * Project a {@link TableSchema} to the `CREATE TABLE IF NOT EXISTS` statement a
800
- * SQLite driver's `open` issues for it.
789
+ * Project a {@link TableSchema} to its `CREATE TABLE IF NOT EXISTS` statement.
801
790
  *
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
- * ```
791
+ * @param schema - The table schema
792
+ * @returns The complete table declaration
815
793
  */
816
794
  export declare function schemaToTable(schema: TableSchema): string;
817
795
 
@@ -825,21 +803,23 @@ export declare function schemaToTable(schema: TableSchema): string;
825
803
  * raw `node:sqlite`. `open` issues `CREATE TABLE IF NOT EXISTS` with real typed
826
804
  * columns (mapped from each {@link TableSchema}'s portable column types) and a
827
805
  * `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**;
806
+ * reopen-safe), and readies a reserved `_metadata` single-row table `metadata()` /
807
+ * `stamp()` read and write — **a user table named `_metadata` collides with it**;
830
808
  * avoid the name. Rows cross the boundary through the codecs in `helpers.ts`
831
809
  * (`json` columns store / parse JSON text, a `boolean` stores `1` / `0`), so the
832
810
  * 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
811
+ * `INSERT OR REPLACE` upsert, while `insert` uses a plain `INSERT` and maps its
812
+ * atomic primary-key constraint failure to `CONFLICT`; every other backend
813
+ * `SQLiteError` is contained by the same `DatabaseError` boundary described
814
+ * below. Querying, ordering, paging, and
815
+ * aggregation is native: `records` / `stream` compile a `QueryInput`
816
+ * to SQL with `compileQuerySQL`, and `aggregate` runs a SQL
817
+ * `COUNT`/`SUM`/`AVG`/`MIN`/`MAX` (via `compileAggregateSQL`) over the same compiled
818
+ * WHERE. `transaction` runs a callback inside native `BEGIN` / `COMMIT` /
819
+ * `ROLLBACK`, passing a scoped storage capability that becomes invalid after
820
+ * settlement. `migrate` runs the plan's projected DDL
821
+ * ({@link import('../compilers.js').stepToSQL}) inside whichever native
822
+ * transaction is active: joined into the active transaction callback
843
823
  * when one exists (the core's versioned reconcile path wraps migrate + stamp
844
824
  * in one native `BEGIN`, and node:sqlite rejects a nested `BEGIN`), or inside
845
825
  * its own `database.transaction` otherwise — a mid-plan failure rolls back
@@ -864,67 +844,72 @@ export declare function schemaToTable(schema: TableSchema): string;
864
844
  */
865
845
  export declare class SQLiteDriver implements DriverInterface_2 {
866
846
  #private;
867
- constructor(path: string, options?: SQLiteDriverOptions);
847
+ constructor(options?: SQLiteDriverOptions);
868
848
  open(schema: readonly TableSchema_2[]): Promise<void>;
869
849
  close(): Promise<void>;
870
850
  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>;
851
+ write(table: string, key: Key, row: Row_2, options?: OperationOptions): Promise<void>;
852
+ insert(table: string, key: Key, row: Row_2, options?: OperationOptions): Promise<void>;
853
+ delete(table: string, key: Key, options?: OperationOptions): Promise<boolean>;
873
854
  keys(table: string): Promise<readonly Key[]>;
874
855
  scan(table: string): AsyncIterable<Row_2>;
875
856
  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>;
857
+ records(table: string, input: QueryInput_2): Promise<readonly Row_2[]>;
858
+ aggregate(table: string, operation: AggregateOperation_2, column: FieldPath, input: QueryInput_2): Promise<number | undefined>;
859
+ stream(table: string, input: QueryInput_2): AsyncIterable<Row_2>;
880
860
  /**
881
861
  * Begin a native transaction — real `BEGIN`, `COMMIT`, `ROLLBACK`.
882
862
  *
883
863
  * @remarks
884
- * Calling `commit` or `rollback` a second time (on either method, in either
885
- * order) throws `DatabaseError` `CONFLICT`.
864
+ * The callback receives a scoped {@link StorageInterface}. Fulfillment
865
+ * commits and returns its value; rejection rolls back and preserves the
866
+ * original error. Root operations and nesting conflict while active, and a
867
+ * captured capability conflicts after settlement.
886
868
  *
887
- * @returns A {@link TransactionInterface} handle to `commit` or `rollback`
869
+ * @returns The callback's resolved value
888
870
  */
889
- transaction(): Promise<TransactionInterface>;
871
+ transaction<R>(scope: (storage: StorageInterface) => Promise<R>): Promise<R>;
890
872
  /**
891
873
  * Apply a {@link Migration} plan by executing each step's projected DDL
892
- * ({@link import('../helpers.js').stepToSQL}).
874
+ * ({@link import('../compilers.js').stepToSQL}).
893
875
  *
894
876
  * @remarks
895
877
  * Atomicity is provided by whichever native transaction is active: when
896
- * this driver's own `transaction()` hook already has a handle open (the
878
+ * this driver's own `transaction()` callback is active (the
897
879
  * core's versioned reconcile / migrate path joins migrate + stamp under
898
880
  * 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
881
+ * transaction — a mid-plan failure rejects the callback and the driver
882
+ * rolls it back. node:sqlite (and SQLite
901
883
  * generally) rejects a nested `BEGIN`, so this driver must never open a
902
884
  * second native transaction while one is already open. Otherwise (no
903
885
  * enclosing transaction), `migrate` wraps the plan in its own native
904
886
  * `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).
887
+ * back every DDL statement already applied by the plan. A scoped migration
888
+ * uses one fixed internal savepoint literal because the published SQLite
889
+ * wrapper intentionally exposes raw `exec` but no savepoint manager. That
890
+ * savepoint contains a caught inner migration so the outer callback
891
+ * transaction remains active and may continue safely. A step referencing a
892
+ * table not in this driver's declared schema (and that is not itself a
893
+ * `table.add`) throws `DatabaseError` `MIGRATION` before any DDL for that
894
+ * step runs.
910
895
  *
911
- * @param plan - The migration plan to apply
896
+ * @param input - The migration plan and optional metadata stamp to apply atomically
912
897
  */
913
- migrate(plan: Migration): Promise<void>;
898
+ migrate(input: MigrationInput): Promise<void>;
914
899
  /**
915
- * Read the persisted {@link DriverMeta} from the reserved `_meta` table.
900
+ * Read the persisted {@link DriverMetadata} from the reserved `_metadata` table.
916
901
  *
917
- * @returns The last-stamped `DriverMeta`, or `undefined` when never stamped
902
+ * @returns The last-stamped `DriverMetadata`, or `undefined` when never stamped
918
903
  * (or the stored row is malformed)
919
904
  */
920
- meta(): Promise<DriverMeta | undefined>;
905
+ metadata(): Promise<DriverMetadata | undefined>;
921
906
  /**
922
- * Persist `meta` verbatim (as JSON) into the reserved `_meta` table's single
923
- * row.
907
+ * Persist an owned metadata snapshot into the reserved `_metadata` table's
908
+ * single row.
924
909
  *
925
- * @param meta - The {@link DriverMeta} to persist
910
+ * @param metadata - The {@link DriverMetadata} to persist
926
911
  */
927
- stamp(meta: DriverMeta): Promise<void>;
912
+ stamp(metadata: DriverMetadata): Promise<void>;
928
913
  snapshot(tables?: readonly string[]): Promise<() => Promise<void>>;
929
914
  }
930
915
 
@@ -936,8 +921,9 @@ export declare class SQLiteDriver implements DriverInterface_2 {
936
921
  * `path` is the database file path (`':memory:'` when omitted); `readonly`
937
922
  * opens the connection read-only (a write then fails as a typed `DRIVER`
938
923
  * {@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
924
+ * a locked database fails `BUSY`; `references` enables or disables foreign-key
925
+ * constraint enforcement, while omission retains the upstream default.
926
+ * `pragmas` is an ordered record of PRAGMA name to
941
927
  * value, applied via the wrapper's `pragma()` right after `connect()`, in
942
928
  * insertion order (e.g. `{ journal_mode: 'WAL' }`). Core rows are
943
929
  * number-typed — this driver never surfaces a `bigint`, so a stored integer
@@ -948,107 +934,16 @@ export declare interface SQLiteDriverOptions {
948
934
  readonly path?: string;
949
935
  readonly readonly?: boolean;
950
936
  readonly timeout?: number;
951
- readonly foreignKeys?: boolean;
937
+ readonly references?: boolean;
952
938
  readonly pragmas?: Readonly<Record<string, string | number>>;
953
939
  }
954
940
 
955
941
  /**
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
942
+ * Project one {@link MigrationStep} to SQLite DDL.
1022
943
  *
1023
- * @example
1024
- * ```ts
1025
- * stepToSQL({ operation: 'column.remove', table: 'users', column: 'legacy' })
1026
- * // ['ALTER TABLE "users" DROP COLUMN "legacy"']
1027
- * ```
944
+ * @param step - The migration step
945
+ * @returns The statements that apply the step
1028
946
  */
1029
947
  export declare function stepToSQL(step: MigrationStep): readonly string[];
1030
948
 
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
949
  export { }