98 lines
3.3 KiB
TypeScript
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 })
|
|
}
|
|
}
|
|
}
|