summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-04-23 01:52:06 +0200
committerFlorian Dold <florian@dold.me>2024-04-23 01:52:06 +0200
commit4b69853c347071acb73efcde9d4969cf06d0dfcc (patch)
tree01ed3cbf9d5939dc44f3ce6c31b71edd8dbdb43e /packages
parent5c0f03f102848d208248e1508f1da8316ebf6407 (diff)
downloadwallet-core-4b69853c347071acb73efcde9d4969cf06d0dfcc.tar.gz
wallet-core-4b69853c347071acb73efcde9d4969cf06d0dfcc.tar.bz2
wallet-core-4b69853c347071acb73efcde9d4969cf06d0dfcc.zip
wallet-core: simple DB trigger mechanism
Diffstat (limited to 'packages')
-rw-r--r--packages/taler-wallet-core/src/query.ts126
-rw-r--r--packages/taler-wallet-core/src/wallet.ts39
2 files changed, 137 insertions, 28 deletions
diff --git a/packages/taler-wallet-core/src/query.ts b/packages/taler-wallet-core/src/query.ts
index bdc8df0c7..0a321b835 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
*/
@@ -563,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) => {
@@ -583,6 +585,7 @@ function runTx<Arg, Res>(
logger.error(`${stack.stack}`);
reject(Error(msg));
}
+ triggerContext.handleAfterCommit();
resolve(funResult);
};
tx.onerror = () => {
@@ -627,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) {
@@ -639,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)
@@ -650,6 +656,7 @@ function makeReadContext(
return new ResultStream<any>(req);
},
getAll(query, count) {
+ triggerContext.storesAccessed.add(storeName);
const req = tx
.objectStore(storeName)
.index(indexName)
@@ -657,6 +664,7 @@ function makeReadContext(
return requestToPromise(req);
},
getAllKeys(query, count) {
+ triggerContext.storesAccessed.add(storeName);
const req = tx
.objectStore(storeName)
.index(indexName)
@@ -664,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);
},
@@ -672,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);
},
@@ -691,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) {
@@ -703,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)
@@ -714,6 +729,7 @@ function makeWriteContext(
return new ResultStream<any>(req);
},
getAll(query, count) {
+ triggerContext.storesAccessed.add(storeName);
const req = tx
.objectStore(storeName)
.index(indexName)
@@ -721,6 +737,7 @@ function makeWriteContext(
return requestToPromise(req);
},
getAllKeys(query, count) {
+ triggerContext.storesAccessed.add(storeName);
const req = tx
.objectStore(storeName)
.index(indexName)
@@ -728,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);
},
@@ -736,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 {
@@ -755,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 {
@@ -762,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);
},
@@ -802,11 +829,47 @@ export interface DbAccess<StoreMap> {
): 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?: (mode: IDBTransactionMode, stores: string[]) => void;
+ 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,
+ });
+ }
+ }
}
/**
@@ -842,12 +905,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;
},
@@ -863,12 +932,19 @@ 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>>>(
+ async runReadWriteTx<T, StoreNameArray extends Array<StoreNames<StoreMap>>>(
storeNames: StoreNameArray,
txf: (tx: DbReadWriteTransaction<StoreMap, StoreNameArray>) => Promise<T>,
): Promise<T> {
@@ -881,12 +957,14 @@ export class DbAccessImpl<StoreMap> implements DbAccess<StoreMap> {
accessibleStores[swi.storeName] = swi;
}
const mode = "readwrite";
+ const triggerContext = new InternalTriggerContext(
+ this.triggers,
+ mode,
+ strStoreNames,
+ );
const tx = this.db.transaction(strStoreNames, mode);
- const writeContext = makeWriteContext(tx, accessibleStores);
- const res = runTx(tx, writeContext, txf);
- if (this.triggers.afterCommit) {
- this.triggers.afterCommit(mode, strStoreNames);
- }
+ const writeContext = makeWriteContext(tx, accessibleStores, triggerContext);
+ const res = await runTx(tx, writeContext, txf, triggerContext);
return res;
}
@@ -903,16 +981,14 @@ export class DbAccessImpl<StoreMap> implements DbAccess<StoreMap> {
accessibleStores[swi.storeName] = swi;
}
const mode = "readonly";
+ const triggerContext = new InternalTriggerContext(
+ this.triggers,
+ mode,
+ strStoreNames,
+ );
const tx = this.db.transaction(strStoreNames, mode);
- const readContext = makeReadContext(tx, accessibleStores);
- const res = runTx(tx, readContext, txf);
- if (this.triggers.afterCommit) {
- this.triggers.afterCommit(mode, strStoreNames);
- }
+ const readContext = makeReadContext(tx, accessibleStores, triggerContext);
+ const res = runTx(tx, readContext, txf, triggerContext);
return res;
}
-
- registerPostCommitTrigger(args: {
- handler: (storeNames: string[]) => void;
- }): void {}
}
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index 9f9b90446..810c78583 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -243,7 +243,12 @@ import {
checkPeerPushDebit,
initiatePeerPushDebit,
} from "./pay-peer-push-debit.js";
-import { DbAccess, DbAccessImpl } from "./query.js";
+import {
+ AfterCommitInfo,
+ DbAccess,
+ DbAccessImpl,
+ TriggerSpec,
+} from "./query.js";
import { forceRefresh } from "./refresh.js";
import {
TaskScheduler,
@@ -665,7 +670,7 @@ export interface PendingOperationsResponse {
/**
* Implementation of the "wallet-core" API.
*/
-async function dispatchRequestInternal<Op extends WalletApiOperation>(
+async function dispatchRequestInternal(
wex: WalletExecutionContext,
cts: CancellationToken.Source,
operation: WalletApiOperation,
@@ -1713,6 +1718,34 @@ export class Cache<T> {
}
/**
+ * Implementation of triggers for the wallet DB.
+ */
+class WalletDbTriggerSpec implements TriggerSpec {
+ constructor(public ws: InternalWalletState) {}
+
+ afterCommit(info: AfterCommitInfo): void {
+ if (info.mode !== "readwrite") {
+ return;
+ }
+ logger.info(
+ `in after commit callback for readwrite, modified ${j2s([
+ ...info.modifiedStores,
+ ])}`,
+ );
+ const modified = info.accessedStores;
+ if (
+ modified.has(WalletStoresV1.exchanges.storeName) ||
+ modified.has(WalletStoresV1.exchangeDetails.storeName) ||
+ modified.has(WalletStoresV1.denominations.storeName) ||
+ modified.has(WalletStoresV1.globalCurrencyAuditors.storeName) ||
+ modified.has(WalletStoresV1.globalCurrencyExchanges.storeName)
+ ) {
+ this.ws.clearAllCaches();
+ }
+ }
+}
+
+/**
* Internal state of the wallet.
*
* This ties together all the operation implementations.
@@ -1804,7 +1837,7 @@ export class InternalWalletState {
return new DbAccessImpl(
this._indexedDbHandle,
WalletStoresV1,
- {},
+ new WalletDbTriggerSpec(this),
cancellationToken,
);
}