From ffd2a62c3f7df94365980302fef3bc3376b48182 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 3 Aug 2020 13:00:48 +0530 Subject: modularize repo, use pnpm, improve typechecking --- src/util/query.ts | 575 ------------------------------------------------------ 1 file changed, 575 deletions(-) delete mode 100644 src/util/query.ts (limited to 'src/util/query.ts') diff --git a/src/util/query.ts b/src/util/query.ts deleted file mode 100644 index be319049b..000000000 --- a/src/util/query.ts +++ /dev/null @@ -1,575 +0,0 @@ -/* - This file is part of TALER - (C) 2016 GNUnet e.V. - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see - */ - -/** - * Database query abstractions. - * @module Query - * @author Florian Dold - */ - -/** - * Imports. - */ -import { openPromise } from "./promiseUtils"; - -/** - * Exception that should be thrown by client code to abort a transaction. - */ -export const TransactionAbort = Symbol("transaction_abort"); - -/** - * Definition of an object store. - */ -export class Store { - constructor( - public name: string, - public storeParams?: IDBObjectStoreParameters, - public validator?: (v: T) => T, - ) {} -} - -/** - * Options for an index. - */ -export interface IndexOptions { - /** - * If true and the path resolves to an array, create an index entry for - * each member of the array (instead of one index entry containing the full array). - * - * Defaults to false. - */ - multiEntry?: boolean; -} - -function requestToPromise(req: IDBRequest): Promise { - const stack = Error("Failed request was started here."); - return new Promise((resolve, reject) => { - req.onsuccess = () => { - resolve(req.result); - }; - req.onerror = () => { - console.log("error in DB request", req.error); - reject(req.error); - console.log("Request failed:", stack); - }; - }); -} - -function transactionToPromise(tx: IDBTransaction): Promise { - const stack = Error("Failed transaction was started here."); - return new Promise((resolve, reject) => { - tx.onabort = () => { - reject(TransactionAbort); - }; - tx.oncomplete = () => { - resolve(); - }; - tx.onerror = () => { - console.error("Transaction failed:", stack); - reject(tx.error); - }; - }); -} - -function applyMutation( - req: IDBRequest, - f: (x: T) => T | undefined, -): Promise { - return new Promise((resolve, reject) => { - req.onsuccess = () => { - const cursor = req.result; - if (cursor) { - const val = cursor.value; - const modVal = f(val); - if (modVal !== undefined && modVal !== null) { - const req2: IDBRequest = cursor.update(modVal); - req2.onerror = () => { - reject(req2.error); - }; - req2.onsuccess = () => { - cursor.continue(); - }; - } else { - cursor.continue(); - } - } else { - resolve(); - } - }; - req.onerror = () => { - reject(req.error); - }; - }); -} - -type CursorResult = CursorEmptyResult | CursorValueResult; - -interface CursorEmptyResult { - hasValue: false; -} - -interface CursorValueResult { - hasValue: true; - value: T; -} - -class ResultStream { - private currentPromise: Promise; - private gotCursorEnd = false; - private awaitingResult = false; - - constructor(private req: IDBRequest) { - this.awaitingResult = true; - let p = openPromise(); - this.currentPromise = p.promise; - req.onsuccess = () => { - if (!this.awaitingResult) { - throw Error("BUG: invariant violated"); - } - const cursor = req.result; - if (cursor) { - this.awaitingResult = false; - p.resolve(); - p = openPromise(); - this.currentPromise = p.promise; - } else { - this.gotCursorEnd = true; - p.resolve(); - } - }; - req.onerror = () => { - p.reject(req.error); - }; - } - - async toArray(): Promise { - const arr: T[] = []; - while (true) { - const x = await this.next(); - if (x.hasValue) { - arr.push(x.value); - } else { - break; - } - } - return arr; - } - - async map(f: (x: T) => R): Promise { - const arr: R[] = []; - while (true) { - const x = await this.next(); - if (x.hasValue) { - arr.push(f(x.value)); - } else { - break; - } - } - return arr; - } - - async forEachAsync(f: (x: T) => Promise): Promise { - while (true) { - const x = await this.next(); - if (x.hasValue) { - await f(x.value); - } else { - break; - } - } - } - - async forEach(f: (x: T) => void): Promise { - while (true) { - const x = await this.next(); - if (x.hasValue) { - f(x.value); - } else { - break; - } - } - } - - async filter(f: (x: T) => boolean): Promise { - const arr: T[] = []; - while (true) { - const x = await this.next(); - if (x.hasValue) { - if (f(x.value)) { - arr.push(x.value); - } - } else { - break; - } - } - return arr; - } - - async next(): Promise> { - if (this.gotCursorEnd) { - return { hasValue: false }; - } - if (!this.awaitingResult) { - const cursor: IDBCursor | undefined = this.req.result; - if (!cursor) { - throw Error("assertion failed"); - } - this.awaitingResult = true; - cursor.continue(); - } - await this.currentPromise; - if (this.gotCursorEnd) { - return { hasValue: false }; - } - const cursor = this.req.result; - if (!cursor) { - throw Error("assertion failed"); - } - return { hasValue: true, value: cursor.value }; - } -} - -export class TransactionHandle { - constructor(private tx: IDBTransaction) {} - - put(store: Store, value: T, key?: any): Promise { - const req = this.tx.objectStore(store.name).put(value, key); - return requestToPromise(req); - } - - add(store: Store, value: T, key?: any): Promise { - const req = this.tx.objectStore(store.name).add(value, key); - return requestToPromise(req); - } - - get(store: Store, key: any): Promise { - const req = this.tx.objectStore(store.name).get(key); - return requestToPromise(req); - } - - getIndexed( - index: Index, - key: any, - ): Promise { - const req = this.tx - .objectStore(index.storeName) - .index(index.indexName) - .get(key); - return requestToPromise(req); - } - - iter(store: Store, key?: any): ResultStream { - const req = this.tx.objectStore(store.name).openCursor(key); - return new ResultStream(req); - } - - iterIndexed( - index: Index, - key?: any, - ): ResultStream { - const req = this.tx - .objectStore(index.storeName) - .index(index.indexName) - .openCursor(key); - return new ResultStream(req); - } - - delete(store: Store, key: any): Promise { - const req = this.tx.objectStore(store.name).delete(key); - return requestToPromise(req); - } - - mutate( - store: Store, - key: any, - f: (x: T) => T | undefined, - ): Promise { - const req = this.tx.objectStore(store.name).openCursor(key); - return applyMutation(req, f); - } -} - -function runWithTransaction( - db: IDBDatabase, - stores: Store[], - f: (t: TransactionHandle) => Promise, - mode: "readonly" | "readwrite", -): Promise { - const stack = Error("Failed transaction was started here."); - return new Promise((resolve, reject) => { - const storeName = stores.map((x) => x.name); - const tx = db.transaction(storeName, mode); - let funResult: any = undefined; - let gotFunResult = false; - tx.oncomplete = () => { - // This is a fatal error: The transaction completed *before* - // the transaction function returned. Likely, the transaction - // function waited on a promise that is *not* resolved in the - // microtask queue, thus triggering the auto-commit behavior. - // Unfortunately, the auto-commit behavior of IDB can't be switched - // of. There are some proposals to add this functionality in the future. - if (!gotFunResult) { - const msg = - "BUG: transaction closed before transaction function returned"; - console.error(msg); - reject(Error(msg)); - } - resolve(funResult); - }; - tx.onerror = () => { - console.error("error in transaction"); - console.error(stack); - }; - tx.onabort = () => { - if (tx.error) { - console.error("Transaction aborted with error:", tx.error); - } else { - console.log("Trasaction aborted (no error)"); - } - reject(TransactionAbort); - }; - const th = new TransactionHandle(tx); - const resP = Promise.resolve().then(() => f(th)); - resP - .then((result) => { - gotFunResult = true; - funResult = result; - }) - .catch((e) => { - if (e == TransactionAbort) { - console.info("aborting transaction"); - } else { - console.error("Transaction failed:", e); - console.error(stack); - tx.abort(); - } - }) - .catch((e) => { - console.error("fatal: aborting transaction failed", e); - }); - }); -} - -/** - * Definition of an index. - */ -export class Index { - /** - * Name of the store that this index is associated with. - */ - storeName: string; - - /** - * Options to use for the index. - */ - options: IndexOptions; - - constructor( - s: Store, - public indexName: string, - public keyPath: string | string[], - options?: IndexOptions, - ) { - const defaultOptions = { - multiEntry: false, - }; - this.options = { ...defaultOptions, ...(options || {}) }; - this.storeName = s.name; - } - - /** - * We want to have the key type parameter in use somewhere, - * because otherwise the compiler complains. In iterIndex the - * key type is pretty useful. - */ - protected _dummyKey: S | undefined; -} - -/** - * Return a promise that resolves - * to the taler wallet db. - */ -export function openDatabase( - idbFactory: IDBFactory, - databaseName: string, - databaseVersion: number, - onVersionChange: () => void, - onUpgradeNeeded: ( - db: IDBDatabase, - oldVersion: number, - newVersion: number, - ) => void, -): Promise { - return new Promise((resolve, reject) => { - const req = idbFactory.open(databaseName, databaseVersion); - req.onerror = (e) => { - console.log("taler database error", e); - reject(new Error("database error")); - }; - req.onsuccess = (e) => { - req.result.onversionchange = (evt: IDBVersionChangeEvent) => { - console.log( - `handling live db version change from ${evt.oldVersion} to ${evt.newVersion}`, - ); - req.result.close(); - onVersionChange(); - }; - resolve(req.result); - }; - req.onupgradeneeded = (e) => { - const db = req.result; - const newVersion = e.newVersion; - if (!newVersion) { - throw Error("upgrade needed, but new version unknown"); - } - onUpgradeNeeded(db, e.oldVersion, newVersion); - }; - }); -} - -export class Database { - constructor(private db: IDBDatabase) {} - - static deleteDatabase(idbFactory: IDBFactory, dbName: string): void { - idbFactory.deleteDatabase(dbName); - } - - async exportDatabase(): Promise { - const db = this.db; - const dump = { - name: db.name, - stores: {} as { [s: string]: any }, - version: db.version, - }; - - return new Promise((resolve, reject) => { - const tx = db.transaction(Array.from(db.objectStoreNames)); - tx.addEventListener("complete", () => { - resolve(dump); - }); - // tslint:disable-next-line:prefer-for-of - for (let i = 0; i < db.objectStoreNames.length; i++) { - const name = db.objectStoreNames[i]; - const storeDump = {} as { [s: string]: any }; - dump.stores[name] = storeDump; - tx.objectStore(name) - .openCursor() - .addEventListener("success", (e: Event) => { - const cursor = (e.target as any).result; - if (cursor) { - storeDump[cursor.key] = cursor.value; - cursor.continue(); - } - }); - } - }); - } - - importDatabase(dump: any): Promise { - const db = this.db; - console.log("importing db", dump); - return new Promise((resolve, reject) => { - const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite"); - if (dump.stores) { - for (const storeName in dump.stores) { - const objects = []; - const dumpStore = dump.stores[storeName]; - for (const key in dumpStore) { - objects.push(dumpStore[key]); - } - console.log(`importing ${objects.length} records into ${storeName}`); - const store = tx.objectStore(storeName); - for (const obj of objects) { - store.put(obj); - } - } - } - tx.addEventListener("complete", () => { - resolve(); - }); - }); - } - - async get(store: Store, key: any): Promise { - const tx = this.db.transaction([store.name], "readonly"); - const req = tx.objectStore(store.name).get(key); - const v = await requestToPromise(req); - await transactionToPromise(tx); - return v; - } - - async getIndexed( - index: Index, - key: any, - ): Promise { - const tx = this.db.transaction([index.storeName], "readonly"); - const req = tx.objectStore(index.storeName).index(index.indexName).get(key); - const v = await requestToPromise(req); - await transactionToPromise(tx); - return v; - } - - async put(store: Store, value: T, key?: any): Promise { - const tx = this.db.transaction([store.name], "readwrite"); - const req = tx.objectStore(store.name).put(value, key); - const v = await requestToPromise(req); - await transactionToPromise(tx); - return v; - } - - async mutate( - store: Store, - key: any, - f: (x: T) => T | undefined, - ): Promise { - const tx = this.db.transaction([store.name], "readwrite"); - const req = tx.objectStore(store.name).openCursor(key); - await applyMutation(req, f); - await transactionToPromise(tx); - } - - iter(store: Store): ResultStream { - const tx = this.db.transaction([store.name], "readonly"); - const req = tx.objectStore(store.name).openCursor(); - return new ResultStream(req); - } - - iterIndex( - index: Index, - query?: any, - ): ResultStream { - const tx = this.db.transaction([index.storeName], "readonly"); - const req = tx - .objectStore(index.storeName) - .index(index.indexName) - .openCursor(query); - return new ResultStream(req); - } - - async runWithReadTransaction( - stores: Store[], - f: (t: TransactionHandle) => Promise, - ): Promise { - return runWithTransaction(this.db, stores, f, "readonly"); - } - - async runWithWriteTransaction( - stores: Store[], - f: (t: TransactionHandle) => Promise, - ): Promise { - return runWithTransaction(this.db, stores, f, "readwrite"); - } -} -- cgit v1.2.3