import {openDB, deleteDB} from "idb" import type {IDBPDatabase} from "idb" import type {Maybe} from "@welshman/lib" export type IDBStore = { name: string keyPath: string } export type IDBOptions = { name: string stores: IDBStore[] } export class IDB { connection: Maybe>> failedToConnect = false constructor(readonly options: IDBOptions) {} // Object stores can only be created during a version change, and which stores we need depends // on who is logged in, so open at whatever version exists and bump it to add missing ones. private open = async () => { const {name, stores} = this.options const db = await openDB(name) const missing = stores.filter(store => !db.objectStoreNames.contains(store.name)) if (missing.length === 0) { return db } const version = db.version + 1 db.close() return openDB(name, version, { upgrade(idbDb: IDBPDatabase) { for (const {name, keyPath} of missing) { idbDb.createObjectStore(name, {keyPath}) } }, blocked() {}, blocking() {}, }) } async connect() { if (!this.failedToConnect && !this.connection) { this.connection = this.open().catch(e => { console.error("Failed to connect to indexeddb", e) this.failedToConnect = true return undefined }) } return this.connection } table = (name: string) => new IDBTable(this, name) getAll = async (table: string): Promise => { const connection = await this.connect() if (!connection) return [] const tx = connection.transaction(table, "readonly") const store = tx.objectStore(table) const result = await store.getAll() await tx.done return result || [] } bulkPut = async (table: string, data: Iterable) => { const connection = await this.connect() if (!connection) return const tx = connection.transaction(table, "readwrite") const store = tx.objectStore(table) await Promise.all( Array.from(data).map(item => { try { store.put(item) } catch (e) { console.error(e, item) } }), ) await tx.done } bulkDelete = async (table: string, ids: Iterable) => { const connection = await this.connect() if (!connection) return const tx = connection.transaction(table, "readwrite") const store = tx.objectStore(table) await Promise.all(Array.from(ids).map(id => store.delete(id))) await tx.done } close = () => { this.connection?.then(c => c?.close()) this.connection = undefined } clear = async () => { await this.connection?.then(c => c?.close()) await deleteDB(this.options.name, { blocked() {}, }) } } export class IDBTable { constructor( readonly db: IDB, readonly name: string, ) {} getAll = () => this.db.getAll(this.name) bulkPut = (data: Iterable) => this.db.bulkPut(this.name, data) bulkDelete = (ids: Iterable) => this.db.bulkDelete(this.name, ids) }