diff --git a/packages/cli-v3/src/apiClient.ts b/packages/cli-v3/src/apiClient.ts index 1e4719dfa..3702ef972 100644 --- a/packages/cli-v3/src/apiClient.ts +++ b/packages/cli-v3/src/apiClient.ts @@ -17,7 +17,9 @@ import { ImportEnvironmentVariablesRequestBody, EnvironmentVariableResponseBody, TaskRunExecution, + APIError, } from "@trigger.dev/core/v3"; +import { zodfetch } from "@trigger.dev/core/v3/zodfetch"; export class CliApiClient { private readonly apiURL: string; @@ -30,7 +32,7 @@ export class CliApiClient { } async createAuthorizationCode() { - return zodfetch( + return wrapZodFetch( CreateAuthorizationCodeResponseSchema, `${this.apiURL}/api/v1/authorization-code`, { @@ -40,7 +42,7 @@ export class CliApiClient { } async getPersonalAccessToken(authorizationCode: string) { - return zodfetch(GetPersonalAccessTokenResponseSchema, `${this.apiURL}/api/v1/token`, { + return wrapZodFetch(GetPersonalAccessTokenResponseSchema, `${this.apiURL}/api/v1/token`, { method: "POST", body: JSON.stringify({ authorizationCode, @@ -53,7 +55,7 @@ export class CliApiClient { throw new Error("whoAmI: No access token"); } - return zodfetch(WhoAmIResponseSchema, `${this.apiURL}/api/v2/whoami`, { + return wrapZodFetch(WhoAmIResponseSchema, `${this.apiURL}/api/v2/whoami`, { headers: { Authorization: `Bearer ${this.accessToken}`, "Content-Type": "application/json", @@ -66,7 +68,7 @@ export class CliApiClient { throw new Error("getProject: No access token"); } - return zodfetch(GetProjectResponseBody, `${this.apiURL}/api/v1/projects/${projectRef}`, { + return wrapZodFetch(GetProjectResponseBody, `${this.apiURL}/api/v1/projects/${projectRef}`, { headers: { Authorization: `Bearer ${this.accessToken}`, "Content-Type": "application/json", @@ -79,7 +81,7 @@ export class CliApiClient { throw new Error("getProjects: No access token"); } - return zodfetch(GetProjectsResponseBody, `${this.apiURL}/api/v1/projects`, { + return wrapZodFetch(GetProjectsResponseBody, `${this.apiURL}/api/v1/projects`, { headers: { Authorization: `Bearer ${this.accessToken}`, "Content-Type": "application/json", @@ -92,7 +94,7 @@ export class CliApiClient { throw new Error("createBackgroundWorker: No access token"); } - return zodfetch( + return wrapZodFetch( CreateBackgroundWorkerResponse, `${this.apiURL}/api/v1/projects/${projectRef}/background-workers`, { @@ -111,7 +113,7 @@ export class CliApiClient { throw new Error("creatTaskRunAttempt: No access token"); } - return zodfetch(TaskRunExecution, `${this.apiURL}/api/v1/runs/${runFriendlyId}/attempts`, { + return wrapZodFetch(TaskRunExecution, `${this.apiURL}/api/v1/runs/${runFriendlyId}/attempts`, { method: "POST", headers: { Authorization: `Bearer ${this.accessToken}`, @@ -131,12 +133,16 @@ export class CliApiClient { throw new Error("getProjectDevEnv: No access token"); } - return zodfetch(GetProjectEnvResponse, `${this.apiURL}/api/v1/projects/${projectRef}/${env}`, { - headers: { - Authorization: `Bearer ${this.accessToken}`, - "Content-Type": "application/json", - }, - }); + return wrapZodFetch( + GetProjectEnvResponse, + `${this.apiURL}/api/v1/projects/${projectRef}/${env}`, + { + headers: { + Authorization: `Bearer ${this.accessToken}`, + "Content-Type": "application/json", + }, + } + ); } async getEnvironmentVariables(projectRef: string) { @@ -144,7 +150,7 @@ export class CliApiClient { throw new Error("getEnvironmentVariables: No access token"); } - return zodfetch( + return wrapZodFetch( GetEnvironmentVariablesResponseBody, `${this.apiURL}/api/v1/projects/${projectRef}/envvars`, { @@ -184,7 +190,7 @@ export class CliApiClient { throw new Error("initializeDeployment: No access token"); } - return zodfetch(InitializeDeploymentResponseBody, `${this.apiURL}/api/v1/deployments`, { + return wrapZodFetch(InitializeDeploymentResponseBody, `${this.apiURL}/api/v1/deployments`, { method: "POST", headers: { Authorization: `Bearer ${this.accessToken}`, @@ -199,7 +205,7 @@ export class CliApiClient { throw new Error("startDeploymentIndexing: No access token"); } - return zodfetch( + return wrapZodFetch( StartDeploymentIndexingResponseBody, `${this.apiURL}/api/v1/deployments/${deploymentId}/start-indexing`, { @@ -218,7 +224,7 @@ export class CliApiClient { throw new Error("getDeployment: No access token"); } - return zodfetch( + return wrapZodFetch( GetDeploymentResponseBody, `${this.apiURL}/api/v1/deployments/${deploymentId}`, { @@ -238,56 +244,42 @@ type ApiResult = error: string; }; -async function zodfetch( - schema: z.Schema, +async function wrapZodFetch( + schema: T, url: string, requestInit?: RequestInit -): Promise> { +): Promise>> { try { - const response = await fetch(url, requestInit); + const response = await zodfetch(schema, url, requestInit, { + retry: { + minTimeoutInMs: 500, + maxTimeoutInMs: 5000, + maxAttempts: 3, + factor: 2, + randomize: false, + }, + }); - if ((!requestInit || requestInit.method === "GET") && response.status === 404) { - return { - success: false, - error: `404: ${response.statusText}`, - }; - } - - if (response.status >= 400 && response.status < 500) { - const body = await response.json(); - if (!body.error) { - return { success: false, error: "Something went wrong" }; - } - - return { success: false, error: body.error }; - } - - if (response.status !== 200) { - return { - success: false, - error: `Failed to fetch ${url}, got status code ${response.status}`, - }; - } - - const jsonBody = await response.json(); - const parsedResult = schema.safeParse(jsonBody); - - if (parsedResult.success) { - return { success: true, data: parsedResult.data }; - } - - if ("error" in jsonBody) { - return { - success: false, - error: typeof jsonBody.error === "string" ? jsonBody.error : JSON.stringify(jsonBody.error), - }; - } - - return { success: false, error: parsedResult.error.message }; - } catch (error) { return { - success: false, - error: error instanceof Error ? error.message : JSON.stringify(error), + success: true, + data: response, }; + } catch (error) { + if (error instanceof APIError) { + return { + success: false, + error: error.message, + }; + } else if (error instanceof Error) { + return { + success: false, + error: error.message, + }; + } else { + return { + success: false, + error: String(error), + }; + } } }