diff options
author | Florian Dold <florian@dold.me> | 2020-11-26 22:14:46 +0100 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2020-11-26 22:14:46 +0100 |
commit | 4e481a51c64084db21d3eea513b13a7a3bd6603a (patch) | |
tree | de77c119522f3cd277bf9a4817c7e6f51f6ea4b2 /packages | |
parent | 2b19594e7adfc4ae75970db5c0881243efcac4df (diff) |
more static typing for transactions (fixes #6653)
Diffstat (limited to 'packages')
-rw-r--r-- | packages/taler-wallet-core/src/operations/tip.ts | 2 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/types/dbTypes.ts | 130 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/util/query.ts | 127 |
3 files changed, 164 insertions, 95 deletions
diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index cbf61a86c..bf565b9b2 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -285,7 +285,7 @@ async function processTipImpl( ); if (!isValid) { - await ws.db.runWithWriteTransaction([Stores.planchets], async (tx) => { + await ws.db.runWithWriteTransaction([Stores.tips], async (tx) => { const tipRecord = await tx.get(Stores.tips, walletTipId); if (!tipRecord) { return; diff --git a/packages/taler-wallet-core/src/types/dbTypes.ts b/packages/taler-wallet-core/src/types/dbTypes.ts index ed3a18ae1..349713ebc 100644 --- a/packages/taler-wallet-core/src/types/dbTypes.ts +++ b/packages/taler-wallet-core/src/types/dbTypes.ts @@ -785,7 +785,7 @@ export interface CoinRecord { /** * Blinding key used when withdrawing the coin. - * Potentionally sed again during payback. + * Potentionally used again during payback. */ blindingKey: string; @@ -1531,135 +1531,160 @@ export enum ImportPayloadType { CoreSchema = "core-schema", } -class ExchangesStore extends Store<ExchangeRecord> { +class ExchangesStore extends Store<"exchanges", ExchangeRecord> { constructor() { super("exchanges", { keyPath: "baseUrl" }); } } -class CoinsStore extends Store<CoinRecord> { +class CoinsStore extends Store<"coins", CoinRecord> { constructor() { super("coins", { keyPath: "coinPub" }); } - exchangeBaseUrlIndex = new Index<string, CoinRecord>( - this, - "exchangeBaseUrl", + exchangeBaseUrlIndex = new Index< + "coins", "exchangeBaseUrl", - ); - denomPubHashIndex = new Index<string, CoinRecord>( - this, + string, + CoinRecord + >(this, "exchangeBaseUrl", "exchangeBaseUrl"); + + denomPubHashIndex = new Index< + "coins", "denomPubHashIndex", - "denomPubHash", - ); + string, + CoinRecord + >(this, "denomPubHashIndex", "denomPubHash"); } -class ProposalsStore extends Store<ProposalRecord> { +class ProposalsStore extends Store<"proposals", ProposalRecord> { constructor() { super("proposals", { keyPath: "proposalId" }); } - urlAndOrderIdIndex = new Index<string, ProposalRecord>(this, "urlIndex", [ - "merchantBaseUrl", - "orderId", - ]); + urlAndOrderIdIndex = new Index< + "proposals", + "urlIndex", + string, + ProposalRecord + >(this, "urlIndex", ["merchantBaseUrl", "orderId"]); } -class PurchasesStore extends Store<PurchaseRecord> { +class PurchasesStore extends Store<"purchases", PurchaseRecord> { constructor() { super("purchases", { keyPath: "proposalId" }); } - fulfillmentUrlIndex = new Index<string, PurchaseRecord>( - this, + fulfillmentUrlIndex = new Index< + "purchases", "fulfillmentUrlIndex", - "contractData.fulfillmentUrl", + string, + PurchaseRecord + >(this, "fulfillmentUrlIndex", "contractData.fulfillmentUrl"); + + orderIdIndex = new Index<"purchases", "orderIdIndex", string, PurchaseRecord>( + this, + "orderIdIndex", + ["contractData.merchantBaseUrl", "contractData.orderId"], ); - orderIdIndex = new Index<string, PurchaseRecord>(this, "orderIdIndex", [ - "contractData.merchantBaseUrl", - "contractData.orderId", - ]); } -class DenominationsStore extends Store<DenominationRecord> { +class DenominationsStore extends Store<"denominations", DenominationRecord> { constructor() { // cast needed because of bug in type annotations super("denominations", { keyPath: (["exchangeBaseUrl", "denomPubHash"] as any) as IDBKeyPath, }); } - exchangeBaseUrlIndex = new Index<string, DenominationRecord>( - this, + exchangeBaseUrlIndex = new Index< + "denominations", "exchangeBaseUrlIndex", - "exchangeBaseUrl", - ); + string, + DenominationRecord + >(this, "exchangeBaseUrlIndex", "exchangeBaseUrl"); } -class CurrenciesStore extends Store<CurrencyRecord> { +class CurrenciesStore extends Store<"currencies", CurrencyRecord> { constructor() { super("currencies", { keyPath: "name" }); } } -class ConfigStore extends Store<ConfigRecord> { +class ConfigStore extends Store<"config", ConfigRecord> { constructor() { super("config", { keyPath: "key" }); } } -class ReservesStore extends Store<ReserveRecord> { +class ReservesStore extends Store<"reserves", ReserveRecord> { constructor() { super("reserves", { keyPath: "reservePub" }); } } -class ReserveHistoryStore extends Store<ReserveHistoryRecord> { +class ReserveHistoryStore extends Store< + "reserveHistory", + ReserveHistoryRecord +> { constructor() { super("reserveHistory", { keyPath: "reservePub" }); } } -class TipsStore extends Store<TipRecord> { +class TipsStore extends Store<"tips", TipRecord> { constructor() { super("tips", { keyPath: "walletTipId" }); } // Added in version 2 - byMerchantTipIdAndBaseUrl = new Index<[string, string], TipRecord>( + byMerchantTipIdAndBaseUrl = new Index< + "tips", + "tipsByMerchantTipIdAndOriginIndex", + [string, string], + TipRecord + >( this, "tipsByMerchantTipIdAndOriginIndex", ["merchantTipId", "merchantBaseUrl"], { versionAdded: 2, - } + }, ); } -class WithdrawalGroupsStore extends Store<WithdrawalGroupRecord> { +class WithdrawalGroupsStore extends Store< + "withdrawals", + WithdrawalGroupRecord +> { constructor() { super("withdrawals", { keyPath: "withdrawalGroupId" }); } } -class PlanchetsStore extends Store<PlanchetRecord> { +class PlanchetsStore extends Store<"planchets", PlanchetRecord> { constructor() { super("planchets", { keyPath: "coinPub" }); } - byGroupAndIndex = new Index<string, PlanchetRecord>( - this, + byGroupAndIndex = new Index< + "planchets", "withdrawalGroupAndCoinIdxIndex", - ["withdrawalGroupId", "coinIdx"], - ); - byGroup = new Index<string, PlanchetRecord>( - this, + string, + PlanchetRecord + >(this, "withdrawalGroupAndCoinIdxIndex", ["withdrawalGroupId", "coinIdx"]); + byGroup = new Index< + "planchets", "withdrawalGroupIndex", - "withdrawalGroupId", - ); + string, + PlanchetRecord + >(this, "withdrawalGroupIndex", "withdrawalGroupId"); } /** * This store is effectively a materialized index for * reserve records that are for a bank-integrated withdrawal. */ -class BankWithdrawUrisStore extends Store<BankWithdrawUriRecord> { +class BankWithdrawUrisStore extends Store< + "bankWithdrawUris", + BankWithdrawUriRecord +> { constructor() { super("bankWithdrawUris", { keyPath: "talerWithdrawUri" }); } @@ -1675,10 +1700,13 @@ export const Stores = { denominations: new DenominationsStore(), exchanges: new ExchangesStore(), proposals: new ProposalsStore(), - refreshGroups: new Store<RefreshGroupRecord>("refreshGroups", { - keyPath: "refreshGroupId", - }), - recoupGroups: new Store<RecoupGroupRecord>("recoupGroups", { + refreshGroups: new Store<"refreshGroups", RefreshGroupRecord>( + "refreshGroups", + { + keyPath: "refreshGroupId", + }, + ), + recoupGroups: new Store<"recoupGroups", RecoupGroupRecord>("recoupGroups", { keyPath: "recoupGroupId", }), reserves: new ReservesStore(), diff --git a/packages/taler-wallet-core/src/util/query.ts b/packages/taler-wallet-core/src/util/query.ts index f533c4cfd..fa0d8beb7 100644 --- a/packages/taler-wallet-core/src/util/query.ts +++ b/packages/taler-wallet-core/src/util/query.ts @@ -59,11 +59,8 @@ export interface StoreParams<T> { /** * Definition of an object store. */ -export class Store<T> { - constructor( - public name: string, - public storeParams?: StoreParams<T>, - ) {} +export class Store<N extends string, T> { + constructor(public name: N, public storeParams?: StoreParams<T>) {} } /** @@ -273,26 +270,48 @@ class ResultStream<T> { } } -export class TransactionHandle { +type StrKey<T> = string & keyof T; + +type StoreName<S> = S extends Store<infer N, any> ? N : never; +type StoreContent<S> = S extends Store<any, infer R> ? R : never; +type IndexRecord<Ind> = Ind extends Index<any, any, any, infer R> ? R : never; + +export class TransactionHandle<StoreTypes extends Store<string, {}>> { constructor(private tx: IDBTransaction) {} - put<T>(store: Store<T>, value: T, key?: any): Promise<any> { + put<S extends StoreTypes>( + store: S, + value: StoreContent<S>, + key?: any, + ): Promise<any> { const req = this.tx.objectStore(store.name).put(value, key); return requestToPromise(req); } - add<T>(store: Store<T>, value: T, key?: any): Promise<any> { + add<S extends StoreTypes>( + store: S, + value: StoreContent<S>, + key?: any, + ): Promise<any> { const req = this.tx.objectStore(store.name).add(value, key); return requestToPromise(req); } - get<T>(store: Store<T>, key: any): Promise<T | undefined> { + get<S extends StoreTypes>( + store: S, + key: any, + ): Promise<StoreContent<S> | undefined> { const req = this.tx.objectStore(store.name).get(key); return requestToPromise(req); } - getIndexed<S extends IDBValidKey, T>( - index: Index<S, T>, + getIndexed< + StoreName extends StrKey<StoreTypes>, + IndexName extends string, + S extends IDBValidKey, + T + >( + index: Index<StoreName, IndexName, S, T>, key: any, ): Promise<T | undefined> { const req = this.tx @@ -302,15 +321,20 @@ export class TransactionHandle { return requestToPromise(req); } - iter<T>(store: Store<T>, key?: any): ResultStream<T> { + iter<N extends StrKey<StoreTypes>, T extends StoreTypes[N]>( + store: Store<N, T>, + key?: any, + ): ResultStream<T> { const req = this.tx.objectStore(store.name).openCursor(key); return new ResultStream<T>(req); } - iterIndexed<S extends IDBValidKey, T>( - index: Index<S, T>, - key?: any, - ): ResultStream<T> { + iterIndexed< + StoreName extends StrKey<StoreTypes>, + IndexName extends string, + S extends IDBValidKey, + T + >(index: Index<StoreName, IndexName, S, T>, key?: any): ResultStream<T> { const req = this.tx .objectStore(index.storeName) .index(index.indexName) @@ -318,13 +342,16 @@ export class TransactionHandle { return new ResultStream<T>(req); } - delete<T>(store: Store<T>, key: any): Promise<void> { + delete<N extends StrKey<StoreTypes>, T extends StoreTypes[N]>( + store: Store<N, T>, + key: any, + ): Promise<void> { const req = this.tx.objectStore(store.name).delete(key); return requestToPromise(req); } - mutate<T>( - store: Store<T>, + mutate<N extends StrKey<StoreTypes>, T extends StoreTypes[N]>( + store: Store<N, T>, key: any, f: (x: T) => T | undefined, ): Promise<void> { @@ -333,10 +360,10 @@ export class TransactionHandle { } } -function runWithTransaction<T>( +function runWithTransaction<T, StoreTypes extends Store<string, {}>>( db: IDBDatabase, - stores: Store<any>[], - f: (t: TransactionHandle) => Promise<T>, + stores: StoreTypes[], + f: (t: TransactionHandle<StoreTypes>) => Promise<T>, mode: "readonly" | "readwrite", ): Promise<T> { const stack = Error("Failed transaction was started here."); @@ -397,7 +424,12 @@ function runWithTransaction<T>( /** * Definition of an index. */ -export class Index<S extends IDBValidKey, T> { +export class Index< + StoreName extends string, + IndexName extends string, + S extends IDBValidKey, + T +> { /** * Name of the store that this index is associated with. */ @@ -409,8 +441,8 @@ export class Index<S extends IDBValidKey, T> { options: IndexOptions; constructor( - s: Store<T>, - public indexName: string, + s: Store<StoreName, T>, + public indexName: IndexName, public keyPath: string | string[], options?: IndexOptions, ) { @@ -539,7 +571,10 @@ export class Database { }); } - async get<T>(store: Store<T>, key: any): Promise<T | undefined> { + async get<N extends string, T>( + store: Store<N, 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); @@ -547,10 +582,12 @@ export class Database { return v; } - async getIndexed<S extends IDBValidKey, T>( - index: Index<S, T>, + async getIndexed<Ind extends Index<string, string, any, any>>( + index: Ind extends Index<infer IndN, infer StN, any, infer R> + ? Index<IndN, StN, any, R> + : never, key: any, - ): Promise<T | undefined> { + ): Promise<IndexRecord<Ind> | 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); @@ -558,7 +595,11 @@ export class Database { return v; } - async put<T>(store: Store<T>, value: T, key?: any): Promise<any> { + async put<St extends Store<string, any>>( + store: St extends Store<infer N, infer R> ? Store<N, R> : never, + value: St extends Store<any, infer R> ? R : never, + 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); @@ -566,8 +607,8 @@ export class Database { return v; } - async mutate<T>( - store: Store<T>, + async mutate<N extends string, T>( + store: Store<N, T>, key: any, f: (x: T) => T | undefined, ): Promise<void> { @@ -577,14 +618,14 @@ export class Database { await transactionToPromise(tx); } - iter<T>(store: Store<T>): ResultStream<T> { + iter<N extends string, T>(store: Store<N, 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>, + iterIndex<N extends string, I extends string, S extends IDBValidKey, T>( + index: Index<N, I, S, T>, query?: any, ): ResultStream<T> { const tx = this.db.transaction([index.storeName], "readonly"); @@ -595,17 +636,17 @@ export class Database { return new ResultStream<T>(req); } - async runWithReadTransaction<T>( - stores: Store<any>[], - f: (t: TransactionHandle) => Promise<T>, + async runWithReadTransaction<T, StoreTypes extends Store<string, any>>( + stores: StoreTypes[], + f: (t: TransactionHandle<StoreTypes>) => Promise<T>, ): Promise<T> { - return runWithTransaction<T>(this.db, stores, f, "readonly"); + return runWithTransaction<T, StoreTypes>(this.db, stores, f, "readonly"); } - async runWithWriteTransaction<T>( - stores: Store<any>[], - f: (t: TransactionHandle) => Promise<T>, + async runWithWriteTransaction<T, StoreTypes extends Store<string, any>>( + stores: StoreTypes[], + f: (t: TransactionHandle<StoreTypes>) => Promise<T>, ): Promise<T> { - return runWithTransaction<T>(this.db, stores, f, "readwrite"); + return runWithTransaction<T, StoreTypes>(this.db, stores, f, "readwrite"); } } |