@orkestrel/database 0.0.6 → 0.0.8
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.
- package/README.md +13 -35
- package/dist/src/browser/index.d.ts +92 -79
- package/dist/src/browser/index.js +357 -220
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +1796 -803
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +545 -706
- package/dist/src/core/index.d.ts +545 -706
- package/dist/src/core/index.js +1775 -790
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +1532 -747
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +471 -566
- package/dist/src/server/index.d.ts +471 -566
- package/dist/src/server/index.js +1515 -731
- package/dist/src/server/index.js.map +1 -1
- package/package.json +9 -10
|
@@ -1,116 +1,155 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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';
|
|
4
5
|
import { Condition } from '../core/index.ts';
|
|
5
|
-
import { Criteria } from '../core/index.ts';
|
|
6
|
-
import { Criteria as Criteria_2 } from '../../core/index.ts';
|
|
7
6
|
import { DriverInterface } from '../core/index.ts';
|
|
8
7
|
import { DriverInterface as DriverInterface_2 } from '../../core/index.ts';
|
|
9
|
-
import {
|
|
8
|
+
import { DriverMetadata } from '../../core/index.ts';
|
|
10
9
|
import { FieldPath } from '@orkestrel/contract';
|
|
11
10
|
import { Key } from '../../core/index.ts';
|
|
12
|
-
import {
|
|
11
|
+
import { MigrationInput } from '../../core/index.ts';
|
|
13
12
|
import { MigrationStep } from '../core/index.ts';
|
|
13
|
+
import { OperationOptions } from '../../core/index.ts';
|
|
14
14
|
import { Order } from '../core/index.ts';
|
|
15
|
+
import { QueryInput } from '../core/index.ts';
|
|
16
|
+
import { QueryInput as QueryInput_2 } from '../../core/index.ts';
|
|
15
17
|
import { Row } from '../core/index.ts';
|
|
16
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';
|
|
17
22
|
import { TableSchema } from '../core/index.ts';
|
|
18
23
|
import { TableSchema as TableSchema_2 } from '../../core/index.ts';
|
|
19
|
-
import { TransactionInterface } from '../../core/index.ts';
|
|
20
24
|
|
|
21
25
|
/**
|
|
22
|
-
* Compile an {@link
|
|
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.
|
|
26
|
+
* Compile an {@link AggregateOperation} over a {@link FieldPath}.
|
|
33
27
|
*
|
|
34
28
|
* @param operation - The aggregate to compute
|
|
35
|
-
* @param column - The column
|
|
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
|
|
32
|
+
export declare function compileAggregateSQL(operation: AggregateOperation, column: FieldPath): string;
|
|
46
33
|
|
|
47
34
|
/**
|
|
48
|
-
* Map a portable {@link
|
|
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
|
|
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
|
|
40
|
+
export declare function compileColumnSQL(storage: ColumnStorage): string;
|
|
67
41
|
|
|
68
42
|
/**
|
|
69
|
-
* Compile
|
|
70
|
-
*
|
|
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
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* the
|
|
78
|
-
*
|
|
79
|
-
*
|
|
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
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
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
|
-
*
|
|
94
|
-
* // { sql: '
|
|
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
|
|
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
|
-
* `
|
|
105
|
-
* consumed by the SQLite driver, which runs `sql` with `
|
|
106
|
-
* order — no further assembly. Keeping `sql` and `
|
|
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
|
|
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 ?',
|
|
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 `
|
|
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
|
|
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 `
|
|
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" >= ?',
|
|
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
|
|
188
|
-
*
|
|
189
|
-
* `Table` / `Query`
|
|
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
|
|
219
|
-
*
|
|
220
|
-
* `Query`
|
|
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
|
-
* `
|
|
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 -
|
|
229
|
-
*
|
|
230
|
-
*
|
|
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?:
|
|
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.
|
|
273
|
-
*
|
|
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
|
|
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
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
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
|
|
300
|
-
* @returns The decoded JS value
|
|
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,
|
|
305
|
-
* decodeValue('{"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,
|
|
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
|
|
410
|
+
* Encode a JS value to its stored {@link SQLiteValue} for a declared column.
|
|
331
411
|
*
|
|
332
412
|
* @remarks
|
|
333
|
-
* The
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
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
|
|
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,
|
|
348
|
-
* encodeValue({ 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,
|
|
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
|
|
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,23 +457,20 @@ 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 `
|
|
384
|
-
* `
|
|
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
|
|
463
|
+
export declare const EXACT_COLUMN_STORAGE: readonly ColumnStorage[];
|
|
390
464
|
|
|
391
465
|
/**
|
|
392
|
-
* The declared {@link
|
|
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
|
|
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
|
|
473
|
+
export declare const EXACT_RANGE_COLUMN_STORAGE: readonly ColumnStorage[];
|
|
400
474
|
|
|
401
475
|
/**
|
|
402
476
|
* Extract a stored row's values in a declared positional order.
|
|
@@ -420,239 +494,77 @@ export declare const EXACT_RANGE_COLUMN_TYPES: readonly ColumnType[];
|
|
|
420
494
|
export declare function extractValues(row: SQLiteRow, names: readonly string[], table: string): readonly SQLiteValue[];
|
|
421
495
|
|
|
422
496
|
/**
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
* @remarks
|
|
426
|
-
* A single string is ONE column — `quote(path)`. An array descends a JSON column:
|
|
427
|
-
* the first element is the (quoted) column, the rest a `json_extract` path
|
|
428
|
-
* (`json_extract("payload", '$.user.id')`), matching the guide's nested-field
|
|
429
|
-
* examples (simple identifier keys). The string's value is never split on `.`
|
|
430
|
-
* (AGENTS — `FieldPath` semantics): a dotted string is one column literally.
|
|
431
|
-
*
|
|
432
|
-
* @param path - The field path (a column, or a column + nested keys)
|
|
433
|
-
* @returns The SQL expression selecting the value
|
|
434
|
-
*
|
|
435
|
-
* @example
|
|
436
|
-
* ```ts
|
|
437
|
-
* fieldColumn('payload') // '"payload"'
|
|
438
|
-
* fieldColumn(['payload', 'user', 'id']) // 'json_extract("payload", \'$.user.id\')'
|
|
439
|
-
* ```
|
|
440
|
-
*/
|
|
441
|
-
export declare function fieldColumn(path: FieldPath): string;
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Compile one condition to its `<column> <operator>` SQL fragment and the params
|
|
445
|
-
* it binds — engine-exact under SQL's three-valued NULL logic.
|
|
446
|
-
*
|
|
447
|
-
* @remarks
|
|
448
|
-
* Every operand is run through `encodeValue`, so a bound value matches the SQL
|
|
449
|
-
* the column side compiles to. A flat column encodes operands with its DECLARED
|
|
450
|
-
* schema type (a flat `json` column → `JSON.stringify`); a nested `FieldPath`
|
|
451
|
-
* encodes each operand as the NATIVE scalar `json_extract` returns, derived from
|
|
452
|
-
* the operand's runtime type (per-operand, since `between` / `any` / `none` can
|
|
453
|
-
* mix types). `any` / `none` collapse an empty list to a constant (`0` matches
|
|
454
|
-
* nothing, `1` matches all) with no params.
|
|
455
|
-
*
|
|
456
|
-
* The core engine's total order ranks `undefined` (rank 0) BELOW `null`
|
|
457
|
-
* (rank 1) (see `compareValues`), so a MISSING/`NULL` column MATCHES
|
|
458
|
-
* `below` / `to` / a scalar `not` / `none` — the opposite of raw SQL, where a
|
|
459
|
-
* comparison against `NULL` is `NULL` (excluded). This fragment replicates the
|
|
460
|
-
* engine exactly. Truth table (`value` = the engine's decoded field read; a
|
|
461
|
-
* FLAT column's stored `NULL` decodes to `undefined` per `decodeRow`, so a
|
|
462
|
-
* flat `value` is NEVER a present `null` — only a NESTED path can be
|
|
463
|
-
* present-but-`null`):
|
|
464
|
-
*
|
|
465
|
-
* ```text
|
|
466
|
-
* operator | value=undefined (absent) | value=null (nested only) | value=scalar
|
|
467
|
-
* --------------------|--------------------------------|---------------------------|-------------
|
|
468
|
-
* equals, first=null | no match | MATCH | no match
|
|
469
|
-
* equals, first=X | no match | no match | value===X
|
|
470
|
-
* not, first=null | MATCH (flat: unconditionally; | no match | MATCH
|
|
471
|
-
* | nested: absent still matches)| |
|
|
472
|
-
* not, first=X | MATCH | MATCH | value!==X
|
|
473
|
-
* below/to, first=X | MATCH (rank 0 < rank(X)) | MATCH (rank 1 < rank(X)) | rank compare
|
|
474
|
-
* none, list=[…] | MATCH (no scalar rank-equal) | MATCH | not-in-list
|
|
475
|
-
* any, list=[…] | no match | no match | in-list
|
|
476
|
-
* above/from/between | no match | no match | rank compare
|
|
477
|
-
* like/glob/starts/… | no match (not a string) | no match | string test
|
|
478
|
-
* present | false | false | true
|
|
479
|
-
* absent | true | true | false
|
|
480
|
-
* ```
|
|
481
|
-
*
|
|
482
|
-
* Because a flat column's `NULL` always decodes to `undefined`, `equals`
|
|
483
|
-
* against a `null` operand needs no special flat compilation (`col = ?`
|
|
484
|
-
* binding a `NULL` param is already always-false in SQL, matching "no match"
|
|
485
|
-
* above) — but flat `not` against `null` must match EVERY row (both the
|
|
486
|
-
* absent and the scalar rows), which `col != ? OR col IS NULL` cannot express
|
|
487
|
-
* (it only catches the `IS NULL` row), so a flat `not`-with-`null`-operand
|
|
488
|
-
* compiles to the constant `1`.
|
|
489
|
-
*
|
|
490
|
-
* A NESTED path can be present-but-`null` (a stored JSON `null`), which
|
|
491
|
-
* `json_extract` reads back as SQL `NULL` — indistinguishable from an ABSENT
|
|
492
|
-
* path. `json_type(col, path)` disambiguates them (`'null'` for present-null,
|
|
493
|
-
* SQL `NULL` for absent), so nested `equals` / `not` against a `null` operand
|
|
494
|
-
* compile through `json_type` instead of `IS NULL` / `IS NOT NULL`.
|
|
495
|
-
*
|
|
496
|
-
* Every other MATCH-on-null-or-absent row is expressed uniformly (flat and
|
|
497
|
-
* nested alike) as `(<column> <op> ? OR <column> IS NULL)` — for a nested
|
|
498
|
-
* path, `json_extract` already collapses BOTH absent and present-null to SQL
|
|
499
|
-
* `NULL`, so `IS NULL` catches both in one clause; for a flat column there is
|
|
500
|
-
* only the absent case to catch.
|
|
501
|
-
*
|
|
502
|
-
* @param condition - The condition to compile
|
|
503
|
-
* @param schema - The table's schema (for declared column types)
|
|
504
|
-
* @returns The SQL fragment and its bound parameters
|
|
505
|
-
*
|
|
506
|
-
* @example
|
|
507
|
-
* ```ts
|
|
508
|
-
* fragment({ column: 'age', operator: 'above', values: [18], connector: 'and' }, schema)
|
|
509
|
-
* // { sql: '"age" > ?', params: [18] }
|
|
510
|
-
* fragment({ column: 'age', operator: 'below', values: [18], connector: 'and' }, schema)
|
|
511
|
-
* // { sql: '("age" < ? OR "age" IS NULL)', params: [18] }
|
|
512
|
-
* ```
|
|
513
|
-
*/
|
|
514
|
-
export declare function fragment(condition: Condition, schema: TableSchema): CompiledSQL;
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Generate a fresh unique key — a v4 UUID string, backed by `node:crypto`.
|
|
518
|
-
*
|
|
519
|
-
* @remarks
|
|
520
|
-
* Supply this as {@link import('@orkestrel/database').DatabaseOptions.key} so a table mints
|
|
521
|
-
* a key when a written row lacks its primary-key value. Strings work as keys on
|
|
522
|
-
* 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.
|
|
523
498
|
*
|
|
524
|
-
* @
|
|
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
|
|
525
502
|
*
|
|
526
503
|
* @example
|
|
527
504
|
* ```ts
|
|
528
|
-
*
|
|
505
|
+
* findColumnStorage('age', schema) // 'integer'
|
|
529
506
|
* ```
|
|
530
507
|
*/
|
|
531
|
-
export declare function
|
|
508
|
+
export declare function findColumnStorage(column: string, schema: TableSchema): ColumnStorage | undefined;
|
|
532
509
|
|
|
533
510
|
/**
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
* {@link stepToSQL}'s `index.add` / `index.remove` (a migration-time DDL),
|
|
537
|
-
* 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`.
|
|
538
513
|
*
|
|
539
514
|
* @remarks
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
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).
|
|
545
521
|
*
|
|
546
|
-
* @param
|
|
547
|
-
* @
|
|
548
|
-
* @returns The deterministic, collision-free index identifier (unquoted)
|
|
522
|
+
* @param value - The runtime operand value
|
|
523
|
+
* @returns The {@link ColumnStorage} to encode it as
|
|
549
524
|
*
|
|
550
525
|
* @example
|
|
551
526
|
* ```ts
|
|
552
|
-
*
|
|
553
|
-
*
|
|
554
|
-
* indexName('a', ['b', 'c']) // 'idx_1_a_1_b_1_c'
|
|
527
|
+
* inferValueStorage(true) // 'boolean'
|
|
528
|
+
* inferValueStorage(9) // 'integer'
|
|
555
529
|
* ```
|
|
556
530
|
*/
|
|
557
|
-
export declare function
|
|
558
|
-
|
|
559
|
-
/**
|
|
560
|
-
* Whether one {@link Condition} compiles to SQL that is PROVABLY identical to
|
|
561
|
-
* the core engine's `matchesCondition` for every value its column's declared
|
|
562
|
-
* type can store.
|
|
563
|
-
*
|
|
564
|
-
* @remarks
|
|
565
|
-
* `false` for a nested `FieldPath` (an array), a column absent from `schema`,
|
|
566
|
-
* or a column whose declared type is not `text` / `integer` / `real` /
|
|
567
|
-
* `boolean` (a `json` / `blob` column) — EXCEPT `absent` / `present`, which
|
|
568
|
-
* compile to `IS NULL` / `IS NOT NULL` and match `decodeRow`'s "a stored NULL
|
|
569
|
-
* decodes to `undefined`" rule for every column type, so they are exact
|
|
570
|
-
* regardless of declared type. `equals` / `not` require a operand matching the
|
|
571
|
-
* column's declared type (a `null` / `undefined` operand is never exact here —
|
|
572
|
-
* `encodeRow` stores both an explicit `null` and an absent field as SQL NULL,
|
|
573
|
-
* so native `IS NULL` semantics cannot match the engine's `deepEqual`-over-
|
|
574
|
-
* decoded-rows truth). `above` / `below` / `from` / `to` / `between` are exact
|
|
575
|
-
* ONLY for a declared type in {@link EXACT_RANGE_COLUMN_TYPES} (`integer` /
|
|
576
|
-
* `real` / `boolean`) — a `text` column's range conditions REFINE, because
|
|
577
|
-
* SQLite's default BINARY collation orders TEXT by Unicode CODE POINT while
|
|
578
|
-
* the core engine's `compareValues` orders JS strings by UTF-16 CODE UNIT,
|
|
579
|
-
* and the two diverge for supplementary-plane characters (see
|
|
580
|
-
* {@link EXACT_COLUMN_TYPES}'s remarks for the full rationale).
|
|
581
|
-
* `any` / `none` require a NON-EMPTY list where every element matches (an empty
|
|
582
|
-
* list is exact under neither: the engine's `any([])` matches nothing while
|
|
583
|
-
* `none([])` matches everything, and SQL `IN ()` is a syntax error) — these
|
|
584
|
-
* stay exact on `text` (byte equality is collation-independent and engine-
|
|
585
|
-
* identical). `starts` / `ends` are exact only on a `text` column with a
|
|
586
|
-
* string operand (case-sensitive `substr` compile, see {@link fragment}) —
|
|
587
|
-
* likewise collation-independent. `like` / `glob` are NEVER exact — SQLite
|
|
588
|
-
* `LIKE` folds case ASCII-only against the engine's Unicode fold, and `GLOB`
|
|
589
|
-
* has character classes the engine treats literally.
|
|
590
|
-
*
|
|
591
|
-
* @param condition - The condition to test
|
|
592
|
-
* @param schema - The table's schema
|
|
593
|
-
* @returns Whether `condition` is exact
|
|
594
|
-
*/
|
|
595
|
-
export declare function isExactCondition(condition: Condition, schema: TableSchema): boolean;
|
|
596
|
-
|
|
597
|
-
/**
|
|
598
|
-
* Whether a whole {@link Criteria} is exact — every condition and every order
|
|
599
|
-
* term is exact. `limit` / `offset` never affect exactness (SQL `LIMIT` /
|
|
600
|
-
* `OFFSET` are always engine-identical).
|
|
601
|
-
*
|
|
602
|
-
* @param criteria - The criteria to test
|
|
603
|
-
* @param schema - The table's schema
|
|
604
|
-
* @returns Whether every part of `criteria` is exact
|
|
605
|
-
*/
|
|
606
|
-
export declare function isExactCriteria(criteria: Criteria, schema: TableSchema): boolean;
|
|
607
|
-
|
|
608
|
-
/**
|
|
609
|
-
* Whether one {@link Order} term's column compiles to an `ORDER BY` that
|
|
610
|
-
* matches the engine's {@link import('@src/core').sortRows} exactly.
|
|
611
|
-
*
|
|
612
|
-
* @remarks
|
|
613
|
-
* `false` for a nested `FieldPath`, a column absent from `schema`, or a
|
|
614
|
-
* declared type outside {@link EXACT_RANGE_COLUMN_TYPES} (`integer` / `real` /
|
|
615
|
-
* `boolean`). `text` is NOT exact here: SQLite's default BINARY collation
|
|
616
|
-
* orders TEXT by Unicode code point while the core engine's `compareValues`
|
|
617
|
-
* orders JS strings by UTF-16 code unit, and the two diverge for
|
|
618
|
-
* supplementary-plane characters (see {@link EXACT_COLUMN_TYPES}'s remarks) —
|
|
619
|
-
* a `text` order term REFINES through the core engine instead.
|
|
620
|
-
*
|
|
621
|
-
* @param order - The order term to test
|
|
622
|
-
* @param schema - The table's schema
|
|
623
|
-
* @returns Whether `order` is exact
|
|
624
|
-
*/
|
|
625
|
-
export declare function isExactOrder(order: Order, schema: TableSchema): boolean;
|
|
531
|
+
export declare function inferValueStorage(value: unknown): ColumnStorage;
|
|
626
532
|
|
|
627
533
|
/**
|
|
628
534
|
* A persistent {@link DriverInterface} backed by a single JSON file — the
|
|
629
535
|
* reference {@link MemoryDriver} plus file load / flush.
|
|
630
536
|
*
|
|
631
537
|
* @remarks
|
|
632
|
-
* A decorator, not a reimplementation:
|
|
633
|
-
* {@link MemoryDriver},
|
|
634
|
-
*
|
|
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`
|
|
635
541
|
* loads the file into memory; every mutation (`write` / `delete` / `clear`) flushes
|
|
636
|
-
* the whole store back. The file is one JSON object, `{
|
|
637
|
-
* [name]: rows } }` — `
|
|
638
|
-
* (an unstamped store
|
|
639
|
-
* 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
|
|
640
545
|
* primary (the table contract), so the key is recovered on load with
|
|
641
546
|
* {@link extractKey} and the file need not store it. The parsed JSON crosses the
|
|
642
547
|
* boundary as `unknown` and is narrowed with {@link isRecord} / {@link extractKey},
|
|
643
|
-
* never asserted (AGENTS §14)
|
|
644
|
-
*
|
|
645
|
-
*
|
|
646
|
-
*
|
|
548
|
+
* never asserted (AGENTS §14). A read that reports no document there starts empty —
|
|
549
|
+
* `ENOENT` for a plain absence, and `ENOTDIR` for a path whose parent is not a
|
|
550
|
+
* directory, which no later write could find either; every other read failure or
|
|
551
|
+
* invalid existing document fails closed without publication, mutation, or
|
|
552
|
+
* automatic repair. It is scan-only — it implements none of
|
|
553
|
+
* the optional native `records` / `aggregate` hooks, so the core engine
|
|
647
554
|
* over `scan` answers every query. For development, small datasets, and portable /
|
|
648
555
|
* inspectable data; for large or concurrent workloads reach for a SQLite-backed
|
|
649
556
|
* driver.
|
|
650
557
|
*
|
|
651
|
-
*
|
|
652
|
-
*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
*
|
|
558
|
+
* Metadata crosses {@link cloneDriverMetadata} at parsed-file ingress, public and
|
|
559
|
+
* scoped write ingress, candidate/root publication, serialization, and copy-out.
|
|
560
|
+
* Callers therefore cannot mutate queued metadata, and `metadata()` always returns a
|
|
561
|
+
* distinct deeply frozen snapshot. A failure in the write path ({@link
|
|
562
|
+
* JSONDriver.#serialize} — `mkdir` / `writeFile` / `rename`) is wrapped and
|
|
563
|
+
* rethrown as `DatabaseError` `DRIVER`, carrying the target `path` and native
|
|
564
|
+
* `cause` in its context. If temporary-file cleanup also fails, the top-level
|
|
565
|
+
* `DRIVER` context additionally carries `temp` and `cleanup`; a precommit abort
|
|
566
|
+
* remains an `ABORTED` `DatabaseError` in `context.cause`. The fail-closed read path
|
|
567
|
+
* ({@link JSONDriver.#document}) remains separate from this write-error contract.
|
|
656
568
|
*/
|
|
657
569
|
export declare class JSONDriver implements DriverInterface_2 {
|
|
658
570
|
#private;
|
|
@@ -660,92 +572,153 @@ export declare class JSONDriver implements DriverInterface_2 {
|
|
|
660
572
|
open(schema: readonly TableSchema_2[]): Promise<void>;
|
|
661
573
|
close(): Promise<void>;
|
|
662
574
|
read(table: string, key: Key): Promise<Row_2 | undefined>;
|
|
663
|
-
write(table: string, key: Key, row: Row_2): Promise<void>;
|
|
664
|
-
|
|
575
|
+
write(table: string, key: Key, row: Row_2, options?: OperationOptions): Promise<void>;
|
|
576
|
+
insert(table: string, key: Key, row: Row_2, options?: OperationOptions): Promise<void>;
|
|
577
|
+
delete(table: string, key: Key, options?: OperationOptions): Promise<boolean>;
|
|
665
578
|
keys(table: string): Promise<readonly Key[]>;
|
|
666
579
|
scan(table: string): AsyncIterable<Row_2>;
|
|
667
580
|
/**
|
|
668
581
|
* Natively filtered lazy iteration — delegates to the inner {@link MemoryDriver}.
|
|
669
582
|
*
|
|
670
583
|
* @remarks
|
|
671
|
-
* Semantics are the memory driver's own: `
|
|
672
|
-
* / `limit` page lazily, and `
|
|
584
|
+
* Semantics are the memory driver's own: `input.conditions` filters, `offset`
|
|
585
|
+
* / `limit` page lazily, and `input.order` is ignored (streaming yields key
|
|
673
586
|
* order; sorted output is `records()`'s job).
|
|
674
587
|
*
|
|
675
588
|
* @param table - The table to stream
|
|
676
|
-
* @param
|
|
589
|
+
* @param input - The filter / offset / limit to apply lazily
|
|
677
590
|
*/
|
|
678
|
-
stream(table: string,
|
|
591
|
+
stream(table: string, input: QueryInput_2): AsyncIterable<Row_2>;
|
|
679
592
|
clear(table: string): Promise<void>;
|
|
680
593
|
/**
|
|
681
|
-
*
|
|
594
|
+
* Run an isolated native transaction callback over a candidate memory store.
|
|
682
595
|
*
|
|
683
596
|
* @remarks
|
|
684
|
-
* Single-writer:
|
|
685
|
-
*
|
|
686
|
-
*
|
|
687
|
-
*
|
|
688
|
-
*
|
|
689
|
-
*
|
|
690
|
-
* `#flush()`, persisting the transaction's net state. `rollback()` restores
|
|
691
|
-
* memory via the captured snapshot thunk, then `#flush()`s so the file reflects
|
|
692
|
-
* the restored state. Outside a transaction, behavior is unchanged — every
|
|
693
|
-
* mutation flushes on its own. Calling `commit` / `rollback` a second time (on
|
|
694
|
-
* either method, in either order) throws `DatabaseError` `CONFLICT`.
|
|
597
|
+
* Single-writer: nesting and root operations while active throw `CONFLICT`.
|
|
598
|
+
* The callback receives a capability over cloned rows, schema, and metadata.
|
|
599
|
+
* Fulfillment atomically serializes that candidate and publishes it to root
|
|
600
|
+
* memory only after file replacement succeeds. Rejection or persistence
|
|
601
|
+
* failure discards the candidate, and every captured capability call after
|
|
602
|
+
* settlement throws `CONFLICT`.
|
|
695
603
|
*
|
|
696
|
-
* @returns
|
|
604
|
+
* @returns The callback's resolved value
|
|
605
|
+
*/
|
|
606
|
+
transaction<R>(scope: (storage: StorageInterface) => Promise<R>): Promise<R>;
|
|
607
|
+
/**
|
|
608
|
+
* Capture an owned row snapshot at an exact writer-queue position.
|
|
609
|
+
*
|
|
610
|
+
* @remarks
|
|
611
|
+
* Capture owns table names, schemas, rows, and one session-local identity per
|
|
612
|
+
* table. Rollback is repeatable: it clones the then-current root into a
|
|
613
|
+
* candidate, adapts captured rows to each surviving same-identity table
|
|
614
|
+
* through the portable migration engine, persists the candidate with current
|
|
615
|
+
* metadata, and publishes memory only after file replacement succeeds.
|
|
616
|
+
* Removed, replaced, uncaptured, and later-added tables remain untouched.
|
|
617
|
+
*
|
|
618
|
+
* @param tables - Existing tables to capture; omitted captures every current table
|
|
619
|
+
* @returns A repeatable rollback operation
|
|
697
620
|
*/
|
|
698
|
-
transaction(): Promise<TransactionInterface>;
|
|
699
621
|
snapshot(tables?: readonly string[]): Promise<() => Promise<void>>;
|
|
700
|
-
|
|
622
|
+
metadata(): Promise<DriverMetadata | undefined>;
|
|
701
623
|
/**
|
|
702
|
-
* Persist
|
|
624
|
+
* Persist an owned metadata snapshot for a later `metadata()` to copy out.
|
|
703
625
|
*
|
|
704
626
|
* @remarks
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
627
|
+
* Root stamping conflicts while a transaction is active. The scoped
|
|
628
|
+
* {@link StorageInterface.stamp} updates candidate metadata and publishes
|
|
629
|
+
* with the candidate rows on callback fulfillment.
|
|
708
630
|
*
|
|
709
|
-
* @param
|
|
631
|
+
* @param metadata - The {@link DriverMetadata} to persist
|
|
710
632
|
*/
|
|
711
|
-
stamp(
|
|
633
|
+
stamp(metadata: DriverMetadata): Promise<void>;
|
|
712
634
|
/**
|
|
713
|
-
* Apply
|
|
714
|
-
* then persist the migrated state.
|
|
635
|
+
* Apply one atomic {@link MigrationInput} through an isolated candidate.
|
|
715
636
|
*
|
|
716
637
|
* @remarks
|
|
717
|
-
* The
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
* this driver's own declared `#schema`, mirroring the bookkeeping `open` does,
|
|
722
|
-
* so a subsequent `#flush` / `#load` round-trip includes (or drops) the table.
|
|
723
|
-
* A successful migration ends with one atomic `#flush()` so the new state
|
|
724
|
-
* survives a close and reopen. A multi-step plan applies its steps
|
|
725
|
-
* sequentially and is NOT atomic — a failure partway through a plan leaves
|
|
726
|
-
* the earlier steps already applied.
|
|
638
|
+
* The candidate receives every plan step plus optional metadata. Its complete
|
|
639
|
+
* rows, derived schema, and metadata serialize through one atomic file
|
|
640
|
+
* replacement before root memory changes. Any migration or persistence failure
|
|
641
|
+
* therefore leaves root state and the prior file exact.
|
|
727
642
|
*
|
|
728
|
-
* @param
|
|
643
|
+
* @param input - The plan and optional metadata to settle together
|
|
729
644
|
*/
|
|
730
|
-
migrate(
|
|
645
|
+
migrate(input: MigrationInput): Promise<void>;
|
|
731
646
|
}
|
|
732
647
|
|
|
733
648
|
/**
|
|
734
|
-
*
|
|
735
|
-
* expression — the {@link fieldColumn} `json_extract` sibling used to tell a
|
|
736
|
-
* PRESENT JSON `null` apart from an ABSENT path (both read back as SQL `NULL`
|
|
737
|
-
* through `json_extract`, but `json_type` reports `'null'` for the former and
|
|
738
|
-
* SQL `NULL` for the latter).
|
|
649
|
+
* Whether a caught filesystem error reports that nothing is there to read.
|
|
739
650
|
*
|
|
740
|
-
* @
|
|
741
|
-
*
|
|
651
|
+
* @remarks
|
|
652
|
+
* Two codes carry that meaning: `ENOENT` is a plain absence, and `ENOTDIR` is a
|
|
653
|
+
* path whose parent is not a directory — an absence in the stronger sense, since
|
|
654
|
+
* no file can exist at that name and no later write could find one. Hosts
|
|
655
|
+
* disagree about which of the two they report for the second shape, so a driver
|
|
656
|
+
* that reads only `ENOENT` opens on one host and fails closed on another over
|
|
657
|
+
* the same tree.
|
|
658
|
+
*
|
|
659
|
+
* Every other code — a permission refusal, a symlink loop, an unreadable
|
|
660
|
+
* existing file — is a real failure and stays one.
|
|
661
|
+
*
|
|
662
|
+
* A caught value that is not a coded `Error` is not a report about a path, so it
|
|
663
|
+
* answers `false` rather than being read for a `code` any object could carry.
|
|
664
|
+
*
|
|
665
|
+
* @param error - The caught value to classify; any runtime is accepted
|
|
666
|
+
* @returns `true` when the error reports that the path holds nothing
|
|
742
667
|
*
|
|
743
668
|
* @example
|
|
744
669
|
* ```ts
|
|
745
|
-
*
|
|
670
|
+
* matchesAbsentPath(Object.assign(new Error('gone'), { code: 'ENOENT' })) // true
|
|
671
|
+
* matchesAbsentPath(Object.assign(new Error('denied'), { code: 'EACCES' })) // false
|
|
672
|
+
* matchesAbsentPath('ENOENT') // false
|
|
746
673
|
* ```
|
|
747
674
|
*/
|
|
748
|
-
export declare function
|
|
675
|
+
export declare function matchesAbsentPath(error: unknown): boolean;
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Determine whether SQLite can execute an aggregate exactly like the core engine.
|
|
679
|
+
*
|
|
680
|
+
* @param operation - Aggregate operation
|
|
681
|
+
* @param column - Aggregate field
|
|
682
|
+
* @param schema - Current table schema
|
|
683
|
+
* @returns Whether native aggregation is exact
|
|
684
|
+
*/
|
|
685
|
+
export declare function matchesAggregateExactly(operation: AggregateOperation, column: FieldPath, schema: TableSchema): boolean;
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Whether one {@link Condition} compiles to SQL that is PROVABLY identical to
|
|
689
|
+
* the core engine's `matchesCondition` for every value its column's declared
|
|
690
|
+
* type can store.
|
|
691
|
+
*
|
|
692
|
+
* @remarks
|
|
693
|
+
* `false` for a nested `FieldPath` (an array) or a column absent from `schema`.
|
|
694
|
+
* `absent` / `present` are exact unless a column is both optional and nullable.
|
|
695
|
+
* In that combined case the storage sentinel for explicit `null` is not SQL
|
|
696
|
+
* `NULL`, while the core treats both absence and explicit `null` as absent.
|
|
697
|
+
* Every scalar operator refines when the column is optional
|
|
698
|
+
* OR nullable, because SQL null semantics and the core total order differ.
|
|
699
|
+
* Required non-null `equals` / `not` require an operand matching the declared
|
|
700
|
+
* storage and exclude `json` / `blob`. `above` / `below` / `from` / `to` /
|
|
701
|
+
* `between` are exact only for {@link EXACT_RANGE_COLUMN_STORAGE} (`integer` /
|
|
702
|
+
* `real` / `boolean`) — a `text` column's range conditions REFINE, because
|
|
703
|
+
* SQLite's default BINARY collation orders TEXT by Unicode CODE POINT while
|
|
704
|
+
* the core engine's `compareValues` orders JS strings by UTF-16 CODE UNIT,
|
|
705
|
+
* and the two diverge for supplementary-plane characters (see
|
|
706
|
+
* {@link EXACT_COLUMN_STORAGE}'s remarks for the full rationale).
|
|
707
|
+
* `any` / `none` require a NON-EMPTY list where every element matches (an empty
|
|
708
|
+
* list is exact under neither: the engine's `any([])` matches nothing while
|
|
709
|
+
* `none([])` matches everything, and SQL `IN ()` is a syntax error) — these
|
|
710
|
+
* stay exact on `text` (byte equality is collation-independent and engine-
|
|
711
|
+
* identical). `starts` / `ends` are exact only on a `text` column with a
|
|
712
|
+
* string operand (case-sensitive `substr` compile, see {@link compileConditionSQL}) —
|
|
713
|
+
* likewise collation-independent. `like` / `glob` are NEVER exact — SQLite
|
|
714
|
+
* `LIKE` folds case ASCII-only against the engine's Unicode fold, and `GLOB`
|
|
715
|
+
* has character classes the engine treats literally.
|
|
716
|
+
*
|
|
717
|
+
* @param condition - The condition to test
|
|
718
|
+
* @param schema - The table's schema
|
|
719
|
+
* @returns Whether `condition` is exact
|
|
720
|
+
*/
|
|
721
|
+
export declare function matchesConditionExactly(condition: Condition, schema: TableSchema): boolean;
|
|
749
722
|
|
|
750
723
|
/**
|
|
751
724
|
* Whether a value's runtime type matches a column's declared exact type —
|
|
@@ -753,7 +726,7 @@ export declare function jsonTypeColumn(path: readonly string[]): string;
|
|
|
753
726
|
*
|
|
754
727
|
* @remarks
|
|
755
728
|
* `text` ↔ string, `integer` / `real` ↔ FINITE number (`NaN` / `±Infinity`
|
|
756
|
-
* fail), `boolean` ↔ boolean. Backs {@link
|
|
729
|
+
* fail), `boolean` ↔ boolean. Backs {@link matchesConditionExactly}'s operand checks.
|
|
757
730
|
*
|
|
758
731
|
* @param value - The condition operand to test
|
|
759
732
|
* @param type - The column's declared portable type
|
|
@@ -765,18 +738,57 @@ export declare function jsonTypeColumn(path: readonly string[]): string;
|
|
|
765
738
|
* matchesDeclaredType(Number.NaN, 'integer') // false — only finite numbers
|
|
766
739
|
* ```
|
|
767
740
|
*/
|
|
768
|
-
export declare function
|
|
741
|
+
export declare function matchesDeclaredStorage(value: unknown, storage: ColumnStorage): boolean;
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Whether one {@link Order} term's column compiles to an `ORDER BY` that
|
|
745
|
+
* matches the engine's {@link import('@src/core').sortRows} exactly.
|
|
746
|
+
*
|
|
747
|
+
* @remarks
|
|
748
|
+
* `false` for a nested `FieldPath`, a column absent from `schema`, or a
|
|
749
|
+
* declared type outside {@link EXACT_RANGE_COLUMN_STORAGE} (`integer` / `real` /
|
|
750
|
+
* `boolean`). `text` is NOT exact here: SQLite's default BINARY collation
|
|
751
|
+
* orders TEXT by Unicode code point while the core engine's `compareValues`
|
|
752
|
+
* orders JS strings by UTF-16 code unit, and the two diverge for
|
|
753
|
+
* supplementary-plane characters (see {@link EXACT_COLUMN_STORAGE}'s remarks) —
|
|
754
|
+
* a `text` order term REFINES through the core engine instead.
|
|
755
|
+
*
|
|
756
|
+
* @param order - The order term to test
|
|
757
|
+
* @param schema - The table's schema
|
|
758
|
+
* @returns Whether `order` is exact
|
|
759
|
+
*/
|
|
760
|
+
export declare function matchesOrderExactly(order: Order, schema: TableSchema): boolean;
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Whether a whole {@link QueryInput} is exact — every condition and every order
|
|
764
|
+
* term is exact. `limit` / `offset` never affect exactness (SQL `LIMIT` /
|
|
765
|
+
* `OFFSET` are always engine-identical).
|
|
766
|
+
*
|
|
767
|
+
* @param input - The query input to test
|
|
768
|
+
* @param schema - The table's schema
|
|
769
|
+
* @returns Whether every part of `input` is exact
|
|
770
|
+
*/
|
|
771
|
+
export declare function matchesQueryExactly(input: QueryInput, schema: TableSchema): boolean;
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Test a declared SQLite type against a portable storage affinity.
|
|
775
|
+
*
|
|
776
|
+
* @param declared - Native declared type
|
|
777
|
+
* @param storage - Portable column storage
|
|
778
|
+
* @returns Whether SQLite's official affinity rules yield the expected affinity
|
|
779
|
+
*/
|
|
780
|
+
export declare function matchesSQLiteAffinity(declared: unknown, storage: ColumnStorage): boolean;
|
|
769
781
|
|
|
770
782
|
/**
|
|
771
783
|
* The reserved metadata table the {@link SQLiteDriver} creates on `open` to
|
|
772
|
-
* persist its stamped `
|
|
773
|
-
* SQLite realization of the `
|
|
784
|
+
* persist its stamped `DriverMetadata` (`version` + declared schema JSON) — the
|
|
785
|
+
* SQLite realization of the `metadata` / `stamp` driver hooks.
|
|
774
786
|
*
|
|
775
787
|
* @remarks
|
|
776
|
-
* A single-row table (`id = 1`). A user table named `
|
|
788
|
+
* A single-row table (`id = 1`). A user table named `_metadata` collides with the
|
|
777
789
|
* reservation — the caller's concern to avoid, documented on the driver class.
|
|
778
790
|
*/
|
|
779
|
-
export declare const
|
|
791
|
+
export declare const METADATA_TABLE = "_metadata";
|
|
780
792
|
|
|
781
793
|
/**
|
|
782
794
|
* Quote a SQL identifier (a table or column name) so any characters are literal.
|
|
@@ -791,48 +803,24 @@ export declare const META_TABLE = "_meta";
|
|
|
791
803
|
*
|
|
792
804
|
* @example
|
|
793
805
|
* ```ts
|
|
794
|
-
*
|
|
806
|
+
* quoteIdentifier('order') // '"order"'
|
|
795
807
|
* ```
|
|
796
808
|
*/
|
|
797
|
-
export declare function
|
|
809
|
+
export declare function quoteIdentifier(identifier: string): string;
|
|
798
810
|
|
|
799
811
|
/**
|
|
800
|
-
* Project a {@link TableSchema} to
|
|
801
|
-
* SQLite driver's `open` issues for its declared indexes.
|
|
812
|
+
* Project a {@link TableSchema} to its declared SQLite indexes.
|
|
802
813
|
*
|
|
803
|
-
* @
|
|
804
|
-
* One statement per
|
|
805
|
-
* (collision-free and deterministic), matching the driver's naming so a
|
|
806
|
-
* repeated `open` is idempotent.
|
|
807
|
-
*
|
|
808
|
-
* @param schema - The table's schema
|
|
809
|
-
* @returns One `CREATE INDEX IF NOT EXISTS …` statement per declared index
|
|
810
|
-
*
|
|
811
|
-
* @example
|
|
812
|
-
* ```ts
|
|
813
|
-
* schemaToIndexes(schema)
|
|
814
|
-
* // ['CREATE INDEX IF NOT EXISTS "idx_5_users_4_name" ON "users" ("name")']
|
|
815
|
-
* ```
|
|
814
|
+
* @param schema - The table schema
|
|
815
|
+
* @returns One statement per declared index
|
|
816
816
|
*/
|
|
817
817
|
export declare function schemaToIndexes(schema: TableSchema): readonly string[];
|
|
818
818
|
|
|
819
819
|
/**
|
|
820
|
-
* Project a {@link TableSchema} to
|
|
821
|
-
* SQLite driver's `open` issues for it.
|
|
822
|
-
*
|
|
823
|
-
* @remarks
|
|
824
|
-
* Each column compiles to `<quoted name> <columnSQL(type)>`; the statement ends
|
|
825
|
-
* with `PRIMARY KEY (<quoted primary>)`. No `NOT NULL` is emitted — the contract
|
|
826
|
-
* validates required-ness, the database is just storage (AGENTS §14).
|
|
820
|
+
* Project a {@link TableSchema} to its `CREATE TABLE IF NOT EXISTS` statement.
|
|
827
821
|
*
|
|
828
|
-
* @param schema - The table
|
|
829
|
-
* @returns The
|
|
830
|
-
*
|
|
831
|
-
* @example
|
|
832
|
-
* ```ts
|
|
833
|
-
* schemaToTable(schema)
|
|
834
|
-
* // 'CREATE TABLE IF NOT EXISTS "users" ("id" TEXT, "age" INTEGER, PRIMARY KEY ("id"))'
|
|
835
|
-
* ```
|
|
822
|
+
* @param schema - The table schema
|
|
823
|
+
* @returns The complete table declaration
|
|
836
824
|
*/
|
|
837
825
|
export declare function schemaToTable(schema: TableSchema): string;
|
|
838
826
|
|
|
@@ -846,21 +834,23 @@ export declare function schemaToTable(schema: TableSchema): string;
|
|
|
846
834
|
* raw `node:sqlite`. `open` issues `CREATE TABLE IF NOT EXISTS` with real typed
|
|
847
835
|
* columns (mapped from each {@link TableSchema}'s portable column types) and a
|
|
848
836
|
* `PRIMARY KEY`, plus a `CREATE INDEX IF NOT EXISTS` per declared index (both
|
|
849
|
-
* reopen-safe), and readies a reserved `
|
|
850
|
-
* `stamp()` read and write — **a user table named `
|
|
837
|
+
* reopen-safe), and readies a reserved `_metadata` single-row table `metadata()` /
|
|
838
|
+
* `stamp()` read and write — **a user table named `_metadata` collides with it**;
|
|
851
839
|
* avoid the name. Rows cross the boundary through the codecs in `helpers.ts`
|
|
852
840
|
* (`json` columns store / parse JSON text, a `boolean` stores `1` / `0`), so the
|
|
853
841
|
* typed layer above imposes the exact shape (AGENTS §14). `write` is an
|
|
854
|
-
* `INSERT OR REPLACE` upsert
|
|
855
|
-
*
|
|
856
|
-
* `SQLiteError`
|
|
857
|
-
*
|
|
858
|
-
*
|
|
859
|
-
* `
|
|
860
|
-
*
|
|
861
|
-
*
|
|
862
|
-
*
|
|
863
|
-
*
|
|
842
|
+
* `INSERT OR REPLACE` upsert, while `insert` uses a plain `INSERT` and maps its
|
|
843
|
+
* atomic primary-key constraint failure to `CONFLICT`; every other backend
|
|
844
|
+
* `SQLiteError` is contained by the same `DatabaseError` boundary described
|
|
845
|
+
* below. Querying, ordering, paging, and
|
|
846
|
+
* aggregation is native: `records` / `stream` compile a `QueryInput`
|
|
847
|
+
* to SQL with `compileQuerySQL`, and `aggregate` runs a SQL
|
|
848
|
+
* `COUNT`/`SUM`/`AVG`/`MIN`/`MAX` (via `compileAggregateSQL`) over the same compiled
|
|
849
|
+
* WHERE. `transaction` runs a callback inside native `BEGIN` / `COMMIT` /
|
|
850
|
+
* `ROLLBACK`, passing a scoped storage capability that becomes invalid after
|
|
851
|
+
* settlement. `migrate` runs the plan's projected DDL
|
|
852
|
+
* ({@link import('../compilers.js').stepToSQL}) inside whichever native
|
|
853
|
+
* transaction is active: joined into the active transaction callback
|
|
864
854
|
* when one exists (the core's versioned reconcile path wraps migrate + stamp
|
|
865
855
|
* in one native `BEGIN`, and node:sqlite rejects a nested `BEGIN`), or inside
|
|
866
856
|
* its own `database.transaction` otherwise — a mid-plan failure rolls back
|
|
@@ -885,67 +875,72 @@ export declare function schemaToTable(schema: TableSchema): string;
|
|
|
885
875
|
*/
|
|
886
876
|
export declare class SQLiteDriver implements DriverInterface_2 {
|
|
887
877
|
#private;
|
|
888
|
-
constructor(
|
|
878
|
+
constructor(options?: SQLiteDriverOptions);
|
|
889
879
|
open(schema: readonly TableSchema_2[]): Promise<void>;
|
|
890
880
|
close(): Promise<void>;
|
|
891
881
|
read(table: string, key: Key): Promise<Row_2 | undefined>;
|
|
892
|
-
write(table: string, key: Key, row: Row_2): Promise<void>;
|
|
893
|
-
|
|
882
|
+
write(table: string, key: Key, row: Row_2, options?: OperationOptions): Promise<void>;
|
|
883
|
+
insert(table: string, key: Key, row: Row_2, options?: OperationOptions): Promise<void>;
|
|
884
|
+
delete(table: string, key: Key, options?: OperationOptions): Promise<boolean>;
|
|
894
885
|
keys(table: string): Promise<readonly Key[]>;
|
|
895
886
|
scan(table: string): AsyncIterable<Row_2>;
|
|
896
887
|
clear(table: string): Promise<void>;
|
|
897
|
-
records(table: string,
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
stream(table: string, criteria: Criteria_2): AsyncIterable<Row_2>;
|
|
888
|
+
records(table: string, input: QueryInput_2): Promise<readonly Row_2[]>;
|
|
889
|
+
aggregate(table: string, operation: AggregateOperation_2, column: FieldPath, input: QueryInput_2): Promise<number | undefined>;
|
|
890
|
+
stream(table: string, input: QueryInput_2): AsyncIterable<Row_2>;
|
|
901
891
|
/**
|
|
902
892
|
* Begin a native transaction — real `BEGIN`, `COMMIT`, `ROLLBACK`.
|
|
903
893
|
*
|
|
904
894
|
* @remarks
|
|
905
|
-
*
|
|
906
|
-
*
|
|
895
|
+
* The callback receives a scoped {@link StorageInterface}. Fulfillment
|
|
896
|
+
* commits and returns its value; rejection rolls back and preserves the
|
|
897
|
+
* original error. Root operations and nesting conflict while active, and a
|
|
898
|
+
* captured capability conflicts after settlement.
|
|
907
899
|
*
|
|
908
|
-
* @returns
|
|
900
|
+
* @returns The callback's resolved value
|
|
909
901
|
*/
|
|
910
|
-
transaction(): Promise<
|
|
902
|
+
transaction<R>(scope: (storage: StorageInterface) => Promise<R>): Promise<R>;
|
|
911
903
|
/**
|
|
912
904
|
* Apply a {@link Migration} plan by executing each step's projected DDL
|
|
913
|
-
* ({@link import('../
|
|
905
|
+
* ({@link import('../compilers.js').stepToSQL}).
|
|
914
906
|
*
|
|
915
907
|
* @remarks
|
|
916
908
|
* Atomicity is provided by whichever native transaction is active: when
|
|
917
|
-
* this driver's own `transaction()`
|
|
909
|
+
* this driver's own `transaction()` callback is active (the
|
|
918
910
|
* core's versioned reconcile / migrate path joins migrate + stamp under
|
|
919
911
|
* one native `BEGIN`), the plan's DDL runs directly inside that enclosing
|
|
920
|
-
* transaction — a mid-plan failure
|
|
921
|
-
*
|
|
912
|
+
* transaction — a mid-plan failure rejects the callback and the driver
|
|
913
|
+
* rolls it back. node:sqlite (and SQLite
|
|
922
914
|
* generally) rejects a nested `BEGIN`, so this driver must never open a
|
|
923
915
|
* second native transaction while one is already open. Otherwise (no
|
|
924
916
|
* enclosing transaction), `migrate` wraps the plan in its own native
|
|
925
917
|
* `database.transaction` — atomic on its own: a mid-plan failure rolls
|
|
926
|
-
* back every DDL statement already applied by the plan. A
|
|
927
|
-
*
|
|
928
|
-
*
|
|
929
|
-
*
|
|
930
|
-
* active
|
|
918
|
+
* back every DDL statement already applied by the plan. A scoped migration
|
|
919
|
+
* uses one fixed internal savepoint literal because the published SQLite
|
|
920
|
+
* wrapper intentionally exposes raw `exec` but no savepoint manager. That
|
|
921
|
+
* savepoint contains a caught inner migration so the outer callback
|
|
922
|
+
* transaction remains active and may continue safely. A step referencing a
|
|
923
|
+
* table not in this driver's declared schema (and that is not itself a
|
|
924
|
+
* `table.add`) throws `DatabaseError` `MIGRATION` before any DDL for that
|
|
925
|
+
* step runs.
|
|
931
926
|
*
|
|
932
|
-
* @param
|
|
927
|
+
* @param input - The migration plan and optional metadata stamp to apply atomically
|
|
933
928
|
*/
|
|
934
|
-
migrate(
|
|
929
|
+
migrate(input: MigrationInput): Promise<void>;
|
|
935
930
|
/**
|
|
936
|
-
* Read the persisted {@link
|
|
931
|
+
* Read the persisted {@link DriverMetadata} from the reserved `_metadata` table.
|
|
937
932
|
*
|
|
938
|
-
* @returns The last-stamped `
|
|
933
|
+
* @returns The last-stamped `DriverMetadata`, or `undefined` when never stamped
|
|
939
934
|
* (or the stored row is malformed)
|
|
940
935
|
*/
|
|
941
|
-
|
|
936
|
+
metadata(): Promise<DriverMetadata | undefined>;
|
|
942
937
|
/**
|
|
943
|
-
* Persist
|
|
944
|
-
* row.
|
|
938
|
+
* Persist an owned metadata snapshot into the reserved `_metadata` table's
|
|
939
|
+
* single row.
|
|
945
940
|
*
|
|
946
|
-
* @param
|
|
941
|
+
* @param metadata - The {@link DriverMetadata} to persist
|
|
947
942
|
*/
|
|
948
|
-
stamp(
|
|
943
|
+
stamp(metadata: DriverMetadata): Promise<void>;
|
|
949
944
|
snapshot(tables?: readonly string[]): Promise<() => Promise<void>>;
|
|
950
945
|
}
|
|
951
946
|
|
|
@@ -957,8 +952,9 @@ export declare class SQLiteDriver implements DriverInterface_2 {
|
|
|
957
952
|
* `path` is the database file path (`':memory:'` when omitted); `readonly`
|
|
958
953
|
* opens the connection read-only (a write then fails as a typed `DRIVER`
|
|
959
954
|
* {@link DatabaseError}); `timeout` is the busy-timeout in milliseconds before
|
|
960
|
-
* a locked database fails `BUSY`; `
|
|
961
|
-
* constraint enforcement
|
|
955
|
+
* a locked database fails `BUSY`; `references` enables or disables foreign-key
|
|
956
|
+
* constraint enforcement, while omission retains the upstream default.
|
|
957
|
+
* `pragmas` is an ordered record of PRAGMA name to
|
|
962
958
|
* value, applied via the wrapper's `pragma()` right after `connect()`, in
|
|
963
959
|
* insertion order (e.g. `{ journal_mode: 'WAL' }`). Core rows are
|
|
964
960
|
* number-typed — this driver never surfaces a `bigint`, so a stored integer
|
|
@@ -969,107 +965,16 @@ export declare interface SQLiteDriverOptions {
|
|
|
969
965
|
readonly path?: string;
|
|
970
966
|
readonly readonly?: boolean;
|
|
971
967
|
readonly timeout?: number;
|
|
972
|
-
readonly
|
|
968
|
+
readonly references?: boolean;
|
|
973
969
|
readonly pragmas?: Readonly<Record<string, string | number>>;
|
|
974
970
|
}
|
|
975
971
|
|
|
976
972
|
/**
|
|
977
|
-
*
|
|
978
|
-
*
|
|
979
|
-
* @remarks
|
|
980
|
-
* Every column value is a {@link SQLiteValue}. The SQLite driver decodes each
|
|
981
|
-
* raw row into this shape before handing it to the core query engine; nothing
|
|
982
|
-
* above the driver ever sees SQLite's native row representation directly.
|
|
983
|
-
*/
|
|
984
|
-
export declare type SQLiteRow = Record<string, SQLiteValue>;
|
|
985
|
-
|
|
986
|
-
/**
|
|
987
|
-
* The value domain a SQLite binding accepts as a bound parameter and returns
|
|
988
|
-
* from a row.
|
|
989
|
-
*
|
|
990
|
-
* @remarks
|
|
991
|
-
* Mirrors SQLite's storage classes (`NULL`, `INTEGER`, `REAL`, `TEXT`, `BLOB`)
|
|
992
|
-
* at the TypeScript boundary: `null`, `number` / `bigint` for integer and
|
|
993
|
-
* floating-point values, `string` for text, and `Uint8Array` for blobs. This
|
|
994
|
-
* type is pure — it names the shape a value must have to cross the binding,
|
|
995
|
-
* independent of any concrete sqlite package (`node:sqlite`, `better-sqlite3`,
|
|
996
|
-
* etc.), so a driver can encode/decode against it without importing one.
|
|
997
|
-
*/
|
|
998
|
-
export declare type SQLiteValue = null | number | bigint | string | Uint8Array;
|
|
999
|
-
|
|
1000
|
-
/**
|
|
1001
|
-
* Project one {@link MigrationStep} onto its table's declared {@link TableSchema}
|
|
1002
|
-
* — the bookkeeping counterpart to {@link stepToSQL} (which projects the DDL a
|
|
1003
|
-
* driver's `migrate` runs against the live database).
|
|
1004
|
-
*
|
|
1005
|
-
* @remarks
|
|
1006
|
-
* `column.add` / `column.remove` add / filter the named column;
|
|
1007
|
-
* `index.add` / `index.remove` add / filter the matching index group (an exact
|
|
1008
|
-
* ordered match on `index`). `table.add` / `table.remove` act on a WHOLE
|
|
1009
|
-
* schema map rather than one table's shape, so they are the caller's concern
|
|
1010
|
-
* (a driver's `migrate` applies them directly against its table map) — passed
|
|
1011
|
-
* here, they return `schema` unchanged.
|
|
1012
|
-
*
|
|
1013
|
-
* @param schema - The table's current declared schema
|
|
1014
|
-
* @param step - The migration step to project onto it
|
|
1015
|
-
* @returns The table's schema after the step
|
|
1016
|
-
*
|
|
1017
|
-
* @example
|
|
1018
|
-
* ```ts
|
|
1019
|
-
* stepToSchema(schema, { operation: 'column.remove', table: 'users', column: 'legacy' })
|
|
1020
|
-
* // schema with the 'legacy' column dropped from `columns`
|
|
1021
|
-
* ```
|
|
1022
|
-
*/
|
|
1023
|
-
export declare function stepToSchema(schema: TableSchema, step: MigrationStep): TableSchema;
|
|
1024
|
-
|
|
1025
|
-
/**
|
|
1026
|
-
* Project one {@link MigrationStep} to the DDL statement(s) a SQLite driver's
|
|
1027
|
-
* `migrate` executes for it.
|
|
1028
|
-
*
|
|
1029
|
-
* @remarks
|
|
1030
|
-
* `table.add` emits the `CREATE TABLE` plus one `CREATE INDEX` per declared
|
|
1031
|
-
* index (via {@link schemaToTable} / {@link schemaToIndexes}); `table.remove`
|
|
1032
|
-
* emits `DROP TABLE IF EXISTS`; `column.add` / `column.remove` emit `ALTER
|
|
1033
|
-
* TABLE … ADD COLUMN` / `… DROP COLUMN`; `index.add` / `index.remove` emit
|
|
1034
|
-
* `CREATE INDEX IF NOT EXISTS` / `DROP INDEX IF EXISTS`, naming the index the
|
|
1035
|
-
* same way `schemaToIndexes` does (`idx_<table>_<columns joined by _>`) so a
|
|
1036
|
-
* plan-built index matches one `open` would have created. Whether the named
|
|
1037
|
-
* table actually exists is the caller's concern (a driver's `migrate` checks
|
|
1038
|
-
* its own declared schema before running these statements) — this projection
|
|
1039
|
-
* is pure and never inspects live state.
|
|
1040
|
-
*
|
|
1041
|
-
* @param step - The migration step to project
|
|
1042
|
-
* @returns The DDL statement(s) that apply the step
|
|
973
|
+
* Project one {@link MigrationStep} to SQLite DDL.
|
|
1043
974
|
*
|
|
1044
|
-
* @
|
|
1045
|
-
*
|
|
1046
|
-
* stepToSQL({ operation: 'column.remove', table: 'users', column: 'legacy' })
|
|
1047
|
-
* // ['ALTER TABLE "users" DROP COLUMN "legacy"']
|
|
1048
|
-
* ```
|
|
975
|
+
* @param step - The migration step
|
|
976
|
+
* @returns The statements that apply the step
|
|
1049
977
|
*/
|
|
1050
978
|
export declare function stepToSQL(step: MigrationStep): readonly string[];
|
|
1051
979
|
|
|
1052
|
-
/**
|
|
1053
|
-
* The storage type a nested (`json_extract`) operand encodes as, derived from its
|
|
1054
|
-
* RUNTIME value — NOT `json`.
|
|
1055
|
-
*
|
|
1056
|
-
* @remarks
|
|
1057
|
-
* `json_extract` returns the unquoted, natively-typed scalar (a JSON boolean as
|
|
1058
|
-
* `1` / `0`, a number as-is, a string as-is), so the operand must encode to that
|
|
1059
|
-
* same scalar to compare. A boolean → `'boolean'` (→ `1` / `0`); a number →
|
|
1060
|
-
* `'integer'` / `'real'`; a bigint → `'integer'`; a string → `'text'`; `null` /
|
|
1061
|
-
* `undefined` → `'text'` (encodes to `null`); an object / array → `'json'` (the
|
|
1062
|
-
* edge of comparing against a json subtree).
|
|
1063
|
-
*
|
|
1064
|
-
* @param value - The runtime operand value
|
|
1065
|
-
* @returns The {@link ColumnType} to encode it as
|
|
1066
|
-
*
|
|
1067
|
-
* @example
|
|
1068
|
-
* ```ts
|
|
1069
|
-
* valueType(true) // 'boolean'
|
|
1070
|
-
* valueType(9) // 'integer'
|
|
1071
|
-
* ```
|
|
1072
|
-
*/
|
|
1073
|
-
export declare function valueType(value: unknown): ColumnType;
|
|
1074
|
-
|
|
1075
980
|
export { }
|