commit 97442be74c9f108d61401384691f5485f0a85457
parent 54acc650f976de4d3bb38d06f1d67d1be13aeca7
Author: Florian Dold <dold@taler.net>
Date: Fri, 28 Aug 2026 23:10:02 +0200
wallet-core: retain file import after database replacement
Diffstat:
3 files changed, 66 insertions(+), 41 deletions(-)
diff --git a/packages/taler-wallet-core/src/requests.ts b/packages/taler-wallet-core/src/requests.ts
@@ -1995,14 +1995,15 @@ async function handleImportDbFromFile(
);
}
const db = wex.ws.db;
- if (!db.readBackupJson) {
+ const readBackupJson = db.readBackupJson;
+ if (!readBackupJson) {
throw TalerError.fromDetail(
TalerErrorCode.WALLET_DB_BACKEND_UNSUPPORTED,
{ backend: db.name },
`the ${db.name} backend cannot read a database dump from a file`,
);
}
- const dump = await db.readBackupJson(req.path);
+ const dump = await readBackupJson.call(db, req.path);
return await importDbDump(wex, dump, req.progressToken);
},
);
diff --git a/packages/taler-wallet-core/src/wallet-db-gate.test.ts b/packages/taler-wallet-core/src/wallet-db-gate.test.ts
@@ -79,6 +79,26 @@ test("database gate drains old work and admits queued work on replacement", asyn
]);
});
+test("optional host capabilities follow the current database handle", async () => {
+ const events: string[] = [];
+ const gate = new DbOperationGate();
+ const oldHandle = fakeHandle("indexeddb", events);
+ const newHandle = fakeHandle("sqlite", events);
+ let current = oldHandle;
+ const admitted = new AdmittedWalletDbHandle(() => current, gate);
+
+ assert.strictEqual(admitted.readBackupJson, undefined);
+ newHandle.readBackupJson = async (path) => ({ path, backend: "sqlite" });
+ current = newHandle;
+ assert.deepStrictEqual(await admitted.readBackupJson!("wallet.json"), {
+ path: "wallet.json",
+ backend: "sqlite",
+ });
+
+ current = oldHandle;
+ assert.strictEqual(admitted.readBackupJson, undefined);
+});
+
test("database import has exclusive admission", async () => {
const events: string[] = [];
const gate = new DbOperationGate();
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
@@ -225,49 +225,53 @@ export class AdmittedWalletDbHandle implements WalletDbHandle {
get name(): string {
return this.current().name;
}
- exportToFile?: WalletDbHandle["exportToFile"];
- readBackupJson?: WalletDbHandle["readBackupJson"];
- importAnyDatabase?: WalletDbHandle["importAnyDatabase"];
- getDiagnosticStats?: WalletDbHandle["getDiagnosticStats"];
+
+ get exportToFile(): WalletDbHandle["exportToFile"] | undefined {
+ if (!this.current().exportToFile) return undefined;
+ return (...args) =>
+ this.gate.runShared(async () => {
+ const db = this.current();
+ const fn = db.exportToFile;
+ if (!fn)
+ throw Error("current database backend cannot export to a file");
+ return await fn.apply(db, args);
+ });
+ }
+
+ get readBackupJson(): WalletDbHandle["readBackupJson"] | undefined {
+ if (!this.current().readBackupJson) return undefined;
+ return (...args) =>
+ this.gate.runShared(async () => {
+ const db = this.current();
+ const fn = db.readBackupJson;
+ if (!fn)
+ throw Error("current database backend cannot read this backup");
+ return await fn.apply(db, args);
+ });
+ }
+
+ get importAnyDatabase(): WalletDbHandle["importAnyDatabase"] | undefined {
+ if (!this.current().importAnyDatabase) return undefined;
+ return (...args) =>
+ this.gate.runExclusive(async () => {
+ const db = this.current();
+ const fn = db.importAnyDatabase;
+ if (!fn) {
+ throw Error("current database backend cannot import a foreign dump");
+ }
+ return await fn.apply(db, args);
+ }, args[2]?.cancellationToken);
+ }
+
+ get getDiagnosticStats(): WalletDbHandle["getDiagnosticStats"] | undefined {
+ if (!this.current().getDiagnosticStats) return undefined;
+ return () => this.current().getDiagnosticStats?.();
+ }
constructor(
private current: () => WalletDbHandle,
private gate: DbOperationGate,
- ) {
- if (current().exportToFile) {
- this.exportToFile = (...args) =>
- gate.runShared(async () => {
- const fn = current().exportToFile;
- if (!fn)
- throw Error("current database backend cannot export to a file");
- return await fn.apply(current(), args);
- });
- }
- if (current().readBackupJson) {
- this.readBackupJson = (...args) =>
- gate.runShared(async () => {
- const fn = current().readBackupJson;
- if (!fn)
- throw Error("current database backend cannot read this backup");
- return await fn.apply(current(), args);
- });
- }
- if (current().importAnyDatabase) {
- this.importAnyDatabase = (...args) =>
- gate.runExclusive(async () => {
- const db = current();
- const fn = db.importAnyDatabase;
- if (!fn)
- throw Error(
- "current database backend cannot import a foreign dump",
- );
- return await fn.apply(db, args);
- }, args[2]?.cancellationToken);
- }
- if (current().getDiagnosticStats) {
- this.getDiagnosticStats = () => current().getDiagnosticStats?.();
- }
- }
+ ) {}
runReadWriteTx<T>(f: (tx: WalletDbTransaction) => Promise<T>): Promise<T> {
return this.gate.runShared(() => this.current().runReadWriteTx(f));