import type { LoadedWorkspace, StoredWorkspace, WorkspaceSummary, } from './workspaceTypes'; const DB_NAME = 'pdf-tools-workspaces'; const DB_VERSION = 1; const WORKSPACE_STORE = 'workspaces'; const PDF_STORE = 'pdfBinaries'; interface PdfBinaryRecord { pdfId: string; name: string; blob: Blob; size: number; createdAt: string; updatedAt: string; } interface SaveWorkspaceInput { workspace: StoredWorkspace; pdfArrayBuffer: ArrayBuffer; } function requestToPromise(request: IDBRequest): Promise { return new Promise((resolve, reject) => { request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } function transactionDone(transaction: IDBTransaction): Promise { return new Promise((resolve, reject) => { transaction.oncomplete = () => resolve(); transaction.onerror = () => reject(transaction.error); transaction.onabort = () => reject(transaction.error); }); } function openWorkspaceDb(): Promise { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION); request.onupgradeneeded = () => { const db = request.result; if (!db.objectStoreNames.contains(WORKSPACE_STORE)) { const workspaceStore = db.createObjectStore(WORKSPACE_STORE, { keyPath: 'id', }); workspaceStore.createIndex('updatedAt', 'updatedAt', { unique: false, }); workspaceStore.createIndex('pdfId', 'pdfId', { unique: false, }); } if (!db.objectStoreNames.contains(PDF_STORE)) { db.createObjectStore(PDF_STORE, { keyPath: 'pdfId', }); } }; request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } export async function listWorkspaces(): Promise { const db = await openWorkspaceDb(); try { const tx = db.transaction(WORKSPACE_STORE, 'readonly'); const store = tx.objectStore(WORKSPACE_STORE); const records = await requestToPromise(store.getAll()); await transactionDone(tx); return records .map((workspace) => ({ id: workspace.id, name: workspace.name, pdfId: workspace.pdfId, pdfName: workspace.pdfName, createdAt: workspace.createdAt, updatedAt: workspace.updatedAt, sourcePageCount: workspace.sourcePageCount, workspacePageCount: workspace.pages.length, historyCount: workspace.history.length, redoCount: workspace.redoHistory?.length ?? 0, })) .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)); } finally { db.close(); } } export async function saveWorkspaceToIndexedDb({ workspace, pdfArrayBuffer, }: SaveWorkspaceInput): Promise { const db = await openWorkspaceDb(); try { const now = new Date().toISOString(); const pdfRecord: PdfBinaryRecord = { pdfId: workspace.pdfId, name: workspace.pdfName, blob: new Blob([pdfArrayBuffer], { type: 'application/pdf' }), size: pdfArrayBuffer.byteLength, createdAt: workspace.createdAt, updatedAt: now, }; const tx = db.transaction([WORKSPACE_STORE, PDF_STORE], 'readwrite'); tx.objectStore(PDF_STORE).put(pdfRecord); tx.objectStore(WORKSPACE_STORE).put(workspace); await transactionDone(tx); } finally { db.close(); } } export async function loadWorkspaceFromIndexedDb( workspaceId: string ): Promise { const db = await openWorkspaceDb(); try { const tx = db.transaction([WORKSPACE_STORE, PDF_STORE], 'readonly'); const workspace = await requestToPromise( tx.objectStore(WORKSPACE_STORE).get(workspaceId) ); if (!workspace) { await transactionDone(tx); return null; } const pdfRecord = await requestToPromise( tx.objectStore(PDF_STORE).get(workspace.pdfId) ); await transactionDone(tx); if (!pdfRecord) { throw new Error(`Missing PDF binary for workspace ${workspaceId}`); } const pdfArrayBuffer = await pdfRecord.blob.arrayBuffer(); return { workspace, pdfArrayBuffer, }; } finally { db.close(); } } export async function deleteWorkspaceFromIndexedDb( workspaceId: string ): Promise { const db = await openWorkspaceDb(); try { const lookupTx = db.transaction(WORKSPACE_STORE, 'readonly'); const workspace = await requestToPromise( lookupTx.objectStore(WORKSPACE_STORE).get(workspaceId) ); await transactionDone(lookupTx); if (!workspace) return; const deleteTx = db.transaction([WORKSPACE_STORE, PDF_STORE], 'readwrite'); deleteTx.objectStore(WORKSPACE_STORE).delete(workspaceId); await transactionDone(deleteTx); // Clean up PDF binary if no remaining workspace references it. const remainingWorkspaces = await listWorkspaces(); const pdfStillUsed = remainingWorkspaces.some( (summary) => summary.pdfId === workspace.pdfId ); if (!pdfStillUsed) { const cleanupDb = await openWorkspaceDb(); try { const cleanupTx = cleanupDb.transaction(PDF_STORE, 'readwrite'); cleanupTx.objectStore(PDF_STORE).delete(workspace.pdfId); await transactionDone(cleanupTx); } finally { cleanupDb.close(); } } } finally { db.close(); } }