aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2016-10-18 01:16:31 +0200
committerFlorian Dold <florian.dold@gmail.com>2016-10-18 01:16:31 +0200
commit89c3c2d58d1a7102a10edfb716cbc7434d0bc117 (patch)
treee5a81fbc118a7ae95e92dea2b653d26301c57bc1 /lib
parent78ea2ba5b110bc36d8625a1dd524257906788552 (diff)
downloadwallet-core-89c3c2d58d1a7102a10edfb716cbc7434d0bc117.tar.xz
make db layer more type safe
Diffstat (limited to 'lib')
-rw-r--r--lib/wallet/db.ts2
-rw-r--r--lib/wallet/query.ts40
-rw-r--r--lib/wallet/wallet.ts137
3 files changed, 103 insertions, 76 deletions
diff --git a/lib/wallet/db.ts b/lib/wallet/db.ts
index 9133330a2..a78abc26a 100644
--- a/lib/wallet/db.ts
+++ b/lib/wallet/db.ts
@@ -15,6 +15,7 @@
*/
"use strict";
+import {IExchangeInfo} from "./types";
/**
* Declarations and helpers for
@@ -27,6 +28,7 @@
const DB_NAME = "taler";
const DB_VERSION = 10;
+
/**
* Return a promise that resolves
* to the taler wallet db.
diff --git a/lib/wallet/query.ts b/lib/wallet/query.ts
index fa78fe640..ce0308ed3 100644
--- a/lib/wallet/query.ts
+++ b/lib/wallet/query.ts
@@ -24,6 +24,16 @@
"use strict";
+export class Store<T> {
+ name: string;
+ validator?: (v: T) => T;
+
+ constructor(name: string, validator?: (v: T) => T) {
+ this.name = name;
+ this.validator = validator;
+ }
+}
+
/**
* Stream that can be filtered, reduced or joined
* with indices.
@@ -277,10 +287,10 @@ export class QueryRoot {
this.db = db;
}
- iter<T>(storeName: string,
+ iter<T>(store: Store<T>,
{only = <string | undefined>undefined, indexName = <string | undefined>undefined} = {}): QueryStream<T> {
- this.stores.add(storeName);
- return new IterQueryStream(this, storeName, { only, indexName });
+ this.stores.add(store.name);
+ return new IterQueryStream(this, store.name, { only, indexName });
}
/**
@@ -288,11 +298,11 @@ export class QueryRoot {
* Overrides if an existing object with the same key exists
* in the store.
*/
- put(storeName: string, val: any): QueryRoot {
+ put<T>(store: Store<T>, val: T): QueryRoot {
let doPut = (tx: IDBTransaction) => {
- tx.objectStore(storeName).put(val);
+ tx.objectStore(store.name).put(val);
};
- this.addWork(doPut, storeName, true);
+ this.addWork(doPut, store.name, true);
return this;
}
@@ -302,13 +312,13 @@ export class QueryRoot {
* Fails if the object's key is already present
* in the object store.
*/
- putAll(storeName: string, iterable: any[]): QueryRoot {
+ putAll<T>(store: Store<T>, iterable: T[]): QueryRoot {
const doPutAll = (tx: IDBTransaction) => {
for (let obj of iterable) {
- tx.objectStore(storeName).put(obj);
+ tx.objectStore(store.name).put(obj);
}
};
- this.addWork(doPutAll, storeName, true);
+ this.addWork(doPutAll, store.name, true);
return this;
}
@@ -317,18 +327,18 @@ export class QueryRoot {
* Fails if the object's key is already present
* in the object store.
*/
- add(storeName: string, val: any): QueryRoot {
+ add<T>(store: Store<T>, val: T): QueryRoot {
const doAdd = (tx: IDBTransaction) => {
- tx.objectStore(storeName).add(val);
+ tx.objectStore(store.name).add(val);
};
- this.addWork(doAdd, storeName, true);
+ this.addWork(doAdd, store.name, true);
return this;
}
/**
* Get one object from a store by its key.
*/
- get<T>(storeName: any, key: any): Promise<T|undefined> {
+ get<T>(store: Store<T>, key: any): Promise<T|undefined> {
if (key === void 0) {
throw Error("key must not be undefined");
}
@@ -336,13 +346,13 @@ export class QueryRoot {
const {resolve, promise} = openPromise();
const doGet = (tx: IDBTransaction) => {
- const req = tx.objectStore(storeName).get(key);
+ const req = tx.objectStore(store.name).get(key);
req.onsuccess = () => {
resolve(req.result);
};
};
- this.addWork(doGet, storeName, false);
+ this.addWork(doGet, store.name, false);
return Promise.resolve()
.then(() => this.finish())
.then(() => promise);
diff --git a/lib/wallet/wallet.ts b/lib/wallet/wallet.ts
index 85fd8c6f0..6ab777801 100644
--- a/lib/wallet/wallet.ts
+++ b/lib/wallet/wallet.ts
@@ -30,7 +30,7 @@ import {
WireInfo, RefreshSession, ReserveRecord, CoinPaySig
} from "./types";
import {HttpResponse, RequestException} from "./http";
-import {QueryRoot} from "./query";
+import {QueryRoot, Store} from "./query";
import {Checkable} from "./checkable";
import {canonicalizeBaseUrl} from "./helpers";
import {ReserveCreationInfo, Amounts} from "./types";
@@ -305,6 +305,17 @@ function getWithdrawDenomList(amountAvailable: AmountJson,
}
+namespace Stores {
+ export let exchanges: Store<IExchangeInfo> = new Store<IExchangeInfo>("exchanges");
+ export let transactions: Store<Transaction> = new Store<Transaction>("transactions");
+ export let reserves: Store<ReserveRecord> = new Store<ReserveRecord>("reserves");
+ export let coins: Store<Coin> = new Store<Coin>("coins");
+ export let refresh: Store<RefreshSession> = new Store<RefreshSession>("refresh");
+ export let history: Store<HistoryRecord> = new Store<HistoryRecord>("history");
+ export let precoins: Store<PreCoin> = new Store<PreCoin>("precoins");
+}
+
+
export class Wallet {
private db: IDBDatabase;
private http: HttpRequestLibrary;
@@ -351,7 +362,7 @@ export class Wallet {
console.log("updating exchanges");
this.q()
- .iter("exchanges")
+ .iter(Stores.exchanges)
.reduce((exchange: IExchangeInfo) => {
this.updateExchangeFromUrl(exchange.baseUrl)
.catch((e) => {
@@ -368,28 +379,28 @@ export class Wallet {
console.log("resuming pending operations from db");
this.q()
- .iter("reserves")
- .reduce((reserve: any) => {
+ .iter(Stores.reserves)
+ .reduce((reserve) => {
console.log("resuming reserve", reserve.reserve_pub);
this.processReserve(reserve);
});
this.q()
- .iter("precoins")
- .reduce((preCoin: any) => {
+ .iter(Stores.precoins)
+ .reduce((preCoin) => {
console.log("resuming precoin");
this.processPreCoin(preCoin);
});
this.q()
- .iter("refresh")
+ .iter(Stores.refresh)
.reduce((r: RefreshSession) => {
this.continueRefreshSession(r);
});
// FIXME: optimize via index
this.q()
- .iter("coins")
+ .iter(Stores.coins)
.reduce((c: Coin) => {
if (c.dirty && !c.transactionPending) {
this.refresh(c.coinPub);
@@ -452,7 +463,7 @@ export class Wallet {
console.log("Checking for merchant's exchange", JSON.stringify(info));
return [
this.q()
- .iter("exchanges", {indexName: "pubKey", only: info.master_pub})
+ .iter(Stores.exchanges, {indexName: "pubKey", only: info.master_pub})
.indexJoin("coins",
"exchangeBaseUrl",
(exchange) => exchange.baseUrl)
@@ -536,7 +547,7 @@ export class Wallet {
merchantSig: offer.merchant_sig,
};
- let historyEntry = {
+ let historyEntry: HistoryRecord = {
type: "pay",
timestamp: (new Date).getTime(),
subjectId: `contract-${offer.H_contract}`,
@@ -545,13 +556,14 @@ export class Wallet {
amount: offer.contract.amount,
contractHash: offer.H_contract,
fulfillmentUrl: offer.contract.fulfillment_url,
- }
+ },
+ level: HistoryLevel.User
};
await this.q()
- .put("transactions", t)
- .put("history", historyEntry)
- .putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin))
+ .put(Stores.transactions, t)
+ .put(Stores.history, historyEntry)
+ .putAll(Stores.coins, payCoinInfo.map((pci) => pci.updatedCoin))
.finish();
this.notifier.notify();
@@ -559,7 +571,7 @@ export class Wallet {
async putHistory(historyEntry: HistoryRecord): Promise<void> {
- await this.q().put("history", historyEntry).finish();
+ await this.q().put(Stores.history, historyEntry).finish();
this.notifier.notify();
}
@@ -571,7 +583,7 @@ export class Wallet {
async confirmPay(offer: Offer): Promise<any> {
console.log("executing confirmPay");
- let transaction = await this.q().get("transactions", offer.H_contract);
+ let transaction = await this.q().get(Stores.transactions, offer.H_contract);
if (transaction) {
// Already payed ...
@@ -604,7 +616,7 @@ export class Wallet {
*/
async checkPay(offer: Offer): Promise<any> {
// First check if we already payed for it.
- let transaction = await this.q().get("transactions", offer.H_contract);
+ let transaction = await this.q().get(Stores.transactions, offer.H_contract);
if (transaction) {
return {isPayed: true};
}
@@ -629,7 +641,7 @@ export class Wallet {
* with the given hash.
*/
async executePayment(H_contract: string): Promise<any> {
- let t = await this.q().get<Transaction>("transactions", H_contract);
+ let t = await this.q().get<Transaction>(Stores.transactions, H_contract);
if (!t) {
return {
success: false,
@@ -661,7 +673,7 @@ export class Wallet {
let n = await this.depleteReserve(reserve, exchange);
if (n != 0) {
- let depleted = {
+ let depleted: HistoryRecord = {
type: "depleted-reserve",
subjectId: `reserve-progress-${reserveRecord.reserve_pub}`,
timestamp: (new Date).getTime(),
@@ -670,9 +682,10 @@ export class Wallet {
reservePub: reserveRecord.reserve_pub,
requestedAmount: reserveRecord.requested_amount,
currentAmount: reserveRecord.current_amount,
- }
+ },
+ level: HistoryLevel.User
};
- await this.q().put("history", depleted).finish();
+ await this.q().put(Stores.history, depleted).finish();
}
} catch (e) {
// random, exponential backoff truncated at 3 minutes
@@ -736,8 +749,8 @@ export class Wallet {
};
await this.q()
- .put("reserves", reserveRecord)
- .put("history", historyEntry)
+ .put(Stores.reserves, reserveRecord)
+ .put(Stores.history, historyEntry)
.finish();
let r: CreateReserveResponse = {
@@ -760,14 +773,14 @@ export class Wallet {
async confirmReserve(req: ConfirmReserveRequest): Promise<void> {
const now = (new Date).getTime();
let reserve: ReserveRecord|undefined = await (
- this.q().get<ReserveRecord>("reserves",
+ this.q().get<ReserveRecord>(Stores.reserves,
req.reservePub));
if (!reserve) {
console.error("Unable to confirm reserve, not found in DB");
return;
}
console.log("reserve confirmed");
- const historyEntry = {
+ const historyEntry: HistoryRecord = {
type: "confirm-reserve",
timestamp: now,
subjectId: `reserve-progress-${reserve.reserve_pub}`,
@@ -775,12 +788,13 @@ export class Wallet {
exchangeBaseUrl: reserve.exchange_base_url,
reservePub: req.reservePub,
requestedAmount: reserve.requested_amount,
- }
+ },
+ level: HistoryLevel.User,
};
reserve.confirmed = true;
await this.q()
- .put("reserves", reserve)
- .put("history", historyEntry)
+ .put(Stores.reserves, reserve)
+ .put(Stores.history, historyEntry)
.finish();
this.processReserve(reserve);
@@ -788,7 +802,8 @@ export class Wallet {
private async withdrawExecute(pc: PreCoin): Promise<Coin> {
- let reserve = await this.q().get<ReserveRecord>("reserves", pc.reservePub);
+ let reserve = await this.q().get<ReserveRecord>(Stores.reserves,
+ pc.reservePub);
if (!reserve) {
throw Error("db inconsistent");
@@ -837,8 +852,8 @@ export class Wallet {
};
await this.q()
.delete("precoins", coin.coinPub)
- .add("coins", coin)
- .add("history", historyEntry)
+ .add(Stores.coins, coin)
+ .add(Stores.history, historyEntry)
.finish();
this.notifier.notify();
}
@@ -853,7 +868,7 @@ export class Wallet {
let preCoin = await this.cryptoApi
.createPreCoin(denom, reserve);
await this.q()
- .put("precoins", preCoin)
+ .put(Stores.precoins, preCoin)
.finish();
await this.processPreCoin(preCoin);
}
@@ -881,7 +896,7 @@ export class Wallet {
private async updateReserve(reservePub: string,
exchange: IExchangeInfo): Promise<ReserveRecord> {
let reserve = await this.q()
- .get<ReserveRecord>("reserves", reservePub);
+ .get<ReserveRecord>(Stores.reserves, reservePub);
if (!reserve) {
throw Error("reserve not in db");
}
@@ -910,7 +925,7 @@ export class Wallet {
}
};
await this.q()
- .put("reserves", reserve)
+ .put(Stores.reserves, reserve)
.finish();
return reserve;
}
@@ -982,7 +997,7 @@ export class Wallet {
private async suspendCoins(exchangeInfo: IExchangeInfo): Promise<void> {
let suspendedCoins = await (
this.q()
- .iter("coins",
+ .iter(Stores.coins,
{indexName: "exchangeBaseUrl", only: exchangeInfo.baseUrl})
.reduce((coin: Coin, suspendedCoins: Coin[]) => {
if (!exchangeInfo.active_denoms.find((c) => c.denom_pub == coin.denomPub)) {
@@ -995,7 +1010,7 @@ export class Wallet {
suspendedCoins.map((c) => {
console.log("suspending coin", c);
c.suspended = true;
- q.put("coins", c);
+ q.put(Stores.coins, c);
});
await q.finish();
}
@@ -1008,7 +1023,7 @@ export class Wallet {
throw Error("invalid update time");
}
- let r = await this.q().get<IExchangeInfo>("exchanges", baseUrl);
+ let r = await this.q().get<IExchangeInfo>(Stores.exchanges, baseUrl);
let exchangeInfo: IExchangeInfo;
@@ -1035,7 +1050,7 @@ export class Wallet {
await this.suspendCoins(updatedExchangeInfo);
await this.q()
- .put("exchanges", updatedExchangeInfo)
+ .put(Stores.exchanges, updatedExchangeInfo)
.finish();
return updatedExchangeInfo;
@@ -1120,7 +1135,7 @@ export class Wallet {
let byCurrency = await (
this.q()
- .iter("coins")
+ .iter(Stores.coins)
.reduce(collectBalances, {}));
return {balances: byCurrency};
@@ -1131,13 +1146,13 @@ export class Wallet {
// FIXME: this is not running in a transaction.
- let coin = await this.q().get<Coin>("coins", oldCoinPub);
+ let coin = await this.q().get<Coin>(Stores.coins, oldCoinPub);
if (!coin) {
throw Error("coin not found");
}
- let exchange = await this.q().get<IExchangeInfo>("exchanges",
+ let exchange = await this.q().get<IExchangeInfo>(Stores.exchanges,
coin.exchangeBaseUrl);
if (!exchange) {
throw Error("db inconsistent");
@@ -1177,8 +1192,8 @@ export class Wallet {
// FIXME: we should check whether the amount still matches!
await this.q()
- .put("refresh", refreshSession)
- .put("coins", coin)
+ .put(Stores.refresh, refreshSession)
+ .put(Stores.coins, coin)
.finish();
return refreshSession;
@@ -1187,12 +1202,12 @@ export class Wallet {
async refresh(oldCoinPub: string): Promise<void> {
let refreshSession: RefreshSession|undefined;
- let oldSession = await this.q().get<RefreshSession>("refresh", oldCoinPub);
+ let oldSession = await this.q().get(Stores.refresh, oldCoinPub);
if (oldSession) {
refreshSession = oldSession;
} else {
- refreshSession = await this.q().get<RefreshSession>("refresh",
- oldCoinPub);
+ refreshSession = await this.q().get(Stores.refresh,
+ oldCoinPub);
}
if (!refreshSession) {
// refreshing not necessary
@@ -1208,7 +1223,7 @@ export class Wallet {
if (typeof refreshSession.norevealIndex !== "number") {
let coinPub = refreshSession.meltCoinPub;
await this.refreshMelt(refreshSession);
- let r = await this.q().get<RefreshSession>("refresh", coinPub);
+ let r = await this.q().get<RefreshSession>(Stores.refresh, coinPub);
if (!r) {
throw Error("refresh session does not exist anymore");
}
@@ -1225,7 +1240,7 @@ export class Wallet {
return;
}
- let coin = await this.q().get<Coin>("coins", refreshSession.meltCoinPub);
+ let coin = await this.q().get<Coin>(Stores.coins, refreshSession.meltCoinPub);
if (!coin) {
console.error("can't melt coin, it does not exist");
return;
@@ -1271,7 +1286,7 @@ export class Wallet {
refreshSession.norevealIndex = norevealIndex;
- await this.q().put("refresh", refreshSession).finish();
+ await this.q().put(Stores.refresh, refreshSession).finish();
}
@@ -1307,7 +1322,7 @@ export class Wallet {
console.log("/refresh/reveal did not contain ev_sigs");
}
- let exchange = await this.q().get<IExchangeInfo>("exchanges",
+ let exchange = await this.q().get<IExchangeInfo>(Stores.exchanges,
refreshSession.exchangeBaseUrl);
if (!exchange) {
console.error(`exchange ${refreshSession.exchangeBaseUrl} not found`);
@@ -1343,8 +1358,8 @@ export class Wallet {
refreshSession.finished = true;
await this.q()
- .putAll("coins", coins)
- .put("refresh", refreshSession)
+ .putAll(Stores.coins, coins)
+ .put(Stores.refresh, refreshSession)
.finish();
}
@@ -1360,7 +1375,7 @@ export class Wallet {
let history = await (
this.q()
- .iter("history", {indexName: "timestamp"})
+ .iter(Stores.history, {indexName: "timestamp"})
.reduce(collect, []));
return {history};
@@ -1368,28 +1383,28 @@ export class Wallet {
async getExchanges(): Promise<IExchangeInfo[]> {
return this.q()
- .iter<IExchangeInfo>("exchanges")
+ .iter<IExchangeInfo>(Stores.exchanges)
.flatMap((e) => [e])
.toArray();
}
async getReserves(exchangeBaseUrl: string): Promise<ReserveRecord[]> {
return this.q()
- .iter<ReserveRecord>("reserves")
+ .iter<ReserveRecord>(Stores.reserves)
.filter((r: ReserveRecord) => r.exchange_base_url === exchangeBaseUrl)
.toArray();
}
async getCoins(exchangeBaseUrl: string): Promise<Coin[]> {
return this.q()
- .iter<Coin>("coins")
+ .iter<Coin>(Stores.coins)
.filter((c: Coin) => c.exchangeBaseUrl === exchangeBaseUrl)
.toArray();
}
async getPreCoins(exchangeBaseUrl: string): Promise<PreCoin[]> {
return this.q()
- .iter<PreCoin>("precoins")
+ .iter<PreCoin>(Stores.precoins)
.filter((c: PreCoin) => c.exchangeBaseUrl === exchangeBaseUrl)
.toArray();
}
@@ -1431,19 +1446,19 @@ export class Wallet {
async paymentSucceeded(contractHash: string): Promise<any> {
const doPaymentSucceeded = async() => {
- let t = await this.q().get<Transaction>("transactions", contractHash);
+ let t = await this.q().get<Transaction>(Stores.transactions, contractHash);
if (!t) {
console.error("contract not found");
return;
}
for (let pc of t.payReq.coins) {
- let c = await this.q().get<Coin>("coins", pc.coin_pub);
+ let c = await this.q().get<Coin>(Stores.coins, pc.coin_pub);
if (!c) {
console.error("coin not found");
return;
}
c.transactionPending = false;
- await this.q().put("coins", c).finish();
+ await this.q().put(Stores.coins, c).finish();
}
for (let c of t.payReq.coins) {
this.refresh(c.coin_pub);
@@ -1452,4 +1467,4 @@ export class Wallet {
doPaymentSucceeded();
return;
}
-} \ No newline at end of file
+}