UNPKG

@nahkies/typescript-axios-runtime

Version:

Runtime package for code generated by @nahkies/openapi-code-generator using the typescript-axios template

163 lines (140 loc) 4.36 kB
import { type Encoding, requestBodyToUrlSearchParams, } from "@nahkies/typescript-common-runtime/request-bodies/url-search-params" import type { HeaderParams, QueryParams, } from "@nahkies/typescript-common-runtime/types" import axios, { AxiosHeaders, type AxiosInstance, type AxiosRequestConfig, type AxiosResponse, type RawAxiosRequestHeaders, } from "axios" export type { HeaderParams, QueryParams, Server, } from "@nahkies/typescript-common-runtime/types" export interface AbstractAxiosConfig { axios?: AxiosInstance | undefined basePath: string defaultHeaders?: Record<string, string> | undefined defaultTimeout?: number | undefined } export abstract class AbstractAxiosClient { protected readonly axios: AxiosInstance protected readonly basePath: string protected readonly defaultHeaders: Record<string, string> protected readonly defaultTimeout: number | undefined protected constructor(config: AbstractAxiosConfig) { this.axios = config.axios ?? axios this.basePath = config.basePath this.defaultHeaders = config.defaultHeaders ?? {} this.defaultTimeout = config.defaultTimeout } protected _request<R extends AxiosResponse>( opts: AxiosRequestConfig, ): Promise<R> { const headers = opts.headers ?? this._headers() const timeout = opts.timeout ?? this.defaultTimeout return this.axios.request({ baseURL: this.basePath, ...opts, ...(timeout !== undefined ? {timeout} : {}), headers, }) } protected _query( params: QueryParams, encodings?: Record<string, Encoding>, ): string { const urlSearchParams = requestBodyToUrlSearchParams(params, encodings) const asString = urlSearchParams.toString() if (!asString.length) { return "" } return `?${asString}` } /** * Combines headers for a request, with precedence * 1. default headers * 2. route level header parameters * 3. raw request config (escape hatch) * * following these rules: * - header values of `undefined` are skipped * - header values of `null` will remove/delete any previously set headers * * Eg: * Passing `Authorization: null` as a parameter, will clear out any * default `Authorization` header. * * But passing `Authorization: undefined` as parameter will fallthrough * to the default `Authorization` header. * * @param paramHeaders * @param optsHeaders * @protected */ protected _headers( paramHeaders: HeaderParams = {}, optsHeaders: AxiosRequestConfig["headers"] = {}, ): RawAxiosRequestHeaders { const headers = new AxiosHeaders() // axios doesn't know how to append headers, so we just apply // from the lowest priority to highest. this.setHeaders(headers, this.defaultHeaders) this.setHeaders(headers, paramHeaders) this.setHeaders(headers, optsHeaders) return headers } protected _requestBodyToUrlSearchParams( obj: Record<string, unknown>, encoding: Record<string, Encoding> = {}, ): URLSearchParams { return requestBodyToUrlSearchParams(obj, encoding) } protected _parseBlobResponse(res: AxiosResponse) { const contentType = res.headers["content-type"] return new Blob([res.data], { type: typeof contentType === "string" ? contentType : "application/octet-stream", }) } private setHeaders( headers: Pick<Headers, "set" | "delete">, headersInit: HeaderParams | AxiosRequestConfig["headers"], ) { const headersArray = this.headersAsArray(headersInit) for (const [headerName, headerValue] of headersArray) { if (headerValue === null) { headers.delete(headerName) } else if (headerValue !== undefined) { headers.set(headerName.toLowerCase(), headerValue.toString()) } } } private headersAsArray( headers: HeaderParams | AxiosRequestConfig["headers"], ): [string, string | number | boolean | undefined | null][] { if (Array.isArray(headers)) { return headers } if (headers instanceof Headers) { const result: [string, string][] = [] headers.forEach((value, key) => { result.push([key, value]) }) return result } if (headers && typeof headers === "object") { return Object.entries(headers) } return [] } }