import type { IndexedDBConfig } from './types' import { IndexedDBError } from './IndexedDBError' export type CoreAccessors = { getDb: () => IDBDatabase | null setDb: (db: IDBDatabase | null) => void getInitPromise: () => Promise | null setInitPromise: (promise: Promise | null) => void getConfig: () => IndexedDBConfig } export async function initDatabase(accessors: CoreAccessors): Promise { const existing = accessors.getDb() if (existing) { return existing } const pending = accessors.getInitPromise() if (pending) { await pending const db = accessors.getDb() if (db) { return db } throw new IndexedDBError('Database initialization failed', 'init', accessors.getConfig().storeName) } const initPromise = openDatabase(accessors) accessors.setInitPromise(initPromise) try { await initPromise const db = accessors.getDb() if (!db) { throw new IndexedDBError('Database not initialized after open', 'init', accessors.getConfig().storeName) } return db } catch (error) { accessors.setInitPromise(null) throw new IndexedDBError(error instanceof Error ? error.message : 'Unknown error', 'init', accessors.getConfig().storeName, error) } } export function openDatabase(accessors: CoreAccessors): Promise { return new Promise((resolve, reject) => { const config = accessors.getConfig() if (typeof window === 'undefined' || !window.indexedDB) { reject(new IndexedDBError('IndexedDB is not available', 'openDatabase', config.storeName)) return } const request = window.indexedDB.open(config.dbName, config.version) request.onerror = (): void => { reject(new IndexedDBError(`Failed to open IndexedDB: ${request.error}`, 'openDatabase', config.storeName, request.error)) } request.onsuccess = (): void => { accessors.setDb(request.result) resolve() } request.onupgradeneeded = (event: IDBVersionChangeEvent): void => { handleUpgrade({ config, event }) } }) } function handleUpgrade(params: { config: IndexedDBConfig; event: IDBVersionChangeEvent }): void { const db = (params.event.target as IDBOpenDBRequest).result if (!db.objectStoreNames.contains(params.config.storeName)) { createObjectStore(db, params.config) } else { createMissingIndexes(db, params.event, params.config) } params.config.onUpgrade?.(db, params.event) } function createObjectStore(db: IDBDatabase, config: IndexedDBConfig): void { const store = db.createObjectStore(config.storeName, { keyPath: config.keyPath }) if (!config.indexes) { return } for (const index of config.indexes) { if (!store.indexNames.contains(index.name)) { store.createIndex(index.name, index.keyPath, { unique: index.unique ?? false }) } } } function createMissingIndexes(_db: IDBDatabase, event: IDBVersionChangeEvent, config: IndexedDBConfig): void { const target = event.target as IDBOpenDBRequest const { transaction } = target if (!transaction || !config.indexes) { return } const store = transaction.objectStore(config.storeName) for (const index of config.indexes) { if (!store.indexNames.contains(index.name)) { store.createIndex(index.name, index.keyPath, { unique: index.unique ?? false }) } } }