Skip to content

Instantly share code, notes, and snippets.

@retorquere
Created February 12, 2024 11:40
Show Gist options
  • Save retorquere/f23bb48673e1d0f146fc65bacf6cf464 to your computer and use it in GitHub Desktop.
Save retorquere/f23bb48673e1d0f146fc65bacf6cf464 to your computer and use it in GitHub Desktop.
// class from https://gist.github.com/lentschi/3fcbf7f6894b11bee1eb3d6a832a84e6
/**
* Asyncronous iterable iterator for IndexedDB object stores or indexes
*
* @author Florian Lentsch <office@florian-lentsch.at>
* @license MIT
*
* ____________________________________________________________________________
* REQUIREMENTS: You may have to add "lib": ["esnext.asynciterable"] to
* your tsconfig.json - see https://stackoverflow.com/questions/43694281/ts2318-cannot-find-global-type-asynciterableiterator-async-generator?answertab=votes#tab-top
*
* ____________________________________________________________________________
* USAGE SAMPLE (Based on the samples at https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB):
*
* const customersStore = db.transaction("customers", "readwrite").objectStore("customers")
* // List all customers:
* const allCustomers = new IDBIterator(customersStore)
* for await (let customer of allCustomers) {
* console.log("Customer", customer)
* }
*
* // List Donnas:
* const customersCalledDonna = new IDBIterator(
* customersStore.index("name"),
* IDBKeyRange.only("Donna")
* )
* for await (let customer of customersCalledDonna) {
* console.log("Customer called 'Donna'", customer)
* }
*
*
*/
class IDBIterator implements AsyncIterableIterator<any> {
private cursorRequest: IDBRequest
private cursor: IDBCursorWithValue
private curResolver: (result: IteratorResult<any>) => void
constructor(private objectStore: IDBObjectStore | IDBIndex, private keyRange: IDBKeyRange = null) { }
[Symbol.asyncIterator](): AsyncIterableIterator<any> {
return this
}
public next(): Promise<IteratorResult<any>> {
return new Promise<IteratorResult<any>>(async resolve => {
// We need to store the resolver as an instance variable, since else the
// success callback would always try to resolve the promise of the first
// next() call only:
this.curResolver = resolve
if (!this.cursorRequest) {
// Initial request -> Open the cursor and listen for subsequent success events:
this.cursorRequest = this.objectStore.openCursor(this.keyRange)
this.cursorRequest.onsuccess = (e) => {
this.cursor = <IDBCursorWithValue>(<any>e.target).result
if (this.cursor) {
this.curResolver({ value: this.cursor.value, done: false })
}
else {
// We have reached the end:
this.curResolver({ value: null, done: true })
}
}
}
else {
// 2nd request or later -> continue (Still, the above success
// listener will be called when the cursor has been moved forward):
this.cursor.continue()
}
})
}
}
export async function promise(request) {
return new Promise((resolve, reject) => {
request.onsuccess = function(event) {
resolve(event.target.result)
}
request.onerror = function(event) {
reject(event.target.error)
}
request.onabort = function(event) {
reject(new Error('Abort'))
}
})
}
type T = any
type PK = any
class Store {
constructor(public objectStore: IDBObjectStore) { }
async add(object: T): Promise<PK> {
return promise(this.objectStore.add(object))
}
async put(object: T): Promise<PK> {
return promise(this.objectStore.put(object))
}
async delete(object: T): Promise<void> {
return promise(this.objectStore.delete(object))
}
async get(key: PK): Promise<void> {
return promise(this.objectStore.get(key))
}
async getAll(): Promise<T[]> {
return promise(this.objectStore.getAll())
}
async getAllKeys(): Promise<PK[]> {
return promise(this.objectStore.getAllKeys())
}
async clear(): Promise<void> {
return promise(this.objectStore.getAllKeys())
}
each(keyRange: IDBKeyRange = null): IDBIterator {
return new IDBIterator(this.objectStore, keyRange)
}
}
export function tx(db, stores, mode): { tx: IDBTransaction, [key: string]: IDBObjectStore } {
if (typeof stores === 'string') stores = [ stores ]
const transaction = db.transaction(stores, mode)
const env = { tx: promise(transaction) }
for (const store of stores) {
env[store] = new Store(env.tx.objectStore(store))
}
return env
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment