@metamask/assets-controller 9.0.2 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -1
- package/dist/AssetsController.cjs +6 -15
- package/dist/AssetsController.cjs.map +1 -1
- package/dist/AssetsController.d.cts.map +1 -1
- package/dist/AssetsController.d.mts.map +1 -1
- package/dist/AssetsController.mjs +6 -15
- package/dist/AssetsController.mjs.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.cjs +1 -1
- package/dist/data-sources/AccountsApiDataSource.cjs.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.mjs +1 -1
- package/dist/data-sources/AccountsApiDataSource.mjs.map +1 -1
- package/dist/data-sources/PriceDataSource.cjs +64 -13
- package/dist/data-sources/PriceDataSource.cjs.map +1 -1
- package/dist/data-sources/PriceDataSource.d.cts +13 -0
- package/dist/data-sources/PriceDataSource.d.cts.map +1 -1
- package/dist/data-sources/PriceDataSource.d.mts +13 -0
- package/dist/data-sources/PriceDataSource.d.mts.map +1 -1
- package/dist/data-sources/PriceDataSource.mjs +64 -13
- package/dist/data-sources/PriceDataSource.mjs.map +1 -1
- package/dist/data-sources/SnapDataSource.cjs +2 -2
- package/dist/data-sources/SnapDataSource.cjs.map +1 -1
- package/dist/data-sources/SnapDataSource.mjs +2 -2
- package/dist/data-sources/SnapDataSource.mjs.map +1 -1
- package/dist/index.cjs +3 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/middlewares/DetectionMiddleware.cjs +43 -23
- package/dist/middlewares/DetectionMiddleware.cjs.map +1 -1
- package/dist/middlewares/DetectionMiddleware.d.cts +12 -13
- package/dist/middlewares/DetectionMiddleware.d.cts.map +1 -1
- package/dist/middlewares/DetectionMiddleware.d.mts +12 -13
- package/dist/middlewares/DetectionMiddleware.d.mts.map +1 -1
- package/dist/middlewares/DetectionMiddleware.mjs +43 -23
- package/dist/middlewares/DetectionMiddleware.mjs.map +1 -1
- package/dist/selectors/balance.cjs +191 -8
- package/dist/selectors/balance.cjs.map +1 -1
- package/dist/selectors/balance.d.cts +80 -0
- package/dist/selectors/balance.d.cts.map +1 -1
- package/dist/selectors/balance.d.mts +80 -0
- package/dist/selectors/balance.d.mts.map +1 -1
- package/dist/selectors/balance.mjs +186 -7
- package/dist/selectors/balance.mjs.map +1 -1
- package/dist/utils/dedupingBatchFetcher.cjs +157 -0
- package/dist/utils/dedupingBatchFetcher.cjs.map +1 -0
- package/dist/utils/dedupingBatchFetcher.d.cts +65 -0
- package/dist/utils/dedupingBatchFetcher.d.cts.map +1 -0
- package/dist/utils/dedupingBatchFetcher.d.mts +65 -0
- package/dist/utils/dedupingBatchFetcher.d.mts.map +1 -0
- package/dist/utils/dedupingBatchFetcher.mjs +153 -0
- package/dist/utils/dedupingBatchFetcher.mjs.map +1 -0
- package/package.json +5 -5
|
@@ -230,7 +230,42 @@ function resolveAccountsToAggregate(args) {
|
|
|
230
230
|
}
|
|
231
231
|
return [selectedInternalAccount];
|
|
232
232
|
}
|
|
233
|
-
|
|
233
|
+
/**
|
|
234
|
+
* Get account ids that belong to a group, reading directly from the account
|
|
235
|
+
* tree. Unlike {@link getInternalAccountsForGroup}, this does not require an
|
|
236
|
+
* `accountsById` lookup map, which makes it convenient for balance aggregation
|
|
237
|
+
* where only the ids are needed.
|
|
238
|
+
*
|
|
239
|
+
* @param accountTreeState - AccountTreeController state.
|
|
240
|
+
* @param groupId - The account group id to look up.
|
|
241
|
+
* @returns The list of account ids in the group, or an empty array.
|
|
242
|
+
*/
|
|
243
|
+
export function getAccountIdsForGroup(accountTreeState, groupId) {
|
|
244
|
+
const wallets = accountTreeState.accountTree?.wallets ?? {};
|
|
245
|
+
for (const wallet of Object.values(wallets)) {
|
|
246
|
+
const groups = (wallet?.groups ?? {});
|
|
247
|
+
const group = groups[groupId];
|
|
248
|
+
if (group?.accounts) {
|
|
249
|
+
return [...group.accounts];
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Aggregate balances across an explicit list of accounts.
|
|
256
|
+
*
|
|
257
|
+
* This is the core aggregation engine shared by the account- and group-level
|
|
258
|
+
* selectors. It intentionally takes a fully-resolved list of accounts so that
|
|
259
|
+
* callers control account resolution and no "selected account" placeholder is
|
|
260
|
+
* required.
|
|
261
|
+
*
|
|
262
|
+
* @param state - AssetsController state slice.
|
|
263
|
+
* @param accounts - Accounts whose balances should be aggregated.
|
|
264
|
+
* @param enabledNetworkMap - Optional map of enabled networks keyed by namespace.
|
|
265
|
+
* @param trace - Optional trace callback for telemetry spans.
|
|
266
|
+
* @returns Aggregated balance entries plus fiat totals when prices are present.
|
|
267
|
+
*/
|
|
268
|
+
function aggregateBalances(state, accounts, enabledNetworkMap, trace) {
|
|
234
269
|
const startTime = trace ? performance.now() : 0;
|
|
235
270
|
const { assetsBalance, assetsInfo, assetPreferences, assetsPrice } = state;
|
|
236
271
|
const metadata = (assetsInfo ?? {});
|
|
@@ -243,12 +278,7 @@ export function getAggregatedBalanceForAccount(state, selectedInternalAccount, e
|
|
|
243
278
|
}
|
|
244
279
|
return false;
|
|
245
280
|
})();
|
|
246
|
-
const accountsToAggregate =
|
|
247
|
-
selectedInternalAccount,
|
|
248
|
-
accountTreeState,
|
|
249
|
-
internalAccountsOrAccountIds,
|
|
250
|
-
accountsById,
|
|
251
|
-
});
|
|
281
|
+
const accountsToAggregate = accounts;
|
|
252
282
|
const assetInfoCache = makeAssetInfoCache();
|
|
253
283
|
const merged = new Map();
|
|
254
284
|
for (const account of accountsToAggregate) {
|
|
@@ -268,6 +298,7 @@ export function getAggregatedBalanceForAccount(state, selectedInternalAccount, e
|
|
|
268
298
|
let idx = 0;
|
|
269
299
|
// If prices exist, compute totals in a single pass over merged.
|
|
270
300
|
let totalBalanceInFiat = 0;
|
|
301
|
+
let previousTotalInFiat = 0;
|
|
271
302
|
let weightedNumerator = 0;
|
|
272
303
|
for (const [assetId, row] of merged.entries()) {
|
|
273
304
|
const { amount } = row;
|
|
@@ -286,6 +317,11 @@ export function getAggregatedBalanceForAccount(state, selectedInternalAccount, e
|
|
|
286
317
|
if (contribution > 0) {
|
|
287
318
|
totalBalanceInFiat += contribution;
|
|
288
319
|
weightedNumerator += contribution * pricePercentChange1d;
|
|
320
|
+
// A -100% move makes the denominator zero; treat it as flat so the
|
|
321
|
+
// previous total never goes to infinity.
|
|
322
|
+
const denom = Number((1 + pricePercentChange1d / 100).toFixed(8));
|
|
323
|
+
previousTotalInFiat +=
|
|
324
|
+
denom === 0 ? contribution : contribution / denom;
|
|
289
325
|
}
|
|
290
326
|
}
|
|
291
327
|
}
|
|
@@ -315,8 +351,151 @@ export function getAggregatedBalanceForAccount(state, selectedInternalAccount, e
|
|
|
315
351
|
entries,
|
|
316
352
|
totalBalanceInFiat,
|
|
317
353
|
pricePercentChange1d,
|
|
354
|
+
previousTotalInFiat,
|
|
318
355
|
};
|
|
319
356
|
}
|
|
320
357
|
return { entries };
|
|
321
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* Aggregate balances for an explicit list of account ids.
|
|
361
|
+
*
|
|
362
|
+
* Prefer this over {@link getAggregatedBalanceForAccount} when the set of
|
|
363
|
+
* accounts to aggregate is already known (e.g. all accounts in a group), since
|
|
364
|
+
* it does not require a "selected account" and therefore avoids the brittle
|
|
365
|
+
* account-resolution heuristics.
|
|
366
|
+
*
|
|
367
|
+
* @param state - AssetsController state slice.
|
|
368
|
+
* @param accountIds - Account ids whose balances should be aggregated.
|
|
369
|
+
* @param enabledNetworkMap - Optional map of enabled networks keyed by namespace.
|
|
370
|
+
* @param trace - Optional trace callback for telemetry spans.
|
|
371
|
+
* @returns Aggregated balance entries plus fiat totals when prices are present.
|
|
372
|
+
*/
|
|
373
|
+
export function getAggregatedBalanceForAccountIds(state, accountIds, enabledNetworkMap, trace) {
|
|
374
|
+
const accounts = accountIds.map((id) => ({ id }));
|
|
375
|
+
return aggregateBalances(state, accounts, enabledNetworkMap, trace);
|
|
376
|
+
}
|
|
377
|
+
export function getAggregatedBalanceForAccount(state, selectedInternalAccount, enabledNetworkMap, accountTreeState, internalAccountsOrAccountIds, accountsById, trace) {
|
|
378
|
+
const accountsToAggregate = resolveAccountsToAggregate({
|
|
379
|
+
selectedInternalAccount,
|
|
380
|
+
accountTreeState,
|
|
381
|
+
internalAccountsOrAccountIds,
|
|
382
|
+
accountsById,
|
|
383
|
+
});
|
|
384
|
+
return aggregateBalances(state, accountsToAggregate, enabledNetworkMap, trace);
|
|
385
|
+
}
|
|
386
|
+
// ============================================================================
|
|
387
|
+
// WALLET- AND GROUP-LEVEL BALANCE CALCULATIONS
|
|
388
|
+
// ============================================================================
|
|
389
|
+
/**
|
|
390
|
+
* Default user currency used when {@link AssetsControllerState.selectedCurrency}
|
|
391
|
+
* is not set.
|
|
392
|
+
*/
|
|
393
|
+
const DEFAULT_USER_CURRENCY = 'usd';
|
|
394
|
+
/**
|
|
395
|
+
* Resolve the user currency for a balance calculation, falling back to a
|
|
396
|
+
* sensible default when the controller has no selected currency.
|
|
397
|
+
*
|
|
398
|
+
* @param assetsControllerState - AssetsController state slice.
|
|
399
|
+
* @returns The user currency code.
|
|
400
|
+
*/
|
|
401
|
+
function getUserCurrency(assetsControllerState) {
|
|
402
|
+
return assetsControllerState.selectedCurrency ?? DEFAULT_USER_CURRENCY;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Resolve the current and previous totals for a change calculation.
|
|
406
|
+
*
|
|
407
|
+
* The AssetsController state only exposes a 1d price change, so non-`1d`
|
|
408
|
+
* periods produce a zeroed change (previous equals current).
|
|
409
|
+
*
|
|
410
|
+
* @param totalBalanceInFiat - Aggregated current balance in user currency.
|
|
411
|
+
* @param previousTotalInFiat - Aggregated prior balance summed per asset.
|
|
412
|
+
* @param period - Period to compute the change for.
|
|
413
|
+
* @returns The current and previous totals in user currency.
|
|
414
|
+
*/
|
|
415
|
+
function getCurrentAndPrevious(totalBalanceInFiat, previousTotalInFiat, period) {
|
|
416
|
+
const current = totalBalanceInFiat;
|
|
417
|
+
const previous = period === '1d' ? previousTotalInFiat : current;
|
|
418
|
+
return { current, previous };
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Build a {@link BalanceChangeResult} from current/previous totals.
|
|
422
|
+
*
|
|
423
|
+
* @param current - Current total in user currency.
|
|
424
|
+
* @param previous - Previous total in user currency.
|
|
425
|
+
* @param period - Period the change was computed for.
|
|
426
|
+
* @param userCurrency - User currency code.
|
|
427
|
+
* @returns The change result with delta and percent change.
|
|
428
|
+
*/
|
|
429
|
+
function buildBalanceChangeResult(current, previous, period, userCurrency) {
|
|
430
|
+
const amountChange = current - previous;
|
|
431
|
+
const percentChange = previous === 0 ? 0 : (amountChange / previous) * 100;
|
|
432
|
+
return {
|
|
433
|
+
period,
|
|
434
|
+
currentTotalInUserCurrency: Number(current.toFixed(8)),
|
|
435
|
+
previousTotalInUserCurrency: Number(previous.toFixed(8)),
|
|
436
|
+
amountChangeInUserCurrency: Number(amountChange.toFixed(8)),
|
|
437
|
+
percentChange: Number(percentChange.toFixed(8)),
|
|
438
|
+
userCurrency,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Calculate aggregated balances for all wallets and groups.
|
|
443
|
+
*
|
|
444
|
+
* Mirrors the legacy `@metamask/assets-controllers` `calculateBalanceForAllWallets`
|
|
445
|
+
* output shape, but sources every group total from the unified AssetsController
|
|
446
|
+
* state via {@link getAggregatedBalanceForAccountIds}. The account tree is walked
|
|
447
|
+
* to aggregate each group individually.
|
|
448
|
+
*
|
|
449
|
+
* @param assetsControllerState - AssetsController state slice.
|
|
450
|
+
* @param accountTreeState - AccountTreeController state.
|
|
451
|
+
* @param enabledNetworkMap - Map of enabled networks keyed by namespace.
|
|
452
|
+
* @param trace - Optional trace callback forwarded to the aggregation selector.
|
|
453
|
+
* @returns Aggregated balances for all wallets and groups.
|
|
454
|
+
*/
|
|
455
|
+
export function calculateBalanceForAllWallets(assetsControllerState, accountTreeState, enabledNetworkMap, trace) {
|
|
456
|
+
const userCurrency = getUserCurrency(assetsControllerState);
|
|
457
|
+
const wallets = {};
|
|
458
|
+
let totalBalanceInUserCurrency = 0;
|
|
459
|
+
for (const [walletId, wallet] of Object.entries(accountTreeState.accountTree?.wallets ?? {})) {
|
|
460
|
+
const walletBalance = {
|
|
461
|
+
walletId,
|
|
462
|
+
groups: {},
|
|
463
|
+
totalBalanceInUserCurrency: 0,
|
|
464
|
+
userCurrency,
|
|
465
|
+
};
|
|
466
|
+
const groups = wallet?.groups ?? {};
|
|
467
|
+
for (const groupId of Object.keys(groups)) {
|
|
468
|
+
const accountIds = getAccountIdsForGroup(accountTreeState, groupId);
|
|
469
|
+
const { totalBalanceInFiat = 0 } = getAggregatedBalanceForAccountIds(assetsControllerState, accountIds, enabledNetworkMap, trace);
|
|
470
|
+
walletBalance.groups[groupId] = {
|
|
471
|
+
walletId,
|
|
472
|
+
groupId,
|
|
473
|
+
totalBalanceInUserCurrency: totalBalanceInFiat,
|
|
474
|
+
userCurrency,
|
|
475
|
+
};
|
|
476
|
+
walletBalance.totalBalanceInUserCurrency += totalBalanceInFiat;
|
|
477
|
+
}
|
|
478
|
+
wallets[walletId] = walletBalance;
|
|
479
|
+
totalBalanceInUserCurrency += walletBalance.totalBalanceInUserCurrency;
|
|
480
|
+
}
|
|
481
|
+
return { wallets, totalBalanceInUserCurrency, userCurrency };
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Calculate the portfolio value change for a single account group and period.
|
|
485
|
+
*
|
|
486
|
+
* @param assetsControllerState - AssetsController state slice.
|
|
487
|
+
* @param accountTreeState - AccountTreeController state.
|
|
488
|
+
* @param groupId - Account group id to compute the change for.
|
|
489
|
+
* @param period - Change period (`1d` | `7d` | `30d`).
|
|
490
|
+
* @param enabledNetworkMap - Map of enabled networks keyed by namespace.
|
|
491
|
+
* @param trace - Optional trace callback forwarded to the aggregation selector.
|
|
492
|
+
* @returns The change result for the requested period.
|
|
493
|
+
*/
|
|
494
|
+
export function calculateBalanceChangeForAccountGroup(assetsControllerState, accountTreeState, groupId, period, enabledNetworkMap, trace) {
|
|
495
|
+
const userCurrency = getUserCurrency(assetsControllerState);
|
|
496
|
+
const accountIds = getAccountIdsForGroup(accountTreeState, groupId);
|
|
497
|
+
const { totalBalanceInFiat = 0, previousTotalInFiat = totalBalanceInFiat } = getAggregatedBalanceForAccountIds(assetsControllerState, accountIds, enabledNetworkMap, trace);
|
|
498
|
+
const { current, previous } = getCurrentAndPrevious(totalBalanceInFiat, previousTotalInFiat, period);
|
|
499
|
+
return buildBalanceChangeResult(current, previous, period, userCurrency);
|
|
500
|
+
}
|
|
322
501
|
//# sourceMappingURL=balance.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"balance.mjs","sourceRoot":"","sources":["../../src/selectors/balance.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccountTreeControllerState } from '@metamask/account-tree-controller';\nimport { toHex } from '@metamask/controller-utils';\nimport type { TraceCallback } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { CaipChainId, Hex } from '@metamask/utils';\nimport {\n KnownCaipNamespace,\n isStrictHexString,\n parseCaipAssetType,\n parseCaipChainId,\n} from '@metamask/utils';\nimport BigNumberJS from 'bignumber.js';\n\nimport type { AssetsControllerState } from '../AssetsController';\nimport type {\n AccountId,\n AssetBalance,\n AssetMetadata,\n AssetPreferences,\n Caip19AssetId,\n} from '../types';\n\n// ============================================================================\n// TRACE NAMES — used in Sentry spans (search these strings in Discover)\n// ============================================================================\nconst TRACE_AGGREGATED_BALANCE_SELECTOR = 'AggregatedBalanceSelector';\n\nexport type EnabledNetworkMap =\n | Record<string, Record<string, boolean>>\n | undefined;\n\nexport type AggregatedBalanceEntry = {\n assetId: Caip19AssetId;\n amount: string;\n decimals?: number;\n symbol?: string;\n name?: string;\n};\n\nexport type AggregatedBalanceForAccount = {\n entries: AggregatedBalanceEntry[];\n totalBalanceInFiat?: number;\n pricePercentChange1d?: number;\n};\n\ntype AccountLike = { id: AccountId };\nexport type AccountsById = Record<AccountId, InternalAccount>;\n\ntype PriceDatum = { price: number; pricePercentChange1d: number };\n\nconst ZERO_PRICE: PriceDatum = { price: 0, pricePercentChange1d: 0 };\n\nconst getAmountFromBalance = (balance: AssetBalance): string => {\n if (typeof balance === 'object' && balance !== null && 'amount' in balance) {\n const { amount } = balance as { amount?: unknown };\n return typeof amount === 'string' ? amount : '0';\n }\n return '0';\n};\n\nconst toBigNumberOrZero = (value: string): BigNumberJS => {\n const parsed = new BigNumberJS(value);\n return parsed.isNaN() || !parsed.isFinite() ? new BigNumberJS(0) : parsed;\n};\n\n/**\n * When state contains raw base units (e.g. SPORE \"16201541034.639288184\" with 9 decimals),\n * scale to human (16.2) before pricing. Only scale when amount >= 10^decimals so we don't\n * double-scale amounts that are already human (e.g. \"1\", \"0.227285\").\n *\n * @param amountRaw - Amount from state (may be raw or human).\n * @param decimals - Token decimals from metadata.\n * @returns Human amount for display and fiat (amountRaw or amountRaw / 10^decimals).\n */\nconst scaleToHumanIfRaw = (\n amountRaw: BigNumberJS,\n decimals: number,\n): BigNumberJS => {\n if (decimals <= 0) {\n return amountRaw;\n }\n const scale = new BigNumberJS(10).pow(decimals);\n if (amountRaw.lt(scale)) {\n return amountRaw;\n }\n return amountRaw.dividedBy(scale);\n};\n\nconst getPriceDatumFast = (\n assetsPrice: AssetsControllerState['assetsPrice'] | undefined,\n assetId: Caip19AssetId,\n): PriceDatum => {\n const raw = assetsPrice?.[assetId];\n if (!raw || typeof raw !== 'object') {\n return ZERO_PRICE;\n }\n const { price, pricePercentChange1d } = raw as {\n price?: unknown;\n pricePercentChange1d?: unknown;\n };\n return {\n price: typeof price === 'number' && Number.isFinite(price) ? price : 0,\n pricePercentChange1d:\n typeof pricePercentChange1d === 'number' &&\n Number.isFinite(pricePercentChange1d)\n ? pricePercentChange1d\n : 0,\n };\n};\n\n/**\n * Enablement lookup optimized:\n * - no array allocations\n * - early returns\n *\n * @param map - Enabled network map (namespace -> storageKey -> boolean)\n * @param id - Chain id as Hex or CAIP-2\n * @returns True if the chain is enabled or map is undefined\n */\nconst isChainEnabledByMap = (\n map: EnabledNetworkMap,\n id: Hex | CaipChainId,\n): boolean => {\n if (!map) {\n return true;\n }\n\n if (isStrictHexString(id)) {\n return Boolean(map[KnownCaipNamespace.Eip155]?.[id]);\n }\n\n const { namespace, reference } = parseCaipChainId(id);\n const namespaceMap = map[namespace];\n if (!namespaceMap) {\n return false;\n }\n\n // exact CAIP-2 id / reference\n if (namespaceMap[id] || namespaceMap[reference]) {\n return true;\n }\n\n // EVM: hex storage key\n if (namespace === KnownCaipNamespace.Eip155) {\n try {\n const storageKey = toHex(reference);\n return Boolean(namespaceMap[storageKey]);\n } catch {\n const parsedRef = Number.parseInt(reference, 10);\n if (Number.isFinite(parsedRef)) {\n return Boolean(namespaceMap[`0x${parsedRef.toString(16)}`]);\n }\n }\n }\n\n return false;\n};\n\n/**\n * Cache parsing results per assetId to avoid parseCaipAssetType/parseCaipChainId\n * being called multiple times across filtering + grouping + pricing.\n */\ntype AssetParseInfo = {\n chainId: CaipChainId;\n isEvm: boolean;\n};\n\nconst makeAssetInfoCache = (): Map<Caip19AssetId, AssetParseInfo> =>\n new Map<Caip19AssetId, AssetParseInfo>();\n\nconst getAssetInfo = (\n cache: Map<Caip19AssetId, AssetParseInfo>,\n assetId: Caip19AssetId,\n): AssetParseInfo => {\n const cached = cache.get(assetId);\n if (cached) {\n return cached;\n }\n const { chainId } = parseCaipAssetType(assetId);\n const { namespace } = parseCaipChainId(chainId);\n const info = { chainId, isEvm: namespace === KnownCaipNamespace.Eip155 };\n cache.set(assetId, info);\n return info;\n};\n\n/**\n * Merge across accounts without building per-account entries arrays.\n * Uses BigNumber for amount accumulation to avoid float precision loss.\n */\ntype AggRow = {\n amount: BigNumberJS;\n decimals?: number;\n symbol?: string;\n name?: string;\n};\n\nfunction mergeBalancesIntoMap(args: {\n out: Map<Caip19AssetId, AggRow>;\n accountBalances: Record<Caip19AssetId, AssetBalance>;\n metadata: Record<Caip19AssetId, AssetMetadata>;\n assetPreferences?: Record<Caip19AssetId, AssetPreferences>;\n enabledNetworkMap?: EnabledNetworkMap;\n assetInfoCache: Map<Caip19AssetId, AssetParseInfo>;\n}): void {\n const {\n out,\n accountBalances,\n metadata,\n assetPreferences,\n enabledNetworkMap,\n assetInfoCache,\n } = args;\n\n for (const assetId in accountBalances) {\n if (!Object.prototype.hasOwnProperty.call(accountBalances, assetId)) {\n continue;\n }\n const typedAssetId = assetId as Caip19AssetId;\n\n if (assetPreferences?.[typedAssetId]?.hidden) {\n continue;\n }\n\n const info = getAssetInfo(assetInfoCache, typedAssetId);\n if (!isChainEnabledByMap(enabledNetworkMap, info.chainId)) {\n continue;\n }\n\n const amountStr = getAmountFromBalance(accountBalances[typedAssetId]);\n let amountBn = toBigNumberOrZero(amountStr);\n const meta = metadata[typedAssetId];\n if (meta?.decimals !== undefined) {\n amountBn = scaleToHumanIfRaw(amountBn, meta.decimals);\n }\n if (amountBn.isZero()) {\n continue; // skip zeros early to reduce map pressure\n }\n\n const existing = out.get(typedAssetId);\n if (existing) {\n existing.amount = existing.amount.plus(amountBn);\n continue;\n }\n\n out.set(typedAssetId, {\n amount: amountBn,\n decimals: meta?.decimals,\n symbol: meta?.symbol,\n name: meta?.name,\n });\n }\n}\n\nexport function getGroupIdForAccount(\n accountTreeState: AccountTreeControllerState,\n accountId: AccountId,\n): string | undefined {\n const wallets = accountTreeState.accountTree?.wallets;\n if (!wallets) {\n return undefined;\n }\n for (const wallet of Object.values(wallets)) {\n for (const [groupId, group] of Object.entries(wallet?.groups ?? {})) {\n const accounts = group?.accounts;\n if (Array.isArray(accounts) && accounts.includes(accountId)) {\n return groupId;\n }\n }\n }\n return undefined;\n}\n\nexport function getInternalAccountsForGroup(\n accountTreeState: AccountTreeControllerState,\n accountsById: AccountsById,\n groupId: string,\n): InternalAccount[] {\n const wallets = accountTreeState.accountTree?.wallets;\n if (!wallets) {\n return [];\n }\n type GroupWithAccounts = { accounts?: AccountId[] };\n const getGroups = (\n walletItem: (typeof wallets)[keyof typeof wallets],\n ): Record<string, GroupWithAccounts> =>\n (walletItem?.groups ?? {}) as Record<string, GroupWithAccounts>;\n\n for (const wallet of Object.values(wallets)) {\n const group = getGroups(wallet)[groupId];\n if (!group?.accounts) {\n continue;\n }\n const result: InternalAccount[] = [];\n for (const id of group.accounts) {\n const acct = accountsById[id];\n if (acct) {\n result.push(acct);\n }\n }\n return result;\n }\n return [];\n}\n\nfunction getAccountIdsInSameGroup(\n accountTreeState: AccountTreeControllerState,\n selectedAccountId: AccountId,\n): AccountId[] | undefined {\n const groupId = getGroupIdForAccount(accountTreeState, selectedAccountId);\n if (!groupId) {\n return undefined;\n }\n const wallets = accountTreeState.accountTree?.wallets;\n if (!wallets) {\n return undefined;\n }\n type GroupWithAccounts = { accounts?: AccountId[] };\n const getGroups = (\n walletItem: (typeof wallets)[keyof typeof wallets],\n ): Record<string, GroupWithAccounts> =>\n (walletItem?.groups ?? {}) as Record<string, GroupWithAccounts>;\n\n for (const wallet of Object.values(wallets)) {\n const group = getGroups(wallet)[groupId];\n if (group?.accounts) {\n return [...group.accounts];\n }\n }\n return undefined;\n}\n\nfunction resolveAccountsToAggregate(args: {\n selectedInternalAccount: InternalAccount;\n accountTreeState?: AccountTreeControllerState;\n internalAccountsOrAccountIds?: InternalAccount[] | AccountId[];\n accountsById?: AccountsById;\n}): AccountLike[] {\n const {\n selectedInternalAccount,\n accountTreeState,\n internalAccountsOrAccountIds,\n accountsById,\n } = args;\n\n if (internalAccountsOrAccountIds && internalAccountsOrAccountIds.length > 0) {\n const first = internalAccountsOrAccountIds[0];\n if (typeof first === 'string') {\n const ids = internalAccountsOrAccountIds as AccountId[];\n const out: AccountLike[] = new Array(ids.length);\n for (let i = 0; i < ids.length; i++) {\n out[i] = { id: ids[i] };\n }\n return out;\n }\n return internalAccountsOrAccountIds as InternalAccount[];\n }\n\n if (\n accountTreeState &&\n accountsById &&\n Object.keys(accountsById).length > 0\n ) {\n const groupId = getGroupIdForAccount(\n accountTreeState,\n selectedInternalAccount.id,\n );\n if (groupId) {\n const groupAccounts = getInternalAccountsForGroup(\n accountTreeState,\n accountsById,\n groupId,\n );\n if (groupAccounts.length > 0) {\n return groupAccounts;\n }\n }\n }\n\n if (accountTreeState) {\n const ids = getAccountIdsInSameGroup(\n accountTreeState,\n selectedInternalAccount.id,\n );\n if (ids && ids.length > 0) {\n const out: AccountLike[] = new Array(ids.length);\n for (let i = 0; i < ids.length; i++) {\n out[i] = { id: ids[i] };\n }\n return out;\n }\n }\n\n return [selectedInternalAccount];\n}\n\nexport function getAggregatedBalanceForAccount(\n state: AssetsControllerState,\n selectedInternalAccount: InternalAccount,\n enabledNetworkMap?: EnabledNetworkMap,\n accountTreeState?: AccountTreeControllerState,\n internalAccountsOrAccountIds?: InternalAccount[] | AccountId[],\n accountsById?: AccountsById,\n trace?: TraceCallback,\n): AggregatedBalanceForAccount {\n const startTime = trace ? performance.now() : 0;\n const { assetsBalance, assetsInfo, assetPreferences, assetsPrice } = state;\n\n const metadata = (assetsInfo ?? {}) as Record<Caip19AssetId, AssetMetadata>;\n\n const hasPrices =\n Boolean(assetsPrice) &&\n ((): boolean => {\n for (const key in assetsPrice) {\n if (Object.prototype.hasOwnProperty.call(assetsPrice, key)) {\n return true;\n }\n }\n return false;\n })();\n\n const accountsToAggregate = resolveAccountsToAggregate({\n selectedInternalAccount,\n accountTreeState,\n internalAccountsOrAccountIds,\n accountsById,\n });\n\n const assetInfoCache = makeAssetInfoCache();\n const merged = new Map<Caip19AssetId, AggRow>();\n\n for (const account of accountsToAggregate) {\n const accountId = account.id;\n const accountBalances =\n assetsBalance?.[accountId] ?? ({} as Record<Caip19AssetId, AssetBalance>);\n\n mergeBalancesIntoMap({\n out: merged,\n accountBalances,\n metadata,\n assetPreferences,\n enabledNetworkMap,\n assetInfoCache,\n });\n }\n\n // Materialize entries once\n const entries: AggregatedBalanceEntry[] = new Array(merged.size);\n let idx = 0;\n\n // If prices exist, compute totals in a single pass over merged.\n let totalBalanceInFiat = 0;\n let weightedNumerator = 0;\n\n for (const [assetId, row] of merged.entries()) {\n const { amount } = row;\n const entry: AggregatedBalanceEntry = {\n assetId,\n amount: amount.toFixed(),\n ...(row.decimals === undefined ? {} : { decimals: row.decimals }),\n ...(row.symbol === undefined ? {} : { symbol: row.symbol }),\n ...(row.name === undefined ? {} : { name: row.name }),\n };\n entries[idx] = entry;\n idx += 1;\n\n if (hasPrices) {\n const { price, pricePercentChange1d } = getPriceDatumFast(\n assetsPrice,\n assetId,\n );\n const contribution = amount.multipliedBy(price).toNumber();\n if (contribution > 0) {\n totalBalanceInFiat += contribution;\n weightedNumerator += contribution * pricePercentChange1d;\n }\n }\n }\n\n if (trace) {\n const durationMs = performance.now() - startTime;\n const uniqueNetworks = new Set<CaipChainId>();\n for (const assetId of merged.keys()) {\n const info = getAssetInfo(assetInfoCache, assetId);\n uniqueNetworks.add(info.chainId);\n }\n trace(\n {\n name: TRACE_AGGREGATED_BALANCE_SELECTOR,\n data: {\n duration_ms: durationMs,\n asset_count: merged.size,\n network_count: uniqueNetworks.size,\n account_count: accountsToAggregate.length,\n },\n tags: { controller: 'AssetsController' },\n },\n () => undefined,\n ).catch(() => {\n // Telemetry failure must not break.\n });\n }\n\n if (hasPrices) {\n const pricePercentChange1d =\n totalBalanceInFiat > 0 ? weightedNumerator / totalBalanceInFiat : 0;\n return {\n entries,\n totalBalanceInFiat,\n pricePercentChange1d,\n };\n }\n\n return { entries };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"balance.mjs","sourceRoot":"","sources":["../../src/selectors/balance.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccountTreeControllerState } from '@metamask/account-tree-controller';\nimport { toHex } from '@metamask/controller-utils';\nimport type { TraceCallback } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { CaipChainId, Hex } from '@metamask/utils';\nimport {\n KnownCaipNamespace,\n isStrictHexString,\n parseCaipAssetType,\n parseCaipChainId,\n} from '@metamask/utils';\nimport BigNumberJS from 'bignumber.js';\n\nimport type { AssetsControllerState } from '../AssetsController';\nimport type {\n AccountId,\n AssetBalance,\n AssetMetadata,\n AssetPreferences,\n Caip19AssetId,\n} from '../types';\n\n// ============================================================================\n// TRACE NAMES — used in Sentry spans (search these strings in Discover)\n// ============================================================================\nconst TRACE_AGGREGATED_BALANCE_SELECTOR = 'AggregatedBalanceSelector';\n\nexport type EnabledNetworkMap =\n | Record<string, Record<string, boolean>>\n | undefined;\n\nexport type AggregatedBalanceEntry = {\n assetId: Caip19AssetId;\n amount: string;\n decimals?: number;\n symbol?: string;\n name?: string;\n};\n\nexport type AggregatedBalanceForAccount = {\n entries: AggregatedBalanceEntry[];\n totalBalanceInFiat?: number;\n pricePercentChange1d?: number;\n previousTotalInFiat?: number;\n};\n\ntype AccountLike = { id: AccountId };\nexport type AccountsById = Record<AccountId, InternalAccount>;\n\ntype PriceDatum = { price: number; pricePercentChange1d: number };\n\nconst ZERO_PRICE: PriceDatum = { price: 0, pricePercentChange1d: 0 };\n\nconst getAmountFromBalance = (balance: AssetBalance): string => {\n if (typeof balance === 'object' && balance !== null && 'amount' in balance) {\n const { amount } = balance as { amount?: unknown };\n return typeof amount === 'string' ? amount : '0';\n }\n return '0';\n};\n\nconst toBigNumberOrZero = (value: string): BigNumberJS => {\n const parsed = new BigNumberJS(value);\n return parsed.isNaN() || !parsed.isFinite() ? new BigNumberJS(0) : parsed;\n};\n\n/**\n * When state contains raw base units (e.g. SPORE \"16201541034.639288184\" with 9 decimals),\n * scale to human (16.2) before pricing. Only scale when amount >= 10^decimals so we don't\n * double-scale amounts that are already human (e.g. \"1\", \"0.227285\").\n *\n * @param amountRaw - Amount from state (may be raw or human).\n * @param decimals - Token decimals from metadata.\n * @returns Human amount for display and fiat (amountRaw or amountRaw / 10^decimals).\n */\nconst scaleToHumanIfRaw = (\n amountRaw: BigNumberJS,\n decimals: number,\n): BigNumberJS => {\n if (decimals <= 0) {\n return amountRaw;\n }\n const scale = new BigNumberJS(10).pow(decimals);\n if (amountRaw.lt(scale)) {\n return amountRaw;\n }\n return amountRaw.dividedBy(scale);\n};\n\nconst getPriceDatumFast = (\n assetsPrice: AssetsControllerState['assetsPrice'] | undefined,\n assetId: Caip19AssetId,\n): PriceDatum => {\n const raw = assetsPrice?.[assetId];\n if (!raw || typeof raw !== 'object') {\n return ZERO_PRICE;\n }\n const { price, pricePercentChange1d } = raw as {\n price?: unknown;\n pricePercentChange1d?: unknown;\n };\n return {\n price: typeof price === 'number' && Number.isFinite(price) ? price : 0,\n pricePercentChange1d:\n typeof pricePercentChange1d === 'number' &&\n Number.isFinite(pricePercentChange1d)\n ? pricePercentChange1d\n : 0,\n };\n};\n\n/**\n * Enablement lookup optimized:\n * - no array allocations\n * - early returns\n *\n * @param map - Enabled network map (namespace -> storageKey -> boolean)\n * @param id - Chain id as Hex or CAIP-2\n * @returns True if the chain is enabled or map is undefined\n */\nconst isChainEnabledByMap = (\n map: EnabledNetworkMap,\n id: Hex | CaipChainId,\n): boolean => {\n if (!map) {\n return true;\n }\n\n if (isStrictHexString(id)) {\n return Boolean(map[KnownCaipNamespace.Eip155]?.[id]);\n }\n\n const { namespace, reference } = parseCaipChainId(id);\n const namespaceMap = map[namespace];\n if (!namespaceMap) {\n return false;\n }\n\n // exact CAIP-2 id / reference\n if (namespaceMap[id] || namespaceMap[reference]) {\n return true;\n }\n\n // EVM: hex storage key\n if (namespace === KnownCaipNamespace.Eip155) {\n try {\n const storageKey = toHex(reference);\n return Boolean(namespaceMap[storageKey]);\n } catch {\n const parsedRef = Number.parseInt(reference, 10);\n if (Number.isFinite(parsedRef)) {\n return Boolean(namespaceMap[`0x${parsedRef.toString(16)}`]);\n }\n }\n }\n\n return false;\n};\n\n/**\n * Cache parsing results per assetId to avoid parseCaipAssetType/parseCaipChainId\n * being called multiple times across filtering + grouping + pricing.\n */\ntype AssetParseInfo = {\n chainId: CaipChainId;\n isEvm: boolean;\n};\n\nconst makeAssetInfoCache = (): Map<Caip19AssetId, AssetParseInfo> =>\n new Map<Caip19AssetId, AssetParseInfo>();\n\nconst getAssetInfo = (\n cache: Map<Caip19AssetId, AssetParseInfo>,\n assetId: Caip19AssetId,\n): AssetParseInfo => {\n const cached = cache.get(assetId);\n if (cached) {\n return cached;\n }\n const { chainId } = parseCaipAssetType(assetId);\n const { namespace } = parseCaipChainId(chainId);\n const info = { chainId, isEvm: namespace === KnownCaipNamespace.Eip155 };\n cache.set(assetId, info);\n return info;\n};\n\n/**\n * Merge across accounts without building per-account entries arrays.\n * Uses BigNumber for amount accumulation to avoid float precision loss.\n */\ntype AggRow = {\n amount: BigNumberJS;\n decimals?: number;\n symbol?: string;\n name?: string;\n};\n\nfunction mergeBalancesIntoMap(args: {\n out: Map<Caip19AssetId, AggRow>;\n accountBalances: Record<Caip19AssetId, AssetBalance>;\n metadata: Record<Caip19AssetId, AssetMetadata>;\n assetPreferences?: Record<Caip19AssetId, AssetPreferences>;\n enabledNetworkMap?: EnabledNetworkMap;\n assetInfoCache: Map<Caip19AssetId, AssetParseInfo>;\n}): void {\n const {\n out,\n accountBalances,\n metadata,\n assetPreferences,\n enabledNetworkMap,\n assetInfoCache,\n } = args;\n\n for (const assetId in accountBalances) {\n if (!Object.prototype.hasOwnProperty.call(accountBalances, assetId)) {\n continue;\n }\n const typedAssetId = assetId as Caip19AssetId;\n\n if (assetPreferences?.[typedAssetId]?.hidden) {\n continue;\n }\n\n const info = getAssetInfo(assetInfoCache, typedAssetId);\n if (!isChainEnabledByMap(enabledNetworkMap, info.chainId)) {\n continue;\n }\n\n const amountStr = getAmountFromBalance(accountBalances[typedAssetId]);\n let amountBn = toBigNumberOrZero(amountStr);\n const meta = metadata[typedAssetId];\n if (meta?.decimals !== undefined) {\n amountBn = scaleToHumanIfRaw(amountBn, meta.decimals);\n }\n if (amountBn.isZero()) {\n continue; // skip zeros early to reduce map pressure\n }\n\n const existing = out.get(typedAssetId);\n if (existing) {\n existing.amount = existing.amount.plus(amountBn);\n continue;\n }\n\n out.set(typedAssetId, {\n amount: amountBn,\n decimals: meta?.decimals,\n symbol: meta?.symbol,\n name: meta?.name,\n });\n }\n}\n\nexport function getGroupIdForAccount(\n accountTreeState: AccountTreeControllerState,\n accountId: AccountId,\n): string | undefined {\n const wallets = accountTreeState.accountTree?.wallets;\n if (!wallets) {\n return undefined;\n }\n for (const wallet of Object.values(wallets)) {\n for (const [groupId, group] of Object.entries(wallet?.groups ?? {})) {\n const accounts = group?.accounts;\n if (Array.isArray(accounts) && accounts.includes(accountId)) {\n return groupId;\n }\n }\n }\n return undefined;\n}\n\nexport function getInternalAccountsForGroup(\n accountTreeState: AccountTreeControllerState,\n accountsById: AccountsById,\n groupId: string,\n): InternalAccount[] {\n const wallets = accountTreeState.accountTree?.wallets;\n if (!wallets) {\n return [];\n }\n type GroupWithAccounts = { accounts?: AccountId[] };\n const getGroups = (\n walletItem: (typeof wallets)[keyof typeof wallets],\n ): Record<string, GroupWithAccounts> =>\n (walletItem?.groups ?? {}) as Record<string, GroupWithAccounts>;\n\n for (const wallet of Object.values(wallets)) {\n const group = getGroups(wallet)[groupId];\n if (!group?.accounts) {\n continue;\n }\n const result: InternalAccount[] = [];\n for (const id of group.accounts) {\n const acct = accountsById[id];\n if (acct) {\n result.push(acct);\n }\n }\n return result;\n }\n return [];\n}\n\nfunction getAccountIdsInSameGroup(\n accountTreeState: AccountTreeControllerState,\n selectedAccountId: AccountId,\n): AccountId[] | undefined {\n const groupId = getGroupIdForAccount(accountTreeState, selectedAccountId);\n if (!groupId) {\n return undefined;\n }\n const wallets = accountTreeState.accountTree?.wallets;\n if (!wallets) {\n return undefined;\n }\n type GroupWithAccounts = { accounts?: AccountId[] };\n const getGroups = (\n walletItem: (typeof wallets)[keyof typeof wallets],\n ): Record<string, GroupWithAccounts> =>\n (walletItem?.groups ?? {}) as Record<string, GroupWithAccounts>;\n\n for (const wallet of Object.values(wallets)) {\n const group = getGroups(wallet)[groupId];\n if (group?.accounts) {\n return [...group.accounts];\n }\n }\n return undefined;\n}\n\nfunction resolveAccountsToAggregate(args: {\n selectedInternalAccount: InternalAccount;\n accountTreeState?: AccountTreeControllerState;\n internalAccountsOrAccountIds?: InternalAccount[] | AccountId[];\n accountsById?: AccountsById;\n}): AccountLike[] {\n const {\n selectedInternalAccount,\n accountTreeState,\n internalAccountsOrAccountIds,\n accountsById,\n } = args;\n\n if (internalAccountsOrAccountIds && internalAccountsOrAccountIds.length > 0) {\n const first = internalAccountsOrAccountIds[0];\n if (typeof first === 'string') {\n const ids = internalAccountsOrAccountIds as AccountId[];\n const out: AccountLike[] = new Array(ids.length);\n for (let i = 0; i < ids.length; i++) {\n out[i] = { id: ids[i] };\n }\n return out;\n }\n return internalAccountsOrAccountIds as InternalAccount[];\n }\n\n if (\n accountTreeState &&\n accountsById &&\n Object.keys(accountsById).length > 0\n ) {\n const groupId = getGroupIdForAccount(\n accountTreeState,\n selectedInternalAccount.id,\n );\n if (groupId) {\n const groupAccounts = getInternalAccountsForGroup(\n accountTreeState,\n accountsById,\n groupId,\n );\n if (groupAccounts.length > 0) {\n return groupAccounts;\n }\n }\n }\n\n if (accountTreeState) {\n const ids = getAccountIdsInSameGroup(\n accountTreeState,\n selectedInternalAccount.id,\n );\n if (ids && ids.length > 0) {\n const out: AccountLike[] = new Array(ids.length);\n for (let i = 0; i < ids.length; i++) {\n out[i] = { id: ids[i] };\n }\n return out;\n }\n }\n\n return [selectedInternalAccount];\n}\n\n/**\n * Get account ids that belong to a group, reading directly from the account\n * tree. Unlike {@link getInternalAccountsForGroup}, this does not require an\n * `accountsById` lookup map, which makes it convenient for balance aggregation\n * where only the ids are needed.\n *\n * @param accountTreeState - AccountTreeController state.\n * @param groupId - The account group id to look up.\n * @returns The list of account ids in the group, or an empty array.\n */\nexport function getAccountIdsForGroup(\n accountTreeState: AccountTreeControllerState,\n groupId: string,\n): AccountId[] {\n const wallets = accountTreeState.accountTree?.wallets ?? {};\n type GroupWithAccounts = { accounts?: AccountId[] };\n for (const wallet of Object.values(wallets)) {\n const groups = (wallet?.groups ?? {}) as Record<string, GroupWithAccounts>;\n const group = groups[groupId];\n if (group?.accounts) {\n return [...group.accounts];\n }\n }\n return [];\n}\n\n/**\n * Aggregate balances across an explicit list of accounts.\n *\n * This is the core aggregation engine shared by the account- and group-level\n * selectors. It intentionally takes a fully-resolved list of accounts so that\n * callers control account resolution and no \"selected account\" placeholder is\n * required.\n *\n * @param state - AssetsController state slice.\n * @param accounts - Accounts whose balances should be aggregated.\n * @param enabledNetworkMap - Optional map of enabled networks keyed by namespace.\n * @param trace - Optional trace callback for telemetry spans.\n * @returns Aggregated balance entries plus fiat totals when prices are present.\n */\nfunction aggregateBalances(\n state: AssetsControllerState,\n accounts: AccountLike[],\n enabledNetworkMap?: EnabledNetworkMap,\n trace?: TraceCallback,\n): AggregatedBalanceForAccount {\n const startTime = trace ? performance.now() : 0;\n const { assetsBalance, assetsInfo, assetPreferences, assetsPrice } = state;\n\n const metadata = (assetsInfo ?? {}) as Record<Caip19AssetId, AssetMetadata>;\n\n const hasPrices =\n Boolean(assetsPrice) &&\n ((): boolean => {\n for (const key in assetsPrice) {\n if (Object.prototype.hasOwnProperty.call(assetsPrice, key)) {\n return true;\n }\n }\n return false;\n })();\n\n const accountsToAggregate = accounts;\n\n const assetInfoCache = makeAssetInfoCache();\n const merged = new Map<Caip19AssetId, AggRow>();\n\n for (const account of accountsToAggregate) {\n const accountId = account.id;\n const accountBalances =\n assetsBalance?.[accountId] ?? ({} as Record<Caip19AssetId, AssetBalance>);\n\n mergeBalancesIntoMap({\n out: merged,\n accountBalances,\n metadata,\n assetPreferences,\n enabledNetworkMap,\n assetInfoCache,\n });\n }\n\n // Materialize entries once\n const entries: AggregatedBalanceEntry[] = new Array(merged.size);\n let idx = 0;\n\n // If prices exist, compute totals in a single pass over merged.\n let totalBalanceInFiat = 0;\n let previousTotalInFiat = 0;\n let weightedNumerator = 0;\n\n for (const [assetId, row] of merged.entries()) {\n const { amount } = row;\n const entry: AggregatedBalanceEntry = {\n assetId,\n amount: amount.toFixed(),\n ...(row.decimals === undefined ? {} : { decimals: row.decimals }),\n ...(row.symbol === undefined ? {} : { symbol: row.symbol }),\n ...(row.name === undefined ? {} : { name: row.name }),\n };\n entries[idx] = entry;\n idx += 1;\n\n if (hasPrices) {\n const { price, pricePercentChange1d } = getPriceDatumFast(\n assetsPrice,\n assetId,\n );\n const contribution = amount.multipliedBy(price).toNumber();\n if (contribution > 0) {\n totalBalanceInFiat += contribution;\n weightedNumerator += contribution * pricePercentChange1d;\n\n // A -100% move makes the denominator zero; treat it as flat so the\n // previous total never goes to infinity.\n const denom = Number((1 + pricePercentChange1d / 100).toFixed(8));\n previousTotalInFiat +=\n denom === 0 ? contribution : contribution / denom;\n }\n }\n }\n\n if (trace) {\n const durationMs = performance.now() - startTime;\n const uniqueNetworks = new Set<CaipChainId>();\n for (const assetId of merged.keys()) {\n const info = getAssetInfo(assetInfoCache, assetId);\n uniqueNetworks.add(info.chainId);\n }\n trace(\n {\n name: TRACE_AGGREGATED_BALANCE_SELECTOR,\n data: {\n duration_ms: durationMs,\n asset_count: merged.size,\n network_count: uniqueNetworks.size,\n account_count: accountsToAggregate.length,\n },\n tags: { controller: 'AssetsController' },\n },\n () => undefined,\n ).catch(() => {\n // Telemetry failure must not break.\n });\n }\n\n if (hasPrices) {\n const pricePercentChange1d =\n totalBalanceInFiat > 0 ? weightedNumerator / totalBalanceInFiat : 0;\n return {\n entries,\n totalBalanceInFiat,\n pricePercentChange1d,\n previousTotalInFiat,\n };\n }\n\n return { entries };\n}\n\n/**\n * Aggregate balances for an explicit list of account ids.\n *\n * Prefer this over {@link getAggregatedBalanceForAccount} when the set of\n * accounts to aggregate is already known (e.g. all accounts in a group), since\n * it does not require a \"selected account\" and therefore avoids the brittle\n * account-resolution heuristics.\n *\n * @param state - AssetsController state slice.\n * @param accountIds - Account ids whose balances should be aggregated.\n * @param enabledNetworkMap - Optional map of enabled networks keyed by namespace.\n * @param trace - Optional trace callback for telemetry spans.\n * @returns Aggregated balance entries plus fiat totals when prices are present.\n */\nexport function getAggregatedBalanceForAccountIds(\n state: AssetsControllerState,\n accountIds: AccountId[],\n enabledNetworkMap?: EnabledNetworkMap,\n trace?: TraceCallback,\n): AggregatedBalanceForAccount {\n const accounts: AccountLike[] = accountIds.map((id) => ({ id }));\n return aggregateBalances(state, accounts, enabledNetworkMap, trace);\n}\n\nexport function getAggregatedBalanceForAccount(\n state: AssetsControllerState,\n selectedInternalAccount: InternalAccount,\n enabledNetworkMap?: EnabledNetworkMap,\n accountTreeState?: AccountTreeControllerState,\n internalAccountsOrAccountIds?: InternalAccount[] | AccountId[],\n accountsById?: AccountsById,\n trace?: TraceCallback,\n): AggregatedBalanceForAccount {\n const accountsToAggregate = resolveAccountsToAggregate({\n selectedInternalAccount,\n accountTreeState,\n internalAccountsOrAccountIds,\n accountsById,\n });\n return aggregateBalances(\n state,\n accountsToAggregate,\n enabledNetworkMap,\n trace,\n );\n}\n\n// ============================================================================\n// WALLET- AND GROUP-LEVEL BALANCE CALCULATIONS\n// ============================================================================\n\n/**\n * Default user currency used when {@link AssetsControllerState.selectedCurrency}\n * is not set.\n */\nconst DEFAULT_USER_CURRENCY = 'usd';\n\nexport type AccountGroupBalance = {\n walletId: string;\n groupId: string;\n totalBalanceInUserCurrency: number;\n userCurrency: string;\n};\n\nexport type WalletBalance = {\n walletId: string;\n groups: Record<string, AccountGroupBalance>;\n totalBalanceInUserCurrency: number;\n userCurrency: string;\n};\n\nexport type AllWalletsBalance = {\n wallets: Record<string, WalletBalance>;\n totalBalanceInUserCurrency: number;\n userCurrency: string;\n};\n\nexport type BalanceChangePeriod = '1d' | '7d' | '30d';\n\nexport type BalanceChangeResult = {\n period: BalanceChangePeriod;\n currentTotalInUserCurrency: number;\n previousTotalInUserCurrency: number;\n amountChangeInUserCurrency: number;\n percentChange: number;\n userCurrency: string;\n};\n\n/**\n * Resolve the user currency for a balance calculation, falling back to a\n * sensible default when the controller has no selected currency.\n *\n * @param assetsControllerState - AssetsController state slice.\n * @returns The user currency code.\n */\nfunction getUserCurrency(assetsControllerState: AssetsControllerState): string {\n return assetsControllerState.selectedCurrency ?? DEFAULT_USER_CURRENCY;\n}\n\n/**\n * Resolve the current and previous totals for a change calculation.\n *\n * The AssetsController state only exposes a 1d price change, so non-`1d`\n * periods produce a zeroed change (previous equals current).\n *\n * @param totalBalanceInFiat - Aggregated current balance in user currency.\n * @param previousTotalInFiat - Aggregated prior balance summed per asset.\n * @param period - Period to compute the change for.\n * @returns The current and previous totals in user currency.\n */\nfunction getCurrentAndPrevious(\n totalBalanceInFiat: number,\n previousTotalInFiat: number,\n period: BalanceChangePeriod,\n): { current: number; previous: number } {\n const current = totalBalanceInFiat;\n const previous = period === '1d' ? previousTotalInFiat : current;\n return { current, previous };\n}\n\n/**\n * Build a {@link BalanceChangeResult} from current/previous totals.\n *\n * @param current - Current total in user currency.\n * @param previous - Previous total in user currency.\n * @param period - Period the change was computed for.\n * @param userCurrency - User currency code.\n * @returns The change result with delta and percent change.\n */\nfunction buildBalanceChangeResult(\n current: number,\n previous: number,\n period: BalanceChangePeriod,\n userCurrency: string,\n): BalanceChangeResult {\n const amountChange = current - previous;\n const percentChange = previous === 0 ? 0 : (amountChange / previous) * 100;\n return {\n period,\n currentTotalInUserCurrency: Number(current.toFixed(8)),\n previousTotalInUserCurrency: Number(previous.toFixed(8)),\n amountChangeInUserCurrency: Number(amountChange.toFixed(8)),\n percentChange: Number(percentChange.toFixed(8)),\n userCurrency,\n };\n}\n\n/**\n * Calculate aggregated balances for all wallets and groups.\n *\n * Mirrors the legacy `@metamask/assets-controllers` `calculateBalanceForAllWallets`\n * output shape, but sources every group total from the unified AssetsController\n * state via {@link getAggregatedBalanceForAccountIds}. The account tree is walked\n * to aggregate each group individually.\n *\n * @param assetsControllerState - AssetsController state slice.\n * @param accountTreeState - AccountTreeController state.\n * @param enabledNetworkMap - Map of enabled networks keyed by namespace.\n * @param trace - Optional trace callback forwarded to the aggregation selector.\n * @returns Aggregated balances for all wallets and groups.\n */\nexport function calculateBalanceForAllWallets(\n assetsControllerState: AssetsControllerState,\n accountTreeState: AccountTreeControllerState,\n enabledNetworkMap?: EnabledNetworkMap,\n trace?: TraceCallback,\n): AllWalletsBalance {\n const userCurrency = getUserCurrency(assetsControllerState);\n const wallets: AllWalletsBalance['wallets'] = {};\n let totalBalanceInUserCurrency = 0;\n\n type WalletWithGroups = { groups?: Record<string, unknown> };\n for (const [walletId, wallet] of Object.entries(\n accountTreeState.accountTree?.wallets ?? {},\n )) {\n const walletBalance: WalletBalance = {\n walletId,\n groups: {},\n totalBalanceInUserCurrency: 0,\n userCurrency,\n };\n\n const groups = (wallet as WalletWithGroups)?.groups ?? {};\n for (const groupId of Object.keys(groups)) {\n const accountIds = getAccountIdsForGroup(accountTreeState, groupId);\n const { totalBalanceInFiat = 0 } = getAggregatedBalanceForAccountIds(\n assetsControllerState,\n accountIds,\n enabledNetworkMap,\n trace,\n );\n\n walletBalance.groups[groupId] = {\n walletId,\n groupId,\n totalBalanceInUserCurrency: totalBalanceInFiat,\n userCurrency,\n };\n walletBalance.totalBalanceInUserCurrency += totalBalanceInFiat;\n }\n\n wallets[walletId] = walletBalance;\n totalBalanceInUserCurrency += walletBalance.totalBalanceInUserCurrency;\n }\n\n return { wallets, totalBalanceInUserCurrency, userCurrency };\n}\n\n/**\n * Calculate the portfolio value change for a single account group and period.\n *\n * @param assetsControllerState - AssetsController state slice.\n * @param accountTreeState - AccountTreeController state.\n * @param groupId - Account group id to compute the change for.\n * @param period - Change period (`1d` | `7d` | `30d`).\n * @param enabledNetworkMap - Map of enabled networks keyed by namespace.\n * @param trace - Optional trace callback forwarded to the aggregation selector.\n * @returns The change result for the requested period.\n */\nexport function calculateBalanceChangeForAccountGroup(\n assetsControllerState: AssetsControllerState,\n accountTreeState: AccountTreeControllerState,\n groupId: string,\n period: BalanceChangePeriod,\n enabledNetworkMap?: EnabledNetworkMap,\n trace?: TraceCallback,\n): BalanceChangeResult {\n const userCurrency = getUserCurrency(assetsControllerState);\n const accountIds = getAccountIdsForGroup(accountTreeState, groupId);\n const { totalBalanceInFiat = 0, previousTotalInFiat = totalBalanceInFiat } =\n getAggregatedBalanceForAccountIds(\n assetsControllerState,\n accountIds,\n enabledNetworkMap,\n trace,\n );\n\n const { current, previous } = getCurrentAndPrevious(\n totalBalanceInFiat,\n previousTotalInFiat,\n period,\n );\n\n return buildBalanceChangeResult(current, previous, period, userCurrency);\n}\n"]}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
+
};
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
13
|
+
var _DedupingBatchFetcher_instances, _DedupingBatchFetcher_fetchBatch, _DedupingBatchFetcher_freshnessTtlMs, _DedupingBatchFetcher_fetchedAt, _DedupingBatchFetcher_inflight, _DedupingBatchFetcher_partition, _DedupingBatchFetcher_isFresh, _DedupingBatchFetcher_startBatchFetch, _DedupingBatchFetcher_joinInflight;
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.DedupingBatchFetcher = void 0;
|
|
16
|
+
/**
|
|
17
|
+
* Deduplicates batched fetches by key across two dimensions:
|
|
18
|
+
*
|
|
19
|
+
* 1. **Freshness TTL** — keys fetched within `freshnessTtlMs` are skipped
|
|
20
|
+
* entirely. Note the freshness window only starts once a fetch *completes*,
|
|
21
|
+
* so it does not cover requests that are still in flight.
|
|
22
|
+
* 2. **Inflight coalescing** — if a fetch is already in progress for a key,
|
|
23
|
+
* concurrent callers join the existing promise instead of issuing a new
|
|
24
|
+
* request. This covers the request-in-flight window the freshness TTL
|
|
25
|
+
* structurally cannot.
|
|
26
|
+
*
|
|
27
|
+
* Both layers are per-key, so a call for a partially-overlapping set of keys
|
|
28
|
+
* reuses the fresh/inflight keys and only fetches the genuinely-missing ones.
|
|
29
|
+
*
|
|
30
|
+
* The fetch is batched: all stale, not-inflight keys from a single `fetch()`
|
|
31
|
+
* call are passed to `fetchBatch` together, then split into per-key results so
|
|
32
|
+
* each key can be joined independently.
|
|
33
|
+
*/
|
|
34
|
+
class DedupingBatchFetcher {
|
|
35
|
+
constructor(options) {
|
|
36
|
+
_DedupingBatchFetcher_instances.add(this);
|
|
37
|
+
_DedupingBatchFetcher_fetchBatch.set(this, void 0);
|
|
38
|
+
_DedupingBatchFetcher_freshnessTtlMs.set(this, void 0);
|
|
39
|
+
/** Tracks the last successful fetch time per key (freshness gating). */
|
|
40
|
+
_DedupingBatchFetcher_fetchedAt.set(this, new Map());
|
|
41
|
+
/**
|
|
42
|
+
* Per-key inflight fetch promises. Each resolves to the value, or `undefined`
|
|
43
|
+
* if the batch failed or returned no data for that key.
|
|
44
|
+
*/
|
|
45
|
+
_DedupingBatchFetcher_inflight.set(this, new Map());
|
|
46
|
+
__classPrivateFieldSet(this, _DedupingBatchFetcher_fetchBatch, options.fetchBatch, "f");
|
|
47
|
+
__classPrivateFieldSet(this, _DedupingBatchFetcher_freshnessTtlMs, options.freshnessTtlMs, "f");
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Minimum age (ms) before a key is re-fetched.
|
|
51
|
+
*
|
|
52
|
+
* @returns The current freshness TTL in milliseconds.
|
|
53
|
+
*/
|
|
54
|
+
get freshnessTtlMs() {
|
|
55
|
+
return __classPrivateFieldGet(this, _DedupingBatchFetcher_freshnessTtlMs, "f");
|
|
56
|
+
}
|
|
57
|
+
set freshnessTtlMs(ms) {
|
|
58
|
+
__classPrivateFieldSet(this, _DedupingBatchFetcher_freshnessTtlMs, ms, "f");
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Fetch values for the given keys, deduplicating against fresh and inflight
|
|
62
|
+
* fetches.
|
|
63
|
+
*
|
|
64
|
+
* @param keys - The keys to fetch.
|
|
65
|
+
* @returns Values keyed by key. Only contains entries for keys that were
|
|
66
|
+
* actually fetched (or joined from inflight) and had a value.
|
|
67
|
+
*/
|
|
68
|
+
async fetch(keys) {
|
|
69
|
+
const { staleKeys, inflightKeys } = __classPrivateFieldGet(this, _DedupingBatchFetcher_instances, "m", _DedupingBatchFetcher_partition).call(this, keys);
|
|
70
|
+
if (staleKeys.length === 0 && inflightKeys.length === 0) {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
// Start a fetch for stale keys and join any fetches already in progress.
|
|
74
|
+
const batchPromise = staleKeys.length > 0 ? __classPrivateFieldGet(this, _DedupingBatchFetcher_instances, "m", _DedupingBatchFetcher_startBatchFetch).call(this, staleKeys) : undefined;
|
|
75
|
+
const values = await __classPrivateFieldGet(this, _DedupingBatchFetcher_instances, "m", _DedupingBatchFetcher_joinInflight).call(this, inflightKeys);
|
|
76
|
+
if (batchPromise) {
|
|
77
|
+
Object.assign(values, await batchPromise);
|
|
78
|
+
}
|
|
79
|
+
return values;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Clear the freshness cache, forcing the next fetch to re-request every key
|
|
83
|
+
* regardless of TTL. Does not affect inflight fetches.
|
|
84
|
+
*/
|
|
85
|
+
invalidate() {
|
|
86
|
+
__classPrivateFieldGet(this, _DedupingBatchFetcher_fetchedAt, "f").clear();
|
|
87
|
+
}
|
|
88
|
+
/** Clear all freshness and inflight state. */
|
|
89
|
+
destroy() {
|
|
90
|
+
__classPrivateFieldGet(this, _DedupingBatchFetcher_fetchedAt, "f").clear();
|
|
91
|
+
__classPrivateFieldGet(this, _DedupingBatchFetcher_inflight, "f").clear();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.DedupingBatchFetcher = DedupingBatchFetcher;
|
|
95
|
+
_DedupingBatchFetcher_fetchBatch = new WeakMap(), _DedupingBatchFetcher_freshnessTtlMs = new WeakMap(), _DedupingBatchFetcher_fetchedAt = new WeakMap(), _DedupingBatchFetcher_inflight = new WeakMap(), _DedupingBatchFetcher_instances = new WeakSet(), _DedupingBatchFetcher_partition = function _DedupingBatchFetcher_partition(keys) {
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
const staleKeys = [];
|
|
98
|
+
const inflightKeys = [];
|
|
99
|
+
for (const key of keys) {
|
|
100
|
+
if (__classPrivateFieldGet(this, _DedupingBatchFetcher_instances, "m", _DedupingBatchFetcher_isFresh).call(this, key, now)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (__classPrivateFieldGet(this, _DedupingBatchFetcher_inflight, "f").has(key)) {
|
|
104
|
+
inflightKeys.push(key);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
staleKeys.push(key);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { staleKeys, inflightKeys };
|
|
111
|
+
}, _DedupingBatchFetcher_isFresh = function _DedupingBatchFetcher_isFresh(key, now) {
|
|
112
|
+
const fetchedAt = __classPrivateFieldGet(this, _DedupingBatchFetcher_fetchedAt, "f").get(key);
|
|
113
|
+
return fetchedAt !== undefined && now - fetchedAt < __classPrivateFieldGet(this, _DedupingBatchFetcher_freshnessTtlMs, "f");
|
|
114
|
+
}, _DedupingBatchFetcher_startBatchFetch = function _DedupingBatchFetcher_startBatchFetch(staleKeys) {
|
|
115
|
+
const batchPromise = __classPrivateFieldGet(this, _DedupingBatchFetcher_fetchBatch, "f").call(this, staleKeys).then((values) => {
|
|
116
|
+
const fetchedAt = Date.now();
|
|
117
|
+
for (const key of staleKeys) {
|
|
118
|
+
__classPrivateFieldGet(this, _DedupingBatchFetcher_fetchedAt, "f").set(key, fetchedAt);
|
|
119
|
+
}
|
|
120
|
+
return values;
|
|
121
|
+
});
|
|
122
|
+
for (const key of staleKeys) {
|
|
123
|
+
const perKey = batchPromise.then((values) => values[key], () => undefined);
|
|
124
|
+
__classPrivateFieldGet(this, _DedupingBatchFetcher_inflight, "f").set(key, perKey);
|
|
125
|
+
}
|
|
126
|
+
// Clean up inflight entries once the batch settles (success or failure).
|
|
127
|
+
// Rejection is already surfaced to the caller via the returned batchPromise.
|
|
128
|
+
batchPromise
|
|
129
|
+
.finally(() => {
|
|
130
|
+
for (const key of staleKeys) {
|
|
131
|
+
__classPrivateFieldGet(this, _DedupingBatchFetcher_inflight, "f").delete(key);
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
.catch(() => undefined);
|
|
135
|
+
return batchPromise;
|
|
136
|
+
}, _DedupingBatchFetcher_joinInflight =
|
|
137
|
+
/**
|
|
138
|
+
* Join the inflight fetches for the given keys and collect their values. Keys
|
|
139
|
+
* whose inflight fetch produced no value are omitted.
|
|
140
|
+
*
|
|
141
|
+
* @param inflightKeys - Keys whose fetches are already in progress.
|
|
142
|
+
* @returns Values keyed by key.
|
|
143
|
+
*/
|
|
144
|
+
async function _DedupingBatchFetcher_joinInflight(inflightKeys) {
|
|
145
|
+
const values = {};
|
|
146
|
+
const results = await Promise.all(inflightKeys.map(async (key) => {
|
|
147
|
+
const value = await __classPrivateFieldGet(this, _DedupingBatchFetcher_inflight, "f").get(key);
|
|
148
|
+
return [key, value];
|
|
149
|
+
}));
|
|
150
|
+
for (const [key, value] of results) {
|
|
151
|
+
if (value !== undefined) {
|
|
152
|
+
values[key] = value;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return values;
|
|
156
|
+
};
|
|
157
|
+
//# sourceMappingURL=dedupingBatchFetcher.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedupingBatchFetcher.cjs","sourceRoot":"","sources":["../../src/utils/dedupingBatchFetcher.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Executes the underlying batched fetch for a set of keys. Only keys that have\n * a value need to be present in the returned record; keys the source had no\n * data for are simply omitted (they are still marked fresh — see\n * {@link DedupingBatchFetcher.fetch}).\n *\n * @param keys - The keys to fetch (already filtered to stale, not-inflight keys).\n * @returns The fetched values keyed by key.\n */\nexport type BatchFetchFn<Key extends string, Value> = (\n keys: Key[],\n) => Promise<Record<Key, Value>>;\n\nexport type DedupingBatchFetcherOptions<Key extends string, Value> = {\n /** Performs the actual batched fetch for stale, not-inflight keys. */\n fetchBatch: BatchFetchFn<Key, Value>;\n /**\n * Minimum age (ms) before a key is considered stale and re-fetched. Keys\n * fetched more recently than this are skipped entirely.\n */\n freshnessTtlMs: number;\n};\n\n/**\n * Deduplicates batched fetches by key across two dimensions:\n *\n * 1. **Freshness TTL** — keys fetched within `freshnessTtlMs` are skipped\n * entirely. Note the freshness window only starts once a fetch *completes*,\n * so it does not cover requests that are still in flight.\n * 2. **Inflight coalescing** — if a fetch is already in progress for a key,\n * concurrent callers join the existing promise instead of issuing a new\n * request. This covers the request-in-flight window the freshness TTL\n * structurally cannot.\n *\n * Both layers are per-key, so a call for a partially-overlapping set of keys\n * reuses the fresh/inflight keys and only fetches the genuinely-missing ones.\n *\n * The fetch is batched: all stale, not-inflight keys from a single `fetch()`\n * call are passed to `fetchBatch` together, then split into per-key results so\n * each key can be joined independently.\n */\nexport class DedupingBatchFetcher<Key extends string, Value> {\n readonly #fetchBatch: BatchFetchFn<Key, Value>;\n\n #freshnessTtlMs: number;\n\n /** Tracks the last successful fetch time per key (freshness gating). */\n readonly #fetchedAt = new Map<Key, number>();\n\n /**\n * Per-key inflight fetch promises. Each resolves to the value, or `undefined`\n * if the batch failed or returned no data for that key.\n */\n readonly #inflight = new Map<Key, Promise<Value | undefined>>();\n\n constructor(options: DedupingBatchFetcherOptions<Key, Value>) {\n this.#fetchBatch = options.fetchBatch;\n this.#freshnessTtlMs = options.freshnessTtlMs;\n }\n\n /**\n * Minimum age (ms) before a key is re-fetched.\n *\n * @returns The current freshness TTL in milliseconds.\n */\n get freshnessTtlMs(): number {\n return this.#freshnessTtlMs;\n }\n\n set freshnessTtlMs(ms: number) {\n this.#freshnessTtlMs = ms;\n }\n\n /**\n * Fetch values for the given keys, deduplicating against fresh and inflight\n * fetches.\n *\n * @param keys - The keys to fetch.\n * @returns Values keyed by key. Only contains entries for keys that were\n * actually fetched (or joined from inflight) and had a value.\n */\n async fetch(keys: Key[]): Promise<Record<Key, Value>> {\n const { staleKeys, inflightKeys } = this.#partition(keys);\n\n if (staleKeys.length === 0 && inflightKeys.length === 0) {\n return {} as Record<Key, Value>;\n }\n\n // Start a fetch for stale keys and join any fetches already in progress.\n const batchPromise =\n staleKeys.length > 0 ? this.#startBatchFetch(staleKeys) : undefined;\n const values = await this.#joinInflight(inflightKeys);\n\n if (batchPromise) {\n Object.assign(values, await batchPromise);\n }\n\n return values;\n }\n\n /**\n * Clear the freshness cache, forcing the next fetch to re-request every key\n * regardless of TTL. Does not affect inflight fetches.\n */\n invalidate(): void {\n this.#fetchedAt.clear();\n }\n\n /** Clear all freshness and inflight state. */\n destroy(): void {\n this.#fetchedAt.clear();\n this.#inflight.clear();\n }\n\n /**\n * Split keys into those that need a fresh fetch and those already being\n * fetched by another caller. Keys still within the freshness TTL are dropped.\n *\n * @param keys - The keys to classify.\n * @returns `staleKeys` (need fetching) and `inflightKeys` (join existing fetch).\n */\n #partition(keys: Key[]): { staleKeys: Key[]; inflightKeys: Key[] } {\n const now = Date.now();\n const staleKeys: Key[] = [];\n const inflightKeys: Key[] = [];\n\n for (const key of keys) {\n if (this.#isFresh(key, now)) {\n continue;\n }\n if (this.#inflight.has(key)) {\n inflightKeys.push(key);\n } else {\n staleKeys.push(key);\n }\n }\n\n return { staleKeys, inflightKeys };\n }\n\n /**\n * Returns true if the key's last fetch is still within the freshness TTL.\n *\n * @param key - The key to check.\n * @param now - Current timestamp (avoids repeated clock reads).\n * @returns True if the key was fetched within the freshness TTL.\n */\n #isFresh(key: Key, now: number): boolean {\n const fetchedAt = this.#fetchedAt.get(key);\n return fetchedAt !== undefined && now - fetchedAt < this.#freshnessTtlMs;\n }\n\n /**\n * Launch a batch fetch and register a per-key inflight promise for each key\n * so concurrent callers can join. On success, all requested keys are marked\n * fresh — including keys the source returned no value for, since the absence\n * of a value is itself a valid answer that should not be re-asked until the\n * TTL expires. A failed batch leaves keys stale so they are retried.\n *\n * @param staleKeys - Keys to fetch (none of which are already inflight).\n * @returns The batch fetch promise.\n */\n #startBatchFetch(staleKeys: Key[]): Promise<Record<Key, Value>> {\n const batchPromise = this.#fetchBatch(staleKeys).then((values) => {\n const fetchedAt = Date.now();\n for (const key of staleKeys) {\n this.#fetchedAt.set(key, fetchedAt);\n }\n return values;\n });\n\n for (const key of staleKeys) {\n const perKey = batchPromise.then(\n (values) => values[key],\n () => undefined,\n );\n this.#inflight.set(key, perKey);\n }\n\n // Clean up inflight entries once the batch settles (success or failure).\n // Rejection is already surfaced to the caller via the returned batchPromise.\n batchPromise\n .finally(() => {\n for (const key of staleKeys) {\n this.#inflight.delete(key);\n }\n })\n .catch(() => undefined);\n\n return batchPromise;\n }\n\n /**\n * Join the inflight fetches for the given keys and collect their values. Keys\n * whose inflight fetch produced no value are omitted.\n *\n * @param inflightKeys - Keys whose fetches are already in progress.\n * @returns Values keyed by key.\n */\n async #joinInflight(inflightKeys: Key[]): Promise<Record<Key, Value>> {\n const values = {} as Record<Key, Value>;\n\n const results = await Promise.all(\n inflightKeys.map(async (key) => {\n const value = await this.#inflight.get(key);\n return [key, value] as const;\n }),\n );\n\n for (const [key, value] of results) {\n if (value !== undefined) {\n values[key] = value;\n }\n }\n\n return values;\n }\n}\n"]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executes the underlying batched fetch for a set of keys. Only keys that have
|
|
3
|
+
* a value need to be present in the returned record; keys the source had no
|
|
4
|
+
* data for are simply omitted (they are still marked fresh — see
|
|
5
|
+
* {@link DedupingBatchFetcher.fetch}).
|
|
6
|
+
*
|
|
7
|
+
* @param keys - The keys to fetch (already filtered to stale, not-inflight keys).
|
|
8
|
+
* @returns The fetched values keyed by key.
|
|
9
|
+
*/
|
|
10
|
+
export type BatchFetchFn<Key extends string, Value> = (keys: Key[]) => Promise<Record<Key, Value>>;
|
|
11
|
+
export type DedupingBatchFetcherOptions<Key extends string, Value> = {
|
|
12
|
+
/** Performs the actual batched fetch for stale, not-inflight keys. */
|
|
13
|
+
fetchBatch: BatchFetchFn<Key, Value>;
|
|
14
|
+
/**
|
|
15
|
+
* Minimum age (ms) before a key is considered stale and re-fetched. Keys
|
|
16
|
+
* fetched more recently than this are skipped entirely.
|
|
17
|
+
*/
|
|
18
|
+
freshnessTtlMs: number;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Deduplicates batched fetches by key across two dimensions:
|
|
22
|
+
*
|
|
23
|
+
* 1. **Freshness TTL** — keys fetched within `freshnessTtlMs` are skipped
|
|
24
|
+
* entirely. Note the freshness window only starts once a fetch *completes*,
|
|
25
|
+
* so it does not cover requests that are still in flight.
|
|
26
|
+
* 2. **Inflight coalescing** — if a fetch is already in progress for a key,
|
|
27
|
+
* concurrent callers join the existing promise instead of issuing a new
|
|
28
|
+
* request. This covers the request-in-flight window the freshness TTL
|
|
29
|
+
* structurally cannot.
|
|
30
|
+
*
|
|
31
|
+
* Both layers are per-key, so a call for a partially-overlapping set of keys
|
|
32
|
+
* reuses the fresh/inflight keys and only fetches the genuinely-missing ones.
|
|
33
|
+
*
|
|
34
|
+
* The fetch is batched: all stale, not-inflight keys from a single `fetch()`
|
|
35
|
+
* call are passed to `fetchBatch` together, then split into per-key results so
|
|
36
|
+
* each key can be joined independently.
|
|
37
|
+
*/
|
|
38
|
+
export declare class DedupingBatchFetcher<Key extends string, Value> {
|
|
39
|
+
#private;
|
|
40
|
+
constructor(options: DedupingBatchFetcherOptions<Key, Value>);
|
|
41
|
+
/**
|
|
42
|
+
* Minimum age (ms) before a key is re-fetched.
|
|
43
|
+
*
|
|
44
|
+
* @returns The current freshness TTL in milliseconds.
|
|
45
|
+
*/
|
|
46
|
+
get freshnessTtlMs(): number;
|
|
47
|
+
set freshnessTtlMs(ms: number);
|
|
48
|
+
/**
|
|
49
|
+
* Fetch values for the given keys, deduplicating against fresh and inflight
|
|
50
|
+
* fetches.
|
|
51
|
+
*
|
|
52
|
+
* @param keys - The keys to fetch.
|
|
53
|
+
* @returns Values keyed by key. Only contains entries for keys that were
|
|
54
|
+
* actually fetched (or joined from inflight) and had a value.
|
|
55
|
+
*/
|
|
56
|
+
fetch(keys: Key[]): Promise<Record<Key, Value>>;
|
|
57
|
+
/**
|
|
58
|
+
* Clear the freshness cache, forcing the next fetch to re-request every key
|
|
59
|
+
* regardless of TTL. Does not affect inflight fetches.
|
|
60
|
+
*/
|
|
61
|
+
invalidate(): void;
|
|
62
|
+
/** Clear all freshness and inflight state. */
|
|
63
|
+
destroy(): void;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=dedupingBatchFetcher.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedupingBatchFetcher.d.cts","sourceRoot":"","sources":["../../src/utils/dedupingBatchFetcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,MAAM,YAAY,CAAC,GAAG,SAAS,MAAM,EAAE,KAAK,IAAI,CACpD,IAAI,EAAE,GAAG,EAAE,KACR,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;AAEjC,MAAM,MAAM,2BAA2B,CAAC,GAAG,SAAS,MAAM,EAAE,KAAK,IAAI;IACnE,sEAAsE;IACtE,UAAU,EAAE,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACrC;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,oBAAoB,CAAC,GAAG,SAAS,MAAM,EAAE,KAAK;;gBAc7C,OAAO,EAAE,2BAA2B,CAAC,GAAG,EAAE,KAAK,CAAC;IAK5D;;;;OAIG;IACH,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,IAAI,cAAc,CAAC,EAAE,EAAE,MAAM,EAE5B;IAED;;;;;;;OAOG;IACG,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAmBrD;;;OAGG;IACH,UAAU,IAAI,IAAI;IAIlB,8CAA8C;IAC9C,OAAO,IAAI,IAAI;CA4GhB"}
|