diff --git a/packages/widget/src/providers/api/api-client-provider.tsx b/packages/widget/src/providers/api/api-client-provider.tsx index 078cffa2..95bd583a 100644 --- a/packages/widget/src/providers/api/api-client-provider.tsx +++ b/packages/widget/src/providers/api/api-client-provider.tsx @@ -1,5 +1,5 @@ import type { PropsWithChildren } from "react"; -import { createContext, useContext, useEffect, useMemo } from "react"; +import { createContext, useContext, useMemo } from "react"; import { config } from "../../config"; import { useSettings } from "../settings"; import { type ApiClient, createApiClient } from "./api-client"; @@ -19,8 +19,6 @@ export const SKApiClientProvider = ({ children }: PropsWithChildren) => { [apiKey, baseUrl, yieldsApiUrl] ); - useEffect(() => () => void apiClient.dispose(), [apiClient]); - return {children}; }; diff --git a/packages/widget/src/providers/api/api-client.ts b/packages/widget/src/providers/api/api-client.ts index f441f2eb..21342630 100644 --- a/packages/widget/src/providers/api/api-client.ts +++ b/packages/widget/src/providers/api/api-client.ts @@ -21,6 +21,8 @@ type RunOptions = { readonly signal?: AbortSignal; }; +const runtime = ManagedRuntime.make(FetchHttpClient.layer); + const inspectResponse = ({ response, }: { @@ -66,8 +68,6 @@ const configureClient = ({ HttpClient.tap((response) => inspectResponse({ response })) ); -type ApiRuntime = ManagedRuntime.ManagedRuntime; - type BoundOperation = Operation extends ( ...args: infer Args ) => Effect.Effect @@ -77,7 +77,6 @@ type BoundOperation = Operation extends ( const bindOperation = < Operation extends (...args: never[]) => Effect.Effect, >( - runtime: ApiRuntime, operation: Operation, runOptions?: RunOptions ): BoundOperation => @@ -90,45 +89,36 @@ const bindOperation = < const bindLegacyApi = ({ api, runOptions, - runtime, }: { readonly api: LegacyApi.LegacyApi; readonly runOptions?: RunOptions; - readonly runtime: ApiRuntime; }) => ({ TokenControllerGetTokenBalances: bindOperation( - runtime, api.TokenControllerGetTokenBalances, runOptions ), TokenControllerGetTokenPrices: bindOperation( - runtime, api.TokenControllerGetTokenPrices, runOptions ), TokenControllerGetTokens: bindOperation( - runtime, api.TokenControllerGetTokens, runOptions ), TokenControllerTokenBalancesScan: bindOperation( - runtime, api.TokenControllerTokenBalancesScan, runOptions ), TransactionControllerGetTransactionVerificationMessageForNetwork: bindOperation( - runtime, api.TransactionControllerGetTransactionVerificationMessageForNetwork, runOptions ), YieldControllerGetSingleYieldRewardsSummary: bindOperation( - runtime, api.YieldControllerGetSingleYieldRewardsSummary, runOptions ), YieldControllerYieldOpportunity: bindOperation( - runtime, api.YieldControllerYieldOpportunity, runOptions ), @@ -137,74 +127,56 @@ const bindLegacyApi = ({ const bindYieldApi = ({ api, runOptions, - runtime, }: { readonly api: YieldApi.YieldApi; readonly runOptions?: RunOptions; - readonly runtime: ApiRuntime; }) => ({ ActionsControllerEnterYield: bindOperation( - runtime, api.ActionsControllerEnterYield, runOptions ), ActionsControllerExitYield: bindOperation( - runtime, api.ActionsControllerExitYield, runOptions ), ActionsControllerGetActions: bindOperation( - runtime, api.ActionsControllerGetActions, runOptions ), ActionsControllerManageYield: bindOperation( - runtime, api.ActionsControllerManageYield, runOptions ), - HealthControllerHealth: bindOperation( - runtime, - api.HealthControllerHealth, - runOptions - ), + HealthControllerHealth: bindOperation(api.HealthControllerHealth, runOptions), NetworksControllerGetNetworks: bindOperation( - runtime, api.NetworksControllerGetNetworks, runOptions ), TransactionsControllerGetTransaction: bindOperation( - runtime, api.TransactionsControllerGetTransaction, runOptions ), TransactionsControllerSubmitTransaction: bindOperation( - runtime, api.TransactionsControllerSubmitTransaction, runOptions ), TransactionsControllerSubmitTransactionHash: bindOperation( - runtime, api.TransactionsControllerSubmitTransactionHash, runOptions ), YieldsControllerGetAggregateBalances: bindOperation( - runtime, api.YieldsControllerGetAggregateBalances, runOptions ), YieldsControllerGetYield: bindOperation( - runtime, api.YieldsControllerGetYield, runOptions ), YieldsControllerGetYieldBalances: bindOperation( - runtime, api.YieldsControllerGetYieldBalances, runOptions ), YieldsControllerGetYieldValidators: bindOperation( - runtime, api.YieldsControllerGetYieldValidators, runOptions ), @@ -213,16 +185,14 @@ const bindYieldApi = ({ const bindApiClients = ({ legacyApi, runOptions, - runtime, yieldApi, }: { readonly legacyApi: LegacyApi.LegacyApi; readonly runOptions?: RunOptions; - readonly runtime: ApiRuntime; readonly yieldApi: YieldApi.YieldApi; }) => ({ - legacy: bindLegacyApi({ api: legacyApi, runOptions, runtime }), - yield: bindYieldApi({ api: yieldApi, runOptions, runtime }), + legacy: bindLegacyApi({ api: legacyApi, runOptions }), + yield: bindYieldApi({ api: yieldApi, runOptions }), }); export const createApiClient = ({ @@ -230,7 +200,6 @@ export const createApiClient = ({ baseUrl, yieldsApiUrl, }: WidgetApiClientOptions) => { - const runtime = ManagedRuntime.make(FetchHttpClient.layer); const baseClient = runtime.runSync(HttpClient.HttpClient); const legacyHttpClient = configureClient({ @@ -245,13 +214,12 @@ export const createApiClient = ({ }); const legacyApi = LegacyApi.make(legacyHttpClient); const yieldApi = YieldApi.make(yieldHttpClient); - const boundClients = bindApiClients({ legacyApi, runtime, yieldApi }); + const boundClients = bindApiClients({ legacyApi, yieldApi }); return { ...boundClients, withRunOptions: (runOptions: RunOptions) => - bindApiClients({ legacyApi, runOptions, runtime, yieldApi }), - dispose: () => runtime.dispose(), + bindApiClients({ legacyApi, runOptions, yieldApi }), }; }; diff --git a/packages/widget/tests/providers/api-client.test.tsx b/packages/widget/tests/providers/api-client.test.tsx index 325cda71..f22172d9 100644 --- a/packages/widget/tests/providers/api-client.test.tsx +++ b/packages/widget/tests/providers/api-client.test.tsx @@ -41,39 +41,31 @@ describe("API client", () => { ); const client = createTestClient(); - try { - await expect( - client.legacy.TokenControllerGetTokens(undefined) - ).resolves.toEqual([]); - await expect( - client.yield.HealthControllerHealth(undefined) - ).resolves.toMatchObject({ - status: "OK", - }); - - expect(calls.map((call) => call.url)).toEqual([ - "https://api.example.com/v1/tokens", - "https://yield.example.com/health", - ]); - expect( - calls.every((call) => call.headers.get("X-API-KEY") === "test-key") - ).toBe(true); - } finally { - client.dispose(); - } + await expect( + client.legacy.TokenControllerGetTokens(undefined) + ).resolves.toEqual([]); + await expect( + client.yield.HealthControllerHealth(undefined) + ).resolves.toMatchObject({ + status: "OK", + }); + + expect(calls.map((call) => call.url)).toEqual([ + "https://api.example.com/v1/tokens", + "https://yield.example.com/health", + ]); + expect( + calls.every((call) => call.headers.get("X-API-KEY") === "test-key") + ).toBe(true); }); it("exposes only the generated operations currently used by the app", () => { const client = createTestClient(); - try { - expect("TokenControllerGetTokens" in client.legacy).toBe(true); - expect("AuthControllerMe" in client.legacy).toBe(false); - expect("YieldsControllerGetAggregateBalances" in client.yield).toBe(true); - expect("ProvidersControllerGetProviders" in client.yield).toBe(false); - } finally { - client.dispose(); - } + expect("TokenControllerGetTokens" in client.legacy).toBe(true); + expect("AuthControllerMe" in client.legacy).toBe(false); + expect("YieldsControllerGetAggregateBalances" in client.yield).toBe(true); + expect("ProvidersControllerGetProviders" in client.yield).toBe(false); }); it("records rich errors for failed StakeKit API responses", async ({ @@ -103,7 +95,6 @@ describe("API client", () => { .poll(() => richError.result.current.error?.message) .toBe("Rich failure"); } finally { - client.dispose(); richError.unmount(); } }); @@ -142,7 +133,6 @@ describe("API client", () => { const value = geoBlock.result.current; expect(value === false ? [] : [...value.tags]).toEqual(["staking"]); } finally { - client.dispose(); geoBlock.unmount(); } }); @@ -163,14 +153,10 @@ describe("API client", () => { ); const client = createTestClient(); - try { - await expect( - client.legacy.TokenControllerGetTokens(undefined) - ).resolves.toEqual([]); - expect(attempts).toBe(3); - } finally { - client.dispose(); - } + await expect( + client.legacy.TokenControllerGetTokens(undefined) + ).resolves.toEqual([]); + expect(attempts).toBe(3); }); it("does not retry non-transient response statuses", async ({ worker }) => { @@ -187,18 +173,14 @@ describe("API client", () => { ); const client = createTestClient(); - try { - await expect( - client.legacy.TokenControllerGetTokens(undefined) - ).rejects.toMatchObject({ - _tag: "TokenControllerGetTokens400", - cause: { code: 400, message: "bad request" }, - response: { status: 400 }, - }); - expect(attempts).toBe(1); - } finally { - client.dispose(); - } + await expect( + client.legacy.TokenControllerGetTokens(undefined) + ).rejects.toMatchObject({ + _tag: "TokenControllerGetTokens400", + cause: { code: 400, message: "bad request" }, + response: { status: 400 }, + }); + expect(attempts).toBe(1); }); it("does not retry aborted requests", async ({ worker }) => { @@ -218,16 +200,12 @@ describe("API client", () => { ); const client = createTestClient(); - try { - await expect( - client - .withRunOptions({ signal: controller.signal }) - .legacy.TokenControllerGetTokens(undefined) - ).rejects.toBeTruthy(); - expect(attempts).toBeLessThanOrEqual(1); - } finally { - client.dispose(); - } + await expect( + client + .withRunOptions({ signal: controller.signal }) + .legacy.TokenControllerGetTokens(undefined) + ).rejects.toBeTruthy(); + expect(attempts).toBeLessThanOrEqual(1); }); it("waits for delayed API requests before resolving successful responses", async ({ @@ -261,7 +239,6 @@ describe("API client", () => { expect(resolved).toBe(true); } finally { - client.dispose(); releaseDelay(); env.isTestMode = originalIsTestMode; } diff --git a/packages/widget/vite/vite.config.package.ts b/packages/widget/vite/vite.config.package.ts index aef44450..ee3d4c58 100644 --- a/packages/widget/vite/vite.config.package.ts +++ b/packages/widget/vite/vite.config.package.ts @@ -2,40 +2,32 @@ import path from "node:path"; import { defineConfig, esmExternalRequirePlugin } from "vite"; import { getConfig } from "./vite.config.base"; -const reactExternals = [/^react(?:\/.*)?$/, /^react-dom(?:\/.*)?$/]; - -const config = getConfig( - { - define: { - // Drop dead AMD branches from bundled UMD dependencies so Next Turbopack - // does not resolve their dependency arrays as real relative imports. - define: "undefined", +const config = getConfig({ + define: { + // Drop dead AMD branches from bundled UMD dependencies so Next Turbopack + // does not resolve their dependency arrays as real relative imports. + define: "undefined", + }, + build: { + lib: { + entry: path.resolve(__dirname, "..", "src/index.package.ts"), + name: "StakeKit", + fileName: "index.package", + formats: ["es"], }, - build: { - lib: { - entry: path.resolve(__dirname, "..", "src/index.package.ts"), - name: "StakeKit", - fileName: "index.package", - formats: ["es"], - }, - rolldownOptions: { - external: reactExternals, - output: { banner: '"use client";\n' }, - }, - copyPublicDir: false, - minify: false, - outDir: "dist/package", - sourcemap: false, + rolldownOptions: { + output: { banner: '"use client";\n' }, + plugins: [ + esmExternalRequirePlugin({ + external: [/^react(-dom)?(\/.+)?$/], + }), + ], }, + copyPublicDir: false, + minify: false, + outDir: "dist/package", + sourcemap: false, }, - { - plugins: [ - esmExternalRequirePlugin({ - external: reactExternals, - skipDuplicateCheck: true, - }), - ], - } -); +}); export default defineConfig(config);