taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit cf10cdeeb6277818bd231f4ce6000e587c304a41
parent 715d71e22b31ab481bd460edec67b54bf8a23001
Author: Florian Dold <dold@taler.net>
Date:   Sat, 18 Jul 2026 20:05:50 +0200

db: move storage-neutral types to db-common.ts

Diffstat:
Mpackages/taler-wallet-core/src/common.ts | 14+++++++++-----
Mpackages/taler-wallet-core/src/db-common.ts | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-wallet-core/src/db-indexeddb.ts | 92+++++++++----------------------------------------------------------------------
Mpackages/taler-wallet-core/src/dbtx-indexeddb.ts | 12++++--------
Mpackages/taler-wallet-core/src/dbtx.ts | 21++++++++++-----------
Mpackages/taler-wallet-core/src/pay-peer-pull-credit.ts | 4++--
Mpackages/taler-wallet-core/src/pay-peer-push-credit.ts | 4++--
Mpackages/taler-wallet-core/src/shepherd.ts | 6+++---
Mpackages/taler-wallet-core/src/transactions.ts | 8++++----
Mpackages/taler-wallet-core/src/withdraw.ts | 6+++---
10 files changed, 143 insertions(+), 120 deletions(-)

diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts @@ -65,6 +65,7 @@ import { ExchangeEntryDbRecordStatus, ExchangeEntryDbUpdateStatus, timestampPreciseToDb, + WalletRetryInfo, } from "./db-common.js"; import { CoinHistoryRecord, @@ -557,11 +558,14 @@ export interface TaskRunErrorResult { errorDetail: TalerErrorDetail; } -export interface DbRetryInfo { - firstTry: DbPreciseTimestamp; - nextRetry: DbPreciseTimestamp; - retryCounter: number; -} +/** + * Retry state of a task. + * + * The stored shape lives in db-common.ts as {@link WalletRetryInfo}; this + * interface exists so the retry-policy helpers below can merge into a + * namespace of the same name. + */ +export interface DbRetryInfo extends WalletRetryInfo {} export interface RetryPolicy { readonly backoffDelta: Duration; diff --git a/packages/taler-wallet-core/src/db-common.ts b/packages/taler-wallet-core/src/db-common.ts @@ -117,6 +117,102 @@ export function timestampOptionalAbsoluteFromDb( } /** + * Metadata for a transaction. + * This object store is effectively a materialzed view of transactions gathered + * from various other object stores. + * + * Primary key: transactionId + */ +export interface WalletTransactionMeta { + /** + * Transaction identifier. + * Also determines the type of the transaction. + */ + transactionId: string; + + timestamp: DbPreciseTimestamp; + + /** + * Status of the transaction, matches the status enum of the + * transaction of the type determined by the transaction ID. + */ + status: number; + + /** + * Exchanges involved in the transaction. + */ + exchanges: string[]; + + currency: string; +} + +/** + * Retry state of a task. + * + * The policy that computes these timestamps lives in common.ts; this is only + * the stored shape. + */ +export interface WalletRetryInfo { + firstTry: DbPreciseTimestamp; + nextRetry: DbPreciseTimestamp; + retryCounter: number; +} + +export interface WalletOperationRetry { + /** + * Unique identifier for the operation. Typically of + * the format `${opType}-${opUniqueKey}` + * + * @see {@link TaskIdentifiers} + */ + id: string; + + lastError?: TalerErrorDetail; + + retryInfo: WalletRetryInfo; +} + +export interface WalletContractTerms { + /** + * Contract terms hash. + */ + h: string; + + /** + * Contract terms JSON. + * + * Deliberately untyped: this is arbitrary JSON as received from the + * merchant, and there is nothing to validate it against at this layer. + */ + contractTermsRaw: any; +} + +/** + * How a coin came into the wallet. + */ +export enum CoinSourceType { + Withdraw = "withdraw", + Refresh = "refresh", + Reward = "reward", +} + +export enum RefundReason { + /** + * Normal refund given by the merchant. + */ + NormalRefund = "normal-refund", + /** + * Refund from an aborted payment. + */ + AbortRefund = "abort-pay-refund", +} + +export enum ExchangeMigrationReason { + MismatchedBaseUrl = "mismatched-base-url", + UnavailableOldUrl = "unavailable-old-url", +} + +/** * Status of a denomination. */ export enum DenominationVerificationStatus { diff --git a/packages/taler-wallet-core/src/db-indexeddb.ts b/packages/taler-wallet-core/src/db-indexeddb.ts @@ -112,8 +112,14 @@ import { timestampProtocolFromDb, WalletDenomination, DenomFees, + CoinSourceType, + WalletTransactionMeta, + WalletOperationRetry, + WalletContractTerms, + RefundReason, + ExchangeMigrationReason, } from "./db-common.js"; -export { DenomFees }; +export { DenomFees, CoinSourceType, RefundReason, ExchangeMigrationReason }; import { DbAccess, DbAccessImpl, @@ -523,12 +529,6 @@ export interface PlanchetRecord { ageCommitmentProof?: AgeCommitmentProof; } -export enum CoinSourceType { - Withdraw = "withdraw", - Refresh = "refresh", - Reward = "reward", -} - export interface WithdrawCoinSource { type: CoinSourceType.Withdraw; @@ -964,17 +964,6 @@ export interface RefreshSessionRecord { // (legacy v1 refresh) } -export enum RefundReason { - /** - * Normal refund given by the merchant. - */ - NormalRefund = "normal-refund", - /** - * Refund from an aborted payment. - */ - AbortRefund = "abort-pay-refund", -} - /** * Partial information about the downloaded proposal. * Only contains data that is relevant for indexing on the @@ -1841,20 +1830,6 @@ export interface ReserveRecord { amlReview?: boolean; } -export interface OperationRetryRecord { - /** - * Unique identifier for the operation. Typically of - * the format `${opType}-${opUniqueKey}` - * - * @see {@link TaskIdentifiers} - */ - id: string; - - lastError?: TalerErrorDetail; - - retryInfo: DbRetryInfo; -} - /** * Availability of coins of a given denomination (and age restriction!). * @@ -1892,18 +1867,6 @@ export interface CoinAvailabilityRecord { pendingRefreshOutputCount?: number; } -export interface ContractTermsRecord { - /** - * Contract terms hash. - */ - h: string; - - /** - * Contract terms JSON. - */ - contractTermsRaw: any; -} - export interface DbExchangeHandle { url: string; exchangeMasterPub: string; @@ -1993,36 +1956,6 @@ export interface GlobalCurrencyExchangeRecord { exchangeMasterPub: string; } -/** - * Metadata for a transaction. - * This object store is effectively a materialzed view of transactions gathered - * from various other object stores. - * - * Primary key: transactionId - */ -export interface TransactionMetaRecord { - /** - * Transaction identifier. - * Also determines the type of the transaction. - */ - transactionId: string; - - timestamp: DbPreciseTimestamp; - - /** - * Status of the transaction, matches the status enum of the - * transaction of the type determined by the transaction ID. - */ - status: number; - - /** - * Exchanges involved in the transaction. - */ - exchanges: string[]; - - currency: string; -} - export interface DenomLossEventRecord { denomLossEventId: string; currency: string; @@ -2051,11 +1984,6 @@ export interface CurrencyInfoRecord { source: "exchange" | "user" | "preset"; } -export enum ExchangeMigrationReason { - MismatchedBaseUrl = "mismatched-base-url", - UnavailableOldUrl = "unavailable-old-url", -} - export interface ExchangeMigrationLogRecord { oldExchangeBaseUrl: string; newExchangeBaseUrl: string; @@ -2185,7 +2113,7 @@ export const WalletIndexedDbStoresV1 = { }, }), transactionsMeta: describeStoreV2({ - recordCodec: passthroughCodec<TransactionMetaRecord>(), + recordCodec: passthroughCodec<WalletTransactionMeta>(), storeName: "transactionsMeta", keyPath: "transactionId", versionAdded: 13, @@ -2631,7 +2559,7 @@ export const WalletIndexedDbStoresV1 = { ), operationRetries: describeStore( "operationRetries", - describeContents<OperationRetryRecord>({ + describeContents<WalletOperationRetry>({ keyPath: "id", }), {}, @@ -2718,7 +2646,7 @@ export const WalletIndexedDbStoresV1 = { ), contractTerms: describeStore( "contractTerms", - describeContents<ContractTermsRecord>({ + describeContents<WalletContractTerms>({ keyPath: "h", }), {}, diff --git a/packages/taler-wallet-core/src/dbtx-indexeddb.ts b/packages/taler-wallet-core/src/dbtx-indexeddb.ts @@ -58,10 +58,6 @@ import type { DonationSummaryRecord, } from "./db-indexeddb.js"; import { - PeerPullCreditRecord, - PeerPushDebitRecord, - PeerPushCreditRecord, - PeerPullPaymentIncomingRecord, PurchaseRecord, WalletIndexedDbTransaction, } from "./db-indexeddb.js"; @@ -591,25 +587,25 @@ export class IdbWalletTransaction implements WalletDbTransaction { ); } - async getActivePeerPushDebits(): Promise<PeerPushDebitRecord[]> { + async getActivePeerPushDebits(): Promise<WalletPeerPushDebit[]> { return await this.tx.peerPushDebit.indexes.byStatus.getAll( getActiveKeyRange(), ); } - async getActivePeerPushCredits(): Promise<PeerPushCreditRecord[]> { + async getActivePeerPushCredits(): Promise<WalletPeerPushCredit[]> { return await this.tx.peerPushCredit.indexes.byStatus.getAll( getActiveKeyRange(), ); } - async getActivePeerPullCredits(): Promise<PeerPullCreditRecord[]> { + async getActivePeerPullCredits(): Promise<WalletPeerPullCredit[]> { return await this.tx.peerPullCredit.indexes.byStatus.getAll( getActiveKeyRange(), ); } - async getActivePeerPullDebits(): Promise<PeerPullPaymentIncomingRecord[]> { + async getActivePeerPullDebits(): Promise<WalletPeerPullDebit[]> { return await this.tx.peerPullDebit.indexes.byStatus.getAll( getActiveKeyRange(), ); diff --git a/packages/taler-wallet-core/src/dbtx.ts b/packages/taler-wallet-core/src/dbtx.ts @@ -26,9 +26,12 @@ * Wallet<Name>. The <Name>Record types in db-indexeddb.ts describe the * IndexedDB object stores and must not appear here. * - * FIXME: The remaining imports from db-indexeddb.js below are a violation of - * that rule, and are removed in phase 0c of the DAL migration (see - * DB_MIGRATION_PLAN.md) by introducing Wallet<Name> counterparts for them. + * FIXME: The nine type-only imports from db-indexeddb.js below still violate + * that rule. They are converted lazily: each gets its Wallet<Name> + * counterpart in db-common.ts when the migration phase that touches its store + * arrives, rather than all at once up front (see DB_MIGRATION_PLAN.md). + * They are type-only, so this file still emits an empty JS module and has no + * runtime dependency on IndexedDB. */ import { @@ -58,10 +61,6 @@ import type { DepositGroupRecord, ExchangeDetailsRecord, DonationSummaryRecord, - PeerPullCreditRecord, - PeerPushDebitRecord, - PeerPushCreditRecord, - PeerPullPaymentIncomingRecord, PurchaseRecord, } from "./db-indexeddb.js"; @@ -246,13 +245,13 @@ export interface WalletDbTransaction { getActiveWithdrawalGroups(): Promise<WithdrawalGroupRecord[]>; - getActivePeerPushDebits(): Promise<PeerPushDebitRecord[]>; + getActivePeerPushDebits(): Promise<WalletPeerPushDebit[]>; - getActivePeerPushCredits(): Promise<PeerPushCreditRecord[]>; + getActivePeerPushCredits(): Promise<WalletPeerPushCredit[]>; - getActivePeerPullCredits(): Promise<PeerPullCreditRecord[]>; + getActivePeerPullCredits(): Promise<WalletPeerPullCredit[]>; - getActivePeerPullDebits(): Promise<PeerPullPaymentIncomingRecord[]>; + getActivePeerPullDebits(): Promise<WalletPeerPullDebit[]>; getActivePurchases(): Promise<PurchaseRecord[]>; diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts @@ -70,9 +70,9 @@ import { WithdrawalGroupStatus, timestampPreciseFromDb, timestampPreciseToDb, + WalletOperationRetry, } from "./db-common.js"; import { - OperationRetryRecord, WithdrawalGroupRecord, WithdrawalRecordType, } from "./db-indexeddb.js"; @@ -177,7 +177,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { const peerContractTerms = ct.contractTermsRaw; let wsr: WithdrawalGroupRecord | undefined = undefined; - let wsrOrt: OperationRetryRecord | undefined = undefined; + let wsrOrt: WalletOperationRetry | undefined = undefined; if (pullCredit.withdrawalGroupId) { wsr = await tx.withdrawalGroups.get(pullCredit.withdrawalGroupId); if (wsr) { diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -72,9 +72,9 @@ import { timestampPreciseFromDb, timestampPreciseToDb, WalletPeerPushCredit, + WalletOperationRetry, } from "./db-common.js"; import { - OperationRetryRecord, WalletIndexedDbTransaction, WithdrawalGroupRecord, WithdrawalRecordType, @@ -164,7 +164,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { } let wg: WithdrawalGroupRecord | undefined = undefined; - let wgRetryRecord: OperationRetryRecord | undefined = undefined; + let wgRetryRecord: WalletOperationRetry | undefined = undefined; if (pushInc.withdrawalGroupId) { wg = await tx.withdrawalGroups.get(pushInc.withdrawalGroupId); if (wg) { diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts @@ -57,9 +57,9 @@ import { ReserveRecordStatus, timestampAbsoluteFromDb, timestampPreciseToDb, + WalletOperationRetry, } from "./db-common.js"; import { - OperationRetryRecord, WalletIndexedDbTransaction, } from "./db-indexeddb.js"; import { processValidateDenoms } from "./denominations.js"; @@ -619,7 +619,7 @@ async function storePendingTaskError( ws: InternalWalletState, pendingTaskId: string, e: TalerErrorDetail, -): Promise<OperationRetryRecord> { +): Promise<WalletOperationRetry> { logger.trace(`storing task [pending] with ERROR for ${pendingTaskId}`); const res = await ws.runStandaloneLegacyWalletDbTx(async (tx) => { let retryRecord = await tx.operationRetries.get(pendingTaskId); @@ -662,7 +662,7 @@ async function storePendingTaskPending( ws: InternalWalletState, pendingTaskId: string, schedTime?: AbsoluteTime, -): Promise<OperationRetryRecord> { +): Promise<WalletOperationRetry> { logger.trace(`storing task [pending] for ${pendingTaskId}`); const res = await ws.runStandaloneLegacyWalletDbTx(async (tx) => { let retryRecord = await tx.operationRetries.get(pendingTaskId); diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts @@ -58,7 +58,7 @@ import { PurchaseStatus, timestampPreciseToDb, } from "./db-common.js"; -import { TransactionMetaRecord } from "./db-indexeddb.js"; +import { WalletTransactionMeta } from "./db-common.js"; import { DepositTransactionContext } from "./deposits.js"; import { DenomLossTransactionContext } from "./exchanges.js"; import { @@ -182,7 +182,7 @@ export function isUnsuccessfulTransaction(state: TransactionState): boolean { function checkFilterIncludes( req: GetTransactionsV2Request | undefined, - mtx: TransactionMetaRecord, + mtx: WalletTransactionMeta, ): boolean { if (shouldSkipCurrency(req, mtx.currency, mtx.exchanges)) { return false; @@ -254,7 +254,7 @@ async function addFiltered( tx: LegacyWalletTxHandle, req: GetTransactionsV2Request | undefined, target: Transaction[], - source: TransactionMetaRecord[], + source: WalletTransactionMeta[], ): Promise<number> { let numAdded: number = 0; for (const mtx of source) { @@ -312,7 +312,7 @@ function sortTransactions( async function findOffsetTransaction( tx: LegacyWalletTxHandle, req?: GetTransactionsV2Request, -): Promise<TransactionMetaRecord | undefined> { +): Promise<WalletTransactionMeta | undefined> { let forwards = req?.limit == null || req.limit >= 0; let closestTimestamp: DbPreciseTimestamp | undefined = undefined; if (req?.offsetTransactionId) { diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts @@ -145,11 +145,11 @@ import { timestampPreciseToDb, timestampProtocolToDb, WalletDenomination, + WalletOperationRetry, } from "./db-common.js"; import { CoinRecord, CoinSourceType, - OperationRetryRecord, PlanchetRecord, WgInfo, WithdrawalGroupRecord, @@ -213,7 +213,7 @@ interface TxKycDetails { function buildTransactionForBankIntegratedWithdraw( wg: WithdrawalGroupRecord, scopes: ScopeInfo[], - ort: OperationRetryRecord | undefined, + ort: WalletOperationRetry | undefined, kycDetails: TxKycDetails | undefined, ): TransactionWithdrawal { if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) { @@ -282,7 +282,7 @@ function buildTransactionForManualWithdraw( wg: WithdrawalGroupRecord, exchangeDetails: ExchangeDetails | undefined, scopes: ScopeInfo[], - ort: OperationRetryRecord | undefined, + ort: WalletOperationRetry | undefined, kycDetails: TxKycDetails | undefined, ): TransactionWithdrawal { if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankManual) {