@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.
- package/README.md +12 -8
- package/dist/src/browser/index.d.ts +95 -70
- package/dist/src/browser/index.js +115 -86
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +595 -410
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +712 -313
- package/dist/src/core/index.d.ts +712 -313
- package/dist/src/core/index.js +588 -409
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +236 -332
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +213 -223
- package/dist/src/server/index.d.ts +213 -223
- package/dist/src/server/index.js +230 -324
- package/dist/src/server/index.js.map +1 -1
- package/package.json +22 -18
package/README.md
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# @orkestrel/database
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
`
|
|
3
|
+
> One typed database API for keyed rows, fluent queries, cursors, and
|
|
4
|
+
> whole-store transactions, running unchanged over an in-memory map, a JSON
|
|
5
|
+
> file, SQLite, or IndexedDB.
|
|
6
|
+
|
|
7
|
+
Declare your tables with the `createDatabase` function, hold each
|
|
8
|
+
`TableInterface` it hands back, and reach rows by key or through the fluent
|
|
9
|
+
`query()` builder. `TableInterface.cursor()` opens the `CursorInterface`
|
|
10
|
+
contract for serial bulk mutation. Built on `@orkestrel/contract` for
|
|
11
|
+
validation and `@orkestrel/emitter` for the observation surface, reusing both
|
|
12
|
+
directly. Part of the `@orkestrel` line.
|
|
9
13
|
|
|
10
14
|
## Install
|
|
11
15
|
|
|
@@ -23,10 +27,10 @@ npm install @orkestrel/database
|
|
|
23
27
|
|
|
24
28
|
## Status
|
|
25
29
|
|
|
26
|
-
Pre-release
|
|
30
|
+
Pre-release: the core engine and the memory, JSON file, SQLite,
|
|
27
31
|
and IndexedDB drivers are all implemented and tested, but the public API is
|
|
28
32
|
still unstable and may change without notice. See
|
|
29
|
-
[guides/
|
|
33
|
+
[guides/database.md](./guides/database.md) for the full documented
|
|
30
34
|
surface.
|
|
31
35
|
|
|
32
36
|
## Package
|
|
@@ -1,37 +1,34 @@
|
|
|
1
|
-
import { ColumnStorage } from '@orkestrel/database';
|
|
2
|
-
import { Condition } from '@orkestrel/database';
|
|
1
|
+
import type { ColumnStorage } from '@orkestrel/database';
|
|
2
|
+
import type { Condition } from '@orkestrel/database';
|
|
3
3
|
import { DatabaseError } from '@orkestrel/database';
|
|
4
|
-
import { DriverInterface } from '@orkestrel/database';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { StoreDefinition } from '@orkestrel/indexeddb';
|
|
15
|
-
import { TableSchema } from '@orkestrel/database';
|
|
16
|
-
import { TableSchema as TableSchema_2 } from '@orkestrel/database';
|
|
4
|
+
import type { DriverInterface } from '@orkestrel/database';
|
|
5
|
+
import type { DriverMetadata } from '@orkestrel/database';
|
|
6
|
+
import type { IndexedDBError } from '@orkestrel/indexeddb';
|
|
7
|
+
import type { Key } from '@orkestrel/database';
|
|
8
|
+
import type { MigrationInput } from '@orkestrel/database';
|
|
9
|
+
import type { OperationOptions } from '@orkestrel/database';
|
|
10
|
+
import type { QueryInput } from '@orkestrel/database';
|
|
11
|
+
import type { Row } from '@orkestrel/database';
|
|
12
|
+
import type { StoreDefinition } from '@orkestrel/indexeddb';
|
|
13
|
+
import type { TableSchema } from '@orkestrel/database';
|
|
17
14
|
|
|
18
15
|
/**
|
|
19
|
-
*
|
|
20
|
-
* of the
|
|
21
|
-
* `undefined`.
|
|
16
|
+
* Translates one {@link Condition} to the `IDBKeyRange` it maps to, when its
|
|
17
|
+
* operator is one of the exact key comparisons over scalar operands; otherwise
|
|
18
|
+
* returns `undefined`.
|
|
22
19
|
*
|
|
23
20
|
* @remarks
|
|
24
21
|
* Only the comparison operators (`equals`/`above`/`below`/`from`/`to`/`between`)
|
|
25
22
|
* translate to a key range that a typed (string/number) column can back with an
|
|
26
23
|
* IndexedDB store/index read — see {@link selectPlan} for the caveats that
|
|
27
|
-
* decide
|
|
24
|
+
* decide which of `below`/`to` may drive a secondary-index read versus the
|
|
28
25
|
* primary store only (a column-type / absent-row concern, not a range-shape
|
|
29
26
|
* one). `starts` is excluded — its prefix range can miss strings past U+FFFF;
|
|
30
27
|
* the membership / negation / pattern / existence operators (`not`/`like`/`glob`/
|
|
31
28
|
* `ends`/`any`/`none`/`absent`/`present`) have no single exact range. The operand
|
|
32
|
-
* guard (`typeof` string/number) rejects a non-scalar value (
|
|
29
|
+
* guard (`typeof` string/number) rejects a non-scalar value (for example an array, a
|
|
33
30
|
* boolean) that is not a usable key. `between` additionally guards against a
|
|
34
|
-
*
|
|
31
|
+
* reversed pair (`first > second`): native `IDBKeyRange.bound` throws a raw
|
|
35
32
|
* `DataError` `DOMException` for a lower bound above the upper bound, so a
|
|
36
33
|
* reversed pair returns `undefined` here (falls back to a full scan, which the
|
|
37
34
|
* engine then correctly resolves to an empty result) rather than letting a
|
|
@@ -47,7 +44,7 @@ import { TableSchema as TableSchema_2 } from '@orkestrel/database';
|
|
|
47
44
|
export declare function conditionToRange(condition: Condition): IDBKeyRange | undefined;
|
|
48
45
|
|
|
49
46
|
/**
|
|
50
|
-
*
|
|
47
|
+
* Creates a persistent IndexedDB {@link DriverInterface} for a browser database name.
|
|
51
48
|
*
|
|
52
49
|
* @remarks
|
|
53
50
|
* Pass it to `createDatabase` from `@orkestrel/database` to run the typed database
|
|
@@ -74,23 +71,23 @@ export declare function conditionToRange(condition: Condition): IDBKeyRange | un
|
|
|
74
71
|
* await db.table('users').set({ id: 'u1', name: 'Ada' }) // persisted to IndexedDB
|
|
75
72
|
* ```
|
|
76
73
|
*/
|
|
77
|
-
export declare function createIndexedDBDriver(name: string):
|
|
74
|
+
export declare function createIndexedDBDriver(name: string): DriverInterface;
|
|
78
75
|
|
|
79
76
|
/**
|
|
80
|
-
*
|
|
77
|
+
* Derives an IndexedDB index name for a declared column group — a bare column
|
|
81
78
|
* name for a single-column index, a deterministic collision-free encoding for a
|
|
82
79
|
* compound one.
|
|
83
80
|
*
|
|
84
81
|
* @remarks
|
|
85
82
|
* Naming a compound index by joining its columns with `_` (`['a', 'b'] →
|
|
86
|
-
* 'a_b'`) collides with a single-column index over a column
|
|
83
|
+
* 'a_b'`) collides with a single-column index over a column literally named
|
|
87
84
|
* `'a_b'` — the same name, two different key paths (`'a_b'` vs `['a', 'b']`),
|
|
88
85
|
* which either throws a native `ConstraintError` from a duplicate
|
|
89
86
|
* `createIndex` call at open, or (worse) lets {@link selectPlan}'s name-based
|
|
90
|
-
* lookup match the wrong index. A single-column index keeps the
|
|
87
|
+
* lookup match the wrong index. A single-column index keeps the bare column
|
|
91
88
|
* name — {@link selectPlan} matches `available.includes(condition.column)` by
|
|
92
89
|
* that exact name, so a single-column index must stay named after its column
|
|
93
|
-
* verbatim. A compound index instead encodes each column as a
|
|
90
|
+
* verbatim. A compound index instead encodes each column as a length-prefixed
|
|
94
91
|
* segment (`'2#1:a1:b'`), so the boundary between columns is self-describing
|
|
95
92
|
* and cannot be reconstructed by any other column list — including one
|
|
96
93
|
* containing a column that happens to look like an encoded segment.
|
|
@@ -106,11 +103,23 @@ export declare function createIndexedDBDriver(name: string): DriverInterface_2;
|
|
|
106
103
|
*/
|
|
107
104
|
export declare function deriveIndexedDBIndexName(columns: readonly string[]): string;
|
|
108
105
|
|
|
109
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Lists the declared {@link ColumnStorage}s that are valid, orderable IndexedDB keys.
|
|
108
|
+
*
|
|
109
|
+
* @remarks
|
|
110
|
+
* `text` / `integer` / `real` occupy IndexedDB's string / number key space, so a
|
|
111
|
+
* column declared with one of them can back a store or index range read.
|
|
112
|
+
* `boolean` / `json` / `blob` are not valid `IDBValidKey`s and a range over one
|
|
113
|
+
* would silently miss rows, so `selectPlan` never pushes a condition down on
|
|
114
|
+
* them and the core engine answers the read instead. A frozen array, matching
|
|
115
|
+
* `EXACT_COLUMN_STORAGE` in `src/server`: a consumer holding it reads the
|
|
116
|
+
* membership with `includes` and cannot change the driver's pushdown behavior.
|
|
117
|
+
*/
|
|
118
|
+
export declare const INDEXABLE_STORAGE: readonly ColumnStorage[];
|
|
110
119
|
|
|
111
120
|
/**
|
|
112
|
-
*
|
|
113
|
-
* the published `@orkestrel/indexeddb` wrapper.
|
|
121
|
+
* Implements the {@link DriverInterface} over IndexedDB — the persistent browser backend,
|
|
122
|
+
* built on the published `@orkestrel/indexeddb` wrapper.
|
|
114
123
|
*
|
|
115
124
|
* @remarks
|
|
116
125
|
* A thin adapter: it implements the storage primitives the core database layer
|
|
@@ -126,12 +135,12 @@ export declare const INDEXABLE_STORAGE: ReadonlySet<ColumnStorage>;
|
|
|
126
135
|
* wrapper's native `getAll` / `getAllKeys`, and `snapshot` rolls back through one
|
|
127
136
|
* atomic wrapper transaction.
|
|
128
137
|
*
|
|
129
|
-
* It also implements the optional native `records` / `stream` hooks
|
|
130
|
-
*
|
|
138
|
+
* It also implements the optional native `records` / `stream` hooks:
|
|
139
|
+
* `selectPlan` ({@link selectPlan}) turns the {@link QueryInput} into a
|
|
131
140
|
* key-range pushdown over the primary key or a single-column secondary index,
|
|
132
141
|
* fetching a candidate **superset** that the core engine (`applyQuery` /
|
|
133
142
|
* `matchesQuery`) then refines — so a native read is byte-identical to a full
|
|
134
|
-
* scan,
|
|
143
|
+
* scan, only cheaper. Pushdown is conservative: only the exact-comparison
|
|
135
144
|
* operators over orderable columns narrow to a range; everything else falls back
|
|
136
145
|
* to a full scan + the engine.
|
|
137
146
|
*
|
|
@@ -143,27 +152,28 @@ export declare const INDEXABLE_STORAGE: ReadonlySet<ColumnStorage>;
|
|
|
143
152
|
* {@link Migration} plan natively: IndexedDB schema DDL (creating/dropping a
|
|
144
153
|
* store, creating/dropping an index) is legal only inside a versionchange
|
|
145
154
|
* transaction (`onupgradeneeded`), so `migrate` closes the current connection
|
|
146
|
-
* and opens a
|
|
155
|
+
* and opens a fresh one at `version + 1` with an `upgrade` hook that walks the
|
|
147
156
|
* plan's steps — dropping stores, adding/removing indexes on the raw
|
|
148
|
-
* `IDBTransaction`, and rewriting rows for `column.remove`
|
|
157
|
+
* `IDBTransaction`, and rewriting rows for `column.remove` through a cursor walk
|
|
149
158
|
* (the one step needing to touch existing data; `column.add` is a no-op — this
|
|
150
159
|
* driver stores whatever a row carries, so there is nothing to backfill). A
|
|
151
|
-
* step referencing an unknown table is validated
|
|
160
|
+
* step referencing an unknown table is validated before the reconnect, so a
|
|
152
161
|
* `MIGRATION` `DatabaseError` never wastes a version bump.
|
|
153
162
|
*
|
|
154
163
|
* @remarks
|
|
155
|
-
* This unit deliberately
|
|
164
|
+
* This unit deliberately omits `aggregate` / `transaction`. There is no native
|
|
156
165
|
* `aggregate` (IndexedDB has no native SUM/AVG); the engine over the narrowed
|
|
157
|
-
* `records` covers it. `transaction` is impossible here: the wrapper
|
|
158
|
-
* an `IDBTransaction` when control yields outside its request
|
|
159
|
-
* callback awaits cannot remain inside one native
|
|
160
|
-
* multi-operation sequence in this driver
|
|
161
|
-
* (`snapshot`'s rollback) instead runs entirely inside
|
|
166
|
+
* `records` covers it. `transaction` is impossible here: the wrapper
|
|
167
|
+
* auto-commits an `IDBTransaction` when control yields outside its request
|
|
168
|
+
* chain, so arbitrary callback awaits cannot remain inside one native
|
|
169
|
+
* transaction. Every atomic multi-operation sequence in this driver
|
|
170
|
+
* (`snapshot`'s rollback) instead runs entirely inside one `db.write(...)`
|
|
171
|
+
* scope.
|
|
162
172
|
*/
|
|
163
173
|
export declare class IndexedDBDriver implements DriverInterface {
|
|
164
174
|
#private;
|
|
165
175
|
constructor(name: string);
|
|
166
|
-
open(schema: readonly
|
|
176
|
+
open(schema: readonly TableSchema[]): Promise<void>;
|
|
167
177
|
close(): Promise<void>;
|
|
168
178
|
read(table: string, key: Key): Promise<Row | undefined>;
|
|
169
179
|
write(table: string, key: Key, row: Row, options?: OperationOptions): Promise<void>;
|
|
@@ -172,11 +182,11 @@ export declare class IndexedDBDriver implements DriverInterface {
|
|
|
172
182
|
keys(table: string): Promise<readonly Key[]>;
|
|
173
183
|
scan(table: string): AsyncIterable<Row>;
|
|
174
184
|
clear(table: string): Promise<void>;
|
|
175
|
-
records(table: string, input:
|
|
176
|
-
stream(table: string, input:
|
|
185
|
+
records(table: string, input: QueryInput): Promise<readonly Row[]>;
|
|
186
|
+
stream(table: string, input: QueryInput): AsyncIterable<Row>;
|
|
177
187
|
snapshot(tables?: readonly string[]): Promise<() => Promise<void>>;
|
|
178
188
|
/**
|
|
179
|
-
*
|
|
189
|
+
* Returns the persisted {@link DriverMetadata}, or `undefined` when the store has
|
|
180
190
|
* never been stamped.
|
|
181
191
|
*
|
|
182
192
|
* @remarks
|
|
@@ -189,22 +199,22 @@ export declare class IndexedDBDriver implements DriverInterface {
|
|
|
189
199
|
*/
|
|
190
200
|
metadata(): Promise<DriverMetadata | undefined>;
|
|
191
201
|
/**
|
|
192
|
-
*
|
|
202
|
+
* Persists an owned metadata snapshot for a later `metadata()` to return.
|
|
193
203
|
*
|
|
194
204
|
* @param metadata - The {@link DriverMetadata} to persist
|
|
195
205
|
*/
|
|
196
206
|
stamp(metadata: DriverMetadata): Promise<void>;
|
|
197
207
|
/**
|
|
198
|
-
*
|
|
208
|
+
* Applies a {@link Migration} plan by reconnecting at a bumped version and
|
|
199
209
|
* running the plan's steps inside the wrapper's `upgrade` hook.
|
|
200
210
|
*
|
|
201
211
|
* @remarks
|
|
202
212
|
* IndexedDB schema DDL is legal only inside `onupgradeneeded`, so this closes
|
|
203
|
-
* the current connection and opens a
|
|
204
|
-
* every
|
|
205
|
-
* and applying `table.remove` / `index.add` / `index.remove` /
|
|
213
|
+
* the current connection and opens a fresh one at `version + 1`, declaring
|
|
214
|
+
* every store known at that point (plus {@link METADATA_STORE}) so nothing is
|
|
215
|
+
* lost, and applying `table.remove` / `index.add` / `index.remove` /
|
|
206
216
|
* `column.remove` inside `upgrade`. Every step's `table` is validated against
|
|
207
|
-
* the driver's own `#schema`
|
|
217
|
+
* the driver's own `#schema` before the reconnect — an unknown-table step
|
|
208
218
|
* throws `DatabaseError` `MIGRATION` without ever bumping the version.
|
|
209
219
|
* `table.add` / `column.add` need no upgrade-time action: `table.add` is
|
|
210
220
|
* created by the wrapper's built-in create-missing-stores pass (its
|
|
@@ -220,7 +230,7 @@ export declare class IndexedDBDriver implements DriverInterface {
|
|
|
220
230
|
}
|
|
221
231
|
|
|
222
232
|
/**
|
|
223
|
-
*
|
|
233
|
+
* Maps a backend {@link IndexedDBError} to the portable `DatabaseError` taxonomy
|
|
224
234
|
* — the default mapping used everywhere except inside `migrate()`.
|
|
225
235
|
*
|
|
226
236
|
* @remarks
|
|
@@ -246,8 +256,8 @@ export declare class IndexedDBDriver implements DriverInterface {
|
|
|
246
256
|
export declare function mapIndexedDBError(error: IndexedDBError): DatabaseError;
|
|
247
257
|
|
|
248
258
|
/**
|
|
249
|
-
*
|
|
250
|
-
* for use
|
|
259
|
+
* Maps a backend {@link IndexedDBError} to the portable `DatabaseError` taxonomy
|
|
260
|
+
* for use inside `migrate()` — the one context where `UPGRADE` means the
|
|
251
261
|
* migration itself failed, not a generic driver fault.
|
|
252
262
|
*
|
|
253
263
|
* @remarks
|
|
@@ -263,14 +273,24 @@ export declare function mapIndexedDBError(error: IndexedDBError): DatabaseError;
|
|
|
263
273
|
*/
|
|
264
274
|
export declare function mapMigrationError(error: IndexedDBError): DatabaseError;
|
|
265
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Names the reserved out-of-line store `__metadata__` the {@link IndexedDBDriver}
|
|
278
|
+
* stamps its {@link DriverMetadata} into.
|
|
279
|
+
*
|
|
280
|
+
* @remarks
|
|
281
|
+
* Backs the driver's `metadata` / `stamp` hooks. A user table named `__metadata__`
|
|
282
|
+
* collides with the driver's own bookkeeping, so a caller must avoid it; the
|
|
283
|
+
* collision is caught at `open`.
|
|
284
|
+
*/
|
|
266
285
|
export declare const METADATA_STORE = "__metadata__";
|
|
267
286
|
|
|
268
287
|
/**
|
|
269
|
-
*
|
|
288
|
+
* Represents a pushdown plan — an optional index and optional `IDBKeyRange` used to narrow
|
|
270
289
|
* a read. An omitted `index` selects the primary store; an omitted `range`
|
|
271
290
|
* performs a full scan. The plan is always a superset of the matching rows;
|
|
272
291
|
* the core engine refines it to the exact result. An empty plan (`{}`) is a
|
|
273
|
-
* primary-store full scan.
|
|
292
|
+
* primary-store full scan. The `@orkestrel/database/browser` entry point exports
|
|
293
|
+
* this type.
|
|
274
294
|
*/
|
|
275
295
|
export declare interface QueryPlan {
|
|
276
296
|
readonly index?: string;
|
|
@@ -278,7 +298,12 @@ export declare interface QueryPlan {
|
|
|
278
298
|
}
|
|
279
299
|
|
|
280
300
|
/**
|
|
281
|
-
*
|
|
301
|
+
* Projects a table schema into the IndexedDB wrapper's store definition.
|
|
302
|
+
*
|
|
303
|
+
* @remarks
|
|
304
|
+
* The definition is what an ordered versionchange migration creates the store
|
|
305
|
+
* from, carrying each declared index under the name {@link deriveIndexedDBIndexName}
|
|
306
|
+
* derives for its column group.
|
|
282
307
|
*
|
|
283
308
|
* @param schema - Portable table schema
|
|
284
309
|
* @returns Store definition with declared indexes
|
|
@@ -286,11 +311,11 @@ export declare interface QueryPlan {
|
|
|
286
311
|
export declare function schemaToStore(schema: TableSchema): StoreDefinition;
|
|
287
312
|
|
|
288
313
|
/**
|
|
289
|
-
*
|
|
314
|
+
* Plans an IndexedDB read for a {@link QueryInput} — picks the index (or the primary
|
|
290
315
|
* store) and {@link IDBKeyRange} to narrow by, falling back to a full scan.
|
|
291
316
|
*
|
|
292
317
|
* @remarks
|
|
293
|
-
* Pushdown is sound
|
|
318
|
+
* Pushdown is sound only when every condition is `and`-joined: the engine folds
|
|
294
319
|
* conditions left-to-right (`c1 && c2 && … && cn`), so the result is a subset of
|
|
295
320
|
* each — narrowing on any one is then a valid superset. A single `or` breaks that
|
|
296
321
|
* (a row can match through a later condition the range would exclude), so any `or`
|
|
@@ -304,21 +329,21 @@ export declare function schemaToStore(schema: TableSchema): StoreDefinition;
|
|
|
304
329
|
* type (`boolean`/`json`/`blob`), uses a non-comparison operator, or has a
|
|
305
330
|
* non-scalar operand cannot push and is skipped.
|
|
306
331
|
*
|
|
307
|
-
* **`below`/`to` may drive a
|
|
332
|
+
* **`below`/`to` may drive a secondary-index range only when the column has no
|
|
308
333
|
* absent/null rows to lose — which this planner cannot verify from the schema
|
|
309
|
-
* alone, so it restricts them to the
|
|
310
|
-
* The engine's total order (`compareValues`, see `@
|
|
311
|
-
* `undefined` (absent) and `null`
|
|
312
|
-
* `matchesCondition('below' | 'to', …)` is
|
|
313
|
-
* or `null` — but a secondary IndexedDB index has
|
|
334
|
+
* alone, so it restricts them to the primary store, where that is always true.**
|
|
335
|
+
* The engine's total order (`compareValues`, see `@orkestrel/database`) ranks
|
|
336
|
+
* `undefined` (absent) and `null` below every number/string, so
|
|
337
|
+
* `matchesCondition('below' | 'to', …)` is true for a row whose field is absent
|
|
338
|
+
* or `null` — but a secondary IndexedDB index has no entry for a row whose
|
|
314
339
|
* indexed field is absent/`null`, so a `below`/`to` range read against that
|
|
315
|
-
* index would
|
|
340
|
+
* index would silently drop those rows (they can never be over-fetched, only
|
|
316
341
|
* missed — the one shape of lossiness this planner must never produce). The
|
|
317
|
-
* table's
|
|
342
|
+
* table's primary key is exempt: a row's primary-key value is always present
|
|
318
343
|
* and never `null` (it is the row's identity, enforced at write time), so a
|
|
319
344
|
* `below`/`to` range against the primary store can never exclude an
|
|
320
345
|
* absent/null-keyed row because no such row exists. `equals`/`above`/`from`/
|
|
321
|
-
* `between` stay index-eligible on
|
|
346
|
+
* `between` stay index-eligible on any orderable column, primary or secondary:
|
|
322
347
|
* each is bounded below by a scalar (`equals`/`between`'s lower bound, `above`/
|
|
323
348
|
* `from`'s lower bound), and every scalar strictly out-ranks `undefined`/`null`
|
|
324
349
|
* in the total order, so an absent/null-valued row can never satisfy them — the
|
|
@@ -332,7 +357,7 @@ export declare function schemaToStore(schema: TableSchema): StoreDefinition;
|
|
|
332
357
|
* metadata.
|
|
333
358
|
*
|
|
334
359
|
* When no condition qualifies the plan is a full scan (`{}`) and the engine
|
|
335
|
-
* does everything. The plan is always a
|
|
360
|
+
* does everything. The plan is always a superset of the
|
|
336
361
|
* matching rows — the only correctness contract — so the driver may safely run
|
|
337
362
|
* the exact engine over it.
|
|
338
363
|
*
|