import type { Endpoint } from "./endpoint"; export function generate({ endpoints, interfaces, }: { endpoints: Endpoint[]; interfaces: string[]; }) { const methods = endpoints.map((endpoint) => generateMethod(endpoint)); return ` /// import { z } from "zod"; ${interfaces.join("\n")} type HttpResult = | { success: true; headers: Record; data: T } | { success: false; headers: Record; data: unknown } ${generateWrapper( [methods.join("\n\n"), generateReturnMethods(endpoints)].join("\n") )} ${generateTraceBuilder()} `; } function generateMethod(endpoint: Endpoint) { const parameters = generateParameters(endpoint); const { bodyType } = endpoint; return ` async function ${endpoint.name}( ${parameters ? `parameters: ${parameters},` : ""} ${bodyType ? `body: ${bodyType},` : ""} fetchOverrides: RequestInit = {} ): Promise<${generateMethodReturnType(endpoint)}> { const url = new URL(${generateUrlPath(endpoint)}, baseUrl); ${generateQueryParameters(endpoint)} const { headers, ...fetchOverridesWithoutHeaders } = fetchOverrides; const { headers: globalHeaders, ...globalFetchOverridesWithoutHeaders } = globalFetchOverrides; const response = await fetch(url.toString(), { method: "${endpoint.method}", headers: { "Accept": "application/json", "Content-Type": "application/json", ...globalHeaders, ...headers, }, ${bodyType ? `body: jsonStringifier(body),` : ""} ...globalFetchOverridesWithoutHeaders, ...fetchOverridesWithoutHeaders, }); if (!response.ok) { return { success: false, headers: Object.fromEntries(response.headers.entries()), data: await response.json() }; } ${generateSuccessfulReturn(endpoint)} } `; } function generateMethodReturnType(endpoint: Endpoint) { if (endpoint.responseType === undefined) { return "HttpResult"; } return `HttpResult>`; } function generateParameters(endpoint: Endpoint) { if (endpoint.parameters === undefined) { return undefined; } return ` { ${endpoint.parameters.map(({ name }) => `${name}: string,\n`)} } `; } function generateUrlPath(endpoint: Endpoint) { let result = `"${endpoint.path}"`; for (const parameter of endpoint.parameters ?? []) { if (parameter.in === "query") { continue; } result += `.replace("{${parameter.name}}", parameters["${parameter.name}"])`; } return result; } function generateQueryParameters(endpoint: Endpoint) { if (endpoint.parameters === undefined) { return ""; } const result: string[] = []; for (const parameter of endpoint.parameters) { if (parameter.in === "path") { continue; } result.push( `url.searchParams.append("${parameter.name}", parameters["${parameter.name}"]);` ); } return result.join("\n"); } function generateSuccessfulReturn(endpoint: Endpoint) { if (!endpoint.responseType) { return "return { success: true, headers: Object.fromEntries(response.headers.entries()), data: undefined }"; } return ` const data = await response.json(); return { success: true, headers: Object.fromEntries(response.headers.entries()), data: ${endpoint.responseType}.parse(data) } `; } function generateWrapper(content: string) { return ` export function createClient( baseUrl: string, globalFetchOverrides: RequestInit = {}, jsonStringifier = JSON.stringify ) { ${content} } `; } function generateReturnMethods(endpoints: Endpoint[]) { const allMethods = endpoints.map((endpoint) => endpoint.name).join(","); return ` return { ${allMethods} } `; } function generateTraceBuilder() { return ` export function createTraceHeader({ traceId, parentSpanId, version }: { traceId: string; parentSpanId: string; version?: string }) { return { traceparent: [ version ?? "00", traceId, parentSpanId, "01" ].join("-") }; } `; }