import createEvent from 'evnty'; import { ClientOptions, RequestOptions, ApidlyRequest, ApidlyResponse, RequestMiddleware, ResponseMiddleware, Events, Middlewares, MiddleWired, EventWired, RequestType, ResponseType, RetryStrategy, Callbable, Compile, } from './types'; import { Endpoint } from './createEndpoint'; import { jsonRequest, jsonResponse } from './dataTypes'; import { pickFirstOption, sanitize, defaultRetryStrategy, retry, compile } from './utils'; interface RequestInitOptions extends ClientOptions, Middlewares, Events { compileBase: ReturnType; } const request = async ( init: RequestInitOptions, endpoint: Endpoint, options: RequestOptions = {} ) => { type RequestOptionsType = RequestOptions, Partial>; const requestType = pickFirstOption>('requestType', jsonRequest, options, endpoint.options, init as any); const responseType = pickFirstOption>( 'responseType', jsonResponse, options, endpoint.options, init as any ); const maxRetries = pickFirstOption('maxRetries', 0, options, endpoint.options, init as any); const retryStrategy = pickFirstOption('retryStrategy', defaultRetryStrategy, options, endpoint.options, init as any); const cache = pickFirstOption('cache', void 0, options, endpoint.options, init as any); const credentials = pickFirstOption('credentials', void 0, options, endpoint.options, init as any); const integrity = pickFirstOption('integrity', void 0, options, endpoint.options, init as any); const keepalive = pickFirstOption('keepalive', void 0, options, endpoint.options, init as any); const method = pickFirstOption('method', 'get', options, endpoint.options, init as any); const mode = pickFirstOption('mode', void 0, options, endpoint.options, init as any); const redirect = pickFirstOption('redirect', void 0, options, endpoint.options, init as any); const referrer = pickFirstOption('referrer', void 0, options, endpoint.options, init as any); const referrerPolicy = pickFirstOption('referrerPolicy', void 0, options, endpoint.options, init as any); const signal = pickFirstOption('signal', void 0, options, endpoint.options, init as any); const window = pickFirstOption('window', void 0, options, endpoint.options, init as any); const headers = new Headers(); for (const headersInit of [init.headers, endpoint.options.headers, options.headers].filter(Boolean)) { new Headers(headersInit).forEach((value, key) => { headers.set(key, value); }); } const dataList = [endpoint.options.data, options.data]; const data = dataList.some((d) => typeof d !== 'undefined') ? ({ ...endpoint.options.data, ...options.data } as Data) : void 0; const params = { ...init.params, ...endpoint.options.params, ...options.params } as Params; const pathname = endpoint.compilePath(params); const base = init.compileBase(params); const url = new URL(pathname, base); url.pathname = url.pathname.replace('//', '/'); for (const [key, value] of url.searchParams) { if (value === '') { url.searchParams.delete(key); } } const request = sanitize>({ requestType, responseType, maxRetries, retryStrategy, data, params, cache, credentials, headers, integrity, keepalive, method, mode, redirect, referrer, referrerPolicy, signal, window, }); if (request.data) { await requestType(url, request); } await init.start(url, request); try { return await retry( async () => { for (const requestMiddleware of [].concat(init.request, endpoint.middlewares.request)) { await requestMiddleware(url, request); } const response = (await fetch(url.href, request)) as ApidlyResponse; response.data = await responseType(response, url, request); for (const responseMiddleware of [].concat(init.response, endpoint.middlewares.response)) { await responseMiddleware(response, url, request); } await init.success(response, url, request); return response.data; }, retryStrategy, maxRetries ); } catch (e: any) { await init.error(e, url, request); throw e; } finally { await init.done(url, request); } }; export interface Client extends MiddleWired, EventWired { (endpoint: Endpoint, options?: RequestOptions, Data>): Promise; } export class Client extends Callbable { private requestInit: RequestInitOptions; constructor(clientOptions: ClientOptions) { const requestInit: RequestInitOptions = { ...clientOptions, start: createEvent(), success: createEvent(), error: createEvent(), done: createEvent(), request: [] as RequestMiddleware[], response: [] as ResponseMiddleware[], compileBase: compile(clientOptions.base), }; super(request.bind(null, requestInit)); this.requestInit = requestInit; } request(middleware: RequestMiddleware) { this.requestInit.request.push(middleware); return this; } response(middleware: ResponseMiddleware) { this.requestInit.response.push(middleware); return this; } onStart(listener: (url: URL, request: ApidlyRequest) => any) { return this.requestInit.start.on(listener); } onSuccess(listener: (response: ApidlyResponse, url: URL, request: ApidlyRequest) => any) { return this.requestInit.success.on(listener); } onDone(listener: (url: URL, request: ApidlyRequest) => any) { return this.requestInit.done.on(listener); } onError(listener: (error: Error, url: URL, request: ApidlyRequest) => any) { return this.requestInit.error.on(listener); } } export default (clientOptions: ClientOptions) => { return new Client(clientOptions); };