@orkestrel/database 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +1508 -757
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +443 -569
- package/dist/src/server/index.d.ts +443 -569
- package/dist/src/server/index.js +1492 -741
- package/dist/src/server/index.js.map +1 -1
- package/package.json +9 -10
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# @orkestrel/database
|
|
2
2
|
|
|
3
|
-
A typed database abstraction for the `@orkestrel` line —
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
A typed database abstraction for the `@orkestrel` line — one public
|
|
4
|
+
`Database` over internal table/query engines and pluggable storage drivers at
|
|
5
|
+
the seams. Consumers use `TableInterface` and `QueryInterface`. Built to sit beside
|
|
6
6
|
`@orkestrel/contract` (validation) and `@orkestrel/emitter` (observable
|
|
7
|
-
lifecycle), reusing both
|
|
7
|
+
lifecycle), reusing both directly. `TableInterface.cursor()` exposes the
|
|
8
|
+
`CursorInterface` contract for serial bulk mutation.
|
|
8
9
|
|
|
9
10
|
## Install
|
|
10
11
|
|
|
@@ -14,14 +15,15 @@ npm install @orkestrel/database
|
|
|
14
15
|
|
|
15
16
|
## Requirements
|
|
16
17
|
|
|
17
|
-
- Node.js >=
|
|
18
|
-
|
|
18
|
+
- Node.js >= 22.12.0, matching the package engine declaration
|
|
19
|
+
- The `./server` SQLite driver additionally requires a Node.js release that
|
|
20
|
+
provides `node:sqlite`
|
|
19
21
|
- Core is ESM; the `./server` subpath ships dual ESM+CJS builds; `./browser`
|
|
20
22
|
is ESM-only
|
|
21
23
|
|
|
22
24
|
## Status
|
|
23
25
|
|
|
24
|
-
Pre-release (`0.0.
|
|
26
|
+
Pre-release (`0.0.7`): the core engine and the memory, JSON file, SQLite,
|
|
25
27
|
and IndexedDB drivers are all implemented and tested, but the public API is
|
|
26
28
|
still unstable and may change without notice. See
|
|
27
29
|
[guides/src/database.md](./guides/src/database.md) for the full documented
|
|
@@ -35,34 +37,10 @@ the in-memory driver), `./server` (adds the JSON file and SQLite drivers),
|
|
|
35
37
|
and `./browser` (adds the IndexedDB driver). Core and `./server` ship dual
|
|
36
38
|
ESM+CJS builds; `./browser` is ESM-only.
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
packages and absorbs every change on this line.
|
|
43
|
-
|
|
44
|
-
This package's SQLite and IndexedDB drivers are built against the wrapper
|
|
45
|
-
surfaces documented by the mirrored guides in this repo — that is,
|
|
46
|
-
`@orkestrel/sqlite@0.0.2` and `@orkestrel/indexeddb@0.0.2` — and the
|
|
47
|
-
dependency ranges pin exactly those versions (`^0.0.2`; on a `0.0.x` version
|
|
48
|
-
a caret means exactly that patch: `>=0.0.2 <0.0.3`). Publish in this order:
|
|
49
|
-
|
|
50
|
-
1. Publish `@orkestrel/sqlite@0.0.2` and `@orkestrel/indexeddb@0.0.2` — they
|
|
51
|
-
are independent of each other (either order; both depend only on the
|
|
52
|
-
already-published `@orkestrel/contract`).
|
|
53
|
-
2. In this repo, run `npm install` to re-resolve `package-lock.json` against
|
|
54
|
-
the newly published wrappers and commit the refreshed lockfile.
|
|
55
|
-
3. Run the `prepublishOnly` gates and publish `@orkestrel/database@0.0.2`.
|
|
56
|
-
|
|
57
|
-
Until step 1 happens, a fresh `npm ci` in this repo fails to resolve
|
|
58
|
-
`^0.0.2` — deliberately. The exact pin makes it impossible to install or
|
|
59
|
-
publish this package against the older `0.0.1` wrappers, which lack driver
|
|
60
|
-
fixes this package's behavior relies on (the SQLite wrapper's mid-stream
|
|
61
|
-
`iterate` fault mapping, the IndexedDB wrapper's abnormal-close recovery and
|
|
62
|
-
`READONLY` fault code) and whose surfaces the mirrored guides here no longer
|
|
63
|
-
describe. The same discipline applies to every future wrapper release: bump
|
|
64
|
-
the pinned range, re-mirror the wrapper guides, and republish this package
|
|
65
|
-
deliberately — wrapper changes never flow in silently.
|
|
40
|
+
The server and browser drivers use the declared `@orkestrel/sqlite` and
|
|
41
|
+
`@orkestrel/indexeddb` package ranges. Wrapper upgrades are deliberate:
|
|
42
|
+
update the declared range, refresh the lockfile, run the full package gates,
|
|
43
|
+
and publish Database only after its wrapper dependencies are available.
|
|
66
44
|
|
|
67
45
|
## License
|
|
68
46
|
|
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ColumnStorage } from '../core/index.ts';
|
|
2
2
|
import { Condition } from '../core/index.ts';
|
|
3
|
-
import { Criteria } from '../core/index.ts';
|
|
4
|
-
import { Criteria as Criteria_2 } from '../../core/index.ts';
|
|
5
3
|
import { DatabaseError } from '../core/index.ts';
|
|
6
4
|
import { DriverInterface } from '../../core/index.ts';
|
|
7
5
|
import { DriverInterface as DriverInterface_2 } from '../core/index.ts';
|
|
8
|
-
import {
|
|
6
|
+
import { DriverMetadata } from '../../core/index.ts';
|
|
9
7
|
import { IndexedDBError } from '@orkestrel/indexeddb';
|
|
10
8
|
import { Key } from '../../core/index.ts';
|
|
11
|
-
import {
|
|
9
|
+
import { MigrationInput } from '../../core/index.ts';
|
|
10
|
+
import { OperationOptions } from '../../core/index.ts';
|
|
11
|
+
import { QueryInput } from '../core/index.ts';
|
|
12
|
+
import { QueryInput as QueryInput_2 } from '../../core/index.ts';
|
|
12
13
|
import { Row } from '../../core/index.ts';
|
|
14
|
+
import { StoreDefinition } from '@orkestrel/indexeddb';
|
|
13
15
|
import { TableSchema } from '../core/index.ts';
|
|
14
16
|
import { TableSchema as TableSchema_2 } from '../../core/index.ts';
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* The `IDBKeyRange` a single {@link Condition} maps to, when its operator is one
|
|
18
|
-
* of the six exact key comparisons over scalar operands
|
|
20
|
+
* of the six exact key comparisons over scalar operands; otherwise
|
|
21
|
+
* `undefined`.
|
|
19
22
|
*
|
|
20
23
|
* @remarks
|
|
21
24
|
* Only the comparison operators (`equals`/`above`/`below`/`from`/`to`/`between`)
|
|
@@ -30,7 +33,7 @@ import { TableSchema as TableSchema_2 } from '../../core/index.ts';
|
|
|
30
33
|
* boolean) that is not a usable key. `between` additionally guards against a
|
|
31
34
|
* REVERSED pair (`first > second`): native `IDBKeyRange.bound` throws a raw
|
|
32
35
|
* `DataError` `DOMException` for a lower bound above the upper bound, so a
|
|
33
|
-
* reversed pair returns `
|
|
36
|
+
* reversed pair returns `undefined` here (falls back to a full scan, which the
|
|
34
37
|
* engine then correctly resolves to an empty result) rather than letting a
|
|
35
38
|
* native exception escape untyped — the same defensive posture as every other
|
|
36
39
|
* backend, which returns empty for a reversed/empty range instead of throwing.
|
|
@@ -39,17 +42,17 @@ import { TableSchema as TableSchema_2 } from '../../core/index.ts';
|
|
|
39
42
|
* to a (possibly lossy) range.
|
|
40
43
|
*
|
|
41
44
|
* @param condition - The condition to translate
|
|
42
|
-
* @returns Its exact key range, or `
|
|
45
|
+
* @returns Its exact key range, or `undefined` when the operator/operands cannot push
|
|
43
46
|
*/
|
|
44
|
-
export declare function
|
|
47
|
+
export declare function conditionToRange(condition: Condition): IDBKeyRange | undefined;
|
|
45
48
|
|
|
46
49
|
/**
|
|
47
50
|
* Create a persistent IndexedDB {@link DriverInterface} for the core database layer.
|
|
48
51
|
*
|
|
49
52
|
* @remarks
|
|
50
|
-
* Pass it to `createDatabase` from `@orkestrel/database` to run the
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
+
* Pass it to `createDatabase` from `@orkestrel/database` to run the typed database
|
|
54
|
+
* layer against IndexedDB instead of memory — the `Database` / `Table` / `Query`
|
|
55
|
+
* API is unchanged; only where the bytes live changes. The
|
|
53
56
|
* driver is built on the published `@orkestrel/indexeddb` wrapper in auto-managed
|
|
54
57
|
* mode, so a table added to the `tables` map is created on the next open with no
|
|
55
58
|
* version bump. This unit omits `transaction` / `aggregate` (see
|
|
@@ -97,13 +100,13 @@ export declare function createIndexedDBDriver(name: string): DriverInterface_2;
|
|
|
97
100
|
*
|
|
98
101
|
* @example
|
|
99
102
|
* ```ts
|
|
100
|
-
*
|
|
101
|
-
*
|
|
103
|
+
* deriveIndexedDBIndexName(['age']) // 'age'
|
|
104
|
+
* deriveIndexedDBIndexName(['a', 'b']) // '2#1:a1:b'
|
|
102
105
|
* ```
|
|
103
106
|
*/
|
|
104
|
-
export declare function
|
|
107
|
+
export declare function deriveIndexedDBIndexName(columns: readonly string[]): string;
|
|
105
108
|
|
|
106
|
-
export declare const
|
|
109
|
+
export declare const INDEXABLE_STORAGE: ReadonlySet<ColumnStorage>;
|
|
107
110
|
|
|
108
111
|
/**
|
|
109
112
|
* The IndexedDB {@link DriverInterface} — the persistent browser backend, built on
|
|
@@ -115,25 +118,27 @@ export declare const INDEXABLE_TYPES: ReadonlySet<ColumnType>;
|
|
|
115
118
|
* / `snapshot`) by delegating to the wrapper's typed store operations — it never
|
|
116
119
|
* touches raw IndexedDB. Rows are stored with **out-of-line keys** (the database
|
|
117
120
|
* passes the key explicitly, `store.set(row, key)`), so each table is declared as a
|
|
118
|
-
* key-path-less store.
|
|
119
|
-
* version), creating
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
121
|
+
* key-path-less store. A fresh database opens in **auto-managed** mode (no fixed
|
|
122
|
+
* version), creating missing declared stores on demand. Once metadata is persisted,
|
|
123
|
+
* bootstrap captures the live stores and version, rejects a missing persisted store,
|
|
124
|
+
* and pins the final open to that version so a competing versionchange cannot
|
|
125
|
+
* silently recreate lost storage. The driver's bulk reads (`scan` / `keys`) use the
|
|
126
|
+
* wrapper's native `getAll` / `getAllKeys`, and `snapshot` rolls back through one
|
|
127
|
+
* atomic wrapper transaction.
|
|
123
128
|
*
|
|
124
|
-
* It also implements the optional native `records` / `
|
|
125
|
-
* (AGENTS §21): `selectPlan` ({@link selectPlan}) turns the {@link
|
|
129
|
+
* It also implements the optional native `records` / `stream` hooks
|
|
130
|
+
* (AGENTS §21): `selectPlan` ({@link selectPlan}) turns the {@link QueryInput} into a
|
|
126
131
|
* key-range pushdown over the primary key or a single-column secondary index,
|
|
127
|
-
* fetching a candidate **superset** that the core engine (`
|
|
128
|
-
* `
|
|
132
|
+
* fetching a candidate **superset** that the core engine (`applyQuery` /
|
|
133
|
+
* `matchesQuery`) then refines — so a native read is byte-identical to a full
|
|
129
134
|
* scan, just cheaper. Pushdown is conservative: only the exact-comparison
|
|
130
135
|
* operators over orderable columns narrow to a range; everything else falls back
|
|
131
136
|
* to a full scan + the engine.
|
|
132
137
|
*
|
|
133
138
|
* @remarks
|
|
134
|
-
* This driver also implements `migrate` / `
|
|
135
|
-
* persist the {@link
|
|
136
|
-
* {@link
|
|
139
|
+
* This driver also implements `migrate` / `metadata` / `stamp`. `metadata` / `stamp`
|
|
140
|
+
* persist the {@link DriverMetadata} in a reserved out-of-line store,
|
|
141
|
+
* {@link METADATA_STORE} (`__metadata__`) — excluded from a whole-store `snapshot`
|
|
137
142
|
* capture, since it is driver bookkeeping, not caller data. `migrate` applies a
|
|
138
143
|
* {@link Migration} plan natively: IndexedDB schema DDL (creating/dropping a
|
|
139
144
|
* store, creating/dropping an index) is legal only inside a versionchange
|
|
@@ -150,9 +155,9 @@ export declare const INDEXABLE_TYPES: ReadonlySet<ColumnType>;
|
|
|
150
155
|
* This unit deliberately OMITS `aggregate` / `transaction`. There is no native
|
|
151
156
|
* `aggregate` (IndexedDB has no native SUM/AVG); the engine over the narrowed
|
|
152
157
|
* `records` covers it. `transaction` is impossible here: the wrapper auto-commits
|
|
153
|
-
* an `IDBTransaction`
|
|
154
|
-
*
|
|
155
|
-
*
|
|
158
|
+
* an `IDBTransaction` when control yields outside its request chain, so arbitrary
|
|
159
|
+
* callback awaits cannot remain inside one native transaction. Every atomic
|
|
160
|
+
* multi-operation sequence in this driver
|
|
156
161
|
* (`snapshot`'s rollback) instead runs entirely inside ONE `db.write(...)` scope.
|
|
157
162
|
*/
|
|
158
163
|
export declare class IndexedDBDriver implements DriverInterface {
|
|
@@ -161,34 +166,34 @@ export declare class IndexedDBDriver implements DriverInterface {
|
|
|
161
166
|
open(schema: readonly TableSchema_2[]): Promise<void>;
|
|
162
167
|
close(): Promise<void>;
|
|
163
168
|
read(table: string, key: Key): Promise<Row | undefined>;
|
|
164
|
-
write(table: string, key: Key, row: Row): Promise<void>;
|
|
165
|
-
|
|
169
|
+
write(table: string, key: Key, row: Row, options?: OperationOptions): Promise<void>;
|
|
170
|
+
insert(table: string, key: Key, row: Row, options?: OperationOptions): Promise<void>;
|
|
171
|
+
delete(table: string, key: Key, options?: OperationOptions): Promise<boolean>;
|
|
166
172
|
keys(table: string): Promise<readonly Key[]>;
|
|
167
173
|
scan(table: string): AsyncIterable<Row>;
|
|
168
174
|
clear(table: string): Promise<void>;
|
|
169
|
-
records(table: string,
|
|
170
|
-
|
|
171
|
-
stream(table: string, criteria: Criteria_2): AsyncIterable<Row>;
|
|
175
|
+
records(table: string, input: QueryInput_2): Promise<readonly Row[]>;
|
|
176
|
+
stream(table: string, input: QueryInput_2): AsyncIterable<Row>;
|
|
172
177
|
snapshot(tables?: readonly string[]): Promise<() => Promise<void>>;
|
|
173
178
|
/**
|
|
174
|
-
* Return the persisted {@link
|
|
179
|
+
* Return the persisted {@link DriverMetadata}, or `undefined` when the store has
|
|
175
180
|
* never been stamped.
|
|
176
181
|
*
|
|
177
182
|
* @remarks
|
|
178
|
-
* Reads `'
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
183
|
+
* Reads `'metadata'` from the reserved {@link METADATA_STORE} in one readonly
|
|
184
|
+
* transaction that distinguishes key absence from a present `undefined`
|
|
185
|
+
* value. Only absence returns `undefined`; present malformed state fails
|
|
186
|
+
* closed with a payload-safe `DRIVER` error.
|
|
182
187
|
*
|
|
183
|
-
* @returns The last-stamped {@link
|
|
188
|
+
* @returns The last-stamped {@link DriverMetadata}, or `undefined`
|
|
184
189
|
*/
|
|
185
|
-
|
|
190
|
+
metadata(): Promise<DriverMetadata | undefined>;
|
|
186
191
|
/**
|
|
187
|
-
* Persist
|
|
192
|
+
* Persist an owned metadata snapshot for a later `metadata()` to return.
|
|
188
193
|
*
|
|
189
|
-
* @param
|
|
194
|
+
* @param metadata - The {@link DriverMetadata} to persist
|
|
190
195
|
*/
|
|
191
|
-
stamp(
|
|
196
|
+
stamp(metadata: DriverMetadata): Promise<void>;
|
|
192
197
|
/**
|
|
193
198
|
* Apply a {@link Migration} plan by reconnecting at a bumped version and
|
|
194
199
|
* running the plan's steps inside the wrapper's `upgrade` hook.
|
|
@@ -196,7 +201,7 @@ export declare class IndexedDBDriver implements DriverInterface {
|
|
|
196
201
|
* @remarks
|
|
197
202
|
* IndexedDB schema DDL is legal only inside `onupgradeneeded`, so this closes
|
|
198
203
|
* the current connection and opens a FRESH one at `version + 1`, declaring
|
|
199
|
-
* every currently-known store (plus {@link
|
|
204
|
+
* every currently-known store (plus {@link METADATA_STORE}) so nothing is lost,
|
|
200
205
|
* and applying `table.remove` / `index.add` / `index.remove` /
|
|
201
206
|
* `column.remove` inside `upgrade`. Every step's `table` is validated against
|
|
202
207
|
* the driver's own `#schema` BEFORE the reconnect — an unknown-table step
|
|
@@ -209,13 +214,11 @@ export declare class IndexedDBDriver implements DriverInterface {
|
|
|
209
214
|
* `open` tracks, so subsequent pushdown planning and a later `migrate` /
|
|
210
215
|
* `open` see the new shape.
|
|
211
216
|
*
|
|
212
|
-
* @param
|
|
217
|
+
* @param input - The migration plan and optional metadata stamp to apply atomically
|
|
213
218
|
*/
|
|
214
|
-
migrate(
|
|
219
|
+
migrate(input: MigrationInput): Promise<void>;
|
|
215
220
|
}
|
|
216
221
|
|
|
217
|
-
export declare function isKey(value: unknown): value is string | number;
|
|
218
|
-
|
|
219
222
|
/**
|
|
220
223
|
* Map a backend {@link IndexedDBError} to the portable `DatabaseError` taxonomy
|
|
221
224
|
* — the default mapping used everywhere except inside `migrate()`.
|
|
@@ -225,17 +228,17 @@ export declare function isKey(value: unknown): value is string | number;
|
|
|
225
228
|
* `CONSTRAINT` (a unique-key violation) is a `CONFLICT` — the same code every
|
|
226
229
|
* other backend uses for a duplicate key. `CLOSED`/`NOT_OPEN`/`INVALID` (the
|
|
227
230
|
* connection is gone, never opened, or the native handle is stale) collapse to
|
|
228
|
-
* `CLOSED`. `QUOTA`
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
* `
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
231
|
+
* `CLOSED`. `QUOTA` is a genuine infrastructure fault (`DRIVER`) carrying a
|
|
232
|
+
* machine-readable `context.code` so a caller can branch without parsing the
|
|
233
|
+
* message. A blocked open or versionchange is nonterminal in the backend and
|
|
234
|
+
* remains pending until the competing connection closes, so it never reaches
|
|
235
|
+
* this error mapper. Every other code (`UPGRADE` here — see
|
|
236
|
+
* {@link mapMigrationError} for the `migrate()`-only remapping to `MIGRATION` —
|
|
237
|
+
* `ABORTED`, `NOT_FOUND`, `DATA`, `OPEN`, `INACTIVE`, `READONLY`, `UNKNOWN`) is
|
|
238
|
+
* an unexpected infrastructure fault and maps to `DRIVER` — the driver opens
|
|
239
|
+
* its own readwrite transactions, so a `READONLY` fault can only mean the
|
|
240
|
+
* backend behaved unexpectedly. The original error is always preserved as
|
|
241
|
+
* `context.cause` for diagnostics.
|
|
239
242
|
*
|
|
240
243
|
* @param error - The backend error to translate
|
|
241
244
|
* @returns The portable `DatabaseError`
|
|
@@ -260,20 +263,30 @@ export declare function mapIndexedDBError(error: IndexedDBError): DatabaseError;
|
|
|
260
263
|
*/
|
|
261
264
|
export declare function mapMigrationError(error: IndexedDBError): DatabaseError;
|
|
262
265
|
|
|
263
|
-
export declare const
|
|
266
|
+
export declare const METADATA_STORE = "__metadata__";
|
|
264
267
|
|
|
265
268
|
/**
|
|
266
|
-
* A pushdown plan —
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
+
* A pushdown plan — an optional index and optional `IDBKeyRange` used to narrow
|
|
270
|
+
* a read. An omitted `index` selects the primary store; an omitted `range`
|
|
271
|
+
* performs a full scan. The plan is always a superset of the matching rows;
|
|
272
|
+
* the core engine refines it to the exact result. An empty plan (`{}`) is a
|
|
273
|
+
* primary-store full scan.
|
|
269
274
|
*/
|
|
270
275
|
export declare interface QueryPlan {
|
|
271
|
-
readonly index
|
|
272
|
-
readonly range
|
|
276
|
+
readonly index?: string;
|
|
277
|
+
readonly range?: IDBKeyRange;
|
|
273
278
|
}
|
|
274
279
|
|
|
275
280
|
/**
|
|
276
|
-
*
|
|
281
|
+
* Project a table schema into the IndexedDB wrapper's store definition.
|
|
282
|
+
*
|
|
283
|
+
* @param schema - Portable table schema
|
|
284
|
+
* @returns Store definition with declared indexes
|
|
285
|
+
*/
|
|
286
|
+
export declare function schemaToStore(schema: TableSchema): StoreDefinition;
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Plan an IndexedDB read for a {@link QueryInput} — pick the index (or the primary
|
|
277
290
|
* store) and {@link IDBKeyRange} to narrow by, falling back to a full scan.
|
|
278
291
|
*
|
|
279
292
|
* @remarks
|
|
@@ -283,9 +296,9 @@ export declare interface QueryPlan {
|
|
|
283
296
|
* (a row can match through a later condition the range would exclude), so any `or`
|
|
284
297
|
* forces a full scan. Otherwise it scans the conditions in order and selects the
|
|
285
298
|
* **first** one that is provably range-exact and backed by a key: a comparison
|
|
286
|
-
* operator (`
|
|
287
|
-
* column that is either the table's primary key (read the store directly
|
|
288
|
-
*
|
|
299
|
+
* operator (`conditionToRange`) over a single, orderable (`text`/`integer`/`real`)
|
|
300
|
+
* column that is either the table's primary key (read the store directly with
|
|
301
|
+
* `index` omitted) or has a single-column secondary index (named exactly the column — read
|
|
289
302
|
* that index). A condition whose column is a nested {@link FieldPath} array
|
|
290
303
|
* (descends a json value, not a key), is absent from the schema, is a non-orderable
|
|
291
304
|
* type (`boolean`/`json`/`blob`), uses a non-comparison operator, or has a
|
|
@@ -311,19 +324,19 @@ export declare interface QueryPlan {
|
|
|
311
324
|
* in the total order, so an absent/null-valued row can never satisfy them — the
|
|
312
325
|
* index's silence on such a row is harmless (it was never going to match).
|
|
313
326
|
* **Declared-type trust caveat:** this reasoning holds under the contract that
|
|
314
|
-
* an {@link
|
|
327
|
+
* an {@link INDEXABLE_STORAGE} column, once contract-validated at write time,
|
|
315
328
|
* holds only `string | number | null` (or is absent) — never some other
|
|
316
329
|
* runtime value that could rank differently; a driver bypassing the write
|
|
317
330
|
* contract (writing raw rows directly to the store) could defeat this
|
|
318
331
|
* argument, but that is out of scope for a planner reading validated schema
|
|
319
332
|
* metadata.
|
|
320
333
|
*
|
|
321
|
-
* When no condition qualifies the plan is a full scan (`{
|
|
322
|
-
*
|
|
334
|
+
* When no condition qualifies the plan is a full scan (`{}`) and the engine
|
|
335
|
+
* does everything. The plan is always a SUPERSET of the
|
|
323
336
|
* matching rows — the only correctness contract — so the driver may safely run
|
|
324
337
|
* the exact engine over it.
|
|
325
338
|
*
|
|
326
|
-
* @param
|
|
339
|
+
* @param input - The read specification (its `conditions` drive the plan), or
|
|
327
340
|
* `undefined` for an unconditional read
|
|
328
341
|
* @param schema - The table's schema — its `primary` key and column types
|
|
329
342
|
* @param available - The secondary-index names that physically exist on the store
|
|
@@ -332,11 +345,11 @@ export declare interface QueryPlan {
|
|
|
332
345
|
*
|
|
333
346
|
* @example
|
|
334
347
|
* ```ts
|
|
335
|
-
* selectPlan({ conditions: [eq('id', 'u1')] }, schema, []) // {
|
|
348
|
+
* selectPlan({ conditions: [eq('id', 'u1')] }, schema, []) // { range: only('u1') }
|
|
336
349
|
* selectPlan({ conditions: [from('age', 18)] }, schema, ['age']) // { index: 'age', range: from(18) }
|
|
337
|
-
* selectPlan({ conditions: [contains('name', 'a')] }, schema, []) // {
|
|
350
|
+
* selectPlan({ conditions: [contains('name', 'a')] }, schema, []) // {}
|
|
338
351
|
* ```
|
|
339
352
|
*/
|
|
340
|
-
export declare function selectPlan(
|
|
353
|
+
export declare function selectPlan(input: QueryInput | undefined, schema: TableSchema, available: readonly string[]): QueryPlan;
|
|
341
354
|
|
|
342
355
|
export { }
|