diff options
author | Florian Dold <florian@dold.me> | 2024-02-19 12:49:17 +0100 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2024-02-19 17:47:35 +0100 |
commit | 1ec521b9d214b286e747b3ccb3113730ac3a2509 (patch) | |
tree | 2f3d2b2906810dca45859b8cbfb8d18d53b27e80 /packages/taler-wallet-core/src/util | |
parent | 1034ecb5f20bd8c75e37e0b4b454ea6c1f4c1da6 (diff) | |
download | wallet-core-1ec521b9d214b286e747b3ccb3113730ac3a2509.tar.xz |
wallet-core: simplify/unify DB access
Diffstat (limited to 'packages/taler-wallet-core/src/util')
3 files changed, 75 insertions, 227 deletions
diff --git a/packages/taler-wallet-core/src/util/coinSelection.ts b/packages/taler-wallet-core/src/util/coinSelection.ts index be868867d..0f6316bce 100644 --- a/packages/taler-wallet-core/src/util/coinSelection.ts +++ b/packages/taler-wallet-core/src/util/coinSelection.ts @@ -58,7 +58,7 @@ import { DenominationRecord } from "../db.js"; import { getExchangeWireDetailsInTx, isWithdrawableDenom, - WalletDbReadOnlyTransactionArr, + WalletDbReadOnlyTransaction, } from "../index.js"; import { InternalWalletState } from "../internal-wallet-state.js"; import { @@ -359,33 +359,31 @@ export async function selectPayCoinsNew( logger.trace(`coin selection request ${j2s(req)}`); logger.trace(`selected coins (via denoms) for payment: ${j2s(finalSel)}`); - await ws.db - .mktx((x) => [x.coins, x.denominations]) - .runReadOnly(async (tx) => { - for (const dph of Object.keys(finalSel)) { - const selInfo = finalSel[dph]; - const numRequested = selInfo.contributions.length; - const query = [ - selInfo.exchangeBaseUrl, - selInfo.denomPubHash, - selInfo.maxAge, - CoinStatus.Fresh, - ]; - logger.trace(`query: ${j2s(query)}`); - const coins = - await tx.coins.indexes.byExchangeDenomPubHashAndAgeAndStatus.getAll( - query, - numRequested, - ); - if (coins.length != numRequested) { - throw Error( - `coin selection failed (not available anymore, got only ${coins.length}/${numRequested})`, - ); - } - coinPubs.push(...coins.map((x) => x.coinPub)); - coinContributions.push(...selInfo.contributions); + await ws.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { + for (const dph of Object.keys(finalSel)) { + const selInfo = finalSel[dph]; + const numRequested = selInfo.contributions.length; + const query = [ + selInfo.exchangeBaseUrl, + selInfo.denomPubHash, + selInfo.maxAge, + CoinStatus.Fresh, + ]; + logger.trace(`query: ${j2s(query)}`); + const coins = + await tx.coins.indexes.byExchangeDenomPubHashAndAgeAndStatus.getAll( + query, + numRequested, + ); + if (coins.length != numRequested) { + throw Error( + `coin selection failed (not available anymore, got only ${coins.length}/${numRequested})`, + ); } - }); + coinPubs.push(...coins.map((x) => x.coinPub)); + coinContributions.push(...selInfo.contributions); + } + }); return { type: "success", @@ -911,7 +909,7 @@ export interface PeerCoinSelectionRequest { */ async function selectPayPeerCandidatesForExchange( ws: InternalWalletState, - tx: WalletDbReadOnlyTransactionArr<["coinAvailability", "denominations"]>, + tx: WalletDbReadOnlyTransaction<["coinAvailability", "denominations"]>, exchangeBaseUrl: string, ): Promise<AvailableDenom[]> { const denoms: AvailableDenom[] = []; @@ -1048,17 +1046,17 @@ export async function selectPeerCoins( // one coin to spend. throw new Error("amount of zero not allowed"); } - return await ws.db - .mktx((x) => [ - x.exchanges, - x.contractTerms, - x.coins, - x.coinAvailability, - x.denominations, - x.refreshGroups, - x.peerPushDebit, - ]) - .runReadWrite(async (tx) => { + return await ws.db.runReadWriteTx( + [ + "exchanges", + "contractTerms", + "coins", + "coinAvailability", + "denominations", + "refreshGroups", + "peerPushDebit", + ], + async (tx) => { const exchanges = await tx.exchanges.iter().toArray(); const exchangeFeeGap: { [url: string]: AmountJson } = {}; const currency = Amounts.currencyOf(instructedAmount); @@ -1232,5 +1230,6 @@ export async function selectPeerCoins( }; return { type: "failure", insufficientBalanceDetails: errDetails }; - }); + }, + ); } diff --git a/packages/taler-wallet-core/src/util/instructedAmountConversion.ts b/packages/taler-wallet-core/src/util/instructedAmountConversion.ts index caa3fdca5..c4a2f2d5c 100644 --- a/packages/taler-wallet-core/src/util/instructedAmountConversion.ts +++ b/packages/taler-wallet-core/src/util/instructedAmountConversion.ts @@ -139,14 +139,9 @@ async function getAvailableDenoms( ): Promise<AvailableCoins> { const operationType = getOperationType(TransactionType.Deposit); - return await ws.db - .mktx((x) => [ - x.exchanges, - x.exchangeDetails, - x.denominations, - x.coinAvailability, - ]) - .runReadOnly(async (tx) => { + return await ws.db.runReadOnlyTx( + ["exchanges", "exchangeDetails", "denominations", "coinAvailability"], + async (tx) => { const list: CoinInfo[] = []; const exchanges: Record<string, ExchangeInfo> = {}; @@ -304,7 +299,8 @@ async function getAvailableDenoms( } return { list, exchanges }; - }); + }, + ); } function buildCoinInfoFromDenom( diff --git a/packages/taler-wallet-core/src/util/query.ts b/packages/taler-wallet-core/src/util/query.ts index 19fa0dbfd..90a3cac70 100644 --- a/packages/taler-wallet-core/src/util/query.ts +++ b/packages/taler-wallet-core/src/util/query.ts @@ -490,78 +490,14 @@ type ValidateKeyPath<T, P> = P extends `${infer PX extends keyof T & // foo({x: [0,1,2]}, "x.0"); -export type GetReadOnlyAccess<BoundStores> = { - [P in keyof BoundStores]: BoundStores[P] extends StoreWithIndexes< - infer StoreName, - infer RecordType, - infer IndexMap - > - ? StoreReadOnlyAccessor<RecordType, IndexMap> - : unknown; -}; - export type StoreNames<StoreMap> = StoreMap extends { [P in keyof StoreMap]: StoreWithIndexes<infer SN1, infer SD1, infer IM1>; } ? keyof StoreMap : unknown; -export type DbReadOnlyTransaction< - StoreMap, - Stores extends StoreNames<StoreMap> & string, -> = StoreMap extends { - [P in Stores]: StoreWithIndexes<infer SN1, infer SD1, infer IM1>; -} - ? { - [P in Stores]: StoreMap[P] extends StoreWithIndexes< - infer StoreName, - infer RecordType, - infer IndexMap - > - ? StoreReadOnlyAccessor<RecordType, IndexMap> - : unknown; - } - : unknown; - export type DbReadWriteTransaction< StoreMap, - Stores extends StoreNames<StoreMap> & string, -> = StoreMap extends { - [P in Stores]: StoreWithIndexes<infer SN1, infer SD1, infer IM1>; -} - ? { - [P in Stores]: StoreMap[P] extends StoreWithIndexes< - infer StoreName, - infer RecordType, - infer IndexMap - > - ? StoreReadWriteAccessor<RecordType, IndexMap> - : unknown; - } - : unknown; - -export type GetReadWriteAccess<BoundStores> = { - [P in keyof BoundStores]: BoundStores[P] extends StoreWithIndexes< - infer StoreName, - infer RecordType, - infer IndexMap - > - ? StoreReadWriteAccessor<RecordType, IndexMap> - : unknown; -}; - -type ReadOnlyTransactionFunction<BoundStores, T> = ( - t: GetReadOnlyAccess<BoundStores>, - rawTx: IDBTransaction, -) => Promise<T>; - -type ReadWriteTransactionFunction<BoundStores, T> = ( - t: GetReadWriteAccess<BoundStores>, - rawTx: IDBTransaction, -) => Promise<T>; - -export type DbReadWriteTransactionArr< - StoreMap, StoresArr extends Array<StoreNames<StoreMap>>, > = StoreMap extends { [P in string]: StoreWithIndexes<infer _SN1, infer _SD1, infer _IM1>; @@ -578,7 +514,7 @@ export type DbReadWriteTransactionArr< } : never; -export type DbReadOnlyTransactionArr< +export type DbReadOnlyTransaction< StoreMap, StoresArr extends Array<StoreNames<StoreMap>>, > = StoreMap extends { @@ -596,11 +532,6 @@ export type DbReadOnlyTransactionArr< } : never; -export interface DbTransactionContext<BoundStores> { - runReadWrite<T>(f: ReadWriteTransactionFunction<BoundStores, T>): Promise<T>; - runReadOnly<T>(f: ReadOnlyTransactionFunction<BoundStores, T>): Promise<T>; -} - /** * Convert the type of an array to a union of the contents. * @@ -811,12 +742,6 @@ function makeWriteContext( return ctx; } -type StoreNamesOf<X> = X extends { [x: number]: infer F } - ? F extends { storeName: infer I } - ? I - : never - : never; - /** * Type-safe access to a database with a particular store map. * @@ -832,65 +757,46 @@ export class DbAccess<StoreMap> { return this.db; } - /** - * Run a transaction with all object stores. - */ - mktxAll(): DbTransactionContext<StoreMap> { - const storeNames: string[] = []; + runAllStoresReadWriteTx<T>( + txf: ( + tx: DbReadWriteTransaction<StoreMap, Array<StoreNames<StoreMap>>>, + ) => Promise<T>, + ): Promise<T> { const accessibleStores: { [x: string]: StoreWithIndexes<any, any, any> } = {}; - for (let i = 0; i < this.db.objectStoreNames.length; i++) { - const sn = this.db.objectStoreNames[i]; + const strStoreNames: string[] = []; + for (const sn of Object.keys(this.stores as any)) { const swi = (this.stores as any)[sn] as StoreWithIndexes<any, any, any>; - if (!swi) { - logger.warn(`store metadata not available (${sn})`); - continue; - } - storeNames.push(sn); - accessibleStores[sn] = swi; + strStoreNames.push(swi.storeName); + accessibleStores[swi.storeName] = swi; } + const tx = this.db.transaction(strStoreNames, "readwrite"); + const writeContext = makeWriteContext(tx, accessibleStores); + return runTx(tx, writeContext, txf); + } - const storeMapKeys = Object.keys(this.stores as any); - for (const storeMapKey of storeMapKeys) { - const swi = (this.stores as any)[storeMapKey] as StoreWithIndexes< - any, - any, - any - >; - if (!accessibleStores[swi.storeName]) { - const version = this.db.version; - throw Error( - `store '${swi.storeName}' required by schema but not in database (minver=${version})`, - ); - } + runAllStoresReadOnlyTx<T>( + txf: ( + tx: DbReadOnlyTransaction<StoreMap, Array<StoreNames<StoreMap>>>, + ) => Promise<T>, + ): Promise<T> { + const accessibleStores: { [x: string]: StoreWithIndexes<any, any, any> } = + {}; + const strStoreNames: string[] = []; + for (const sn of Object.keys(this.stores as any)) { + const swi = (this.stores as any)[sn] as StoreWithIndexes<any, any, any>; + strStoreNames.push(swi.storeName); + accessibleStores[swi.storeName] = swi; } - - const runReadOnly = <T>( - txf: ReadOnlyTransactionFunction<StoreMap, T>, - ): Promise<T> => { - const tx = this.db.transaction(storeNames, "readonly"); - const readContext = makeReadContext(tx, accessibleStores); - return runTx(tx, readContext, txf); - }; - - const runReadWrite = <T>( - txf: ReadWriteTransactionFunction<StoreMap, T>, - ): Promise<T> => { - const tx = this.db.transaction(storeNames, "readwrite"); - const writeContext = makeWriteContext(tx, accessibleStores); - return runTx(tx, writeContext, txf); - }; - - return { - runReadOnly, - runReadWrite, - }; + const tx = this.db.transaction(strStoreNames, "readonly"); + const writeContext = makeReadContext(tx, accessibleStores); + return runTx(tx, writeContext, txf); } runReadWriteTx<T, StoreNameArray extends Array<StoreNames<StoreMap>>>( storeNames: StoreNameArray, txf: ( - tx: DbReadWriteTransactionArr<StoreMap, StoreNameArray>, + tx: DbReadWriteTransaction<StoreMap, StoreNameArray>, ) => Promise<T>, ): Promise<T> { const accessibleStores: { [x: string]: StoreWithIndexes<any, any, any> } = @@ -908,7 +814,7 @@ export class DbAccess<StoreMap> { runReadOnlyTx<T, StoreNameArray extends Array<StoreNames<StoreMap>>>( storeNames: StoreNameArray, - txf: (tx: DbReadOnlyTransactionArr<StoreMap, StoreNameArray>) => Promise<T>, + txf: (tx: DbReadOnlyTransaction<StoreMap, StoreNameArray>) => Promise<T>, ): Promise<T> { const accessibleStores: { [x: string]: StoreWithIndexes<any, any, any> } = {}; @@ -922,57 +828,4 @@ export class DbAccess<StoreMap> { const readContext = makeReadContext(tx, accessibleStores); return runTx(tx, readContext, txf); } - - /** - * Run a transaction with selected object stores. - * - * The {@link namePicker} must be a function that selects a list of object - * stores from all available object stores. - */ - mktx< - StoreNames extends keyof StoreMap, - Stores extends StoreMap[StoreNames], - StoreList extends Stores[], - BoundStores extends { - [X in StoreNamesOf<StoreList>]: StoreList[number] & { storeName: X }; - }, - >(namePicker: (x: StoreMap) => StoreList): DbTransactionContext<BoundStores> { - const storeNames: string[] = []; - const accessibleStores: { [x: string]: StoreWithIndexes<any, any, any> } = - {}; - - const storePick = namePicker(this.stores) as any; - if (typeof storePick !== "object" || storePick === null) { - throw Error(); - } - for (const swiPicked of storePick) { - const swi = swiPicked as StoreWithIndexes<any, any, any>; - if (swi.mark !== storeWithIndexesSymbol) { - throw Error("invalid store descriptor returned from selector function"); - } - storeNames.push(swi.storeName); - accessibleStores[swi.storeName] = swi; - } - - const runReadOnly = <T>( - txf: ReadOnlyTransactionFunction<BoundStores, T>, - ): Promise<T> => { - const tx = this.db.transaction(storeNames, "readonly"); - const readContext = makeReadContext(tx, accessibleStores); - return runTx(tx, readContext, txf); - }; - - const runReadWrite = <T>( - txf: ReadWriteTransactionFunction<BoundStores, T>, - ): Promise<T> => { - const tx = this.db.transaction(storeNames, "readwrite"); - const writeContext = makeWriteContext(tx, accessibleStores); - return runTx(tx, writeContext, txf); - }; - - return { - runReadOnly, - runReadWrite, - }; - } } |