@orkestrel/database 0.0.12 → 0.0.14

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,26 +1,26 @@
1
- import { DatabaseError, MemoryDriver, applyQuery, bindRowKey, checkAbort, cloneDriverMetadata, cloneMigrationInput, computeAggregate, equalsValue, extractKey, filterRows, isDatabaseError, isKey, matchesQuery, migrateRows, normalizeDriverSchema, planMigration, projectMigrationSchema, validatePage } from "../core/index.js";
1
+ import { DatabaseError, DriverIterator, MemoryDriver, applyQuery, bindRowKey, checkAbort, cloneDriverMetadata, cloneMigrationInput, computeAggregate, equalsValue, extractKey, filterRows, findColumn, isDatabaseError, isKey, matchesQuery, migrateRows, normalizeDriverSchema, planMigration, projectMigrationSchema, validatePage } from "../core/index.js";
2
2
  import { cloneJSONValue, isBoolean, isFiniteNumber, isRecord, isString } from "@orkestrel/contract";
3
3
  import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
4
4
  import { dirname } from "node:path";
5
5
  import { createSQLiteDatabase, isSQLiteError } from "@orkestrel/sqlite";
6
6
  //#region src/server/constants.ts
7
7
  /**
8
- * The declared {@link ColumnStorage}s whose SQL EQUALITY comparisons (`equals` /
8
+ * Lists the declared {@link ColumnStorage}s whose SQL equality comparisons (`equals` /
9
9
  * `not` / `any` / `none`) and `starts` / `ends` compiles are provably
10
10
  * engine-exact under declared-type trust — `text` / `integer` / `real` /
11
11
  * `boolean`; a `json` or `blob` column always refines instead.
12
12
  *
13
13
  * @remarks
14
- * This set governs equality and prefix/suffix matching only. RANGE
14
+ * This set governs equality and prefix/suffix matching only. Range
15
15
  * comparisons (`above` / `below` / `from` / `to` / `between`) and `ORDER BY`
16
- * are exact for `integer` / `real` / `boolean` but NOT for `text`: compiled
16
+ * are exact for `integer` / `real` / `boolean` but not for `text`: compiled
17
17
  * SQL orders/ranges under SQLite's default BINARY collation, which compares
18
- * TEXT byte-for-byte as UTF-8 — equivalent to Unicode CODE-POINT order —
18
+ * TEXT byte-for-byte as UTF-8 — equivalent to Unicode code-point order —
19
19
  * while the core engine's `compareValues` orders JS strings with `<`, which
20
- * compares UTF-16 CODE-UNIT order. The two orders diverge for supplementary-
21
- * plane characters (code points ≥ U+10000, e.g. many emoji): a lead surrogate
22
- * (`\uD800`–`\uDBFF`) sorts BELOW ``–`￿` in code-unit order, while
23
- * its code point sorts ABOVE them. So `matchesConditionExactly`'s range family and
20
+ * compares UTF-16 code-unit order. The two orders diverge for supplementary-
21
+ * plane characters (code points ≥ U+10000, for example many emoji): a lead surrogate
22
+ * (`\uD800`–`\uDBFF`) sorts below ``–`￿` in code-unit order, while
23
+ * its code point sorts above them. So `matchesConditionExactly`'s range family and
24
24
  * `matchesOrderExactly` exclude `text`, refining through the core engine instead.
25
25
  */
26
26
  var EXACT_COLUMN_STORAGE = Object.freeze([
@@ -30,7 +30,7 @@ var EXACT_COLUMN_STORAGE = Object.freeze([
30
30
  "boolean"
31
31
  ]);
32
32
  /**
33
- * The declared {@link ColumnStorage}s whose SQL RANGE comparisons
33
+ * Lists the declared {@link ColumnStorage}s whose SQL range comparisons
34
34
  * (`above` / `below` / `from` / `to` / `between`) and `ORDER BY` compiles are
35
35
  * provably engine-exact — `integer` / `real` / `boolean` only. `text` is
36
36
  * excluded: see {@link EXACT_COLUMN_STORAGE}'s remarks for the BINARY-collation
@@ -43,7 +43,7 @@ var EXACT_RANGE_COLUMN_STORAGE = Object.freeze([
43
43
  "boolean"
44
44
  ]);
45
45
  /**
46
- * The reserved metadata table the {@link SQLiteDriver} creates on `open` to
46
+ * Names the reserved metadata table the {@link SQLiteDriver} creates on `open` to
47
47
  * persist its stamped `DriverMetadata` (`version` + declared schema JSON) — the
48
48
  * SQLite realization of the `metadata` / `stamp` driver hooks.
49
49
  *
@@ -55,7 +55,7 @@ var METADATA_TABLE = "_metadata";
55
55
  //#endregion
56
56
  //#region src/server/helpers.ts
57
57
  /**
58
- * Whether a caught filesystem error reports that nothing is there to read.
58
+ * Reports whether a caught filesystem error says that nothing is there to read.
59
59
  *
60
60
  * @remarks
61
61
  * Two codes carry that meaning: `ENOENT` is a plain absence, and `ENOTDIR` is a
@@ -72,7 +72,7 @@ var METADATA_TABLE = "_metadata";
72
72
  * answers `false` rather than being read for a `code` any object could carry.
73
73
  *
74
74
  * @param error - The caught value to classify; any runtime is accepted
75
- * @returns `true` when the error reports that the path holds nothing
75
+ * @returns True if the error reports that the path holds nothing; false otherwise
76
76
  *
77
77
  * @example
78
78
  * ```ts
@@ -86,21 +86,21 @@ function matchesAbsentPath(error) {
86
86
  return error.code === "ENOENT" || error.code === "ENOTDIR";
87
87
  }
88
88
  /**
89
- * Whether a value's runtime type matches a column's declared exact type
90
- * the operand side of the declared-type-trust proof.
89
+ * Reports whether a value's runtime type matches a column's declared exact type
90
+ * the operand side of the declared-type-trust proof.
91
91
  *
92
92
  * @remarks
93
- * `text` ↔ string, `integer` / `real` ↔ FINITE number (`NaN` / `±Infinity`
93
+ * `text` ↔ string, `integer` / `real` ↔ finite number (`NaN` / `±Infinity`
94
94
  * fail), `boolean` ↔ boolean. Backs {@link matchesConditionExactly}'s operand checks.
95
95
  *
96
96
  * @param value - The condition operand to test
97
- * @param type - The column's declared portable type
98
- * @returns `true` when the operand's runtime type matches the declared type
97
+ * @param storage - The column's declared portable storage type
98
+ * @returns True if the operand's runtime type matches the declared type; false otherwise
99
99
  *
100
100
  * @example
101
101
  * ```ts
102
- * matchesDeclaredType('Ada', 'text') // true
103
- * matchesDeclaredType(Number.NaN, 'integer') // false — only finite numbers
102
+ * matchesDeclaredStorage('Ada', 'text') // true
103
+ * matchesDeclaredStorage(Number.NaN, 'integer') // false — only finite numbers
104
104
  * ```
105
105
  */
106
106
  function matchesDeclaredStorage(value, storage) {
@@ -109,9 +109,9 @@ function matchesDeclaredStorage(value, storage) {
109
109
  return isFiniteNumber(value);
110
110
  }
111
111
  /**
112
- * Whether one {@link Condition} compiles to SQL that is PROVABLY identical to
113
- * the core engine's `matchesCondition` for every value its column's declared
114
- * type can store.
112
+ * Reports whether one {@link Condition} compiles to SQL that is provably
113
+ * identical to the core engine's `matchesCondition` for every value its
114
+ * column's declared type can store.
115
115
  *
116
116
  * @remarks
117
117
  * `false` for a nested `FieldPath` (an array) or a column absent from `schema`.
@@ -123,32 +123,32 @@ function matchesDeclaredStorage(value, storage) {
123
123
  * Required non-null `equals` / `not` require an operand matching the declared
124
124
  * storage and exclude `json` / `blob`. `above` / `below` / `from` / `to` /
125
125
  * `between` are exact only for {@link EXACT_RANGE_COLUMN_STORAGE} (`integer` /
126
- * `real` / `boolean`) — a `text` column's range conditions REFINE, because
127
- * SQLite's default BINARY collation orders TEXT by Unicode CODE POINT while
128
- * the core engine's `compareValues` orders JS strings by UTF-16 CODE UNIT,
126
+ * `real` / `boolean`) — a `text` column's range conditions refine, because
127
+ * SQLite's default BINARY collation orders TEXT by Unicode code point while
128
+ * the core engine's `compareValues` orders JS strings by UTF-16 code unit,
129
129
  * and the two diverge for supplementary-plane characters (see
130
130
  * {@link EXACT_COLUMN_STORAGE}'s remarks for the full rationale).
131
- * `any` / `none` require a NON-EMPTY list where every element matches (an empty
131
+ * `any` / `none` require a non-empty list where every element matches (an empty
132
132
  * list is exact under neither: the engine's `any([])` matches nothing while
133
133
  * `none([])` matches everything, and SQL `IN ()` is a syntax error) — these
134
134
  * stay exact on `text` (byte equality is collation-independent and engine-
135
135
  * identical). `starts` / `ends` are exact only on a `text` column with a
136
136
  * string operand (case-sensitive `substr` compile, see {@link compileConditionSQL}) —
137
- * likewise collation-independent. `like` / `glob` are NEVER exact — SQLite
137
+ * likewise collation-independent. `like` / `glob` are never exact — SQLite
138
138
  * `LIKE` folds case ASCII-only against the engine's Unicode fold, and `GLOB`
139
139
  * has character classes the engine treats literally.
140
140
  *
141
141
  * @param condition - The condition to test
142
142
  * @param schema - The table's schema
143
- * @returns Whether `condition` is exact
143
+ * @returns True if `condition` is exact; false otherwise
144
144
  */
145
145
  function matchesConditionExactly(condition, schema) {
146
146
  if (!isString(condition.column)) return false;
147
- const column = schema.columns.find((candidate) => candidate.name === condition.column);
147
+ const column = findColumn(condition.column, schema);
148
148
  if (column === void 0) return false;
149
149
  if (condition.operator === "absent" || condition.operator === "present") return !(column.optional && column.nullable);
150
150
  if (column.optional || column.nullable) return false;
151
- if (!EXACT_COLUMN_STORAGE.some((storage) => storage === column.storage)) return false;
151
+ if (!EXACT_COLUMN_STORAGE.includes(column.storage)) return false;
152
152
  const first = condition.values[0];
153
153
  const second = condition.values[1];
154
154
  switch (condition.operator) {
@@ -157,8 +157,8 @@ function matchesConditionExactly(condition, schema) {
157
157
  case "above":
158
158
  case "below":
159
159
  case "from":
160
- case "to": return EXACT_RANGE_COLUMN_STORAGE.some((storage) => storage === column.storage) && matchesDeclaredStorage(first, column.storage);
161
- case "between": return EXACT_RANGE_COLUMN_STORAGE.some((storage) => storage === column.storage) && matchesDeclaredStorage(first, column.storage) && matchesDeclaredStorage(second, column.storage);
160
+ case "to": return EXACT_RANGE_COLUMN_STORAGE.includes(column.storage) && matchesDeclaredStorage(first, column.storage);
161
+ case "between": return EXACT_RANGE_COLUMN_STORAGE.includes(column.storage) && matchesDeclaredStorage(first, column.storage) && matchesDeclaredStorage(second, column.storage);
162
162
  case "any":
163
163
  case "none": return condition.values.length > 0 && condition.values.every((value) => matchesDeclaredStorage(value, column.storage));
164
164
  case "starts":
@@ -168,36 +168,43 @@ function matchesConditionExactly(condition, schema) {
168
168
  }
169
169
  }
170
170
  /**
171
- * Whether one {@link Order} term's column compiles to an `ORDER BY` that
172
- * matches the engine's {@link import('@src/core').sortRows} exactly.
171
+ * Reports whether one {@link Order} term's column compiles to an `ORDER BY`
172
+ * that matches the engine's {@link import('@src/core').sortRows} exactly.
173
173
  *
174
174
  * @remarks
175
175
  * `false` for a nested `FieldPath`, a column absent from `schema`, or a
176
176
  * declared type outside {@link EXACT_RANGE_COLUMN_STORAGE} (`integer` / `real` /
177
- * `boolean`). `text` is NOT exact here: SQLite's default BINARY collation
177
+ * `boolean`). `text` is not exact here: SQLite's default BINARY collation
178
178
  * orders TEXT by Unicode code point while the core engine's `compareValues`
179
179
  * orders JS strings by UTF-16 code unit, and the two diverge for
180
180
  * supplementary-plane characters (see {@link EXACT_COLUMN_STORAGE}'s remarks) —
181
- * a `text` order term REFINES through the core engine instead.
181
+ * a `text` order term refines through the core engine instead. The column must
182
+ * also be required and non-null: an optional or nullable column refines, because
183
+ * SQL orders its `NULL`s ahead of every value while the core total order ranks
184
+ * `undefined` before `null` before every other value.
182
185
  *
183
186
  * @param order - The order term to test
184
187
  * @param schema - The table's schema
185
- * @returns Whether `order` is exact
188
+ * @returns True if `order` is exact; false otherwise
186
189
  */
187
190
  function matchesOrderExactly(order, schema) {
188
191
  if (!isString(order.column)) return false;
189
- const column = schema.columns.find((candidate) => candidate.name === order.column);
192
+ const column = findColumn(order.column, schema);
190
193
  if (column === void 0) return false;
191
- return !column.optional && !column.nullable && EXACT_RANGE_COLUMN_STORAGE.some((storage) => storage === column.storage);
194
+ return !column.optional && !column.nullable && EXACT_RANGE_COLUMN_STORAGE.includes(column.storage);
192
195
  }
193
196
  /**
194
- * Whether a whole {@link QueryInput} is exact — every condition and every order
195
- * term is exact. `limit` / `offset` never affect exactness (SQL `LIMIT` /
196
- * `OFFSET` are always engine-identical).
197
+ * Reports whether a whole {@link QueryInput} is exact — every condition and
198
+ * every order term is exact. `limit` / `offset` never affect exactness (SQL
199
+ * `LIMIT` / `OFFSET` are always engine-identical).
200
+ *
201
+ * @remarks
202
+ * The gate {@link import('./drivers/SQLiteDriver.js').SQLiteDriver} checks before
203
+ * trusting a native SQL read over a full-scan refine through the core engine.
197
204
  *
198
205
  * @param input - The query input to test
199
206
  * @param schema - The table's schema
200
- * @returns Whether every part of `input` is exact
207
+ * @returns True if every part of `input` is exact; false otherwise
201
208
  */
202
209
  function matchesQueryExactly(input, schema) {
203
210
  const conditions = input.conditions ?? [];
@@ -205,25 +212,25 @@ function matchesQueryExactly(input, schema) {
205
212
  return conditions.every((condition) => matchesConditionExactly(condition, schema)) && order.every((term) => matchesOrderExactly(term, schema));
206
213
  }
207
214
  /**
208
- * Determine whether SQLite can execute an aggregate exactly like the core engine.
215
+ * Reports whether SQLite can execute an aggregate exactly like the core engine.
209
216
  *
210
217
  * @param operation - Aggregate operation
211
218
  * @param column - Aggregate field
212
219
  * @param schema - Current table schema
213
- * @returns Whether native aggregation is exact
220
+ * @returns True if native aggregation is exact; false otherwise
214
221
  */
215
222
  function matchesAggregateExactly(operation, column, schema) {
216
223
  if (operation === "count") return true;
217
224
  if (operation === "sum" || operation === "average" || !isString(column)) return false;
218
- const declared = schema.columns.find((candidate) => candidate.name === column);
225
+ const declared = findColumn(column, schema);
219
226
  return declared !== void 0 && (declared.storage === "integer" || declared.storage === "real") && !(declared.optional && declared.nullable);
220
227
  }
221
228
  /**
222
- * Test a declared SQLite type against a portable storage affinity.
229
+ * Checks a declared SQLite type against a portable storage affinity.
223
230
  *
224
231
  * @param declared - Native declared type
225
232
  * @param storage - Portable column storage
226
- * @returns Whether SQLite's official affinity rules yield the expected affinity
233
+ * @returns True if SQLite's official affinity rules yield the expected affinity; false otherwise
227
234
  */
228
235
  function matchesSQLiteAffinity(declared, storage) {
229
236
  if (!isString(declared)) return false;
@@ -240,7 +247,7 @@ function matchesSQLiteAffinity(declared, storage) {
240
247
  return affinity === "REAL";
241
248
  }
242
249
  /**
243
- * Quote a SQL identifier (a table or column name) so any characters are literal.
250
+ * Quotes a SQL identifier (a table or column name) so any characters are literal.
244
251
  *
245
252
  * @remarks
246
253
  * Wraps the name in double quotes and doubles any embedded quote — the standard
@@ -259,7 +266,7 @@ function quoteIdentifier(identifier) {
259
266
  return "\"" + identifier.replaceAll("\"", "\"\"") + "\"";
260
267
  }
261
268
  /**
262
- * Encode a JS value to its stored {@link SQLiteValue} for a declared column.
269
+ * Encodes a JS value to its stored {@link SQLiteValue} for a declared column.
263
270
  *
264
271
  * @remarks
265
272
  * The codec is total: a malformed value encodes to SQL `NULL`. Absence always
@@ -298,7 +305,7 @@ function encodeValue(value, column) {
298
305
  }
299
306
  }
300
307
  /**
301
- * Decode a stored {@link SQLiteValue} back to its JS value for a declared column —
308
+ * Decodes a stored {@link SQLiteValue} back to its JS value for a declared column —
302
309
  * the exact inverse of {@link encodeValue}.
303
310
  *
304
311
  * @remarks
@@ -339,7 +346,7 @@ function decodeValue(value, column) {
339
346
  }
340
347
  }
341
348
  /**
342
- * Encode a whole {@link Row} to a {@link SQLiteRow} by its table's schema.
349
+ * Encodes a whole {@link Row} to a {@link SQLiteRow} by its table's schema.
343
350
  *
344
351
  * @remarks
345
352
  * Encodes each declared column's value with {@link encodeValue}; columns the row
@@ -361,7 +368,7 @@ function encodeRow(row, schema) {
361
368
  return result;
362
369
  }
363
370
  /**
364
- * Extract a stored row's values in a declared positional order.
371
+ * Extracts a stored row's values in a declared positional order.
365
372
  *
366
373
  * @remarks
367
374
  * SQLite statements bind arrays positionally. Every requested column must be
@@ -392,7 +399,7 @@ function extractValues(row, names, table) {
392
399
  return values;
393
400
  }
394
401
  /**
395
- * Decode a stored {@link SQLiteRow} back to a {@link Row} by its table's schema.
402
+ * Decodes a stored {@link SQLiteRow} back to a {@link Row} by its table's schema.
396
403
  *
397
404
  * @remarks
398
405
  * Decodes each declared column with {@link decodeValue} and **omits** any column
@@ -421,12 +428,12 @@ function decodeRow(row, schema) {
421
428
  return result;
422
429
  }
423
430
  /**
424
- * Build a collision-free SQL index name for a table + column-group index —
431
+ * Builds a collision-free SQL index name for a table + column-group index —
425
432
  * shared by the compiler module's `schemaToIndexes` and `stepToSQL`,
426
433
  * so a plan-built index name always matches one `open` would have created.
427
434
  *
428
435
  * @remarks
429
- * A naive `idx_<table>_<cols joined by _>` is AMBIGUOUS: table `'a_b'` with
436
+ * A naive `idx_<table>_<cols joined by _>` is ambiguous: table `'a_b'` with
430
437
  * column `'c'` and table `'a'` with columns `['b', 'c']` both produce
431
438
  * `idx_a_b_c`. This encodes each part (the table name, then each column name)
432
439
  * length-prefixed (`<len>_<part>`) so the boundary between parts is always
@@ -447,9 +454,39 @@ function deriveSQLiteIndexName(table, columns) {
447
454
  return "idx_" + [table, ...columns].map((part) => String(part.length) + "_" + part).join("_");
448
455
  }
449
456
  //#endregion
457
+ //#region src/server/inferers.ts
458
+ /**
459
+ * Reads the storage type a nested (`json_extract`) operand encodes as from its
460
+ * runtime value, never as `json`.
461
+ *
462
+ * @remarks
463
+ * `json_extract` returns the unquoted, natively-typed scalar (a JSON boolean as
464
+ * `1` / `0`, a number as-is, a string as-is), so the operand must encode to that
465
+ * same scalar to compare. A boolean → `'boolean'` (→ `1` / `0`); a number →
466
+ * `'integer'` / `'real'`; a bigint → `'integer'`; a string → `'text'`; `null` /
467
+ * `undefined` → `'text'` (encodes to `null`); an object / array → `'json'` (the
468
+ * edge of comparing against a json subtree).
469
+ *
470
+ * @param value - The runtime operand value
471
+ * @returns The {@link ColumnStorage} to encode it as
472
+ *
473
+ * @example
474
+ * ```ts
475
+ * inferValueStorage(true) // 'boolean'
476
+ * inferValueStorage(9) // 'integer'
477
+ * ```
478
+ */
479
+ function inferValueStorage(value) {
480
+ if (typeof value === "boolean") return "boolean";
481
+ if (typeof value === "number") return Number.isInteger(value) ? "integer" : "real";
482
+ if (typeof value === "bigint") return "integer";
483
+ if (typeof value === "object" && value !== null) return "json";
484
+ return "text";
485
+ }
486
+ //#endregion
450
487
  //#region src/server/compilers.ts
451
488
  /**
452
- * Map a portable {@link ColumnStorage} to its SQLite column type.
489
+ * Maps a portable {@link ColumnStorage} to its SQLite column type.
453
490
  *
454
491
  * @param storage - The portable column type
455
492
  * @returns The SQLite column type keyword
@@ -465,7 +502,11 @@ function compileColumnSQL(storage) {
465
502
  }
466
503
  }
467
504
  /**
468
- * Compile a {@link FieldPath} to the SQL expression that reads it.
505
+ * Compiles a {@link FieldPath} to the SQL expression that reads it.
506
+ *
507
+ * @remarks
508
+ * A flat path compiles to the quoted column; a nested path compiles to a
509
+ * `json_extract` over the head column with the rest of the path as its accessor.
469
510
  *
470
511
  * @param path - The field path
471
512
  * @returns The SQL expression selecting the value
@@ -478,7 +519,7 @@ function compileFieldSQL(path) {
478
519
  return "json_extract(" + quoteIdentifier(column) + ", '$" + rest + "')";
479
520
  }
480
521
  /**
481
- * Compile an {@link AggregateOperation} over a {@link FieldPath}.
522
+ * Compiles an {@link AggregateOperation} over a {@link FieldPath}.
482
523
  *
483
524
  * @param operation - The aggregate to compute
484
525
  * @param column - The column or nested path to aggregate
@@ -494,9 +535,9 @@ function compileAggregateSQL(operation, column) {
494
535
  }
495
536
  }
496
537
  /**
497
- * Compile a NESTED {@link FieldPath} to the `json_type(<col>, <path>)` SQL
538
+ * Compiles a nested {@link FieldPath} to the `json_type(<col>, <path>)` SQL
498
539
  * expression — the {@link compileFieldSQL} `json_extract` sibling used to tell a
499
- * PRESENT JSON `null` apart from an ABSENT path (both read back as SQL `NULL`
540
+ * present JSON `null` apart from an absent path (both read back as SQL `NULL`
500
541
  * through `json_extract`, but `json_type` reports `'null'` for the former and
501
542
  * SQL `NULL` for the latter).
502
543
  *
@@ -515,83 +556,25 @@ function compileJSONTypeSQL(path) {
515
556
  return "json_type(" + quoteIdentifier(column) + ", '$" + rest + "')";
516
557
  }
517
558
  /**
518
- * Escape `\`, `%`, and `_` (each with a leading `\`) so a `starts` / `ends`
519
- * operand is matched literally under the `LIKE … ESCAPE '\'` clause.
520
- *
521
- * @param text - The raw operand text
522
- * @returns The text with LIKE metacharacters escaped
523
- *
524
- * @example
525
- * ```ts
526
- * escapeLike('50%_off') // '50\\%\\_off'
527
- * ```
528
- */
529
- function escapeLike(text) {
530
- return text.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
531
- }
532
- /**
533
- * The declared storage type of a flat (string) column, read from the schema.
534
- *
535
- * @param column - The column name
536
- * @param schema - The table's schema
537
- * @returns The column's {@link ColumnStorage}, or `undefined` if the schema does not carry it
538
- *
539
- * @example
540
- * ```ts
541
- * findColumnStorage('age', schema) // 'integer'
542
- * ```
543
- */
544
- function findColumnStorage(column, schema) {
545
- return schema.columns.find((candidate) => candidate.name === column)?.storage;
546
- }
547
- /**
548
- * The storage type a nested (`json_extract`) operand encodes as, derived from its
549
- * RUNTIME value — NOT `json`.
550
- *
551
- * @remarks
552
- * `json_extract` returns the unquoted, natively-typed scalar (a JSON boolean as
553
- * `1` / `0`, a number as-is, a string as-is), so the operand must encode to that
554
- * same scalar to compare. A boolean → `'boolean'` (→ `1` / `0`); a number →
555
- * `'integer'` / `'real'`; a bigint → `'integer'`; a string → `'text'`; `null` /
556
- * `undefined` → `'text'` (encodes to `null`); an object / array → `'json'` (the
557
- * edge of comparing against a json subtree).
558
- *
559
- * @param value - The runtime operand value
560
- * @returns The {@link ColumnStorage} to encode it as
561
- *
562
- * @example
563
- * ```ts
564
- * inferValueStorage(true) // 'boolean'
565
- * inferValueStorage(9) // 'integer'
566
- * ```
567
- */
568
- function inferValueStorage(value) {
569
- if (typeof value === "boolean") return "boolean";
570
- if (typeof value === "number") return Number.isInteger(value) ? "integer" : "real";
571
- if (typeof value === "bigint") return "integer";
572
- if (typeof value === "object" && value !== null) return "json";
573
- return "text";
574
- }
575
- /**
576
- * Compile one condition to its `<column> <operator>` SQL fragment and the parameters
559
+ * Compiles one condition to its `<column> <operator>` SQL fragment and the parameters
577
560
  * it binds — engine-exact under SQL's three-valued NULL logic.
578
561
  *
579
562
  * @remarks
580
563
  * Every operand is run through `encodeValue`, so a bound value matches the SQL
581
- * the column side compiles to. A flat column encodes operands with its DECLARED
564
+ * the column side compiles to. A flat column encodes operands with its declared
582
565
  * schema type (a flat `json` column → `JSON.stringify`); a nested `FieldPath`
583
- * encodes each operand as the NATIVE scalar `json_extract` returns, derived from
566
+ * encodes each operand as the native scalar `json_extract` returns, derived from
584
567
  * the operand's runtime type (per-operand, since `between` / `any` / `none` can
585
568
  * mix types). `any` / `none` collapse an empty list to a constant (`0` matches
586
569
  * nothing, `1` matches all) with no parameters.
587
570
  *
588
- * The core engine's total order ranks `undefined` (rank 0) BELOW `null`
589
- * (rank 1) (see `compareValues`), so a MISSING/`NULL` column MATCHES
571
+ * The core engine's total order ranks `undefined` (rank 0) below `null`
572
+ * (rank 1) (see `compareValues`), so a missing/`NULL` column matches
590
573
  * `below` / `to` / a scalar `not` / `none` — the opposite of raw SQL, where a
591
574
  * comparison against `NULL` is `NULL` (excluded). This fragment replicates the
592
575
  * engine exactly. Truth table (`value` = the engine's decoded field read; a
593
- * FLAT column's stored `NULL` decodes to `undefined` per `decodeRow`, so a
594
- * flat `value` is NEVER a present `null` — only a NESTED path can be
576
+ * flat column's stored `NULL` decodes to `undefined` per `decodeRow`, so a
577
+ * flat `value` is never a present `null` — only a nested path can be
595
578
  * present-but-`null`):
596
579
  *
597
580
  * ```text
@@ -617,15 +600,15 @@ function inferValueStorage(value) {
617
600
  * refines every optional or nullable scalar comparison through the core engine.
618
601
  * This compiler still emits a total SQL fragment for direct consumers.
619
602
  *
620
- * A NESTED path can be present-but-`null` (a stored JSON `null`), which
621
- * `json_extract` reads back as SQL `NULL` — indistinguishable from an ABSENT
603
+ * A nested path can be present-but-`null` (a stored JSON `null`), which
604
+ * `json_extract` reads back as SQL `NULL` — indistinguishable from an absent
622
605
  * path. `json_type(col, path)` disambiguates them (`'null'` for present-null,
623
606
  * SQL `NULL` for absent), so nested `equals` / `not` against a `null` operand
624
607
  * compile through `json_type` instead of `IS NULL` / `IS NOT NULL`.
625
608
  *
626
609
  * Every other MATCH-on-null-or-absent row is expressed uniformly (flat and
627
610
  * nested alike) as `(<column> <op> ? OR <column> IS NULL)` — for a nested
628
- * path, `json_extract` already collapses BOTH absent and present-null to SQL
611
+ * path, `json_extract` already collapses both absent and present-null to SQL
629
612
  * `NULL`, so `IS NULL` catches both in one clause; for a flat column there is
630
613
  * only the absent case to catch.
631
614
  *
@@ -644,7 +627,7 @@ function inferValueStorage(value) {
644
627
  function compileConditionSQL(condition, schema) {
645
628
  const column = compileFieldSQL(condition.column);
646
629
  const nested = !isString(condition.column);
647
- const declared = isString(condition.column) ? schema.columns.find((candidate) => candidate.name === condition.column) : void 0;
630
+ const declared = isString(condition.column) ? findColumn(condition.column, schema) : void 0;
648
631
  const first = condition.values[0];
649
632
  const second = condition.values[1];
650
633
  const nullOperand = first === null || first === void 0;
@@ -763,13 +746,13 @@ function compileConditionSQL(condition, schema) {
763
746
  };
764
747
  }
765
748
  /**
766
- * Fold the conditions into one WHERE clause, parenthesizing progressively
749
+ * Folds the conditions into one WHERE clause, parenthesizing progressively
767
750
  * left-to-right so the grouping matches the engine's `matchesQuery` fold.
768
751
  *
769
752
  * @remarks
770
753
  * The first condition's connector is ignored, per the {@link Condition} types.
771
754
  * Every fragment (see {@link compileConditionSQL}'s truth table) replicates the core
772
- * engine's total order EXACTLY under SQL's three-valued NULL logic, so this
755
+ * engine's total order exactly under SQL's three-valued NULL logic, so this
773
756
  * clause matches `applyQuery` row-for-row over the same table — a native
774
757
  * `records` / `count` read never disagrees with a scan-and-filter fallback.
775
758
  *
@@ -779,11 +762,11 @@ function compileConditionSQL(condition, schema) {
779
762
  *
780
763
  * @example
781
764
  * ```ts
782
- * compileWhere([{ column: 'age', operator: 'from', values: [18], connector: 'and' }], schema)
765
+ * compileWhereSQL([{ column: 'age', operator: 'from', values: [18], connector: 'and' }], schema)
783
766
  * // { sql: 'WHERE "age" >= ?', parameters: [18] }
784
767
  * ```
785
768
  */
786
- function compileWhere(conditions, schema) {
769
+ function compileWhereSQL(conditions, schema) {
787
770
  const [first, ...remaining] = conditions;
788
771
  if (first === void 0) return {
789
772
  sql: "",
@@ -804,14 +787,14 @@ function compileWhere(conditions, schema) {
804
787
  };
805
788
  }
806
789
  /**
807
- * Compile the ORDER BY clause from the order terms, always ending with the
790
+ * Compiles the ORDER BY clause from the order terms, always ending with the
808
791
  * primary key as the final determinant.
809
792
  *
810
793
  * @remarks
811
794
  * The native `records` read then resolves ties in key order, matching a
812
795
  * primary-key-ordered `scan` and the core engine's stable `sortRows` over a
813
796
  * key-ordered scan (and IndexedDB's key-ordered reads), so a native read equals
814
- * the scan path (AGENTS §21 / §22 native ↔ engine parity). SQLite without an
797
+ * the scan path native ↔ engine parity. SQLite without an
815
798
  * `ORDER BY` returns rowid (insertion) order, and an explicit order alone breaks
816
799
  * ties by rowid too — both diverge from every key-ordered backend. The
817
800
  * tie-breaker is ASCENDING regardless of the explicit directions: the engine's
@@ -825,17 +808,17 @@ function compileWhere(conditions, schema) {
825
808
  *
826
809
  * @example
827
810
  * ```ts
828
- * compileOrder([{ column: 'age', direction: 'descending' }], schema)
811
+ * compileOrderSQL([{ column: 'age', direction: 'descending' }], schema)
829
812
  * // 'ORDER BY "age" DESC, "id"'
830
813
  * ```
831
814
  */
832
- function compileOrder(order, schema) {
815
+ function compileOrderSQL(order, schema) {
833
816
  const terms = (order ?? []).map((term) => compileFieldSQL(term.column) + (term.direction === "descending" ? " DESC" : " ASC"));
834
817
  if (!(order ?? []).some((term) => isString(term.column) && term.column === schema.primary)) terms.push(quoteIdentifier(schema.primary));
835
818
  return terms.length === 0 ? "" : "ORDER BY " + terms.join(", ");
836
819
  }
837
820
  /**
838
- * Compile the LIMIT / OFFSET clause.
821
+ * Compiles the LIMIT / OFFSET clause.
839
822
  *
840
823
  * @remarks
841
824
  * An offset without a limit uses `LIMIT -1` (SQLite's "no limit") so OFFSET is
@@ -847,10 +830,10 @@ function compileOrder(order, schema) {
847
830
  *
848
831
  * @example
849
832
  * ```ts
850
- * compilePage(undefined, 5) // { sql: 'LIMIT -1 OFFSET ?', parameters: [5] }
833
+ * compilePageSQL(undefined, 5) // { sql: 'LIMIT -1 OFFSET ?', parameters: [5] }
851
834
  * ```
852
835
  */
853
- function compilePage(limit, offset) {
836
+ function compilePageSQL(limit, offset) {
854
837
  validatePage({
855
838
  ...limit === void 0 ? {} : { limit },
856
839
  ...offset === void 0 ? {} : { offset }
@@ -873,7 +856,7 @@ function compilePage(limit, offset) {
873
856
  };
874
857
  }
875
858
  /**
876
- * Compile a {@link QueryInput} into the SQL clause that follows a table name, with
859
+ * Compiles a {@link QueryInput} into the SQL clause that follows a table name, with
877
860
  * its bound parameters in clause order.
878
861
  *
879
862
  * @remarks
@@ -883,11 +866,13 @@ function compilePage(limit, offset) {
883
866
  * over a JS `scan`. The WHERE fold is parenthesized **left-to-right** to mirror
884
867
  * the core engine's `matchesQuery` (not SQL's native AND-over-OR precedence),
885
868
  * so a native and an engine read return identical rows. Each operand is encoded
886
- * via `encodeValue`: a flat column uses its declared schema type, while a nested
869
+ * through `encodeValue`: a flat column uses its declared schema type, while a nested
887
870
  * `FieldPath` (a `json_extract` read) encodes each operand as the native scalar
888
871
  * the extract returns — derived from the operand's runtime type — so it compares.
889
- * The 15 operators map per the databases guide's operator table, with
890
- * `starts` / `ends` using `LIKE ESCAPE '\'` and an empty `any` / `none` list
872
+ * Every operator maps per the databases guide's operator table, with
873
+ * `starts` / `ends` compiling to a code-point `substr` slice guarded by
874
+ * `typeof(<column>) = 'text'` (case-sensitive, matching the engine's
875
+ * `String.prototype.startsWith` / `endsWith`) and an empty `any` / `none` list
891
876
  * collapsing to a constant. An `undefined` input (or one with no parts)
892
877
  * compiles to an empty clause.
893
878
  *
@@ -903,9 +888,9 @@ function compilePage(limit, offset) {
903
888
  */
904
889
  function compileQuerySQL(input, schema) {
905
890
  validatePage(input);
906
- const where = compileWhere(input?.conditions ?? [], schema);
907
- const orderBy = compileOrder(input?.order, schema);
908
- const page = compilePage(input?.limit, input?.offset);
891
+ const where = compileWhereSQL(input?.conditions ?? [], schema);
892
+ const orderBy = compileOrderSQL(input?.order, schema);
893
+ const page = compilePageSQL(input?.limit, input?.offset);
909
894
  return {
910
895
  sql: [
911
896
  where.sql,
@@ -916,7 +901,7 @@ function compileQuerySQL(input, schema) {
916
901
  };
917
902
  }
918
903
  /**
919
- * Project a {@link TableSchema} to its `CREATE TABLE IF NOT EXISTS` statement.
904
+ * Projects a {@link TableSchema} to its `CREATE TABLE IF NOT EXISTS` statement.
920
905
  *
921
906
  * @param schema - The table schema
922
907
  * @returns The complete table declaration
@@ -926,7 +911,11 @@ function schemaToTable(schema) {
926
911
  return "CREATE TABLE IF NOT EXISTS " + quoteIdentifier(schema.name) + " (" + columns.join(", ") + ", PRIMARY KEY (" + quoteIdentifier(schema.primary) + "))";
927
912
  }
928
913
  /**
929
- * Project a {@link TableSchema} to its declared SQLite indexes.
914
+ * Projects a {@link TableSchema} to its declared SQLite indexes.
915
+ *
916
+ * @remarks
917
+ * Each statement is a `CREATE INDEX IF NOT EXISTS` named by
918
+ * {@link deriveSQLiteIndexName}, so a reopen re-issues the set safely.
930
919
  *
931
920
  * @param schema - The table schema
932
921
  * @returns One statement per declared index
@@ -935,7 +924,11 @@ function schemaToIndexes(schema) {
935
924
  return schema.indexes.map((group) => "CREATE INDEX IF NOT EXISTS " + quoteIdentifier(deriveSQLiteIndexName(schema.name, group)) + " ON " + quoteIdentifier(schema.name) + " (" + group.map(quoteIdentifier).join(", ") + ")");
936
925
  }
937
926
  /**
938
- * Project one {@link MigrationStep} to SQLite DDL.
927
+ * Projects one {@link MigrationStep} to SQLite DDL.
928
+ *
929
+ * @remarks
930
+ * These are the statements the SQLite driver's `migrate` executes for the step,
931
+ * inside whichever native transaction is active.
939
932
  *
940
933
  * @param step - The migration step
941
934
  * @returns The statements that apply the step
@@ -951,97 +944,9 @@ function stepToSQL(step) {
951
944
  }
952
945
  }
953
946
  //#endregion
954
- //#region src/core/DriverIterator.ts
955
- /**
956
- * The internal continuation boundary for a root driver async iterator.
957
- *
958
- * @remarks
959
- * A driver transaction can begin while a caller holds an idle root iterator.
960
- * Every `next` therefore checks the driver's root-state guard immediately
961
- * before and after advancing the source. A failed continuation terminalizes the
962
- * iterator, discards any row produced before the post-advance guard failed, and
963
- * attempts source cleanup exactly once.
964
- */
965
- var DriverIterator = class {
966
- #source;
967
- #guard;
968
- #terminal = false;
969
- #cleaned = false;
970
- constructor(source, guard) {
971
- this.#source = source;
972
- this.#guard = guard;
973
- }
974
- [Symbol.asyncIterator]() {
975
- return this;
976
- }
977
- async next() {
978
- if (this.#terminal) return {
979
- done: true,
980
- value: void 0
981
- };
982
- try {
983
- this.#guard();
984
- const result = await this.#source.next();
985
- this.#guard();
986
- if (result.done === true) {
987
- this.#terminal = true;
988
- this.#cleaned = true;
989
- }
990
- return result;
991
- } catch (error) {
992
- this.#terminal = true;
993
- await this.#discard();
994
- throw error;
995
- }
996
- }
997
- async return() {
998
- if (this.#terminal) return {
999
- done: true,
1000
- value: void 0
1001
- };
1002
- this.#terminal = true;
1003
- if (this.#cleaned || this.#source.return === void 0) {
1004
- this.#cleaned = true;
1005
- return {
1006
- done: true,
1007
- value: void 0
1008
- };
1009
- }
1010
- this.#cleaned = true;
1011
- return this.#source.return();
1012
- }
1013
- async throw(error) {
1014
- if (this.#terminal) throw error;
1015
- if (this.#source.throw === void 0) {
1016
- this.#terminal = true;
1017
- await this.#discard();
1018
- throw error;
1019
- }
1020
- try {
1021
- const result = await this.#source.throw(error);
1022
- if (result.done === true) {
1023
- this.#terminal = true;
1024
- this.#cleaned = true;
1025
- }
1026
- return result;
1027
- } catch (cause) {
1028
- this.#terminal = true;
1029
- await this.#discard();
1030
- throw cause;
1031
- }
1032
- }
1033
- async #discard() {
1034
- if (this.#cleaned) return;
1035
- this.#cleaned = true;
1036
- try {
1037
- await this.#source.return?.();
1038
- } catch {}
1039
- }
1040
- };
1041
- //#endregion
1042
947
  //#region src/server/drivers/JSONDriver.ts
1043
948
  /**
1044
- * A persistent {@link DriverInterface} backed by a single JSON file — the
949
+ * Implements a persistent {@link DriverInterface} backed by a single JSON file — the
1045
950
  * reference {@link MemoryDriver} plus file load / flush.
1046
951
  *
1047
952
  * @remarks
@@ -1055,15 +960,15 @@ var DriverIterator = class {
1055
960
  * primary (the table contract), so the key is recovered on load with
1056
961
  * {@link extractKey} and the file need not store it. The parsed JSON crosses the
1057
962
  * boundary as `unknown` and is narrowed with {@link isRecord} / {@link extractKey},
1058
- * never asserted (AGENTS §14). A read that reports no document there starts empty —
963
+ * never asserted. A read that reports no document there starts empty —
1059
964
  * `ENOENT` for a plain absence, and `ENOTDIR` for a path whose parent is not a
1060
965
  * directory, which no later write could find either; every other read failure or
1061
966
  * invalid existing document fails closed without publication, mutation, or
1062
- * automatic repair. It is scan-only it implements none of
1063
- * the optional native `records` / `aggregate` hooks, so the core engine
1064
- * over `scan` answers every query. For development, small datasets, and portable /
1065
- * inspectable data; for large or concurrent workloads reach for a SQLite-backed
1066
- * driver.
967
+ * automatic repair. It implements the optional native `stream` hook that
968
+ * `TableInterface.scan` prefers over `scan`, and neither `records` nor
969
+ * `aggregate`, so the core engine's `matchesQuery` answers every query on
970
+ * either path. For development, small datasets, and portable / inspectable
971
+ * data; for large or concurrent workloads reach for a SQLite-backed driver.
1067
972
  *
1068
973
  * Metadata crosses {@link cloneDriverMetadata} at parsed-file ingress, public and
1069
974
  * scoped write ingress, candidate/root publication, serialization, and copy-out.
@@ -1127,7 +1032,7 @@ var JSONDriver = class {
1127
1032
  return new DriverIterator(this.#scan(table)[Symbol.asyncIterator](), () => this.#root());
1128
1033
  }
1129
1034
  /**
1130
- * Natively filtered lazy iteration — delegates to the inner {@link MemoryDriver}.
1035
+ * Iterates rows lazily with native filtering — delegates to the inner {@link MemoryDriver}.
1131
1036
  *
1132
1037
  * @remarks
1133
1038
  * Semantics are the memory driver's own: `input.conditions` filters, `offset`
@@ -1146,7 +1051,7 @@ var JSONDriver = class {
1146
1051
  await this.#enqueue(() => this.#clear(table));
1147
1052
  }
1148
1053
  /**
1149
- * Run an isolated native transaction callback over a candidate memory store.
1054
+ * Runs an isolated native transaction callback over a candidate memory store.
1150
1055
  *
1151
1056
  * @remarks
1152
1057
  * Single-writer: nesting and root operations while active throw `CONFLICT`.
@@ -1163,7 +1068,7 @@ var JSONDriver = class {
1163
1068
  return this.#enqueue(() => this.#transact(scope));
1164
1069
  }
1165
1070
  /**
1166
- * Capture an owned row snapshot at an exact writer-queue position.
1071
+ * Captures an owned row snapshot at an exact writer-queue position.
1167
1072
  *
1168
1073
  * @remarks
1169
1074
  * Capture owns table names, schemas, rows, and one session-local identity per
@@ -1191,7 +1096,7 @@ var JSONDriver = class {
1191
1096
  return this.#metadata === void 0 ? void 0 : cloneDriverMetadata(this.#metadata);
1192
1097
  }
1193
1098
  /**
1194
- * Persist an owned metadata snapshot for a later `metadata()` to copy out.
1099
+ * Persists an owned metadata snapshot for a later `metadata()` to copy out.
1195
1100
  *
1196
1101
  * @remarks
1197
1102
  * Root stamping conflicts while a transaction is active. The scoped
@@ -1206,7 +1111,7 @@ var JSONDriver = class {
1206
1111
  await this.#enqueue(() => this.#stamp(owned));
1207
1112
  }
1208
1113
  /**
1209
- * Apply one atomic {@link MigrationInput} through an isolated candidate.
1114
+ * Applies one atomic {@link MigrationInput} through an isolated candidate.
1210
1115
  *
1211
1116
  * @remarks
1212
1117
  * The candidate receives every plan step plus optional metadata. Its complete
@@ -1420,7 +1325,8 @@ var JSONDriver = class {
1420
1325
  return this.#requireCandidate(token).keys(table);
1421
1326
  }
1422
1327
  #scanCandidate(token, table) {
1423
- return new DriverIterator(this.#requireCandidate(token).scan(table)[Symbol.asyncIterator](), () => {
1328
+ const source = this.#requireCandidate(token).scan(table);
1329
+ return new DriverIterator(source[Symbol.asyncIterator](), () => {
1424
1330
  this.#requireCandidate(token);
1425
1331
  });
1426
1332
  }
@@ -1667,8 +1573,8 @@ var JSONDriver = class {
1667
1573
  //#endregion
1668
1574
  //#region src/server/drivers/SQLiteDriver.ts
1669
1575
  /**
1670
- * The SQLite {@link DriverInterface} — the server-native, trusted-mode backend
1671
- * built on the published `@orkestrel/sqlite` synchronous wrapper.
1576
+ * Implements the {@link DriverInterface} over SQLite — the server-native, trusted-mode
1577
+ * backend built on the published `@orkestrel/sqlite` synchronous wrapper.
1672
1578
  *
1673
1579
  * @remarks
1674
1580
  * A thin adapter: it implements the storage primitives the core database layer
@@ -1679,41 +1585,40 @@ var JSONDriver = class {
1679
1585
  * reopen-safe), and readies a reserved `_metadata` single-row table `metadata()` /
1680
1586
  * `stamp()` read and write — **a user table named `_metadata` collides with it**;
1681
1587
  * avoid the name. Rows cross the boundary through the codecs in `helpers.ts`
1682
- * (`json` columns store / parse JSON text, a `boolean` stores `1` / `0`), so the
1683
- * typed layer above imposes the exact shape (AGENTS §14). `write` is an
1588
+ * (`json` columns store / parse JSON text, a `boolean` stores `1` / `0`), so
1589
+ * the typed layer above imposes the exact shape. `write` is an
1684
1590
  * `INSERT OR REPLACE` upsert, while `insert` uses a plain `INSERT` and maps its
1685
1591
  * atomic primary-key constraint failure to `CONFLICT`; every other backend
1686
1592
  * `SQLiteError` is contained by the same `DatabaseError` boundary described
1687
- * below. Querying, ordering, paging, and
1688
- * aggregation is native: `records` / `stream` compile a `QueryInput`
1689
- * to SQL with `compileQuerySQL`, and `aggregate` runs a SQL
1690
- * `COUNT`/`SUM`/`AVG`/`MIN`/`MAX` (via `compileAggregateSQL`) over the same compiled
1691
- * WHERE. `transaction` runs a callback inside native `BEGIN` / `COMMIT` /
1692
- * `ROLLBACK`, passing a scoped storage capability that becomes invalid after
1693
- * settlement. `migrate` runs the plan's projected DDL
1694
- * ({@link import('../compilers.js').stepToSQL}) inside whichever native
1695
- * transaction is active: joined into the active transaction callback
1696
- * when one exists (the core's versioned reconcile path wraps migrate + stamp
1697
- * in one native `BEGIN`, and node:sqlite rejects a nested `BEGIN`), or inside
1698
- * its own `database.transaction` otherwise a mid-plan failure rolls back
1699
- * atomically either way, an improvement over the non-atomic `MemoryDriver` /
1700
- * `JSONDriver` migrate; a step referencing an undeclared table throws
1593
+ * below. Querying, ordering, paging, and aggregation is native: `records` /
1594
+ * `stream` compile a `QueryInput` to SQL with `compileQuerySQL`, and
1595
+ * `aggregate` runs a SQL `COUNT`/`SUM`/`AVG`/`MIN`/`MAX` (through
1596
+ * `compileAggregateSQL`) over the same compiled WHERE. `transaction` runs a
1597
+ * callback inside native `BEGIN` / `COMMIT` / `ROLLBACK`, passing a scoped
1598
+ * storage capability that becomes invalid after settlement. `migrate` runs the
1599
+ * plan's projected DDL ({@link import('../compilers.js').stepToSQL}) inside
1600
+ * whichever native transaction is active: joined into the active transaction
1601
+ * callback when one exists (the core's versioned reconcile path wraps migrate +
1602
+ * stamp in one native `BEGIN`, and node:sqlite rejects a nested `BEGIN`), or
1603
+ * inside its own `database.transact` otherwise a mid-plan failure rolls
1604
+ * back atomically either way, an improvement over the non-atomic `MemoryDriver`
1605
+ * / `JSONDriver` migrate; a step referencing an undeclared table throws
1701
1606
  * `DatabaseError` `MIGRATION` before any DDL for that step runs. `snapshot` is
1702
- * capture-replay (SELECT the
1703
- * named tables' rows, replay via DELETE + INSERT OR REPLACE inside a native
1704
- * transaction on rollback) rather than a SQL `SAVEPOINT`, since the core
1705
- * `transaction` calls the rollback thunk only on failure with no commit-on-
1706
- * success signal a long-lived `SAVEPOINT` would leave the connection
1707
- * uncommitted (lost on close). Every backend interaction runs through `#guard`,
1708
- * which maps a thrown backend `SQLiteError` (or any unexpected non-`SQLiteError`
1709
- * throw) to a typed {@link DatabaseError} never a raw backend error escapes
1710
- * `DriverInterface`: `CONSTRAINT` → `CONFLICT`, the wrapper's own `CLOSED`
1711
- * `CLOSED`, `BUSY` (a locked database that outlasted the configured `timeout`)
1712
- * → a retryable `DRIVER` (`context.retryable` is `true`), and `UNKNOWN` / any
1713
- * other throw `DRIVER`. The original error is preserved as `context.cause`.
1714
- * A `DatabaseError` this driver throws directly (`CLOSED` from the `#require`
1715
- * gate, `NOT_FOUND` from `#table`, `MIGRATION` from a migration-plan fault)
1716
- * passes through `#guard` unchanged, never re-wrapped.
1607
+ * capture-replay (SELECT the named tables' rows, replay through DELETE + INSERT OR
1608
+ * REPLACE inside a native transaction on rollback) rather than a SQL
1609
+ * `SAVEPOINT`, since the core `transaction` calls the rollback thunk only on
1610
+ * failure with no commit-on-success signal — a long-lived `SAVEPOINT` would
1611
+ * leave the connection uncommitted (lost on close). Every backend interaction
1612
+ * runs through `#guard`, which maps a thrown backend `SQLiteError` (or any
1613
+ * unexpected non-`SQLiteError` throw) to a typed {@link DatabaseError} never
1614
+ * a raw backend error escapes `DriverInterface`: `CONSTRAINT` `CONFLICT`, the
1615
+ * wrapper's own `CLOSED` → `CLOSED`, `BUSY` (a locked database that outlasted
1616
+ * the configured `timeout`) a retryable `DRIVER` (`context.retryable` is
1617
+ * `true`), and `UNKNOWN` / any other throw → `DRIVER`. The original error is
1618
+ * preserved as `context.cause`. A `DatabaseError` this driver throws directly
1619
+ * (`CLOSED` from the `#require` gate, `NOT_FOUND` from `#table`, `MIGRATION`
1620
+ * from a migration-plan fault) passes through `#guard` unchanged, never
1621
+ * re-wrapped.
1717
1622
  */
1718
1623
  var SQLiteDriver = class {
1719
1624
  #path;
@@ -1750,7 +1655,7 @@ var SQLiteDriver = class {
1750
1655
  for (const [name, value] of Object.entries(this.#options.pragmas ?? {})) database.pragma(name, value);
1751
1656
  const map = /* @__PURE__ */ new Map();
1752
1657
  const identities = /* @__PURE__ */ new Map();
1753
- database.transaction(() => {
1658
+ database.transact(() => {
1754
1659
  this.#ensureMetadataTable(database);
1755
1660
  const stored = this.#readMetadata(database);
1756
1661
  const deployed = normalizeDriverSchema(stored?.schema ?? owned);
@@ -1768,11 +1673,11 @@ var SQLiteDriver = class {
1768
1673
  for (const table of deployed) {
1769
1674
  const absent = missing.get(table.name);
1770
1675
  if (absent === void 0) {
1771
- database.exec(schemaToTable(table));
1772
- for (const sql of schemaToIndexes(table)) database.exec(sql);
1676
+ database.execute(schemaToTable(table));
1677
+ for (const sql of schemaToIndexes(table)) database.execute(sql);
1773
1678
  } else for (const [index, group] of table.indexes.entries()) if (absent.includes(deriveSQLiteIndexName(table.name, group))) {
1774
1679
  const sql = schemaToIndexes(table)[index];
1775
- if (sql !== void 0) database.exec(sql);
1680
+ if (sql !== void 0) database.execute(sql);
1776
1681
  }
1777
1682
  identities.set(table.name, previousIdentities.get(table.name) ?? {});
1778
1683
  }
@@ -1844,7 +1749,7 @@ var SQLiteDriver = class {
1844
1749
  const conditionsExact = conditions.every((condition) => matchesConditionExactly(condition, schema));
1845
1750
  const columnExact = matchesAggregateExactly(operation, column, schema);
1846
1751
  if (conditionsExact && columnExact) return this.#guard(() => {
1847
- const { sql, parameters } = compileWhere(conditions, schema);
1752
+ const { sql, parameters } = compileWhereSQL(conditions, schema);
1848
1753
  const value = this.#require().prepare("SELECT " + compileAggregateSQL(operation, column) + " AS value FROM " + quoteIdentifier(table) + (sql === "" ? "" : " " + sql)).get(parameters)?.value;
1849
1754
  return value === null || value === void 0 ? void 0 : Number(value);
1850
1755
  });
@@ -1857,7 +1762,7 @@ var SQLiteDriver = class {
1857
1762
  return new DriverIterator(this.#stream(table, input)[Symbol.asyncIterator](), () => this.#root());
1858
1763
  }
1859
1764
  /**
1860
- * Begin a native transaction — real `BEGIN`, `COMMIT`, `ROLLBACK`.
1765
+ * Begins a native transaction — real `BEGIN`, `COMMIT`, `ROLLBACK`.
1861
1766
  *
1862
1767
  * @remarks
1863
1768
  * The callback receives a scoped {@link StorageInterface}. Fulfillment
@@ -1904,23 +1809,22 @@ var SQLiteDriver = class {
1904
1809
  }
1905
1810
  }
1906
1811
  /**
1907
- * Apply a {@link Migration} plan by executing each step's projected DDL
1812
+ * Applies a {@link Migration} plan by executing each step's projected DDL
1908
1813
  * ({@link import('../compilers.js').stepToSQL}).
1909
1814
  *
1910
1815
  * @remarks
1911
- * Atomicity is provided by whichever native transaction is active: when
1912
- * this driver's own `transaction()` callback is active (the
1913
- * core's versioned reconcile / migrate path joins migrate + stamp under
1914
- * one native `BEGIN`), the plan's DDL runs directly inside that enclosing
1915
- * transaction — a mid-plan failure rejects the callback and the driver
1916
- * rolls it back. node:sqlite (and SQLite
1917
- * generally) rejects a nested `BEGIN`, so this driver must never open a
1816
+ * Atomicity is provided by whichever native transaction is active: when this
1817
+ * driver's own `transaction()` callback is active (the core's versioned
1818
+ * reconcile / migrate path joins migrate + stamp under one native `BEGIN`),
1819
+ * the plan's DDL runs directly inside that enclosing transaction — a mid-plan
1820
+ * failure rejects the callback and the driver rolls it back. node:sqlite (and
1821
+ * SQLite generally) rejects a nested `BEGIN`, so this driver must never open a
1918
1822
  * second native transaction while one is already open. Otherwise (no
1919
1823
  * enclosing transaction), `migrate` wraps the plan in its own native
1920
1824
  * `database.transaction` — atomic on its own: a mid-plan failure rolls
1921
1825
  * back every DDL statement already applied by the plan. A scoped migration
1922
1826
  * uses one fixed internal savepoint literal because the published SQLite
1923
- * wrapper intentionally exposes raw `exec` but no savepoint manager. That
1827
+ * wrapper intentionally exposes raw `execute` but no savepoint manager. That
1924
1828
  * savepoint contains a caught inner migration so the outer callback
1925
1829
  * transaction remains active and may continue safely. A step referencing a
1926
1830
  * table not in this driver's declared schema (and that is not itself a
@@ -1940,7 +1844,7 @@ var SQLiteDriver = class {
1940
1844
  metadata: owned.metadata.schema
1941
1845
  });
1942
1846
  this.#guard(() => {
1943
- database.transaction(() => {
1847
+ database.transact(() => {
1944
1848
  this.#applyPlan(database, owned);
1945
1849
  if (owned.metadata !== void 0) this.#writeMetadata(database, owned.metadata);
1946
1850
  });
@@ -1949,7 +1853,7 @@ var SQLiteDriver = class {
1949
1853
  this.#identities = identities;
1950
1854
  }
1951
1855
  /**
1952
- * Read the persisted {@link DriverMetadata} from the reserved `_metadata` table.
1856
+ * Reads the persisted {@link DriverMetadata} from the reserved `_metadata` table.
1953
1857
  *
1954
1858
  * @returns The last-stamped `DriverMetadata`, or `undefined` when never stamped
1955
1859
  * (or the stored row is malformed)
@@ -1959,7 +1863,7 @@ var SQLiteDriver = class {
1959
1863
  return this.#metadata();
1960
1864
  }
1961
1865
  /**
1962
- * Persist an owned metadata snapshot into the reserved `_metadata` table's
1866
+ * Persists an owned metadata snapshot into the reserved `_metadata` table's
1963
1867
  * single row.
1964
1868
  *
1965
1869
  * @param metadata - The {@link DriverMetadata} to persist
@@ -2014,11 +1918,11 @@ var SQLiteDriver = class {
2014
1918
  }
2015
1919
  this.#guard(() => {
2016
1920
  const current = this.#require();
2017
- current.transaction(() => {
1921
+ current.transact(() => {
2018
1922
  for (const [name, replacement] of replacements) {
2019
- current.exec("DELETE FROM " + quoteIdentifier(name));
1923
+ current.execute("DELETE FROM " + quoteIdentifier(name));
2020
1924
  const statement = current.prepare("INSERT OR REPLACE INTO " + quoteIdentifier(name) + " (" + replacement.names.map(quoteIdentifier).join(", ") + ") VALUES (" + replacement.names.map(() => "?").join(", ") + ")");
2021
- for (const values of replacement.values) statement.run(values);
1925
+ for (const values of replacement.values) statement.execute(values);
2022
1926
  }
2023
1927
  });
2024
1928
  });
@@ -2039,7 +1943,7 @@ var SQLiteDriver = class {
2039
1943
  const values = extractValues(encoded, names, table);
2040
1944
  const statement = this.#require().prepare("INSERT OR REPLACE INTO " + quoteIdentifier(table) + " (" + names.map(quoteIdentifier).join(", ") + ") VALUES (" + names.map(() => "?").join(", ") + ")");
2041
1945
  checkAbort(options?.signal);
2042
- statement.run(values);
1946
+ statement.execute(values);
2043
1947
  });
2044
1948
  }
2045
1949
  async #insert(table, key, row, options) {
@@ -2050,7 +1954,7 @@ var SQLiteDriver = class {
2050
1954
  const values = extractValues(encoded, names, table);
2051
1955
  const statement = this.#require().prepare("INSERT INTO " + quoteIdentifier(table) + " (" + names.map(quoteIdentifier).join(", ") + ") VALUES (" + names.map(() => "?").join(", ") + ")");
2052
1956
  checkAbort(options?.signal);
2053
- statement.run(values);
1957
+ statement.execute(values);
2054
1958
  });
2055
1959
  }
2056
1960
  async #delete(table, key, options) {
@@ -2058,7 +1962,7 @@ var SQLiteDriver = class {
2058
1962
  return this.#guard(() => {
2059
1963
  const statement = this.#require().prepare("DELETE FROM " + quoteIdentifier(table) + " WHERE " + quoteIdentifier(schema.primary) + " = ?");
2060
1964
  checkAbort(options?.signal);
2061
- return statement.run([this.#key(key, schema)]).changes > 0;
1965
+ return statement.execute([this.#key(key, schema)]).changes > 0;
2062
1966
  });
2063
1967
  }
2064
1968
  async #keys(table) {
@@ -2120,14 +2024,14 @@ var SQLiteDriver = class {
2120
2024
  async #clear(table) {
2121
2025
  this.#table(table);
2122
2026
  this.#guard(() => {
2123
- this.#require().prepare("DELETE FROM " + quoteIdentifier(table)).run();
2027
+ this.#require().prepare("DELETE FROM " + quoteIdentifier(table)).execute();
2124
2028
  });
2125
2029
  }
2126
2030
  async #metadata() {
2127
2031
  return this.#guard(() => this.#readMetadata(this.#require()));
2128
2032
  }
2129
2033
  #ensureMetadataTable(database) {
2130
- database.exec("CREATE TABLE IF NOT EXISTS " + quoteIdentifier(METADATA_TABLE) + " (\"id\" INTEGER, \"version\" INTEGER, \"schema\" TEXT, PRIMARY KEY (\"id\"))");
2034
+ database.execute("CREATE TABLE IF NOT EXISTS " + quoteIdentifier(METADATA_TABLE) + " (\"id\" INTEGER, \"version\" INTEGER, \"schema\" TEXT, PRIMARY KEY (\"id\"))");
2131
2035
  }
2132
2036
  #validateTable(database, schema) {
2133
2037
  const object = database.prepare("SELECT \"type\" AS \"category\" FROM \"sqlite_schema\" WHERE \"name\" = ?").get([schema.name]);
@@ -2262,7 +2166,7 @@ var SQLiteDriver = class {
2262
2166
  this.#guard(() => this.#writeMetadata(database, owned));
2263
2167
  }
2264
2168
  #writeMetadata(database, metadata) {
2265
- database.prepare("INSERT OR REPLACE INTO " + quoteIdentifier(METADATA_TABLE) + " (\"id\", \"version\", \"schema\") VALUES (1, ?, ?)").run([metadata.version, JSON.stringify(metadata.schema)]);
2169
+ database.prepare("INSERT OR REPLACE INTO " + quoteIdentifier(METADATA_TABLE) + " (\"id\", \"version\", \"schema\") VALUES (1, ?, ?)").execute([metadata.version, JSON.stringify(metadata.schema)]);
2266
2170
  }
2267
2171
  #capability(token) {
2268
2172
  return {
@@ -2321,16 +2225,16 @@ var SQLiteDriver = class {
2321
2225
  metadata: owned.metadata.schema
2322
2226
  });
2323
2227
  this.#guard(() => {
2324
- database.exec("SAVEPOINT \"_orkestrel_migration\"");
2228
+ database.execute("SAVEPOINT \"_orkestrel_migration\"");
2325
2229
  try {
2326
2230
  this.#applyPlan(database, owned);
2327
2231
  if (owned.metadata !== void 0) this.#writeMetadata(database, owned.metadata);
2328
- database.exec("RELEASE SAVEPOINT \"_orkestrel_migration\"");
2232
+ database.execute("RELEASE SAVEPOINT \"_orkestrel_migration\"");
2329
2233
  } catch (error) {
2330
2234
  try {
2331
- database.exec("ROLLBACK TO SAVEPOINT \"_orkestrel_migration\"");
2235
+ database.execute("ROLLBACK TO SAVEPOINT \"_orkestrel_migration\"");
2332
2236
  } finally {
2333
- database.exec("RELEASE SAVEPOINT \"_orkestrel_migration\"");
2237
+ database.execute("RELEASE SAVEPOINT \"_orkestrel_migration\"");
2334
2238
  }
2335
2239
  throw error;
2336
2240
  }
@@ -2406,13 +2310,13 @@ var SQLiteDriver = class {
2406
2310
  });
2407
2311
  }
2408
2312
  #applyPlan(database, input) {
2409
- for (const step of input.plan.steps) for (const sql of stepToSQL(step)) database.exec(sql);
2313
+ for (const step of input.plan.steps) for (const sql of stepToSQL(step)) database.execute(sql);
2410
2314
  }
2411
2315
  };
2412
2316
  //#endregion
2413
2317
  //#region src/server/factories.ts
2414
2318
  /**
2415
- * Create a persistent JSON-file {@link DriverInterface} for the core database layer.
2319
+ * Creates a persistent JSON-file {@link DriverInterface} for a given path.
2416
2320
  *
2417
2321
  * @remarks
2418
2322
  * Pass it to `createDatabase` from `@orkestrel/database` to run the typed
@@ -2420,9 +2324,10 @@ var SQLiteDriver = class {
2420
2324
  * `Table` / `Query` API is unchanged; only where the bytes live changes.
2421
2325
  * The driver is the reference `MemoryDriver` plus JSON-file persistence: `open` loads
2422
2326
  * the file, every mutation flushes the whole store back, and querying runs through
2423
- * the core engine over `scan` (it is scan-only no native `records` / `count` /
2424
- * `aggregate`). A missing, corrupt, or wrong-shaped file starts empty rather than
2425
- * throwing.
2327
+ * the core engine's `matchesQuery`. The driver implements the native `stream`
2328
+ * hook and neither `records` nor `aggregate`, so the engine answers every query
2329
+ * on either path. A missing, corrupt, or wrong-shaped file starts empty rather
2330
+ * than throwing.
2426
2331
  *
2427
2332
  * @param path - The JSON file path data is loaded from and flushed to
2428
2333
  * @returns A {@link DriverInterface} backed by a JSON file
@@ -2444,7 +2349,8 @@ function createJSONDriver(path) {
2444
2349
  return new JSONDriver(path);
2445
2350
  }
2446
2351
  /**
2447
- * Create a trusted-mode SQLite {@link DriverInterface} for the core database layer.
2352
+ * Creates a trusted-mode, server-native SQLite {@link DriverInterface} for a database path,
2353
+ * or for `:memory:` when the options bag omits one.
2448
2354
  *
2449
2355
  * @remarks
2450
2356
  * Pass it to `createDatabase` from `@orkestrel/database` to run the typed
@@ -2483,6 +2389,6 @@ function createSQLiteDriver(options) {
2483
2389
  return new SQLiteDriver(options);
2484
2390
  }
2485
2391
  //#endregion
2486
- export { EXACT_COLUMN_STORAGE, EXACT_RANGE_COLUMN_STORAGE, JSONDriver, METADATA_TABLE, SQLiteDriver, compileAggregateSQL, compileColumnSQL, compileConditionSQL, compileFieldSQL, compileJSONTypeSQL, compileOrder, compilePage, compileQuerySQL, compileWhere, createJSONDriver, createSQLiteDriver, decodeRow, decodeValue, deriveSQLiteIndexName, encodeRow, encodeValue, escapeLike, extractValues, findColumnStorage, inferValueStorage, matchesAbsentPath, matchesAggregateExactly, matchesConditionExactly, matchesDeclaredStorage, matchesOrderExactly, matchesQueryExactly, matchesSQLiteAffinity, quoteIdentifier, schemaToIndexes, schemaToTable, stepToSQL };
2392
+ export { EXACT_COLUMN_STORAGE, EXACT_RANGE_COLUMN_STORAGE, JSONDriver, METADATA_TABLE, SQLiteDriver, compileAggregateSQL, compileColumnSQL, compileConditionSQL, compileFieldSQL, compileJSONTypeSQL, compileOrderSQL, compilePageSQL, compileQuerySQL, compileWhereSQL, createJSONDriver, createSQLiteDriver, decodeRow, decodeValue, deriveSQLiteIndexName, encodeRow, encodeValue, extractValues, inferValueStorage, matchesAbsentPath, matchesAggregateExactly, matchesConditionExactly, matchesDeclaredStorage, matchesOrderExactly, matchesQueryExactly, matchesSQLiteAffinity, quoteIdentifier, schemaToIndexes, schemaToTable, stepToSQL };
2487
2393
 
2488
2394
  //# sourceMappingURL=index.js.map