import { decamelizeKeys } from 'xcase'; import FormData from 'form-data'; import { stringify } from 'query-string'; // Types export interface Constructable { new (...args: any[]): T; } export interface RequesterType { get(endpoint: string, options?: Record): Promise; post(endpoint: string, options?: Record): Promise; put(endpoint: string, options?: Record): Promise; delete(endpoint: string, options?: Record): Promise; stream?(endpoint: string, options?: Record): NodeJS.ReadableStream; } export type DefaultServiceOptions = { headers: { [header: string]: string }; requestTimeout: number; url: string; rejectUnauthorized: boolean; }; export type DefaultRequestOptions = { body?: FormData | Record; query?: Record; sudo?: string; method?: string; }; export type DefaultRequestReturn = { headers: Record | Headers; timeout?: number; method: string; searchParams?: string; prefixUrl?: string; body?: string | FormData; }; // Utility methods export function formatQuery(params: Record = {}) { const decamelized = decamelizeKeys(params); if (decamelized.not) decamelized.not = JSON.stringify(decamelized.not); return stringify(decamelized, { arrayFormat: 'bracket' }); } export function defaultOptionsHandler( serviceOptions: DefaultServiceOptions, { body, query, sudo, method = 'get' }: DefaultRequestOptions = {}, ): DefaultRequestReturn { const { headers, requestTimeout, url } = serviceOptions; let bod: FormData | string; if (sudo) headers.sudo = sudo; // FIXME: Not the best comparison, but...it will have to do for now. if (typeof body === 'object' && body.constructor.name !== 'FormData') { bod = JSON.stringify(decamelizeKeys(body)); headers['content-type'] = 'application/json'; } else { bod = body as FormData; } return { headers, timeout: requestTimeout, method, searchParams: formatQuery(query), prefixUrl: url, body: bod, }; } export function createRequesterFn( optionsHandler, requestHandler, ): (serviceOptions: DefaultServiceOptions) => RequesterType { const methods = ['get', 'post', 'put', 'delete', 'stream']; return (serviceOptions) => { const requester: RequesterType = {} as RequesterType; methods.forEach((m) => { requester[m] = (endpoint: string, options: Record) => { const requestOptions = optionsHandler(serviceOptions, { ...options, method: m }); return requestHandler(endpoint, requestOptions); }; }); return requester; }; } function extendClass(Base: T, customConfig: Record): T { return class extends Base { constructor(...options: any[]) { const [config, ...opts] = options; super({ ...customConfig, ...config }, ...opts); } }; } export function modifyServices(services: T, customConfig: Record = {}) { const updated = {}; Object.entries(services).forEach(([k, s]) => { updated[k] = extendClass(s, customConfig); }); return updated as T; } export async function wait(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); }