diff --git a/packages/core-backend/CHANGELOG.md b/packages/core-backend/CHANGELOG.md index 607833768cd..b21394178b0 100644 --- a/packages/core-backend/CHANGELOG.md +++ b/packages/core-backend/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add optional `apiUrls` option to `ApiPlatformClientOptions`, allowing API base URLs (accounts, prices, token, tokens) to be overridden per client instance, e.g. from client env vars; unspecified services fall back to the production `API_URLS` ([#10196](https://github.com/MetaMask/core/pull/10196)) + ### Changed - **BREAKING:** Accounts API `V2SupportedNetworksResponse` now uses CAIP-2 string arrays for both `fullSupport` and `partialSupport`, replacing decimal `fullSupport` and object-shaped `partialSupport.balances` ([#10144](https://github.com/MetaMask/core/pull/10144)) diff --git a/packages/core-backend/src/api/accounts/client.ts b/packages/core-backend/src/api/accounts/client.ts index 4b5468abbd1..9c4765035ec 100644 --- a/packages/core-backend/src/api/accounts/client.ts +++ b/packages/core-backend/src/api/accounts/client.ts @@ -17,12 +17,7 @@ import type { QueryFunctionContext, } from '@tanstack/query-core'; -import { - BaseApiClient, - API_URLS, - STALE_TIMES, - GC_TIMES, -} from '../base-client.js'; +import { BaseApiClient, STALE_TIMES, GC_TIMES } from '../base-client.js'; import { getQueryOptionsOverrides } from '../shared-types.js'; import type { FetchOptions } from '../shared-types.js'; import type { @@ -97,7 +92,7 @@ export class AccountsApiClient extends BaseApiClient { queryKey: ['accounts', 'v1SupportedNetworks'], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.ACCOUNTS, + this.apiUrls.ACCOUNTS, '/v1/supportedNetworks', { signal }, ), @@ -134,7 +129,7 @@ export class AccountsApiClient extends BaseApiClient { queryKey: ['accounts', 'v2SupportedNetworks'], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.ACCOUNTS, + this.apiUrls.ACCOUNTS, '/v2/supportedNetworks', { signal }, ), @@ -198,7 +193,7 @@ export class AccountsApiClient extends BaseApiClient { return { activeNetworks: [] }; } return this.fetch( - API_URLS.ACCOUNTS, + this.apiUrls.ACCOUNTS, '/v2/activeNetworks', { signal, @@ -289,7 +284,7 @@ export class AccountsApiClient extends BaseApiClient { return { count: 0, balances: [], unprocessedNetworks: [] }; } return this.fetch( - API_URLS.ACCOUNTS, + this.apiUrls.ACCOUNTS, `/v2/accounts/${address}/balances`, { signal, @@ -373,7 +368,7 @@ export class AccountsApiClient extends BaseApiClient { return { count: 0, balances: [], unprocessedNetworks: [] }; } return this.fetch( - API_URLS.ACCOUNTS, + this.apiUrls.ACCOUNTS, '/v4/multiaccount/balances', { signal, @@ -457,7 +452,7 @@ export class AccountsApiClient extends BaseApiClient { return { count: 0, unprocessedNetworks: [], balances: [] }; } return this.fetch( - API_URLS.ACCOUNTS, + this.apiUrls.ACCOUNTS, '/v5/multiaccount/balances', { signal, @@ -584,7 +579,7 @@ export class AccountsApiClient extends BaseApiClient { }; } return this.fetch( - API_URLS.ACCOUNTS, + this.apiUrls.ACCOUNTS, '/v6/multiaccount/balances', { signal, @@ -709,7 +704,7 @@ export class AccountsApiClient extends BaseApiClient { ], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.ACCOUNTS, + this.apiUrls.ACCOUNTS, `/v1/networks/${chainId}/transactions/${txHash}`, { signal, @@ -806,7 +801,7 @@ export class AccountsApiClient extends BaseApiClient { return { data: [], pageInfo: { count: 0, hasNextPage: false } }; } return this.fetch( - API_URLS.ACCOUNTS, + this.apiUrls.ACCOUNTS, `/v1/accounts/${address}/transactions`, { signal, @@ -914,7 +909,7 @@ export class AccountsApiClient extends BaseApiClient { ], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.ACCOUNTS, + this.apiUrls.ACCOUNTS, '/v4/multiaccount/transactions', { signal, @@ -1006,7 +1001,7 @@ export class AccountsApiClient extends BaseApiClient { signal?: AbortSignal; }) => this.fetch( - API_URLS.ACCOUNTS, + this.apiUrls.ACCOUNTS, '/v4/multiaccount/transactions', { signal, @@ -1106,7 +1101,7 @@ export class AccountsApiClient extends BaseApiClient { signal, }: QueryFunctionContext): Promise => this.fetch( - API_URLS.ACCOUNTS, + this.apiUrls.ACCOUNTS, `/v1/networks/${chainId}/accounts/${from}/relationships/${to}`, { signal }, ), @@ -1175,7 +1170,7 @@ export class AccountsApiClient extends BaseApiClient { return { data: [], pageInfo: { count: 0, hasNextPage: false } }; } return this.fetch( - API_URLS.ACCOUNTS, + this.apiUrls.ACCOUNTS, `/v2/accounts/${address}/nfts`, { signal, @@ -1253,7 +1248,7 @@ export class AccountsApiClient extends BaseApiClient { return { data: [] }; } return this.fetch( - API_URLS.ACCOUNTS, + this.apiUrls.ACCOUNTS, `/v2/accounts/${address}/tokens`, { signal, diff --git a/packages/core-backend/src/api/base-client.test.ts b/packages/core-backend/src/api/base-client.test.ts index 60150c44849..ac5bd082585 100644 --- a/packages/core-backend/src/api/base-client.test.ts +++ b/packages/core-backend/src/api/base-client.test.ts @@ -76,6 +76,40 @@ describe('BaseApiClient', () => { }); }); + describe('API URL overrides', () => { + beforeEach(() => { + mockFetch.mockReset(); + mockFetch.mockResolvedValue( + createMockResponse({ supportedNetworks: [] }), + ); + }); + + it('uses the production URL by default', async () => { + const client = new AccountsApiClient({ + clientProduct: 'test-product', + }); + + await client.fetchV1SupportedNetworks(); + + expect(String(mockFetch.mock.calls[0]?.[0])).toMatch( + 'https://accounts.api.cx.metamask.io/', + ); + }); + + it('uses the overridden URL when apiUrls is provided', async () => { + const client = new AccountsApiClient({ + clientProduct: 'test-product', + apiUrls: { ACCOUNTS: 'https://accounts.dev-api.cx.metamask.io' }, + }); + + await client.fetchV1SupportedNetworks(); + + expect(String(mockFetch.mock.calls[0]?.[0])).toMatch( + 'https://accounts.dev-api.cx.metamask.io/', + ); + }); + }); + describe('bearer token timeout', () => { /** * Creates a client whose token provider only resolves when told to. diff --git a/packages/core-backend/src/api/base-client.ts b/packages/core-backend/src/api/base-client.ts index a5636a39477..54a429a3180 100644 --- a/packages/core-backend/src/api/base-client.ts +++ b/packages/core-backend/src/api/base-client.ts @@ -14,7 +14,7 @@ import { shouldRetry, HttpError, } from './shared-types.js'; -import type { ApiPlatformClientOptions } from './shared-types.js'; +import type { ApiPlatformClientOptions, ApiUrls } from './shared-types.js'; // Auth query keys - shared for token management across clients export const authQueryKeys = { @@ -47,6 +47,9 @@ export class BaseApiClient { protected readonly authTokenTimeout: number; + /** Resolved API base URLs (production defaults merged with any overrides). */ + readonly apiUrls: ApiUrls; + readonly #queryClientInstance: QueryClient; /** @@ -78,6 +81,7 @@ export class BaseApiClient { this.getBearerToken = options.getBearerToken; this.authTokenTimeout = options.authTokenTimeout ?? DEFAULT_AUTH_TOKEN_TIMEOUT; + this.apiUrls = { ...API_URLS, ...options.apiUrls }; this.#queryClientInstance = options.queryClient ?? @@ -213,5 +217,5 @@ export class BaseApiClient { } } -// Re-export constants for use by API clients -export { API_URLS, STALE_TIMES, GC_TIMES, HttpError }; +// Re-export cache timings for use by API clients +export { STALE_TIMES, GC_TIMES }; diff --git a/packages/core-backend/src/api/index.ts b/packages/core-backend/src/api/index.ts index b0d78234e1a..cd4c15db4f8 100644 --- a/packages/core-backend/src/api/index.ts +++ b/packages/core-backend/src/api/index.ts @@ -9,6 +9,7 @@ export type { SupportedCurrency, MarketDataDetails, ApiPlatformClientOptions, + ApiUrls, FetchOptions, } from './shared-types.js'; export { diff --git a/packages/core-backend/src/api/prices/client.ts b/packages/core-backend/src/api/prices/client.ts index f8cc86fa993..693bff25438 100644 --- a/packages/core-backend/src/api/prices/client.ts +++ b/packages/core-backend/src/api/prices/client.ts @@ -14,12 +14,7 @@ import type { QueryFunctionContext, } from '@tanstack/query-core'; -import { - BaseApiClient, - API_URLS, - STALE_TIMES, - GC_TIMES, -} from '../base-client.js'; +import { BaseApiClient, STALE_TIMES, GC_TIMES } from '../base-client.js'; import { getQueryOptionsOverrides } from '../shared-types.js'; import type { FetchOptions, @@ -70,7 +65,7 @@ export class PricesApiClient extends BaseApiClient { queryKey: ['prices', 'v1SupportedNetworks'], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.PRICES, + this.apiUrls.PRICES, '/v1/supportedNetworks', { signal }, ), @@ -107,7 +102,7 @@ export class PricesApiClient extends BaseApiClient { queryKey: ['prices', 'v2SupportedNetworks'], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.PRICES, + this.apiUrls.PRICES, '/v2/supportedNetworks', { signal }, ), @@ -155,7 +150,7 @@ export class PricesApiClient extends BaseApiClient { return {}; } return this.fetch( - API_URLS.PRICES, + this.apiUrls.PRICES, '/v1/exchange-rates', { signal, @@ -201,7 +196,7 @@ export class PricesApiClient extends BaseApiClient { queryKey: ['prices', 'v1FiatExchangeRates'], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.PRICES, + this.apiUrls.PRICES, '/v1/exchange-rates/fiat', { signal }, ), @@ -238,7 +233,7 @@ export class PricesApiClient extends BaseApiClient { queryKey: ['prices', 'v1CryptoExchangeRates'], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.PRICES, + this.apiUrls.PRICES, '/v1/exchange-rates/crypto', { signal }, ), @@ -290,7 +285,7 @@ export class PricesApiClient extends BaseApiClient { return {}; } return this.fetch>( - API_URLS.PRICES, + this.apiUrls.PRICES, '/v1/spot-prices', { signal, @@ -345,7 +340,7 @@ export class PricesApiClient extends BaseApiClient { return { id: '', price: 0 }; } return this.fetch( - API_URLS.PRICES, + this.apiUrls.PRICES, `/v1/spot-prices/${coinId}`, { signal, @@ -423,7 +418,7 @@ export class PricesApiClient extends BaseApiClient { return {}; } return this.fetch>>( - API_URLS.PRICES, + this.apiUrls.PRICES, `/v1/chains/${chainIdDecimal}/spot-prices`, { signal, @@ -496,7 +491,7 @@ export class PricesApiClient extends BaseApiClient { signal, }: QueryFunctionContext): Promise => { return this.fetch( - API_URLS.PRICES, + this.apiUrls.PRICES, `/v1/chains/${chainIdDecimal}/spot-prices/${tokenAddress}`, { signal, @@ -580,7 +575,7 @@ export class PricesApiClient extends BaseApiClient { return {}; } return this.fetch>( - API_URLS.PRICES, + this.apiUrls.PRICES, `/v2/chains/${chainIdDecimal}/spot-prices`, { signal, @@ -676,7 +671,7 @@ export class PricesApiClient extends BaseApiClient { return {}; } return this.fetch( - API_URLS.PRICES, + this.apiUrls.PRICES, '/v3/spot-prices', { signal, @@ -753,7 +748,7 @@ export class PricesApiClient extends BaseApiClient { queryKey: ['prices', 'v1HistoricalByCoinId', coinId, queryOptions], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.PRICES, + this.apiUrls.PRICES, `/v1/historical-prices/${coinId}`, { signal, @@ -839,7 +834,7 @@ export class PricesApiClient extends BaseApiClient { ], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.PRICES, + this.apiUrls.PRICES, `/v1/chains/${chainIdDecimal}/historical-prices`, { signal, @@ -923,7 +918,7 @@ export class PricesApiClient extends BaseApiClient { ], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.PRICES, + this.apiUrls.PRICES, `/v1/chains/${chainIdDecimal}/historical-prices/${tokenAddress}`, { signal, @@ -1000,7 +995,7 @@ export class PricesApiClient extends BaseApiClient { queryKey: ['prices', 'v3Historical', chainId, assetType, queryOptions], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.PRICES, + this.apiUrls.PRICES, `/v3/historical-prices/${chainId}/${assetType}`, { signal, @@ -1080,7 +1075,7 @@ export class PricesApiClient extends BaseApiClient { queryKey: ['prices', 'v1GraphByCoinId', coinId, currency, includeOHLC], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.PRICES, + this.apiUrls.PRICES, `/v1/historical-prices-graph/${coinId}`, { signal, @@ -1151,7 +1146,7 @@ export class PricesApiClient extends BaseApiClient { ], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.PRICES, + this.apiUrls.PRICES, `/v1/chains/${chainIdDecimal}/historical-prices-graph/${tokenAddress}`, { signal, diff --git a/packages/core-backend/src/api/shared-types.ts b/packages/core-backend/src/api/shared-types.ts index 3160eb23eb6..7eb3772e482 100644 --- a/packages/core-backend/src/api/shared-types.ts +++ b/packages/core-backend/src/api/shared-types.ts @@ -145,8 +145,17 @@ export type ApiPlatformClientOptions = { authTokenTimeout?: number; /** Optional custom QueryClient instance */ queryClient?: QueryClient; + /** + * Optional overrides for the API base URLs. Any service not specified falls + * back to the production URL in {@link API_URLS}. Useful for pointing + * clients at dev/local backend environments (e.g. via client env vars). + */ + apiUrls?: Partial; }; +/** Map of API service names to their base URLs. */ +export type ApiUrls = { [Service in keyof typeof API_URLS]: string }; + /** * Options for API fetch and query methods. * Extends TanStack Query options (e.g. select, initialPageParam, retry) so callers diff --git a/packages/core-backend/src/api/token/client.ts b/packages/core-backend/src/api/token/client.ts index 520a9602209..1ae30d2eb5e 100644 --- a/packages/core-backend/src/api/token/client.ts +++ b/packages/core-backend/src/api/token/client.ts @@ -17,12 +17,7 @@ import type { QueryFunctionContext, } from '@tanstack/query-core'; -import { - BaseApiClient, - API_URLS, - STALE_TIMES, - GC_TIMES, -} from '../base-client.js'; +import { BaseApiClient, STALE_TIMES, GC_TIMES } from '../base-client.js'; import { getQueryOptionsOverrides } from '../shared-types.js'; import type { FetchOptions } from '../shared-types.js'; import type { @@ -72,7 +67,7 @@ export class TokenApiClient extends BaseApiClient { return { queryKey: ['token', 'networks'], queryFn: ({ signal }: QueryFunctionContext) => - this.fetch(API_URLS.TOKEN, '/networks', { signal }), + this.fetch(this.apiUrls.TOKEN, '/networks', { signal }), ...getQueryOptionsOverrides(options), staleTime: options?.staleTime ?? STALE_TIMES.SUPPORTED_NETWORKS, gcTime: options?.gcTime ?? GC_TIMES.EXTENDED, @@ -103,7 +98,7 @@ export class TokenApiClient extends BaseApiClient { return { queryKey: ['token', 'networkByChainId', chainId], queryFn: ({ signal }: QueryFunctionContext) => - this.fetch(API_URLS.TOKEN, `/networks/${chainId}`, { + this.fetch(this.apiUrls.TOKEN, `/networks/${chainId}`, { signal, }), ...getQueryOptionsOverrides(options), @@ -167,7 +162,7 @@ export class TokenApiClient extends BaseApiClient { return { queryKey: ['token', 'tokenList', { chainId, options: queryOptions }], queryFn: ({ signal }: QueryFunctionContext) => - this.fetch(API_URLS.TOKEN, `/tokens/${chainId}`, { + this.fetch(this.apiUrls.TOKEN, `/tokens/${chainId}`, { signal, params: { includeTokenFees: queryOptions?.includeTokenFees, @@ -271,21 +266,25 @@ export class TokenApiClient extends BaseApiClient { queryFn: async ({ signal, }: QueryFunctionContext): Promise => { - return this.fetch(API_URLS.TOKEN, `/token/${chainId}`, { - signal, - params: { - address: tokenAddress, - includeTokenFees: queryOptions?.includeTokenFees, - includeAssetType: queryOptions?.includeAssetType, - includeAggregators: queryOptions?.includeAggregators, - includeERC20Permit: queryOptions?.includeERC20Permit, - includeOccurrences: queryOptions?.includeOccurrences, - includeStorage: queryOptions?.includeStorage, - includeIconUrl: queryOptions?.includeIconUrl, - includeAddress: queryOptions?.includeAddress, - includeName: queryOptions?.includeName, + return this.fetch( + this.apiUrls.TOKEN, + `/token/${chainId}`, + { + signal, + params: { + address: tokenAddress, + includeTokenFees: queryOptions?.includeTokenFees, + includeAssetType: queryOptions?.includeAssetType, + includeAggregators: queryOptions?.includeAggregators, + includeERC20Permit: queryOptions?.includeERC20Permit, + includeOccurrences: queryOptions?.includeOccurrences, + includeStorage: queryOptions?.includeStorage, + includeIconUrl: queryOptions?.includeIconUrl, + includeAddress: queryOptions?.includeAddress, + includeName: queryOptions?.includeName, + }, }, - }); + ); }, ...getQueryOptionsOverrides(options), staleTime: options?.staleTime ?? STALE_TIMES.TOKEN_METADATA, @@ -360,7 +359,7 @@ export class TokenApiClient extends BaseApiClient { signal, }: QueryFunctionContext): Promise => this.fetch( - API_URLS.TOKEN, + this.apiUrls.TOKEN, `/token/${chainId}/description`, { signal, @@ -434,7 +433,7 @@ export class TokenApiClient extends BaseApiClient { { chainIds: [...chainIds].sort(), options: queryOptions }, ], queryFn: ({ signal }: QueryFunctionContext) => - this.fetch(API_URLS.TOKEN, '/v3/tokens/trending', { + this.fetch(this.apiUrls.TOKEN, '/v3/tokens/trending', { signal, params: { chainIds, @@ -521,19 +520,23 @@ export class TokenApiClient extends BaseApiClient { { chainIds: [...chainIds].sort(), options: queryOptions }, ], queryFn: ({ signal }: QueryFunctionContext) => - this.fetch(API_URLS.TOKEN, '/v3/tokens/top-gainers', { - signal, - params: { - chainIds, - sort: queryOptions?.sort, - blockRegion: queryOptions?.blockRegion, - minLiquidity: queryOptions?.minLiquidity, - minVolume24hUsd: queryOptions?.minVolume24hUsd, - maxVolume24hUsd: queryOptions?.maxVolume24hUsd, - minMarketCap: queryOptions?.minMarketCap, - maxMarketCap: queryOptions?.maxMarketCap, + this.fetch( + this.apiUrls.TOKEN, + '/v3/tokens/top-gainers', + { + signal, + params: { + chainIds, + sort: queryOptions?.sort, + blockRegion: queryOptions?.blockRegion, + minLiquidity: queryOptions?.minLiquidity, + minVolume24hUsd: queryOptions?.minVolume24hUsd, + maxVolume24hUsd: queryOptions?.maxVolume24hUsd, + minMarketCap: queryOptions?.minMarketCap, + maxMarketCap: queryOptions?.maxMarketCap, + }, }, - }), + ), ...getQueryOptionsOverrides(options), staleTime: options?.staleTime ?? STALE_TIMES.TRENDING, gcTime: options?.gcTime ?? GC_TIMES.SHORT, @@ -606,7 +609,7 @@ export class TokenApiClient extends BaseApiClient { { chainIds: [...chainIds].sort(), options: queryOptions }, ], queryFn: ({ signal }: QueryFunctionContext) => - this.fetch(API_URLS.TOKEN, '/v3/tokens/popular', { + this.fetch(this.apiUrls.TOKEN, '/v3/tokens/popular', { signal, params: { chainIds, @@ -673,7 +676,7 @@ export class TokenApiClient extends BaseApiClient { return { queryKey: ['token', 'topAssets', chainId], queryFn: ({ signal }: QueryFunctionContext) => - this.fetch(API_URLS.TOKEN, `/topAssets/${chainId}`, { + this.fetch(this.apiUrls.TOKEN, `/topAssets/${chainId}`, { signal, }), ...getQueryOptionsOverrides(options), @@ -715,7 +718,7 @@ export class TokenApiClient extends BaseApiClient { queryKey: ['token', 'v1SuggestedOccurrenceFloors'], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.TOKEN, + this.apiUrls.TOKEN, '/v1/suggestedOccurrenceFloors', { signal }, ), diff --git a/packages/core-backend/src/api/tokens/client.ts b/packages/core-backend/src/api/tokens/client.ts index 37d5d3ea589..c838b9a9bb1 100644 --- a/packages/core-backend/src/api/tokens/client.ts +++ b/packages/core-backend/src/api/tokens/client.ts @@ -11,12 +11,7 @@ import type { QueryFunctionContext, } from '@tanstack/query-core'; -import { - BaseApiClient, - API_URLS, - STALE_TIMES, - GC_TIMES, -} from '../base-client.js'; +import { BaseApiClient, STALE_TIMES, GC_TIMES } from '../base-client.js'; import { getQueryOptionsOverrides } from '../shared-types.js'; import type { FetchOptions } from '../shared-types.js'; import type { @@ -61,7 +56,7 @@ export class TokensApiClient extends BaseApiClient { queryKey: ['tokens', 'v1SupportedNetworks'], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.TOKENS, + this.apiUrls.TOKENS, '/v1/supportedNetworks', { signal }, ), @@ -98,7 +93,7 @@ export class TokensApiClient extends BaseApiClient { queryKey: ['tokens', 'v2SupportedNetworks'], queryFn: ({ signal }: QueryFunctionContext) => this.fetch( - API_URLS.TOKENS, + this.apiUrls.TOKENS, '/v2/supportedNetworks', { signal }, ), @@ -152,13 +147,17 @@ export class TokensApiClient extends BaseApiClient { if (assetIds.length === 0) { return []; } - return this.fetch(API_URLS.TOKENS, '/v3/assets', { - signal, - params: { - assetIds, - ...queryOptions, + return this.fetch( + this.apiUrls.TOKENS, + '/v3/assets', + { + signal, + params: { + assetIds, + ...queryOptions, + }, }, - }); + ); }, ...getQueryOptionsOverrides(fetchOptions), staleTime: fetchOptions?.staleTime ?? STALE_TIMES.TOKEN_METADATA, diff --git a/packages/core-backend/src/index.ts b/packages/core-backend/src/index.ts index 98afbedb096..9832e7319c2 100644 --- a/packages/core-backend/src/index.ts +++ b/packages/core-backend/src/index.ts @@ -137,6 +137,7 @@ export { export type { // Client options ApiPlatformClientOptions, + ApiUrls, FetchOptions, // Shared types PageInfo,