import { Agent } from 'https'; import { decamelizeKeys } from 'xcase'; import { stringify } from 'query-string'; // Types export interface RequesterType { get(service: object, endpoint: string, options?: object): Promise; post(service: object, endpoint: string, options?: object): Promise; put(service: object, endpoint: string, options?: object): Promise; delete(service: object, endpoint: string, options?: object): Promise; stream?(service: object, endpoint: string, options?: object): Promise; } export interface Service { headers: Record; requestTimeout: number; url: string; rejectUnauthorized?: boolean; } export type DefaultRequestOptions = { body?: FormData | object; query?: object; sudo?: string; method?: string; }; // Utility methods export function formatQuery(options) { return stringify(decamelizeKeys(options || {}) as object, { arrayFormat: 'bracket' }); } export function defaultRequest( service: Service, { body, query, sudo, method = 'get' }: DefaultRequestOptions = {}, ): Record> { const { headers } = service; let agent; let bod; 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; } return { agent, headers, timeout: service.requestTimeout, method, searchParams: formatQuery(query), prefixUrl: service.url, body: bod, }; } export function createInstance(optionsHandler, requestHandler): RequesterType { const requester: RequesterType = {} as RequesterType; const methods = ['get', 'post', 'put', 'delete', 'stream']; methods.forEach((m) => { /* eslint func-names:0 */ requester[m] = function (service, endpoint, options) { const requestOptions = optionsHandler(service, { ...options, method: m }); return requestHandler(endpoint, requestOptions); }; }); return requester; } export interface Constructable { new (...args: any[]): T; } function extendClass(Base: T, customConfig: object): T { return class extends Base { constructor(...options: any[]) { const [config, ...opts] = options; super({ ...customConfig, ...config }, ...opts); } }; } export function modifyServices( services: T, customConfig: object = {}, ) { const updated: { [name: string]: Constructable } = {}; Object.entries(services).forEach(([k, s]) => { updated[k] = extendClass(s, customConfig); }); return updated as T; }