@workglow/indexeddb 0.2.31 → 0.2.32
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 +34 -0
- package/dist/job-queue/IndexedDbQueueStorage.d.ts +16 -11
- package/dist/job-queue/IndexedDbQueueStorage.d.ts.map +1 -1
- package/dist/job-queue/IndexedDbRateLimiterStorage.d.ts +15 -4
- package/dist/job-queue/IndexedDbRateLimiterStorage.d.ts.map +1 -1
- package/dist/job-queue/browser.js +399 -351
- package/dist/job-queue/browser.js.map +9 -6
- package/dist/job-queue/common.d.ts +3 -0
- package/dist/job-queue/common.d.ts.map +1 -1
- package/dist/job-queue/node.js +399 -351
- package/dist/job-queue/node.js.map +9 -6
- package/dist/migrations/IndexedDbMigrationRunner.d.ts +93 -0
- package/dist/migrations/IndexedDbMigrationRunner.d.ts.map +1 -0
- package/dist/migrations/indexedDbQueueMigrations.d.ts +24 -0
- package/dist/migrations/indexedDbQueueMigrations.d.ts.map +1 -0
- package/dist/migrations/indexedDbRateLimiterMigrations.d.ts +37 -0
- package/dist/migrations/indexedDbRateLimiterMigrations.d.ts.map +1 -0
- package/dist/storage/IndexedDbTable.d.ts.map +1 -1
- package/dist/storage/IndexedDbTabularMigrationApplier.d.ts +84 -0
- package/dist/storage/IndexedDbTabularMigrationApplier.d.ts.map +1 -0
- package/dist/storage/IndexedDbTabularStorage.d.ts +21 -2
- package/dist/storage/IndexedDbTabularStorage.d.ts.map +1 -1
- package/dist/storage/browser.js +472 -30
- package/dist/storage/browser.js.map +8 -5
- package/dist/storage/common.d.ts +3 -0
- package/dist/storage/common.d.ts.map +1 -1
- package/dist/storage/node.js +472 -30
- package/dist/storage/node.js.map +8 -5
- package/dist/storage/openIdb.d.ts +19 -0
- package/dist/storage/openIdb.d.ts.map +1 -0
- package/package.json +7 -7
package/dist/storage/common.d.ts
CHANGED
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
* Copyright 2025 Steven Roussey <sroussey@gmail.com>
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
+
export * from "./openIdb";
|
|
6
7
|
export * from "./IndexedDbTable";
|
|
7
8
|
export * from "./IndexedDbKvStorage";
|
|
8
9
|
export * from "./IndexedDbTabularStorage";
|
|
10
|
+
export * from "./IndexedDbTabularMigrationApplier";
|
|
9
11
|
export * from "./IndexedDbVectorStorage";
|
|
12
|
+
export * from "../migrations/IndexedDbMigrationRunner";
|
|
10
13
|
//# sourceMappingURL=common.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/storage/common.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC"}
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/storage/common.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oCAAoC,CAAC;AACnD,cAAc,0BAA0B,CAAC;AAGzC,cAAc,wCAAwC,CAAC"}
|
package/dist/storage/node.js
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
// src/storage/openIdb.ts
|
|
2
|
+
function openIdb(dbName, options = {}) {
|
|
3
|
+
return new Promise((resolve, reject) => {
|
|
4
|
+
const req = indexedDB.open(dbName, options.version);
|
|
5
|
+
req.onsuccess = () => {
|
|
6
|
+
const db = req.result;
|
|
7
|
+
db.onversionchange = () => db.close();
|
|
8
|
+
resolve(db);
|
|
9
|
+
};
|
|
10
|
+
req.onupgradeneeded = (ev) => options.onUpgradeNeeded?.(ev);
|
|
11
|
+
req.onerror = () => {
|
|
12
|
+
const err = req.error;
|
|
13
|
+
if (err && err.name === "VersionError") {
|
|
14
|
+
reject(new Error(`IndexedDB ${dbName} exists at a higher version than ${options.version ?? "current"}`));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
reject(err);
|
|
18
|
+
};
|
|
19
|
+
req.onblocked = () => reject(new Error(`IndexedDB ${dbName} is blocked — close other tabs using this database.`));
|
|
20
|
+
});
|
|
21
|
+
}
|
|
1
22
|
// src/storage/IndexedDbTable.ts
|
|
2
23
|
import { deepEqual } from "@workglow/util";
|
|
3
24
|
var METADATA_STORE_NAME = "__schema_metadata__";
|
|
@@ -15,33 +36,8 @@ async function saveSchemaMetadata(db, tableName, snapshot) {
|
|
|
15
36
|
}
|
|
16
37
|
});
|
|
17
38
|
}
|
|
18
|
-
|
|
19
|
-
return
|
|
20
|
-
const openRequest = indexedDB.open(tableName, version);
|
|
21
|
-
openRequest.onsuccess = (event) => {
|
|
22
|
-
const db = event.target.result;
|
|
23
|
-
db.onversionchange = () => {
|
|
24
|
-
db.close();
|
|
25
|
-
};
|
|
26
|
-
resolve(db);
|
|
27
|
-
};
|
|
28
|
-
openRequest.onupgradeneeded = (event) => {
|
|
29
|
-
if (upgradeNeededCallback) {
|
|
30
|
-
upgradeNeededCallback(event);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
openRequest.onerror = () => {
|
|
34
|
-
const error = openRequest.error;
|
|
35
|
-
if (error && error.name === "VersionError") {
|
|
36
|
-
reject(new Error(`Database ${tableName} exists at a higher version. Cannot open at version ${version || "current"}.`));
|
|
37
|
-
} else {
|
|
38
|
-
reject(error);
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
openRequest.onblocked = () => {
|
|
42
|
-
reject(new Error(`Database ${tableName} is blocked. Close all other tabs using this database.`));
|
|
43
|
-
};
|
|
44
|
-
});
|
|
39
|
+
function openIndexedDbTable(tableName, version, upgradeNeededCallback) {
|
|
40
|
+
return openIdb(tableName, { version, onUpgradeNeeded: upgradeNeededCallback });
|
|
45
41
|
}
|
|
46
42
|
async function deleteIndexedDbTable(tableName) {
|
|
47
43
|
return new Promise((resolve, reject) => {
|
|
@@ -325,6 +321,404 @@ import {
|
|
|
325
321
|
isSearchCondition,
|
|
326
322
|
pickCoveringIndex
|
|
327
323
|
} from "@workglow/storage";
|
|
324
|
+
|
|
325
|
+
// src/migrations/IndexedDbMigrationRunner.ts
|
|
326
|
+
import {
|
|
327
|
+
MIGRATIONS_TABLE,
|
|
328
|
+
sortMigrations
|
|
329
|
+
} from "@workglow/storage";
|
|
330
|
+
function getIndexedDb() {
|
|
331
|
+
const idb = globalThis.indexedDB;
|
|
332
|
+
if (!idb) {
|
|
333
|
+
throw new Error("indexedDB is not available in this environment. Provide one via the IndexedDbMigrationRunner constructor or polyfill globalThis.indexedDB.");
|
|
334
|
+
}
|
|
335
|
+
return idb;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
class MigrationAbortedByOtherTabError extends Error {
|
|
339
|
+
constructor(dbName) {
|
|
340
|
+
super(`IndexedDB ${dbName} migration aborted: another tab requested a higher version`);
|
|
341
|
+
this.name = "MigrationAbortedByOtherTabError";
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
var RUN_LOCKS = new Map;
|
|
345
|
+
|
|
346
|
+
class IndexedDbMigrationRunner {
|
|
347
|
+
dbName;
|
|
348
|
+
idb;
|
|
349
|
+
constructor(dbName, idb = getIndexedDb()) {
|
|
350
|
+
this.dbName = dbName;
|
|
351
|
+
this.idb = idb;
|
|
352
|
+
}
|
|
353
|
+
async probe() {
|
|
354
|
+
return new Promise((resolve, reject) => {
|
|
355
|
+
let settled = false;
|
|
356
|
+
const finalize = (db, outcome) => {
|
|
357
|
+
if (settled)
|
|
358
|
+
return;
|
|
359
|
+
settled = true;
|
|
360
|
+
if (db) {
|
|
361
|
+
try {
|
|
362
|
+
db.close();
|
|
363
|
+
} catch {}
|
|
364
|
+
}
|
|
365
|
+
if (outcome.ok)
|
|
366
|
+
resolve(outcome.value);
|
|
367
|
+
else
|
|
368
|
+
reject(outcome.error);
|
|
369
|
+
};
|
|
370
|
+
const req = this.idb.open(this.dbName);
|
|
371
|
+
req.onupgradeneeded = () => {
|
|
372
|
+
if (settled)
|
|
373
|
+
return;
|
|
374
|
+
const u = req.result;
|
|
375
|
+
if (!u.objectStoreNames.contains(MIGRATIONS_TABLE)) {
|
|
376
|
+
u.createObjectStore(MIGRATIONS_TABLE, { keyPath: ["component", "version"] });
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
req.onsuccess = () => {
|
|
380
|
+
if (settled)
|
|
381
|
+
return;
|
|
382
|
+
const db = req.result;
|
|
383
|
+
const currentVersion = db.version;
|
|
384
|
+
if (!db.objectStoreNames.contains(MIGRATIONS_TABLE)) {
|
|
385
|
+
finalize(db, { ok: true, value: { currentVersion, applied: new Set } });
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const tx = db.transaction(MIGRATIONS_TABLE, "readonly");
|
|
389
|
+
const store = tx.objectStore(MIGRATIONS_TABLE);
|
|
390
|
+
const getAll = store.getAll();
|
|
391
|
+
getAll.onsuccess = () => {
|
|
392
|
+
if (settled)
|
|
393
|
+
return;
|
|
394
|
+
const rows = getAll.result;
|
|
395
|
+
finalize(db, {
|
|
396
|
+
ok: true,
|
|
397
|
+
value: {
|
|
398
|
+
currentVersion,
|
|
399
|
+
applied: new Set(rows.map((r) => `${r.component}@${r.version}`))
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
};
|
|
403
|
+
getAll.onerror = () => {
|
|
404
|
+
if (settled)
|
|
405
|
+
return;
|
|
406
|
+
finalize(db, { ok: false, error: getAll.error });
|
|
407
|
+
};
|
|
408
|
+
};
|
|
409
|
+
req.onerror = () => {
|
|
410
|
+
if (settled)
|
|
411
|
+
return;
|
|
412
|
+
finalize(undefined, { ok: false, error: req.error });
|
|
413
|
+
};
|
|
414
|
+
req.onblocked = () => {
|
|
415
|
+
if (settled)
|
|
416
|
+
return;
|
|
417
|
+
finalize(undefined, {
|
|
418
|
+
ok: false,
|
|
419
|
+
error: new Error(`IndexedDB ${this.dbName} blocked while probing for bookkeeping store`)
|
|
420
|
+
});
|
|
421
|
+
};
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
async ensureBookkeepingTable() {
|
|
425
|
+
await this.probe();
|
|
426
|
+
}
|
|
427
|
+
async appliedVersions(component) {
|
|
428
|
+
const { applied } = await this.probe();
|
|
429
|
+
const versions = new Set;
|
|
430
|
+
for (const key of applied) {
|
|
431
|
+
const at = key.lastIndexOf("@");
|
|
432
|
+
if (at < 0)
|
|
433
|
+
continue;
|
|
434
|
+
const c = key.slice(0, at);
|
|
435
|
+
const v = Number(key.slice(at + 1));
|
|
436
|
+
if (c === component && Number.isFinite(v))
|
|
437
|
+
versions.add(v);
|
|
438
|
+
}
|
|
439
|
+
return versions;
|
|
440
|
+
}
|
|
441
|
+
async run(migrations, options = {}) {
|
|
442
|
+
const prev = RUN_LOCKS.get(this.dbName) ?? Promise.resolve();
|
|
443
|
+
const next = prev.catch(() => {
|
|
444
|
+
return;
|
|
445
|
+
}).then(() => this.runLocked(migrations, options));
|
|
446
|
+
RUN_LOCKS.set(this.dbName, next);
|
|
447
|
+
try {
|
|
448
|
+
return await next;
|
|
449
|
+
} finally {
|
|
450
|
+
if (RUN_LOCKS.get(this.dbName) === next)
|
|
451
|
+
RUN_LOCKS.delete(this.dbName);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
async runLocked(migrations, options) {
|
|
455
|
+
const sorted = sortMigrations(migrations);
|
|
456
|
+
if (sorted.length === 0)
|
|
457
|
+
return [];
|
|
458
|
+
const { currentVersion, applied: alreadyApplied } = await this.probe();
|
|
459
|
+
const pending = sorted.filter((m) => !alreadyApplied.has(`${m.component}@${m.version}`));
|
|
460
|
+
if (pending.length === 0)
|
|
461
|
+
return [];
|
|
462
|
+
const targetVersion = currentVersion + 1;
|
|
463
|
+
const applied = [];
|
|
464
|
+
const onProgress = options.onProgress;
|
|
465
|
+
const buffered = [];
|
|
466
|
+
const emitLater = onProgress ? (ev) => buffered.push(ev) : undefined;
|
|
467
|
+
await new Promise((resolve, reject) => {
|
|
468
|
+
let settled = false;
|
|
469
|
+
let upgradeDb;
|
|
470
|
+
const finalize = (outcome) => {
|
|
471
|
+
if (settled)
|
|
472
|
+
return;
|
|
473
|
+
settled = true;
|
|
474
|
+
if (upgradeDb) {
|
|
475
|
+
try {
|
|
476
|
+
upgradeDb.close();
|
|
477
|
+
} catch {}
|
|
478
|
+
}
|
|
479
|
+
if (outcome.ok)
|
|
480
|
+
resolve();
|
|
481
|
+
else
|
|
482
|
+
reject(outcome.error);
|
|
483
|
+
};
|
|
484
|
+
const upreq = this.idb.open(this.dbName, targetVersion);
|
|
485
|
+
upreq.onupgradeneeded = (ev) => {
|
|
486
|
+
if (settled)
|
|
487
|
+
return;
|
|
488
|
+
try {
|
|
489
|
+
const db = upreq.result;
|
|
490
|
+
upgradeDb = db;
|
|
491
|
+
const tx = upreq.transaction;
|
|
492
|
+
const oldVersion = ev.oldVersion;
|
|
493
|
+
const newVersion = ev.newVersion ?? targetVersion;
|
|
494
|
+
db.onversionchange = () => {
|
|
495
|
+
try {
|
|
496
|
+
tx.abort();
|
|
497
|
+
} catch {}
|
|
498
|
+
finalize({ ok: false, error: new MigrationAbortedByOtherTabError(this.dbName) });
|
|
499
|
+
};
|
|
500
|
+
if (!db.objectStoreNames.contains(MIGRATIONS_TABLE)) {
|
|
501
|
+
db.createObjectStore(MIGRATIONS_TABLE, { keyPath: ["component", "version"] });
|
|
502
|
+
}
|
|
503
|
+
const meta = tx.objectStore(MIGRATIONS_TABLE);
|
|
504
|
+
for (const m of pending) {
|
|
505
|
+
emitLater?.({
|
|
506
|
+
component: m.component,
|
|
507
|
+
version: m.version,
|
|
508
|
+
phase: "starting",
|
|
509
|
+
description: m.description
|
|
510
|
+
});
|
|
511
|
+
const ctx = { db, tx, oldVersion, newVersion };
|
|
512
|
+
const result = m.up(ctx, (fraction) => {
|
|
513
|
+
emitLater?.({
|
|
514
|
+
component: m.component,
|
|
515
|
+
version: m.version,
|
|
516
|
+
phase: "running",
|
|
517
|
+
description: m.description,
|
|
518
|
+
fraction
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
if (result instanceof Promise) {
|
|
522
|
+
throw new Error(`IndexedDB migration "${m.component}@${m.version}" returned a Promise; ` + `IDB upgrade transactions cannot span async work.`);
|
|
523
|
+
}
|
|
524
|
+
meta.add({
|
|
525
|
+
component: m.component,
|
|
526
|
+
version: m.version,
|
|
527
|
+
description: m.description ?? null,
|
|
528
|
+
applied_at: new Date().toISOString()
|
|
529
|
+
});
|
|
530
|
+
applied.push(m);
|
|
531
|
+
emitLater?.({
|
|
532
|
+
component: m.component,
|
|
533
|
+
version: m.version,
|
|
534
|
+
phase: "completed",
|
|
535
|
+
description: m.description,
|
|
536
|
+
fraction: 1
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
} catch (err) {
|
|
540
|
+
try {
|
|
541
|
+
upreq.transaction?.abort();
|
|
542
|
+
} catch {}
|
|
543
|
+
const lastStart = [...buffered].reverse().find((e) => e.phase === "starting");
|
|
544
|
+
if (lastStart) {
|
|
545
|
+
emitLater?.({
|
|
546
|
+
component: lastStart.component,
|
|
547
|
+
version: lastStart.version,
|
|
548
|
+
phase: "failed",
|
|
549
|
+
description: lastStart.description,
|
|
550
|
+
error: err
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
finalize({ ok: false, error: err });
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
upreq.onsuccess = () => {
|
|
557
|
+
if (settled)
|
|
558
|
+
return;
|
|
559
|
+
upgradeDb = upreq.result;
|
|
560
|
+
finalize({ ok: true });
|
|
561
|
+
};
|
|
562
|
+
upreq.onerror = () => {
|
|
563
|
+
if (settled)
|
|
564
|
+
return;
|
|
565
|
+
finalize({ ok: false, error: upreq.error });
|
|
566
|
+
};
|
|
567
|
+
upreq.onblocked = () => {
|
|
568
|
+
if (settled)
|
|
569
|
+
return;
|
|
570
|
+
finalize({
|
|
571
|
+
ok: false,
|
|
572
|
+
error: new Error(`IndexedDB ${this.dbName} upgrade blocked — close other tabs.`)
|
|
573
|
+
});
|
|
574
|
+
};
|
|
575
|
+
}).finally(() => {
|
|
576
|
+
if (onProgress) {
|
|
577
|
+
for (const ev of buffered)
|
|
578
|
+
onProgress(ev);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
return applied;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
async function runIndexedDbMigrationGroups(groups, options = {}) {
|
|
585
|
+
const { idb, onProgress } = options;
|
|
586
|
+
const all = [];
|
|
587
|
+
for (const group of groups) {
|
|
588
|
+
const runner = idb ? new IndexedDbMigrationRunner(group.dbName, idb) : new IndexedDbMigrationRunner(group.dbName);
|
|
589
|
+
const applied = await runner.run(group.migrations, { onProgress });
|
|
590
|
+
all.push(...applied);
|
|
591
|
+
}
|
|
592
|
+
return all;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// src/storage/IndexedDbTabularMigrationApplier.ts
|
|
596
|
+
async function idbObjectStoreExists(dbName, storeName) {
|
|
597
|
+
const idb = globalThis.indexedDB;
|
|
598
|
+
if (!idb)
|
|
599
|
+
throw new Error("indexedDB is not available in this environment");
|
|
600
|
+
return new Promise((resolve, reject) => {
|
|
601
|
+
const req = idb.open(dbName);
|
|
602
|
+
req.onsuccess = () => {
|
|
603
|
+
const db = req.result;
|
|
604
|
+
const exists = db.objectStoreNames.contains(storeName);
|
|
605
|
+
db.close();
|
|
606
|
+
resolve(exists);
|
|
607
|
+
};
|
|
608
|
+
req.onerror = () => reject(req.error);
|
|
609
|
+
req.onblocked = () => reject(new Error(`IndexedDB ${dbName} blocked while probing for object store`));
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
class IndexedDbTabularMigrationApplier {
|
|
614
|
+
dbName;
|
|
615
|
+
storeName;
|
|
616
|
+
storage;
|
|
617
|
+
runner;
|
|
618
|
+
constructor(dbName, storeName, storage, runner) {
|
|
619
|
+
this.dbName = dbName;
|
|
620
|
+
this.storeName = storeName;
|
|
621
|
+
this.storage = storage;
|
|
622
|
+
this.runner = runner ?? new IndexedDbMigrationRunner(dbName);
|
|
623
|
+
}
|
|
624
|
+
async ensureBookkeeping() {
|
|
625
|
+
await this.runner.ensureBookkeepingTable();
|
|
626
|
+
}
|
|
627
|
+
async appliedVersions(component) {
|
|
628
|
+
return this.runner.appliedVersions(component);
|
|
629
|
+
}
|
|
630
|
+
async tableExists() {
|
|
631
|
+
return idbObjectStoreExists(this.dbName, this.storeName);
|
|
632
|
+
}
|
|
633
|
+
async markAllApplied(component, versions) {
|
|
634
|
+
if (versions.length === 0)
|
|
635
|
+
return;
|
|
636
|
+
await this.runner.run(versions.map((v) => ({
|
|
637
|
+
component,
|
|
638
|
+
version: v.version,
|
|
639
|
+
description: v.description,
|
|
640
|
+
up: () => {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
})));
|
|
644
|
+
}
|
|
645
|
+
async applyMigration(component, version, description, ops, onProgress) {
|
|
646
|
+
const ddlOps = [];
|
|
647
|
+
const backfills = [];
|
|
648
|
+
for (const op of ops) {
|
|
649
|
+
switch (op.kind) {
|
|
650
|
+
case "addIndex":
|
|
651
|
+
case "dropIndex":
|
|
652
|
+
ddlOps.push(op);
|
|
653
|
+
break;
|
|
654
|
+
case "backfill":
|
|
655
|
+
backfills.push(op);
|
|
656
|
+
break;
|
|
657
|
+
case "addColumn":
|
|
658
|
+
case "dropColumn":
|
|
659
|
+
case "renameColumn":
|
|
660
|
+
break;
|
|
661
|
+
default: {
|
|
662
|
+
const _exhaustive = op;
|
|
663
|
+
throw new Error(`IndexedDbTabularMigrationApplier: unhandled op kind ${_exhaustive.kind}`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
let processed = 0;
|
|
668
|
+
const total = Math.max(ops.length, 1);
|
|
669
|
+
for (const op of backfills) {
|
|
670
|
+
const batchSize = op.batchSize ?? 500;
|
|
671
|
+
let cursor;
|
|
672
|
+
while (true) {
|
|
673
|
+
const page = await this.storage.getPage({ limit: batchSize, cursor });
|
|
674
|
+
for (const row of page.items) {
|
|
675
|
+
const out = await op.transform(row);
|
|
676
|
+
if (out === row)
|
|
677
|
+
continue;
|
|
678
|
+
if (out === undefined) {
|
|
679
|
+
await this.storage.delete(row);
|
|
680
|
+
} else {
|
|
681
|
+
await this.storage.put(out);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (!page.nextCursor)
|
|
685
|
+
break;
|
|
686
|
+
cursor = page.nextCursor;
|
|
687
|
+
}
|
|
688
|
+
processed++;
|
|
689
|
+
onProgress?.(processed / total);
|
|
690
|
+
}
|
|
691
|
+
const storeName = this.storeName;
|
|
692
|
+
await this.runner.run([
|
|
693
|
+
{
|
|
694
|
+
component,
|
|
695
|
+
version,
|
|
696
|
+
description,
|
|
697
|
+
up: (ctx) => {
|
|
698
|
+
if (!ctx.db.objectStoreNames.contains(storeName))
|
|
699
|
+
return;
|
|
700
|
+
const store = ctx.tx.objectStore(storeName);
|
|
701
|
+
for (const op of ddlOps) {
|
|
702
|
+
if (op.kind === "addIndex") {
|
|
703
|
+
if (store.indexNames.contains(op.name))
|
|
704
|
+
continue;
|
|
705
|
+
const keyPath = op.columns.length === 1 ? op.columns[0] : [...op.columns];
|
|
706
|
+
store.createIndex(op.name, keyPath, { unique: op.unique ?? false });
|
|
707
|
+
} else {
|
|
708
|
+
if (!store.indexNames.contains(op.name))
|
|
709
|
+
continue;
|
|
710
|
+
store.deleteIndex(op.name);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
]);
|
|
716
|
+
processed += ddlOps.length;
|
|
717
|
+
onProgress?.(processed / total);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// src/storage/IndexedDbTabularStorage.ts
|
|
328
722
|
var IDB_TABULAR_REPOSITORY = createServiceToken("storage.tabularRepository.indexedDb");
|
|
329
723
|
function compareEntitiesForChange(a, b) {
|
|
330
724
|
const au = a?.updated_at;
|
|
@@ -338,13 +732,14 @@ function compareEntitiesForChange(a, b) {
|
|
|
338
732
|
class IndexedDbTabularStorage extends BaseTabularStorage {
|
|
339
733
|
table;
|
|
340
734
|
db;
|
|
735
|
+
applyingMigrations = false;
|
|
341
736
|
setupPromise = null;
|
|
342
737
|
migrationOptions;
|
|
343
738
|
hybridManager = null;
|
|
344
739
|
hybridOptions;
|
|
345
740
|
cursorSafeIndexes;
|
|
346
|
-
constructor(table = "tabular_store", schema, primaryKeyNames, indexes = [], migrationOptions = {}, clientProvidedKeys = "if-missing") {
|
|
347
|
-
super(schema, primaryKeyNames, indexes, clientProvidedKeys);
|
|
741
|
+
constructor(table = "tabular_store", schema, primaryKeyNames, indexes = [], migrationOptions = {}, clientProvidedKeys = "if-missing", tabularMigrations) {
|
|
742
|
+
super(schema, primaryKeyNames, indexes, clientProvidedKeys, tabularMigrations, table);
|
|
348
743
|
this.table = table;
|
|
349
744
|
this.migrationOptions = migrationOptions;
|
|
350
745
|
this.hybridOptions = {
|
|
@@ -365,12 +760,46 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
|
|
|
365
760
|
await this.setupPromise;
|
|
366
761
|
return;
|
|
367
762
|
}
|
|
763
|
+
if (this.applyingMigrations) {
|
|
764
|
+
this.setupPromise = this.performSetup();
|
|
765
|
+
try {
|
|
766
|
+
this.db = await this.setupPromise;
|
|
767
|
+
this.rewireOnVersionChange();
|
|
768
|
+
} finally {
|
|
769
|
+
this.setupPromise = null;
|
|
770
|
+
}
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
const freshTable = this.tabularMigrations && this.tabularMigrations.length > 0 ? !await this.probeObjectStoreExists() : false;
|
|
368
774
|
this.setupPromise = this.performSetup();
|
|
369
775
|
try {
|
|
370
776
|
this.db = await this.setupPromise;
|
|
371
777
|
} finally {
|
|
372
778
|
this.setupPromise = null;
|
|
373
779
|
}
|
|
780
|
+
if (this.tabularMigrations && this.tabularMigrations.length > 0) {
|
|
781
|
+
this.rewireOnVersionChange();
|
|
782
|
+
this.applyingMigrations = true;
|
|
783
|
+
try {
|
|
784
|
+
await this.applyTabularMigrations({ freshTable });
|
|
785
|
+
} finally {
|
|
786
|
+
this.applyingMigrations = false;
|
|
787
|
+
}
|
|
788
|
+
if (!this.db) {
|
|
789
|
+
this.db = await this.performSetup();
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
rewireOnVersionChange() {
|
|
794
|
+
if (!this.db)
|
|
795
|
+
return;
|
|
796
|
+
this.db.onversionchange = () => {
|
|
797
|
+
this.db?.close();
|
|
798
|
+
this.db = undefined;
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
async probeObjectStoreExists() {
|
|
802
|
+
return idbObjectStoreExists(this.table, this.table);
|
|
374
803
|
}
|
|
375
804
|
async performSetup() {
|
|
376
805
|
const pkColumns = super.primaryKeyColumns();
|
|
@@ -394,6 +823,13 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
|
|
|
394
823
|
const useAutoIncrement = this.hasAutoGeneratedKey() && this.autoGeneratedKeyStrategy === "autoincrement" && pkColumns.length === 1;
|
|
395
824
|
return await ensureIndexedDbTable(this.table, primaryKey, expectedIndexes, this.migrationOptions, useAutoIncrement);
|
|
396
825
|
}
|
|
826
|
+
getMigrationApplier() {
|
|
827
|
+
return new IndexedDbTabularMigrationApplier(this.table, this.table, {
|
|
828
|
+
getPage: (req) => this.getPage(req),
|
|
829
|
+
put: (row) => this.put(row),
|
|
830
|
+
delete: (row) => this.delete(row)
|
|
831
|
+
});
|
|
832
|
+
}
|
|
397
833
|
generateKeyValue(columnName, strategy) {
|
|
398
834
|
if (strategy === "uuid") {
|
|
399
835
|
return uuid4();
|
|
@@ -1169,14 +1605,20 @@ class IndexedDbVectorStorage extends IndexedDbTabularStorage {
|
|
|
1169
1605
|
}
|
|
1170
1606
|
}
|
|
1171
1607
|
export {
|
|
1608
|
+
runIndexedDbMigrationGroups,
|
|
1609
|
+
openIdb,
|
|
1610
|
+
idbObjectStoreExists,
|
|
1172
1611
|
ensureIndexedDbTable,
|
|
1173
1612
|
dropIndexedDbTable,
|
|
1613
|
+
MigrationAbortedByOtherTabError,
|
|
1174
1614
|
IndexedDbVectorStorage,
|
|
1175
1615
|
IndexedDbTabularStorage,
|
|
1616
|
+
IndexedDbTabularMigrationApplier,
|
|
1617
|
+
IndexedDbMigrationRunner,
|
|
1176
1618
|
IndexedDbKvStorage,
|
|
1177
1619
|
IDB_VECTOR_REPOSITORY,
|
|
1178
1620
|
IDB_TABULAR_REPOSITORY,
|
|
1179
1621
|
IDB_KV_REPOSITORY
|
|
1180
1622
|
};
|
|
1181
1623
|
|
|
1182
|
-
//# debugId=
|
|
1624
|
+
//# debugId=2A9580D89ED3759364756E2164756E21
|