import { DEFAULT_UA } from "./constants"; type HttpVerb = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; type ConvertibleObject = Record< string, string | number | boolean | File | undefined | null >; export interface Output { data: ReturnType; headers: Headers; } const objectToFormData = (obj: ConvertibleObject): FormData => { return Object.keys(obj).reduce((formData, key) => { if (obj[key] === undefined || obj[key] === null) return formData; formData.append(key, String(obj[key])); return formData; }, new FormData()); }; export class ResponseError extends Error {} export class BaseClient { constructor( protected baseUrl: URL, private accessToken?: string, ) {} get url(): URL { return this.baseUrl; } get token(): string | undefined { return this.accessToken; } private async request( request: Request, ): Promise> { const result = await fetch(request); if (!result.ok) { const error = await result.json(); throw new ResponseError( `Request failed (${result.status}): ${ error.error || error.message || result.statusText }`, ); } return { data: await result.json(), headers: result.headers, }; } private async constructRequest( path: string, method: HttpVerb, body?: object | FormData, extra?: RequestInit, ): Promise { return new Request(new URL(path, this.baseUrl).toString(), { method, headers: { Authorization: this.accessToken ? `Bearer ${this.accessToken}` : "", "Content-Type": "application/json", "User-Agent": DEFAULT_UA, ...extra?.headers, }, body: body ? body instanceof FormData ? body : JSON.stringify(body) : undefined, ...extra, }); } protected async get( path: string, extra?: RequestInit, ): Promise> { return await this.request( await this.constructRequest(path, "GET", undefined, extra), ); } protected async post( path: string, body?: object, extra?: RequestInit, ): Promise> { return await this.request( await this.constructRequest(path, "POST", body, extra), ); } protected async postForm( path: string, body: FormData | ConvertibleObject, extra?: RequestInit, ): Promise> { return await this.request( await this.constructRequest( path, "POST", body instanceof FormData ? body : objectToFormData(body), extra, ), ); } protected async put( path: string, body?: object, extra?: RequestInit, ): Promise> { return await this.request( await this.constructRequest(path, "PUT", body, extra), ); } protected async putForm( path: string, body: FormData | ConvertibleObject, extra?: RequestInit, ): Promise> { return await this.request( await this.constructRequest( path, "PUT", body instanceof FormData ? body : objectToFormData(body), extra, ), ); } protected async patch( path: string, body?: object, extra?: RequestInit, ): Promise> { return await this.request( await this.constructRequest(path, "PATCH", body, extra), ); } protected async patchForm( path: string, body: FormData | ConvertibleObject, extra?: RequestInit, ): Promise> { return await this.request( await this.constructRequest( path, "PATCH", body instanceof FormData ? body : objectToFormData(body), extra, ), ); } protected async delete( path: string, body?: object, extra?: RequestInit, ): Promise> { return await this.request( await this.constructRequest(path, "DELETE", body, extra), ); } protected async deleteForm( path: string, body: FormData | ConvertibleObject, extra?: RequestInit, ): Promise> { return await this.request( await this.constructRequest( path, "DELETE", body instanceof FormData ? body : objectToFormData(body), extra, ), ); } }