gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] 02/03: refactor DB access


From: gnunet
Subject: [taler-wallet-core] 02/03: refactor DB access
Date: Fri, 13 Dec 2019 13:10:26 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

commit f3329ecf062b217b2e062b92034152f623685a87
Author: Florian Dold <address@hidden>
AuthorDate: Thu Dec 12 22:39:45 2019 +0100

    refactor DB access
---
 src/db.ts                   | 119 ++---------------
 src/headless/helpers.ts     |   9 +-
 src/operations/balance.ts   |   5 +-
 src/operations/exchanges.ts |  34 ++---
 src/operations/history.ts   |   5 +-
 src/operations/pay.ts       | 103 +++++++--------
 src/operations/payback.ts   |  16 +--
 src/operations/pending.ts   |   5 +-
 src/operations/refresh.ts   |  68 +++-------
 src/operations/reserves.ts  |  38 +++---
 src/operations/return.ts    |  25 ++--
 src/operations/state.ts     |   3 +-
 src/operations/tip.ts       |  22 ++--
 src/operations/withdraw.ts  |  53 +++-----
 src/util/query.ts           | 302 ++++++++++++++++++++++++++++----------------
 src/wallet.ts               |  46 +++----
 src/webex/wxBackend.ts      |  21 +--
 17 files changed, 389 insertions(+), 485 deletions(-)

diff --git a/src/db.ts b/src/db.ts
index 70338122..0a07171b 100644
--- a/src/db.ts
+++ b/src/db.ts
@@ -1,118 +1,27 @@
 import { Stores, WALLET_DB_VERSION } from "./types/dbTypes";
-import { Store, Index } from "./util/query";
+import { openDatabase, Database } from "./util/query";
 
-const DB_NAME = "taler";
+const TALER_DB_NAME = "taler";
 
 /**
  * Return a promise that resolves
  * to the taler wallet db.
  */
-export function openDatabase(
+export function openTalerDatabase(
   idbFactory: IDBFactory,
   onVersionChange: () => void,
   onUpgradeUnsupported: (oldVersion: number, newVersion: number) => void,
 ): Promise<IDBDatabase> {
-  return new Promise<IDBDatabase>((resolve, reject) => {
-    const req = idbFactory.open(DB_NAME, WALLET_DB_VERSION);
-    req.onerror = e => {
-      console.log("taler database error", e);
-      reject(new Error("database error"));
-    };
-    req.onsuccess = e => {
-      req.result.onversionchange = (evt: IDBVersionChangeEvent) => {
-        console.log(
-          `handling live db version change from ${evt.oldVersion} to 
${evt.newVersion}`,
-        );
-        req.result.close();
-        onVersionChange();
-      };
-      resolve(req.result);
-    };
-    req.onupgradeneeded = e => {
-      const db = req.result;
-      console.log(
-        `DB: upgrade needed: oldVersion=${e.oldVersion}, 
newVersion=${e.newVersion}`,
-      );
-      switch (e.oldVersion) {
-        case 0: // DB does not exist yet
-          for (const n in Stores) {
-            if ((Stores as any)[n] instanceof Store) {
-              const si: Store<any> = (Stores as any)[n];
-              const s = db.createObjectStore(si.name, si.storeParams);
-              for (const indexName in si as any) {
-                if ((si as any)[indexName] instanceof Index) {
-                  const ii: Index<any, any> = (si as any)[indexName];
-                  s.createIndex(ii.indexName, ii.keyPath, ii.options);
-                }
-              }
-            }
-          }
-          break;
-        default:
-          if (e.oldVersion !== WALLET_DB_VERSION) {
-            onUpgradeUnsupported(e.oldVersion, WALLET_DB_VERSION);
-            throw Error("incompatible DB");
-          }
-          break;
-      }
-    };
-  });
+  return openDatabase(
+    idbFactory,
+    TALER_DB_NAME,
+    WALLET_DB_VERSION,
+    Stores,
+    onVersionChange,
+    onUpgradeUnsupported,
+  );
 }
 
-export function exportDatabase(db: IDBDatabase): Promise<any> {
-  const dump = {
-    name: db.name,
-    stores: {} as { [s: string]: any },
-    version: db.version,
-  };
-
-  return new Promise((resolve, reject) => {
-    const tx = db.transaction(Array.from(db.objectStoreNames));
-    tx.addEventListener("complete", () => {
-      resolve(dump);
-    });
-    // tslint:disable-next-line:prefer-for-of
-    for (let i = 0; i < db.objectStoreNames.length; i++) {
-      const name = db.objectStoreNames[i];
-      const storeDump = {} as { [s: string]: any };
-      dump.stores[name] = storeDump;
-      tx.objectStore(name)
-        .openCursor()
-        .addEventListener("success", (e: Event) => {
-          const cursor = (e.target as any).result;
-          if (cursor) {
-            storeDump[cursor.key] = cursor.value;
-            cursor.continue();
-          }
-        });
-    }
-  });
-}
-
-export function importDatabase(db: IDBDatabase, dump: any): Promise<void> {
-  console.log("importing db", dump);
-  return new Promise<void>((resolve, reject) => {
-    const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
-    if (dump.stores) {
-      for (const storeName in dump.stores) {
-        const objects = [];
-        const dumpStore = dump.stores[storeName];
-        for (const key in dumpStore) {
-          objects.push(dumpStore[key]);
-        }
-        console.log(`importing ${objects.length} records into ${storeName}`);
-        const store = tx.objectStore(storeName);
-        for (const obj of objects) {
-          store.put(obj);
-        }
-      }
-    }
-    tx.addEventListener("complete", () => {
-      resolve();
-    });
-  });
-}
-
-export function deleteDatabase(idbFactory: IDBFactory) {
-  idbFactory.deleteDatabase(DB_NAME);
-}
+export function deleteTalerDatabase(idbFactory: IDBFactory) {
+  Database.deleteDatabase(idbFactory, TALER_DB_NAME);
+}
\ No newline at end of file
diff --git a/src/headless/helpers.ts b/src/headless/helpers.ts
index 2c0824a7..33304cd0 100644
--- a/src/headless/helpers.ts
+++ b/src/headless/helpers.ts
@@ -23,7 +23,7 @@
  */
 import { Wallet } from "../wallet";
 import { MemoryBackend, BridgeIDBFactory, shimIndexedDB } from "idb-bridge";
-import { openDatabase } from "../db";
+import { openTalerDatabase } from "../db";
 import Axios, { AxiosPromise, AxiosResponse } from "axios";
 import {
   HttpRequestLibrary,
@@ -39,6 +39,7 @@ import { NodeThreadCryptoWorkerFactory } from 
"../crypto/workers/nodeThreadWorke
 import { SynchronousCryptoWorkerFactory } from 
"../crypto/workers/synchronousWorker";
 import { RequestThrottler } from "../util/RequestThrottler";
 import { WalletNotification, NotificationType } from "../types/notifications";
+import { Database } from "../util/query";
 
 const logger = new Logger("helpers.ts");
 
@@ -191,7 +192,7 @@ export async function getDefaultNodeWallet(
 
   shimIndexedDB(myBridgeIdbFactory);
 
-  const myDb = await openDatabase(
+  const myDb = await openTalerDatabase(
     myIdbFactory,
     myVersionChange,
     myUnsupportedUpgrade,
@@ -202,7 +203,9 @@ export async function getDefaultNodeWallet(
 
   const worker = new NodeThreadCryptoWorkerFactory();
 
-  const w = new Wallet(myDb, myHttpLib, worker);
+  const dbWrap = new Database(myDb);
+
+  const w = new Wallet(dbWrap, myHttpLib, worker);
   if (args.notifyHandler) {
     w.addNotificationListener(args.notifyHandler);
   }
diff --git a/src/operations/balance.ts b/src/operations/balance.ts
index 8c8a2a9c..f5a51abe 100644
--- a/src/operations/balance.ts
+++ b/src/operations/balance.ts
@@ -18,7 +18,7 @@
  * Imports.
  */
 import { WalletBalance, WalletBalanceEntry } from "../types/walletTypes";
-import { runWithReadTransaction } from "../util/query";
+import { Database } from "../util/query";
 import { InternalWalletState } from "./state";
 import { Stores, TipRecord, CoinStatus } from "../types/dbTypes";
 import * as Amounts from "../util/amounts";
@@ -73,8 +73,7 @@ export async function getBalances(
     byExchange: {},
   };
 
-  await runWithReadTransaction(
-    ws.db,
+  await ws.db.runWithReadTransaction(
     [Stores.coins, Stores.refresh, Stores.reserves, Stores.purchases, 
Stores.withdrawalSession],
     async tx => {
       await tx.iter(Stores.coins).forEach(c => {
diff --git a/src/operations/exchanges.ts b/src/operations/exchanges.ts
index 836bce6e..6c4c1aa0 100644
--- a/src/operations/exchanges.ts
+++ b/src/operations/exchanges.ts
@@ -32,10 +32,7 @@ import {
   extractTalerStampOrThrow,
 } from "../util/helpers";
 import {
-  oneShotGet,
-  oneShotPut,
-  runWithWriteTransaction,
-  oneShotMutate,
+  Database
 } from "../util/query";
 import * as Amounts from "../util/amounts";
 import { parsePaytoUri } from "../util/payto";
@@ -81,7 +78,7 @@ async function setExchangeError(
     exchange.lastError = err;
     return exchange;
   };
-  await oneShotMutate(ws.db, Stores.exchanges, baseUrl, mut);
+  await ws.db.mutate( Stores.exchanges, baseUrl, mut);
 }
 
 /**
@@ -94,8 +91,7 @@ async function updateExchangeWithKeys(
   ws: InternalWalletState,
   baseUrl: string,
 ): Promise<void> {
-  const existingExchangeRecord = await oneShotGet(
-    ws.db,
+  const existingExchangeRecord = await ws.db.get(
     Stores.exchanges,
     baseUrl,
   );
@@ -180,8 +176,7 @@ async function updateExchangeWithKeys(
     ),
   );
 
-  await runWithWriteTransaction(
-    ws.db,
+  await ws.db.runWithWriteTransaction(
     [Stores.exchanges, Stores.denominations],
     async tx => {
       const r = await tx.get(Stores.exchanges, baseUrl);
@@ -222,7 +217,7 @@ async function updateExchangeWithTermsOfService(
   ws: InternalWalletState,
   exchangeBaseUrl: string,
 ) {
-  const exchange = await oneShotGet(ws.db, Stores.exchanges, exchangeBaseUrl);
+  const exchange = await ws.db.get(Stores.exchanges, exchangeBaseUrl);
   if (!exchange) {
     return;
   }
@@ -243,7 +238,7 @@ async function updateExchangeWithTermsOfService(
   const tosText = await resp.text();
   const tosEtag = resp.headers.get("etag") || undefined;
 
-  await runWithWriteTransaction(ws.db, [Stores.exchanges], async tx => {
+  await ws.db.runWithWriteTransaction([Stores.exchanges], async tx => {
     const r = await tx.get(Stores.exchanges, exchangeBaseUrl);
     if (!r) {
       return;
@@ -263,7 +258,7 @@ export async function acceptExchangeTermsOfService(
   exchangeBaseUrl: string,
   etag: string | undefined,
 ) {
-  await runWithWriteTransaction(ws.db, [Stores.exchanges], async tx => {
+  await ws.db.runWithWriteTransaction([Stores.exchanges], async tx => {
     const r = await tx.get(Stores.exchanges, exchangeBaseUrl);
     if (!r) {
       return;
@@ -283,7 +278,7 @@ async function updateExchangeWithWireInfo(
   ws: InternalWalletState,
   exchangeBaseUrl: string,
 ) {
-  const exchange = await oneShotGet(ws.db, Stores.exchanges, exchangeBaseUrl);
+  const exchange = await ws.db.get(Stores.exchanges, exchangeBaseUrl);
   if (!exchange) {
     return;
   }
@@ -349,7 +344,7 @@ async function updateExchangeWithWireInfo(
     feesForType[wireMethod] = feeList;
   }
 
-  await runWithWriteTransaction(ws.db, [Stores.exchanges], async tx => {
+  await ws.db.runWithWriteTransaction([Stores.exchanges], async tx => {
     const r = await tx.get(Stores.exchanges, exchangeBaseUrl);
     if (!r) {
       return;
@@ -392,7 +387,7 @@ async function updateExchangeFromUrlImpl(
   const now = getTimestampNow();
   baseUrl = canonicalizeBaseUrl(baseUrl);
 
-  const r = await oneShotGet(ws.db, Stores.exchanges, baseUrl);
+  const r = await ws.db.get(Stores.exchanges, baseUrl);
   if (!r) {
     const newExchangeRecord: ExchangeRecord = {
       baseUrl: baseUrl,
@@ -407,9 +402,9 @@ async function updateExchangeFromUrlImpl(
       termsOfServiceLastEtag: undefined,
       termsOfServiceText: undefined,
     };
-    await oneShotPut(ws.db, Stores.exchanges, newExchangeRecord);
+    await ws.db.put(Stores.exchanges, newExchangeRecord);
   } else {
-    await runWithWriteTransaction(ws.db, [Stores.exchanges], async t => {
+    await ws.db.runWithWriteTransaction([Stores.exchanges], async t => {
       const rec = await t.get(Stores.exchanges, baseUrl);
       if (!rec) {
         return;
@@ -431,7 +426,7 @@ async function updateExchangeFromUrlImpl(
   await updateExchangeWithWireInfo(ws, baseUrl);
   await updateExchangeWithTermsOfService(ws, baseUrl);
 
-  const updatedExchange = await oneShotGet(ws.db, Stores.exchanges, baseUrl);
+  const updatedExchange = await ws.db.get(Stores.exchanges, baseUrl);
 
   if (!updatedExchange) {
     // This should practically never happen
@@ -453,8 +448,7 @@ export async function getExchangeTrust(
   if (!exchangeDetails) {
     throw Error(`exchange ${exchangeInfo.baseUrl} details not available`);
   }
-  const currencyRecord = await oneShotGet(
-    ws.db,
+  const currencyRecord = await ws.db.get(
     Stores.currencies,
     exchangeDetails.currency,
   );
diff --git a/src/operations/history.ts b/src/operations/history.ts
index 9c4bb6a9..b8d756cc 100644
--- a/src/operations/history.ts
+++ b/src/operations/history.ts
@@ -17,7 +17,7 @@
 /**
  * Imports.
  */
-import { oneShotIter, runWithReadTransaction } from "../util/query";
+import { Database } from "../util/query";
 import { InternalWalletState } from "./state";
 import { Stores, TipRecord } from "../types/dbTypes";
 import * as Amounts from "../util/amounts";
@@ -38,8 +38,7 @@ export async function getHistory(
   // This works as timestamps are guaranteed to be monotonically
   // increasing even
 
-  await runWithReadTransaction(
-    ws.db,
+  await ws.db.runWithReadTransaction(
     [
       Stores.currencies,
       Stores.coins,
diff --git a/src/operations/pay.ts b/src/operations/pay.ts
index 08d22792..27f0e440 100644
--- a/src/operations/pay.ts
+++ b/src/operations/pay.ts
@@ -36,13 +36,7 @@ import {
   OperationError,
 } from "../types/walletTypes";
 import {
-  oneShotIter,
-  oneShotIterIndex,
-  oneShotGet,
-  runWithWriteTransaction,
-  oneShotPut,
-  oneShotGetIndexed,
-  oneShotMutate,
+  Database
 } from "../util/query";
 import {
   Stores,
@@ -202,7 +196,7 @@ async function getCoinsForPayment(
 
   let remainingAmount = paymentAmount;
 
-  const exchanges = await oneShotIter(ws.db, Stores.exchanges).toArray();
+  const exchanges = await ws.db.iter(Stores.exchanges).toArray();
 
   for (const exchange of exchanges) {
     let isOkay: boolean = false;
@@ -242,14 +236,12 @@ async function getCoinsForPayment(
       continue;
     }
 
-    const coins = await oneShotIterIndex(
-      ws.db,
+    const coins = await ws.db.iterIndex(
       Stores.coins.exchangeBaseUrlIndex,
       exchange.baseUrl,
     ).toArray();
 
-    const denoms = await oneShotIterIndex(
-      ws.db,
+    const denoms = await ws.db.iterIndex(
       Stores.denominations.exchangeBaseUrlIndex,
       exchange.baseUrl,
     ).toArray();
@@ -260,7 +252,7 @@ async function getCoinsForPayment(
 
     // Denomination of the first coin, we assume that all other
     // coins have the same currency
-    const firstDenom = await oneShotGet(ws.db, Stores.denominations, [
+    const firstDenom = await ws.db.get(Stores.denominations, [
       exchange.baseUrl,
       coins[0].denomPub,
     ]);
@@ -270,7 +262,7 @@ async function getCoinsForPayment(
     const currency = firstDenom.value.currency;
     const cds: CoinWithDenom[] = [];
     for (const coin of coins) {
-      const denom = await oneShotGet(ws.db, Stores.denominations, [
+      const denom = await ws.db.get(Stores.denominations, [
         exchange.baseUrl,
         coin.denomPub,
       ]);
@@ -377,8 +369,7 @@ async function recordConfirmPay(
     paymentSubmitPending: true,
   };
 
-  await runWithWriteTransaction(
-    ws.db,
+  await ws.db.runWithWriteTransaction(
     [Stores.coins, Stores.purchases, Stores.proposals],
     async tx => {
       const p = await tx.get(Stores.proposals, proposal.proposalId);
@@ -417,7 +408,7 @@ export async function abortFailedPayment(
   ws: InternalWalletState,
   proposalId: string,
 ): Promise<void> {
-  const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId);
+  const purchase = await ws.db.get(Stores.purchases, proposalId);
   if (!purchase) {
     throw Error("Purchase not found, unable to abort with refund");
   }
@@ -434,7 +425,7 @@ export async function abortFailedPayment(
   // From now on, we can't retry payment anymore,
   // so mark this in the DB in case the /pay abort
   // does not complete on the first try.
-  await oneShotPut(ws.db, Stores.purchases, purchase);
+  await ws.db.put(Stores.purchases, purchase);
 
   let resp;
 
@@ -457,7 +448,7 @@ export async function abortFailedPayment(
   const refundResponse = MerchantRefundResponse.checked(await resp.json());
   await acceptRefundResponse(ws, purchase.proposalId, refundResponse);
 
-  await runWithWriteTransaction(ws.db, [Stores.purchases], async tx => {
+  await ws.db.runWithWriteTransaction([Stores.purchases], async tx => {
     const p = await tx.get(Stores.purchases, proposalId);
     if (!p) {
       return;
@@ -472,7 +463,7 @@ async function incrementProposalRetry(
   proposalId: string,
   err: OperationError | undefined,
 ): Promise<void> {
-  await runWithWriteTransaction(ws.db, [Stores.proposals], async tx => {
+  await ws.db.runWithWriteTransaction([Stores.proposals], async tx => {
     const pr = await tx.get(Stores.proposals, proposalId);
     if (!pr) {
       return;
@@ -494,7 +485,7 @@ async function incrementPurchasePayRetry(
   err: OperationError | undefined,
 ): Promise<void> {
   console.log("incrementing purchase pay retry with error", err);
-  await runWithWriteTransaction(ws.db, [Stores.purchases], async tx => {
+  await ws.db.runWithWriteTransaction([Stores.purchases], async tx => {
     const pr = await tx.get(Stores.purchases, proposalId);
     if (!pr) {
       return;
@@ -516,7 +507,7 @@ async function incrementPurchaseQueryRefundRetry(
   err: OperationError | undefined,
 ): Promise<void> {
   console.log("incrementing purchase refund query retry with error", err);
-  await runWithWriteTransaction(ws.db, [Stores.purchases], async tx => {
+  await ws.db.runWithWriteTransaction([Stores.purchases], async tx => {
     const pr = await tx.get(Stores.purchases, proposalId);
     if (!pr) {
       return;
@@ -538,7 +529,7 @@ async function incrementPurchaseApplyRefundRetry(
   err: OperationError | undefined,
 ): Promise<void> {
   console.log("incrementing purchase refund apply retry with error", err);
-  await runWithWriteTransaction(ws.db, [Stores.purchases], async tx => {
+  await ws.db.runWithWriteTransaction([Stores.purchases], async tx => {
     const pr = await tx.get(Stores.purchases, proposalId);
     if (!pr) {
       return;
@@ -571,7 +562,7 @@ async function resetDownloadProposalRetry(
   ws: InternalWalletState,
   proposalId: string,
 ) {
-  await oneShotMutate(ws.db, Stores.proposals, proposalId, x => {
+  await ws.db.mutate(Stores.proposals, proposalId, x => {
     if (x.retryInfo.active) {
       x.retryInfo = initRetryInfo();
     }
@@ -587,7 +578,7 @@ async function processDownloadProposalImpl(
   if (forceNow) {
     await resetDownloadProposalRetry(ws, proposalId);
   }
-  const proposal = await oneShotGet(ws.db, Stores.proposals, proposalId);
+  const proposal = await ws.db.get(Stores.proposals, proposalId);
   if (!proposal) {
     return;
   }
@@ -621,8 +612,7 @@ async function processDownloadProposalImpl(
 
   const fulfillmentUrl = proposalResp.contract_terms.fulfillment_url;
 
-  await runWithWriteTransaction(
-    ws.db,
+  await ws.db.runWithWriteTransaction(
     [Stores.proposals, Stores.purchases],
     async tx => {
       const p = await tx.get(Stores.proposals, proposalId);
@@ -677,8 +667,7 @@ async function startDownloadProposal(
   orderId: string,
   sessionId: string | undefined,
 ): Promise<string> {
-  const oldProposal = await oneShotGetIndexed(
-    ws.db,
+  const oldProposal = await ws.db.getIndexed(
     Stores.proposals.urlAndOrderIdIndex,
     [merchantBaseUrl, orderId],
   );
@@ -705,7 +694,7 @@ async function startDownloadProposal(
     downloadSessionId: sessionId,
   };
 
-  await runWithWriteTransaction(ws.db, [Stores.proposals], async (tx) => {
+  await ws.db.runWithWriteTransaction([Stores.proposals], async (tx) => {
     const existingRecord = await 
tx.getIndexed(Stores.proposals.urlAndOrderIdIndex, [
       merchantBaseUrl,
       orderId,
@@ -725,7 +714,7 @@ export async function submitPay(
   ws: InternalWalletState,
   proposalId: string,
 ): Promise<ConfirmPayResult> {
-  const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId);
+  const purchase = await ws.db.get(Stores.purchases, proposalId);
   if (!purchase) {
     throw Error("Purchase not found: " + proposalId);
   }
@@ -788,7 +777,7 @@ export async function submitPay(
 
   const modifiedCoins: CoinRecord[] = [];
   for (const pc of purchase.payReq.coins) {
-    const c = await oneShotGet(ws.db, Stores.coins, pc.coin_pub);
+    const c = await ws.db.get(Stores.coins, pc.coin_pub);
     if (!c) {
       console.error("coin not found");
       throw Error("coin used in payment not found");
@@ -797,8 +786,7 @@ export async function submitPay(
     modifiedCoins.push(c);
   }
 
-  await runWithWriteTransaction(
-    ws.db,
+  await ws.db.runWithWriteTransaction(
     [Stores.coins, Stores.purchases],
     async tx => {
       for (let c of modifiedCoins) {
@@ -849,7 +837,7 @@ export async function preparePay(
     uriResult.sessionId,
   );
 
-  let proposal = await oneShotGet(ws.db, Stores.proposals, proposalId);
+  let proposal = await ws.db.get(Stores.proposals, proposalId);
   if (!proposal) {
     throw Error(`could not get proposal ${proposalId}`);
   }
@@ -859,7 +847,7 @@ export async function preparePay(
       throw Error("invalid proposal state");
     }
     console.log("using existing purchase for same product");
-    proposal = await oneShotGet(ws.db, Stores.proposals, existingProposalId);
+    proposal = await ws.db.get(Stores.proposals, existingProposalId);
     if (!proposal) {
       throw Error("existing proposal is in wrong state");
     }
@@ -878,7 +866,7 @@ export async function preparePay(
   proposalId = proposal.proposalId;
 
   // First check if we already payed for it.
-  const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId);
+  const purchase = await ws.db.get(Stores.purchases, proposalId);
 
   if (!purchase) {
     const paymentAmount = Amounts.parseOrThrow(contractTerms.amount);
@@ -966,7 +954,7 @@ async function getSpeculativePayData(
   const coinKeys = sp.payCoinInfo.updatedCoins.map(x => x.coinPub);
   const coins: CoinRecord[] = [];
   for (let coinKey of coinKeys) {
-    const cc = await oneShotGet(ws.db, Stores.coins, coinKey);
+    const cc = await ws.db.get(Stores.coins, coinKey);
     if (cc) {
       coins.push(cc);
     }
@@ -997,7 +985,7 @@ export async function confirmPay(
   logger.trace(
     `executing confirmPay with proposalId ${proposalId} and sessionIdOverride 
${sessionIdOverride}`,
   );
-  const proposal = await oneShotGet(ws.db, Stores.proposals, proposalId);
+  const proposal = await ws.db.get(Stores.proposals, proposalId);
 
   if (!proposal) {
     throw Error(`proposal with id ${proposalId} not found`);
@@ -1008,7 +996,7 @@ export async function confirmPay(
     throw Error("proposal is in invalid state");
   }
 
-  let purchase = await oneShotGet(ws.db, Stores.purchases, 
d.contractTermsHash);
+  let purchase = await ws.db.get(Stores.purchases, d.contractTermsHash);
 
   if (purchase) {
     if (
@@ -1016,7 +1004,7 @@ export async function confirmPay(
       sessionIdOverride != purchase.lastSessionId
     ) {
       logger.trace(`changing session ID to ${sessionIdOverride}`);
-      await oneShotMutate(ws.db, Stores.purchases, purchase.proposalId, x => {
+      await ws.db.mutate(Stores.purchases, purchase.proposalId, x => {
         x.lastSessionId = sessionIdOverride;
         x.paymentSubmitPending = true;
         return x;
@@ -1092,8 +1080,7 @@ export async function getFullRefundFees(
   if (refundPermissions.length === 0) {
     throw Error("no refunds given");
   }
-  const coin0 = await oneShotGet(
-    ws.db,
+  const coin0 = await ws.db.get(
     Stores.coins,
     refundPermissions[0].coin_pub,
   );
@@ -1104,18 +1091,17 @@ export async function getFullRefundFees(
     Amounts.parseOrThrow(refundPermissions[0].refund_amount).currency,
   );
 
-  const denoms = await oneShotIterIndex(
-    ws.db,
+  const denoms = await ws.db.iterIndex(
     Stores.denominations.exchangeBaseUrlIndex,
     coin0.exchangeBaseUrl,
   ).toArray();
 
   for (const rp of refundPermissions) {
-    const coin = await oneShotGet(ws.db, Stores.coins, rp.coin_pub);
+    const coin = await ws.db.get(Stores.coins, rp.coin_pub);
     if (!coin) {
       throw Error("coin not found");
     }
-    const denom = await oneShotGet(ws.db, Stores.denominations, [
+    const denom = await ws.db.get(Stores.denominations, [
       coin0.exchangeBaseUrl,
       coin.denomPub,
     ]);
@@ -1147,7 +1133,7 @@ async function acceptRefundResponse(
 
   let numNewRefunds = 0;
 
-  await runWithWriteTransaction(ws.db, [Stores.purchases], async tx => {
+  await ws.db.runWithWriteTransaction([Stores.purchases], async tx => {
     const p = await tx.get(Stores.purchases, proposalId);
     if (!p) {
       console.error("purchase not found, not adding refunds");
@@ -1215,8 +1201,7 @@ async function startRefundQuery(
   ws: InternalWalletState,
   proposalId: string,
 ): Promise<void> {
-  const success = await runWithWriteTransaction(
-    ws.db,
+  const success = await ws.db.runWithWriteTransaction(
     [Stores.purchases],
     async tx => {
       const p = await tx.get(Stores.purchases, proposalId);
@@ -1259,8 +1244,7 @@ export async function applyRefund(
     throw Error("invalid refund URI");
   }
 
-  const purchase = await oneShotGetIndexed(
-    ws.db,
+  const purchase = await ws.db.getIndexed(
     Stores.purchases.orderIdIndex,
     [parseResult.merchantBaseUrl, parseResult.orderId],
   );
@@ -1292,7 +1276,7 @@ async function resetPurchasePayRetry(
   ws: InternalWalletState,
   proposalId: string,
 ) {
-  await oneShotMutate(ws.db, Stores.purchases, proposalId, x => {
+  await ws.db.mutate(Stores.purchases, proposalId, x => {
     if (x.payRetryInfo.active) {
       x.payRetryInfo = initRetryInfo();
     }
@@ -1308,7 +1292,7 @@ async function processPurchasePayImpl(
   if (forceNow) {
     await resetPurchasePayRetry(ws, proposalId);
   }
-  const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId);
+  const purchase = await ws.db.get(Stores.purchases, proposalId);
   if (!purchase) {
     return;
   }
@@ -1336,7 +1320,7 @@ async function resetPurchaseQueryRefundRetry(
   ws: InternalWalletState,
   proposalId: string,
 ) {
-  await oneShotMutate(ws.db, Stores.purchases, proposalId, x => {
+  await ws.db.mutate(Stores.purchases, proposalId, x => {
     if (x.refundStatusRetryInfo.active) {
       x.refundStatusRetryInfo = initRetryInfo();
     }
@@ -1352,7 +1336,7 @@ async function processPurchaseQueryRefundImpl(
   if (forceNow) {
     await resetPurchaseQueryRefundRetry(ws, proposalId);
   }
-  const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId);
+  const purchase = await ws.db.get(Stores.purchases, proposalId);
   if (!purchase) {
     return;
   }
@@ -1398,7 +1382,7 @@ async function resetPurchaseApplyRefundRetry(
   ws: InternalWalletState,
   proposalId: string,
 ) {
-  await oneShotMutate(ws.db, Stores.purchases, proposalId, x => {
+  await ws.db.mutate(Stores.purchases, proposalId, x => {
     if (x.refundApplyRetryInfo.active) {
       x.refundApplyRetryInfo = initRetryInfo();
     }
@@ -1414,7 +1398,7 @@ async function processPurchaseApplyRefundImpl(
   if (forceNow) {
     await resetPurchaseApplyRefundRetry(ws, proposalId);
   }
-  const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId);
+  const purchase = await ws.db.get(Stores.purchases, proposalId);
   if (!purchase) {
     console.error("not submitting refunds, payment not found:");
     return;
@@ -1448,8 +1432,7 @@ async function processPurchaseApplyRefundImpl(
 
     let allRefundsProcessed = false;
 
-    await runWithWriteTransaction(
-      ws.db,
+    await ws.db.runWithWriteTransaction(
       [Stores.purchases, Stores.coins],
       async tx => {
         const p = await tx.get(Stores.purchases, proposalId);
diff --git a/src/operations/payback.ts b/src/operations/payback.ts
index 2d8a7283..51adb6ad 100644
--- a/src/operations/payback.ts
+++ b/src/operations/payback.ts
@@ -18,10 +18,7 @@
  * Imports.
  */
 import {
-  oneShotIter,
-  runWithWriteTransaction,
-  oneShotGet,
-  oneShotPut,
+  Database
 } from "../util/query";
 import { InternalWalletState } from "./state";
 import { Stores, TipRecord, CoinStatus } from "../types/dbTypes";
@@ -37,7 +34,7 @@ export async function payback(
   ws: InternalWalletState,
   coinPub: string,
 ): Promise<void> {
-  let coin = await oneShotGet(ws.db, Stores.coins, coinPub);
+  let coin = await ws.db.get(Stores.coins, coinPub);
   if (!coin) {
     throw Error(`Coin ${coinPub} not found, can't request payback`);
   }
@@ -45,7 +42,7 @@ export async function payback(
   if (!reservePub) {
     throw Error(`Can't request payback for a refreshed coin`);
   }
-  const reserve = await oneShotGet(ws.db, Stores.reserves, reservePub);
+  const reserve = await ws.db.get(Stores.reserves, reservePub);
   if (!reserve) {
     throw Error(`Reserve of coin ${coinPub} not found`);
   }
@@ -58,8 +55,7 @@ export async function payback(
   // technically we might update reserve status before we get the response
   // from the reserve for the payback request.
   reserve.hasPayback = true;
-  await runWithWriteTransaction(
-    ws.db,
+  await ws.db.runWithWriteTransaction(
     [Stores.coins, Stores.reserves],
     async tx => {
       await tx.put(Stores.coins, coin!!);
@@ -80,12 +76,12 @@ export async function payback(
   if (paybackConfirmation.reserve_pub !== coin.reservePub) {
     throw Error(`Coin's reserve doesn't match reserve on payback`);
   }
-  coin = await oneShotGet(ws.db, Stores.coins, coinPub);
+  coin = await ws.db.get(Stores.coins, coinPub);
   if (!coin) {
     throw Error(`Coin ${coinPub} not found, can't confirm payback`);
   }
   coin.status = CoinStatus.Dormant;
-  await oneShotPut(ws.db, Stores.coins, coin);
+  await ws.db.put(Stores.coins, coin);
   ws.notify({
     type: NotificationType.PaybackFinished,
   });
diff --git a/src/operations/pending.ts b/src/operations/pending.ts
index b9fc1d20..13859c64 100644
--- a/src/operations/pending.ts
+++ b/src/operations/pending.ts
@@ -22,7 +22,7 @@ import {
   Timestamp,
   Duration,
 } from "../types/walletTypes";
-import { runWithReadTransaction, TransactionHandle } from "../util/query";
+import { Database, TransactionHandle } from "../util/query";
 import { InternalWalletState } from "./state";
 import {
   Stores,
@@ -425,8 +425,7 @@ export async function getPendingOperations(
     pendingOperations: [],
   };
   const now = getTimestampNow();
-  await runWithReadTransaction(
-    ws.db,
+  await ws.db.runWithReadTransaction(
     [
       Stores.exchanges,
       Stores.reserves,
diff --git a/src/operations/refresh.ts b/src/operations/refresh.ts
index 4e4449d9..4ffc3ea6 100644
--- a/src/operations/refresh.ts
+++ b/src/operations/refresh.ts
@@ -27,21 +27,12 @@ import {
   updateRetryInfoTimeout,
 } from "../types/dbTypes";
 import { amountToPretty } from "../util/helpers";
-import {
-  oneShotGet,
-  oneShotMutate,
-  runWithWriteTransaction,
-  TransactionAbort,
-  oneShotIterIndex,
-} from "../util/query";
+import { Database } from "../util/query";
 import { InternalWalletState } from "./state";
 import { Logger } from "../util/logging";
 import { getWithdrawDenomList } from "./withdraw";
 import { updateExchangeFromUrl } from "./exchanges";
-import {
-  getTimestampNow,
-  OperationError,
-} from "../types/walletTypes";
+import { getTimestampNow, OperationError } from "../types/walletTypes";
 import { guardOperationException } from "./errors";
 import { NotificationType } from "../types/notifications";
 
@@ -84,11 +75,7 @@ async function refreshMelt(
   ws: InternalWalletState,
   refreshSessionId: string,
 ): Promise<void> {
-  const refreshSession = await oneShotGet(
-    ws.db,
-    Stores.refresh,
-    refreshSessionId,
-  );
+  const refreshSession = await ws.db.get(Stores.refresh, refreshSessionId);
   if (!refreshSession) {
     return;
   }
@@ -96,11 +83,7 @@ async function refreshMelt(
     return;
   }
 
-  const coin = await oneShotGet(
-    ws.db,
-    Stores.coins,
-    refreshSession.meltCoinPub,
-  );
+  const coin = await ws.db.get(Stores.coins, refreshSession.meltCoinPub);
 
   if (!coin) {
     console.error("can't melt coin, it does not exist");
@@ -139,7 +122,7 @@ async function refreshMelt(
 
   refreshSession.norevealIndex = norevealIndex;
 
-  await oneShotMutate(ws.db, Stores.refresh, refreshSessionId, rs => {
+  await ws.db.mutate(Stores.refresh, refreshSessionId, rs => {
     if (rs.norevealIndex !== undefined) {
       return;
     }
@@ -159,11 +142,7 @@ async function refreshReveal(
   ws: InternalWalletState,
   refreshSessionId: string,
 ): Promise<void> {
-  const refreshSession = await oneShotGet(
-    ws.db,
-    Stores.refresh,
-    refreshSessionId,
-  );
+  const refreshSession = await ws.db.get(Stores.refresh, refreshSessionId);
   if (!refreshSession) {
     return;
   }
@@ -179,8 +158,7 @@ async function refreshReveal(
     throw Error("refresh index error");
   }
 
-  const meltCoinRecord = await oneShotGet(
-    ws.db,
+  const meltCoinRecord = await ws.db.get(
     Stores.coins,
     refreshSession.meltCoinPub,
   );
@@ -241,7 +219,7 @@ async function refreshReveal(
   const coins: CoinRecord[] = [];
 
   for (let i = 0; i < respJson.ev_sigs.length; i++) {
-    const denom = await oneShotGet(ws.db, Stores.denominations, [
+    const denom = await ws.db.get(Stores.denominations, [
       refreshSession.exchangeBaseUrl,
       refreshSession.newDenoms[i],
     ]);
@@ -274,8 +252,7 @@ async function refreshReveal(
     coins.push(coin);
   }
 
-  await runWithWriteTransaction(
-    ws.db,
+  await ws.db.runWithWriteTransaction(
     [Stores.coins, Stores.refresh],
     async tx => {
       const rs = await tx.get(Stores.refresh, refreshSessionId);
@@ -306,7 +283,7 @@ async function incrementRefreshRetry(
   refreshSessionId: string,
   err: OperationError | undefined,
 ): Promise<void> {
-  await runWithWriteTransaction(ws.db, [Stores.refresh], async tx => {
+  await ws.db.runWithWriteTransaction([Stores.refresh], async tx => {
     const r = await tx.get(Stores.refresh, refreshSessionId);
     if (!r) {
       return;
@@ -341,7 +318,7 @@ async function resetRefreshSessionRetry(
   ws: InternalWalletState,
   refreshSessionId: string,
 ) {
-  await oneShotMutate(ws.db, Stores.refresh, refreshSessionId, (x) => {
+  await ws.db.mutate(Stores.refresh, refreshSessionId, x => {
     if (x.retryInfo.active) {
       x.retryInfo = initRetryInfo();
     }
@@ -357,11 +334,7 @@ async function processRefreshSessionImpl(
   if (forceNow) {
     await resetRefreshSessionRetry(ws, refreshSessionId);
   }
-  const refreshSession = await oneShotGet(
-    ws.db,
-    Stores.refresh,
-    refreshSessionId,
-  );
+  const refreshSession = await ws.db.get(Stores.refresh, refreshSessionId);
   if (!refreshSession) {
     return;
   }
@@ -380,7 +353,7 @@ export async function refresh(
   oldCoinPub: string,
   force: boolean = false,
 ): Promise<void> {
-  const coin = await oneShotGet(ws.db, Stores.coins, oldCoinPub);
+  const coin = await ws.db.get(Stores.coins, oldCoinPub);
   if (!coin) {
     console.warn("can't refresh, coin not in database");
     return;
@@ -402,7 +375,7 @@ export async function refresh(
     throw Error("db inconsistent: exchange of coin not found");
   }
 
-  const oldDenom = await oneShotGet(ws.db, Stores.denominations, [
+  const oldDenom = await ws.db.get(Stores.denominations, [
     exchange.baseUrl,
     coin.denomPub,
   ]);
@@ -411,11 +384,9 @@ export async function refresh(
     throw Error("db inconsistent: denomination for coin not found");
   }
 
-  const availableDenoms: DenominationRecord[] = await oneShotIterIndex(
-    ws.db,
-    Stores.denominations.exchangeBaseUrlIndex,
-    exchange.baseUrl,
-  ).toArray();
+  const availableDenoms: DenominationRecord[] = await ws.db
+    .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl)
+    .toArray();
 
   const availableAmount = Amounts.sub(coin.currentAmount, oldDenom.feeRefresh)
     .amount;
@@ -428,7 +399,7 @@ export async function refresh(
         availableAmount,
       )} too small`,
     );
-    await oneShotMutate(ws.db, Stores.coins, oldCoinPub, x => {
+    await ws.db.mutate(Stores.coins, oldCoinPub, x => {
       if (x.status != coin.status) {
         // Concurrent modification?
         return;
@@ -450,8 +421,7 @@ export async function refresh(
 
   // Store refresh session and subtract refreshed amount from
   // coin in the same transaction.
-  await runWithWriteTransaction(
-    ws.db,
+  await ws.db.runWithWriteTransaction(
     [Stores.refresh, Stores.coins],
     async tx => {
       const c = await tx.get(Stores.coins, coin.coinPub);
diff --git a/src/operations/reserves.ts b/src/operations/reserves.ts
index 5ad13a67..215d5ba7 100644
--- a/src/operations/reserves.ts
+++ b/src/operations/reserves.ts
@@ -33,10 +33,7 @@ import {
   updateRetryInfoTimeout,
 } from "../types/dbTypes";
 import {
-  oneShotMutate,
-  oneShotPut,
-  oneShotGet,
-  runWithWriteTransaction,
+  Database,
   TransactionAbort,
 } from "../util/query";
 import { Logger } from "../util/logging";
@@ -104,7 +101,7 @@ export async function createReserve(
     const rec = {
       paytoUri: senderWire,
     };
-    await oneShotPut(ws.db, Stores.senderWires, rec);
+    await ws.db.put(Stores.senderWires, rec);
   }
 
   const exchangeInfo = await updateExchangeFromUrl(ws, req.exchange);
@@ -114,8 +111,7 @@ export async function createReserve(
     throw Error("exchange not updated");
   }
   const { isAudited, isTrusted } = await getExchangeTrust(ws, exchangeInfo);
-  let currencyRecord = await oneShotGet(
-    ws.db,
+  let currencyRecord = await ws.db.get(
     Stores.currencies,
     exchangeDetails.currency,
   );
@@ -137,8 +133,7 @@ export async function createReserve(
 
   const cr: CurrencyRecord = currencyRecord;
 
-  const resp = await runWithWriteTransaction(
-    ws.db,
+  const resp = await ws.db.runWithWriteTransaction(
     [Stores.currencies, Stores.reserves, Stores.bankWithdrawUris],
     async tx => {
       // Check if we have already created a reserve for that 
bankWithdrawStatusUrl
@@ -212,7 +207,7 @@ async function registerReserveWithBank(
   ws: InternalWalletState,
   reservePub: string,
 ): Promise<void> {
-  let reserve = await oneShotGet(ws.db, Stores.reserves, reservePub);
+  let reserve = await ws.db.get(Stores.reserves, reservePub);
   switch (reserve?.reserveStatus) {
     case ReserveRecordStatus.WAIT_CONFIRM_BANK:
     case ReserveRecordStatus.REGISTERING_BANK:
@@ -233,7 +228,7 @@ async function registerReserveWithBank(
     selected_exchange: reserve.exchangeWire,
   });
   console.log("got response", bankResp);
-  await oneShotMutate(ws.db, Stores.reserves, reservePub, r => {
+  await ws.db.mutate(Stores.reserves, reservePub, r => {
     switch (r.reserveStatus) {
       case ReserveRecordStatus.REGISTERING_BANK:
       case ReserveRecordStatus.WAIT_CONFIRM_BANK:
@@ -266,7 +261,7 @@ async function processReserveBankStatusImpl(
   ws: InternalWalletState,
   reservePub: string,
 ): Promise<void> {
-  let reserve = await oneShotGet(ws.db, Stores.reserves, reservePub);
+  let reserve = await ws.db.get(Stores.reserves, reservePub);
   switch (reserve?.reserveStatus) {
     case ReserveRecordStatus.WAIT_CONFIRM_BANK:
     case ReserveRecordStatus.REGISTERING_BANK:
@@ -303,7 +298,7 @@ async function processReserveBankStatusImpl(
   }
 
   if (status.transfer_done) {
-    await oneShotMutate(ws.db, Stores.reserves, reservePub, r => {
+    await ws.db.mutate(Stores.reserves, reservePub, r => {
       switch (r.reserveStatus) {
         case ReserveRecordStatus.REGISTERING_BANK:
         case ReserveRecordStatus.WAIT_CONFIRM_BANK:
@@ -319,7 +314,7 @@ async function processReserveBankStatusImpl(
     });
     await processReserveImpl(ws, reservePub, true);
   } else {
-    await oneShotMutate(ws.db, Stores.reserves, reservePub, r => {
+    await ws.db.mutate(Stores.reserves, reservePub, r => {
       switch (r.reserveStatus) {
         case ReserveRecordStatus.WAIT_CONFIRM_BANK:
           break;
@@ -339,7 +334,7 @@ async function incrementReserveRetry(
   reservePub: string,
   err: OperationError | undefined,
 ): Promise<void> {
-  await runWithWriteTransaction(ws.db, [Stores.reserves], async tx => {
+  await ws.db.runWithWriteTransaction([Stores.reserves], async tx => {
     const r = await tx.get(Stores.reserves, reservePub);
     if (!r) {
       return;
@@ -363,7 +358,7 @@ async function updateReserve(
   ws: InternalWalletState,
   reservePub: string,
 ): Promise<void> {
-  const reserve = await oneShotGet(ws.db, Stores.reserves, reservePub);
+  const reserve = await ws.db.get(Stores.reserves, reservePub);
   if (!reserve) {
     throw Error("reserve not in db");
   }
@@ -400,7 +395,7 @@ async function updateReserve(
   }
   const reserveInfo = ReserveStatus.checked(await resp.json());
   const balance = Amounts.parseOrThrow(reserveInfo.balance);
-  await oneShotMutate(ws.db, Stores.reserves, reserve.reservePub, r => {
+  await ws.db.mutate(Stores.reserves, reserve.reservePub, r => {
     if (r.reserveStatus !== ReserveRecordStatus.QUERYING_STATUS) {
       return;
     }
@@ -442,7 +437,7 @@ async function processReserveImpl(
   reservePub: string,
   forceNow: boolean = false,
 ): Promise<void> {
-  const reserve = await oneShotGet(ws.db, Stores.reserves, reservePub);
+  const reserve = await ws.db.get(Stores.reserves, reservePub);
   if (!reserve) {
     console.log("not processing reserve: reserve does not exist");
     return;
@@ -488,7 +483,7 @@ export async function confirmReserve(
   req: ConfirmReserveRequest,
 ): Promise<void> {
   const now = getTimestampNow();
-  await oneShotMutate(ws.db, Stores.reserves, req.reservePub, reserve => {
+  await ws.db.mutate(Stores.reserves, req.reservePub, reserve => {
     if (reserve.reserveStatus !== ReserveRecordStatus.UNCONFIRMED) {
       return;
     }
@@ -515,7 +510,7 @@ async function depleteReserve(
   ws: InternalWalletState,
   reservePub: string,
 ): Promise<void> {
-  const reserve = await oneShotGet(ws.db, Stores.reserves, reservePub);
+  const reserve = await ws.db.get(Stores.reserves, reservePub);
   if (!reserve) {
     return;
   }
@@ -600,8 +595,7 @@ async function depleteReserve(
     return r;
   }
 
-  const success = await runWithWriteTransaction(
-    ws.db,
+  const success = await ws.db.runWithWriteTransaction(
     [Stores.withdrawalSession, Stores.reserves],
     async tx => {
       const myReserve = await tx.get(Stores.reserves, reservePub);
diff --git a/src/operations/return.ts b/src/operations/return.ts
index 74885a73..01d2802d 100644
--- a/src/operations/return.ts
+++ b/src/operations/return.ts
@@ -21,7 +21,7 @@ import {
   ReturnCoinsRequest,
   CoinWithDenom,
 } from "../types/walletTypes";
-import { runWithWriteTransaction, oneShotGet, oneShotIterIndex, oneShotPut } 
from "../util/query";
+import { Database } from "../util/query";
 import { InternalWalletState } from "./state";
 import { Stores, TipRecord, CoinStatus, CoinsReturnRecord, CoinRecord } from 
"../types/dbTypes";
 import * as Amounts from "../util/amounts";
@@ -38,8 +38,7 @@ async function getCoinsForReturn(
   exchangeBaseUrl: string,
   amount: AmountJson,
 ): Promise<CoinWithDenom[] | undefined> {
-  const exchange = await oneShotGet(
-    ws.db,
+  const exchange = await ws.db.get(
     Stores.exchanges,
     exchangeBaseUrl,
   );
@@ -47,8 +46,7 @@ async function getCoinsForReturn(
     throw Error(`Exchange ${exchangeBaseUrl} not known to the wallet`);
   }
 
-  const coins: CoinRecord[] = await oneShotIterIndex(
-    ws.db,
+  const coins: CoinRecord[] = await ws.db.iterIndex(
     Stores.coins.exchangeBaseUrlIndex,
     exchange.baseUrl,
   ).toArray();
@@ -57,15 +55,14 @@ async function getCoinsForReturn(
     return [];
   }
 
-  const denoms = await oneShotIterIndex(
-    ws.db,
+  const denoms = await ws.db.iterIndex(
     Stores.denominations.exchangeBaseUrlIndex,
     exchange.baseUrl,
   ).toArray();
 
   // Denomination of the first coin, we assume that all other
   // coins have the same currency
-  const firstDenom = await oneShotGet(ws.db, Stores.denominations, [
+  const firstDenom = await ws.db.get(Stores.denominations, [
     exchange.baseUrl,
     coins[0].denomPub,
   ]);
@@ -76,7 +73,7 @@ async function getCoinsForReturn(
 
   const cds: CoinWithDenom[] = [];
   for (const coin of coins) {
-    const denom = await oneShotGet(ws.db, Stores.denominations, [
+    const denom = await ws.db.get(Stores.denominations, [
       exchange.baseUrl,
       coin.denomPub,
     ]);
@@ -121,7 +118,7 @@ export async function returnCoins(
     return;
   }
   const stampSecNow = Math.floor(new Date().getTime() / 1000);
-  const exchange = await oneShotGet(ws.db, Stores.exchanges, req.exchange);
+  const exchange = await ws.db.get(Stores.exchanges, req.exchange);
   if (!exchange) {
     console.error(`Exchange ${req.exchange} not known to the wallet`);
     return;
@@ -190,8 +187,7 @@ export async function returnCoins(
     wire: req.senderWire,
   };
 
-  await runWithWriteTransaction(
-    ws.db,
+  await ws.db.runWithWriteTransaction(
     [Stores.coinsReturns, Stores.coins],
     async tx => {
       await tx.put(Stores.coinsReturns, coinsReturnRecord);
@@ -248,8 +244,7 @@ async function depositReturnedCoins(
     // FIXME: verify signature
 
     // For every successful deposit, we replace the old record with an updated 
one
-    const currentCrr = await oneShotGet(
-      ws.db,
+    const currentCrr = await ws.db.get(
       Stores.coinsReturns,
       coinsReturnRecord.contractTermsHash,
     );
@@ -262,6 +257,6 @@ async function depositReturnedCoins(
         nc.depositedSig = respJson.sig;
       }
     }
-    await oneShotPut(ws.db, Stores.coinsReturns, currentCrr);
+    await ws.db.put(Stores.coinsReturns, currentCrr);
   }
 }
diff --git a/src/operations/state.ts b/src/operations/state.ts
index 47bf40de..1e4b9036 100644
--- a/src/operations/state.ts
+++ b/src/operations/state.ts
@@ -25,6 +25,7 @@ import { AsyncOpMemoMap, AsyncOpMemoSingle } from 
"../util/asyncMemo";
 import { Logger } from "../util/logging";
 import { PendingOperationsResponse } from "../types/pending";
 import { WalletNotification } from "../types/notifications";
+import { Database } from "../util/query";
 
 type NotificationListener = (n: WalletNotification) => void;
 
@@ -45,7 +46,7 @@ export class InternalWalletState {
   listeners: NotificationListener[] = [];
 
   constructor(
-    public db: IDBDatabase,
+    public db: Database,
     public http: HttpRequestLibrary,
     cryptoWorkerFactory: CryptoWorkerFactory,
   ) {
diff --git a/src/operations/tip.ts b/src/operations/tip.ts
index 0a710f67..f723374f 100644
--- a/src/operations/tip.ts
+++ b/src/operations/tip.ts
@@ -15,7 +15,7 @@
  */
 
  
-import { oneShotGet, oneShotPut, oneShotMutate, runWithWriteTransaction } from 
"../util/query";
+import { Database } from "../util/query";
 import { InternalWalletState } from "./state";
 import { parseTipUri } from "../util/taleruri";
 import { TipStatus, getTimestampNow, OperationError } from 
"../types/walletTypes";
@@ -53,7 +53,7 @@ export async function getTipStatus(
 
   let amount = Amounts.parseOrThrow(tipPickupStatus.amount);
 
-  let tipRecord = await oneShotGet(ws.db, Stores.tips, [
+  let tipRecord = await ws.db.get(Stores.tips, [
     res.merchantTipId,
     res.merchantOrigin,
   ]);
@@ -87,7 +87,7 @@ export async function getTipStatus(
       retryInfo: initRetryInfo(),
       lastError: undefined,
     };
-    await oneShotPut(ws.db, Stores.tips, tipRecord);
+    await ws.db.put(Stores.tips, tipRecord);
   }
 
   const tipStatus: TipStatus = {
@@ -112,7 +112,7 @@ async function incrementTipRetry(
   refreshSessionId: string,
   err: OperationError | undefined,
 ): Promise<void> {
-  await runWithWriteTransaction(ws.db, [Stores.tips], async tx => {
+  await ws.db.runWithWriteTransaction([Stores.tips], async tx => {
     const t = await tx.get(Stores.tips, refreshSessionId);
     if (!t) {
       return;
@@ -141,7 +141,7 @@ async function resetTipRetry(
   ws: InternalWalletState,
   tipId: string,
 ): Promise<void> {
-  await oneShotMutate(ws.db, Stores.tips, tipId, (x) => {
+  await ws.db.mutate(Stores.tips, tipId, (x) => {
     if (x.retryInfo.active) {
       x.retryInfo = initRetryInfo();
     }
@@ -157,7 +157,7 @@ async function processTipImpl(
   if (forceNow) {
     await resetTipRetry(ws, tipId);
   }
-  let tipRecord = await oneShotGet(ws.db, Stores.tips, tipId);
+  let tipRecord = await ws.db.get(Stores.tips, tipId);
   if (!tipRecord) {
     return;
   }
@@ -179,7 +179,7 @@ async function processTipImpl(
       denomsForWithdraw.map(d => ws.cryptoApi.createTipPlanchet(d)),
     );
 
-    await oneShotMutate(ws.db, Stores.tips, tipId, r => {
+    await ws.db.mutate(Stores.tips, tipId, r => {
       if (!r.planchets) {
         r.planchets = planchets;
       }
@@ -187,7 +187,7 @@ async function processTipImpl(
     });
   }
 
-  tipRecord = await oneShotGet(ws.db, Stores.tips, tipId);
+  tipRecord = await ws.db.get(Stores.tips, tipId);
   if (!tipRecord) {
     throw Error("tip not in database");
   }
@@ -267,7 +267,7 @@ async function processTipImpl(
   };
 
 
-  await runWithWriteTransaction(ws.db, [Stores.tips, 
Stores.withdrawalSession], async (tx) => {
+  await ws.db.runWithWriteTransaction([Stores.tips, Stores.withdrawalSession], 
async (tx) => {
     const tr = await tx.get(Stores.tips, tipId);
     if (!tr) {
       return;
@@ -291,14 +291,14 @@ export async function acceptTip(
   ws: InternalWalletState,
   tipId: string,
 ): Promise<void> {
-  const tipRecord = await oneShotGet(ws.db, Stores.tips, tipId);
+  const tipRecord = await ws.db.get(Stores.tips, tipId);
   if (!tipRecord) {
     console.log("tip not found");
     return;
   }
 
   tipRecord.accepted = true;
-  await oneShotPut(ws.db, Stores.tips, tipRecord);
+  await ws.db.put(Stores.tips, tipRecord);
 
   await processTip(ws, tipId);
   return;
diff --git a/src/operations/withdraw.ts b/src/operations/withdraw.ts
index 4ecc321f..a34eec5a 100644
--- a/src/operations/withdraw.ts
+++ b/src/operations/withdraw.ts
@@ -39,12 +39,7 @@ import { InternalWalletState } from "./state";
 import { parseWithdrawUri } from "../util/taleruri";
 import { Logger } from "../util/logging";
 import {
-  oneShotGet,
-  oneShotPut,
-  oneShotIterIndex,
-  oneShotGetIndexed,
-  runWithWriteTransaction,
-  oneShotMutate,
+  Database
 } from "../util/query";
 import {
   updateExchangeFromUrl,
@@ -167,8 +162,7 @@ async function getPossibleDenoms(
   ws: InternalWalletState,
   exchangeBaseUrl: string,
 ): Promise<DenominationRecord[]> {
-  return await oneShotIterIndex(
-    ws.db,
+  return await ws.db.iterIndex(
     Stores.denominations.exchangeBaseUrlIndex,
     exchangeBaseUrl,
   ).filter(d => {
@@ -187,8 +181,7 @@ async function processPlanchet(
   withdrawalSessionId: string,
   coinIdx: number,
 ): Promise<void> {
-  const withdrawalSession = await oneShotGet(
-    ws.db,
+  const withdrawalSession = await ws.db.get(
     Stores.withdrawalSession,
     withdrawalSessionId,
   );
@@ -205,8 +198,7 @@ async function processPlanchet(
     console.log("processPlanchet: planchet not found");
     return;
   }
-  const exchange = await oneShotGet(
-    ws.db,
+  const exchange = await ws.db.get(
     Stores.exchanges,
     withdrawalSession.exchangeBaseUrl,
   );
@@ -215,7 +207,7 @@ async function processPlanchet(
     return;
   }
 
-  const denom = await oneShotGet(ws.db, Stores.denominations, [
+  const denom = await ws.db.get(Stores.denominations, [
     withdrawalSession.exchangeBaseUrl,
     planchet.denomPub,
   ]);
@@ -268,8 +260,7 @@ async function processPlanchet(
   let withdrawSessionFinished = false;
   let reserveDepleted = false;
 
-  const success = await runWithWriteTransaction(
-    ws.db,
+  const success = await ws.db.runWithWriteTransaction(
     [Stores.coins, Stores.withdrawalSession, Stores.reserves],
     async tx => {
       const ws = await tx.get(Stores.withdrawalSession, withdrawalSessionId);
@@ -346,7 +337,7 @@ export async function getVerifiedWithdrawDenomList(
   exchangeBaseUrl: string,
   amount: AmountJson,
 ): Promise<DenominationRecord[]> {
-  const exchange = await oneShotGet(ws.db, Stores.exchanges, exchangeBaseUrl);
+  const exchange = await ws.db.get(Stores.exchanges, exchangeBaseUrl);
   if (!exchange) {
     console.log("exchange not found");
     throw Error(`exchange ${exchangeBaseUrl} not found`);
@@ -391,7 +382,7 @@ export async function getVerifiedWithdrawDenomList(
           denom.status = DenominationStatus.VerifiedGood;
           nextPossibleDenoms.push(denom);
         }
-        await oneShotPut(ws.db, Stores.denominations, denom);
+        await ws.db.put(Stores.denominations, denom);
       } else {
         nextPossibleDenoms.push(denom);
       }
@@ -408,8 +399,7 @@ async function makePlanchet(
   withdrawalSessionId: string,
   coinIndex: number,
 ): Promise<void> {
-  const withdrawalSession = await oneShotGet(
-    ws.db,
+  const withdrawalSession = await ws.db.get(
     Stores.withdrawalSession,
     withdrawalSessionId,
   );
@@ -420,11 +410,11 @@ async function makePlanchet(
   if (src.type !== "reserve") {
     throw Error("invalid state");
   }
-  const reserve = await oneShotGet(ws.db, Stores.reserves, src.reservePub);
+  const reserve = await ws.db.get(Stores.reserves, src.reservePub);
   if (!reserve) {
     return;
   }
-  const denom = await oneShotGet(ws.db, Stores.denominations, [
+  const denom = await ws.db.get(Stores.denominations, [
     withdrawalSession.exchangeBaseUrl,
     withdrawalSession.denoms[coinIndex],
   ]);
@@ -450,7 +440,7 @@ async function makePlanchet(
     reservePub: r.reservePub,
     withdrawSig: r.withdrawSig,
   };
-  await runWithWriteTransaction(ws.db, [Stores.withdrawalSession], async tx => 
{
+  await ws.db.runWithWriteTransaction([Stores.withdrawalSession], async tx => {
     const myWs = await tx.get(Stores.withdrawalSession, withdrawalSessionId);
     if (!myWs) {
       return;
@@ -469,8 +459,7 @@ async function processWithdrawCoin(
   coinIndex: number,
 ) {
   logger.trace("starting withdraw for coin", coinIndex);
-  const withdrawalSession = await oneShotGet(
-    ws.db,
+  const withdrawalSession = await ws.db.get(
     Stores.withdrawalSession,
     withdrawalSessionId,
   );
@@ -479,8 +468,7 @@ async function processWithdrawCoin(
     return;
   }
 
-  const coin = await oneShotGetIndexed(
-    ws.db,
+  const coin = await ws.db.getIndexed(
     Stores.coins.byWithdrawalWithIdx,
     [withdrawalSessionId, coinIndex],
   );
@@ -505,7 +493,7 @@ async function incrementWithdrawalRetry(
   withdrawalSessionId: string,
   err: OperationError | undefined,
 ): Promise<void> {
-  await runWithWriteTransaction(ws.db, [Stores.withdrawalSession], async tx => 
{
+  await ws.db.runWithWriteTransaction([Stores.withdrawalSession], async tx => {
     const wsr = await tx.get(Stores.withdrawalSession, withdrawalSessionId);
     if (!wsr) {
       return;
@@ -538,7 +526,7 @@ async function resetWithdrawSessionRetry(
   ws: InternalWalletState,
   withdrawalSessionId: string,
 ) {
-  await oneShotMutate(ws.db, Stores.withdrawalSession, withdrawalSessionId, 
(x) => {
+  await ws.db.mutate(Stores.withdrawalSession, withdrawalSessionId, (x) => {
     if (x.retryInfo.active) {
       x.retryInfo = initRetryInfo();
     }
@@ -555,8 +543,7 @@ async function processWithdrawSessionImpl(
   if (forceNow) {
     await resetWithdrawSessionRetry(ws, withdrawalSessionId);
   }
-  const withdrawalSession = await oneShotGet(
-    ws.db,
+  const withdrawalSession = await ws.db.get(
     Stores.withdrawalSession,
     withdrawalSessionId,
   );
@@ -615,15 +602,13 @@ export async function getExchangeWithdrawalInfo(
     }
   }
 
-  const possibleDenoms = await oneShotIterIndex(
-    ws.db,
+  const possibleDenoms = await ws.db.iterIndex(
     Stores.denominations.exchangeBaseUrlIndex,
     baseUrl,
   ).filter(d => d.isOffered);
 
   const trustedAuditorPubs = [];
-  const currencyRecord = await oneShotGet(
-    ws.db,
+  const currencyRecord = await ws.db.get(
     Stores.currencies,
     amount.currency,
   );
diff --git a/src/util/query.ts b/src/util/query.ts
index e05656bb..08a8fec0 100644
--- a/src/util/query.ts
+++ b/src/util/query.ts
@@ -25,22 +25,6 @@
  */
 import { openPromise } from "./promiseUtils";
 
-/**
- * Result of an inner join.
- */
-export interface JoinResult<L, R> {
-  left: L;
-  right: R;
-}
-
-/**
- * Result of a left outer join.
- */
-export interface JoinLeftResult<L, R> {
-  left: L;
-  right?: R;
-}
-
 /**
  * Definition of an object store.
  */
@@ -95,46 +79,6 @@ function transactionToPromise(tx: IDBTransaction): 
Promise<void> {
   });
 }
 
-export async function oneShotGet<T>(
-  db: IDBDatabase,
-  store: Store<T>,
-  key: any,
-): Promise<T | undefined> {
-  const tx = db.transaction([store.name], "readonly");
-  const req = tx.objectStore(store.name).get(key);
-  const v = await requestToPromise(req);
-  await transactionToPromise(tx);
-  return v;
-}
-
-export async function oneShotGetIndexed<S extends IDBValidKey, T>(
-  db: IDBDatabase,
-  index: Index<S, T>,
-  key: any,
-): Promise<T | undefined> {
-  const tx = db.transaction([index.storeName], "readonly");
-  const req = tx
-    .objectStore(index.storeName)
-    .index(index.indexName)
-    .get(key);
-  const v = await requestToPromise(req);
-  await transactionToPromise(tx);
-  return v;
-}
-
-export async function oneShotPut<T>(
-  db: IDBDatabase,
-  store: Store<T>,
-  value: T,
-  key?: any,
-): Promise<any> {
-  const tx = db.transaction([store.name], "readwrite");
-  const req = tx.objectStore(store.name).put(value, key);
-  const v = await requestToPromise(req);
-  await transactionToPromise(tx);
-  return v;
-}
-
 function applyMutation<T>(
   req: IDBRequest,
   f: (x: T) => T | undefined,
@@ -166,18 +110,6 @@ function applyMutation<T>(
   });
 }
 
-export async function oneShotMutate<T>(
-  db: IDBDatabase,
-  store: Store<T>,
-  key: any,
-  f: (x: T) => T | undefined,
-): Promise<void> {
-  const tx = db.transaction([store.name], "readwrite");
-  const req = tx.objectStore(store.name).openCursor(key);
-  await applyMutation(req, f);
-  await transactionToPromise(tx);
-}
-
 type CursorResult<T> = CursorEmptyResult<T> | CursorValueResult<T>;
 
 interface CursorEmptyResult<T> {
@@ -294,28 +226,6 @@ class ResultStream<T> {
   }
 }
 
-export function oneShotIter<T>(
-  db: IDBDatabase,
-  store: Store<T>,
-): ResultStream<T> {
-  const tx = db.transaction([store.name], "readonly");
-  const req = tx.objectStore(store.name).openCursor();
-  return new ResultStream<T>(req);
-}
-
-export function oneShotIterIndex<S extends IDBValidKey, T>(
-  db: IDBDatabase,
-  index: Index<S, T>,
-  query?: any,
-): ResultStream<T> {
-  const tx = db.transaction([index.storeName], "readonly");
-  const req = tx
-    .objectStore(index.storeName)
-    .index(index.indexName)
-    .openCursor(query);
-  return new ResultStream<T>(req);
-}
-
 export class TransactionHandle {
   constructor(private tx: IDBTransaction) {}
 
@@ -361,22 +271,6 @@ export class TransactionHandle {
   }
 }
 
-export function runWithReadTransaction<T>(
-  db: IDBDatabase,
-  stores: Store<any>[],
-  f: (t: TransactionHandle) => Promise<T>,
-): Promise<T> {
-  return runWithTransaction<T>(db, stores, f, "readonly");
-}
-
-export function runWithWriteTransaction<T>(
-  db: IDBDatabase,
-  stores: Store<any>[],
-  f: (t: TransactionHandle) => Promise<T>,
-): Promise<T> {
-  return runWithTransaction<T>(db, stores, f, "readwrite");
-}
-
 function runWithTransaction<T>(
   db: IDBDatabase,
   stores: Store<any>[],
@@ -470,7 +364,203 @@ export class Index<S extends IDBValidKey, T> {
   protected _dummyKey: S | undefined;
 }
 
+/**
+ * Return a promise that resolves
+ * to the taler wallet db.
+ */
+export function openDatabase(
+  idbFactory: IDBFactory,
+  databaseName: string,
+  databaseVersion: number,
+  schema: any,
+  onVersionChange: () => void,
+  onUpgradeUnsupported: (oldVersion: number, newVersion: number) => void,
+): Promise<IDBDatabase> {
+  return new Promise<IDBDatabase>((resolve, reject) => {
+    const req = idbFactory.open(databaseName, databaseVersion);
+    req.onerror = e => {
+      console.log("taler database error", e);
+      reject(new Error("database error"));
+    };
+    req.onsuccess = e => {
+      req.result.onversionchange = (evt: IDBVersionChangeEvent) => {
+        console.log(
+          `handling live db version change from ${evt.oldVersion} to 
${evt.newVersion}`,
+        );
+        req.result.close();
+        onVersionChange();
+      };
+      resolve(req.result);
+    };
+    req.onupgradeneeded = e => {
+      const db = req.result;
+      console.log(
+        `DB: upgrade needed: oldVersion=${e.oldVersion}, 
newVersion=${e.newVersion}`,
+      );
+      switch (e.oldVersion) {
+        case 0: // DB does not exist yet
+          for (const n in schema) {
+            if (schema[n] instanceof Store) {
+              const si: Store<any> = schema[n];
+              const s = db.createObjectStore(si.name, si.storeParams);
+              for (const indexName in si as any) {
+                if ((si as any)[indexName] instanceof Index) {
+                  const ii: Index<any, any> = (si as any)[indexName];
+                  s.createIndex(ii.indexName, ii.keyPath, ii.options);
+                }
+              }
+            }
+          }
+          break;
+        default:
+          if (e.oldVersion !== databaseVersion) {
+            onUpgradeUnsupported(e.oldVersion, databaseVersion);
+            throw Error("incompatible DB");
+          }
+          break;
+      }
+    };
+  });
+}
+
 /**
  * Exception that should be thrown by client code to abort a transaction.
  */
 export const TransactionAbort = Symbol("transaction_abort");
+
+export class Database {
+  constructor(private db: IDBDatabase) {}
+
+  static deleteDatabase(idbFactory: IDBFactory, dbName: string) {
+    idbFactory.deleteDatabase(dbName);
+  }
+
+  async exportDatabase(): Promise<any> {
+    const db = this.db;
+    const dump = {
+      name: db.name,
+      stores: {} as { [s: string]: any },
+      version: db.version,
+    };
+  
+    return new Promise((resolve, reject) => {
+      const tx = db.transaction(Array.from(db.objectStoreNames));
+      tx.addEventListener("complete", () => {
+        resolve(dump);
+      });
+      // tslint:disable-next-line:prefer-for-of
+      for (let i = 0; i < db.objectStoreNames.length; i++) {
+        const name = db.objectStoreNames[i];
+        const storeDump = {} as { [s: string]: any };
+        dump.stores[name] = storeDump;
+        tx.objectStore(name)
+          .openCursor()
+          .addEventListener("success", (e: Event) => {
+            const cursor = (e.target as any).result;
+            if (cursor) {
+              storeDump[cursor.key] = cursor.value;
+              cursor.continue();
+            }
+          });
+      }
+    });
+  }
+
+  importDatabase(dump: any): Promise<void> {
+    const db = this.db;
+    console.log("importing db", dump);
+    return new Promise<void>((resolve, reject) => {
+      const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
+      if (dump.stores) {
+        for (const storeName in dump.stores) {
+          const objects = [];
+          const dumpStore = dump.stores[storeName];
+          for (const key in dumpStore) {
+            objects.push(dumpStore[key]);
+          }
+          console.log(`importing ${objects.length} records into ${storeName}`);
+          const store = tx.objectStore(storeName);
+          for (const obj of objects) {
+            store.put(obj);
+          }
+        }
+      }
+      tx.addEventListener("complete", () => {
+        resolve();
+      });
+    });
+  }
+  
+  async get<T>(store: Store<T>, key: any): Promise<T | undefined> {
+    const tx = this.db.transaction([store.name], "readonly");
+    const req = tx.objectStore(store.name).get(key);
+    const v = await requestToPromise(req);
+    await transactionToPromise(tx);
+    return v;
+  }
+
+  async getIndexed<S extends IDBValidKey, T>(
+    index: Index<S, T>,
+    key: any,
+  ): Promise<T | undefined> {
+    const tx = this.db.transaction([index.storeName], "readonly");
+    const req = tx
+      .objectStore(index.storeName)
+      .index(index.indexName)
+      .get(key);
+    const v = await requestToPromise(req);
+    await transactionToPromise(tx);
+    return v;
+  }
+
+  async put<T>(store: Store<T>, value: T, key?: any): Promise<any> {
+    const tx = this.db.transaction([store.name], "readwrite");
+    const req = tx.objectStore(store.name).put(value, key);
+    const v = await requestToPromise(req);
+    await transactionToPromise(tx);
+    return v;
+  }
+
+  async mutate<T>(
+    store: Store<T>,
+    key: any,
+    f: (x: T) => T | undefined,
+  ): Promise<void> {
+    const tx = this.db.transaction([store.name], "readwrite");
+    const req = tx.objectStore(store.name).openCursor(key);
+    await applyMutation(req, f);
+    await transactionToPromise(tx);
+  }
+
+  iter<T>(store: Store<T>): ResultStream<T> {
+    const tx = this.db.transaction([store.name], "readonly");
+    const req = tx.objectStore(store.name).openCursor();
+    return new ResultStream<T>(req);
+  }
+
+  iterIndex<S extends IDBValidKey, T>(
+    index: Index<S, T>,
+    query?: any,
+  ): ResultStream<T> {
+    const tx = this.db.transaction([index.storeName], "readonly");
+    const req = tx
+      .objectStore(index.storeName)
+      .index(index.indexName)
+      .openCursor(query);
+    return new ResultStream<T>(req);
+  }
+
+  async runWithReadTransaction<T>(
+    stores: Store<any>[],
+    f: (t: TransactionHandle) => Promise<T>,
+  ): Promise<T> {
+    return runWithTransaction<T>(this.db, stores, f, "readonly");
+  }
+
+  async runWithWriteTransaction<T>(
+    stores: Store<any>[],
+    f: (t: TransactionHandle) => Promise<T>,
+  ): Promise<T> {
+    return runWithTransaction<T>(this.db, stores, f, "readwrite");
+  }
+}
diff --git a/src/wallet.ts b/src/wallet.ts
index 1db458b3..e4088fab 100644
--- a/src/wallet.ts
+++ b/src/wallet.ts
@@ -25,11 +25,7 @@
 import { CryptoWorkerFactory } from "./crypto/workers/cryptoApi";
 import { HttpRequestLibrary } from "./util/http";
 import {
-  oneShotPut,
-  oneShotGet,
-  runWithWriteTransaction,
-  oneShotIter,
-  oneShotIterIndex,
+  Database
 } from "./util/query";
 
 import { AmountJson } from "./util/amounts";
@@ -148,12 +144,12 @@ export class Wallet {
   private stopped: boolean = false;
   private memoRunRetryLoop = new AsyncOpMemoSingle<void>();
 
-  get db(): IDBDatabase {
+  get db(): Database {
     return this.ws.db;
   }
 
   constructor(
-    db: IDBDatabase,
+    db: Database,
     http: HttpRequestLibrary,
     cryptoWorkerFactory: CryptoWorkerFactory,
   ) {
@@ -345,8 +341,7 @@ export class Wallet {
    * already been applied.
    */
   async fillDefaults() {
-    await runWithWriteTransaction(
-      this.db,
+    await this.db.runWithWriteTransaction(
       [Stores.config, Stores.currencies],
       async tx => {
         let applied = false;
@@ -381,7 +376,7 @@ export class Wallet {
    */
   async refreshDirtyCoins(): Promise<{ numRefreshed: number }> {
     let n = 0;
-    const coins = await oneShotIter(this.db, Stores.coins).toArray();
+    const coins = await this.db.iter(Stores.coins).toArray();
     for (let coin of coins) {
       if (coin.status == CoinStatus.Dirty) {
         try {
@@ -512,7 +507,7 @@ export class Wallet {
   async findExchange(
     exchangeBaseUrl: string,
   ): Promise<ExchangeRecord | undefined> {
-    return await oneShotGet(this.db, Stores.exchanges, exchangeBaseUrl);
+    return await this.db.get(Stores.exchanges, exchangeBaseUrl);
   }
 
   /**
@@ -540,8 +535,7 @@ export class Wallet {
   }
 
   async getDenoms(exchangeUrl: string): Promise<DenominationRecord[]> {
-    const denoms = await oneShotIterIndex(
-      this.db,
+    const denoms = await this.db.iterIndex(
       Stores.denominations.exchangeBaseUrlIndex,
       exchangeUrl,
     ).toArray();
@@ -549,37 +543,37 @@ export class Wallet {
   }
 
   async getProposal(proposalId: string): Promise<ProposalRecord | undefined> {
-    const proposal = await oneShotGet(this.db, Stores.proposals, proposalId);
+    const proposal = await this.db.get(Stores.proposals, proposalId);
     return proposal;
   }
 
   async getExchanges(): Promise<ExchangeRecord[]> {
-    return await oneShotIter(this.db, Stores.exchanges).toArray();
+    return await this.db.iter(Stores.exchanges).toArray();
   }
 
   async getCurrencies(): Promise<CurrencyRecord[]> {
-    return await oneShotIter(this.db, Stores.currencies).toArray();
+    return await this.db.iter(Stores.currencies).toArray();
   }
 
   async updateCurrency(currencyRecord: CurrencyRecord): Promise<void> {
     logger.trace("updating currency to", currencyRecord);
-    await oneShotPut(this.db, Stores.currencies, currencyRecord);
+    await this.db.put(Stores.currencies, currencyRecord);
   }
 
   async getReserves(exchangeBaseUrl: string): Promise<ReserveRecord[]> {
-    return await oneShotIter(this.db, Stores.reserves).filter(
+    return await this.db.iter(Stores.reserves).filter(
       r => r.exchangeBaseUrl === exchangeBaseUrl,
     );
   }
 
   async getCoinsForExchange(exchangeBaseUrl: string): Promise<CoinRecord[]> {
-    return await oneShotIter(this.db, Stores.coins).filter(
+    return await this.db.iter(Stores.coins).filter(
       c => c.exchangeBaseUrl === exchangeBaseUrl,
     );
   }
 
   async getCoins(): Promise<CoinRecord[]> {
-    return await oneShotIter(this.db, Stores.coins).toArray();
+    return await this.db.iter(Stores.coins).toArray();
   }
 
   async payback(coinPub: string): Promise<void> {
@@ -587,7 +581,7 @@ export class Wallet {
   }
 
   async getPaybackReserves(): Promise<ReserveRecord[]> {
-    return await oneShotIter(this.db, Stores.reserves).filter(
+    return await this.db.iter(Stores.reserves).filter(
       r => r.hasPayback,
     );
   }
@@ -604,7 +598,7 @@ export class Wallet {
   async getSenderWireInfos(): Promise<SenderWireInfos> {
     const m: { [url: string]: Set<string> } = {};
 
-    await oneShotIter(this.db, Stores.exchanges).forEach(x => {
+    await this.db.iter(Stores.exchanges).forEach(x => {
       const wi = x.wireInfo;
       if (!wi) {
         return;
@@ -619,7 +613,7 @@ export class Wallet {
     });
 
     const senderWiresSet: Set<string> = new Set();
-    await oneShotIter(this.db, Stores.senderWires).forEach(x => {
+    await this.db.iter(Stores.senderWires).forEach(x => {
       senderWiresSet.add(x.paytoUri);
     });
 
@@ -649,7 +643,7 @@ export class Wallet {
   async getPurchase(
     contractTermsHash: string,
   ): Promise<PurchaseRecord | undefined> {
-    return oneShotGet(this.db, Stores.purchases, contractTermsHash);
+    return this.db.get(Stores.purchases, contractTermsHash);
   }
 
   async getFullRefundFees(
@@ -683,7 +677,7 @@ export class Wallet {
    * confirmation from the bank.).
    */
   public async handleNotifyReserve() {
-    const reserves = await oneShotIter(this.db, Stores.reserves).toArray();
+    const reserves = await this.db.iter(Stores.reserves).toArray();
     for (const r of reserves) {
       if (r.reserveStatus === ReserveRecordStatus.WAIT_CONFIRM_BANK) {
         try {
@@ -718,7 +712,7 @@ export class Wallet {
   }
 
   async getPurchaseDetails(hc: string): Promise<PurchaseDetails> {
-    const purchase = await oneShotGet(this.db, Stores.purchases, hc);
+    const purchase = await this.db.get(Stores.purchases, hc);
     if (!purchase) {
       throw Error("unknown purchase");
     }
diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts
index 547ec35e..0a44dc19 100644
--- a/src/webex/wxBackend.ts
+++ b/src/webex/wxBackend.ts
@@ -24,7 +24,7 @@
  * Imports.
  */
 import { BrowserCryptoWorkerFactory } from "../crypto/workers/cryptoApi";
-import { deleteDatabase, exportDatabase, importDatabase, openDatabase } from 
"../db";
+import { deleteTalerDatabase, openTalerDatabase } from "../db";
 import { WALLET_DB_VERSION } from "../types/dbTypes";
 import { ConfirmReserveRequest, CreateReserveRequest, ReturnCoinsRequest, 
WalletDiagnostics } from "../types/walletTypes";
 import { AmountJson } from "../util/amounts";
@@ -37,6 +37,7 @@ import { isFirefox } from "./compat";
 import { MessageType } from "./messages";
 import * as wxApi from "./wxApi";
 import MessageSender = chrome.runtime.MessageSender;
+import { Database } from "../util/query";
 
 const NeedsWallet = Symbol("NeedsWallet");
 
@@ -67,25 +68,17 @@ async function handleMessage(
     }
     case "dump-db": {
       const db = needsWallet().db;
-      return exportDatabase(db);
+      return db.exportDatabase()
     }
     case "import-db": {
       const db = needsWallet().db;
-      return importDatabase(db, detail.dump);
+      return db.importDatabase(detail.dump);
     }
     case "ping": {
       return Promise.resolve();
     }
     case "reset-db": {
-      if (currentWallet) {
-        const db = currentWallet.db;
-        const tx = db.transaction(Array.from(db.objectStoreNames), 
"readwrite");
-        // tslint:disable-next-line:prefer-for-of
-        for (let i = 0; i < db.objectStoreNames.length; i++) {
-          tx.objectStore(db.objectStoreNames[i]).clear();
-        }
-      }
-      deleteDatabase(indexedDB);
+      deleteTalerDatabase(indexedDB);
       setBadgeText({ text: "" });
       console.log("reset done");
       if (!currentWallet) {
@@ -417,7 +410,7 @@ async function reinitWallet() {
   setBadgeText({ text: "" });
   const badge = new ChromeBadge();
   try {
-    currentDatabase = await openDatabase(
+    currentDatabase = await openTalerDatabase(
       indexedDB,
       reinitWallet,
       handleUpgradeUnsupported,
@@ -430,7 +423,7 @@ async function reinitWallet() {
   const http = new BrowserHttpLib();
   console.log("setting wallet");
   const wallet = new Wallet(
-    currentDatabase,
+    new Database(currentDatabase),
     http,
     new BrowserCryptoWorkerFactory(),
   );

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]