import { Note } from '../types'; const DB_NAME = 'nextcloud-notes-db'; const DB_VERSION = 2; // Bumped to clear old cache with URL-encoded categories const NOTES_STORE = 'notes'; const SYNC_QUEUE_STORE = 'syncQueue'; export interface SyncOperation { id: string; type: 'create' | 'update' | 'delete'; noteId: number | string; note?: Note; timestamp: number; retryCount: number; } class LocalDB { private db: IDBDatabase | null = null; async init(): Promise { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION); request.onerror = () => reject(request.error); request.onsuccess = () => { this.db = request.result; resolve(); }; request.onupgradeneeded = (event) => { const db = (event.target as IDBOpenDBRequest).result; const oldVersion = event.oldVersion; if (!db.objectStoreNames.contains(NOTES_STORE)) { const notesStore = db.createObjectStore(NOTES_STORE, { keyPath: 'id' }); notesStore.createIndex('modified', 'modified', { unique: false }); notesStore.createIndex('category', 'category', { unique: false }); } else if (oldVersion < 2) { // Clear notes store when upgrading to v2 to remove old cached notes // with stripped first lines const transaction = (event.target as IDBOpenDBRequest).transaction!; const notesStore = transaction.objectStore(NOTES_STORE); notesStore.clear(); } if (!db.objectStoreNames.contains(SYNC_QUEUE_STORE)) { db.createObjectStore(SYNC_QUEUE_STORE, { keyPath: 'id' }); } }; }); } private getStore(storeName: string, mode: IDBTransactionMode = 'readonly'): IDBObjectStore { if (!this.db) throw new Error('Database not initialized'); const transaction = this.db.transaction(storeName, mode); return transaction.objectStore(storeName); } // Notes operations async getAllNotes(): Promise { return new Promise((resolve, reject) => { const store = this.getStore(NOTES_STORE); const request = store.getAll(); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } async getNote(id: number | string): Promise { return new Promise((resolve, reject) => { const store = this.getStore(NOTES_STORE); const request = store.get(id); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } async saveNote(note: Note): Promise { return new Promise((resolve, reject) => { const store = this.getStore(NOTES_STORE, 'readwrite'); const request = store.put(note); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } async saveNotes(notes: Note[]): Promise { return new Promise((resolve, reject) => { const store = this.getStore(NOTES_STORE, 'readwrite'); const transaction = store.transaction; notes.forEach(note => store.put(note)); transaction.oncomplete = () => resolve(); transaction.onerror = () => reject(transaction.error); }); } async deleteNote(id: number | string): Promise { return new Promise((resolve, reject) => { const store = this.getStore(NOTES_STORE, 'readwrite'); const request = store.delete(id); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } async clearNotes(): Promise { return new Promise((resolve, reject) => { const store = this.getStore(NOTES_STORE, 'readwrite'); const request = store.clear(); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } // Sync queue operations async addToSyncQueue(operation: SyncOperation): Promise { return new Promise((resolve, reject) => { const store = this.getStore(SYNC_QUEUE_STORE, 'readwrite'); const request = store.put(operation); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } async getSyncQueue(): Promise { return new Promise((resolve, reject) => { const store = this.getStore(SYNC_QUEUE_STORE); const request = store.getAll(); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } async removeFromSyncQueue(id: string): Promise { return new Promise((resolve, reject) => { const store = this.getStore(SYNC_QUEUE_STORE, 'readwrite'); const request = store.delete(id); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } async clearSyncQueue(): Promise { return new Promise((resolve, reject) => { const store = this.getStore(SYNC_QUEUE_STORE, 'readwrite'); const request = store.clear(); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } } export const localDB = new LocalDB();