@metamask/assets-controller 9.0.2 → 10.0.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.
Files changed (98) hide show
  1. package/CHANGELOG.md +44 -1
  2. package/dist/AssetsController.cjs +357 -105
  3. package/dist/AssetsController.cjs.map +1 -1
  4. package/dist/AssetsController.d.cts +11 -8
  5. package/dist/AssetsController.d.cts.map +1 -1
  6. package/dist/AssetsController.d.mts +11 -8
  7. package/dist/AssetsController.d.mts.map +1 -1
  8. package/dist/AssetsController.mjs +357 -105
  9. package/dist/AssetsController.mjs.map +1 -1
  10. package/dist/data-sources/AccountsApiDataSource.cjs +15 -2
  11. package/dist/data-sources/AccountsApiDataSource.cjs.map +1 -1
  12. package/dist/data-sources/AccountsApiDataSource.d.cts +8 -0
  13. package/dist/data-sources/AccountsApiDataSource.d.cts.map +1 -1
  14. package/dist/data-sources/AccountsApiDataSource.d.mts +8 -0
  15. package/dist/data-sources/AccountsApiDataSource.d.mts.map +1 -1
  16. package/dist/data-sources/AccountsApiDataSource.mjs +15 -2
  17. package/dist/data-sources/AccountsApiDataSource.mjs.map +1 -1
  18. package/dist/data-sources/BackendWebsocketDataSource.cjs +236 -149
  19. package/dist/data-sources/BackendWebsocketDataSource.cjs.map +1 -1
  20. package/dist/data-sources/BackendWebsocketDataSource.d.cts +15 -0
  21. package/dist/data-sources/BackendWebsocketDataSource.d.cts.map +1 -1
  22. package/dist/data-sources/BackendWebsocketDataSource.d.mts +15 -0
  23. package/dist/data-sources/BackendWebsocketDataSource.d.mts.map +1 -1
  24. package/dist/data-sources/BackendWebsocketDataSource.mjs +236 -146
  25. package/dist/data-sources/BackendWebsocketDataSource.mjs.map +1 -1
  26. package/dist/data-sources/PriceDataSource.cjs +82 -22
  27. package/dist/data-sources/PriceDataSource.cjs.map +1 -1
  28. package/dist/data-sources/PriceDataSource.d.cts +13 -0
  29. package/dist/data-sources/PriceDataSource.d.cts.map +1 -1
  30. package/dist/data-sources/PriceDataSource.d.mts +13 -0
  31. package/dist/data-sources/PriceDataSource.d.mts.map +1 -1
  32. package/dist/data-sources/PriceDataSource.mjs +83 -23
  33. package/dist/data-sources/PriceDataSource.mjs.map +1 -1
  34. package/dist/data-sources/RpcDataSource.cjs +29 -9
  35. package/dist/data-sources/RpcDataSource.cjs.map +1 -1
  36. package/dist/data-sources/RpcDataSource.d.cts +5 -0
  37. package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
  38. package/dist/data-sources/RpcDataSource.d.mts +5 -0
  39. package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
  40. package/dist/data-sources/RpcDataSource.mjs +29 -9
  41. package/dist/data-sources/RpcDataSource.mjs.map +1 -1
  42. package/dist/data-sources/SnapDataSource.cjs +2 -2
  43. package/dist/data-sources/SnapDataSource.cjs.map +1 -1
  44. package/dist/data-sources/SnapDataSource.mjs +2 -2
  45. package/dist/data-sources/SnapDataSource.mjs.map +1 -1
  46. package/dist/index.cjs +3 -1
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.cts +2 -2
  49. package/dist/index.d.cts.map +1 -1
  50. package/dist/index.d.mts +2 -2
  51. package/dist/index.d.mts.map +1 -1
  52. package/dist/index.mjs +1 -1
  53. package/dist/index.mjs.map +1 -1
  54. package/dist/middlewares/DetectionMiddleware.cjs +64 -23
  55. package/dist/middlewares/DetectionMiddleware.cjs.map +1 -1
  56. package/dist/middlewares/DetectionMiddleware.d.cts +15 -13
  57. package/dist/middlewares/DetectionMiddleware.d.cts.map +1 -1
  58. package/dist/middlewares/DetectionMiddleware.d.mts +15 -13
  59. package/dist/middlewares/DetectionMiddleware.d.mts.map +1 -1
  60. package/dist/middlewares/DetectionMiddleware.mjs +64 -23
  61. package/dist/middlewares/DetectionMiddleware.mjs.map +1 -1
  62. package/dist/middlewares/ParallelMiddleware.cjs +7 -0
  63. package/dist/middlewares/ParallelMiddleware.cjs.map +1 -1
  64. package/dist/middlewares/ParallelMiddleware.d.cts.map +1 -1
  65. package/dist/middlewares/ParallelMiddleware.d.mts.map +1 -1
  66. package/dist/middlewares/ParallelMiddleware.mjs +7 -0
  67. package/dist/middlewares/ParallelMiddleware.mjs.map +1 -1
  68. package/dist/selectors/balance.cjs +191 -8
  69. package/dist/selectors/balance.cjs.map +1 -1
  70. package/dist/selectors/balance.d.cts +80 -0
  71. package/dist/selectors/balance.d.cts.map +1 -1
  72. package/dist/selectors/balance.d.mts +80 -0
  73. package/dist/selectors/balance.d.mts.map +1 -1
  74. package/dist/selectors/balance.mjs +186 -7
  75. package/dist/selectors/balance.mjs.map +1 -1
  76. package/dist/types.cjs.map +1 -1
  77. package/dist/types.d.cts +20 -4
  78. package/dist/types.d.cts.map +1 -1
  79. package/dist/types.d.mts +20 -4
  80. package/dist/types.d.mts.map +1 -1
  81. package/dist/types.mjs.map +1 -1
  82. package/dist/utils/dedupingBatchFetcher.cjs +168 -0
  83. package/dist/utils/dedupingBatchFetcher.cjs.map +1 -0
  84. package/dist/utils/dedupingBatchFetcher.d.cts +72 -0
  85. package/dist/utils/dedupingBatchFetcher.d.cts.map +1 -0
  86. package/dist/utils/dedupingBatchFetcher.d.mts +72 -0
  87. package/dist/utils/dedupingBatchFetcher.d.mts.map +1 -0
  88. package/dist/utils/dedupingBatchFetcher.mjs +164 -0
  89. package/dist/utils/dedupingBatchFetcher.mjs.map +1 -0
  90. package/dist/utils/processAccountActivityBalanceUpdates.cjs +54 -0
  91. package/dist/utils/processAccountActivityBalanceUpdates.cjs.map +1 -0
  92. package/dist/utils/processAccountActivityBalanceUpdates.d.cts +13 -0
  93. package/dist/utils/processAccountActivityBalanceUpdates.d.cts.map +1 -0
  94. package/dist/utils/processAccountActivityBalanceUpdates.d.mts +13 -0
  95. package/dist/utils/processAccountActivityBalanceUpdates.d.mts.map +1 -0
  96. package/dist/utils/processAccountActivityBalanceUpdates.mjs +47 -0
  97. package/dist/utils/processAccountActivityBalanceUpdates.mjs.map +1 -0
  98. package/package.json +7 -7
@@ -3,16 +3,14 @@ import type { Middleware } from "../types.mjs";
3
3
  * DetectionMiddleware builds the set of assets that downstream sources use for
4
4
  * metadata and price fetching.
5
5
  *
6
- * This middleware:
7
- * - Includes every asset that appears in response.assetsBalance (so prices and
8
- * metadata are fetched for existing assets as well as new ones)
9
- * - Includes each account's custom assets from state (so custom tokens get
10
- * metadata and prices even when they have no balance yet)
11
- * - Fills response.detectedAssets with these asset IDs per account
12
- *
13
- * TokenDataSource and PriceDataSource both key off detectedAssets. TokenDataSource
14
- * then filters to only fetch metadata for assets that lack it; PriceDataSource
15
- * fetches prices for all detected assets.
6
+ * An asset is included in `detectedAssets` only when it is genuinely new:
7
+ * - Assets from `response.assetsBalance` are included only if they are absent
8
+ * from BOTH `state.assetsBalance` (never tracked before) AND `state.assetsInfo`
9
+ * (no metadata yet). Assets already present in either collection are considered
10
+ * known and are intentionally excluded PriceDataSource's own subscription
11
+ * handles periodic refreshes for those.
12
+ * - Each account's custom assets from state are always included because they
13
+ * may have no balance yet and are explicitly managed by the user.
16
14
  *
17
15
  * Usage:
18
16
  * ```typescript
@@ -27,9 +25,13 @@ export declare class DetectionMiddleware {
27
25
  * Get the middleware that builds detectedAssets for metadata and price fetching.
28
26
  *
29
27
  * This middleware:
30
- * 1. Includes all assets from response.assetsBalance (so prices are fetched for existing assets too)
31
- * 2. Merges each account's custom assets from state
32
- * 3. Fills response.detectedAssets with these asset IDs per account
28
+ * 1. Includes assets from response.assetsBalance that are absent from both
29
+ * state.assetsBalance and state.assetsInfo (brand-new assets only)
30
+ * 2. Always includes each account's custom assets from state
31
+ * 3. Fills response.detectedAssets with the resulting asset IDs per account
32
+ * 4. Queues detected assets that lack a price in state on
33
+ * request.assetsForPriceUpdate so PriceDataSource fetches them in the same
34
+ * pipeline pass (including the background RPC detection path)
33
35
  *
34
36
  * @returns The middleware function for the assets pipeline.
35
37
  */
@@ -1 +1 @@
1
- {"version":3,"file":"DetectionMiddleware.d.mts","sourceRoot":"","sources":["../../src/middlewares/DetectionMiddleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA4B,UAAU,EAAE,qBAAiB;AAerE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,mBAAmB;IAC9B,QAAQ,CAAC,IAAI,yBAAmB;IAEhC,OAAO,IAAI,MAAM;IAIjB;;;;;;;;;OASG;IACH,IAAI,gBAAgB,IAAI,UAAU,CAuDjC;CACF"}
1
+ {"version":3,"file":"DetectionMiddleware.d.mts","sourceRoot":"","sources":["../../src/middlewares/DetectionMiddleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA4B,UAAU,EAAE,qBAAiB;AAgBrE;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,mBAAmB;IAC9B,QAAQ,CAAC,IAAI,yBAAmB;IAEhC,OAAO,IAAI,MAAM;IAIjB;;;;;;;;;;;;;OAaG;IACH,IAAI,gBAAgB,IAAI,UAAU,CA4GjC;CACF"}
@@ -1,5 +1,6 @@
1
1
  import { projectLogger, createModuleLogger } from "../logger.mjs";
2
2
  import { forDataTypes } from "../types.mjs";
3
+ import { normalizeAssetId } from "../utils/index.mjs";
3
4
  // ============================================================================
4
5
  // CONSTANTS
5
6
  // ============================================================================
@@ -13,16 +14,14 @@ createModuleLogger(projectLogger, CONTROLLER_NAME);
13
14
  * DetectionMiddleware builds the set of assets that downstream sources use for
14
15
  * metadata and price fetching.
15
16
  *
16
- * This middleware:
17
- * - Includes every asset that appears in response.assetsBalance (so prices and
18
- * metadata are fetched for existing assets as well as new ones)
19
- * - Includes each account's custom assets from state (so custom tokens get
20
- * metadata and prices even when they have no balance yet)
21
- * - Fills response.detectedAssets with these asset IDs per account
22
- *
23
- * TokenDataSource and PriceDataSource both key off detectedAssets. TokenDataSource
24
- * then filters to only fetch metadata for assets that lack it; PriceDataSource
25
- * fetches prices for all detected assets.
17
+ * An asset is included in `detectedAssets` only when it is genuinely new:
18
+ * - Assets from `response.assetsBalance` are included only if they are absent
19
+ * from BOTH `state.assetsBalance` (never tracked before) AND `state.assetsInfo`
20
+ * (no metadata yet). Assets already present in either collection are considered
21
+ * known and are intentionally excluded PriceDataSource's own subscription
22
+ * handles periodic refreshes for those.
23
+ * - Each account's custom assets from state are always included because they
24
+ * may have no balance yet and are explicitly managed by the user.
26
25
  *
27
26
  * Usage:
28
27
  * ```typescript
@@ -41,51 +40,93 @@ export class DetectionMiddleware {
41
40
  * Get the middleware that builds detectedAssets for metadata and price fetching.
42
41
  *
43
42
  * This middleware:
44
- * 1. Includes all assets from response.assetsBalance (so prices are fetched for existing assets too)
45
- * 2. Merges each account's custom assets from state
46
- * 3. Fills response.detectedAssets with these asset IDs per account
43
+ * 1. Includes assets from response.assetsBalance that are absent from both
44
+ * state.assetsBalance and state.assetsInfo (brand-new assets only)
45
+ * 2. Always includes each account's custom assets from state
46
+ * 3. Fills response.detectedAssets with the resulting asset IDs per account
47
+ * 4. Queues detected assets that lack a price in state on
48
+ * request.assetsForPriceUpdate so PriceDataSource fetches them in the same
49
+ * pipeline pass (including the background RPC detection path)
47
50
  *
48
51
  * @returns The middleware function for the assets pipeline.
49
52
  */
50
53
  get assetsMiddleware() {
51
54
  return forDataTypes(['balance'], async (ctx, next) => {
52
55
  const { request, response } = ctx;
53
- // Get state for custom assets
56
+ // Get state for custom assets, existing balances, and existing metadata
54
57
  const state = ctx.getAssetsState();
55
- const { customAssets: stateCustomAssets } = state;
58
+ const { customAssets: stateCustomAssets, assetsBalance: stateAssetsBalance, assetsInfo: stateAssetsInfo, assetsPrice: stateAssetsPrice, } = state;
56
59
  const detectedAssets = {};
57
- // 1. From balance response: include every asset with balance (so prices + metadata path include existing assets)
60
+ // 1. From balance response: only include assets that are genuinely new
61
+ // not already present in state.assetsBalance or state.assetsInfo.
58
62
  if (response.assetsBalance) {
59
63
  for (const [accountId, accountBalances] of Object.entries(response.assetsBalance)) {
60
64
  const detected = [];
65
+ const stateAccountBalances = stateAssetsBalance[accountId] ?? {};
61
66
  for (const assetId of Object.keys(accountBalances)) {
62
- detected.push(assetId);
67
+ const caipAssetId = assetId;
68
+ // Skip if already tracked in state balances or already has metadata
69
+ if (stateAccountBalances[caipAssetId] !== undefined ||
70
+ stateAssetsInfo[caipAssetId] !== undefined) {
71
+ continue;
72
+ }
73
+ detected.push(caipAssetId);
63
74
  }
64
- // Merge this account's custom assets from state
75
+ // Merge custom assets for this account, applying the same filter:
76
+ // skip if already in state balance or already has metadata.
65
77
  const customForAccount = stateCustomAssets?.[accountId] ?? [];
66
78
  for (const assetId of customForAccount) {
67
- if (!detected.includes(assetId)) {
68
- detected.push(assetId);
79
+ if (detected.includes(assetId)) {
80
+ continue;
69
81
  }
82
+ if (stateAccountBalances[assetId] !== undefined ||
83
+ stateAssetsInfo[assetId] !== undefined) {
84
+ continue;
85
+ }
86
+ detected.push(assetId);
70
87
  }
71
88
  if (detected.length > 0) {
72
89
  detectedAssets[accountId] = detected;
73
90
  }
74
91
  }
75
92
  }
76
- // 2. Accounts in request that weren't in balance response: include their custom assets
93
+ // 2. Accounts in request that weren't in balance response: include their
94
+ // custom assets that are not yet in state.
77
95
  for (const { account } of request.accountsWithSupportedChains) {
78
96
  const accountId = account.id;
79
97
  if (detectedAssets[accountId]) {
80
98
  continue;
81
99
  }
100
+ const stateAccountBalances = stateAssetsBalance[accountId] ?? {};
82
101
  const customForAccount = stateCustomAssets?.[accountId] ?? [];
83
- if (customForAccount.length > 0) {
84
- detectedAssets[accountId] = customForAccount;
102
+ const newCustomAssets = customForAccount.filter((assetId) => {
103
+ const inBalance = stateAccountBalances[assetId] !== undefined;
104
+ const inInfo = stateAssetsInfo[assetId] !== undefined;
105
+ return !inBalance && !inInfo;
106
+ });
107
+ if (newCustomAssets.length > 0) {
108
+ detectedAssets[accountId] = newCustomAssets;
85
109
  }
86
110
  }
87
111
  if (Object.keys(detectedAssets).length > 0) {
88
112
  response.detectedAssets = detectedAssets;
113
+ const prices = stateAssetsPrice;
114
+ const missingPriceAssets = new Set();
115
+ for (const accountAssetIds of Object.values(detectedAssets)) {
116
+ for (const assetId of accountAssetIds) {
117
+ const normalizedAssetId = normalizeAssetId(assetId);
118
+ if (prices[normalizedAssetId] === undefined &&
119
+ prices[assetId] === undefined) {
120
+ missingPriceAssets.add(normalizedAssetId);
121
+ }
122
+ }
123
+ }
124
+ if (missingPriceAssets.size > 0) {
125
+ request.assetsForPriceUpdate = [
126
+ ...(request.assetsForPriceUpdate ?? []),
127
+ ...missingPriceAssets,
128
+ ];
129
+ }
89
130
  }
90
131
  return next(ctx);
91
132
  });
@@ -1 +1 @@
1
- {"version":3,"file":"DetectionMiddleware.mjs","sourceRoot":"","sources":["../../src/middlewares/DetectionMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,sBAAkB;AAC9D,OAAO,EAAE,YAAY,EAAE,qBAAiB;AAGxC,+EAA+E;AAC/E,YAAY;AACZ,+EAA+E;AAE/E,MAAM,eAAe,GAAG,qBAAqB,CAAC;AAE9C,uBAAuB;AACvB,kBAAkB,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;AAEnD,+EAA+E;AAC/E,uBAAuB;AACvB,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,OAAO,mBAAmB;IAAhC;QACW,SAAI,GAAG,eAAe,CAAC;IAwElC,CAAC;IAtEC,OAAO;QACL,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED;;;;;;;;;OASG;IACH,IAAI,gBAAgB;QAClB,OAAO,YAAY,CAAC,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;YACnD,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC;YAElC,8BAA8B;YAC9B,MAAM,KAAK,GAAG,GAAG,CAAC,cAAc,EAAE,CAAC;YACnC,MAAM,EAAE,YAAY,EAAE,iBAAiB,EAAE,GAAG,KAAK,CAAC;YAElD,MAAM,cAAc,GAAuC,EAAE,CAAC;YAE9D,iHAAiH;YACjH,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;gBAC3B,KAAK,MAAM,CAAC,SAAS,EAAE,eAAe,CAAC,IAAI,MAAM,CAAC,OAAO,CACvD,QAAQ,CAAC,aAAa,CACvB,EAAE,CAAC;oBACF,MAAM,QAAQ,GAAoB,EAAE,CAAC;oBAErC,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,IAAI,CAC/B,eAA0C,CAC3C,EAAE,CAAC;wBACF,QAAQ,CAAC,IAAI,CAAC,OAAwB,CAAC,CAAC;oBAC1C,CAAC;oBAED,gDAAgD;oBAChD,MAAM,gBAAgB,GAAG,iBAAiB,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;oBAC9D,KAAK,MAAM,OAAO,IAAI,gBAAgB,EAAE,CAAC;wBACvC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;4BAChC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;wBACzB,CAAC;oBACH,CAAC;oBAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBACxB,cAAc,CAAC,SAAS,CAAC,GAAG,QAAQ,CAAC;oBACvC,CAAC;gBACH,CAAC;YACH,CAAC;YAED,uFAAuF;YACvF,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,OAAO,CAAC,2BAA2B,EAAE,CAAC;gBAC9D,MAAM,SAAS,GAAG,OAAO,CAAC,EAAE,CAAC;gBAC7B,IAAI,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC;oBAC9B,SAAS;gBACX,CAAC;gBACD,MAAM,gBAAgB,GAAG,iBAAiB,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;gBAC9D,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAChC,cAAc,CAAC,SAAS,CAAC,GAAG,gBAAgB,CAAC;gBAC/C,CAAC;YACH,CAAC;YAED,IAAI,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC3C,QAAQ,CAAC,cAAc,GAAG,cAAc,CAAC;YAC3C,CAAC;YAED,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["import { projectLogger, createModuleLogger } from '../logger';\nimport { forDataTypes } from '../types';\nimport type { AccountId, Caip19AssetId, Middleware } from '../types';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'DetectionMiddleware';\n\n// Logger for debugging\ncreateModuleLogger(projectLogger, CONTROLLER_NAME);\n\n// ============================================================================\n// DETECTION MIDDLEWARE\n// ============================================================================\n\n/**\n * DetectionMiddleware builds the set of assets that downstream sources use for\n * metadata and price fetching.\n *\n * This middleware:\n * - Includes every asset that appears in response.assetsBalance (so prices and\n * metadata are fetched for existing assets as well as new ones)\n * - Includes each account's custom assets from state (so custom tokens get\n * metadata and prices even when they have no balance yet)\n * - Fills response.detectedAssets with these asset IDs per account\n *\n * TokenDataSource and PriceDataSource both key off detectedAssets. TokenDataSource\n * then filters to only fetch metadata for assets that lack it; PriceDataSource\n * fetches prices for all detected assets.\n *\n * Usage:\n * ```typescript\n * const detectionMiddleware = new DetectionMiddleware();\n * const middleware = detectionMiddleware.assetsMiddleware;\n * ```\n */\nexport class DetectionMiddleware {\n readonly name = CONTROLLER_NAME;\n\n getName(): string {\n return this.name;\n }\n\n /**\n * Get the middleware that builds detectedAssets for metadata and price fetching.\n *\n * This middleware:\n * 1. Includes all assets from response.assetsBalance (so prices are fetched for existing assets too)\n * 2. Merges each account's custom assets from state\n * 3. Fills response.detectedAssets with these asset IDs per account\n *\n * @returns The middleware function for the assets pipeline.\n */\n get assetsMiddleware(): Middleware {\n return forDataTypes(['balance'], async (ctx, next) => {\n const { request, response } = ctx;\n\n // Get state for custom assets\n const state = ctx.getAssetsState();\n const { customAssets: stateCustomAssets } = state;\n\n const detectedAssets: Record<AccountId, Caip19AssetId[]> = {};\n\n // 1. From balance response: include every asset with balance (so prices + metadata path include existing assets)\n if (response.assetsBalance) {\n for (const [accountId, accountBalances] of Object.entries(\n response.assetsBalance,\n )) {\n const detected: Caip19AssetId[] = [];\n\n for (const assetId of Object.keys(\n accountBalances as Record<string, unknown>,\n )) {\n detected.push(assetId as Caip19AssetId);\n }\n\n // Merge this account's custom assets from state\n const customForAccount = stateCustomAssets?.[accountId] ?? [];\n for (const assetId of customForAccount) {\n if (!detected.includes(assetId)) {\n detected.push(assetId);\n }\n }\n\n if (detected.length > 0) {\n detectedAssets[accountId] = detected;\n }\n }\n }\n\n // 2. Accounts in request that weren't in balance response: include their custom assets\n for (const { account } of request.accountsWithSupportedChains) {\n const accountId = account.id;\n if (detectedAssets[accountId]) {\n continue;\n }\n const customForAccount = stateCustomAssets?.[accountId] ?? [];\n if (customForAccount.length > 0) {\n detectedAssets[accountId] = customForAccount;\n }\n }\n\n if (Object.keys(detectedAssets).length > 0) {\n response.detectedAssets = detectedAssets;\n }\n\n return next(ctx);\n });\n }\n}\n"]}
1
+ {"version":3,"file":"DetectionMiddleware.mjs","sourceRoot":"","sources":["../../src/middlewares/DetectionMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { projectLogger, createModuleLogger } from '../logger';\nimport { forDataTypes } from '../types';\nimport type { AccountId, Caip19AssetId, Middleware } from '../types';\nimport { normalizeAssetId } from '../utils';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'DetectionMiddleware';\n\n// Logger for debugging\ncreateModuleLogger(projectLogger, CONTROLLER_NAME);\n\n// ============================================================================\n// DETECTION MIDDLEWARE\n// ============================================================================\n\n/**\n * DetectionMiddleware builds the set of assets that downstream sources use for\n * metadata and price fetching.\n *\n * An asset is included in `detectedAssets` only when it is genuinely new:\n * - Assets from `response.assetsBalance` are included only if they are absent\n * from BOTH `state.assetsBalance` (never tracked before) AND `state.assetsInfo`\n * (no metadata yet). Assets already present in either collection are considered\n * known and are intentionally excluded PriceDataSource's own subscription\n * handles periodic refreshes for those.\n * - Each account's custom assets from state are always included because they\n * may have no balance yet and are explicitly managed by the user.\n *\n * Usage:\n * ```typescript\n * const detectionMiddleware = new DetectionMiddleware();\n * const middleware = detectionMiddleware.assetsMiddleware;\n * ```\n */\nexport class DetectionMiddleware {\n readonly name = CONTROLLER_NAME;\n\n getName(): string {\n return this.name;\n }\n\n /**\n * Get the middleware that builds detectedAssets for metadata and price fetching.\n *\n * This middleware:\n * 1. Includes assets from response.assetsBalance that are absent from both\n * state.assetsBalance and state.assetsInfo (brand-new assets only)\n * 2. Always includes each account's custom assets from state\n * 3. Fills response.detectedAssets with the resulting asset IDs per account\n * 4. Queues detected assets that lack a price in state on\n * request.assetsForPriceUpdate so PriceDataSource fetches them in the same\n * pipeline pass (including the background RPC detection path)\n *\n * @returns The middleware function for the assets pipeline.\n */\n get assetsMiddleware(): Middleware {\n return forDataTypes(['balance'], async (ctx, next) => {\n const { request, response } = ctx;\n\n // Get state for custom assets, existing balances, and existing metadata\n const state = ctx.getAssetsState();\n const {\n customAssets: stateCustomAssets,\n assetsBalance: stateAssetsBalance,\n assetsInfo: stateAssetsInfo,\n assetsPrice: stateAssetsPrice,\n } = state;\n\n const detectedAssets: Record<AccountId, Caip19AssetId[]> = {};\n\n // 1. From balance response: only include assets that are genuinely new —\n // not already present in state.assetsBalance or state.assetsInfo.\n if (response.assetsBalance) {\n for (const [accountId, accountBalances] of Object.entries(\n response.assetsBalance,\n )) {\n const detected: Caip19AssetId[] = [];\n\n const stateAccountBalances = stateAssetsBalance[accountId] ?? {};\n\n for (const assetId of Object.keys(\n accountBalances as Record<string, unknown>,\n )) {\n const caipAssetId = assetId as Caip19AssetId;\n // Skip if already tracked in state balances or already has metadata\n if (\n stateAccountBalances[caipAssetId] !== undefined ||\n stateAssetsInfo[caipAssetId] !== undefined\n ) {\n continue;\n }\n detected.push(caipAssetId);\n }\n\n // Merge custom assets for this account, applying the same filter:\n // skip if already in state balance or already has metadata.\n const customForAccount = stateCustomAssets?.[accountId] ?? [];\n for (const assetId of customForAccount) {\n if (detected.includes(assetId)) {\n continue;\n }\n if (\n stateAccountBalances[assetId] !== undefined ||\n stateAssetsInfo[assetId] !== undefined\n ) {\n continue;\n }\n detected.push(assetId);\n }\n\n if (detected.length > 0) {\n detectedAssets[accountId] = detected;\n }\n }\n }\n\n // 2. Accounts in request that weren't in balance response: include their\n // custom assets that are not yet in state.\n for (const { account } of request.accountsWithSupportedChains) {\n const accountId = account.id;\n if (detectedAssets[accountId]) {\n continue;\n }\n const stateAccountBalances = stateAssetsBalance[accountId] ?? {};\n const customForAccount = stateCustomAssets?.[accountId] ?? [];\n const newCustomAssets = customForAccount.filter((assetId) => {\n const inBalance = stateAccountBalances[assetId] !== undefined;\n const inInfo = stateAssetsInfo[assetId] !== undefined;\n return !inBalance && !inInfo;\n });\n if (newCustomAssets.length > 0) {\n detectedAssets[accountId] = newCustomAssets;\n }\n }\n\n if (Object.keys(detectedAssets).length > 0) {\n response.detectedAssets = detectedAssets;\n\n const prices = stateAssetsPrice as Record<string, unknown>;\n const missingPriceAssets = new Set<Caip19AssetId>();\n\n for (const accountAssetIds of Object.values(detectedAssets)) {\n for (const assetId of accountAssetIds) {\n const normalizedAssetId = normalizeAssetId(assetId);\n if (\n prices[normalizedAssetId] === undefined &&\n prices[assetId] === undefined\n ) {\n missingPriceAssets.add(normalizedAssetId);\n }\n }\n }\n\n if (missingPriceAssets.size > 0) {\n request.assetsForPriceUpdate = [\n ...(request.assetsForPriceUpdate ?? []),\n ...missingPriceAssets,\n ];\n }\n }\n\n return next(ctx);\n });\n }\n}\n"]}
@@ -54,6 +54,13 @@ function mergeDataResponses(responses) {
54
54
  if (response.updateMode === 'full') {
55
55
  merged.updateMode = 'full';
56
56
  }
57
+ else if (response.updateMode === 'merge' &&
58
+ merged.updateMode !== 'full') {
59
+ merged.updateMode = 'merge';
60
+ }
61
+ if (response.replaceCoveredChainBalances) {
62
+ merged.replaceCoveredChainBalances = true;
63
+ }
57
64
  }
58
65
  merged.updateMode ?? (merged.updateMode = 'merge');
59
66
  return merged;
@@ -1 +1 @@
1
- {"version":3,"file":"ParallelMiddleware.cjs","sourceRoot":"","sources":["../../src/middlewares/ParallelMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import pLimit from 'p-limit';\n\nimport type {\n ChainId,\n Context,\n DataRequest,\n DataResponse,\n Middleware,\n AssetsDataSource,\n} from '../types';\n\n// ============================================================================\n// MERGE HELPER\n// ============================================================================\n\n/**\n * Deep-merge multiple DataResponses into one.\n * Used when running balance data sources in parallel.\n *\n * @param responses - Array of DataResponse from each source.\n * @returns Single merged DataResponse.\n */\nexport function mergeDataResponses(responses: DataResponse[]): DataResponse {\n const merged: DataResponse = {};\n\n for (const response of responses) {\n if (response.assetsBalance) {\n merged.assetsBalance ??= {};\n for (const [accountId, accountBalances] of Object.entries(\n response.assetsBalance,\n )) {\n merged.assetsBalance[accountId] = {\n ...(merged.assetsBalance[accountId] ?? {}),\n ...accountBalances,\n };\n }\n }\n if (response.assetsInfo) {\n merged.assetsInfo = {\n ...(merged.assetsInfo ?? {}),\n ...response.assetsInfo,\n };\n }\n if (response.assetsPrice) {\n merged.assetsPrice = {\n ...(merged.assetsPrice ?? {}),\n ...response.assetsPrice,\n };\n }\n if (response.errors) {\n merged.errors = {\n ...(merged.errors ?? {}),\n ...response.errors,\n };\n }\n if (response.detectedAssets) {\n merged.detectedAssets = {\n ...(merged.detectedAssets ?? {}),\n ...response.detectedAssets,\n };\n }\n if (response.updateMode === 'full') {\n merged.updateMode = 'full';\n }\n }\n merged.updateMode ??= 'merge';\n\n return merged;\n}\n\n// ============================================================================\n// PARALLEL BALANCE MIDDLEWARE\n// ============================================================================\n\nconst PARALLEL_BALANCE_MIDDLEWARE_NAME = 'ParallelBalanceMiddleware';\n\n/** Max concurrent balance source calls (round 1 and fallback). */\nconst BALANCE_CONCURRENCY = 3;\n\nexport type BalanceSource = {\n getName(): string;\n /** Chains this source can fetch (e.g. from getActiveChainsSync()). Used to partition chains with no overlap. */\n getActiveChainsSync(): ChainId[];\n assetsMiddleware: Middleware;\n};\n\n/**\n * Partition request.chainIds so each chain is assigned to exactly one source\n * (by source order: first source that supports the chain gets it). Ensures no\n * chain overlap across data source calls.\n *\n * @param request - The data request with chainIds to partition.\n * @param sources - Balance sources in priority order (e.g. AccountsAPI, Snap, Rpc).\n * @returns Array of requests, one per source, each with only that source's assigned chainIds.\n */\nfunction partitionChainsBySource(\n request: DataRequest,\n sources: BalanceSource[],\n): DataRequest[] {\n const { chainIds } = request;\n const assigned = new Set<ChainId>();\n\n return sources.map((source) => {\n const supported = new Set(source.getActiveChainsSync());\n const chainsForSource = chainIds.filter(\n (id) => supported.has(id) && !assigned.has(id),\n );\n chainsForSource.forEach((id) => assigned.add(id));\n\n return {\n ...request,\n chainIds: chainsForSource,\n };\n });\n}\n\n/**\n * Collect chain IDs that failed in the first round (present in response.errors).\n * Used to run a fallback round with remaining sources.\n *\n * @param requests - Partitioned requests, one per source (same order as results).\n * @param results - Results from each source; chain IDs in requests[i] that have errors in results[i].response.errors are considered failed.\n * @returns Set of chain IDs that had errors in the first round.\n */\nfunction getFailedChainIds(\n requests: DataRequest[],\n results: { response: DataResponse }[],\n): Set<ChainId> {\n const failed = new Set<ChainId>();\n for (let i = 0; i < results.length; i++) {\n const errors = results[i].response.errors ?? {};\n for (const chainId of requests[i].chainIds) {\n if (errors[chainId]) {\n failed.add(chainId);\n }\n }\n }\n return failed;\n}\n\n/**\n * Middleware that runs multiple balance data source middlewares in parallel,\n * with no chain overlap. Chains that fail (response.errors) are re-partitioned\n * and fetched again in a fallback round so lower-priority sources can try them.\n *\n * @param sources - Array of balance sources in priority order (each with getName(), getActiveChainsSync(), assetsMiddleware).\n * @returns A single middleware that runs all sources in parallel and merges responses.\n */\nexport function createParallelBalanceMiddleware(sources: BalanceSource[]): {\n getName(): string;\n assetsMiddleware: Middleware;\n} {\n return {\n getName(): string {\n return PARALLEL_BALANCE_MIDDLEWARE_NAME;\n },\n\n assetsMiddleware: async (context, next): Promise<Context> => {\n if (sources.length === 0) {\n return next(context);\n }\n\n const noopNext = async (ctx: typeof context): Promise<typeof context> =>\n ctx;\n const limit = pLimit(BALANCE_CONCURRENCY);\n\n // Round 1: partition chains (no overlap), run with limited concurrency\n const requests = partitionChainsBySource(context.request, sources);\n const round1Timed = await Promise.all(\n sources.map((source, i) =>\n limit(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: requests[i],\n response: {},\n getAssetsState: context.getAssetsState,\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n const results = round1Timed.map((entry) => entry.result);\n\n const durationByDataSource: Record<string, number> = {\n ...context.durationByDataSource,\n };\n for (const { sourceName, durationMs } of round1Timed) {\n const key = `${PARALLEL_BALANCE_MIDDLEWARE_NAME}.${sourceName}`;\n durationByDataSource[key] =\n (durationByDataSource[key] ?? 0) + durationMs;\n }\n\n let mergedResponse = mergeDataResponses(\n results.map((result) => result.response),\n );\n\n // Fallback: chains that failed (in errors) get re-partitioned and tried again\n const failedChainIds = getFailedChainIds(requests, results);\n if (failedChainIds.size > 0) {\n const fallbackRequest: DataRequest = {\n ...context.request,\n chainIds: [...failedChainIds],\n };\n const fallbackRequests = partitionChainsBySource(\n fallbackRequest,\n sources,\n );\n const fallbackTimed = await Promise.all(\n sources.map((source, i) =>\n limit(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: fallbackRequests[i],\n response: {},\n getAssetsState: context.getAssetsState,\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n const fallbackResults = fallbackTimed.map((entry) => entry.result);\n for (const { sourceName, durationMs } of fallbackTimed) {\n const key = `${PARALLEL_BALANCE_MIDDLEWARE_NAME}.${sourceName}`;\n durationByDataSource[key] =\n (durationByDataSource[key] ?? 0) + durationMs;\n }\n const fallbackMerged = mergeDataResponses(\n fallbackResults.map((result) => result.response),\n );\n mergedResponse = mergeDataResponses([mergedResponse, fallbackMerged]);\n // Remove errors for chains we successfully got balance for in fallback\n if (mergedResponse.errors && mergedResponse.assetsBalance) {\n const chainsWithBalance = new Set<ChainId>();\n for (const accountBalances of Object.values(\n mergedResponse.assetsBalance,\n )) {\n for (const assetId of Object.keys(accountBalances)) {\n const chainId = assetId.split('/')[0] as ChainId;\n chainsWithBalance.add(chainId);\n }\n }\n for (const chainId of failedChainIds) {\n if (chainsWithBalance.has(chainId)) {\n delete mergedResponse.errors[chainId];\n }\n }\n }\n }\n\n return next({\n ...context,\n response: mergeDataResponses([context.response, mergedResponse]),\n durationByDataSource,\n });\n },\n };\n}\n\n// ============================================================================\n// PARALLEL TOKEN/PRICE MIDDLEWARE\n// ============================================================================\n\nconst PARALLEL_MIDDLEWARE_NAME = 'ParallelMiddleware';\n\n/** Max concurrent token/price source calls. */\nconst CONCURRENCY = 2;\n\n/**\n * Middleware that runs multiple data source middlewares (e.g. TokenDataSource,\n * PriceDataSource) in parallel with the same request. Responses are merged so\n * that assetsInfo (token metadata) and assetsPrice are combined. Use this to\n * fetch token and price data concurrently instead of sequentially.\n *\n * @param sources - Array of named middleware sources (getName + assetsMiddleware).\n * @returns A single middleware that runs all sources in parallel and merges responses.\n */\nexport function createParallelMiddleware(sources: AssetsDataSource[]): {\n getName(): string;\n assetsMiddleware: Middleware;\n} {\n return {\n getName(): string {\n return PARALLEL_MIDDLEWARE_NAME;\n },\n\n assetsMiddleware: async (context, next): Promise<Context> => {\n if (sources.length === 0) {\n return next(context);\n }\n\n const noopNext = async (ctx: typeof context): Promise<typeof context> =>\n ctx;\n const limit = pLimit(CONCURRENCY);\n\n const timedResults = await Promise.all(\n sources.map((source) =>\n limit(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: context.request,\n response: { ...context.response },\n getAssetsState: context.getAssetsState,\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n\n const results = timedResults.map((entry) => entry.result);\n const mergedResponse = mergeDataResponses(\n results.map((result) => result.response),\n );\n\n const durationByDataSource: Record<string, number> = {\n ...context.durationByDataSource,\n };\n for (const { sourceName, durationMs } of timedResults) {\n durationByDataSource[`${PARALLEL_MIDDLEWARE_NAME}.${sourceName}`] =\n durationMs;\n }\n\n return next({\n ...context,\n response: mergeDataResponses([context.response, mergedResponse]),\n durationByDataSource,\n });\n },\n };\n}\n"]}
1
+ {"version":3,"file":"ParallelMiddleware.cjs","sourceRoot":"","sources":["../../src/middlewares/ParallelMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import pLimit from 'p-limit';\n\nimport type {\n ChainId,\n Context,\n DataRequest,\n DataResponse,\n Middleware,\n AssetsDataSource,\n} from '../types';\n\n// ============================================================================\n// MERGE HELPER\n// ============================================================================\n\n/**\n * Deep-merge multiple DataResponses into one.\n * Used when running balance data sources in parallel.\n *\n * @param responses - Array of DataResponse from each source.\n * @returns Single merged DataResponse.\n */\nexport function mergeDataResponses(responses: DataResponse[]): DataResponse {\n const merged: DataResponse = {};\n\n for (const response of responses) {\n if (response.assetsBalance) {\n merged.assetsBalance ??= {};\n for (const [accountId, accountBalances] of Object.entries(\n response.assetsBalance,\n )) {\n merged.assetsBalance[accountId] = {\n ...(merged.assetsBalance[accountId] ?? {}),\n ...accountBalances,\n };\n }\n }\n if (response.assetsInfo) {\n merged.assetsInfo = {\n ...(merged.assetsInfo ?? {}),\n ...response.assetsInfo,\n };\n }\n if (response.assetsPrice) {\n merged.assetsPrice = {\n ...(merged.assetsPrice ?? {}),\n ...response.assetsPrice,\n };\n }\n if (response.errors) {\n merged.errors = {\n ...(merged.errors ?? {}),\n ...response.errors,\n };\n }\n if (response.detectedAssets) {\n merged.detectedAssets = {\n ...(merged.detectedAssets ?? {}),\n ...response.detectedAssets,\n };\n }\n if (response.updateMode === 'full') {\n merged.updateMode = 'full';\n } else if (\n response.updateMode === 'merge' &&\n merged.updateMode !== 'full'\n ) {\n merged.updateMode = 'merge';\n }\n if (response.replaceCoveredChainBalances) {\n merged.replaceCoveredChainBalances = true;\n }\n }\n merged.updateMode ??= 'merge';\n\n return merged;\n}\n\n// ============================================================================\n// PARALLEL BALANCE MIDDLEWARE\n// ============================================================================\n\nconst PARALLEL_BALANCE_MIDDLEWARE_NAME = 'ParallelBalanceMiddleware';\n\n/** Max concurrent balance source calls (round 1 and fallback). */\nconst BALANCE_CONCURRENCY = 3;\n\nexport type BalanceSource = {\n getName(): string;\n /** Chains this source can fetch (e.g. from getActiveChainsSync()). Used to partition chains with no overlap. */\n getActiveChainsSync(): ChainId[];\n assetsMiddleware: Middleware;\n};\n\n/**\n * Partition request.chainIds so each chain is assigned to exactly one source\n * (by source order: first source that supports the chain gets it). Ensures no\n * chain overlap across data source calls.\n *\n * @param request - The data request with chainIds to partition.\n * @param sources - Balance sources in priority order (e.g. AccountsAPI, Snap, Rpc).\n * @returns Array of requests, one per source, each with only that source's assigned chainIds.\n */\nfunction partitionChainsBySource(\n request: DataRequest,\n sources: BalanceSource[],\n): DataRequest[] {\n const { chainIds } = request;\n const assigned = new Set<ChainId>();\n\n return sources.map((source) => {\n const supported = new Set(source.getActiveChainsSync());\n const chainsForSource = chainIds.filter(\n (id) => supported.has(id) && !assigned.has(id),\n );\n chainsForSource.forEach((id) => assigned.add(id));\n\n return {\n ...request,\n chainIds: chainsForSource,\n };\n });\n}\n\n/**\n * Collect chain IDs that failed in the first round (present in response.errors).\n * Used to run a fallback round with remaining sources.\n *\n * @param requests - Partitioned requests, one per source (same order as results).\n * @param results - Results from each source; chain IDs in requests[i] that have errors in results[i].response.errors are considered failed.\n * @returns Set of chain IDs that had errors in the first round.\n */\nfunction getFailedChainIds(\n requests: DataRequest[],\n results: { response: DataResponse }[],\n): Set<ChainId> {\n const failed = new Set<ChainId>();\n for (let i = 0; i < results.length; i++) {\n const errors = results[i].response.errors ?? {};\n for (const chainId of requests[i].chainIds) {\n if (errors[chainId]) {\n failed.add(chainId);\n }\n }\n }\n return failed;\n}\n\n/**\n * Middleware that runs multiple balance data source middlewares in parallel,\n * with no chain overlap. Chains that fail (response.errors) are re-partitioned\n * and fetched again in a fallback round so lower-priority sources can try them.\n *\n * @param sources - Array of balance sources in priority order (each with getName(), getActiveChainsSync(), assetsMiddleware).\n * @returns A single middleware that runs all sources in parallel and merges responses.\n */\nexport function createParallelBalanceMiddleware(sources: BalanceSource[]): {\n getName(): string;\n assetsMiddleware: Middleware;\n} {\n return {\n getName(): string {\n return PARALLEL_BALANCE_MIDDLEWARE_NAME;\n },\n\n assetsMiddleware: async (context, next): Promise<Context> => {\n if (sources.length === 0) {\n return next(context);\n }\n\n const noopNext = async (ctx: typeof context): Promise<typeof context> =>\n ctx;\n const limit = pLimit(BALANCE_CONCURRENCY);\n\n // Round 1: partition chains (no overlap), run with limited concurrency\n const requests = partitionChainsBySource(context.request, sources);\n const round1Timed = await Promise.all(\n sources.map((source, i) =>\n limit(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: requests[i],\n response: {},\n getAssetsState: context.getAssetsState,\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n const results = round1Timed.map((entry) => entry.result);\n\n const durationByDataSource: Record<string, number> = {\n ...context.durationByDataSource,\n };\n for (const { sourceName, durationMs } of round1Timed) {\n const key = `${PARALLEL_BALANCE_MIDDLEWARE_NAME}.${sourceName}`;\n durationByDataSource[key] =\n (durationByDataSource[key] ?? 0) + durationMs;\n }\n\n let mergedResponse = mergeDataResponses(\n results.map((result) => result.response),\n );\n\n // Fallback: chains that failed (in errors) get re-partitioned and tried again\n const failedChainIds = getFailedChainIds(requests, results);\n if (failedChainIds.size > 0) {\n const fallbackRequest: DataRequest = {\n ...context.request,\n chainIds: [...failedChainIds],\n };\n const fallbackRequests = partitionChainsBySource(\n fallbackRequest,\n sources,\n );\n const fallbackTimed = await Promise.all(\n sources.map((source, i) =>\n limit(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: fallbackRequests[i],\n response: {},\n getAssetsState: context.getAssetsState,\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n const fallbackResults = fallbackTimed.map((entry) => entry.result);\n for (const { sourceName, durationMs } of fallbackTimed) {\n const key = `${PARALLEL_BALANCE_MIDDLEWARE_NAME}.${sourceName}`;\n durationByDataSource[key] =\n (durationByDataSource[key] ?? 0) + durationMs;\n }\n const fallbackMerged = mergeDataResponses(\n fallbackResults.map((result) => result.response),\n );\n mergedResponse = mergeDataResponses([mergedResponse, fallbackMerged]);\n // Remove errors for chains we successfully got balance for in fallback\n if (mergedResponse.errors && mergedResponse.assetsBalance) {\n const chainsWithBalance = new Set<ChainId>();\n for (const accountBalances of Object.values(\n mergedResponse.assetsBalance,\n )) {\n for (const assetId of Object.keys(accountBalances)) {\n const chainId = assetId.split('/')[0] as ChainId;\n chainsWithBalance.add(chainId);\n }\n }\n for (const chainId of failedChainIds) {\n if (chainsWithBalance.has(chainId)) {\n delete mergedResponse.errors[chainId];\n }\n }\n }\n }\n\n return next({\n ...context,\n response: mergeDataResponses([context.response, mergedResponse]),\n durationByDataSource,\n });\n },\n };\n}\n\n// ============================================================================\n// PARALLEL TOKEN/PRICE MIDDLEWARE\n// ============================================================================\n\nconst PARALLEL_MIDDLEWARE_NAME = 'ParallelMiddleware';\n\n/** Max concurrent token/price source calls. */\nconst CONCURRENCY = 2;\n\n/**\n * Middleware that runs multiple data source middlewares (e.g. TokenDataSource,\n * PriceDataSource) in parallel with the same request. Responses are merged so\n * that assetsInfo (token metadata) and assetsPrice are combined. Use this to\n * fetch token and price data concurrently instead of sequentially.\n *\n * @param sources - Array of named middleware sources (getName + assetsMiddleware).\n * @returns A single middleware that runs all sources in parallel and merges responses.\n */\nexport function createParallelMiddleware(sources: AssetsDataSource[]): {\n getName(): string;\n assetsMiddleware: Middleware;\n} {\n return {\n getName(): string {\n return PARALLEL_MIDDLEWARE_NAME;\n },\n\n assetsMiddleware: async (context, next): Promise<Context> => {\n if (sources.length === 0) {\n return next(context);\n }\n\n const noopNext = async (ctx: typeof context): Promise<typeof context> =>\n ctx;\n const limit = pLimit(CONCURRENCY);\n\n const timedResults = await Promise.all(\n sources.map((source) =>\n limit(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: context.request,\n response: { ...context.response },\n getAssetsState: context.getAssetsState,\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n\n const results = timedResults.map((entry) => entry.result);\n const mergedResponse = mergeDataResponses(\n results.map((result) => result.response),\n );\n\n const durationByDataSource: Record<string, number> = {\n ...context.durationByDataSource,\n };\n for (const { sourceName, durationMs } of timedResults) {\n durationByDataSource[`${PARALLEL_MIDDLEWARE_NAME}.${sourceName}`] =\n durationMs;\n }\n\n return next({\n ...context,\n response: mergeDataResponses([context.response, mergedResponse]),\n durationByDataSource,\n });\n },\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"ParallelMiddleware.d.cts","sourceRoot":"","sources":["../../src/middlewares/ParallelMiddleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,OAAO,EAGP,YAAY,EACZ,UAAU,EACV,gBAAgB,EACjB,qBAAiB;AAMlB;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,YAAY,CA8C1E;AAWD,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,IAAI,MAAM,CAAC;IAClB,gHAAgH;IAChH,mBAAmB,IAAI,OAAO,EAAE,CAAC;IACjC,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAAC;AAwDF;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG;IACzE,OAAO,IAAI,MAAM,CAAC;IAClB,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAiHA;AAWD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG;IACrE,OAAO,IAAI,MAAM,CAAC;IAClB,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAqDA"}
1
+ {"version":3,"file":"ParallelMiddleware.d.cts","sourceRoot":"","sources":["../../src/middlewares/ParallelMiddleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,OAAO,EAGP,YAAY,EACZ,UAAU,EACV,gBAAgB,EACjB,qBAAiB;AAMlB;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,YAAY,CAsD1E;AAWD,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,IAAI,MAAM,CAAC;IAClB,gHAAgH;IAChH,mBAAmB,IAAI,OAAO,EAAE,CAAC;IACjC,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAAC;AAwDF;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG;IACzE,OAAO,IAAI,MAAM,CAAC;IAClB,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAiHA;AAWD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG;IACrE,OAAO,IAAI,MAAM,CAAC;IAClB,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAqDA"}
@@ -1 +1 @@
1
- {"version":3,"file":"ParallelMiddleware.d.mts","sourceRoot":"","sources":["../../src/middlewares/ParallelMiddleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,OAAO,EAGP,YAAY,EACZ,UAAU,EACV,gBAAgB,EACjB,qBAAiB;AAMlB;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,YAAY,CA8C1E;AAWD,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,IAAI,MAAM,CAAC;IAClB,gHAAgH;IAChH,mBAAmB,IAAI,OAAO,EAAE,CAAC;IACjC,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAAC;AAwDF;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG;IACzE,OAAO,IAAI,MAAM,CAAC;IAClB,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAiHA;AAWD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG;IACrE,OAAO,IAAI,MAAM,CAAC;IAClB,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAqDA"}
1
+ {"version":3,"file":"ParallelMiddleware.d.mts","sourceRoot":"","sources":["../../src/middlewares/ParallelMiddleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,OAAO,EAGP,YAAY,EACZ,UAAU,EACV,gBAAgB,EACjB,qBAAiB;AAMlB;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,YAAY,CAsD1E;AAWD,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,IAAI,MAAM,CAAC;IAClB,gHAAgH;IAChH,mBAAmB,IAAI,OAAO,EAAE,CAAC;IACjC,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAAC;AAwDF;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG;IACzE,OAAO,IAAI,MAAM,CAAC;IAClB,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAiHA;AAWD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG;IACrE,OAAO,IAAI,MAAM,CAAC;IAClB,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAqDA"}
@@ -55,6 +55,13 @@ export function mergeDataResponses(responses) {
55
55
  if (response.updateMode === 'full') {
56
56
  merged.updateMode = 'full';
57
57
  }
58
+ else if (response.updateMode === 'merge' &&
59
+ merged.updateMode !== 'full') {
60
+ merged.updateMode = 'merge';
61
+ }
62
+ if (response.replaceCoveredChainBalances) {
63
+ merged.replaceCoveredChainBalances = true;
64
+ }
58
65
  }
59
66
  merged.updateMode ?? (merged.updateMode = 'merge');
60
67
  return merged;
@@ -1 +1 @@
1
- {"version":3,"file":"ParallelMiddleware.mjs","sourceRoot":"","sources":["../../src/middlewares/ParallelMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import pLimit from 'p-limit';\n\nimport type {\n ChainId,\n Context,\n DataRequest,\n DataResponse,\n Middleware,\n AssetsDataSource,\n} from '../types';\n\n// ============================================================================\n// MERGE HELPER\n// ============================================================================\n\n/**\n * Deep-merge multiple DataResponses into one.\n * Used when running balance data sources in parallel.\n *\n * @param responses - Array of DataResponse from each source.\n * @returns Single merged DataResponse.\n */\nexport function mergeDataResponses(responses: DataResponse[]): DataResponse {\n const merged: DataResponse = {};\n\n for (const response of responses) {\n if (response.assetsBalance) {\n merged.assetsBalance ??= {};\n for (const [accountId, accountBalances] of Object.entries(\n response.assetsBalance,\n )) {\n merged.assetsBalance[accountId] = {\n ...(merged.assetsBalance[accountId] ?? {}),\n ...accountBalances,\n };\n }\n }\n if (response.assetsInfo) {\n merged.assetsInfo = {\n ...(merged.assetsInfo ?? {}),\n ...response.assetsInfo,\n };\n }\n if (response.assetsPrice) {\n merged.assetsPrice = {\n ...(merged.assetsPrice ?? {}),\n ...response.assetsPrice,\n };\n }\n if (response.errors) {\n merged.errors = {\n ...(merged.errors ?? {}),\n ...response.errors,\n };\n }\n if (response.detectedAssets) {\n merged.detectedAssets = {\n ...(merged.detectedAssets ?? {}),\n ...response.detectedAssets,\n };\n }\n if (response.updateMode === 'full') {\n merged.updateMode = 'full';\n }\n }\n merged.updateMode ??= 'merge';\n\n return merged;\n}\n\n// ============================================================================\n// PARALLEL BALANCE MIDDLEWARE\n// ============================================================================\n\nconst PARALLEL_BALANCE_MIDDLEWARE_NAME = 'ParallelBalanceMiddleware';\n\n/** Max concurrent balance source calls (round 1 and fallback). */\nconst BALANCE_CONCURRENCY = 3;\n\nexport type BalanceSource = {\n getName(): string;\n /** Chains this source can fetch (e.g. from getActiveChainsSync()). Used to partition chains with no overlap. */\n getActiveChainsSync(): ChainId[];\n assetsMiddleware: Middleware;\n};\n\n/**\n * Partition request.chainIds so each chain is assigned to exactly one source\n * (by source order: first source that supports the chain gets it). Ensures no\n * chain overlap across data source calls.\n *\n * @param request - The data request with chainIds to partition.\n * @param sources - Balance sources in priority order (e.g. AccountsAPI, Snap, Rpc).\n * @returns Array of requests, one per source, each with only that source's assigned chainIds.\n */\nfunction partitionChainsBySource(\n request: DataRequest,\n sources: BalanceSource[],\n): DataRequest[] {\n const { chainIds } = request;\n const assigned = new Set<ChainId>();\n\n return sources.map((source) => {\n const supported = new Set(source.getActiveChainsSync());\n const chainsForSource = chainIds.filter(\n (id) => supported.has(id) && !assigned.has(id),\n );\n chainsForSource.forEach((id) => assigned.add(id));\n\n return {\n ...request,\n chainIds: chainsForSource,\n };\n });\n}\n\n/**\n * Collect chain IDs that failed in the first round (present in response.errors).\n * Used to run a fallback round with remaining sources.\n *\n * @param requests - Partitioned requests, one per source (same order as results).\n * @param results - Results from each source; chain IDs in requests[i] that have errors in results[i].response.errors are considered failed.\n * @returns Set of chain IDs that had errors in the first round.\n */\nfunction getFailedChainIds(\n requests: DataRequest[],\n results: { response: DataResponse }[],\n): Set<ChainId> {\n const failed = new Set<ChainId>();\n for (let i = 0; i < results.length; i++) {\n const errors = results[i].response.errors ?? {};\n for (const chainId of requests[i].chainIds) {\n if (errors[chainId]) {\n failed.add(chainId);\n }\n }\n }\n return failed;\n}\n\n/**\n * Middleware that runs multiple balance data source middlewares in parallel,\n * with no chain overlap. Chains that fail (response.errors) are re-partitioned\n * and fetched again in a fallback round so lower-priority sources can try them.\n *\n * @param sources - Array of balance sources in priority order (each with getName(), getActiveChainsSync(), assetsMiddleware).\n * @returns A single middleware that runs all sources in parallel and merges responses.\n */\nexport function createParallelBalanceMiddleware(sources: BalanceSource[]): {\n getName(): string;\n assetsMiddleware: Middleware;\n} {\n return {\n getName(): string {\n return PARALLEL_BALANCE_MIDDLEWARE_NAME;\n },\n\n assetsMiddleware: async (context, next): Promise<Context> => {\n if (sources.length === 0) {\n return next(context);\n }\n\n const noopNext = async (ctx: typeof context): Promise<typeof context> =>\n ctx;\n const limit = pLimit(BALANCE_CONCURRENCY);\n\n // Round 1: partition chains (no overlap), run with limited concurrency\n const requests = partitionChainsBySource(context.request, sources);\n const round1Timed = await Promise.all(\n sources.map((source, i) =>\n limit(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: requests[i],\n response: {},\n getAssetsState: context.getAssetsState,\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n const results = round1Timed.map((entry) => entry.result);\n\n const durationByDataSource: Record<string, number> = {\n ...context.durationByDataSource,\n };\n for (const { sourceName, durationMs } of round1Timed) {\n const key = `${PARALLEL_BALANCE_MIDDLEWARE_NAME}.${sourceName}`;\n durationByDataSource[key] =\n (durationByDataSource[key] ?? 0) + durationMs;\n }\n\n let mergedResponse = mergeDataResponses(\n results.map((result) => result.response),\n );\n\n // Fallback: chains that failed (in errors) get re-partitioned and tried again\n const failedChainIds = getFailedChainIds(requests, results);\n if (failedChainIds.size > 0) {\n const fallbackRequest: DataRequest = {\n ...context.request,\n chainIds: [...failedChainIds],\n };\n const fallbackRequests = partitionChainsBySource(\n fallbackRequest,\n sources,\n );\n const fallbackTimed = await Promise.all(\n sources.map((source, i) =>\n limit(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: fallbackRequests[i],\n response: {},\n getAssetsState: context.getAssetsState,\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n const fallbackResults = fallbackTimed.map((entry) => entry.result);\n for (const { sourceName, durationMs } of fallbackTimed) {\n const key = `${PARALLEL_BALANCE_MIDDLEWARE_NAME}.${sourceName}`;\n durationByDataSource[key] =\n (durationByDataSource[key] ?? 0) + durationMs;\n }\n const fallbackMerged = mergeDataResponses(\n fallbackResults.map((result) => result.response),\n );\n mergedResponse = mergeDataResponses([mergedResponse, fallbackMerged]);\n // Remove errors for chains we successfully got balance for in fallback\n if (mergedResponse.errors && mergedResponse.assetsBalance) {\n const chainsWithBalance = new Set<ChainId>();\n for (const accountBalances of Object.values(\n mergedResponse.assetsBalance,\n )) {\n for (const assetId of Object.keys(accountBalances)) {\n const chainId = assetId.split('/')[0] as ChainId;\n chainsWithBalance.add(chainId);\n }\n }\n for (const chainId of failedChainIds) {\n if (chainsWithBalance.has(chainId)) {\n delete mergedResponse.errors[chainId];\n }\n }\n }\n }\n\n return next({\n ...context,\n response: mergeDataResponses([context.response, mergedResponse]),\n durationByDataSource,\n });\n },\n };\n}\n\n// ============================================================================\n// PARALLEL TOKEN/PRICE MIDDLEWARE\n// ============================================================================\n\nconst PARALLEL_MIDDLEWARE_NAME = 'ParallelMiddleware';\n\n/** Max concurrent token/price source calls. */\nconst CONCURRENCY = 2;\n\n/**\n * Middleware that runs multiple data source middlewares (e.g. TokenDataSource,\n * PriceDataSource) in parallel with the same request. Responses are merged so\n * that assetsInfo (token metadata) and assetsPrice are combined. Use this to\n * fetch token and price data concurrently instead of sequentially.\n *\n * @param sources - Array of named middleware sources (getName + assetsMiddleware).\n * @returns A single middleware that runs all sources in parallel and merges responses.\n */\nexport function createParallelMiddleware(sources: AssetsDataSource[]): {\n getName(): string;\n assetsMiddleware: Middleware;\n} {\n return {\n getName(): string {\n return PARALLEL_MIDDLEWARE_NAME;\n },\n\n assetsMiddleware: async (context, next): Promise<Context> => {\n if (sources.length === 0) {\n return next(context);\n }\n\n const noopNext = async (ctx: typeof context): Promise<typeof context> =>\n ctx;\n const limit = pLimit(CONCURRENCY);\n\n const timedResults = await Promise.all(\n sources.map((source) =>\n limit(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: context.request,\n response: { ...context.response },\n getAssetsState: context.getAssetsState,\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n\n const results = timedResults.map((entry) => entry.result);\n const mergedResponse = mergeDataResponses(\n results.map((result) => result.response),\n );\n\n const durationByDataSource: Record<string, number> = {\n ...context.durationByDataSource,\n };\n for (const { sourceName, durationMs } of timedResults) {\n durationByDataSource[`${PARALLEL_MIDDLEWARE_NAME}.${sourceName}`] =\n durationMs;\n }\n\n return next({\n ...context,\n response: mergeDataResponses([context.response, mergedResponse]),\n durationByDataSource,\n });\n },\n };\n}\n"]}
1
+ {"version":3,"file":"ParallelMiddleware.mjs","sourceRoot":"","sources":["../../src/middlewares/ParallelMiddleware.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,OAAM,yBAAgB;;AAW7B,+EAA+E;AAC/E,eAAe;AACf,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAyB;IAC1D,MAAM,MAAM,GAAiB,EAAE,CAAC;IAEhC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;YAC3B,MAAM,CAAC,aAAa,KAApB,MAAM,CAAC,aAAa,GAAK,EAAE,EAAC;YAC5B,KAAK,MAAM,CAAC,SAAS,EAAE,eAAe,CAAC,IAAI,MAAM,CAAC,OAAO,CACvD,QAAQ,CAAC,aAAa,CACvB,EAAE,CAAC;gBACF,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,GAAG;oBAChC,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;oBAC1C,GAAG,eAAe;iBACnB,CAAC;YACJ,CAAC;QACH,CAAC;QACD,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;YACxB,MAAM,CAAC,UAAU,GAAG;gBAClB,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC;gBAC5B,GAAG,QAAQ,CAAC,UAAU;aACvB,CAAC;QACJ,CAAC;QACD,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;YACzB,MAAM,CAAC,WAAW,GAAG;gBACnB,GAAG,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;gBAC7B,GAAG,QAAQ,CAAC,WAAW;aACxB,CAAC;QACJ,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;YACpB,MAAM,CAAC,MAAM,GAAG;gBACd,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;gBACxB,GAAG,QAAQ,CAAC,MAAM;aACnB,CAAC;QACJ,CAAC;QACD,IAAI,QAAQ,CAAC,cAAc,EAAE,CAAC;YAC5B,MAAM,CAAC,cAAc,GAAG;gBACtB,GAAG,CAAC,MAAM,CAAC,cAAc,IAAI,EAAE,CAAC;gBAChC,GAAG,QAAQ,CAAC,cAAc;aAC3B,CAAC;QACJ,CAAC;QACD,IAAI,QAAQ,CAAC,UAAU,KAAK,MAAM,EAAE,CAAC;YACnC,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC;QAC7B,CAAC;aAAM,IACL,QAAQ,CAAC,UAAU,KAAK,OAAO;YAC/B,MAAM,CAAC,UAAU,KAAK,MAAM,EAC5B,CAAC;YACD,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC;QAC9B,CAAC;QACD,IAAI,QAAQ,CAAC,2BAA2B,EAAE,CAAC;YACzC,MAAM,CAAC,2BAA2B,GAAG,IAAI,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,MAAM,CAAC,UAAU,KAAjB,MAAM,CAAC,UAAU,GAAK,OAAO,EAAC;IAE9B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,+EAA+E;AAC/E,8BAA8B;AAC9B,+EAA+E;AAE/E,MAAM,gCAAgC,GAAG,2BAA2B,CAAC;AAErE,kEAAkE;AAClE,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAS9B;;;;;;;;GAQG;AACH,SAAS,uBAAuB,CAC9B,OAAoB,EACpB,OAAwB;IAExB,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAC7B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAW,CAAC;IAEpC,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QAC5B,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,mBAAmB,EAAE,CAAC,CAAC;QACxD,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CACrC,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAC/C,CAAC;QACF,eAAe,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAElD,OAAO;YACL,GAAG,OAAO;YACV,QAAQ,EAAE,eAAe;SAC1B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,iBAAiB,CACxB,QAAuB,EACvB,OAAqC;IAErC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAW,CAAC;IAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC;QAChD,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;YAC3C,IAAI,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;gBACpB,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,+BAA+B,CAAC,OAAwB;IAItE,OAAO;QACL,OAAO;YACL,OAAO,gCAAgC,CAAC;QAC1C,CAAC;QAED,gBAAgB,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAoB,EAAE;YAC1D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzB,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,CAAC;YAED,MAAM,QAAQ,GAAG,KAAK,EAAE,GAAmB,EAA2B,EAAE,CACtE,GAAG,CAAC;YACN,MAAM,KAAK,GAAG,MAAM,CAAC,mBAAmB,CAAC,CAAC;YAE1C,uEAAuE;YACvE,MAAM,QAAQ,GAAG,uBAAuB,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACnE,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,GAAG,CACnC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CACxB,KAAK,CAAC,KAAK,IAAI,EAAE;gBACf,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBACzB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAC1C;oBACE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;oBACpB,QAAQ,EAAE,EAAE;oBACZ,cAAc,EAAE,OAAO,CAAC,cAAc;iBACvC,EACD,QAAQ,CACT,CAAC;gBACF,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;gBACtC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,CAAC;YAC9D,CAAC,CAAC,CACH,CACF,CAAC;YACF,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAEzD,MAAM,oBAAoB,GAA2B;gBACnD,GAAG,OAAO,CAAC,oBAAoB;aAChC,CAAC;YACF,KAAK,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,WAAW,EAAE,CAAC;gBACrD,MAAM,GAAG,GAAG,GAAG,gCAAgC,IAAI,UAAU,EAAE,CAAC;gBAChE,oBAAoB,CAAC,GAAG,CAAC;oBACvB,CAAC,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,UAAU,CAAC;YAClD,CAAC;YAED,IAAI,cAAc,GAAG,kBAAkB,CACrC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CACzC,CAAC;YAEF,8EAA8E;YAC9E,MAAM,cAAc,GAAG,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAC5D,IAAI,cAAc,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBAC5B,MAAM,eAAe,GAAgB;oBACnC,GAAG,OAAO,CAAC,OAAO;oBAClB,QAAQ,EAAE,CAAC,GAAG,cAAc,CAAC;iBAC9B,CAAC;gBACF,MAAM,gBAAgB,GAAG,uBAAuB,CAC9C,eAAe,EACf,OAAO,CACR,CAAC;gBACF,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,GAAG,CACrC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CACxB,KAAK,CAAC,KAAK,IAAI,EAAE;oBACf,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;oBACzB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAC1C;wBACE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC;wBAC5B,QAAQ,EAAE,EAAE;wBACZ,cAAc,EAAE,OAAO,CAAC,cAAc;qBACvC,EACD,QAAQ,CACT,CAAC;oBACF,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;oBACtC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,CAAC;gBAC9D,CAAC,CAAC,CACH,CACF,CAAC;gBACF,MAAM,eAAe,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBACnE,KAAK,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,aAAa,EAAE,CAAC;oBACvD,MAAM,GAAG,GAAG,GAAG,gCAAgC,IAAI,UAAU,EAAE,CAAC;oBAChE,oBAAoB,CAAC,GAAG,CAAC;wBACvB,CAAC,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,UAAU,CAAC;gBAClD,CAAC;gBACD,MAAM,cAAc,GAAG,kBAAkB,CACvC,eAAe,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CACjD,CAAC;gBACF,cAAc,GAAG,kBAAkB,CAAC,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC;gBACtE,uEAAuE;gBACvE,IAAI,cAAc,CAAC,MAAM,IAAI,cAAc,CAAC,aAAa,EAAE,CAAC;oBAC1D,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAW,CAAC;oBAC7C,KAAK,MAAM,eAAe,IAAI,MAAM,CAAC,MAAM,CACzC,cAAc,CAAC,aAAa,CAC7B,EAAE,CAAC;wBACF,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;4BACnD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAY,CAAC;4BACjD,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;wBACjC,CAAC;oBACH,CAAC;oBACD,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;wBACrC,IAAI,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;4BACnC,OAAO,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;wBACxC,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAED,OAAO,IAAI,CAAC;gBACV,GAAG,OAAO;gBACV,QAAQ,EAAE,kBAAkB,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;gBAChE,oBAAoB;aACrB,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,kCAAkC;AAClC,+EAA+E;AAE/E,MAAM,wBAAwB,GAAG,oBAAoB,CAAC;AAEtD,+CAA+C;AAC/C,MAAM,WAAW,GAAG,CAAC,CAAC;AAEtB;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAA2B;IAIlE,OAAO;QACL,OAAO;YACL,OAAO,wBAAwB,CAAC;QAClC,CAAC;QAED,gBAAgB,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAoB,EAAE;YAC1D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzB,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,CAAC;YAED,MAAM,QAAQ,GAAG,KAAK,EAAE,GAAmB,EAA2B,EAAE,CACtE,GAAG,CAAC;YACN,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;YAElC,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,GAAG,CACpC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CACrB,KAAK,CAAC,KAAK,IAAI,EAAE;gBACf,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBACzB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAC1C;oBACE,OAAO,EAAE,OAAO,CAAC,OAAO;oBACxB,QAAQ,EAAE,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE;oBACjC,cAAc,EAAE,OAAO,CAAC,cAAc;iBACvC,EACD,QAAQ,CACT,CAAC;gBACF,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;gBACtC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,CAAC;YAC9D,CAAC,CAAC,CACH,CACF,CAAC;YAEF,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC1D,MAAM,cAAc,GAAG,kBAAkB,CACvC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CACzC,CAAC;YAEF,MAAM,oBAAoB,GAA2B;gBACnD,GAAG,OAAO,CAAC,oBAAoB;aAChC,CAAC;YACF,KAAK,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,YAAY,EAAE,CAAC;gBACtD,oBAAoB,CAAC,GAAG,wBAAwB,IAAI,UAAU,EAAE,CAAC;oBAC/D,UAAU,CAAC;YACf,CAAC;YAED,OAAO,IAAI,CAAC;gBACV,GAAG,OAAO;gBACV,QAAQ,EAAE,kBAAkB,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;gBAChE,oBAAoB;aACrB,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import pLimit from 'p-limit';\n\nimport type {\n ChainId,\n Context,\n DataRequest,\n DataResponse,\n Middleware,\n AssetsDataSource,\n} from '../types';\n\n// ============================================================================\n// MERGE HELPER\n// ============================================================================\n\n/**\n * Deep-merge multiple DataResponses into one.\n * Used when running balance data sources in parallel.\n *\n * @param responses - Array of DataResponse from each source.\n * @returns Single merged DataResponse.\n */\nexport function mergeDataResponses(responses: DataResponse[]): DataResponse {\n const merged: DataResponse = {};\n\n for (const response of responses) {\n if (response.assetsBalance) {\n merged.assetsBalance ??= {};\n for (const [accountId, accountBalances] of Object.entries(\n response.assetsBalance,\n )) {\n merged.assetsBalance[accountId] = {\n ...(merged.assetsBalance[accountId] ?? {}),\n ...accountBalances,\n };\n }\n }\n if (response.assetsInfo) {\n merged.assetsInfo = {\n ...(merged.assetsInfo ?? {}),\n ...response.assetsInfo,\n };\n }\n if (response.assetsPrice) {\n merged.assetsPrice = {\n ...(merged.assetsPrice ?? {}),\n ...response.assetsPrice,\n };\n }\n if (response.errors) {\n merged.errors = {\n ...(merged.errors ?? {}),\n ...response.errors,\n };\n }\n if (response.detectedAssets) {\n merged.detectedAssets = {\n ...(merged.detectedAssets ?? {}),\n ...response.detectedAssets,\n };\n }\n if (response.updateMode === 'full') {\n merged.updateMode = 'full';\n } else if (\n response.updateMode === 'merge' &&\n merged.updateMode !== 'full'\n ) {\n merged.updateMode = 'merge';\n }\n if (response.replaceCoveredChainBalances) {\n merged.replaceCoveredChainBalances = true;\n }\n }\n merged.updateMode ??= 'merge';\n\n return merged;\n}\n\n// ============================================================================\n// PARALLEL BALANCE MIDDLEWARE\n// ============================================================================\n\nconst PARALLEL_BALANCE_MIDDLEWARE_NAME = 'ParallelBalanceMiddleware';\n\n/** Max concurrent balance source calls (round 1 and fallback). */\nconst BALANCE_CONCURRENCY = 3;\n\nexport type BalanceSource = {\n getName(): string;\n /** Chains this source can fetch (e.g. from getActiveChainsSync()). Used to partition chains with no overlap. */\n getActiveChainsSync(): ChainId[];\n assetsMiddleware: Middleware;\n};\n\n/**\n * Partition request.chainIds so each chain is assigned to exactly one source\n * (by source order: first source that supports the chain gets it). Ensures no\n * chain overlap across data source calls.\n *\n * @param request - The data request with chainIds to partition.\n * @param sources - Balance sources in priority order (e.g. AccountsAPI, Snap, Rpc).\n * @returns Array of requests, one per source, each with only that source's assigned chainIds.\n */\nfunction partitionChainsBySource(\n request: DataRequest,\n sources: BalanceSource[],\n): DataRequest[] {\n const { chainIds } = request;\n const assigned = new Set<ChainId>();\n\n return sources.map((source) => {\n const supported = new Set(source.getActiveChainsSync());\n const chainsForSource = chainIds.filter(\n (id) => supported.has(id) && !assigned.has(id),\n );\n chainsForSource.forEach((id) => assigned.add(id));\n\n return {\n ...request,\n chainIds: chainsForSource,\n };\n });\n}\n\n/**\n * Collect chain IDs that failed in the first round (present in response.errors).\n * Used to run a fallback round with remaining sources.\n *\n * @param requests - Partitioned requests, one per source (same order as results).\n * @param results - Results from each source; chain IDs in requests[i] that have errors in results[i].response.errors are considered failed.\n * @returns Set of chain IDs that had errors in the first round.\n */\nfunction getFailedChainIds(\n requests: DataRequest[],\n results: { response: DataResponse }[],\n): Set<ChainId> {\n const failed = new Set<ChainId>();\n for (let i = 0; i < results.length; i++) {\n const errors = results[i].response.errors ?? {};\n for (const chainId of requests[i].chainIds) {\n if (errors[chainId]) {\n failed.add(chainId);\n }\n }\n }\n return failed;\n}\n\n/**\n * Middleware that runs multiple balance data source middlewares in parallel,\n * with no chain overlap. Chains that fail (response.errors) are re-partitioned\n * and fetched again in a fallback round so lower-priority sources can try them.\n *\n * @param sources - Array of balance sources in priority order (each with getName(), getActiveChainsSync(), assetsMiddleware).\n * @returns A single middleware that runs all sources in parallel and merges responses.\n */\nexport function createParallelBalanceMiddleware(sources: BalanceSource[]): {\n getName(): string;\n assetsMiddleware: Middleware;\n} {\n return {\n getName(): string {\n return PARALLEL_BALANCE_MIDDLEWARE_NAME;\n },\n\n assetsMiddleware: async (context, next): Promise<Context> => {\n if (sources.length === 0) {\n return next(context);\n }\n\n const noopNext = async (ctx: typeof context): Promise<typeof context> =>\n ctx;\n const limit = pLimit(BALANCE_CONCURRENCY);\n\n // Round 1: partition chains (no overlap), run with limited concurrency\n const requests = partitionChainsBySource(context.request, sources);\n const round1Timed = await Promise.all(\n sources.map((source, i) =>\n limit(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: requests[i],\n response: {},\n getAssetsState: context.getAssetsState,\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n const results = round1Timed.map((entry) => entry.result);\n\n const durationByDataSource: Record<string, number> = {\n ...context.durationByDataSource,\n };\n for (const { sourceName, durationMs } of round1Timed) {\n const key = `${PARALLEL_BALANCE_MIDDLEWARE_NAME}.${sourceName}`;\n durationByDataSource[key] =\n (durationByDataSource[key] ?? 0) + durationMs;\n }\n\n let mergedResponse = mergeDataResponses(\n results.map((result) => result.response),\n );\n\n // Fallback: chains that failed (in errors) get re-partitioned and tried again\n const failedChainIds = getFailedChainIds(requests, results);\n if (failedChainIds.size > 0) {\n const fallbackRequest: DataRequest = {\n ...context.request,\n chainIds: [...failedChainIds],\n };\n const fallbackRequests = partitionChainsBySource(\n fallbackRequest,\n sources,\n );\n const fallbackTimed = await Promise.all(\n sources.map((source, i) =>\n limit(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: fallbackRequests[i],\n response: {},\n getAssetsState: context.getAssetsState,\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n const fallbackResults = fallbackTimed.map((entry) => entry.result);\n for (const { sourceName, durationMs } of fallbackTimed) {\n const key = `${PARALLEL_BALANCE_MIDDLEWARE_NAME}.${sourceName}`;\n durationByDataSource[key] =\n (durationByDataSource[key] ?? 0) + durationMs;\n }\n const fallbackMerged = mergeDataResponses(\n fallbackResults.map((result) => result.response),\n );\n mergedResponse = mergeDataResponses([mergedResponse, fallbackMerged]);\n // Remove errors for chains we successfully got balance for in fallback\n if (mergedResponse.errors && mergedResponse.assetsBalance) {\n const chainsWithBalance = new Set<ChainId>();\n for (const accountBalances of Object.values(\n mergedResponse.assetsBalance,\n )) {\n for (const assetId of Object.keys(accountBalances)) {\n const chainId = assetId.split('/')[0] as ChainId;\n chainsWithBalance.add(chainId);\n }\n }\n for (const chainId of failedChainIds) {\n if (chainsWithBalance.has(chainId)) {\n delete mergedResponse.errors[chainId];\n }\n }\n }\n }\n\n return next({\n ...context,\n response: mergeDataResponses([context.response, mergedResponse]),\n durationByDataSource,\n });\n },\n };\n}\n\n// ============================================================================\n// PARALLEL TOKEN/PRICE MIDDLEWARE\n// ============================================================================\n\nconst PARALLEL_MIDDLEWARE_NAME = 'ParallelMiddleware';\n\n/** Max concurrent token/price source calls. */\nconst CONCURRENCY = 2;\n\n/**\n * Middleware that runs multiple data source middlewares (e.g. TokenDataSource,\n * PriceDataSource) in parallel with the same request. Responses are merged so\n * that assetsInfo (token metadata) and assetsPrice are combined. Use this to\n * fetch token and price data concurrently instead of sequentially.\n *\n * @param sources - Array of named middleware sources (getName + assetsMiddleware).\n * @returns A single middleware that runs all sources in parallel and merges responses.\n */\nexport function createParallelMiddleware(sources: AssetsDataSource[]): {\n getName(): string;\n assetsMiddleware: Middleware;\n} {\n return {\n getName(): string {\n return PARALLEL_MIDDLEWARE_NAME;\n },\n\n assetsMiddleware: async (context, next): Promise<Context> => {\n if (sources.length === 0) {\n return next(context);\n }\n\n const noopNext = async (ctx: typeof context): Promise<typeof context> =>\n ctx;\n const limit = pLimit(CONCURRENCY);\n\n const timedResults = await Promise.all(\n sources.map((source) =>\n limit(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: context.request,\n response: { ...context.response },\n getAssetsState: context.getAssetsState,\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n\n const results = timedResults.map((entry) => entry.result);\n const mergedResponse = mergeDataResponses(\n results.map((result) => result.response),\n );\n\n const durationByDataSource: Record<string, number> = {\n ...context.durationByDataSource,\n };\n for (const { sourceName, durationMs } of timedResults) {\n durationByDataSource[`${PARALLEL_MIDDLEWARE_NAME}.${sourceName}`] =\n durationMs;\n }\n\n return next({\n ...context,\n response: mergeDataResponses([context.response, mergedResponse]),\n durationByDataSource,\n });\n },\n };\n}\n"]}