import {openDB, deleteDB} from "idb" import type {IDBPDatabase} from "idb" import type {Unsubscriber} from "svelte/store" import {call} from "@welshman/lib" import type {Maybe} from "@welshman/lib" export type IDBAdapter = { name: string keyPath: string init: (table: IDBTable) => Promise } export type IDBAdapters = IDBAdapter[] export type IDBOptions = { name: string version: number } export class IDB { adapters: IDBAdapters = [] connection: Maybe> unsubscribers: Maybe failedToConnect = false constructor(readonly options: IDBOptions) {} async connect() { if (!this.failedToConnect && !this.connection) { const {name, version} = this.options const adapters = this.adapters try { this.connection = openDB(name, version, { upgrade(idbDb: IDBPDatabase) { const names = new Set(adapters.map(a => a.name)) for (const table of idbDb.objectStoreNames) { if (!names.has(table)) { idbDb.deleteObjectStore(table) } } for (const {name, keyPath} of adapters) { try { idbDb.createObjectStore(name, {keyPath}) } catch (e) { console.warn(e) } } }, blocked() {}, blocking() {}, }) this.unsubscribers = await Promise.all( adapters.map(({name, init}) => init(this.table(name))), ) } catch (e) { console.error("Failed to connect to indexeddb", e) this.failedToConnect = true } } 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, "readwrite") 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.unsubscribers?.forEach(call) this.unsubscribers = undefined 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) }