de-duplicating imports and fixing another typescript minor issues

This commit is contained in:
Sebastian 2022-01-16 17:47:43 -03:00
parent bf0cb6ab13
commit f8ae2671c1
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
5 changed files with 116 additions and 201 deletions

View File

@ -22,22 +22,20 @@
/** /**
* Imports. * Imports.
*/ */
import type { IDBFactory } from "@gnu-taler/idb-bridge";
// eslint-disable-next-line no-duplicate-imports
import { import {
MemoryBackend, BridgeIDBFactory, MemoryBackend, shimIndexedDB
BridgeIDBFactory,
shimIndexedDB,
} from "@gnu-taler/idb-bridge"; } from "@gnu-taler/idb-bridge";
import { AccessStats } from "@gnu-taler/idb-bridge/src/MemoryBackend";
import { Logger, WalletNotification } from "@gnu-taler/taler-util";
import * as fs from "fs";
import { NodeThreadCryptoWorkerFactory } from "../crypto/workers/nodeThreadWorker.js";
import { SynchronousCryptoWorkerFactory } from "../crypto/workers/synchronousWorkerFactory.js";
import { openTalerDatabase } from "../db-utils.js"; import { openTalerDatabase } from "../db-utils.js";
import { HttpRequestLibrary } from "../util/http.js"; import { HttpRequestLibrary } from "../util/http.js";
import { NodeThreadCryptoWorkerFactory } from "../crypto/workers/nodeThreadWorker.js";
import { NodeHttpLib } from "./NodeHttpLib.js";
import { Logger } from "@gnu-taler/taler-util";
import { SynchronousCryptoWorkerFactory } from "../crypto/workers/synchronousWorker.js";
import type { IDBFactory } from "@gnu-taler/idb-bridge";
import { WalletNotification } from "@gnu-taler/taler-util";
import { Wallet } from "../wallet.js"; import { Wallet } from "../wallet.js";
import * as fs from "fs"; import { NodeHttpLib } from "./NodeHttpLib.js";
import { AccessStats } from "@gnu-taler/idb-bridge/src/MemoryBackend";
const logger = new Logger("headless/helpers.ts"); const logger = new Logger("headless/helpers.ts");
@ -165,6 +163,7 @@ export async function getDefaultNodeWallet2(
try { try {
// Try if we have worker threads available, fails in older node versions. // Try if we have worker threads available, fails in older node versions.
const _r = "require"; const _r = "require";
// eslint-disable-next-line no-unused-vars
const worker_threads = module[_r]("worker_threads"); const worker_threads = module[_r]("worker_threads");
// require("worker_threads"); // require("worker_threads");
workerFactory = new NodeThreadCryptoWorkerFactory(); workerFactory = new NodeThreadCryptoWorkerFactory();

View File

@ -27,9 +27,7 @@ import {
CreateDepositGroupRequest, CreateDepositGroupRequest,
CreateDepositGroupResponse, CreateDepositGroupResponse,
DenomKeyType, DenomKeyType,
durationFromSpec, durationFromSpec, encodeCrock, GetFeeForDepositRequest, getRandomBytes, getTimestampNow,
GetFeeForDepositRequest,
getTimestampNow,
Logger, Logger,
NotificationType, NotificationType,
parsePaytoUri, parsePaytoUri,
@ -40,15 +38,9 @@ import {
timestampTruncateToSecond, timestampTruncateToSecond,
TrackDepositGroupRequest, TrackDepositGroupRequest,
TrackDepositGroupResponse, TrackDepositGroupResponse,
URL, URL
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { InternalWalletState } from "../common.js"; import { InternalWalletState } from "../common.js";
import { kdf } from "@gnu-taler/taler-util";
import {
encodeCrock,
getRandomBytes,
stringToBytes,
} from "@gnu-taler/taler-util";
import { DepositGroupRecord, OperationStatus } from "../db.js"; import { DepositGroupRecord, OperationStatus } from "../db.js";
import { guardOperationException } from "../errors.js"; import { guardOperationException } from "../errors.js";
import { PayCoinSelection, selectPayCoins } from "../util/coinSelection.js"; import { PayCoinSelection, selectPayCoins } from "../util/coinSelection.js";
@ -62,7 +54,7 @@ import {
getCandidatePayCoins, getCandidatePayCoins,
getTotalPaymentCost, getTotalPaymentCost,
hashWire, hashWire,
hashWireLegacy, hashWireLegacy
} from "./pay.js"; } from "./pay.js";
import { getTotalRefreshCost } from "./refresh.js"; import { getTotalRefreshCost } from "./refresh.js";
@ -168,7 +160,7 @@ export async function processDepositGroup(
async function processDepositGroupImpl( async function processDepositGroupImpl(
ws: InternalWalletState, ws: InternalWalletState,
depositGroupId: string, depositGroupId: string,
forceNow: boolean = false, forceNow = false,
): Promise<void> { ): Promise<void> {
if (forceNow) { if (forceNow) {
await resetDepositGroupRetry(ws, depositGroupId); await resetDepositGroupRetry(ws, depositGroupId);

View File

@ -26,58 +26,14 @@
*/ */
import { import {
AmountJson, AmountJson,
Amounts, Amounts, codecForContractTerms, codecForMerchantPayResponse, codecForProposal, CoinDepositPermission, ConfirmPayResult,
timestampIsBetween, ConfirmPayResultType, ContractTerms, decodeCrock, DenomKeyType, Duration,
getTimestampNow,
isTimestampExpired,
Timestamp,
RefreshReason,
CoinDepositPermission,
NotificationType,
TalerErrorDetails,
Duration,
durationMax, durationMax,
durationMin, durationMin,
durationMul, durationMul, encodeCrock, getDurationRemaining, getRandomBytes, getTimestampNow, HttpStatusCode, isTimestampExpired, j2s, kdf, Logger, NotificationType, parsePayUri, PreparePayResult,
ContractTerms, PreparePayResultType, RefreshReason, stringToBytes, TalerErrorCode, TalerErrorDetails, Timestamp, timestampAddDuration, URL
codecForProposal,
TalerErrorCode,
codecForContractTerms,
timestampAddDuration,
ConfirmPayResult,
ConfirmPayResultType,
codecForMerchantPayResponse,
PreparePayResult,
PreparePayResultType,
parsePayUri,
Logger,
URL,
getDurationRemaining,
HttpStatusCode,
DenomKeyType,
kdf,
stringToBytes,
decodeCrock,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util"; import { EXCHANGE_COINS_LOCK, InternalWalletState } from "../common.js";
import {
PayCoinSelection,
CoinCandidateSelection,
AvailableCoinInfo,
selectPayCoins,
PreviousPayCoins,
} from "../util/coinSelection.js";
import { j2s } from "@gnu-taler/taler-util";
import {
initRetryInfo,
updateRetryInfoTimeout,
getRetryDuration,
} from "../util/retries.js";
import { getTotalRefreshCost, createRefreshGroup } from "./refresh.js";
import { InternalWalletState, EXCHANGE_COINS_LOCK } from "../common.js";
import { ContractTermsUtil } from "../util/contractTerms.js";
import { getExchangeDetails } from "./exchanges.js";
import { GetReadWriteAccess } from "../util/query.js";
import { import {
AbortStatus, AbortStatus,
AllowedAuditorInfo, AllowedAuditorInfo,
@ -90,22 +46,33 @@ import {
ProposalStatus, ProposalStatus,
PurchaseRecord, PurchaseRecord,
WalletContractData, WalletContractData,
WalletStoresV1, WalletStoresV1
} from "../db.js"; } from "../db.js";
import {
guardOperationException,
makeErrorDetails,
OperationFailedAndReportedError,
OperationFailedError
} from "../errors.js";
import {
AvailableCoinInfo, CoinCandidateSelection, PayCoinSelection, PreviousPayCoins, selectPayCoins
} from "../util/coinSelection.js";
import { ContractTermsUtil } from "../util/contractTerms.js";
import { import {
getHttpResponseErrorDetails, getHttpResponseErrorDetails,
readSuccessResponseJsonOrErrorCode, readSuccessResponseJsonOrErrorCode,
readSuccessResponseJsonOrThrow, readSuccessResponseJsonOrThrow,
readTalerErrorResponse, readTalerErrorResponse,
readUnexpectedResponseDetails, readUnexpectedResponseDetails,
throwUnexpectedRequestError, throwUnexpectedRequestError
} from "../util/http.js"; } from "../util/http.js";
import { GetReadWriteAccess } from "../util/query.js";
import { import {
guardOperationException, getRetryDuration, initRetryInfo,
makeErrorDetails, updateRetryInfoTimeout
OperationFailedAndReportedError, } from "../util/retries.js";
OperationFailedError, import { getExchangeDetails } from "./exchanges.js";
} from "../errors.js"; import { createRefreshGroup, getTotalRefreshCost } from "./refresh.js";
/** /**
* Logger. * Logger.
@ -368,7 +335,7 @@ export async function applyCoinSpend(
}>, }>,
coinSelection: PayCoinSelection, coinSelection: PayCoinSelection,
allocationId: string, allocationId: string,
) { ): Promise<void> {
logger.info(`applying coin spend ${j2s(coinSelection)}`); logger.info(`applying coin spend ${j2s(coinSelection)}`);
for (let i = 0; i < coinSelection.coinPubs.length; i++) { for (let i = 0; i < coinSelection.coinPubs.length; i++) {
const coin = await tx.coins.get(coinSelection.coinPubs[i]); const coin = await tx.coins.get(coinSelection.coinPubs[i]);
@ -530,8 +497,7 @@ async function incrementPurchasePayRetry(
pr.payRetryInfo.retryCounter++; pr.payRetryInfo.retryCounter++;
updateRetryInfoTimeout(pr.payRetryInfo); updateRetryInfoTimeout(pr.payRetryInfo);
logger.trace( logger.trace(
`retrying pay in ${ `retrying pay in ${getDurationRemaining(pr.payRetryInfo.nextRetry).d_ms
getDurationRemaining(pr.payRetryInfo.nextRetry).d_ms
} ms`, } ms`,
); );
pr.lastPayError = err; pr.lastPayError = err;
@ -1105,7 +1071,7 @@ async function unblockBackup(
await ws.db await ws.db
.mktx((x) => ({ backupProviders: x.backupProviders })) .mktx((x) => ({ backupProviders: x.backupProviders }))
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const bp = await tx.backupProviders.indexes.byPaymentProposalId await tx.backupProviders.indexes.byPaymentProposalId
.iter(proposalId) .iter(proposalId)
.forEachAsync(async (bp) => { .forEachAsync(async (bp) => {
if (bp.state.tag === BackupProviderStateTag.Retrying) { if (bp.state.tag === BackupProviderStateTag.Retrying) {
@ -1143,6 +1109,7 @@ async function submitPay(
logger.trace("paying with session ID", sessionId); logger.trace("paying with session ID", sessionId);
//FIXME: not used, does it expect a side effect?
const merchantInfo = await ws.merchantOps.getMerchantInfo( const merchantInfo = await ws.merchantOps.getMerchantInfo(
ws, ws,
purchase.download.contractData.merchantBaseUrl, purchase.download.contractData.merchantBaseUrl,

View File

@ -27,9 +27,8 @@ import {
AmountJson, AmountJson,
Amounts, Amounts,
DenominationPubKey, DenominationPubKey,
DenomKeyType, DenomKeyType, Logger, strcmp
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { strcmp, Logger } from "@gnu-taler/taler-util";
const logger = new Logger("coinSelection.ts"); const logger = new Logger("coinSelection.ts");

View File

@ -23,30 +23,50 @@
* Imports. * Imports.
*/ */
import { import {
BalancesResponse, AcceptManualWithdrawalResult,
codecForAny, AcceptWithdrawalResponse, AmountJson, Amounts, BalancesResponse, codecForAbortPayWithRefundRequest,
codecForDeleteTransactionRequest, codecForAcceptBankIntegratedWithdrawalRequest,
codecForRetryTransactionRequest, codecForAcceptExchangeTosRequest,
codecForSetWalletDeviceIdRequest, codecForAcceptManualWithdrawalRequet,
codecForGetExchangeWithdrawalInfo, codecForAcceptTipRequest,
durationFromSpec, codecForAddExchangeRequest, codecForAny, codecForApplyRefundRequest,
durationMin, codecForConfirmPayRequest,
getDurationRemaining, codecForCreateDepositGroupRequest, codecForDeleteTransactionRequest, codecForForceRefreshRequest,
isTimestampExpired, codecForGetExchangeTosRequest, codecForGetExchangeWithdrawalInfo, codecForGetFeeForDeposit, codecForGetWithdrawalDetailsForAmountRequest,
j2s, codecForGetWithdrawalDetailsForUri, codecForImportDbRequest, codecForIntegrationTestArgs, codecForListKnownBankAccounts, codecForPreparePayRequest,
TalerErrorCode, codecForPrepareTipRequest, codecForRetryTransactionRequest, codecForSetCoinSuspendedRequest, codecForSetWalletDeviceIdRequest, codecForTestPayArgs,
codecForTrackDepositGroupRequest, codecForTransactionsRequest, codecForWithdrawFakebankRequest, codecForWithdrawTestBalance, CoinDumpJson, CoreApiResponse, durationFromSpec,
durationMin, ExchangeListItem,
ExchangesListRespose, getDurationRemaining, GetExchangeTosResult, isTimestampExpired,
j2s, KnownBankAccounts, Logger, ManualWithdrawalDetails, NotificationType, parsePaytoUri, PaytoUri, RefreshReason, TalerErrorCode,
Timestamp, Timestamp,
timestampMin, timestampMin, URL, WalletNotification
WalletNotification,
codecForWithdrawFakebankRequest,
URL,
parsePaytoUri,
KnownBankAccounts,
PaytoUri,
codecForGetFeeForDeposit,
codecForListKnownBankAccounts,
codecForImportDbRequest,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import {
DenomInfo,
ExchangeOperations,
InternalWalletState,
MerchantInfo,
MerchantOperations,
NotificationListener,
RecoupOperations,
ReserveOperations
} from "./common.js";
import { CryptoApi, CryptoWorkerFactory } from "./crypto/workers/cryptoApi.js";
import {
AuditorTrustRecord,
CoinSourceType,
exportDb,
importDb,
ReserveRecordStatus,
WalletStoresV1
} from "./db.js";
import {
makeErrorDetails,
OperationFailedAndReportedError,
OperationFailedError
} from "./errors.js";
import { exportBackup } from "./operations/backup/export.js";
import { import {
addBackupProvider, addBackupProvider,
codecForAddBackupProviderRequest, codecForAddBackupProviderRequest,
@ -57,142 +77,80 @@ import {
loadBackupRecovery, loadBackupRecovery,
processBackupForProvider, processBackupForProvider,
removeBackupProvider, removeBackupProvider,
runBackupCycle, runBackupCycle
} from "./operations/backup/index.js"; } from "./operations/backup/index.js";
import { exportBackup } from "./operations/backup/export.js"; import { setWalletDeviceId } from "./operations/backup/state.js";
import { getBalances } from "./operations/balance.js"; import { getBalances } from "./operations/balance.js";
import { import {
createDepositGroup, createDepositGroup,
getFeeForDeposit, getFeeForDeposit,
processDepositGroup, processDepositGroup,
trackDepositGroup, trackDepositGroup
} from "./operations/deposits.js"; } from "./operations/deposits.js";
import {
makeErrorDetails,
OperationFailedAndReportedError,
OperationFailedError,
} from "./errors.js";
import { import {
acceptExchangeTermsOfService, acceptExchangeTermsOfService,
getExchangeDetails, getExchangeDetails,
getExchangeTrust, getExchangeTrust,
updateExchangeFromUrl, updateExchangeFromUrl
} from "./operations/exchanges.js"; } from "./operations/exchanges.js";
import { getMerchantInfo } from "./operations/merchants.js";
import { import {
confirmPay, confirmPay,
preparePayForUri, preparePayForUri,
processDownloadProposal, processDownloadProposal,
processPurchasePay, processPurchasePay
} from "./operations/pay.js"; } from "./operations/pay.js";
import { getPendingOperations } from "./operations/pending.js"; import { getPendingOperations } from "./operations/pending.js";
import { createRecoupGroup, processRecoupGroup } from "./operations/recoup.js"; import { createRecoupGroup, processRecoupGroup } from "./operations/recoup.js";
import { import {
autoRefresh, autoRefresh,
createRefreshGroup, createRefreshGroup,
processRefreshGroup, processRefreshGroup
} from "./operations/refresh.js"; } from "./operations/refresh.js";
import { import {
abortFailedPayWithRefund, abortFailedPayWithRefund,
applyRefund, applyRefund,
processPurchaseQueryRefund, processPurchaseQueryRefund
} from "./operations/refund.js"; } from "./operations/refund.js";
import { import {
createReserve, createReserve,
createTalerWithdrawReserve, createTalerWithdrawReserve,
getFundingPaytoUris, getFundingPaytoUris,
processReserve, processReserve
} from "./operations/reserves.js"; } from "./operations/reserves.js";
import {
DenomInfo,
ExchangeOperations,
InternalWalletState,
MerchantInfo,
MerchantOperations,
NotificationListener,
RecoupOperations,
ReserveOperations,
} from "./common.js";
import { import {
runIntegrationTest, runIntegrationTest,
testPay, testPay,
withdrawTestBalance, withdrawTestBalance
} from "./operations/testing.js"; } from "./operations/testing.js";
import { acceptTip, prepareTip, processTip } from "./operations/tip.js"; import { acceptTip, prepareTip, processTip } from "./operations/tip.js";
import { import {
deleteTransaction, deleteTransaction,
getTransactions, getTransactions,
retryTransaction, retryTransaction
} from "./operations/transactions.js"; } from "./operations/transactions.js";
import { import {
getExchangeWithdrawalInfo, getExchangeWithdrawalInfo,
getWithdrawalDetailsForUri, getWithdrawalDetailsForUri,
processWithdrawGroup, processWithdrawGroup
} from "./operations/withdraw.js"; } from "./operations/withdraw.js";
import { import {
AuditorTrustRecord, PendingOperationsResponse, PendingTaskInfo, PendingTaskType
CoinSourceType,
exportDb,
importDb,
ReserveRecordStatus,
WalletStoresV1,
} from "./db.js";
import { NotificationType } from "@gnu-taler/taler-util";
import {
PendingTaskInfo,
PendingOperationsResponse,
PendingTaskType,
} from "./pending-types.js"; } from "./pending-types.js";
import { CoinDumpJson } from "@gnu-taler/taler-util";
import { codecForTransactionsRequest } from "@gnu-taler/taler-util";
import {
AcceptManualWithdrawalResult,
AcceptWithdrawalResponse,
codecForAbortPayWithRefundRequest,
codecForAcceptBankIntegratedWithdrawalRequest,
codecForAcceptExchangeTosRequest,
codecForAcceptManualWithdrawalRequet,
codecForAcceptTipRequest,
codecForAddExchangeRequest,
codecForApplyRefundRequest,
codecForConfirmPayRequest,
codecForCreateDepositGroupRequest,
codecForForceRefreshRequest,
codecForGetExchangeTosRequest,
codecForGetWithdrawalDetailsForAmountRequest,
codecForGetWithdrawalDetailsForUri,
codecForIntegrationTestArgs,
codecForPreparePayRequest,
codecForPrepareTipRequest,
codecForSetCoinSuspendedRequest,
codecForTestPayArgs,
codecForTrackDepositGroupRequest,
codecForWithdrawTestBalance,
CoreApiResponse,
ExchangeListItem,
ExchangesListRespose,
GetExchangeTosResult,
ManualWithdrawalDetails,
RefreshReason,
} from "@gnu-taler/taler-util";
import { AmountJson, Amounts } from "@gnu-taler/taler-util";
import { assertUnreachable } from "./util/assertUnreachable.js"; import { assertUnreachable } from "./util/assertUnreachable.js";
import { Logger } from "@gnu-taler/taler-util";
import { setWalletDeviceId } from "./operations/backup/state.js";
import { WalletCoreApiClient } from "./wallet-api-types.js";
import { AsyncOpMemoMap, AsyncOpMemoSingle } from "./util/asyncMemo.js"; import { AsyncOpMemoMap, AsyncOpMemoSingle } from "./util/asyncMemo.js";
import { CryptoApi, CryptoWorkerFactory } from "./crypto/workers/cryptoApi.js"; import {
import { TimerGroup } from "./util/timer.js"; HttpRequestLibrary,
readSuccessResponseJsonOrThrow
} from "./util/http.js";
import { import {
AsyncCondition, AsyncCondition,
OpenedPromise, OpenedPromise,
openPromise, openPromise
} from "./util/promiseUtils.js"; } from "./util/promiseUtils.js";
import { DbAccess, GetReadWriteAccess } from "./util/query.js"; import { DbAccess, GetReadWriteAccess } from "./util/query.js";
import { import { TimerGroup } from "./util/timer.js";
HttpRequestLibrary, import { WalletCoreApiClient } from "./wallet-api-types.js";
readSuccessResponseJsonOrThrow,
} from "./util/http.js";
import { getMerchantInfo } from "./operations/merchants.js";
const builtinAuditors: AuditorTrustRecord[] = [ const builtinAuditors: AuditorTrustRecord[] = [
{ {
@ -1047,7 +1005,7 @@ export async function handleCoreApiRequest(
try { try {
logger.error("Caught unexpected exception:"); logger.error("Caught unexpected exception:");
logger.error(e.stack); logger.error(e.stack);
} catch (e) {} } catch (e) { }
return { return {
type: "error", type: "error",
operation, operation,
@ -1077,7 +1035,7 @@ export class Wallet {
this.ws = new InternalWalletStateImpl(db, http, cryptoWorkerFactory); this.ws = new InternalWalletStateImpl(db, http, cryptoWorkerFactory);
} }
get client() { get client(): WalletCoreApiClient {
return this._client; return this._client;
} }
@ -1085,7 +1043,7 @@ export class Wallet {
* Trust the exchange, do not validate signatures. * Trust the exchange, do not validate signatures.
* Only used to benchmark the exchange. * Only used to benchmark the exchange.
*/ */
setInsecureTrustExchange() { setInsecureTrustExchange(): void {
this.ws.insecureTrustExchange = true; this.ws.insecureTrustExchange = true;
} }
@ -1107,11 +1065,11 @@ export class Wallet {
this.ws.stop(); this.ws.stop();
} }
runPending(forceNow: boolean = false) { runPending(forceNow = false): Promise<void> {
return runPending(this.ws, forceNow); return runPending(this.ws, forceNow);
} }
runTaskLoop(opts?: RetryLoopOpts) { runTaskLoop(opts?: RetryLoopOpts): Promise<void> {
return runTaskLoop(this.ws, opts); return runTaskLoop(this.ws, opts);
} }
@ -1142,7 +1100,7 @@ class InternalWalletStateImpl implements InternalWalletState {
merchantInfoCache: Record<string, MerchantInfo> = {}; merchantInfoCache: Record<string, MerchantInfo> = {};
insecureTrustExchange: boolean = false; insecureTrustExchange = false;
timerGroup: TimerGroup = new TimerGroup(); timerGroup: TimerGroup = new TimerGroup();
latch = new AsyncCondition(); latch = new AsyncCondition();
@ -1150,7 +1108,7 @@ class InternalWalletStateImpl implements InternalWalletState {
listeners: NotificationListener[] = []; listeners: NotificationListener[] = [];
initCalled: boolean = false; initCalled = false;
exchangeOps: ExchangeOperations = { exchangeOps: ExchangeOperations = {
getExchangeDetails, getExchangeDetails,
@ -1159,16 +1117,16 @@ class InternalWalletStateImpl implements InternalWalletState {
}; };
recoupOps: RecoupOperations = { recoupOps: RecoupOperations = {
createRecoupGroup: createRecoupGroup, createRecoupGroup,
processRecoupGroup: processRecoupGroup, processRecoupGroup,
}; };
merchantOps: MerchantOperations = { merchantOps: MerchantOperations = {
getMerchantInfo: getMerchantInfo, getMerchantInfo,
}; };
reserveOps: ReserveOperations = { reserveOps: ReserveOperations = {
processReserve: processReserve, processReserve,
}; };
// FIXME: Use an LRU cache here. // FIXME: Use an LRU cache here.
@ -1253,7 +1211,7 @@ class InternalWalletStateImpl implements InternalWalletState {
* Run an async function after acquiring a list of locks, identified * Run an async function after acquiring a list of locks, identified
* by string tokens. * by string tokens.
*/ */
async runSequentialized<T>(tokens: string[], f: () => Promise<T>) { async runSequentialized<T>(tokens: string[], f: () => Promise<T>): Promise<T> {
// Make sure locks are always acquired in the same order // Make sure locks are always acquired in the same order
tokens = [...tokens].sort(); tokens = [...tokens].sort();