2b828a5eeb
* Rename to saveBatch * forgot a file * first pass of cacheReader * OL2 tests have infinite timeout * OL2 refactor works * fix for tests * get rid of pumps and fix test * lint fix * redo mermaid diagrams * add readme section on deferment
127 lines
3.8 KiB
TypeScript
127 lines
3.8 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
|
|
import BatchingQueue from '../../helpers/batchingQueue.js'
|
|
import { CustomLogger, Item } from '../../types/types.js'
|
|
import { isSafari } from '@speckle/shared'
|
|
import { Dexie, DexieOptions, Table } from 'dexie'
|
|
import { Database } from '../interfaces.js'
|
|
|
|
export class ObjectStore extends Dexie {
|
|
static #databaseName: string = 'speckle-cache'
|
|
objects!: Table<Item, string> // Table type: <entity, primaryKey>
|
|
|
|
constructor(options: DexieOptions) {
|
|
super(ObjectStore.#databaseName, options)
|
|
|
|
this.version(1).stores({
|
|
objects: 'baseId, item' // baseId is primary key
|
|
})
|
|
}
|
|
}
|
|
|
|
export interface IndexedDatabaseOptions {
|
|
logger?: CustomLogger
|
|
indexedDB?: IDBFactory
|
|
keyRange?: {
|
|
bound: Function
|
|
lowerBound: Function
|
|
upperBound: Function
|
|
}
|
|
}
|
|
|
|
export default class IndexedDatabase implements Database {
|
|
#options: IndexedDatabaseOptions
|
|
#logger: CustomLogger
|
|
|
|
#cacheDB?: ObjectStore
|
|
|
|
#writeQueue: BatchingQueue<Item> | undefined
|
|
|
|
// #count: number = 0
|
|
|
|
constructor(options: IndexedDatabaseOptions) {
|
|
this.#options = options
|
|
this.#logger = options.logger || ((): void => {})
|
|
}
|
|
|
|
async getAll(keys: string[]): Promise<(Item | undefined)[]> {
|
|
await this.#setupCacheDb()
|
|
let items: (Item | undefined)[] = []
|
|
// this.#count++
|
|
// const startTime = performance.now()
|
|
// this.#logger('Start read ' + x + ' ' + batch.length)
|
|
|
|
//faster than BulkGet with dexie
|
|
await this.#cacheDB!.transaction('r', this.#cacheDB!.objects, async () => {
|
|
const gets = keys.map((key) => this.#cacheDB!.objects.get(key))
|
|
const cachedData = await Promise.all(gets)
|
|
items = cachedData
|
|
})
|
|
// const endTime = performance.now()
|
|
// const duration = endTime - startTime
|
|
//this.#logger('Saved batch ' + x + ' ' + batch.length + ' ' + duration / TIME_MS.second)
|
|
|
|
return items
|
|
}
|
|
|
|
async #openDatabase(): Promise<ObjectStore> {
|
|
const db = new ObjectStore({
|
|
indexedDB: this.#options.indexedDB ?? globalThis.indexedDB,
|
|
IDBKeyRange: this.#options.keyRange ?? IDBKeyRange,
|
|
chromeTransactionDurability: 'relaxed'
|
|
})
|
|
await db.open()
|
|
return db
|
|
}
|
|
|
|
async #setupCacheDb(): Promise<void> {
|
|
if (this.#cacheDB !== undefined) {
|
|
return
|
|
}
|
|
|
|
// Initialize
|
|
await this.#safariFix()
|
|
this.#cacheDB = await this.#openDatabase()
|
|
}
|
|
|
|
async saveBatch(params: { batch: Item[] }): Promise<void> {
|
|
await this.#setupCacheDb()
|
|
const { batch } = params
|
|
//const x = this.#count
|
|
//this.#count++
|
|
|
|
// const startTime = performance.now()
|
|
// this.#logger('Start save ' + x + ' ' + batch.length)
|
|
await this.#cacheDB!.objects.bulkPut(batch)
|
|
// const endTime = performance.now()
|
|
// const duration = endTime - startTime
|
|
//this.#logger('Saved batch ' + x + ' ' + batch.length + ' ' + duration / TIME_MS.second)
|
|
}
|
|
|
|
/**
|
|
* Fixes a Safari bug where IndexedDB requests get lost and never resolve - invoke before you use IndexedDB
|
|
* @link Credits and more info: https://github.com/jakearchibald/safari-14-idb-fix
|
|
*/
|
|
async #safariFix(): Promise<void> {
|
|
// No point putting other browsers or older versions of Safari through this mess.
|
|
if (!isSafari() || !this.#options.indexedDB?.databases) return Promise.resolve()
|
|
|
|
let intervalId: ReturnType<typeof setInterval>
|
|
|
|
return new Promise<void>((resolve: () => void) => {
|
|
const tryIdb = (): Promise<IDBDatabaseInfo[]> | undefined =>
|
|
this.#options.indexedDB?.databases().finally(resolve)
|
|
intervalId = setInterval(() => {
|
|
void tryIdb()
|
|
}, 100)
|
|
void tryIdb()
|
|
}).finally(() => clearInterval(intervalId))
|
|
}
|
|
|
|
async disposeAsync(): Promise<void> {
|
|
this.#cacheDB?.close()
|
|
this.#cacheDB = undefined
|
|
await this.#writeQueue?.disposeAsync()
|
|
this.#writeQueue = undefined
|
|
}
|
|
}
|