This commit is contained in:
Stan
2026-04-19 21:14:16 +02:00
parent 0c74a75126
commit 28d167f11f
42 changed files with 5681 additions and 55 deletions
+133
View File
@@ -0,0 +1,133 @@
/*
* IndexedDB operations module. Provides typed helpers for reading and writing to
* the browser-side database. All functions depend on `state.db` being set during
* initialization.
*
* Changes from the original monolithic app.js:
* - A5: `dbTransaction()` wraps multi-store operations in a single IndexedDB
* transaction so related writes (e.g. delete report + delete attachments) are
* atomic and won't leave orphaned records on mid-operation failure.
*/
import { state } from './state.js';
import {
DB_NAME,
DB_VERSION,
STORE_TEMPLATES,
STORE_LOOKUPS,
STORE_CONFIG,
STORE_REPORTS,
STORE_ATTACHMENTS,
STORE_SETTINGS
} from './constants.js';
/* ── Database bootstrap ─────────────────────────────────────────────────── */
export function openDatabase() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = request.result;
if (!db.objectStoreNames.contains(STORE_TEMPLATES)) {
db.createObjectStore(STORE_TEMPLATES, { keyPath: 'cacheKey' });
}
if (!db.objectStoreNames.contains(STORE_LOOKUPS)) {
db.createObjectStore(STORE_LOOKUPS, { keyPath: 'code' });
}
if (!db.objectStoreNames.contains(STORE_CONFIG)) {
db.createObjectStore(STORE_CONFIG, { keyPath: 'key' });
}
if (!db.objectStoreNames.contains(STORE_REPORTS)) {
db.createObjectStore(STORE_REPORTS, { keyPath: 'id' });
}
if (!db.objectStoreNames.contains(STORE_ATTACHMENTS)) {
const store = db.createObjectStore(STORE_ATTACHMENTS, { keyPath: 'id' });
store.createIndex('byReportId', 'reportId', { unique: false });
}
if (!db.objectStoreNames.contains(STORE_SETTINGS)) {
db.createObjectStore(STORE_SETTINGS, { keyPath: 'key' });
}
};
});
}
/* ── Single-store helpers ───────────────────────────────────────────────── */
export function dbGetAll(storeName) {
return executeStoreRequest(storeName, 'readonly', (store) => store.getAll());
}
export function dbGet(storeName, key) {
return executeStoreRequest(storeName, 'readonly', (store) => store.get(key));
}
export function dbPut(storeName, value) {
return executeStoreRequest(storeName, 'readwrite', (store) => store.put(value));
}
export function dbDelete(storeName, key) {
return executeStoreRequest(storeName, 'readwrite', (store) => store.delete(key));
}
export function dbGetAllByIndex(storeName, indexName, key) {
return executeStoreRequest(storeName, 'readonly', (store) => {
return store.index(indexName).getAll(IDBKeyRange.only(key));
});
}
function executeStoreRequest(storeName, mode, callback) {
return new Promise((resolve, reject) => {
const transaction = state.db.transaction(storeName, mode);
const store = transaction.objectStore(storeName);
const request = callback(store);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
/* ── Multi-store transaction (A5) ───────────────────────────────────────── */
/*
* Wraps multiple writes across different object stores in a single IndexedDB
* transaction. The callback receives a helper that returns a store by name.
* All writes either commit together or roll back as a unit.
*
* Example:
* await dbTransaction([STORE_REPORTS, STORE_ATTACHMENTS], 'readwrite', (getStore) => {
* getStore(STORE_REPORTS).delete(reportId);
* getStore(STORE_ATTACHMENTS).delete(attachmentId);
* });
*/
export function dbTransaction(storeNames, mode, callback) {
return new Promise((resolve, reject) => {
const tx = state.db.transaction(storeNames, mode);
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
tx.onabort = () => reject(tx.error || new Error('Transaction aborted'));
const getStore = (name) => tx.objectStore(name);
callback(getStore);
});
}
/* ── Settings helpers ───────────────────────────────────────────────────── */
export async function saveSetting(key, value) {
await dbPut(STORE_SETTINGS, { key, value });
}
export async function loadSetting(key) {
const record = await dbGet(STORE_SETTINGS, key);
return record?.value;
}