import { Result } from "./Result.js"; import type { StandardSchemaV1 } from "./StandardSchemaV1.js"; type RequestBody = Args["requestBody"] extends StandardSchemaV1 ? StandardSchemaV1.InferOutput : void; type ResponseBody = Args["responseBody"] extends StandardSchemaV1 ? StandardSchemaV1.InferOutput : void; type ErrorBody = Args["errorBody"] extends StandardSchemaV1 ? StandardSchemaV1.InferOutput : void; type Api = (requestBody: RequestBody, signal?: AbortSignal) => Promise, ErrorBody>>; interface ArgsBase { readonly url: string; readonly method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | (string & {}); readonly requestBody?: StandardSchemaV1 | undefined; readonly responseBody?: StandardSchemaV1 | undefined; readonly errorBody?: StandardSchemaV1 | undefined; readonly cache?: RequestInit["cache"]; readonly credentials?: RequestInit["credentials"]; readonly headers?: RequestInit["headers"]; } export const defineApi = ({ url, method, requestBody: requestBodySchema, responseBody: responseBodySchema, errorBody: errorBodySchema, cache, credentials, headers: headersInit, }: Args): Api => { const headers = new Headers(headersInit); if (requestBodySchema !== undefined) { headers.set("Content-Type", "application/json"); } const init: RequestInit = { method, headers, ...(cache !== undefined ? { cache } : null), ...(credentials !== undefined ? { credentials } : null), }; return async (requestBody, signal) => { const body = requestBodySchema !== undefined ? JSON.stringify(requestBody) : null; const response = await fetch(url, { ...init, body, ...(signal !== undefined ? { signal } : null), }); if (response.ok) { if (responseBodySchema !== undefined) { const json = await response.json(); const validateResult = await responseBodySchema["~standard"].validate(json); if (validateResult.issues !== undefined) { throw new ValidationError({ schema: responseBodySchema, input: json, issues: validateResult.issues, }); } else { return Result.success(validateResult.value); } } else { return Result.success(undefined); } } else { if (errorBodySchema !== undefined) { const json = await response.json(); const validateResult = await errorBodySchema["~standard"].validate(json); if (validateResult.issues !== undefined) { throw new ValidationError({ schema: errorBodySchema, input: json, issues: validateResult.issues, }); } else { return Result.failure(validateResult.value); } } else { return Result.failure(undefined); } } }; }; export namespace ValidationError { export interface Props { readonly schema: StandardSchemaV1; readonly input: unknown; readonly issues: readonly StandardSchemaV1.Issue[]; } } export class ValidationError extends Error { readonly schema: StandardSchemaV1; readonly input: unknown; readonly issues: readonly StandardSchemaV1.Issue[]; constructor({ schema, input, issues, }: ValidationError.Props) { super(); this.schema = schema; this.input = input; this.issues = issues; } }