taler-typescript-core

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

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:
Mpackages/taler-wallet-core/src/requests.ts | 5+++--
Mpackages/taler-wallet-core/src/wallet-db-gate.test.ts | 20++++++++++++++++++++
Mpackages/taler-wallet-core/src/wallet.ts | 82+++++++++++++++++++++++++++++++++++++++++--------------------------------------
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));