From f525aeb45232c66910b87cd48bfd74b8c1d7eb5a Mon Sep 17 00:00:00 2001 From: Emiliano Heyns Date: Tue, 7 May 2024 16:55:08 +0200 Subject: [PATCH] indexed --- content/db/indexed.ts | 77 +++++++++++++++++++++++++++++++++++ typings/async-indexed-db.d.ts | 34 ++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 content/db/indexed.ts create mode 100644 typings/async-indexed-db.d.ts diff --git a/content/db/indexed.ts b/content/db/indexed.ts new file mode 100644 index 0000000000..7d2fbc8c0b --- /dev/null +++ b/content/db/indexed.ts @@ -0,0 +1,77 @@ +import { default as AsyncIndexedDB, AsyncIDBObjectStore } from 'async-indexed-db' + +export class Cache extends AsyncIndexedDB { + constructor() { + // eslint-disable-next-line @typescript-eslint/no-empty-function + super('better-bibtex:cache', async (_db: IDBDatabase) => {}, 1) + } + + async open(lastUpdated?: string): Promise { + if (this.db) return this + + return new Promise((resolve, reject) => { + const openRequest = indexedDB.open(this.name, 1) + + openRequest.onerror = openRequest.onblocked = (): void => { + const error = `could not open cache ${this.name}: ${openRequest.error?.message || 'Unknown error'}` + Zotero.debug(error) + reject(new Error(error)) + } + + openRequest.onsuccess = async () => { + try { + this.db = openRequest.result + const clear = lastUpdated && (lastUpdated > (Zotero.Prefs.get('translators.better-bibtex.cache.lastUpdated') || '')) + if (clear) { + await this.tx(['ExportFormat', 'Exported', 'ExportContext'], 'readwriteflush', async ({ ExportFormat, Exported, ExportContext }) => { + await Promise.all([ExportFormat.clear(), Exported.clear(), ExportContext.clear()]) + }) + } + resolve(this) + } + catch (err) { + reject(err) + } + } + + openRequest.onupgradeneeded = () => { + const cache = openRequest.result + const stores = { + ExportFormat: { keyPath: 'itemID', indices: undefined }, + /* + Exported: { keyPath: ['context', 'itemID'], indices: { // keyPath order matters for key retrieval! + itemID: { unique: false }, + context: { unique: false } + } }, + ExportContext:{ keyPath: 'id', autoIncrement: true, indices: { + properties: { unique: false, multiEntry: true }, + } } + */ + } + + for (const [name, config] of Object.entries(stores)) { + if (cache.objectStoreNames.contains(name)) cache.deleteObjectStore(name) + const indices = config.indices + delete config.indices + const store = cache.createObjectStore(name, config) + if (indices) { + for (const [index, setup] of Object.entries(indices)) { + store.createIndex(index, index, setup) + } + } + } + } + }) + } + + public async tx(stores: string | string[], mode: 'readonly' | 'readwrite' | 'readwriteflush' = 'readonly', handler: (stores: Record) => Promise): Promise { + if (typeof stores === 'string') stores = [ stores ] + const tx = this.db.transaction(stores, mode as IDBTransactionMode) + const env: Record = {} + for (const store of stores) { + env[store] = AsyncIndexedDB.proxy(tx.objectStore(store)) as AsyncIDBObjectStore + } + await handler(env) + tx.commit() + } +} diff --git a/typings/async-indexed-db.d.ts b/typings/async-indexed-db.d.ts new file mode 100644 index 0000000000..7f54dfe874 --- /dev/null +++ b/typings/async-indexed-db.d.ts @@ -0,0 +1,34 @@ +declare module 'async-indexed-db' { + class AsyncIndexedDB { + public name: string + public db: IDBDatabase + + constructor(name: string, schema: (db: IDBDatabase) => Promise, version: number) + + static proxy(obj: any): any + } + + export class AsyncIDBObjectStore { + name: string + keyPath: string | string[] | null + indexNames: DOMStringList + // transaction: IDBTransaction + autoIncrement: boolean + async add(value: any, key?: IDBValidKey | IDBKeyRange | null): Promise + clear(): Promise + // count(key?: IDBValidKey | IDBKeyRange | null): IDBRequest + // createIndex(name: string, keyPath: string | string[], options?: IDBIndexParameters): IDBIndex + // delete(key: IDBValidKey | IDBKeyRange): IDBRequest + // deleteIndex(indexName: string): void + // get(key: IDBValidKey | IDBKeyRange): IDBRequest + // getAll(query?: IDBValidKey | IDBKeyRange | null, count?: number): IDBRequest + // getAllKeys(query?: IDBValidKey | IDBKeyRange | null, count?: number): IDBRequest + // getKey(key: IDBValidKey | IDBKeyRange): IDBRequest + // index(name: string): IDBIndex + // openCursor(range?: IDBValidKey | IDBKeyRange | null, direction?: IDBCursorDirection): IDBRequest + // openKeyCursor(range?: IDBValidKey | IDBKeyRange | null, direction?: IDBCursorDirection): IDBRequest + // put(value: any, key?: IDBValidKey | IDBKeyRange | null): IDBRequest + } + + export = AsyncIndexedDB +}