taler-typescript-core

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

commit 625d6b6bbc40d1f82a80b95abed2a1e7a506830f
parent bfde4170fe757a22725264ba8b9a9eda0003e215
Author: Florian Dold <dold@taler.net>
Date:   Thu, 20 Aug 2026 19:06:51 +0200

wallet-core: isolate migration faults from notifications

Diffstat:
Mpackages/taler-wallet-core/src/db-converter.ts | 18++++++++++++++++--
Mpackages/taler-wallet-core/src/db-native-migration.test.ts | 24+++++++++++++-----------
Mpackages/taler-wallet-core/src/db-native-migration.ts | 4+++-
3 files changed, 32 insertions(+), 14 deletions(-)

diff --git a/packages/taler-wallet-core/src/db-converter.ts b/packages/taler-wallet-core/src/db-converter.ts @@ -32,6 +32,7 @@ */ import { + DatabaseMaintenanceProgressNotification, Logger, NotificationType, sha256, @@ -518,6 +519,16 @@ export interface DbConversionReport { totalRecords: number; } +/** Optional hooks for observing or deliberately interrupting a conversion. */ +export interface DbConversionOptions { + /** + * Called after a progress notification has been delivered to the source + * handle. Throwing aborts the conversion, which lets callers inject a + * controlled interruption without relying on host notification callbacks. + */ + onProgress?: (notification: DatabaseMaintenanceProgressNotification) => void; +} + /** Small enough to bound retained records while amortising transaction setup. */ export const DB_CONVERSION_BATCH_SIZE = 128; @@ -643,6 +654,7 @@ async function digestStore( export async function convertWalletDb( src: WalletDbHandle, dst: WalletDbHandle, + options: DbConversionOptions = {}, ): Promise<DbConversionReport> { const copied: Record<string, number> = {}; @@ -668,7 +680,7 @@ export async function convertWalletDb( step?: CopyStep, processedRecords?: number, ): void => { - src.emitNotification({ + const notification: DatabaseMaintenanceProgressNotification = { type: NotificationType.DatabaseMaintenanceProgress, operation: "indexeddb-to-native-migration", phase, @@ -677,7 +689,9 @@ export async function convertWalletDb( ...(step ? { step: step.name } : {}), ...(processedRecords !== undefined ? { processedRecords } : {}), totalRecords, - }); + }; + src.emitNotification(notification); + options.onProgress?.(notification); }; const makeRecordProgress = (phase: "copy" | "verify") => { diff --git a/packages/taler-wallet-core/src/db-native-migration.test.ts b/packages/taler-wallet-core/src/db-native-migration.test.ts @@ -348,19 +348,21 @@ test("native migration: an interrupted attempt restarts after reopening", async const progress: WalletNotification[] = []; first.handle.setNotificationSink((n) => { progress.push(n); - if ( - !interruptionInjected && - n.type === NotificationType.DatabaseMaintenanceProgress && - n.operation === "indexeddb-to-native-migration" && - n.phase === "copy" && - (n.processedRecords ?? 0) >= DB_CONVERSION_PROGRESS_RECORDS - ) { - interruptionInjected = true; - throw Error("simulated migration interruption"); - } }); await assert.rejects( - () => migrateWalletDbToNative(first.db, first.handle), + () => + migrateWalletDbToNative(first.db, first.handle, { + onProgress(n) { + if ( + !interruptionInjected && + n.phase === "copy" && + (n.processedRecords ?? 0) >= DB_CONVERSION_PROGRESS_RECORDS + ) { + interruptionInjected = true; + throw Error("simulated migration interruption"); + } + }, + }), /simulated migration interruption/, ); assert.ok( diff --git a/packages/taler-wallet-core/src/db-native-migration.ts b/packages/taler-wallet-core/src/db-native-migration.ts @@ -56,6 +56,7 @@ import type { Sqlite3Database } from "@gnu-taler/idb-bridge"; import { convertWalletDb, DB_CONVERSION_STEP_COUNT, + DbConversionOptions, DbConversionReport, } from "./db-converter.js"; import { @@ -313,6 +314,7 @@ export interface NativeMigrationResult { export async function migrateWalletDbToNative( db: Sqlite3Database, src: WalletDbHandle, + conversionOptions: DbConversionOptions = {}, ): Promise<NativeMigrationResult> { // Read this before native initialization upgrades the schema. A running // marker written by versions before cleanup_safe existed is trustworthy: @@ -382,7 +384,7 @@ export async function migrateWalletDbToNative( logger.info("migrating the wallet database to the native schema"); // Verifies its own copy record by record and throws on any difference, so // reaching the next statement means the native tables hold the wallet. - const report = await convertWalletDb(src, dst); + const report = await convertWalletDb(src, dst, conversionOptions); await ndb.lock.run(async () => { const violations = await (