commit 79f0e4637717d0b600343a366d7e8120b1ff3b36
parent 13120f507aaff31d9af336a7ef06216c5c88b2e0
Author: Florian Dold <dold@taler.net>
Date: Thu, 20 Aug 2026 17:45:49 +0200
wallet-core: report database migration errors
Diffstat:
4 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/packages/taler-util/src/notifications.ts b/packages/taler-util/src/notifications.ts
@@ -396,6 +396,8 @@ export interface DatabaseMaintenanceProgressNotification {
processedRecords?: number;
/** Total records in the whole migration, known before copying starts. */
totalRecords?: number;
+ /** Why the maintenance operation failed. Present when phase is failed. */
+ error?: TalerErrorDetail;
}
export type WalletNotification =
diff --git a/packages/taler-wallet-core/src/db-indexeddb.ts b/packages/taler-wallet-core/src/db-indexeddb.ts
@@ -80,6 +80,7 @@ import {
checkDbInvariant,
codecForAny,
encodeCrock,
+ getErrorDetailFromException,
hash,
j2s,
stringToBytes,
@@ -2211,6 +2212,7 @@ export async function applyFixups(
step: fixupInstruction.name,
completedSteps: index,
totalSteps: walletDbFixups.length,
+ error: getErrorDetailFromException(e),
});
}
throw e;
diff --git a/packages/taler-wallet-core/src/db-native-migration.test.ts b/packages/taler-wallet-core/src/db-native-migration.test.ts
@@ -37,7 +37,11 @@ import {
Sqlite3Database,
} from "@gnu-taler/idb-bridge";
import { createNodeHelperSqlite3Impl } from "@gnu-taler/idb-bridge/node-helper-sqlite3-impl";
-import { NotificationType, WalletNotification } from "@gnu-taler/taler-util";
+import {
+ DatabaseMaintenanceProgressNotification,
+ NotificationType,
+ WalletNotification,
+} from "@gnu-taler/taler-util";
import {
dropExpiredMigrationBackup,
@@ -324,7 +328,9 @@ test("native migration: an interrupted attempt restarts after reopening", async
// progress. This leaves the same durable state as process termination:
// untouched IndexedDB tables, a running marker and partial native rows.
let interruptionInjected = false;
+ const progress: WalletNotification[] = [];
first.handle.setNotificationSink((n) => {
+ progress.push(n);
if (
!interruptionInjected &&
n.type === NotificationType.DatabaseMaintenanceProgress &&
@@ -344,6 +350,18 @@ test("native migration: an interrupted attempt restarts after reopening", async
interruptionInjected,
"migration was not interrupted after a copy",
);
+ const failed = progress.find(
+ (n): n is DatabaseMaintenanceProgressNotification =>
+ n.type === NotificationType.DatabaseMaintenanceProgress &&
+ n.operation === "indexeddb-to-native-migration" &&
+ n.phase === "failed",
+ );
+ assert.ok(failed, "interrupted migration did not report failure");
+ assert.match(
+ failed.error?.hint ?? "",
+ /simulated migration interruption/,
+ "failed migration notification did not include the exception",
+ );
const interrupted = await inspectWalletDbFileDetails(first.db);
assert.strictEqual(interrupted.kind, "indexeddb");
assert.ok(interrupted.nativeRecords > 0, "no partial native copy was left");
diff --git a/packages/taler-wallet-core/src/db-native-migration.ts b/packages/taler-wallet-core/src/db-native-migration.ts
@@ -45,7 +45,12 @@
* away. {@link restoreMigrationBackup} is that statement.
*/
-import { Duration, Logger, NotificationType } from "@gnu-taler/taler-util";
+import {
+ Duration,
+ getErrorDetailFromException,
+ Logger,
+ NotificationType,
+} from "@gnu-taler/taler-util";
import type { Sqlite3Database } from "@gnu-taler/idb-bridge";
import {
@@ -459,6 +464,7 @@ export async function migrateWalletDbToNative(
phase: "failed",
completedSteps: 0,
totalSteps: DB_CONVERSION_STEP_COUNT,
+ error: getErrorDetailFromException(e),
});
throw e;
}