taler-typescript-core

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

commit f847430fd1bf458215541b96416cb342a4c4c213
parent 736778f85efd2cbe3208d24d486e4d9da69cac61
Author: Florian Dold <dold@taler.net>
Date:   Sun, 19 Jul 2026 03:16:26 +0200

db: add getRecordCounts, move diagnostics behind the DAL

handleGetDiagnostics reached into object stores directly.

Diffstat:
Mpackages/taler-wallet-core/src/dbtx-conformance-cases.ts | 17+++++++++++++++++
Mpackages/taler-wallet-core/src/dbtx-indexeddb.ts | 14++++++++++++++
Mpackages/taler-wallet-core/src/dbtx.ts | 19+++++++++++++++++++
Mpackages/taler-wallet-core/src/requests.ts | 23+++++++++++------------
4 files changed, 61 insertions(+), 12 deletions(-)

diff --git a/packages/taler-wallet-core/src/dbtx-conformance-cases.ts b/packages/taler-wallet-core/src/dbtx-conformance-cases.ts @@ -464,6 +464,23 @@ export const conformanceCases: ConformanceCase[] = [ }, { + name: "getRecordCounts reflects what was written", + async run(t, runner) { + const before = await runner.runTx((tx) => tx.getRecordCounts()); + await runner.runTx(async (tx) => { + await tx.upsertDenomination(makeDenomination("https://cnt/", "c-1")); + await tx.upsertDenomination(makeDenomination("https://cnt/", "c-2")); + }); + const after = await runner.runTx((tx) => tx.getRecordCounts()); + t.equal( + after.denominations - before.denominations, + 2, + "counts must track inserts", + ); + }, + }, + + { name: "upsert overwrites an existing row rather than duplicating it", async run(t, runner) { await runner.runTx(async (tx) => { diff --git a/packages/taler-wallet-core/src/dbtx-indexeddb.ts b/packages/taler-wallet-core/src/dbtx-indexeddb.ts @@ -89,6 +89,7 @@ import { import type {} from "./db-indexeddb.js"; import { WalletIndexedDbTransaction } from "./db-indexeddb.js"; import type { + WalletDbRecordCounts, GetCurrencyInfoDbResult, StoreCurrencyInfoDbRequest, WalletDbTransaction, @@ -494,6 +495,19 @@ export class IdbWalletTransaction implements WalletDbTransaction { await this.tx.bankAccountsV2.put(rec); } + async getRecordCounts(): Promise<WalletDbRecordCounts> { + const tx = this.tx; + return { + coins: await tx.coins.count(), + coinAvailability: await tx.coinAvailability.count(), + denominations: await tx.denominations.count(), + denominationFamilies: await tx.denominationFamilies.count(), + exchanges: await tx.exchanges.count(), + exchangeDetails: await tx.exchangeDetails.count(), + exchangeSignKeys: await tx.exchangeSignKeys.count(), + }; + } + async listAllCoins(): Promise<WalletCoin[]> { return await this.tx.coins.iter().toArray(); } diff --git a/packages/taler-wallet-core/src/dbtx.ts b/packages/taler-wallet-core/src/dbtx.ts @@ -103,6 +103,22 @@ export interface StoreCurrencyInfoDbRequest { source: "exchange" | "user" | "preset"; } +/** + * Record counts for diagnostics. + * + * Named per entity rather than per object store, so the numbers mean the same + * thing whichever backend produced them. + */ +export interface WalletDbRecordCounts { + coins: number; + coinAvailability: number; + denominations: number; + denominationFamilies: number; + exchanges: number; + exchangeDetails: number; + exchangeSignKeys: number; +} + export interface WalletDbTransaction { /** Get the currency specification for a scope, if one is stored. */ getCurrencyInfo( @@ -343,6 +359,9 @@ export interface WalletDbTransaction { /** Create or update a known bank account. */ upsertBankAccount(rec: WalletBankAccount): Promise<void>; + /** Count stored records per entity, for diagnostics. */ + getRecordCounts(): Promise<WalletDbRecordCounts>; + /** List every coin in the wallet. */ listAllCoins(): Promise<WalletCoin[]>; diff --git a/packages/taler-wallet-core/src/requests.ts b/packages/taler-wallet-core/src/requests.ts @@ -1903,18 +1903,17 @@ export async function handleGetDiagnostics( ): Promise<TestingGetDiagnosticsResponse> { const cnt: Record<string, number> = {}; const exchangeEntries: TestingGetDiagnosticsResponse["exchangeEntries"] = []; - await wex.db.runAllStoresReadWriteTx({}, async (tx) => { - cnt["coinAvailability"] = await tx.coinAvailability.count(); - cnt["coins"] = await tx.coins.count(); - cnt["denominationFamilies"] = await tx.denominationFamilies.count(); - cnt["denominations"] = await tx.denominations.count(); - cnt["exchangeDetails"] = await tx.exchangeDetails.count(); - cnt["exchangeSignKeys"] = await tx.exchangeSignKeys.count(); - cnt["exchanges"] = await tx.exchanges.count(); - for (const exch of await tx.exchanges.getAll()) { - const denoms = await tx.denominations.indexes.byExchangeBaseUrl.getAll( - exch.baseUrl, - ); + await wex.runWalletDbTx(async (tx) => { + const counts = await tx.getRecordCounts(); + cnt["coinAvailability"] = counts.coinAvailability; + cnt["coins"] = counts.coins; + cnt["denominationFamilies"] = counts.denominationFamilies; + cnt["denominations"] = counts.denominations; + cnt["exchangeDetails"] = counts.exchangeDetails; + cnt["exchangeSignKeys"] = counts.exchangeSignKeys; + cnt["exchanges"] = counts.exchanges; + for (const exch of await tx.getExchanges()) { + const denoms = await tx.getDenominationsByExchange(exch.baseUrl); let numWithdrawableDenoms = 0; let numCandidateWithdrawableDenoms = 0; for (let i = 0; i < denoms.length; i++) {