commit 89c2b5f4044b140695dcb75dd0567457308d4afb
parent 7ec5453abfcc3eebec27313583f9560a7e4cbb1b
Author: Florian Dold <florian@dold.me>
Date: Fri, 13 Feb 2026 18:10:56 +0100
wallet-core: refactor refresh transitions
Diffstat:
1 file changed, 39 insertions(+), 94 deletions(-)
diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts
@@ -84,14 +84,14 @@ import {
cancelableFetch,
constructTaskIdentifier,
genericWaitForState,
+ getGenericRecordHandle,
makeCoinsVisible,
PendingTaskType,
+ RecordHandle,
TaskIdStr,
TaskRunResult,
TaskRunResultType,
TransactionContext,
- TransitionResult,
- TransitionResultType,
} from "./common.js";
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
import {
@@ -115,7 +115,6 @@ import {
WalletDbAllStoresReadOnlyTransaction,
WalletDbReadOnlyTransaction,
WalletDbReadWriteTransaction,
- WalletDbStoresArr,
} from "./db.js";
import { selectWithdrawalDenominations } from "./denomSelection.js";
import { fetchFreshExchange, getScopeForAllExchanges } from "./exchanges.js";
@@ -226,84 +225,22 @@ export class RefreshTransactionContext implements TransactionContext {
};
}
- /**
- * Transition a withdrawal transaction.
- * Extra object stores may be accessed during the transition.
- */
- async transition<StoreNameArray extends WalletDbStoresArr = []>(
- opts: { extraStores?: StoreNameArray; transactionLabel?: string },
- f: (
- rec: RefreshGroupRecord | undefined,
- tx: WalletDbReadWriteTransaction<
- [
- "refreshGroups",
- "transactionsMeta",
- "operationRetries",
- "exchanges",
- "exchangeDetails",
- ...StoreNameArray,
- ]
- >,
- ) => Promise<TransitionResult<RefreshGroupRecord>>,
- ): Promise<boolean> {
- const baseStores = [
- "refreshGroups" as const,
- "transactionsMeta" as const,
- "operationRetries" as const,
- "exchanges" as const,
- "exchangeDetails" as const,
- ];
- let stores = opts.extraStores
- ? [...baseStores, ...opts.extraStores]
- : baseStores;
- return await this.wex.db.runReadWriteTx(
- { storeNames: stores },
- async (tx) => {
- const wgRec = await tx.refreshGroups.get(this.refreshGroupId);
- let oldStId: number;
- let oldTxState: TransactionState;
- if (wgRec) {
- oldTxState = computeRefreshTransactionState(wgRec);
- oldStId = wgRec.operationStatus;
- } else {
- oldTxState = {
- major: TransactionMajorState.None,
- };
- oldStId = 0;
- }
- const res = await f(wgRec, tx);
- switch (res.type) {
- case TransitionResultType.Transition: {
- await tx.refreshGroups.put(res.rec);
- await this.updateTransactionMeta(tx);
- const newTxState = computeRefreshTransactionState(res.rec);
- applyNotifyTransition(tx.notify, this.transactionId, {
- oldTxState,
- newTxState,
- balanceEffect: BalanceEffect.PreserveUserVisible,
- oldStId,
- newStId: res.rec.operationStatus,
- });
- return true;
- }
- case TransitionResultType.Delete:
- await tx.refreshGroups.delete(this.refreshGroupId);
- await this.updateTransactionMeta(tx);
- applyNotifyTransition(tx.notify, this.transactionId, {
- oldTxState,
- newTxState: {
- major: TransactionMajorState.None,
- },
- // Deletion will affect balance
- balanceEffect: BalanceEffect.Any,
- newStId: -1,
- oldStId,
- });
- return true;
- default:
- return false;
- }
+ async getRecordHandle(
+ tx: WalletDbReadWriteTransaction<["refreshGroups", "transactionsMeta"]>,
+ ): Promise<
+ [RefreshGroupRecord | undefined, RecordHandle<RefreshGroupRecord>]
+ > {
+ return getGenericRecordHandle<RefreshGroupRecord>(
+ this,
+ tx as any,
+ async () => tx.refreshGroups.get(this.refreshGroupId),
+ async (r) => {
+ await tx.refreshGroups.put(r);
},
+ async () => tx.refreshGroups.delete(this.refreshGroupId),
+ (r) => computeRefreshTransactionState(r),
+ (r) => r.operationStatus,
+ () => this.updateTransactionMeta(tx),
);
}
@@ -355,23 +292,26 @@ export class RefreshTransactionContext implements TransactionContext {
}
async suspendTransaction(): Promise<void> {
- await this.transition({}, async (rec, tx) => {
+ await this.wex.db.runAllStoresReadWriteTx({}, async (tx) => {
+ const [rec, h] = await this.getRecordHandle(tx);
if (!rec) {
- return TransitionResult.stay();
+ return;
}
switch (rec.operationStatus) {
case RefreshOperationStatus.Finished:
case RefreshOperationStatus.Suspended:
case RefreshOperationStatus.SuspendedRedenominate:
case RefreshOperationStatus.Failed:
- return TransitionResult.stay();
+ break;
case RefreshOperationStatus.Pending: {
rec.operationStatus = RefreshOperationStatus.Suspended;
- return TransitionResult.transition(rec);
+ await h.update(rec);
+ break;
}
case RefreshOperationStatus.PendingRedenominate: {
rec.operationStatus = RefreshOperationStatus.SuspendedRedenominate;
- return TransitionResult.transition(rec);
+ await h.update(rec);
+ break;
}
default:
assertUnreachable(rec.operationStatus);
@@ -385,23 +325,26 @@ export class RefreshTransactionContext implements TransactionContext {
}
async resumeTransaction(): Promise<void> {
- await this.transition({}, async (rec, tx) => {
+ await this.wex.db.runAllStoresReadWriteTx({}, async (tx) => {
+ const [rec, h] = await this.getRecordHandle(tx);
if (!rec) {
- return TransitionResult.stay();
+ return;
}
switch (rec.operationStatus) {
case RefreshOperationStatus.Finished:
case RefreshOperationStatus.Failed:
case RefreshOperationStatus.Pending:
case RefreshOperationStatus.PendingRedenominate:
- return TransitionResult.stay();
+ break;
case RefreshOperationStatus.Suspended: {
rec.operationStatus = RefreshOperationStatus.Pending;
- return TransitionResult.transition(rec);
+ await h.update(rec);
+ break;
}
case RefreshOperationStatus.SuspendedRedenominate: {
rec.operationStatus = RefreshOperationStatus.PendingRedenominate;
- return TransitionResult.transition(rec);
+ await h.update(rec);
+ break;
}
default:
assertUnreachable(rec.operationStatus);
@@ -410,21 +353,23 @@ export class RefreshTransactionContext implements TransactionContext {
}
async failTransaction(reason?: TalerErrorDetail): Promise<void> {
- await this.transition({}, async (rec, tx) => {
+ await this.wex.db.runAllStoresReadWriteTx({}, async (tx) => {
+ const [rec, h] = await this.getRecordHandle(tx);
if (!rec) {
- return TransitionResult.stay();
+ return;
}
switch (rec.operationStatus) {
case RefreshOperationStatus.Finished:
case RefreshOperationStatus.Failed:
- return TransitionResult.stay();
+ break;
case RefreshOperationStatus.Pending:
case RefreshOperationStatus.PendingRedenominate:
case RefreshOperationStatus.SuspendedRedenominate:
case RefreshOperationStatus.Suspended: {
rec.operationStatus = RefreshOperationStatus.Failed;
rec.failReason = reason;
- return TransitionResult.transition(rec);
+ await h.update(rec);
+ break;
}
default:
assertUnreachable(rec.operationStatus);