Initial Commit
This commit is contained in:
106
packages/foundation/data/pwa/src/sync-queue/indexeddb-store.ts
Normal file
106
packages/foundation/data/pwa/src/sync-queue/indexeddb-store.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import {
|
||||
DEFAULT_IDB_DATABASE,
|
||||
DEFAULT_IDB_OBJECT_STORE,
|
||||
} from "../constants/index";
|
||||
import type { SyncQueueEntry } from "../types/common";
|
||||
import type { SyncQueueStore } from "./sync-queue.types";
|
||||
|
||||
/** Options consumed by {@link IndexedDbStore}. */
|
||||
export interface IndexedDbStoreOptions {
|
||||
/** `IDBFactory` seam. Defaults to `globalThis.indexedDB`. */
|
||||
factory?: IDBFactory;
|
||||
/** Database name. Defaults to `__suaveplan_pwa__`. */
|
||||
databaseName?: string;
|
||||
/** Object-store name. Defaults to `sync_queue`. */
|
||||
objectStoreName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* IndexedDB-backed `SyncQueueStore`. Default production store: scales beyond
|
||||
* the 5 MiB `localStorage` budget and lets large request bodies persist
|
||||
* without blocking the main thread on JSON serialisation.
|
||||
*/
|
||||
export class IndexedDbStore implements SyncQueueStore {
|
||||
private readonly _factory: IDBFactory;
|
||||
private readonly _databaseName: string;
|
||||
private readonly _objectStoreName: string;
|
||||
|
||||
public constructor(options: IndexedDbStoreOptions = {}) {
|
||||
const candidate =
|
||||
options.factory ??
|
||||
(globalThis as { indexedDB?: IDBFactory }).indexedDB;
|
||||
if (candidate === undefined) {
|
||||
throw new Error(
|
||||
"IndexedDbStore requires an IDBFactory; none was supplied and globalThis.indexedDB is undefined"
|
||||
);
|
||||
}
|
||||
this._factory = candidate;
|
||||
this._databaseName = options.databaseName ?? DEFAULT_IDB_DATABASE;
|
||||
this._objectStoreName =
|
||||
options.objectStoreName ?? DEFAULT_IDB_OBJECT_STORE;
|
||||
}
|
||||
|
||||
public async load(): Promise<Record<string, unknown>[]> {
|
||||
const db = await this._open();
|
||||
try {
|
||||
return await new Promise<Record<string, unknown>[]>(
|
||||
(resolve, reject) => {
|
||||
const tx = db.transaction(
|
||||
this._objectStoreName,
|
||||
"readonly"
|
||||
);
|
||||
const store = tx.objectStore(this._objectStoreName);
|
||||
const req = store.getAll();
|
||||
req.onsuccess = () => {
|
||||
const result = req.result as Record<string, unknown>[];
|
||||
resolve(result);
|
||||
};
|
||||
req.onerror = () =>
|
||||
reject(req.error ?? new Error("idb getAll failed"));
|
||||
}
|
||||
);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
public async save(entries: readonly SyncQueueEntry[]): Promise<void> {
|
||||
const db = await this._open();
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const tx = db.transaction(this._objectStoreName, "readwrite");
|
||||
const store = tx.objectStore(this._objectStoreName);
|
||||
store.clear();
|
||||
for (const entry of entries) {
|
||||
store.put(entry);
|
||||
}
|
||||
tx.oncomplete = () => resolve();
|
||||
tx.onerror = () =>
|
||||
reject(tx.error ?? new Error("idb save failed"));
|
||||
tx.onabort = () =>
|
||||
reject(tx.error ?? new Error("idb save aborted"));
|
||||
});
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
private async _open(): Promise<IDBDatabase> {
|
||||
return await new Promise<IDBDatabase>((resolve, reject) => {
|
||||
const req = this._factory.open(this._databaseName, 1);
|
||||
req.onupgradeneeded = () => {
|
||||
// `onupgradeneeded` only fires when the requested version is
|
||||
// higher than the on-disk version; at v1 fresh installs there
|
||||
// is no existing object store to clash with.
|
||||
req.result.createObjectStore(this._objectStoreName, {
|
||||
keyPath: "id",
|
||||
});
|
||||
};
|
||||
req.onsuccess = () => resolve(req.result);
|
||||
req.onerror = () =>
|
||||
reject(req.error ?? new Error("idb open failed"));
|
||||
req.onblocked = () =>
|
||||
reject(new Error("idb open blocked by another connection"));
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user