summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/query.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src/query.ts')
-rw-r--r--packages/taler-wallet-core/src/query.ts165
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;
}
}