diff options
Diffstat (limited to 'packages/taler-wallet-core/src/query.ts')
-rw-r--r-- | packages/taler-wallet-core/src/query.ts | 165 |
1 files changed, 142 insertions, 23 deletions
diff --git a/packages/taler-wallet-core/src/query.ts b/packages/taler-wallet-core/src/query.ts index cffad84df..dc15bbdd1 100644 --- a/packages/taler-wallet-core/src/query.ts +++ b/packages/taler-wallet-core/src/query.ts @@ -15,8 +15,9 @@ */ /** - * Database query abstractions. - * @module Query + * @fileoverview + * Query helpers for IndexedDB databases. + * * @author Florian Dold */ @@ -31,10 +32,16 @@ import { IDBKeyRange, IDBRequest, IDBTransaction, + IDBTransactionMode, IDBValidKey, IDBVersionChangeEvent, } from "@gnu-taler/idb-bridge"; -import { Codec, Logger, openPromise } from "@gnu-taler/taler-util"; +import { + CancellationToken, + Codec, + Logger, + openPromise, +} from "@gnu-taler/taler-util"; const logger = new Logger("query.ts"); @@ -557,6 +564,7 @@ function runTx<Arg, Res>( tx: IDBTransaction, arg: Arg, f: (t: Arg, t2: IDBTransaction) => Promise<Res>, + triggerContext: InternalTriggerContext, ): Promise<Res> { const stack = Error("Failed transaction was started here."); return new Promise((resolve, reject) => { @@ -577,6 +585,7 @@ function runTx<Arg, Res>( logger.error(`${stack.stack}`); reject(Error(msg)); } + triggerContext.handleAfterCommit(); resolve(funResult); }; tx.onerror = () => { @@ -621,6 +630,7 @@ function runTx<Arg, Res>( function makeReadContext( tx: IDBTransaction, storePick: { [n: string]: StoreWithIndexes<any, any, any> }, + triggerContext: InternalTriggerContext, ): any { const ctx: { [s: string]: StoreReadOnlyAccessor<any, any> } = {}; for (const storeAlias in storePick) { @@ -633,10 +643,12 @@ function makeReadContext( const indexName = indexDescriptor.name; indexes[indexAlias] = { get(key) { + triggerContext.storesAccessed.add(storeName); const req = tx.objectStore(storeName).index(indexName).get(key); return requestToPromise(req); }, iter(query) { + triggerContext.storesAccessed.add(storeName); const req = tx .objectStore(storeName) .index(indexName) @@ -644,6 +656,7 @@ function makeReadContext( return new ResultStream<any>(req); }, getAll(query, count) { + triggerContext.storesAccessed.add(storeName); const req = tx .objectStore(storeName) .index(indexName) @@ -651,6 +664,7 @@ function makeReadContext( return requestToPromise(req); }, getAllKeys(query, count) { + triggerContext.storesAccessed.add(storeName); const req = tx .objectStore(storeName) .index(indexName) @@ -658,6 +672,7 @@ function makeReadContext( return requestToPromise(req); }, count(query) { + triggerContext.storesAccessed.add(storeName); const req = tx.objectStore(storeName).index(indexName).count(query); return requestToPromise(req); }, @@ -666,14 +681,17 @@ function makeReadContext( ctx[storeAlias] = { indexes, get(key) { + triggerContext.storesAccessed.add(storeName); const req = tx.objectStore(storeName).get(key); return requestToPromise(req); }, getAll(query, count) { + triggerContext.storesAccessed.add(storeName); const req = tx.objectStore(storeName).getAll(query, count); return requestToPromise(req); }, iter(query) { + triggerContext.storesAccessed.add(storeName); const req = tx.objectStore(storeName).openCursor(query); return new ResultStream<any>(req); }, @@ -685,6 +703,7 @@ function makeReadContext( function makeWriteContext( tx: IDBTransaction, storePick: { [n: string]: StoreWithIndexes<any, any, any> }, + triggerContext: InternalTriggerContext, ): any { const ctx: { [s: string]: StoreReadWriteAccessor<any, any> } = {}; for (const storeAlias in storePick) { @@ -697,10 +716,12 @@ function makeWriteContext( const indexName = indexDescriptor.name; indexes[indexAlias] = { get(key) { + triggerContext.storesAccessed.add(storeName); const req = tx.objectStore(storeName).index(indexName).get(key); return requestToPromise(req); }, iter(query) { + triggerContext.storesAccessed.add(storeName); const req = tx .objectStore(storeName) .index(indexName) @@ -708,6 +729,7 @@ function makeWriteContext( return new ResultStream<any>(req); }, getAll(query, count) { + triggerContext.storesAccessed.add(storeName); const req = tx .objectStore(storeName) .index(indexName) @@ -715,6 +737,7 @@ function makeWriteContext( return requestToPromise(req); }, getAllKeys(query, count) { + triggerContext.storesAccessed.add(storeName); const req = tx .objectStore(storeName) .index(indexName) @@ -722,6 +745,7 @@ function makeWriteContext( return requestToPromise(req); }, count(query) { + triggerContext.storesAccessed.add(storeName); const req = tx.objectStore(storeName).index(indexName).count(query); return requestToPromise(req); }, @@ -730,18 +754,23 @@ function makeWriteContext( ctx[storeAlias] = { indexes, get(key) { + triggerContext.storesAccessed.add(storeName); const req = tx.objectStore(storeName).get(key); return requestToPromise(req); }, getAll(query, count) { + triggerContext.storesAccessed.add(storeName); const req = tx.objectStore(storeName).getAll(query, count); return requestToPromise(req); }, iter(query) { + triggerContext.storesAccessed.add(storeName); const req = tx.objectStore(storeName).openCursor(query); return new ResultStream<any>(req); }, async add(r, k) { + triggerContext.storesAccessed.add(storeName); + triggerContext.storesModified.add(storeName); const req = tx.objectStore(storeName).add(r, k); const key = await requestToPromise(req); return { @@ -749,6 +778,8 @@ function makeWriteContext( }; }, async put(r, k) { + triggerContext.storesAccessed.add(storeName); + triggerContext.storesModified.add(storeName); const req = tx.objectStore(storeName).put(r, k); const key = await requestToPromise(req); return { @@ -756,6 +787,8 @@ function makeWriteContext( }; }, delete(k) { + triggerContext.storesAccessed.add(storeName); + triggerContext.storesModified.add(storeName); const req = tx.objectStore(storeName).delete(k); return requestToPromise(req); }, @@ -766,6 +799,7 @@ function makeWriteContext( export interface DbAccess<StoreMap> { idbHandle(): IDBDatabase; + runAllStoresReadWriteTx<T>( options: { label?: string; @@ -774,6 +808,7 @@ export interface DbAccess<StoreMap> { tx: DbReadWriteTransaction<StoreMap, Array<StoreNames<StoreMap>>>, ) => Promise<T>, ): Promise<T>; + runAllStoresReadOnlyTx<T>( options: { label?: string; @@ -782,16 +817,67 @@ export interface DbAccess<StoreMap> { tx: DbReadOnlyTransaction<StoreMap, Array<StoreNames<StoreMap>>>, ) => Promise<T>, ): Promise<T>; + runReadWriteTx<T, StoreNameArray extends Array<StoreNames<StoreMap>>>( - storeNames: StoreNameArray, + opts: { + storeNames: StoreNameArray; + label?: string; + }, txf: (tx: DbReadWriteTransaction<StoreMap, StoreNameArray>) => Promise<T>, ): Promise<T>; + runReadOnlyTx<T, StoreNameArray extends Array<StoreNames<StoreMap>>>( - storeNames: StoreNameArray, + opts: { + storeNames: StoreNameArray; + label?: string; + }, txf: (tx: DbReadOnlyTransaction<StoreMap, StoreNameArray>) => Promise<T>, ): Promise<T>; } +export interface AfterCommitInfo { + mode: IDBTransactionMode; + scope: Set<string>; + accessedStores: Set<string>; + modifiedStores: Set<string>; +} + +export interface TriggerSpec { + /** + * Trigger run after every successful commit, run outside of the transaction. + */ + afterCommit?: (info: AfterCommitInfo) => void; + + // onRead(store, value) + // initState<State> () => State + // beforeCommit<State>? (tx: Transaction, s: State | undefined) => Promise<void>; +} + +class InternalTriggerContext { + storesScope: Set<string>; + storesAccessed: Set<string> = new Set(); + storesModified: Set<string> = new Set(); + + constructor( + private triggerSpec: TriggerSpec, + private mode: IDBTransactionMode, + scope: string[], + ) { + this.storesScope = new Set(scope); + } + + handleAfterCommit() { + if (this.triggerSpec.afterCommit) { + this.triggerSpec.afterCommit({ + mode: this.mode, + accessedStores: this.storesAccessed, + modifiedStores: this.storesModified, + scope: this.storesScope, + }); + } + } +} + /** * Type-safe access to a database with a particular store map. * @@ -801,6 +887,8 @@ export class DbAccessImpl<StoreMap> implements DbAccess<StoreMap> { constructor( private db: IDBDatabase, private stores: StoreMap, + private triggers: TriggerSpec = {}, + private cancellationToken: CancellationToken, ) {} idbHandle(): IDBDatabase { @@ -823,12 +911,18 @@ export class DbAccessImpl<StoreMap> implements DbAccess<StoreMap> { strStoreNames.push(swi.storeName); accessibleStores[swi.storeName] = swi; } - const tx = this.db.transaction(strStoreNames, "readwrite"); - const writeContext = makeWriteContext(tx, accessibleStores); - return runTx(tx, writeContext, txf); + const mode = "readwrite"; + const triggerContext = new InternalTriggerContext( + this.triggers, + mode, + strStoreNames, + ); + const tx = this.db.transaction(strStoreNames, mode); + const writeContext = makeWriteContext(tx, accessibleStores, triggerContext); + return runTx(tx, writeContext, txf, triggerContext); } - runAllStoresReadOnlyTx<T>( + async runAllStoresReadOnlyTx<T>( options: { label?: string; }, @@ -844,42 +938,67 @@ export class DbAccessImpl<StoreMap> implements DbAccess<StoreMap> { strStoreNames.push(swi.storeName); accessibleStores[swi.storeName] = swi; } - const tx = this.db.transaction(strStoreNames, "readonly"); - const writeContext = makeReadContext(tx, accessibleStores); - return runTx(tx, writeContext, txf); + const mode = "readonly"; + const triggerContext = new InternalTriggerContext( + this.triggers, + mode, + strStoreNames, + ); + const tx = this.db.transaction(strStoreNames, mode); + const writeContext = makeReadContext(tx, accessibleStores, triggerContext); + const res = await runTx(tx, writeContext, txf, triggerContext); + return res; } - runReadWriteTx<T, StoreNameArray extends Array<StoreNames<StoreMap>>>( - storeNames: StoreNameArray, + async runReadWriteTx<T, StoreNameArray extends Array<StoreNames<StoreMap>>>( + opts: { + storeNames: StoreNameArray; + }, txf: (tx: DbReadWriteTransaction<StoreMap, StoreNameArray>) => Promise<T>, ): Promise<T> { const accessibleStores: { [x: string]: StoreWithIndexes<any, any, any> } = {}; const strStoreNames: string[] = []; - for (const sn of storeNames) { + for (const sn of opts.storeNames) { const swi = (this.stores as any)[sn] as StoreWithIndexes<any, any, any>; strStoreNames.push(swi.storeName); accessibleStores[swi.storeName] = swi; } - const tx = this.db.transaction(strStoreNames, "readwrite"); - const writeContext = makeWriteContext(tx, accessibleStores); - return runTx(tx, writeContext, txf); + const mode = "readwrite"; + const triggerContext = new InternalTriggerContext( + this.triggers, + mode, + strStoreNames, + ); + const tx = this.db.transaction(strStoreNames, mode); + const writeContext = makeWriteContext(tx, accessibleStores, triggerContext); + const res = await runTx(tx, writeContext, txf, triggerContext); + return res; } runReadOnlyTx<T, StoreNameArray extends Array<StoreNames<StoreMap>>>( - storeNames: StoreNameArray, + opts: { + storeNames: StoreNameArray; + }, txf: (tx: DbReadOnlyTransaction<StoreMap, StoreNameArray>) => Promise<T>, ): Promise<T> { const accessibleStores: { [x: string]: StoreWithIndexes<any, any, any> } = {}; const strStoreNames: string[] = []; - for (const sn of storeNames) { + for (const sn of opts.storeNames) { const swi = (this.stores as any)[sn] as StoreWithIndexes<any, any, any>; strStoreNames.push(swi.storeName); accessibleStores[swi.storeName] = swi; } - const tx = this.db.transaction(strStoreNames, "readonly"); - const readContext = makeReadContext(tx, accessibleStores); - return runTx(tx, readContext, txf); + const mode = "readonly"; + const triggerContext = new InternalTriggerContext( + this.triggers, + mode, + strStoreNames, + ); + const tx = this.db.transaction(strStoreNames, mode); + const readContext = makeReadContext(tx, accessibleStores, triggerContext); + const res = runTx(tx, readContext, txf, triggerContext); + return res; } } |