/* eslint-disable class-methods-use-this */ import type { EmptyTypes, ExtendRequest } from "types"; import { getAdapterOnError, getAdapterBindings } from "./adapter.bindings"; import type { EndpointMapper, AdapterFetcherType, AdapterPayloadMappingType, HeaderMappingType, QueryParamsMapper, QueryParamsType, RequestResponseType, ResponseType, } from "./adapter.types"; import type { RequestInstance, RequestOptionsType } from "request"; import type { Client, ClientInstance } from "client"; import { mocker } from "mocker"; import type { LoggerMethods } from "managers"; import type { getErrorMessage } from "./adapter.utils"; import { getAdapterHeaders, getAdapterPayload, RequestProcessingError } from "./adapter.utils"; export type DefaultMapperType = (value: V, config: C) => V; export const defaultMapper: DefaultMapperType = (value) => value; export class Adapter< AdapterOptions, MethodType extends string, StatusType extends number | string, Extra extends Record, QueryParams = QueryParamsType | string | EmptyTypes, DefaultQueryParams = undefined, EndpointType = string, EndpointMapperType extends EndpointMapper | DefaultMapperType = DefaultMapperType, QueryParamsMapperType extends QueryParamsMapper | DefaultMapperType = DefaultMapperType, HeaderMapperType extends HeaderMappingType | DefaultMapperType = DefaultMapperType, PayloadMapperType extends AdapterPayloadMappingType | DefaultMapperType = DefaultMapperType, > { /** Fetching function */ public unstable_fetcher: AdapterFetcherType< Adapter< AdapterOptions, MethodType, StatusType, Extra, QueryParams, DefaultQueryParams, EndpointType, EndpointMapperType, QueryParamsMapperType, HeaderMapperType, PayloadMapperType > >; /** * ******************** * Defaults * ******************** */ public name: string; public defaultMethod: MethodType; public defaultExtra: Extra; public systemErrorStatus: StatusType; public systemErrorExtra: Extra; public defaultRequestOptions?: RequestOptionsType; public logger: LoggerMethods; public initialized = false; public client: ClientInstance; public unstable_onInitializeCallback?: (options: { client: ClientInstance }) => void; public unstable_queryParamsMapperConfig: Parameters[1]; public unstable_headerMapperConfig: Parameters[1]; public unstable_payloadMapperConfig: Parameters[1]; public unstable_endpointMapperConfig: Parameters[1]; constructor( public options: { name: string; defaultMethod: MethodType; defaultExtra: Extra; systemErrorStatus: StatusType; systemErrorExtra: Extra; defaultRequestOptions?: RequestOptionsType; }, ) { this.name = options.name; this.defaultMethod = options.defaultMethod; this.defaultExtra = options.defaultExtra; this.systemErrorStatus = options.systemErrorStatus; this.systemErrorExtra = options.systemErrorExtra; this.defaultRequestOptions = options.defaultRequestOptions; } initialize = (client: ClientInstance) => { this.logger = client.loggerManager.initialize(client, "Adapter"); this.initialized = true; this.client = client; this.unstable_onInitializeCallback?.({ client }); return this; }; onInitialize = (callback: (options: { client: ClientInstance }) => void) => { this.unstable_onInitializeCallback = callback; return this; }; /** * ******************** * Options Setters * ******************** */ public unstable_internalErrorMapping: (error: ReturnType) => any = (error) => error; /** Method to get default headers and to map them based on the data format exchange, by default it handles FormData / JSON formats. */ public unstable_headerMapper: HeaderMapperType = getAdapterHeaders as HeaderMapperType; /** Method to get request data and transform them to the required format. It handles FormData and JSON by default. */ public unstable_payloadMapper: PayloadMapperType = getAdapterPayload as PayloadMapperType; /** Method to get the endpoint for the adapter request. */ public unstable_endpointMapper: EndpointMapperType = defaultMapper as EndpointMapperType; /** Method to get request data and transform them to the required format. */ public unstable_queryParamsMapper: QueryParamsMapperType = defaultMapper as QueryParamsMapperType; /** Get default adapter options for the request. */ public unstable_getAdapterDefaults?: ( request: ExtendRequest< RequestInstance, { client: Client< any, Adapter< AdapterOptions, MethodType, StatusType, Extra, QueryParams, DefaultQueryParams, EndpointType, EndpointMapperType, QueryParamsMapperType, HeaderMapperType, PayloadMapperType > >; } >, ) => AdapterOptions; /** Get default request options for the request. */ public unstable_getRequestDefaults?: ( options: RequestOptionsType, ) => Partial>; /** * Get formatted endpoint name of the request. * Helpful in displaying long endpoints like in case of graphql schemas etc. */ public unstable_devtoolsEndpointGetter: (endpoint: string) => string = (endpoint) => endpoint; /** * ******************** * Methods * ******************** */ setDefaultMethod = (method: MethodType) => { this.defaultMethod = method; return this; }; setDefaultExtra = (extra: Extra) => { this.defaultExtra = extra; return this; }; setDevtoolsEndpointGetter = (callback: (endpoint: string) => string) => { this.unstable_devtoolsEndpointGetter = callback; return this; }; /** * This method allows to configure global defaults for the request configuration like method, auth, deduplication etc. */ setRequestDefaults = (callback: typeof this.unstable_getRequestDefaults): this => { this.unstable_getRequestDefaults = callback; return this; }; /** * Set the adapter default options added to every sent request */ setAdapterDefaults = ( callback: ( request: ExtendRequest< RequestInstance, { // No need for global error type, because we haven't sent the request yet client: Client< any, Adapter< AdapterOptions, MethodType, StatusType, Extra, QueryParams, DefaultQueryParams, EndpointType, EndpointMapperType, QueryParamsMapperType, HeaderMapperType, PayloadMapperType > >; } >, ) => AdapterOptions, ): this => { this.unstable_getAdapterDefaults = callback; return this; }; setInternalErrorMapping = (callback: (error: ReturnType) => any) => { this.unstable_internalErrorMapping = callback; return this; }; /** * Set the custom header mapping function */ setHeaderMapper = (headerMapper: NewMapper) => { this.unstable_headerMapper = ((req: Parameters[0]) => headerMapper(req, this.unstable_headerMapperConfig as Parameters[1])) as unknown as HeaderMapperType; return this as unknown as Adapter< AdapterOptions, MethodType, StatusType, Extra, QueryParams, DefaultQueryParams, EndpointType, EndpointMapperType, QueryParamsMapperType, NewMapper, PayloadMapperType >; }; /** * Set the request payload mapping function which gets triggered before request is send */ setPayloadMapper = (payloadMapper: NewMapper) => { this.unstable_payloadMapper = ((req: Parameters[0]) => payloadMapper( req, this.unstable_payloadMapperConfig as Parameters[1], )) as unknown as PayloadMapperType; return this as unknown as Adapter< AdapterOptions, MethodType, StatusType, Extra, QueryParams, DefaultQueryParams, EndpointType, EndpointMapperType, QueryParamsMapperType, HeaderMapperType, NewMapper >; }; /** * Set the request payload mapping function which get triggered before request get sent */ setEndpointMapper = >(endpointMapper: NewEndpointMapper) => { this.unstable_endpointMapper = ((endpoint: Parameters[0]) => endpointMapper( endpoint, this.unstable_endpointMapperConfig as Parameters[1], )) as unknown as EndpointMapperType; return this as unknown as Adapter< AdapterOptions, MethodType, StatusType, Extra, QueryParams, DefaultQueryParams, EndpointType, NewEndpointMapper, QueryParamsMapperType, HeaderMapperType, PayloadMapperType >; }; /** * Set the query params mapping function which get triggered before request get sent */ setQueryParamsMapper = >( queryParamsMapper: NewQueryParamsMapper, ) => { this.unstable_queryParamsMapper = ((queryParams: Parameters[0]) => queryParamsMapper( queryParams, this.unstable_queryParamsMapperConfig as Parameters[1], )) as unknown as QueryParamsMapperType; return this as unknown as Adapter< AdapterOptions, MethodType, StatusType, Extra, QueryParams, DefaultQueryParams, EndpointType, EndpointMapperType, NewQueryParamsMapper, HeaderMapperType, PayloadMapperType >; }; setQueryParamsMapperConfig = [1]>( config: NewQueryParamsMapperConfig, ) => { this.unstable_queryParamsMapperConfig = config; return this; }; setHeaderMapperConfig = [1]>( config: NewHeaderMapperConfig, ) => { this.unstable_headerMapperConfig = config; return this; }; setEndpointMapperConfig = [1]>( config: NewEndpointMapperConfig, ) => { this.unstable_endpointMapperConfig = config; return this; }; setPayloadMapperConfig = [1]>( config: NewPayloadMapperConfig, ) => { this.unstable_payloadMapperConfig = config; return this; }; /** * ******************** * Fetching * ******************** */ public setFetcher( fetcher: AdapterFetcherType< Adapter< AdapterOptions, MethodType, StatusType, Extra, QueryParams, DefaultQueryParams, EndpointType, EndpointMapperType, QueryParamsMapperType, HeaderMapperType, PayloadMapperType > >, ) { this.unstable_fetcher = fetcher.bind(this); return this; } public async fetch( request: ExtendRequest< RequestInstance, { // No need for global error type, it doesn't matter to the fetcher itself client: Client< any, Adapter< AdapterOptions, MethodType, StatusType, Extra, QueryParams, DefaultQueryParams, EndpointType, EndpointMapperType, QueryParamsMapperType, HeaderMapperType, PayloadMapperType > >; } >, requestId: string, ): Promise> { let startTime: number | undefined; const execute = async (resolve: (value: ResponseType) => void) => { try { if (!this.initialized) { throw new RequestProcessingError(`Adapter ${this.options.name} is not initialized`); } if (!this.unstable_fetcher) { throw new RequestProcessingError(`Fetcher for ${this.options.name} adapter is not set`); } this.client.triggerPlugins("onAdapterFetch", { adapter: this, request, requestId, }); const bindings = await getAdapterBindings< Adapter< AdapterOptions, MethodType, StatusType, Extra, QueryParams, DefaultQueryParams, EndpointType, EndpointMapperType, QueryParamsMapperType, HeaderMapperType, PayloadMapperType > >({ request, requestId, resolve, internalErrorMapping: this.unstable_internalErrorMapping, onStartTime: (time) => { startTime = time; }, }); if (request.unstable_mock && request.isMockerEnabled && request.client.isMockerEnabled) { return await mocker< Adapter< AdapterOptions, MethodType, StatusType, Extra, QueryParams, DefaultQueryParams, EndpointType, EndpointMapperType, QueryParamsMapperType, HeaderMapperType, PayloadMapperType > >(bindings); } return this.unstable_fetcher(bindings); } catch (error) { const onError = getAdapterOnError({ request, requestId, startTime: startTime || Date.now(), logger: this.logger, resolve, }); return onError({ error: this.unstable_internalErrorMapping(error as ReturnType), status: this.options.systemErrorStatus, extra: this.options.systemErrorExtra, }); } }; const promise = new Promise((resolve) => { execute(resolve); }); return promise as Promise>; } }