2026-01-13 14:49:19 +01:00

98 lines
3.3 KiB
TypeScript

import type { IndexedDBConfig } from './types'
import { IndexedDBError } from './IndexedDBError'
export type CoreAccessors = {
getDb: () => IDBDatabase | null
setDb: (db: IDBDatabase | null) => void
getInitPromise: () => Promise<void> | null
setInitPromise: (promise: Promise<void> | null) => void
getConfig: () => IndexedDBConfig
}
export async function initDatabase(accessors: CoreAccessors): Promise<IDBDatabase> {
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<void> {
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 })
}
}
}