import { AttachmentData, EncodingType, LocalStorageAdapter } from '@powersync/common'; /** * IndexDBFileSystemStorageAdapter implements LocalStorageAdapter using IndexedDB. * Suitable for web browsers and web-based environments. */ export class IndexDBFileSystemStorageAdapter implements LocalStorageAdapter { private dbPromise!: Promise; constructor(private databaseName: string = 'PowerSyncFiles') {} async initialize(): Promise { this.dbPromise = new Promise((resolve, reject) => { const request = indexedDB.open(this.databaseName, 1); request.onupgradeneeded = () => { request.result.createObjectStore('files'); }; request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } async clear(): Promise { const db = await this.dbPromise; return new Promise((resolve, reject) => { const tx = db.transaction('files', 'readwrite'); const store = tx.objectStore('files'); const req = store.clear(); req.onsuccess = () => resolve(); req.onerror = () => reject(req.error); }); } getLocalUri(filename: string): string { return `indexeddb://${this.databaseName}/files/${filename}`; } private async getStore(mode: IDBTransactionMode = 'readonly'): Promise { const db = await this.dbPromise; const tx = db.transaction('files', mode); return tx.objectStore('files'); } async saveFile(filePath: string, data: AttachmentData): Promise { const store = await this.getStore('readwrite'); let dataToStore: ArrayBuffer; let size: number; if (typeof data === 'string') { const binaryString = atob(data); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } dataToStore = bytes.buffer; size = bytes.byteLength; } else { dataToStore = data; size = dataToStore.byteLength; } return await new Promise((resolve, reject) => { const req = store.put(dataToStore, filePath); req.onsuccess = () => resolve(size); req.onerror = () => reject(req.error); }); } async readFile(fileUri: string, options?: { encoding?: EncodingType; mediaType?: string }): Promise { const store = await this.getStore(); return new Promise((resolve, reject) => { const req = store.get(fileUri); req.onsuccess = async () => { if (!req.result) { reject(new Error('File not found')); return; } resolve(req.result as ArrayBuffer); }; req.onerror = () => reject(req.error); }); } async deleteFile(uri: string, options?: { filename?: string }): Promise { const store = await this.getStore('readwrite'); await new Promise((resolve, reject) => { const req = store.delete(uri); req.onsuccess = () => resolve(); req.onerror = () => reject(req.error); }); } async fileExists(fileUri: string): Promise { const store = await this.getStore(); return new Promise((resolve, reject) => { const req = store.get(fileUri); req.onsuccess = () => resolve(!!req.result); req.onerror = () => reject(req.error); }); } async makeDir(path: string): Promise { // No-op for IndexedDB as it does not have a directory structure } async rmDir(path: string): Promise { const store = await this.getStore('readwrite'); const range = IDBKeyRange.bound(path + '/', path + '/\uffff', false, false); await new Promise((resolve, reject) => { const req = store.delete(range); req.onsuccess = () => resolve(); req.onerror = () => reject(req.error); }); } }