/** * KeepItGoing - Offline IndexedDB helpers * Pure local storage access for offline task data. No network calls here — * see offline-sync.js for the /api/sync/ replay logic. */ const OFFLINE_DB_NAME = 'keepitgoing-offline'; const OFFLINE_DB_VERSION = 1; function openOfflineDB() { return new Promise((resolve, reject) => { const request = indexedDB.open(OFFLINE_DB_NAME, OFFLINE_DB_VERSION); request.onupgradeneeded = (event) => { const db = event.target.result; if (!db.objectStoreNames.contains('tasks')) { db.createObjectStore('tasks', { keyPath: 'sync_id' }); } if (!db.objectStoreNames.contains('meta')) { db.createObjectStore('meta', { keyPath: 'key' }); } }; request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } function promisifyRequest(request) { return new Promise((resolve, reject) => { request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } async function getMeta(key) { const db = await openOfflineDB(); const tx = db.transaction('meta', 'readonly'); const result = await promisifyRequest(tx.objectStore('meta').get(key)); return result ? result.value : undefined; } async function setMeta(key, value) { const db = await openOfflineDB(); const tx = db.transaction('meta', 'readwrite'); await promisifyRequest(tx.objectStore('meta').put({ key, value })); } async function ensureDeviceId() { let deviceId = await getMeta('device_id'); if (!deviceId) { deviceId = 'web-' + crypto.randomUUID(); await setMeta('device_id', deviceId); } return deviceId; } async function getAllTasks() { const db = await openOfflineDB(); const tx = db.transaction('tasks', 'readonly'); return promisifyRequest(tx.objectStore('tasks').getAll()); } async function putLocalTask(task) { const db = await openOfflineDB(); const tx = db.transaction('tasks', 'readwrite'); await promisifyRequest(tx.objectStore('tasks').put(task)); } async function getDirtyTasks() { const tasks = await getAllTasks(); return tasks.filter((t) => t._dirty === true); }