UNPKG

alinea

Version:
136 lines (134 loc) 4.48 kB
import "../../chunks/chunk-NZLE2WMY.js"; // src/core/source/IndexedDBSource.ts import { ShaMismatchError } from "./ShaMismatchError.js"; import { ReadonlyTree } from "./Tree.js"; var IndexedDBSource = class { #factory; #name; #connection; constructor(indexedDB, name) { this.#factory = indexedDB; this.#name = name; } #createConnection() { return new Promise((resolve, reject) => { const request = this.#factory.open(this.#name); request.onsuccess = () => { const db = request.result; db.onclose = () => { console.info("IndexedDB connection closed"); this.#connection = void 0; }; resolve(db); }; request.onerror = () => reject(request.error); request.onupgradeneeded = () => { const db = request.result; db.createObjectStore("blobs"); db.createObjectStore("tree"); }; }); } #connect() { this.#connection ??= this.#createConnection(); return this.#connection; } #retryIfClosing(handle) { return (error) => { if (error instanceof Error && error.message.includes("closing")) { this.#connection = void 0; return handle(); } throw error; }; } getTree() { const handle = async () => { const db = await this.#connect(); const transaction = db.transaction(["tree", "blobs"], "readonly"); const treeStore = transaction.objectStore("tree"); const blobsStore = transaction.objectStore("blobs"); const [tree, blobKeys] = await Promise.all([ new Promise((resolve, reject) => { const request = treeStore.get("tree"); request.onsuccess = (event) => { const entry = event.target.result; resolve(entry ? new ReadonlyTree(entry) : ReadonlyTree.EMPTY); }; request.onerror = (event) => reject(event.target.error); }), new Promise((resolve, reject) => { const request = blobsStore.getAllKeys(); request.onsuccess = () => resolve(request.result); request.onerror = (event) => reject(event.target.error); }) ]); for (const sha of tree.shas) { if (!blobKeys.includes(sha)) { console.warn(`Blob ${sha} in tree, but not found`); return ReadonlyTree.EMPTY; } } return tree; }; return handle().catch(this.#retryIfClosing(handle)); } async getTreeIfDifferent(sha) { const current = await this.getTree(); return current.sha === sha ? void 0 : current; } async *getBlobs(shas) { const db = await this.#connect(); const transaction = db.transaction(["blobs"], "readonly"); const store = transaction.objectStore("blobs"); for (const sha of shas) { const request = store.get(sha); yield new Promise((resolve, reject) => { request.onsuccess = (event) => { const entry = event.target.result; if (entry) resolve([sha, entry]); else reject(new Error(`Blob not found: ${sha}`)); }; request.onerror = (event) => reject(event.target.error); }); } } async applyChanges(batch) { const db = await this.#connect(); const current = await this.getTree(); if (batch.fromSha !== current.sha) throw new ShaMismatchError( current.sha, batch.fromSha, "Cannot apply changes locally due to SHA mismatch" ); const updatedTree = current.clone(); updatedTree.applyChanges(batch); const compiled = await updatedTree.compile(); const transaction = db.transaction(["blobs", "tree"], "readwrite"); const blobs = transaction.objectStore("blobs"); const tree = transaction.objectStore("tree"); tree.put(compiled.toJSON(), "tree"); for (const change of batch.changes) switch (change.op) { case "add": blobs.put(change.contents, change.sha); break; } const blobKeys = await new Promise((resolve, reject) => { const request = blobs.getAllKeys(); request.onsuccess = () => resolve(request.result); request.onerror = (event) => reject(event.target.error); }); for (const sha of blobKeys) { if (!compiled.hasSha(sha)) blobs.delete(sha); } return new Promise((resolve, reject) => { transaction.oncomplete = () => resolve(); transaction.onerror = (event) => reject(event.target.error); }); } }; export { IndexedDBSource };