@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
|
@@ -1,31 +1,52 @@
|
|
|
1
|
-
import { DatabaseError, applyQuery, bindRowKey, checkAbort, cloneDriverMetadata, cloneMigrationInput, compareValues, equalsValue, extractKey, isDatabaseError, isKey, matchesQuery, migrateRows, normalizeDriverSchema, planMigration, projectMigrationSchema, validatePage } from "../core/index.js";
|
|
2
|
-
import { createIndexedDBDatabase, isIndexedDBError, rangeAboveKey, rangeBelowKey,
|
|
1
|
+
import { DatabaseError, applyQuery, bindRowKey, checkAbort, cloneDriverMetadata, cloneMigrationInput, compareValues, equalsValue, extractKey, findColumn, isDatabaseError, isKey, matchesQuery, migrateRows, normalizeDriverSchema, planMigration, projectMigrationSchema, validatePage } from "../core/index.js";
|
|
2
|
+
import { createIndexedDBDatabase, isIndexedDBError, rangeAboveKey, rangeBelowKey, rangeFromKey, rangeToKey } from "@orkestrel/indexeddb";
|
|
3
3
|
//#region src/browser/constants.ts
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Lists the declared {@link ColumnStorage}s that are valid, orderable IndexedDB keys.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* `text` / `integer` / `real` occupy IndexedDB's string / number key space, so a
|
|
9
|
+
* column declared with one of them can back a store or index range read.
|
|
10
|
+
* `boolean` / `json` / `blob` are not valid `IDBValidKey`s and a range over one
|
|
11
|
+
* would silently miss rows, so `selectPlan` never pushes a condition down on
|
|
12
|
+
* them and the core engine answers the read instead. A frozen array, matching
|
|
13
|
+
* `EXACT_COLUMN_STORAGE` in `src/server`: a consumer holding it reads the
|
|
14
|
+
* membership with `includes` and cannot change the driver's pushdown behavior.
|
|
15
|
+
*/
|
|
16
|
+
var INDEXABLE_STORAGE = Object.freeze([
|
|
5
17
|
"text",
|
|
6
18
|
"integer",
|
|
7
19
|
"real"
|
|
8
20
|
]);
|
|
21
|
+
/**
|
|
22
|
+
* Names the reserved out-of-line store `__metadata__` the {@link IndexedDBDriver}
|
|
23
|
+
* stamps its {@link DriverMetadata} into.
|
|
24
|
+
*
|
|
25
|
+
* @remarks
|
|
26
|
+
* Backs the driver's `metadata` / `stamp` hooks. A user table named `__metadata__`
|
|
27
|
+
* collides with the driver's own bookkeeping, so a caller must avoid it; the
|
|
28
|
+
* collision is caught at `open`.
|
|
29
|
+
*/
|
|
9
30
|
var METADATA_STORE = "__metadata__";
|
|
10
31
|
//#endregion
|
|
11
32
|
//#region src/browser/helpers.ts
|
|
12
33
|
/**
|
|
13
|
-
*
|
|
14
|
-
* of the
|
|
15
|
-
* `undefined`.
|
|
34
|
+
* Translates one {@link Condition} to the `IDBKeyRange` it maps to, when its
|
|
35
|
+
* operator is one of the exact key comparisons over scalar operands; otherwise
|
|
36
|
+
* returns `undefined`.
|
|
16
37
|
*
|
|
17
38
|
* @remarks
|
|
18
39
|
* Only the comparison operators (`equals`/`above`/`below`/`from`/`to`/`between`)
|
|
19
40
|
* translate to a key range that a typed (string/number) column can back with an
|
|
20
41
|
* IndexedDB store/index read — see {@link selectPlan} for the caveats that
|
|
21
|
-
* decide
|
|
42
|
+
* decide which of `below`/`to` may drive a secondary-index read versus the
|
|
22
43
|
* primary store only (a column-type / absent-row concern, not a range-shape
|
|
23
44
|
* one). `starts` is excluded — its prefix range can miss strings past U+FFFF;
|
|
24
45
|
* the membership / negation / pattern / existence operators (`not`/`like`/`glob`/
|
|
25
46
|
* `ends`/`any`/`none`/`absent`/`present`) have no single exact range. The operand
|
|
26
|
-
* guard (`typeof` string/number) rejects a non-scalar value (
|
|
47
|
+
* guard (`typeof` string/number) rejects a non-scalar value (for example an array, a
|
|
27
48
|
* boolean) that is not a usable key. `between` additionally guards against a
|
|
28
|
-
*
|
|
49
|
+
* reversed pair (`first > second`): native `IDBKeyRange.bound` throws a raw
|
|
29
50
|
* `DataError` `DOMException` for a lower bound above the upper bound, so a
|
|
30
51
|
* reversed pair returns `undefined` here (falls back to a full scan, which the
|
|
31
52
|
* engine then correctly resolves to an empty result) rather than letting a
|
|
@@ -42,12 +63,12 @@ function conditionToRange(condition) {
|
|
|
42
63
|
const first = condition.values[0];
|
|
43
64
|
const second = condition.values[1];
|
|
44
65
|
switch (condition.operator) {
|
|
45
|
-
case "equals": return isKey(first) ?
|
|
66
|
+
case "equals": return isKey(first) ? IDBKeyRange.only(first) : void 0;
|
|
46
67
|
case "above": return isKey(first) ? rangeAboveKey(first) : void 0;
|
|
47
68
|
case "below": return isKey(first) ? rangeBelowKey(first) : void 0;
|
|
48
69
|
case "from": return isKey(first) ? rangeFromKey(first) : void 0;
|
|
49
70
|
case "to": return isKey(first) ? rangeToKey(first) : void 0;
|
|
50
|
-
case "between": return isKey(first) && isKey(second) && compareValues(first, second) <= 0 ?
|
|
71
|
+
case "between": return isKey(first) && isKey(second) && compareValues(first, second) <= 0 ? IDBKeyRange.bound(first, second) : void 0;
|
|
51
72
|
case "not":
|
|
52
73
|
case "like":
|
|
53
74
|
case "glob":
|
|
@@ -60,11 +81,11 @@ function conditionToRange(condition) {
|
|
|
60
81
|
}
|
|
61
82
|
}
|
|
62
83
|
/**
|
|
63
|
-
*
|
|
84
|
+
* Plans an IndexedDB read for a {@link QueryInput} — picks the index (or the primary
|
|
64
85
|
* store) and {@link IDBKeyRange} to narrow by, falling back to a full scan.
|
|
65
86
|
*
|
|
66
87
|
* @remarks
|
|
67
|
-
* Pushdown is sound
|
|
88
|
+
* Pushdown is sound only when every condition is `and`-joined: the engine folds
|
|
68
89
|
* conditions left-to-right (`c1 && c2 && … && cn`), so the result is a subset of
|
|
69
90
|
* each — narrowing on any one is then a valid superset. A single `or` breaks that
|
|
70
91
|
* (a row can match through a later condition the range would exclude), so any `or`
|
|
@@ -78,21 +99,21 @@ function conditionToRange(condition) {
|
|
|
78
99
|
* type (`boolean`/`json`/`blob`), uses a non-comparison operator, or has a
|
|
79
100
|
* non-scalar operand cannot push and is skipped.
|
|
80
101
|
*
|
|
81
|
-
* **`below`/`to` may drive a
|
|
102
|
+
* **`below`/`to` may drive a secondary-index range only when the column has no
|
|
82
103
|
* absent/null rows to lose — which this planner cannot verify from the schema
|
|
83
|
-
* alone, so it restricts them to the
|
|
104
|
+
* alone, so it restricts them to the primary store, where that is always true.**
|
|
84
105
|
* The engine's total order (`compareValues`, see `@src/core`) ranks
|
|
85
|
-
* `undefined` (absent) and `null`
|
|
86
|
-
* `matchesCondition('below' | 'to', …)` is
|
|
87
|
-
* or `null` — but a secondary IndexedDB index has
|
|
106
|
+
* `undefined` (absent) and `null` below every number/string, so
|
|
107
|
+
* `matchesCondition('below' | 'to', …)` is true for a row whose field is absent
|
|
108
|
+
* or `null` — but a secondary IndexedDB index has no entry for a row whose
|
|
88
109
|
* indexed field is absent/`null`, so a `below`/`to` range read against that
|
|
89
|
-
* index would
|
|
110
|
+
* index would silently drop those rows (they can never be over-fetched, only
|
|
90
111
|
* missed — the one shape of lossiness this planner must never produce). The
|
|
91
|
-
* table's
|
|
112
|
+
* table's primary key is exempt: a row's primary-key value is always present
|
|
92
113
|
* and never `null` (it is the row's identity, enforced at write time), so a
|
|
93
114
|
* `below`/`to` range against the primary store can never exclude an
|
|
94
115
|
* absent/null-keyed row because no such row exists. `equals`/`above`/`from`/
|
|
95
|
-
* `between` stay index-eligible on
|
|
116
|
+
* `between` stay index-eligible on any orderable column, primary or secondary:
|
|
96
117
|
* each is bounded below by a scalar (`equals`/`between`'s lower bound, `above`/
|
|
97
118
|
* `from`'s lower bound), and every scalar strictly out-ranks `undefined`/`null`
|
|
98
119
|
* in the total order, so an absent/null-valued row can never satisfy them — the
|
|
@@ -106,7 +127,7 @@ function conditionToRange(condition) {
|
|
|
106
127
|
* metadata.
|
|
107
128
|
*
|
|
108
129
|
* When no condition qualifies the plan is a full scan (`{}`) and the engine
|
|
109
|
-
* does everything. The plan is always a
|
|
130
|
+
* does everything. The plan is always a superset of the
|
|
110
131
|
* matching rows — the only correctness contract — so the driver may safely run
|
|
111
132
|
* the exact engine over it.
|
|
112
133
|
*
|
|
@@ -129,8 +150,8 @@ function selectPlan(input, schema, available) {
|
|
|
129
150
|
if (conditions.slice(1).some((condition) => condition.connector === "or")) return {};
|
|
130
151
|
for (const condition of conditions) {
|
|
131
152
|
if (typeof condition.column !== "string") continue;
|
|
132
|
-
const column =
|
|
133
|
-
if (column === void 0 || !INDEXABLE_STORAGE.
|
|
153
|
+
const column = findColumn(condition.column, schema);
|
|
154
|
+
if (column === void 0 || !INDEXABLE_STORAGE.includes(column.storage)) continue;
|
|
134
155
|
const range = conditionToRange(condition);
|
|
135
156
|
if (range === void 0) continue;
|
|
136
157
|
if (condition.column === schema.primary) return { range };
|
|
@@ -143,7 +164,7 @@ function selectPlan(input, schema, available) {
|
|
|
143
164
|
return {};
|
|
144
165
|
}
|
|
145
166
|
/**
|
|
146
|
-
*
|
|
167
|
+
* Maps a backend {@link IndexedDBError} to the portable `DatabaseError` taxonomy
|
|
147
168
|
* — the default mapping used everywhere except inside `migrate()`.
|
|
148
169
|
*
|
|
149
170
|
* @remarks
|
|
@@ -187,8 +208,8 @@ function mapIndexedDBError(error) {
|
|
|
187
208
|
}
|
|
188
209
|
}
|
|
189
210
|
/**
|
|
190
|
-
*
|
|
191
|
-
* for use
|
|
211
|
+
* Maps a backend {@link IndexedDBError} to the portable `DatabaseError` taxonomy
|
|
212
|
+
* for use inside `migrate()` — the one context where `UPGRADE` means the
|
|
192
213
|
* migration itself failed, not a generic driver fault.
|
|
193
214
|
*
|
|
194
215
|
* @remarks
|
|
@@ -207,20 +228,20 @@ function mapMigrationError(error) {
|
|
|
207
228
|
return mapIndexedDBError(error);
|
|
208
229
|
}
|
|
209
230
|
/**
|
|
210
|
-
*
|
|
231
|
+
* Derives an IndexedDB index name for a declared column group — a bare column
|
|
211
232
|
* name for a single-column index, a deterministic collision-free encoding for a
|
|
212
233
|
* compound one.
|
|
213
234
|
*
|
|
214
235
|
* @remarks
|
|
215
236
|
* Naming a compound index by joining its columns with `_` (`['a', 'b'] →
|
|
216
|
-
* 'a_b'`) collides with a single-column index over a column
|
|
237
|
+
* 'a_b'`) collides with a single-column index over a column literally named
|
|
217
238
|
* `'a_b'` — the same name, two different key paths (`'a_b'` vs `['a', 'b']`),
|
|
218
239
|
* which either throws a native `ConstraintError` from a duplicate
|
|
219
240
|
* `createIndex` call at open, or (worse) lets {@link selectPlan}'s name-based
|
|
220
|
-
* lookup match the wrong index. A single-column index keeps the
|
|
241
|
+
* lookup match the wrong index. A single-column index keeps the bare column
|
|
221
242
|
* name — {@link selectPlan} matches `available.includes(condition.column)` by
|
|
222
243
|
* that exact name, so a single-column index must stay named after its column
|
|
223
|
-
* verbatim. A compound index instead encodes each column as a
|
|
244
|
+
* verbatim. A compound index instead encodes each column as a length-prefixed
|
|
224
245
|
* segment (`'2#1:a1:b'`), so the boundary between columns is self-describing
|
|
225
246
|
* and cannot be reconstructed by any other column list — including one
|
|
226
247
|
* containing a column that happens to look like an encoded segment.
|
|
@@ -240,7 +261,12 @@ function deriveIndexedDBIndexName(columns) {
|
|
|
240
261
|
return `${columns.length}#${columns.map((part) => `${part.length}:${part}`).join("")}`;
|
|
241
262
|
}
|
|
242
263
|
/**
|
|
243
|
-
*
|
|
264
|
+
* Projects a table schema into the IndexedDB wrapper's store definition.
|
|
265
|
+
*
|
|
266
|
+
* @remarks
|
|
267
|
+
* The definition is what an ordered versionchange migration creates the store
|
|
268
|
+
* from, carrying each declared index under the name {@link deriveIndexedDBIndexName}
|
|
269
|
+
* derives for its column group.
|
|
244
270
|
*
|
|
245
271
|
* @param schema - Portable table schema
|
|
246
272
|
* @returns Store definition with declared indexes
|
|
@@ -257,8 +283,8 @@ function schemaToStore(schema) {
|
|
|
257
283
|
//#endregion
|
|
258
284
|
//#region src/browser/drivers/IndexedDBDriver.ts
|
|
259
285
|
/**
|
|
260
|
-
*
|
|
261
|
-
* the published `@orkestrel/indexeddb` wrapper.
|
|
286
|
+
* Implements the {@link DriverInterface} over IndexedDB — the persistent browser backend,
|
|
287
|
+
* built on the published `@orkestrel/indexeddb` wrapper.
|
|
262
288
|
*
|
|
263
289
|
* @remarks
|
|
264
290
|
* A thin adapter: it implements the storage primitives the core database layer
|
|
@@ -274,12 +300,12 @@ function schemaToStore(schema) {
|
|
|
274
300
|
* wrapper's native `getAll` / `getAllKeys`, and `snapshot` rolls back through one
|
|
275
301
|
* atomic wrapper transaction.
|
|
276
302
|
*
|
|
277
|
-
* It also implements the optional native `records` / `stream` hooks
|
|
278
|
-
*
|
|
303
|
+
* It also implements the optional native `records` / `stream` hooks:
|
|
304
|
+
* `selectPlan` ({@link selectPlan}) turns the {@link QueryInput} into a
|
|
279
305
|
* key-range pushdown over the primary key or a single-column secondary index,
|
|
280
306
|
* fetching a candidate **superset** that the core engine (`applyQuery` /
|
|
281
307
|
* `matchesQuery`) then refines — so a native read is byte-identical to a full
|
|
282
|
-
* scan,
|
|
308
|
+
* scan, only cheaper. Pushdown is conservative: only the exact-comparison
|
|
283
309
|
* operators over orderable columns narrow to a range; everything else falls back
|
|
284
310
|
* to a full scan + the engine.
|
|
285
311
|
*
|
|
@@ -291,22 +317,23 @@ function schemaToStore(schema) {
|
|
|
291
317
|
* {@link Migration} plan natively: IndexedDB schema DDL (creating/dropping a
|
|
292
318
|
* store, creating/dropping an index) is legal only inside a versionchange
|
|
293
319
|
* transaction (`onupgradeneeded`), so `migrate` closes the current connection
|
|
294
|
-
* and opens a
|
|
320
|
+
* and opens a fresh one at `version + 1` with an `upgrade` hook that walks the
|
|
295
321
|
* plan's steps — dropping stores, adding/removing indexes on the raw
|
|
296
|
-
* `IDBTransaction`, and rewriting rows for `column.remove`
|
|
322
|
+
* `IDBTransaction`, and rewriting rows for `column.remove` through a cursor walk
|
|
297
323
|
* (the one step needing to touch existing data; `column.add` is a no-op — this
|
|
298
324
|
* driver stores whatever a row carries, so there is nothing to backfill). A
|
|
299
|
-
* step referencing an unknown table is validated
|
|
325
|
+
* step referencing an unknown table is validated before the reconnect, so a
|
|
300
326
|
* `MIGRATION` `DatabaseError` never wastes a version bump.
|
|
301
327
|
*
|
|
302
328
|
* @remarks
|
|
303
|
-
* This unit deliberately
|
|
329
|
+
* This unit deliberately omits `aggregate` / `transaction`. There is no native
|
|
304
330
|
* `aggregate` (IndexedDB has no native SUM/AVG); the engine over the narrowed
|
|
305
|
-
* `records` covers it. `transaction` is impossible here: the wrapper
|
|
306
|
-
* an `IDBTransaction` when control yields outside its request
|
|
307
|
-
* callback awaits cannot remain inside one native
|
|
308
|
-
* multi-operation sequence in this driver
|
|
309
|
-
* (`snapshot`'s rollback) instead runs entirely inside
|
|
331
|
+
* `records` covers it. `transaction` is impossible here: the wrapper
|
|
332
|
+
* auto-commits an `IDBTransaction` when control yields outside its request
|
|
333
|
+
* chain, so arbitrary callback awaits cannot remain inside one native
|
|
334
|
+
* transaction. Every atomic multi-operation sequence in this driver
|
|
335
|
+
* (`snapshot`'s rollback) instead runs entirely inside one `db.write(...)`
|
|
336
|
+
* scope.
|
|
310
337
|
*/
|
|
311
338
|
var IndexedDBDriver = class {
|
|
312
339
|
#name;
|
|
@@ -434,30 +461,6 @@ var IndexedDBDriver = class {
|
|
|
434
461
|
validatePage(input);
|
|
435
462
|
return this.#stream(table, input);
|
|
436
463
|
}
|
|
437
|
-
async *#stream(table, input) {
|
|
438
|
-
try {
|
|
439
|
-
const schema = this.#table(table);
|
|
440
|
-
const store = this.#store(table);
|
|
441
|
-
const plan = selectPlan(input, schema, store.indexes);
|
|
442
|
-
const conditions = input.conditions ?? [];
|
|
443
|
-
const offset = input.offset ?? 0;
|
|
444
|
-
const limit = input.limit;
|
|
445
|
-
let skipped = 0;
|
|
446
|
-
let yielded = 0;
|
|
447
|
-
for (const row of await this.#candidates(store, schema, plan)) {
|
|
448
|
-
if (limit !== void 0 && yielded >= limit) break;
|
|
449
|
-
if (!matchesQuery(row, conditions)) continue;
|
|
450
|
-
if (skipped < offset) {
|
|
451
|
-
skipped += 1;
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
yielded += 1;
|
|
455
|
-
yield row;
|
|
456
|
-
}
|
|
457
|
-
} catch (error) {
|
|
458
|
-
throw this.#wrap(error);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
464
|
async snapshot(tables) {
|
|
462
465
|
try {
|
|
463
466
|
const database = this.#require();
|
|
@@ -529,7 +532,7 @@ var IndexedDBDriver = class {
|
|
|
529
532
|
}
|
|
530
533
|
}
|
|
531
534
|
/**
|
|
532
|
-
*
|
|
535
|
+
* Returns the persisted {@link DriverMetadata}, or `undefined` when the store has
|
|
533
536
|
* never been stamped.
|
|
534
537
|
*
|
|
535
538
|
* @remarks
|
|
@@ -548,7 +551,7 @@ var IndexedDBDriver = class {
|
|
|
548
551
|
}
|
|
549
552
|
}
|
|
550
553
|
/**
|
|
551
|
-
*
|
|
554
|
+
* Persists an owned metadata snapshot for a later `metadata()` to return.
|
|
552
555
|
*
|
|
553
556
|
* @param metadata - The {@link DriverMetadata} to persist
|
|
554
557
|
*/
|
|
@@ -565,16 +568,16 @@ var IndexedDBDriver = class {
|
|
|
565
568
|
}
|
|
566
569
|
}
|
|
567
570
|
/**
|
|
568
|
-
*
|
|
571
|
+
* Applies a {@link Migration} plan by reconnecting at a bumped version and
|
|
569
572
|
* running the plan's steps inside the wrapper's `upgrade` hook.
|
|
570
573
|
*
|
|
571
574
|
* @remarks
|
|
572
575
|
* IndexedDB schema DDL is legal only inside `onupgradeneeded`, so this closes
|
|
573
|
-
* the current connection and opens a
|
|
574
|
-
* every
|
|
575
|
-
* and applying `table.remove` / `index.add` / `index.remove` /
|
|
576
|
+
* the current connection and opens a fresh one at `version + 1`, declaring
|
|
577
|
+
* every store known at that point (plus {@link METADATA_STORE}) so nothing is
|
|
578
|
+
* lost, and applying `table.remove` / `index.add` / `index.remove` /
|
|
576
579
|
* `column.remove` inside `upgrade`. Every step's `table` is validated against
|
|
577
|
-
* the driver's own `#schema`
|
|
580
|
+
* the driver's own `#schema` before the reconnect — an unknown-table step
|
|
578
581
|
* throws `DatabaseError` `MIGRATION` without ever bumping the version.
|
|
579
582
|
* `table.add` / `column.add` need no upgrade-time action: `table.add` is
|
|
580
583
|
* created by the wrapper's built-in create-missing-stores pass (its
|
|
@@ -647,6 +650,30 @@ var IndexedDBDriver = class {
|
|
|
647
650
|
throw cause;
|
|
648
651
|
}
|
|
649
652
|
}
|
|
653
|
+
async *#stream(table, input) {
|
|
654
|
+
try {
|
|
655
|
+
const schema = this.#table(table);
|
|
656
|
+
const store = this.#store(table);
|
|
657
|
+
const plan = selectPlan(input, schema, store.indexes);
|
|
658
|
+
const conditions = input.conditions ?? [];
|
|
659
|
+
const offset = input.offset ?? 0;
|
|
660
|
+
const limit = input.limit;
|
|
661
|
+
let skipped = 0;
|
|
662
|
+
let yielded = 0;
|
|
663
|
+
for (const row of await this.#candidates(store, schema, plan)) {
|
|
664
|
+
if (limit !== void 0 && yielded >= limit) break;
|
|
665
|
+
if (!matchesQuery(row, conditions)) continue;
|
|
666
|
+
if (skipped < offset) {
|
|
667
|
+
skipped += 1;
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
yielded += 1;
|
|
671
|
+
yield row;
|
|
672
|
+
}
|
|
673
|
+
} catch (error) {
|
|
674
|
+
throw this.#wrap(error);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
650
677
|
async #mutate(table, options, scope) {
|
|
651
678
|
const signal = options?.signal;
|
|
652
679
|
checkAbort(signal);
|
|
@@ -759,31 +786,33 @@ var IndexedDBDriver = class {
|
|
|
759
786
|
return schema;
|
|
760
787
|
}
|
|
761
788
|
async #upgrade(input, added, context) {
|
|
762
|
-
for (const name of added) if (name !== "__metadata__" && context.stores.includes(name)) context.drop(name);
|
|
789
|
+
for (const name of added) if (name !== "__metadata__" && context.stores.names.includes(name)) context.stores.drop(name);
|
|
763
790
|
for (const step of input.plan.steps) switch (step.operation) {
|
|
764
791
|
case "table.add":
|
|
765
|
-
context.create(step.table.name, schemaToStore(step.table));
|
|
792
|
+
context.stores.create(step.table.name, schemaToStore(step.table));
|
|
766
793
|
break;
|
|
767
794
|
case "table.remove":
|
|
768
|
-
context.drop(step.table);
|
|
795
|
+
context.stores.drop(step.table);
|
|
769
796
|
break;
|
|
770
797
|
case "index.add": {
|
|
771
798
|
const name = deriveIndexedDBIndexName(step.index);
|
|
772
799
|
const [column] = step.index;
|
|
773
800
|
const path = step.index.length === 1 && column !== void 0 ? column : [...step.index];
|
|
774
|
-
context.
|
|
801
|
+
context.indexes.create(step.table, {
|
|
775
802
|
name,
|
|
776
803
|
path
|
|
777
804
|
});
|
|
778
805
|
break;
|
|
779
806
|
}
|
|
780
807
|
case "index.remove":
|
|
781
|
-
context.
|
|
808
|
+
context.indexes.drop(step.table, deriveIndexedDBIndexName(step.index));
|
|
782
809
|
break;
|
|
783
810
|
case "column.remove": {
|
|
784
|
-
let cursor = await context.store(step.table).cursor();
|
|
811
|
+
let cursor = await context.stores.store(step.table).cursor();
|
|
785
812
|
while (cursor !== null) {
|
|
786
|
-
const
|
|
813
|
+
const row = cursor.value;
|
|
814
|
+
if (row === void 0) throw new DatabaseError("MIGRATION", "migrate: stored value is not a record", { table: step.table });
|
|
815
|
+
const [migrated] = migrateRows([row], [step]);
|
|
787
816
|
if (migrated === void 0) throw new DatabaseError("MIGRATION", "migrate: transformed row is missing", { table: step.table });
|
|
788
817
|
await cursor.update(migrated);
|
|
789
818
|
cursor = await cursor.continue();
|
|
@@ -791,7 +820,7 @@ var IndexedDBDriver = class {
|
|
|
791
820
|
break;
|
|
792
821
|
}
|
|
793
822
|
}
|
|
794
|
-
if (input.metadata !== void 0) await context.store(METADATA_STORE).set({
|
|
823
|
+
if (input.metadata !== void 0) await context.stores.store(METADATA_STORE).set({
|
|
795
824
|
version: input.metadata.version,
|
|
796
825
|
schema: input.metadata.schema
|
|
797
826
|
}, "metadata");
|
|
@@ -800,7 +829,7 @@ var IndexedDBDriver = class {
|
|
|
800
829
|
//#endregion
|
|
801
830
|
//#region src/browser/factories.ts
|
|
802
831
|
/**
|
|
803
|
-
*
|
|
832
|
+
* Creates a persistent IndexedDB {@link DriverInterface} for a browser database name.
|
|
804
833
|
*
|
|
805
834
|
* @remarks
|
|
806
835
|
* Pass it to `createDatabase` from `@orkestrel/database` to run the typed database
|