From 1961acad37bed174869a39b303da50858cf82fd0 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 22 Aug 2024 02:47:18 +0200 Subject: [PATCH] feat: Build --- dist/browser/index.global.js | 2 +- dist/browser/index.global.js.map | 2 +- dist/browser/index.mjs | 2 +- dist/browser/index.mjs.map | 2 +- dist/node/index.js | 2 +- dist/node/index.js.map | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dist/browser/index.global.js b/dist/browser/index.global.js index d1cf76e..ff2399c 100644 --- a/dist/browser/index.global.js +++ b/dist/browser/index.global.js @@ -1,2 +1,2 @@ -(()=>{async function C(c,e){if(!e)return c;let s=Array.isArray(e)?e:[e],t={...c};for(let n of s)t=await n(t);return t}async function w(c,e){if(!e)return c;let s=Array.isArray(e)?e:[e],t=c;for(let n of s)t=await n(t);return t}var b=class extends Error{response;request;config;status;statusText;constructor(e,s,t){super(e),this.name="ResponseError",this.message=e,this.status=t.status,this.statusText=t.statusText,this.request=s,this.config=s,this.response=t}};var R=class{requestInstance;baseURL="";timeout=3e4;cancellable=!1;rejectCancelled=!1;strategy="reject";method="get";flattenResponse=!1;defaultResponse=null;fetcher;logger;onError;requestsQueue;retry={retries:0,delay:1e3,maxDelay:3e4,backoff:1.5,retryOn:[408,409,425,429,500,502,503,504],shouldRetry:async()=>!0};config;constructor({fetcher:e=null,timeout:s=null,rejectCancelled:t=!1,strategy:n=null,flattenResponse:a=null,defaultResponse:l={},logger:r=null,onError:o=null,...i}){this.fetcher=e,this.timeout=s??this.timeout,this.strategy=n||this.strategy,this.cancellable=i.cancellable||this.cancellable,this.rejectCancelled=t||this.rejectCancelled,this.flattenResponse=a||this.flattenResponse,this.defaultResponse=l,this.logger=r||(globalThis?globalThis.console:null)||null,this.onError=o,this.requestsQueue=new WeakMap,this.baseURL=i.baseURL||i.apiUrl||"",this.method=i.method||this.method,this.config=i,this.retry={...this.retry,...i.retry||{}},this.requestInstance=this.isCustomFetcher()?e.create({...i,baseURL:this.baseURL,timeout:this.timeout}):null}getInstance(){return this.requestInstance}replaceUrlPathParams(e,s){return s?e.replace(/:[a-zA-Z]+/gi,t=>{let n=t.substring(1);return String(s[n]?s[n]:t)}):e}appendQueryParams(e,s){if(!s)return e;let t=Object.entries(s).flatMap(([n,a])=>Array.isArray(a)?a.map(l=>`${encodeURIComponent(n)}[]=${encodeURIComponent(l)}`):`${encodeURIComponent(n)}=${encodeURIComponent(String(a))}`).join("&");return e.includes("?")?`${e}&${t}`:t?`${e}?${t}`:e}isJSONSerializable(e){if(e==null)return!1;let s=typeof e;if(s==="string"||s==="number"||s==="boolean")return!0;if(s!=="object")return!1;if(Array.isArray(e))return!0;if(Buffer.isBuffer(e)||e instanceof Date)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null||typeof e.toJSON=="function"}buildConfig(e,s,t){let n=t.method||this.method,a=n.toLowerCase(),l=a==="get"||a==="head",r=this.replaceUrlPathParams(e,t.urlPathParams||this.config.urlPathParams),o=t.body||t.data||this.config.body||this.config.data;if(this.isCustomFetcher())return{...t,url:r,method:a,...l?{params:s}:{},...!l&&s&&o?{params:s}:{},...!l&&s&&!o?{data:s}:{},...!l&&o?{data:o}:{}};let i=o||s,y=t.withCredentials||this.config.withCredentials?"include":t.credentials;delete t.data,delete t.withCredentials;let d=!l&&s&&!t.body||!s?r:this.appendQueryParams(r,s),h=d.includes("://")?"":typeof t.baseURL<"u"?t.baseURL:this.baseURL;return{...t,credentials:y,url:h+d,method:n.toUpperCase(),headers:{Accept:"application/json, text/plain, */*","Content-Type":"application/json;charset=utf-8",...t.headers||this.config.headers||{}},...l?{}:{body:!(i instanceof URLSearchParams)&&this.isJSONSerializable(i)?typeof i=="string"?i:JSON.stringify(i):i}}}processError(e,s){var t;this.isRequestCancelled(e)||((t=this.logger)!=null&&t.warn&&this.logger.warn("API ERROR",e),s.onError&&typeof s.onError=="function"&&s.onError(e),this.onError&&typeof this.onError=="function"&&this.onError(e))}async outputErrorResponse(e,s){let t=this.isRequestCancelled(e),n=s.strategy||this.strategy,a=typeof s.rejectCancelled<"u"?s.rejectCancelled:this.rejectCancelled,l=typeof s.defaultResponse<"u"?s.defaultResponse:this.defaultResponse;return n==="softFail"?this.outputResponse(e.response,s,e):t&&!a?l:n==="silent"?(await new Promise(()=>null),l):n==="reject"?Promise.reject(e):l}isRequestCancelled(e){return e.name==="AbortError"||e.name==="CanceledError"}isCustomFetcher(){return this.fetcher!==null}addCancellationToken(e){if(!this.cancellable&&!e.cancellable)return{};if(typeof e.cancellable<"u"&&!e.cancellable)return{};if(typeof AbortController>"u")return console.error("AbortController is unavailable."),{};let s=this.requestsQueue.get(e);s&&s.abort();let t=new AbortController;if(!this.isCustomFetcher()&&this.timeout>0){let n=setTimeout(()=>{let a=new Error(`[TimeoutError]: The ${e.url} request was aborted due to timeout`);throw a.name="TimeoutError",a.code=23,t.abort(a),clearTimeout(n),a},e.timeout||this.timeout)}return this.requestsQueue.set(e,t),{signal:t.signal}}async request(e,s=null,t=null){var P,q,F;let n=null,a=t||{},l=this.buildConfig(e,s,a),r={...this.addCancellationToken(l),...l},{retries:o,delay:i,backoff:y,retryOn:d,shouldRetry:g,maxDelay:h}={...this.retry,...(r==null?void 0:r.retry)||{}},f=0,m=i;for(;f<=o;)try{if(r=await C(r,r.onRequest),r=await C(r,this.config.onRequest),this.isCustomFetcher())n=await this.requestInstance.request(r);else{n=await globalThis.fetch(r.url,r);let u=String(((P=n==null?void 0:n.headers)==null?void 0:P.get("Content-Type"))||""),p,j=n.clone();if(!u)try{p=await j.json()}catch{}if(typeof p>"u"&&(u&&(u.includes("application/json")||u.includes("+json"))?p=await n.json():u.includes("multipart/form-data")?p=await n.formData():u.includes("application/octet-stream")?p=await n.blob():u.includes("application/x-www-form-urlencoded")?p=await n.formData():typeof n.text<"u"?p=await n.text():p=n.body||n.data||null),n.config=r,n.data=p,!n.ok)throw new b(`${r.url} failed! Status: ${n.status||null}`,r,n)}return n=await w(n,r.onResponse),n=await w(n,this.config.onResponse),this.outputResponse(n,r)}catch(u){if(f===o||!await g(u,f)||!(d!=null&&d.includes(((q=u==null?void 0:u.response)==null?void 0:q.status)||(u==null?void 0:u.status))))return this.processError(u,r),this.outputErrorResponse(u,r);(F=this.logger)!=null&&F.warn&&this.logger.warn(`Attempt ${f+1} failed. Retrying in ${m}ms...`),await this.delay(m),m*=y,m=Math.min(m,h),f++}return this.outputResponse(n,r)}async delay(e){return new Promise(s=>setTimeout(()=>s(!0),e))}processHeaders(e){if(!e.headers)return{};let s={};if(e.headers instanceof Headers)for(let[t,n]of e.headers.entries())s[t]=n;else s={...e.headers};return s}outputResponse(e,s,t=null){let n=typeof s.defaultResponse<"u"?s.defaultResponse:this.defaultResponse;return e?(s.flattenResponse||this.flattenResponse)&&typeof e.data<"u"?e.data!==null&&typeof e.data=="object"&&typeof e.data.data<"u"&&Object.keys(e.data).length===1?e.data.data:e.data:e!==null&&typeof e=="object"&&e.constructor===Object&&Object.keys(e).length===0?n:this.isCustomFetcher()?e:(t!==null&&(t==null||delete t.response,t==null||delete t.request,t==null||delete t.config),{body:e.body,blob:e.blob,json:e.json,text:e.text,clone:e.clone,bodyUsed:e.bodyUsed,arrayBuffer:e.arrayBuffer,formData:e.formData,ok:e.ok,redirected:e.redirected,type:e.type,url:e.url,status:e.status,statusText:e.statusText,error:t,data:e.data,headers:this.processHeaders(e),config:s}):n}};function S(c){let e=c.endpoints,s=new R(c);function t(){return s.getInstance()}function n(o){return console.error(`${o} endpoint must be added to 'endpoints'.`),Promise.resolve(null)}async function a(o,i={},y={},d={}){let h={...e[o]};return await s.request(h.url,i,{...h,...d,urlPathParams:y})}function l(o){return o in r?r[o]:e[o]?r.request.bind(null,o):n.bind(null,o)}let r={config:c,endpoints:e,requestHandler:s,getInstance:t,request:a};return new Proxy(r,{get:(o,i)=>l(i)})}async function M(c,e={}){return new R(e).request(c,e.body||e.data||e.params,e)}})(); +(()=>{async function g(o,e){if(!e)return o;let s=Array.isArray(e)?e:[e],t={...o};for(let r of s)t=await r(t);return t}async function C(o,e){if(!e)return o;let s=Array.isArray(e)?e:[e],t=o;for(let r of s)t=await r(t);return t}var m=class extends Error{response;request;config;status;statusText;constructor(e,s,t){super(e),this.name="ResponseError",this.message=e,this.status=t.status,this.statusText=t.statusText,this.request=s,this.config=s,this.response=t}};function F(o,e){if(!e)return o;let s=[],t=function(a,n){n=typeof n=="function"?n():n,n=n===null||n===void 0?"":n,s[s.length]=encodeURIComponent(a)+"="+encodeURIComponent(n)},r=(a,n)=>{let i,p,c;if(a)if(Array.isArray(n))for(i=0,p=n.length;i{let t=s.substring(1);return String(e[t]?e[t]:s)}):o}function D(o){if(o==null)return!1;let e=typeof o;if(e==="string"||e==="number"||e==="boolean")return!0;if(e!=="object")return!1;if(Array.isArray(o))return!0;if(Buffer.isBuffer(o)||o instanceof Date)return!1;let s=Object.getPrototypeOf(o);return s===Object.prototype||s===null||typeof o.toJSON=="function"}async function I(o){return new Promise(e=>setTimeout(()=>e(!0),o))}var P="application/json",R=class{requestInstance;baseURL="";timeout=3e4;cancellable=!1;rejectCancelled=!1;strategy="reject";method="get";flattenResponse=!1;defaultResponse=null;fetcher;logger;onError;requestsQueue;retry={retries:0,delay:1e3,maxDelay:3e4,backoff:1.5,retryOn:[408,409,425,429,500,502,503,504],shouldRetry:async()=>!0};config;constructor({fetcher:e=null,timeout:s=null,rejectCancelled:t=!1,strategy:r=null,flattenResponse:l=null,defaultResponse:u={},logger:a=null,onError:n=null,...i}){this.fetcher=e,this.timeout=s??this.timeout,this.strategy=r||this.strategy,this.cancellable=i.cancellable||this.cancellable,this.rejectCancelled=t||this.rejectCancelled,this.flattenResponse=l||this.flattenResponse,this.defaultResponse=u,this.logger=a||(globalThis?globalThis.console:null)||null,this.onError=n,this.requestsQueue=new WeakMap,this.baseURL=i.baseURL||i.apiUrl||"",this.method=i.method||this.method,this.config=i,this.retry={...this.retry,...i.retry||{}},this.requestInstance=this.isCustomFetcher()?e.create({...i,baseURL:this.baseURL,timeout:this.timeout}):null}getInstance(){return this.requestInstance}buildConfig(e,s,t){let r=t.method||this.method,l=r.toLowerCase(),u=l==="get"||l==="head",a=A(e,t.urlPathParams||this.config.urlPathParams),n=t.body||t.data||this.config.body||this.config.data;if(this.isCustomFetcher())return{...t,url:a,method:l,...u?{params:s}:{},...!u&&s&&n?{params:s}:{},...!u&&s&&!n?{data:s}:{},...!u&&n?{data:n}:{}};let i=n||s,p=t.withCredentials||this.config.withCredentials?"include":t.credentials;delete t.data,delete t.withCredentials;let c=!u&&s&&!t.body||!s?a:F(a,s),f=c.includes("://")?"":typeof t.baseURL<"u"?t.baseURL:this.baseURL;return{...t,credentials:p,url:f+c,method:r.toUpperCase(),headers:{Accept:P+", text/plain, */*","Content-Type":P+";charset=utf-8",...t.headers||this.config.headers||{}},...u?{}:{body:!(i instanceof URLSearchParams)&&D(i)?typeof i=="string"?i:JSON.stringify(i):i}}}processError(e,s){var t;this.isRequestCancelled(e)||((t=this.logger)!=null&&t.warn&&this.logger.warn("API ERROR",e),s.onError&&typeof s.onError=="function"&&s.onError(e),this.onError&&typeof this.onError=="function"&&this.onError(e))}async outputErrorResponse(e,s){let t=this.isRequestCancelled(e),r=s.strategy||this.strategy,l=typeof s.rejectCancelled<"u"?s.rejectCancelled:this.rejectCancelled,u=typeof s.defaultResponse<"u"?s.defaultResponse:this.defaultResponse;return r==="softFail"?this.outputResponse(e.response,s,e):t&&!l?u:r==="silent"?(await new Promise(()=>null),u):r==="reject"?Promise.reject(e):u}isRequestCancelled(e){return e.name==="AbortError"||e.name==="CanceledError"}isCustomFetcher(){return this.fetcher!==null}addCancellationToken(e){if(!this.cancellable&&!e.cancellable)return{};if(typeof e.cancellable<"u"&&!e.cancellable)return{};if(typeof AbortController>"u")return console.error("AbortController is unavailable."),{};let s=this.requestsQueue.get(e);s&&s.abort();let t=new AbortController;if(!this.isCustomFetcher()&&this.timeout>0){let r=setTimeout(()=>{let l=new Error(`[TimeoutError]: The ${e.url} request was aborted due to timeout`);throw l.name="TimeoutError",l.code=23,t.abort(l),clearTimeout(r),l},e.timeout||this.timeout)}return this.requestsQueue.set(e,t),{signal:t.signal}}async request(e,s=null,t=null){var w,q;let r=null,l=t||{},u=this.buildConfig(e,s,l),a={...this.addCancellationToken(u),...u},{retries:n,delay:i,backoff:p,retryOn:c,shouldRetry:b,maxDelay:f}={...this.retry,...(a==null?void 0:a.retry)||{}},h=0,y=i;for(;h<=n;)try{if(a=await g(a,a.onRequest),a=await g(a,this.config.onRequest),this.isCustomFetcher())r=await this.requestInstance.request(a);else if(r=await globalThis.fetch(a.url,a),r.config=a,r.data=await this.parseData(r),!r.ok)throw new m(`${a.url} failed! Status: ${r.status||null}`,a,r);return r=await C(r,a.onResponse),r=await C(r,this.config.onResponse),this.outputResponse(r,a)}catch(d){if(h===n||!await b(d,h)||!(c!=null&&c.includes(((w=d==null?void 0:d.response)==null?void 0:w.status)||(d==null?void 0:d.status))))return this.processError(d,a),this.outputErrorResponse(d,a);(q=this.logger)!=null&&q.warn&&this.logger.warn(`Attempt ${h+1} failed. Retrying in ${y}ms...`),await I(y),y*=p,y=Math.min(y,f),h++}return this.outputResponse(r,a)}async parseData(e){var r;let s=String(((r=e.headers)==null?void 0:r.get("Content-Type"))||""),t;if(!s){let l=e.clone();try{t=await l.json()}catch{t=null}}if(typeof t>"u")try{s.includes(P)||s.includes("+json")?t=await e.json():s.includes("multipart/form-data")?t=await e.formData():s.includes("application/octet-stream")?t=await e.blob():s.includes("application/x-www-form-urlencoded")?t=await e.formData():typeof e.text=="function"?t=await e.text():t=e.body||e.data||null}catch{t=null}return t}processHeaders(e){if(!e.headers)return{};let s={},t=e.headers;if(t instanceof Headers)for(let[r,l]of t.entries())s[r]=l;else s={...t};return s}outputResponse(e,s,t=null){let r=typeof s.defaultResponse<"u"?s.defaultResponse:this.defaultResponse;return e?(s.flattenResponse||this.flattenResponse)&&typeof e.data<"u"?e.data!==null&&typeof e.data=="object"&&typeof e.data.data<"u"&&Object.keys(e.data).length===1?e.data.data:e.data:e!==null&&typeof e=="object"&&e.constructor===Object&&Object.keys(e).length===0?r:this.isCustomFetcher()?e:(t!==null&&(t==null||delete t.response,t==null||delete t.request,t==null||delete t.config),{body:e.body,blob:e.blob,json:e.json,text:e.text,clone:e.clone,bodyUsed:e.bodyUsed,arrayBuffer:e.arrayBuffer,formData:e.formData,ok:e.ok,redirected:e.redirected,type:e.type,url:e.url,status:e.status,statusText:e.statusText,error:t,data:e.data,headers:this.processHeaders(e),config:s}):r}};function Q(o){let e=o.endpoints,s=new R(o);function t(){return s.getInstance()}function r(n){return console.error(`${n} endpoint must be added to 'endpoints'.`),Promise.resolve(null)}async function l(n,i={},p={},c={}){let f={...e[n]};return await s.request(f.url,i,{...f,...c,urlPathParams:p})}function u(n){return n in a?a[n]:e[n]?a.request.bind(null,n):r.bind(null,n)}let a={config:o,endpoints:e,requestHandler:s,getInstance:t,request:l};return new Proxy(a,{get:(n,i)=>u(i)})}async function B(o,e={}){return new R(e).request(o,e.body||e.data||e.params,e)}})(); //# sourceMappingURL=index.global.js.map \ No newline at end of file diff --git a/dist/browser/index.global.js.map b/dist/browser/index.global.js.map index 7e107e2..a121ede 100644 --- a/dist/browser/index.global.js.map +++ b/dist/browser/index.global.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/interceptor-manager.ts","../src/response-error.ts","../src/request-handler.ts","../src/api-handler.ts","../src/index.ts"],"sourcesContent":["import type { RequestHandlerConfig, FetchResponse } from './types';\nimport type {\n RequestInterceptor,\n ResponseInterceptor,\n} from './types/interceptor-manager';\n\n/**\n * Applies a series of request interceptors to the provided configuration.\n * @param {RequestHandlerConfig} config - The initial request configuration.\n * @param {RequestInterceptor | RequestInterceptor[]} interceptors - The request interceptor function(s) to apply.\n * @returns {Promise} - The modified request configuration.\n */\nexport async function interceptRequest(\n config: RequestHandlerConfig,\n interceptors: RequestInterceptor | RequestInterceptor[],\n): Promise {\n if (!interceptors) {\n return config;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedConfig = { ...config };\n\n for (const interceptor of interceptorList) {\n interceptedConfig = await interceptor(interceptedConfig);\n }\n\n return interceptedConfig;\n}\n\n/**\n * Applies a series of response interceptors to the provided response.\n * @param {FetchResponse} response - The initial response object.\n * @param {ResponseInterceptor | ResponseInterceptor[]} interceptors - The response interceptor function(s) to apply.\n * @returns {Promise>} - The modified response object.\n */\nexport async function interceptResponse(\n response: FetchResponse,\n interceptors: ResponseInterceptor | ResponseInterceptor[],\n): Promise> {\n if (!interceptors) {\n return response;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedResponse = response;\n\n for (const interceptor of interceptorList) {\n interceptedResponse = await interceptor(interceptedResponse);\n }\n\n return interceptedResponse;\n}\n","import type { FetchResponse, RequestConfig } from './types';\n\nexport class ResponseErr extends Error {\n response: FetchResponse;\n request: RequestConfig;\n config: RequestConfig;\n status: number;\n statusText: string;\n\n constructor(\n message: string,\n requestInfo: RequestConfig,\n response: FetchResponse,\n ) {\n super(message);\n\n this.name = 'ResponseError';\n this.message = message;\n this.status = response.status;\n this.statusText = response.statusText;\n this.request = requestInfo;\n this.config = requestInfo;\n this.response = response;\n }\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {\n ErrorHandlingStrategy,\n RequestHandlerConfig,\n RequestConfig,\n FetcherInstance,\n Method,\n RetryOptions,\n FetchResponse,\n ResponseError,\n HeadersObject,\n} from './types/request-handler';\nimport type {\n APIResponse,\n QueryParams,\n QueryParamsOrBody,\n UrlPathParams,\n} from './types/api-handler';\nimport { interceptRequest, interceptResponse } from './interceptor-manager';\nimport { ResponseErr } from './response-error';\n\n/**\n * Generic Request Handler\n * It creates an Request Fetcher instance and handles requests within that instance\n * It handles errors depending on a chosen error handling strategy\n */\nexport class RequestHandler {\n /**\n * @var requestInstance Provider's instance\n */\n public requestInstance: FetcherInstance;\n\n /**\n * @var baseURL Base API url\n */\n public baseURL: string = '';\n\n /**\n * @var timeout Request timeout\n */\n public timeout: number = 30000;\n\n /**\n * @var cancellable Response cancellation\n */\n public cancellable: boolean = false;\n\n /**\n * @var rejectCancelled Whether to reject cancelled requests or not\n */\n public rejectCancelled: boolean = false;\n\n /**\n * @var strategy Request timeout\n */\n public strategy: ErrorHandlingStrategy = 'reject';\n\n /**\n * @var method Request method\n */\n public method: Method | string = 'get';\n\n /**\n * @var flattenResponse Response flattening\n */\n public flattenResponse: boolean = false;\n\n /**\n * @var defaultResponse Response flattening\n */\n public defaultResponse: any = null;\n\n /**\n * @var fetcher Request Fetcher instance\n */\n protected fetcher: FetcherInstance;\n\n /**\n * @var logger Logger\n */\n protected logger: any;\n\n /**\n * @var onError HTTP error service\n */\n protected onError: any;\n\n /**\n * @var requestsQueue Queue of requests\n */\n protected requestsQueue: WeakMap;\n\n /**\n * Request Handler Config\n */\n protected retry: RetryOptions = {\n retries: 0,\n delay: 1000,\n maxDelay: 30000,\n backoff: 1.5,\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status\n retryOn: [\n 408, // Request Timeout\n 409, // Conflict\n 425, // Too Early\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n ],\n\n shouldRetry: async () => true,\n };\n\n /**\n * Request Handler Config\n */\n public config: RequestHandlerConfig;\n\n /**\n * Creates an instance of RequestHandler.\n *\n * @param {Object} config - Configuration object for the request.\n * @param {string} config.baseURL - The base URL for the request.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n */\n public constructor({\n fetcher = null,\n timeout = null,\n rejectCancelled = false,\n strategy = null,\n flattenResponse = null,\n defaultResponse = {},\n logger = null,\n onError = null,\n ...config\n }: RequestHandlerConfig) {\n this.fetcher = fetcher;\n this.timeout =\n timeout !== null && timeout !== undefined ? timeout : this.timeout;\n this.strategy = strategy || this.strategy;\n this.cancellable = config.cancellable || this.cancellable;\n this.rejectCancelled = rejectCancelled || this.rejectCancelled;\n this.flattenResponse = flattenResponse || this.flattenResponse;\n this.defaultResponse = defaultResponse;\n this.logger = logger || (globalThis ? globalThis.console : null) || null;\n this.onError = onError;\n this.requestsQueue = new WeakMap();\n this.baseURL = config.baseURL || config.apiUrl || '';\n this.method = config.method || this.method;\n this.config = config;\n this.retry = {\n ...this.retry,\n ...(config.retry || {}),\n };\n\n this.requestInstance = this.isCustomFetcher()\n ? (fetcher as any).create({\n ...config,\n baseURL: this.baseURL,\n timeout: this.timeout,\n })\n : null;\n }\n\n /**\n * Get Provider Instance\n *\n * @returns {FetcherInstance} Provider's instance\n */\n public getInstance(): FetcherInstance {\n return this.requestInstance;\n }\n\n /**\n * Replaces dynamic URI parameters in a URL string with values from the provided `urlPathParams` object.\n * Parameters in the URL are denoted by `:`, where `` is a key in `urlPathParams`.\n *\n * @param {string} url - The URL string containing placeholders in the format `:`.\n * @param {Object} urlPathParams - An object containing the parameter values to replace placeholders.\n * @param {string} urlPathParams.paramName - The value to replace the placeholder `:` in the URL.\n * @returns {string} - The URL string with placeholders replaced by corresponding values from `urlPathParams`.\n */\n public replaceUrlPathParams(\n url: string,\n urlPathParams: UrlPathParams,\n ): string {\n if (!urlPathParams) {\n return url;\n }\n\n return url.replace(/:[a-zA-Z]+/gi, (str): string => {\n const word = str.substring(1);\n\n return String(urlPathParams[word] ? urlPathParams[word] : str);\n });\n }\n\n /**\n * Appends query parameters to the given URL\n *\n * @param {string} url - The base URL to which query parameters will be appended.\n * @param {QueryParams} params - An instance of URLSearchParams containing the query parameters to append.\n * @returns {string} - The URL with the appended query parameters.\n */\n public appendQueryParams(url: string, params: QueryParams): string {\n if (!params) {\n return url;\n }\n\n // We don't use URLSearchParams here as we want to ensure that arrays are properly converted similarily to Axios\n // So { foo: [1, 2] } would become: foo[]=1&foo[]=2\n const queryString = Object.entries(params)\n .flatMap(([key, value]) => {\n if (Array.isArray(value)) {\n return value.map(\n (val) => `${encodeURIComponent(key)}[]=${encodeURIComponent(val)}`,\n );\n }\n return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`;\n })\n .join('&');\n\n return url.includes('?')\n ? `${url}&${queryString}`\n : queryString\n ? `${url}?${queryString}`\n : url;\n }\n\n /**\n * Checks if a value is JSON serializable.\n *\n * JSON serializable values include:\n * - Primitive types: string, number, boolean, null\n * - Arrays\n * - Plain objects (i.e., objects without special methods)\n * - Values with a `toJSON` method\n *\n * @param {any} value - The value to check for JSON serializability.\n * @returns {boolean} - Returns `true` if the value is JSON serializable, otherwise `false`.\n */\n protected isJSONSerializable(value: any): boolean {\n if (value === undefined || value === null) {\n return false;\n }\n\n const t = typeof value;\n if (t === 'string' || t === 'number' || t === 'boolean') {\n return true;\n }\n\n if (t !== 'object') {\n return false; // bigint, function, symbol, undefined\n }\n\n if (Array.isArray(value)) {\n return true;\n }\n\n if (Buffer.isBuffer(value)) {\n return false;\n }\n\n if (value instanceof Date) {\n return false;\n }\n\n const proto = Object.getPrototypeOf(value);\n\n // Check if the prototype is `Object.prototype` or `null` (plain object)\n if (proto === Object.prototype || proto === null) {\n return true;\n }\n\n // Check if the object has a toJSON method\n if (typeof value.toJSON === 'function') {\n return true;\n }\n\n return false;\n }\n\n /**\n * Build request configuration\n *\n * @param {string} url Request url\n * @param {QueryParamsOrBody} data Request data\n * @param {RequestConfig} config Request config\n * @returns {RequestConfig} Provider's instance\n */\n protected buildConfig(\n url: string,\n data: QueryParamsOrBody,\n config: RequestConfig,\n ): RequestConfig {\n const method = config.method || this.method;\n const methodLowerCase = method.toLowerCase();\n const isGetAlikeMethod =\n methodLowerCase === 'get' || methodLowerCase === 'head';\n\n const dynamicUrl = this.replaceUrlPathParams(\n url,\n config.urlPathParams || this.config.urlPathParams,\n );\n\n // Bonus: Specifying it here brings support for \"body\" in Axios\n const configData =\n config.body || config.data || this.config.body || this.config.data;\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n return {\n ...config,\n url: dynamicUrl,\n method: methodLowerCase,\n\n ...(isGetAlikeMethod ? { params: data } : {}),\n\n // For POST requests body payload is the first param for convenience (\"data\")\n // In edge cases we want to split so to treat it as query params, and use \"body\" coming from the config instead\n ...(!isGetAlikeMethod && data && configData ? { params: data } : {}),\n\n // Only applicable for request methods 'PUT', 'POST', 'DELETE', and 'PATCH'\n ...(!isGetAlikeMethod && data && !configData ? { data } : {}),\n ...(!isGetAlikeMethod && configData ? { data: configData } : {}),\n };\n }\n\n // Native fetch\n const payload = configData || data;\n const credentials =\n config.withCredentials || this.config.withCredentials\n ? 'include'\n : config.credentials;\n\n delete config.data;\n delete config.withCredentials;\n\n const urlPath =\n (!isGetAlikeMethod && data && !config.body) || !data\n ? dynamicUrl\n : this.appendQueryParams(dynamicUrl, data);\n const isFullUrl = urlPath.includes('://');\n const baseURL = isFullUrl\n ? ''\n : typeof config.baseURL !== 'undefined'\n ? config.baseURL\n : this.baseURL;\n\n return {\n ...config,\n credentials,\n\n // Native fetch generally requires query params to be appended in the URL\n // Do not append query params only if it's a POST-alike request with only \"data\" specified as it's treated as body payload\n url: baseURL + urlPath,\n\n // Uppercase method name\n method: method.toUpperCase(),\n\n // For convenience, add the same default headers as Axios does\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json;charset=utf-8',\n ...(config.headers || this.config.headers || {}),\n },\n\n // Automatically JSON stringify request bodies, if possible and when not dealing with strings\n ...(!isGetAlikeMethod\n ? {\n body:\n !(payload instanceof URLSearchParams) &&\n this.isJSONSerializable(payload)\n ? typeof payload === 'string'\n ? payload\n : JSON.stringify(payload)\n : payload,\n }\n : {}),\n };\n }\n\n /**\n * Process global Request Error\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {void}\n */\n protected processError(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): void {\n if (this.isRequestCancelled(error)) {\n return;\n }\n\n if (this.logger?.warn) {\n this.logger.warn('API ERROR', error);\n }\n\n // Invoke per request \"onError\" interceptor\n if (requestConfig.onError && typeof requestConfig.onError === 'function') {\n requestConfig.onError(error);\n }\n\n // Invoke global \"onError\" interceptor\n if (this.onError && typeof this.onError === 'function') {\n this.onError(error);\n }\n }\n\n /**\n * Output default response in case of an error, depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {*} Error response\n */\n protected async outputErrorResponse(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): Promise {\n const isRequestCancelled = this.isRequestCancelled(error);\n const errorHandlingStrategy = requestConfig.strategy || this.strategy;\n const rejectCancelled =\n typeof requestConfig.rejectCancelled !== 'undefined'\n ? requestConfig.rejectCancelled\n : this.rejectCancelled;\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n // Output full response with the error object\n if (errorHandlingStrategy === 'softFail') {\n return this.outputResponse(error.response, requestConfig, error);\n }\n\n // By default cancelled requests aren't rejected\n if (isRequestCancelled && !rejectCancelled) {\n return defaultResponse;\n }\n\n // Hang the promise\n if (errorHandlingStrategy === 'silent') {\n await new Promise(() => null);\n\n return defaultResponse;\n }\n\n // Reject the promise\n if (errorHandlingStrategy === 'reject') {\n return Promise.reject(error);\n }\n\n return defaultResponse;\n }\n\n /**\n * Output error response depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @returns {boolean} True if request is aborted\n */\n public isRequestCancelled(error: ResponseError): boolean {\n return error.name === 'AbortError' || error.name === 'CanceledError';\n }\n\n /**\n * Detects if a custom fetcher is utilized\n *\n * @returns {boolean} True if it's a custom fetcher\n */\n protected isCustomFetcher(): boolean {\n return this.fetcher !== null;\n }\n\n /**\n * Automatically Cancel Previous Requests using AbortController when \"cancellable\" is defined\n *\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {Object} Controller Signal to abort\n */\n protected addCancellationToken(\n requestConfig: RequestConfig,\n ): Partial> {\n // Both disabled\n if (!this.cancellable && !requestConfig.cancellable) {\n return {};\n }\n\n // Explicitly disabled per request\n if (\n typeof requestConfig.cancellable !== 'undefined' &&\n !requestConfig.cancellable\n ) {\n return {};\n }\n\n // Check if AbortController is available\n if (typeof AbortController === 'undefined') {\n console.error('AbortController is unavailable.');\n\n return {};\n }\n\n // Generate unique key as a cancellation token\n const previousRequest = this.requestsQueue.get(requestConfig);\n\n if (previousRequest) {\n previousRequest.abort();\n }\n\n const controller = new AbortController();\n\n // Introduce timeout for native fetch\n if (!this.isCustomFetcher() && this.timeout > 0) {\n const abortTimeout = setTimeout(() => {\n const error = new Error(\n `[TimeoutError]: The ${requestConfig.url} request was aborted due to timeout`,\n );\n\n error.name = 'TimeoutError';\n (error as any).code = 23; // DOMException.TIMEOUT_ERR\n controller.abort(error);\n clearTimeout(abortTimeout);\n throw error;\n }, requestConfig.timeout || this.timeout);\n }\n\n this.requestsQueue.set(requestConfig, controller);\n\n return {\n signal: controller.signal,\n };\n }\n\n /**\n * Handle Request depending on used strategy\n *\n * @param {string} url - Request url\n * @param {QueryParamsOrBody} data - Request data\n * @param {RequestConfig} config - Request config\n * @param {RequestConfig} payload.config Request config\n * @throws {ResponseError}\n * @returns {Promise>} Response Data\n */\n public async request(\n url: string,\n data: QueryParamsOrBody = null,\n config: RequestConfig = null,\n ): Promise> {\n let response: FetchResponse = null;\n const _config = config || {};\n const _requestConfig = this.buildConfig(url, data, _config);\n\n let requestConfig: RequestConfig = {\n ...this.addCancellationToken(_requestConfig),\n ..._requestConfig,\n };\n\n const { retries, delay, backoff, retryOn, shouldRetry, maxDelay } = {\n ...this.retry,\n ...(requestConfig?.retry || {}),\n };\n\n let attempt = 0;\n let waitTime = delay;\n\n while (attempt <= retries) {\n try {\n // Local interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n requestConfig.onRequest,\n );\n\n // Global interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n this.config.onRequest,\n );\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n response = (await (this.requestInstance as any).request(\n requestConfig,\n )) as FetchResponse;\n } else {\n response = (await globalThis.fetch(\n requestConfig.url,\n requestConfig,\n )) as FetchResponse;\n\n // Attempt to collect response data regardless of response status\n const contentType = String(\n (response as Response)?.headers?.get('Content-Type') || '',\n );\n let data;\n const responseClone = response.clone();\n\n // Handle edge case of no content type being provided... We assume json here.\n if (!contentType) {\n try {\n data = await responseClone.json();\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (_error) {\n //\n }\n }\n\n if (typeof data === 'undefined') {\n if (\n contentType &&\n (contentType.includes('application/json') ||\n // This Media Type Suffix is standardizded by IETF in RFC 6839\n contentType.includes('+json'))\n ) {\n data = await response.json(); // Parse JSON response\n } else if (contentType.includes('multipart/form-data')) {\n data = await response.formData(); // Parse as FormData\n } else if (contentType.includes('application/octet-stream')) {\n data = await response.blob(); // Parse as blob\n } else if (\n contentType.includes('application/x-www-form-urlencoded')\n ) {\n data = await response.formData(); // Handle URL-encoded forms\n } else if (typeof response.text !== 'undefined') {\n data = await response.text();\n } else {\n // Handle streams\n data = response.body || response.data || null;\n }\n }\n\n // Add more information to response object\n response.config = requestConfig;\n response.data = data;\n\n // Check if the response status is not outside the range 200-299 and if so, output error\n if (!response.ok) {\n throw new ResponseErr(\n `${requestConfig.url} failed! Status: ${response.status || null}`,\n requestConfig,\n response,\n );\n }\n }\n\n // Local interceptors\n response = await interceptResponse(response, requestConfig.onResponse);\n\n // Global interceptors\n response = await interceptResponse(response, this.config.onResponse);\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n } catch (error) {\n if (\n attempt === retries ||\n !(await shouldRetry(error, attempt)) ||\n !retryOn?.includes(error?.response?.status || error?.status)\n ) {\n this.processError(error, requestConfig);\n\n return this.outputErrorResponse(error, requestConfig);\n }\n\n if (this.logger?.warn) {\n this.logger.warn(\n `Attempt ${attempt + 1} failed. Retrying in ${waitTime}ms...`,\n );\n }\n\n await this.delay(waitTime);\n\n waitTime *= backoff;\n waitTime = Math.min(waitTime, maxDelay);\n attempt++;\n }\n }\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n }\n\n public async delay(ms: number): Promise {\n return new Promise((resolve) =>\n setTimeout(() => {\n return resolve(true);\n }, ms),\n );\n }\n\n public processHeaders(\n response: FetchResponse,\n ): HeadersObject {\n if (!response.headers) {\n return {};\n }\n\n let headersObject: HeadersObject = {};\n\n // Handle Headers object with entries() method\n if (response.headers instanceof Headers) {\n for (const [key, value] of (response.headers as any).entries()) {\n headersObject[key] = value;\n }\n } else {\n // Handle plain object\n headersObject = { ...(response.headers as HeadersObject) };\n }\n\n return headersObject;\n }\n\n /**\n * Output response\n *\n * @param response - Response payload\n * @param {RequestConfig} requestConfig - Request config\n * @param {*} error - whether the response is erroneous\n * @returns {ResponseData | FetchResponse} Response data\n */\n protected outputResponse(\n response: FetchResponse,\n requestConfig: RequestConfig,\n error = null,\n ): ResponseData | FetchResponse {\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n if (!response) {\n return defaultResponse;\n }\n\n if (\n (requestConfig.flattenResponse || this.flattenResponse) &&\n typeof response.data !== 'undefined'\n ) {\n // Special case of only data property within response data object (happens in Axios)\n // This is in fact a proper response but we may want to flatten it\n // To ease developers' lives when obtaining the response\n if (\n response.data !== null &&\n typeof response.data === 'object' &&\n typeof (response.data as any).data !== 'undefined' &&\n Object.keys(response.data).length === 1\n ) {\n return (response.data as any).data;\n }\n\n return response.data;\n }\n\n // If empty object is returned, ensure that the default response is used instead\n if (\n response !== null &&\n typeof response === 'object' &&\n response.constructor === Object &&\n Object.keys(response).length === 0\n ) {\n return defaultResponse;\n }\n\n const isCustomFetcher = this.isCustomFetcher();\n\n if (isCustomFetcher) {\n return response;\n }\n\n if (error !== null) {\n delete error?.response;\n delete error?.request;\n delete error?.config;\n }\n\n // Native fetch()\n return {\n body: response.body,\n blob: response.blob,\n json: response.json,\n text: response.text,\n clone: response.clone,\n bodyUsed: response.bodyUsed,\n arrayBuffer: response.arrayBuffer,\n formData: response.formData,\n ok: response.ok,\n redirected: response.redirected,\n type: response.type,\n url: response.url,\n status: response.status,\n statusText: response.statusText,\n\n // Extend with extra information\n error,\n data: response.data,\n headers: this.processHeaders(response),\n config: requestConfig,\n };\n }\n}\n","import { RequestHandler } from './request-handler';\nimport type {\n FetcherInstance,\n RequestConfig,\n FetchResponse,\n} from './types/request-handler';\nimport type {\n ApiHandlerConfig,\n ApiHandlerMethods,\n ApiHandlerReturnType,\n APIResponse,\n QueryParams,\n UrlPathParams,\n} from './types/api-handler';\n\n/**\n * Creates an instance of API Handler.\n * It creates an API fetcher function using native fetch() or Axios if it is passed as \"fetcher\".\n *\n * @param {Object} config - Configuration object for the API fetcher.\n * @param {string} config.apiUrl - The base URL for the API.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n * @returns API handler functions and endpoints to call\n *\n * @example\n * // Import axios (optional)\n * import axios from 'axios';\n *\n * // Define endpoint paths\n * const endpoints = {\n * getUser: '/user',\n * createPost: '/post',\n * };\n *\n * // Create the API fetcher with configuration\n * const api = createApiFetcher({\n * fetcher: axios, // Axios instance (optional)\n * endpoints,\n * apiUrl: 'https://example.com/api',\n * onError(error) {\n * console.log('Request failed', error);\n * },\n * headers: {\n * 'my-auth-key': 'example-auth-key-32rjjfa',\n * },\n * });\n *\n * // Fetch user data\n * const response = await api.getUser({ userId: 1, ratings: [1, 2] })\n */\nfunction createApiFetcher<\n EndpointsMethods extends object,\n EndpointsCfg = never,\n>(config: ApiHandlerConfig) {\n const endpoints = config.endpoints;\n const requestHandler = new RequestHandler(config);\n\n /**\n * Get Fetcher Provider Instance\n *\n * @returns {FetcherInstance} Request Handler's Fetcher instance\n */\n function getInstance(): FetcherInstance {\n return requestHandler.getInstance();\n }\n\n /**\n * Triggered when trying to use non-existent endpoints\n *\n * @param endpointName Endpoint Name\n * @returns {Promise}\n */\n function handleNonImplemented(endpointName: string): Promise {\n console.error(`${endpointName} endpoint must be added to 'endpoints'.`);\n\n return Promise.resolve(null);\n }\n\n /**\n * Handle Single API Request\n * It considers settings in following order: per-request settings, global per-endpoint settings, global settings.\n *\n * @param {string} endpointName - The name of the API endpoint to call.\n * @param {QueryParams} [queryParams={}] - Query parameters to include in the request.\n * @param {UrlPathParams} [urlPathParams={}] - URI parameters to include in the request.\n * @param {EndpointConfig} [requestConfig={}] - Additional configuration for the request.\n * @returns {Promise} - A promise that resolves with the response from the API provider.\n */\n async function request(\n endpointName: keyof EndpointsMethods | string,\n queryParams: QueryParams = {},\n urlPathParams: UrlPathParams = {},\n requestConfig: RequestConfig = {},\n ): Promise> {\n // Use global per-endpoint settings\n const endpointConfig = endpoints[endpointName as string];\n const endpointSettings = { ...endpointConfig };\n\n const responseData = await requestHandler.request(\n endpointSettings.url,\n queryParams,\n {\n ...endpointSettings,\n ...requestConfig,\n urlPathParams,\n },\n );\n\n return responseData;\n }\n\n /**\n * Maps all API requests using native Proxy\n *\n * @param {*} prop Caller\n */\n function get(prop: string | symbol) {\n if (prop in apiHandler) {\n return apiHandler[prop];\n }\n\n // Prevent handler from triggering non-existent endpoints\n if (!endpoints[prop as string]) {\n return handleNonImplemented.bind(null, prop);\n }\n\n return apiHandler.request.bind(null, prop);\n }\n\n const apiHandler: ApiHandlerMethods = {\n config,\n endpoints,\n requestHandler,\n getInstance,\n request,\n };\n\n return new Proxy(apiHandler, {\n get: (_target, prop) => get(prop),\n }) as ApiHandlerReturnType;\n}\n\nexport { createApiFetcher };\n","import { RequestHandler } from './request-handler';\nimport type { APIResponse, FetchResponse, RequestHandlerConfig } from './types';\n\n/**\n * Simple wrapper for request fetching.\n * It abstracts the creation of RequestHandler, making it easy to perform API requests.\n *\n * @param {string | URL | globalThis.Request} url - Request URL.\n * @param {RequestHandlerConfig} config - Configuration object for the request handler.\n * @returns {Promise>} Response Data.\n */\nexport async function fetchf(\n url: string,\n config: RequestHandlerConfig = {},\n): Promise> {\n return new RequestHandler(config).request(\n url,\n config.body || config.data || config.params,\n config,\n );\n}\n\nexport * from './types';\nexport * from './api-handler';\n"],"mappings":"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","names":["interceptRequest","config","interceptors","interceptorList","interceptedConfig","interceptor","interceptResponse","response","interceptedResponse","ResponseErr","message","requestInfo","response","RequestHandler","fetcher","timeout","rejectCancelled","strategy","flattenResponse","defaultResponse","logger","onError","config","url","urlPathParams","str","word","params","queryString","key","value","val","t","proto","data","method","methodLowerCase","isGetAlikeMethod","dynamicUrl","configData","payload","credentials","urlPath","baseURL","error","requestConfig","_a","isRequestCancelled","errorHandlingStrategy","previousRequest","controller","abortTimeout","_b","_c","response","_config","_requestConfig","retries","delay","backoff","retryOn","shouldRetry","maxDelay","attempt","waitTime","interceptRequest","contentType","responseClone","ResponseErr","interceptResponse","ms","resolve","headersObject","createApiFetcher","config","endpoints","requestHandler","RequestHandler","getInstance","handleNonImplemented","endpointName","request","queryParams","urlPathParams","requestConfig","endpointSettings","get","prop","apiHandler","_target","fetchf","url","config","RequestHandler"]} \ No newline at end of file +{"version":3,"sources":["../src/interceptor-manager.ts","../src/response-error.ts","../src/utils.ts","../src/request-handler.ts","../src/api-handler.ts","../src/index.ts"],"sourcesContent":["import type { RequestHandlerConfig, FetchResponse } from './types';\nimport type {\n RequestInterceptor,\n ResponseInterceptor,\n} from './types/interceptor-manager';\n\n/**\n * Applies a series of request interceptors to the provided configuration.\n * @param {RequestHandlerConfig} config - The initial request configuration.\n * @param {RequestInterceptor | RequestInterceptor[]} interceptors - The request interceptor function(s) to apply.\n * @returns {Promise} - The modified request configuration.\n */\nexport async function interceptRequest(\n config: RequestHandlerConfig,\n interceptors: RequestInterceptor | RequestInterceptor[],\n): Promise {\n if (!interceptors) {\n return config;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedConfig = { ...config };\n\n for (const interceptor of interceptorList) {\n interceptedConfig = await interceptor(interceptedConfig);\n }\n\n return interceptedConfig;\n}\n\n/**\n * Applies a series of response interceptors to the provided response.\n * @param {FetchResponse} response - The initial response object.\n * @param {ResponseInterceptor | ResponseInterceptor[]} interceptors - The response interceptor function(s) to apply.\n * @returns {Promise>} - The modified response object.\n */\nexport async function interceptResponse(\n response: FetchResponse,\n interceptors: ResponseInterceptor | ResponseInterceptor[],\n): Promise> {\n if (!interceptors) {\n return response;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedResponse = response;\n\n for (const interceptor of interceptorList) {\n interceptedResponse = await interceptor(interceptedResponse);\n }\n\n return interceptedResponse;\n}\n","import type { FetchResponse, RequestConfig } from './types';\n\nexport class ResponseErr extends Error {\n response: FetchResponse;\n request: RequestConfig;\n config: RequestConfig;\n status: number;\n statusText: string;\n\n constructor(\n message: string,\n requestInfo: RequestConfig,\n response: FetchResponse,\n ) {\n super(message);\n\n this.name = 'ResponseError';\n this.message = message;\n this.status = response.status;\n this.statusText = response.statusText;\n this.request = requestInfo;\n this.config = requestInfo;\n this.response = response;\n }\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { QueryParams, UrlPathParams } from './types';\n\n/**\n * Appends query parameters to a given URL.\n *\n * @param {string} url - The base URL to which query parameters will be appended.\n * @param {QueryParams} params - An object containing the query parameters to append.\n * @returns {string} - The URL with the appended query parameters.\n */\nexport function appendQueryParams(url: string, params: QueryParams): string {\n if (!params) {\n return url;\n }\n\n // This is exact copy of what JQ used to do. It works much better than URLSearchParams\n const s = [];\n const add = function (k: string, v: any) {\n v = typeof v === 'function' ? v() : v;\n v = v === null ? '' : v === undefined ? '' : v;\n s[s.length] = encodeURIComponent(k) + '=' + encodeURIComponent(v);\n };\n\n const buildParams = (prefix: string, obj: any) => {\n let i: number, len: number, key: string;\n\n if (prefix) {\n if (Array.isArray(obj)) {\n for (i = 0, len = obj.length; i < len; i++) {\n buildParams(\n prefix +\n '[' +\n (typeof obj[i] === 'object' && obj[i] ? i : '') +\n ']',\n obj[i],\n );\n }\n } else if (typeof obj === 'object' && obj !== null) {\n for (key in obj) {\n buildParams(prefix + '[' + key + ']', obj[key]);\n }\n } else {\n add(prefix, obj);\n }\n } else if (Array.isArray(obj)) {\n for (i = 0, len = obj.length; i < len; i++) {\n add(obj[i].name, obj[i].value);\n }\n } else {\n for (key in obj) {\n buildParams(key, obj[key]);\n }\n }\n return s;\n };\n\n const queryStringParts = buildParams('', params).join('&');\n\n // Encode special characters as per RFC 3986, https://datatracker.ietf.org/doc/html/rfc3986\n const encodedQueryString = queryStringParts.replace(/%5B%5D/g, '[]'); // Keep '[]' for arrays\n\n return url.includes('?')\n ? `${url}&${encodedQueryString}`\n : encodedQueryString\n ? `${url}?${encodedQueryString}`\n : url;\n}\n\n/**\n * Replaces dynamic URI parameters in a URL string with values from the provided `urlPathParams` object.\n * Parameters in the URL are denoted by `:`, where `` is a key in `urlPathParams`.\n *\n * @param {string} url - The URL string containing placeholders in the format `:`.\n * @param {Object} urlPathParams - An object containing the parameter values to replace placeholders.\n * @param {string} urlPathParams.paramName - The value to replace the placeholder `:` in the URL.\n * @returns {string} - The URL string with placeholders replaced by corresponding values from `urlPathParams`.\n */\nexport function replaceUrlPathParams(\n url: string,\n urlPathParams: UrlPathParams,\n): string {\n if (!urlPathParams) {\n return url;\n }\n\n return url.replace(/:[a-zA-Z]+/gi, (str): string => {\n const word = str.substring(1);\n\n return String(urlPathParams[word] ? urlPathParams[word] : str);\n });\n}\n\n/**\n * Checks if a value is JSON serializable.\n *\n * JSON serializable values include:\n * - Primitive types: string, number, boolean, null\n * - Arrays\n * - Plain objects (i.e., objects without special methods)\n * - Values with a `toJSON` method\n *\n * @param {any} value - The value to check for JSON serializability.\n * @returns {boolean} - Returns `true` if the value is JSON serializable, otherwise `false`.\n */\nexport function isJSONSerializable(value: any): boolean {\n if (value === undefined || value === null) {\n return false;\n }\n\n const t = typeof value;\n if (t === 'string' || t === 'number' || t === 'boolean') {\n return true;\n }\n\n if (t !== 'object') {\n return false; // bigint, function, symbol, undefined\n }\n\n if (Array.isArray(value)) {\n return true;\n }\n\n if (Buffer.isBuffer(value)) {\n return false;\n }\n\n if (value instanceof Date) {\n return false;\n }\n\n const proto = Object.getPrototypeOf(value);\n\n // Check if the prototype is `Object.prototype` or `null` (plain object)\n if (proto === Object.prototype || proto === null) {\n return true;\n }\n\n // Check if the object has a toJSON method\n if (typeof value.toJSON === 'function') {\n return true;\n }\n\n return false;\n}\n\nexport async function delayInvocation(ms: number): Promise {\n return new Promise((resolve) =>\n setTimeout(() => {\n return resolve(true);\n }, ms),\n );\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {\n ErrorHandlingStrategy,\n RequestHandlerConfig,\n RequestConfig,\n FetcherInstance,\n Method,\n RetryOptions,\n FetchResponse,\n ResponseError,\n HeadersObject,\n} from './types/request-handler';\nimport type { APIResponse, QueryParamsOrBody } from './types/api-handler';\nimport { interceptRequest, interceptResponse } from './interceptor-manager';\nimport { ResponseErr } from './response-error';\nimport {\n appendQueryParams,\n isJSONSerializable,\n replaceUrlPathParams,\n delayInvocation,\n} from './utils';\n\nconst APPLICATION_JSON = 'application/json';\n\n/**\n * Generic Request Handler\n * It creates an Request Fetcher instance and handles requests within that instance\n * It handles errors depending on a chosen error handling strategy\n */\nexport class RequestHandler {\n /**\n * @var requestInstance Provider's instance\n */\n public requestInstance: FetcherInstance;\n\n /**\n * @var baseURL Base API url\n */\n public baseURL: string = '';\n\n /**\n * @var timeout Request timeout\n */\n public timeout: number = 30000;\n\n /**\n * @var cancellable Response cancellation\n */\n public cancellable: boolean = false;\n\n /**\n * @var rejectCancelled Whether to reject cancelled requests or not\n */\n public rejectCancelled: boolean = false;\n\n /**\n * @var strategy Request timeout\n */\n public strategy: ErrorHandlingStrategy = 'reject';\n\n /**\n * @var method Request method\n */\n public method: Method | string = 'get';\n\n /**\n * @var flattenResponse Response flattening\n */\n public flattenResponse: boolean = false;\n\n /**\n * @var defaultResponse Response flattening\n */\n public defaultResponse: any = null;\n\n /**\n * @var fetcher Request Fetcher instance\n */\n protected fetcher: FetcherInstance;\n\n /**\n * @var logger Logger\n */\n protected logger: any;\n\n /**\n * @var onError HTTP error service\n */\n protected onError: any;\n\n /**\n * @var requestsQueue Queue of requests\n */\n protected requestsQueue: WeakMap;\n\n /**\n * Request Handler Config\n */\n protected retry: RetryOptions = {\n retries: 0,\n delay: 1000,\n maxDelay: 30000,\n backoff: 1.5,\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status\n retryOn: [\n 408, // Request Timeout\n 409, // Conflict\n 425, // Too Early\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n ],\n\n shouldRetry: async () => true,\n };\n\n /**\n * Request Handler Config\n */\n public config: RequestHandlerConfig;\n\n /**\n * Creates an instance of RequestHandler.\n *\n * @param {Object} config - Configuration object for the request.\n * @param {string} config.baseURL - The base URL for the request.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n */\n public constructor({\n fetcher = null,\n timeout = null,\n rejectCancelled = false,\n strategy = null,\n flattenResponse = null,\n defaultResponse = {},\n logger = null,\n onError = null,\n ...config\n }: RequestHandlerConfig) {\n this.fetcher = fetcher;\n this.timeout =\n timeout !== null && timeout !== undefined ? timeout : this.timeout;\n this.strategy = strategy || this.strategy;\n this.cancellable = config.cancellable || this.cancellable;\n this.rejectCancelled = rejectCancelled || this.rejectCancelled;\n this.flattenResponse = flattenResponse || this.flattenResponse;\n this.defaultResponse = defaultResponse;\n this.logger = logger || (globalThis ? globalThis.console : null) || null;\n this.onError = onError;\n this.requestsQueue = new WeakMap();\n this.baseURL = config.baseURL || config.apiUrl || '';\n this.method = config.method || this.method;\n this.config = config;\n this.retry = {\n ...this.retry,\n ...(config.retry || {}),\n };\n\n this.requestInstance = this.isCustomFetcher()\n ? (fetcher as any).create({\n ...config,\n baseURL: this.baseURL,\n timeout: this.timeout,\n })\n : null;\n }\n\n /**\n * Get Provider Instance\n *\n * @returns {FetcherInstance} Provider's instance\n */\n public getInstance(): FetcherInstance {\n return this.requestInstance;\n }\n\n /**\n * Build request configuration\n *\n * @param {string} url Request url\n * @param {QueryParamsOrBody} data Request data\n * @param {RequestConfig} config Request config\n * @returns {RequestConfig} Provider's instance\n */\n protected buildConfig(\n url: string,\n data: QueryParamsOrBody,\n config: RequestConfig,\n ): RequestConfig {\n const method = config.method || this.method;\n const methodLowerCase = method.toLowerCase();\n const isGetAlikeMethod =\n methodLowerCase === 'get' || methodLowerCase === 'head';\n\n const dynamicUrl = replaceUrlPathParams(\n url,\n config.urlPathParams || this.config.urlPathParams,\n );\n\n // Bonus: Specifying it here brings support for \"body\" in Axios\n const configData =\n config.body || config.data || this.config.body || this.config.data;\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n return {\n ...config,\n url: dynamicUrl,\n method: methodLowerCase,\n\n ...(isGetAlikeMethod ? { params: data } : {}),\n\n // For POST requests body payload is the first param for convenience (\"data\")\n // In edge cases we want to split so to treat it as query params, and use \"body\" coming from the config instead\n ...(!isGetAlikeMethod && data && configData ? { params: data } : {}),\n\n // Only applicable for request methods 'PUT', 'POST', 'DELETE', and 'PATCH'\n ...(!isGetAlikeMethod && data && !configData ? { data } : {}),\n ...(!isGetAlikeMethod && configData ? { data: configData } : {}),\n };\n }\n\n // Native fetch\n const payload = configData || data;\n const credentials =\n config.withCredentials || this.config.withCredentials\n ? 'include'\n : config.credentials;\n\n delete config.data;\n delete config.withCredentials;\n\n const urlPath =\n (!isGetAlikeMethod && data && !config.body) || !data\n ? dynamicUrl\n : appendQueryParams(dynamicUrl, data);\n const isFullUrl = urlPath.includes('://');\n const baseURL = isFullUrl\n ? ''\n : typeof config.baseURL !== 'undefined'\n ? config.baseURL\n : this.baseURL;\n\n return {\n ...config,\n credentials,\n\n // Native fetch generally requires query params to be appended in the URL\n // Do not append query params only if it's a POST-alike request with only \"data\" specified as it's treated as body payload\n url: baseURL + urlPath,\n\n // Uppercase method name\n method: method.toUpperCase(),\n\n // For convenience, add the same default headers as Axios does\n headers: {\n Accept: APPLICATION_JSON + ', text/plain, */*',\n 'Content-Type': APPLICATION_JSON + ';charset=utf-8',\n ...(config.headers || this.config.headers || {}),\n },\n\n // Automatically JSON stringify request bodies, if possible and when not dealing with strings\n ...(!isGetAlikeMethod\n ? {\n body:\n !(payload instanceof URLSearchParams) &&\n isJSONSerializable(payload)\n ? typeof payload === 'string'\n ? payload\n : JSON.stringify(payload)\n : payload,\n }\n : {}),\n };\n }\n\n /**\n * Process global Request Error\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {void}\n */\n protected processError(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): void {\n if (this.isRequestCancelled(error)) {\n return;\n }\n\n if (this.logger?.warn) {\n this.logger.warn('API ERROR', error);\n }\n\n // Invoke per request \"onError\" interceptor\n if (requestConfig.onError && typeof requestConfig.onError === 'function') {\n requestConfig.onError(error);\n }\n\n // Invoke global \"onError\" interceptor\n if (this.onError && typeof this.onError === 'function') {\n this.onError(error);\n }\n }\n\n /**\n * Output default response in case of an error, depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {*} Error response\n */\n protected async outputErrorResponse(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): Promise {\n const isRequestCancelled = this.isRequestCancelled(error);\n const errorHandlingStrategy = requestConfig.strategy || this.strategy;\n const rejectCancelled =\n typeof requestConfig.rejectCancelled !== 'undefined'\n ? requestConfig.rejectCancelled\n : this.rejectCancelled;\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n // Output full response with the error object\n if (errorHandlingStrategy === 'softFail') {\n return this.outputResponse(error.response, requestConfig, error);\n }\n\n // By default cancelled requests aren't rejected\n if (isRequestCancelled && !rejectCancelled) {\n return defaultResponse;\n }\n\n // Hang the promise\n if (errorHandlingStrategy === 'silent') {\n await new Promise(() => null);\n\n return defaultResponse;\n }\n\n // Reject the promise\n if (errorHandlingStrategy === 'reject') {\n return Promise.reject(error);\n }\n\n return defaultResponse;\n }\n\n /**\n * Output error response depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @returns {boolean} True if request is aborted\n */\n public isRequestCancelled(error: ResponseError): boolean {\n return error.name === 'AbortError' || error.name === 'CanceledError';\n }\n\n /**\n * Detects if a custom fetcher is utilized\n *\n * @returns {boolean} True if it's a custom fetcher\n */\n protected isCustomFetcher(): boolean {\n return this.fetcher !== null;\n }\n\n /**\n * Automatically Cancel Previous Requests using AbortController when \"cancellable\" is defined\n *\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {Object} Controller Signal to abort\n */\n protected addCancellationToken(\n requestConfig: RequestConfig,\n ): Partial> {\n // Both disabled\n if (!this.cancellable && !requestConfig.cancellable) {\n return {};\n }\n\n // Explicitly disabled per request\n if (\n typeof requestConfig.cancellable !== 'undefined' &&\n !requestConfig.cancellable\n ) {\n return {};\n }\n\n // Check if AbortController is available\n if (typeof AbortController === 'undefined') {\n console.error('AbortController is unavailable.');\n\n return {};\n }\n\n // Generate unique key as a cancellation token\n const previousRequest = this.requestsQueue.get(requestConfig);\n\n if (previousRequest) {\n previousRequest.abort();\n }\n\n const controller = new AbortController();\n\n // Introduce timeout for native fetch\n if (!this.isCustomFetcher() && this.timeout > 0) {\n const abortTimeout = setTimeout(() => {\n const error = new Error(\n `[TimeoutError]: The ${requestConfig.url} request was aborted due to timeout`,\n );\n\n error.name = 'TimeoutError';\n (error as any).code = 23; // DOMException.TIMEOUT_ERR\n controller.abort(error);\n clearTimeout(abortTimeout);\n throw error;\n }, requestConfig.timeout || this.timeout);\n }\n\n this.requestsQueue.set(requestConfig, controller);\n\n return {\n signal: controller.signal,\n };\n }\n\n /**\n * Handle Request depending on used strategy\n *\n * @param {string} url - Request url\n * @param {QueryParamsOrBody} data - Request data\n * @param {RequestConfig} config - Request config\n * @param {RequestConfig} payload.config Request config\n * @throws {ResponseError}\n * @returns {Promise>} Response Data\n */\n public async request(\n url: string,\n data: QueryParamsOrBody = null,\n config: RequestConfig = null,\n ): Promise> {\n let response: FetchResponse = null;\n const _config = config || {};\n const _requestConfig = this.buildConfig(url, data, _config);\n\n let requestConfig: RequestConfig = {\n ...this.addCancellationToken(_requestConfig),\n ..._requestConfig,\n };\n\n const { retries, delay, backoff, retryOn, shouldRetry, maxDelay } = {\n ...this.retry,\n ...(requestConfig?.retry || {}),\n };\n\n let attempt = 0;\n let waitTime = delay;\n\n while (attempt <= retries) {\n try {\n // Local interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n requestConfig.onRequest,\n );\n\n // Global interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n this.config.onRequest,\n );\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n response = (await (this.requestInstance as any).request(\n requestConfig,\n )) as FetchResponse;\n } else {\n response = (await globalThis.fetch(\n requestConfig.url,\n requestConfig,\n )) as FetchResponse;\n\n // Add more information to response object\n response.config = requestConfig;\n response.data = await this.parseData(response);\n\n // Check if the response status is not outside the range 200-299 and if so, output error\n if (!response.ok) {\n throw new ResponseErr(\n `${requestConfig.url} failed! Status: ${response.status || null}`,\n requestConfig,\n response,\n );\n }\n }\n\n // Local interceptors\n response = await interceptResponse(response, requestConfig.onResponse);\n\n // Global interceptors\n response = await interceptResponse(response, this.config.onResponse);\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n } catch (error) {\n if (\n attempt === retries ||\n !(await shouldRetry(error, attempt)) ||\n !retryOn?.includes(error?.response?.status || error?.status)\n ) {\n this.processError(error, requestConfig);\n\n return this.outputErrorResponse(error, requestConfig);\n }\n\n if (this.logger?.warn) {\n this.logger.warn(\n `Attempt ${attempt + 1} failed. Retrying in ${waitTime}ms...`,\n );\n }\n\n await delayInvocation(waitTime);\n\n waitTime *= backoff;\n waitTime = Math.min(waitTime, maxDelay);\n attempt++;\n }\n }\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n }\n\n /**\n * Parses the response data based on the Content-Type header.\n *\n * @param response - The Response object to parse.\n * @returns A Promise that resolves to the parsed data.\n */\n public async parseData(\n response: FetchResponse,\n ): Promise {\n const contentType = String(\n (response as Response).headers?.get('Content-Type') || '',\n );\n let data;\n\n // Handle edge case of no content type being provided... We assume JSON here.\n if (!contentType) {\n const responseClone = response.clone();\n try {\n data = await responseClone.json();\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (_error) {\n // JSON parsing failed, fallback to null\n data = null;\n }\n }\n\n if (typeof data === 'undefined') {\n try {\n if (\n contentType.includes(APPLICATION_JSON) ||\n contentType.includes('+json')\n ) {\n data = await response.json(); // Parse JSON response\n } else if (contentType.includes('multipart/form-data')) {\n data = await response.formData(); // Parse as FormData\n } else if (contentType.includes('application/octet-stream')) {\n data = await response.blob(); // Parse as blob\n } else if (contentType.includes('application/x-www-form-urlencoded')) {\n data = await response.formData(); // Handle URL-encoded forms\n } else if (typeof response.text === 'function') {\n data = await response.text(); // Parse as text\n } else {\n // Handle streams\n data = response.body || response.data || null;\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (_error) {\n // Parsing failed, fallback to null\n data = null;\n }\n }\n\n return data;\n }\n\n public processHeaders(\n response: FetchResponse,\n ): HeadersObject {\n if (!response.headers) {\n return {};\n }\n\n let headersObject: HeadersObject = {};\n const headers = response.headers;\n\n // Handle Headers object with entries() method\n if (headers instanceof Headers) {\n for (const [key, value] of (headers as any).entries()) {\n headersObject[key] = value;\n }\n } else {\n // Handle plain object\n headersObject = { ...(headers as HeadersObject) };\n }\n\n return headersObject;\n }\n\n /**\n * Output response\n *\n * @param response - Response payload\n * @param {RequestConfig} requestConfig - Request config\n * @param {*} error - whether the response is erroneous\n * @returns {ResponseData | FetchResponse} Response data\n */\n protected outputResponse(\n response: FetchResponse,\n requestConfig: RequestConfig,\n error = null,\n ): ResponseData | FetchResponse {\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n if (!response) {\n return defaultResponse;\n }\n\n if (\n (requestConfig.flattenResponse || this.flattenResponse) &&\n typeof response.data !== 'undefined'\n ) {\n // Special case of only data property within response data object (happens in Axios)\n // This is in fact a proper response but we may want to flatten it\n // To ease developers' lives when obtaining the response\n if (\n response.data !== null &&\n typeof response.data === 'object' &&\n typeof (response.data as any).data !== 'undefined' &&\n Object.keys(response.data).length === 1\n ) {\n return (response.data as any).data;\n }\n\n return response.data;\n }\n\n // If empty object is returned, ensure that the default response is used instead\n if (\n response !== null &&\n typeof response === 'object' &&\n response.constructor === Object &&\n Object.keys(response).length === 0\n ) {\n return defaultResponse;\n }\n\n const isCustomFetcher = this.isCustomFetcher();\n\n if (isCustomFetcher) {\n return response;\n }\n\n if (error !== null) {\n delete error?.response;\n delete error?.request;\n delete error?.config;\n }\n\n // Native fetch()\n return {\n body: response.body,\n blob: response.blob,\n json: response.json,\n text: response.text,\n clone: response.clone,\n bodyUsed: response.bodyUsed,\n arrayBuffer: response.arrayBuffer,\n formData: response.formData,\n ok: response.ok,\n redirected: response.redirected,\n type: response.type,\n url: response.url,\n status: response.status,\n statusText: response.statusText,\n\n // Extend with extra information\n error,\n data: response.data,\n headers: this.processHeaders(response),\n config: requestConfig,\n };\n }\n}\n","import { RequestHandler } from './request-handler';\nimport type {\n FetcherInstance,\n RequestConfig,\n FetchResponse,\n} from './types/request-handler';\nimport type {\n ApiHandlerConfig,\n ApiHandlerMethods,\n ApiHandlerReturnType,\n APIResponse,\n QueryParams,\n UrlPathParams,\n} from './types/api-handler';\n\n/**\n * Creates an instance of API Handler.\n * It creates an API fetcher function using native fetch() or Axios if it is passed as \"fetcher\".\n *\n * @param {Object} config - Configuration object for the API fetcher.\n * @param {string} config.apiUrl - The base URL for the API.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n * @returns API handler functions and endpoints to call\n *\n * @example\n * // Import axios (optional)\n * import axios from 'axios';\n *\n * // Define endpoint paths\n * const endpoints = {\n * getUser: '/user',\n * createPost: '/post',\n * };\n *\n * // Create the API fetcher with configuration\n * const api = createApiFetcher({\n * fetcher: axios, // Axios instance (optional)\n * endpoints,\n * apiUrl: 'https://example.com/api',\n * onError(error) {\n * console.log('Request failed', error);\n * },\n * headers: {\n * 'my-auth-key': 'example-auth-key-32rjjfa',\n * },\n * });\n *\n * // Fetch user data\n * const response = await api.getUser({ userId: 1, ratings: [1, 2] })\n */\nfunction createApiFetcher<\n EndpointsMethods extends object,\n EndpointsCfg = never,\n>(config: ApiHandlerConfig) {\n const endpoints = config.endpoints;\n const requestHandler = new RequestHandler(config);\n\n /**\n * Get Fetcher Provider Instance\n *\n * @returns {FetcherInstance} Request Handler's Fetcher instance\n */\n function getInstance(): FetcherInstance {\n return requestHandler.getInstance();\n }\n\n /**\n * Triggered when trying to use non-existent endpoints\n *\n * @param endpointName Endpoint Name\n * @returns {Promise}\n */\n function handleNonImplemented(endpointName: string): Promise {\n console.error(`${endpointName} endpoint must be added to 'endpoints'.`);\n\n return Promise.resolve(null);\n }\n\n /**\n * Handle Single API Request\n * It considers settings in following order: per-request settings, global per-endpoint settings, global settings.\n *\n * @param {string} endpointName - The name of the API endpoint to call.\n * @param {QueryParams} [queryParams={}] - Query parameters to include in the request.\n * @param {UrlPathParams} [urlPathParams={}] - URI parameters to include in the request.\n * @param {EndpointConfig} [requestConfig={}] - Additional configuration for the request.\n * @returns {Promise} - A promise that resolves with the response from the API provider.\n */\n async function request(\n endpointName: keyof EndpointsMethods | string,\n queryParams: QueryParams = {},\n urlPathParams: UrlPathParams = {},\n requestConfig: RequestConfig = {},\n ): Promise> {\n // Use global per-endpoint settings\n const endpointConfig = endpoints[endpointName as string];\n const endpointSettings = { ...endpointConfig };\n\n const responseData = await requestHandler.request(\n endpointSettings.url,\n queryParams,\n {\n ...endpointSettings,\n ...requestConfig,\n urlPathParams,\n },\n );\n\n return responseData;\n }\n\n /**\n * Maps all API requests using native Proxy\n *\n * @param {*} prop Caller\n */\n function get(prop: string | symbol) {\n if (prop in apiHandler) {\n return apiHandler[prop];\n }\n\n // Prevent handler from triggering non-existent endpoints\n if (!endpoints[prop as string]) {\n return handleNonImplemented.bind(null, prop);\n }\n\n return apiHandler.request.bind(null, prop);\n }\n\n const apiHandler: ApiHandlerMethods = {\n config,\n endpoints,\n requestHandler,\n getInstance,\n request,\n };\n\n return new Proxy(apiHandler, {\n get: (_target, prop) => get(prop),\n }) as ApiHandlerReturnType;\n}\n\nexport { createApiFetcher };\n","import { RequestHandler } from './request-handler';\nimport type { APIResponse, FetchResponse, RequestHandlerConfig } from './types';\n\n/**\n * Simple wrapper for request fetching.\n * It abstracts the creation of RequestHandler, making it easy to perform API requests.\n *\n * @param {string | URL | globalThis.Request} url - Request URL.\n * @param {RequestHandlerConfig} config - Configuration object for the request handler.\n * @returns {Promise>} Response Data.\n */\nexport async function fetchf(\n url: string,\n config: RequestHandlerConfig = {},\n): Promise> {\n return new RequestHandler(config).request(\n url,\n config.body || config.data || config.params,\n config,\n );\n}\n\nexport * from './types';\nexport * from './api-handler';\n"],"mappings":"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","names":["interceptRequest","config","interceptors","interceptorList","interceptedConfig","interceptor","interceptResponse","response","interceptedResponse","ResponseErr","message","requestInfo","response","appendQueryParams","url","params","add","k","v","buildParams","prefix","obj","len","key","encodedQueryString","replaceUrlPathParams","urlPathParams","str","word","isJSONSerializable","value","t","proto","delayInvocation","ms","resolve","APPLICATION_JSON","RequestHandler","fetcher","timeout","rejectCancelled","strategy","flattenResponse","defaultResponse","logger","onError","config","url","data","method","methodLowerCase","isGetAlikeMethod","dynamicUrl","replaceUrlPathParams","configData","payload","credentials","urlPath","appendQueryParams","baseURL","isJSONSerializable","error","requestConfig","_a","isRequestCancelled","errorHandlingStrategy","previousRequest","controller","abortTimeout","_b","response","_config","_requestConfig","retries","delay","backoff","retryOn","shouldRetry","maxDelay","attempt","waitTime","interceptRequest","ResponseErr","interceptResponse","delayInvocation","contentType","responseClone","headersObject","headers","key","value","createApiFetcher","config","endpoints","requestHandler","RequestHandler","getInstance","handleNonImplemented","endpointName","request","queryParams","urlPathParams","requestConfig","endpointSettings","get","prop","apiHandler","_target","fetchf","url","config","RequestHandler"]} \ No newline at end of file diff --git a/dist/browser/index.mjs b/dist/browser/index.mjs index 6358325..b73a1e7 100644 --- a/dist/browser/index.mjs +++ b/dist/browser/index.mjs @@ -1,2 +1,2 @@ -async function C(c,e){if(!e)return c;let s=Array.isArray(e)?e:[e],t={...c};for(let n of s)t=await n(t);return t}async function w(c,e){if(!e)return c;let s=Array.isArray(e)?e:[e],t=c;for(let n of s)t=await n(t);return t}var b=class extends Error{response;request;config;status;statusText;constructor(e,s,t){super(e),this.name="ResponseError",this.message=e,this.status=t.status,this.statusText=t.statusText,this.request=s,this.config=s,this.response=t}};var R=class{requestInstance;baseURL="";timeout=3e4;cancellable=!1;rejectCancelled=!1;strategy="reject";method="get";flattenResponse=!1;defaultResponse=null;fetcher;logger;onError;requestsQueue;retry={retries:0,delay:1e3,maxDelay:3e4,backoff:1.5,retryOn:[408,409,425,429,500,502,503,504],shouldRetry:async()=>!0};config;constructor({fetcher:e=null,timeout:s=null,rejectCancelled:t=!1,strategy:n=null,flattenResponse:a=null,defaultResponse:l={},logger:r=null,onError:o=null,...i}){this.fetcher=e,this.timeout=s??this.timeout,this.strategy=n||this.strategy,this.cancellable=i.cancellable||this.cancellable,this.rejectCancelled=t||this.rejectCancelled,this.flattenResponse=a||this.flattenResponse,this.defaultResponse=l,this.logger=r||(globalThis?globalThis.console:null)||null,this.onError=o,this.requestsQueue=new WeakMap,this.baseURL=i.baseURL||i.apiUrl||"",this.method=i.method||this.method,this.config=i,this.retry={...this.retry,...i.retry||{}},this.requestInstance=this.isCustomFetcher()?e.create({...i,baseURL:this.baseURL,timeout:this.timeout}):null}getInstance(){return this.requestInstance}replaceUrlPathParams(e,s){return s?e.replace(/:[a-zA-Z]+/gi,t=>{let n=t.substring(1);return String(s[n]?s[n]:t)}):e}appendQueryParams(e,s){if(!s)return e;let t=Object.entries(s).flatMap(([n,a])=>Array.isArray(a)?a.map(l=>`${encodeURIComponent(n)}[]=${encodeURIComponent(l)}`):`${encodeURIComponent(n)}=${encodeURIComponent(String(a))}`).join("&");return e.includes("?")?`${e}&${t}`:t?`${e}?${t}`:e}isJSONSerializable(e){if(e==null)return!1;let s=typeof e;if(s==="string"||s==="number"||s==="boolean")return!0;if(s!=="object")return!1;if(Array.isArray(e))return!0;if(Buffer.isBuffer(e)||e instanceof Date)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null||typeof e.toJSON=="function"}buildConfig(e,s,t){let n=t.method||this.method,a=n.toLowerCase(),l=a==="get"||a==="head",r=this.replaceUrlPathParams(e,t.urlPathParams||this.config.urlPathParams),o=t.body||t.data||this.config.body||this.config.data;if(this.isCustomFetcher())return{...t,url:r,method:a,...l?{params:s}:{},...!l&&s&&o?{params:s}:{},...!l&&s&&!o?{data:s}:{},...!l&&o?{data:o}:{}};let i=o||s,y=t.withCredentials||this.config.withCredentials?"include":t.credentials;delete t.data,delete t.withCredentials;let d=!l&&s&&!t.body||!s?r:this.appendQueryParams(r,s),h=d.includes("://")?"":typeof t.baseURL<"u"?t.baseURL:this.baseURL;return{...t,credentials:y,url:h+d,method:n.toUpperCase(),headers:{Accept:"application/json, text/plain, */*","Content-Type":"application/json;charset=utf-8",...t.headers||this.config.headers||{}},...l?{}:{body:!(i instanceof URLSearchParams)&&this.isJSONSerializable(i)?typeof i=="string"?i:JSON.stringify(i):i}}}processError(e,s){var t;this.isRequestCancelled(e)||((t=this.logger)!=null&&t.warn&&this.logger.warn("API ERROR",e),s.onError&&typeof s.onError=="function"&&s.onError(e),this.onError&&typeof this.onError=="function"&&this.onError(e))}async outputErrorResponse(e,s){let t=this.isRequestCancelled(e),n=s.strategy||this.strategy,a=typeof s.rejectCancelled<"u"?s.rejectCancelled:this.rejectCancelled,l=typeof s.defaultResponse<"u"?s.defaultResponse:this.defaultResponse;return n==="softFail"?this.outputResponse(e.response,s,e):t&&!a?l:n==="silent"?(await new Promise(()=>null),l):n==="reject"?Promise.reject(e):l}isRequestCancelled(e){return e.name==="AbortError"||e.name==="CanceledError"}isCustomFetcher(){return this.fetcher!==null}addCancellationToken(e){if(!this.cancellable&&!e.cancellable)return{};if(typeof e.cancellable<"u"&&!e.cancellable)return{};if(typeof AbortController>"u")return console.error("AbortController is unavailable."),{};let s=this.requestsQueue.get(e);s&&s.abort();let t=new AbortController;if(!this.isCustomFetcher()&&this.timeout>0){let n=setTimeout(()=>{let a=new Error(`[TimeoutError]: The ${e.url} request was aborted due to timeout`);throw a.name="TimeoutError",a.code=23,t.abort(a),clearTimeout(n),a},e.timeout||this.timeout)}return this.requestsQueue.set(e,t),{signal:t.signal}}async request(e,s=null,t=null){var P,q,F;let n=null,a=t||{},l=this.buildConfig(e,s,a),r={...this.addCancellationToken(l),...l},{retries:o,delay:i,backoff:y,retryOn:d,shouldRetry:g,maxDelay:h}={...this.retry,...(r==null?void 0:r.retry)||{}},f=0,m=i;for(;f<=o;)try{if(r=await C(r,r.onRequest),r=await C(r,this.config.onRequest),this.isCustomFetcher())n=await this.requestInstance.request(r);else{n=await globalThis.fetch(r.url,r);let u=String(((P=n==null?void 0:n.headers)==null?void 0:P.get("Content-Type"))||""),p,j=n.clone();if(!u)try{p=await j.json()}catch{}if(typeof p>"u"&&(u&&(u.includes("application/json")||u.includes("+json"))?p=await n.json():u.includes("multipart/form-data")?p=await n.formData():u.includes("application/octet-stream")?p=await n.blob():u.includes("application/x-www-form-urlencoded")?p=await n.formData():typeof n.text<"u"?p=await n.text():p=n.body||n.data||null),n.config=r,n.data=p,!n.ok)throw new b(`${r.url} failed! Status: ${n.status||null}`,r,n)}return n=await w(n,r.onResponse),n=await w(n,this.config.onResponse),this.outputResponse(n,r)}catch(u){if(f===o||!await g(u,f)||!(d!=null&&d.includes(((q=u==null?void 0:u.response)==null?void 0:q.status)||(u==null?void 0:u.status))))return this.processError(u,r),this.outputErrorResponse(u,r);(F=this.logger)!=null&&F.warn&&this.logger.warn(`Attempt ${f+1} failed. Retrying in ${m}ms...`),await this.delay(m),m*=y,m=Math.min(m,h),f++}return this.outputResponse(n,r)}async delay(e){return new Promise(s=>setTimeout(()=>s(!0),e))}processHeaders(e){if(!e.headers)return{};let s={};if(e.headers instanceof Headers)for(let[t,n]of e.headers.entries())s[t]=n;else s={...e.headers};return s}outputResponse(e,s,t=null){let n=typeof s.defaultResponse<"u"?s.defaultResponse:this.defaultResponse;return e?(s.flattenResponse||this.flattenResponse)&&typeof e.data<"u"?e.data!==null&&typeof e.data=="object"&&typeof e.data.data<"u"&&Object.keys(e.data).length===1?e.data.data:e.data:e!==null&&typeof e=="object"&&e.constructor===Object&&Object.keys(e).length===0?n:this.isCustomFetcher()?e:(t!==null&&(t==null||delete t.response,t==null||delete t.request,t==null||delete t.config),{body:e.body,blob:e.blob,json:e.json,text:e.text,clone:e.clone,bodyUsed:e.bodyUsed,arrayBuffer:e.arrayBuffer,formData:e.formData,ok:e.ok,redirected:e.redirected,type:e.type,url:e.url,status:e.status,statusText:e.statusText,error:t,data:e.data,headers:this.processHeaders(e),config:s}):n}};function S(c){let e=c.endpoints,s=new R(c);function t(){return s.getInstance()}function n(o){return console.error(`${o} endpoint must be added to 'endpoints'.`),Promise.resolve(null)}async function a(o,i={},y={},d={}){let h={...e[o]};return await s.request(h.url,i,{...h,...d,urlPathParams:y})}function l(o){return o in r?r[o]:e[o]?r.request.bind(null,o):n.bind(null,o)}let r={config:c,endpoints:e,requestHandler:s,getInstance:t,request:a};return new Proxy(r,{get:(o,i)=>l(i)})}async function M(c,e={}){return new R(e).request(c,e.body||e.data||e.params,e)}export{S as createApiFetcher,M as fetchf}; +async function g(o,e){if(!e)return o;let s=Array.isArray(e)?e:[e],t={...o};for(let r of s)t=await r(t);return t}async function C(o,e){if(!e)return o;let s=Array.isArray(e)?e:[e],t=o;for(let r of s)t=await r(t);return t}var m=class extends Error{response;request;config;status;statusText;constructor(e,s,t){super(e),this.name="ResponseError",this.message=e,this.status=t.status,this.statusText=t.statusText,this.request=s,this.config=s,this.response=t}};function F(o,e){if(!e)return o;let s=[],t=function(a,n){n=typeof n=="function"?n():n,n=n===null||n===void 0?"":n,s[s.length]=encodeURIComponent(a)+"="+encodeURIComponent(n)},r=(a,n)=>{let i,p,c;if(a)if(Array.isArray(n))for(i=0,p=n.length;i{let t=s.substring(1);return String(e[t]?e[t]:s)}):o}function D(o){if(o==null)return!1;let e=typeof o;if(e==="string"||e==="number"||e==="boolean")return!0;if(e!=="object")return!1;if(Array.isArray(o))return!0;if(Buffer.isBuffer(o)||o instanceof Date)return!1;let s=Object.getPrototypeOf(o);return s===Object.prototype||s===null||typeof o.toJSON=="function"}async function I(o){return new Promise(e=>setTimeout(()=>e(!0),o))}var P="application/json",R=class{requestInstance;baseURL="";timeout=3e4;cancellable=!1;rejectCancelled=!1;strategy="reject";method="get";flattenResponse=!1;defaultResponse=null;fetcher;logger;onError;requestsQueue;retry={retries:0,delay:1e3,maxDelay:3e4,backoff:1.5,retryOn:[408,409,425,429,500,502,503,504],shouldRetry:async()=>!0};config;constructor({fetcher:e=null,timeout:s=null,rejectCancelled:t=!1,strategy:r=null,flattenResponse:l=null,defaultResponse:u={},logger:a=null,onError:n=null,...i}){this.fetcher=e,this.timeout=s??this.timeout,this.strategy=r||this.strategy,this.cancellable=i.cancellable||this.cancellable,this.rejectCancelled=t||this.rejectCancelled,this.flattenResponse=l||this.flattenResponse,this.defaultResponse=u,this.logger=a||(globalThis?globalThis.console:null)||null,this.onError=n,this.requestsQueue=new WeakMap,this.baseURL=i.baseURL||i.apiUrl||"",this.method=i.method||this.method,this.config=i,this.retry={...this.retry,...i.retry||{}},this.requestInstance=this.isCustomFetcher()?e.create({...i,baseURL:this.baseURL,timeout:this.timeout}):null}getInstance(){return this.requestInstance}buildConfig(e,s,t){let r=t.method||this.method,l=r.toLowerCase(),u=l==="get"||l==="head",a=A(e,t.urlPathParams||this.config.urlPathParams),n=t.body||t.data||this.config.body||this.config.data;if(this.isCustomFetcher())return{...t,url:a,method:l,...u?{params:s}:{},...!u&&s&&n?{params:s}:{},...!u&&s&&!n?{data:s}:{},...!u&&n?{data:n}:{}};let i=n||s,p=t.withCredentials||this.config.withCredentials?"include":t.credentials;delete t.data,delete t.withCredentials;let c=!u&&s&&!t.body||!s?a:F(a,s),f=c.includes("://")?"":typeof t.baseURL<"u"?t.baseURL:this.baseURL;return{...t,credentials:p,url:f+c,method:r.toUpperCase(),headers:{Accept:P+", text/plain, */*","Content-Type":P+";charset=utf-8",...t.headers||this.config.headers||{}},...u?{}:{body:!(i instanceof URLSearchParams)&&D(i)?typeof i=="string"?i:JSON.stringify(i):i}}}processError(e,s){var t;this.isRequestCancelled(e)||((t=this.logger)!=null&&t.warn&&this.logger.warn("API ERROR",e),s.onError&&typeof s.onError=="function"&&s.onError(e),this.onError&&typeof this.onError=="function"&&this.onError(e))}async outputErrorResponse(e,s){let t=this.isRequestCancelled(e),r=s.strategy||this.strategy,l=typeof s.rejectCancelled<"u"?s.rejectCancelled:this.rejectCancelled,u=typeof s.defaultResponse<"u"?s.defaultResponse:this.defaultResponse;return r==="softFail"?this.outputResponse(e.response,s,e):t&&!l?u:r==="silent"?(await new Promise(()=>null),u):r==="reject"?Promise.reject(e):u}isRequestCancelled(e){return e.name==="AbortError"||e.name==="CanceledError"}isCustomFetcher(){return this.fetcher!==null}addCancellationToken(e){if(!this.cancellable&&!e.cancellable)return{};if(typeof e.cancellable<"u"&&!e.cancellable)return{};if(typeof AbortController>"u")return console.error("AbortController is unavailable."),{};let s=this.requestsQueue.get(e);s&&s.abort();let t=new AbortController;if(!this.isCustomFetcher()&&this.timeout>0){let r=setTimeout(()=>{let l=new Error(`[TimeoutError]: The ${e.url} request was aborted due to timeout`);throw l.name="TimeoutError",l.code=23,t.abort(l),clearTimeout(r),l},e.timeout||this.timeout)}return this.requestsQueue.set(e,t),{signal:t.signal}}async request(e,s=null,t=null){var w,q;let r=null,l=t||{},u=this.buildConfig(e,s,l),a={...this.addCancellationToken(u),...u},{retries:n,delay:i,backoff:p,retryOn:c,shouldRetry:b,maxDelay:f}={...this.retry,...(a==null?void 0:a.retry)||{}},h=0,y=i;for(;h<=n;)try{if(a=await g(a,a.onRequest),a=await g(a,this.config.onRequest),this.isCustomFetcher())r=await this.requestInstance.request(a);else if(r=await globalThis.fetch(a.url,a),r.config=a,r.data=await this.parseData(r),!r.ok)throw new m(`${a.url} failed! Status: ${r.status||null}`,a,r);return r=await C(r,a.onResponse),r=await C(r,this.config.onResponse),this.outputResponse(r,a)}catch(d){if(h===n||!await b(d,h)||!(c!=null&&c.includes(((w=d==null?void 0:d.response)==null?void 0:w.status)||(d==null?void 0:d.status))))return this.processError(d,a),this.outputErrorResponse(d,a);(q=this.logger)!=null&&q.warn&&this.logger.warn(`Attempt ${h+1} failed. Retrying in ${y}ms...`),await I(y),y*=p,y=Math.min(y,f),h++}return this.outputResponse(r,a)}async parseData(e){var r;let s=String(((r=e.headers)==null?void 0:r.get("Content-Type"))||""),t;if(!s){let l=e.clone();try{t=await l.json()}catch{t=null}}if(typeof t>"u")try{s.includes(P)||s.includes("+json")?t=await e.json():s.includes("multipart/form-data")?t=await e.formData():s.includes("application/octet-stream")?t=await e.blob():s.includes("application/x-www-form-urlencoded")?t=await e.formData():typeof e.text=="function"?t=await e.text():t=e.body||e.data||null}catch{t=null}return t}processHeaders(e){if(!e.headers)return{};let s={},t=e.headers;if(t instanceof Headers)for(let[r,l]of t.entries())s[r]=l;else s={...t};return s}outputResponse(e,s,t=null){let r=typeof s.defaultResponse<"u"?s.defaultResponse:this.defaultResponse;return e?(s.flattenResponse||this.flattenResponse)&&typeof e.data<"u"?e.data!==null&&typeof e.data=="object"&&typeof e.data.data<"u"&&Object.keys(e.data).length===1?e.data.data:e.data:e!==null&&typeof e=="object"&&e.constructor===Object&&Object.keys(e).length===0?r:this.isCustomFetcher()?e:(t!==null&&(t==null||delete t.response,t==null||delete t.request,t==null||delete t.config),{body:e.body,blob:e.blob,json:e.json,text:e.text,clone:e.clone,bodyUsed:e.bodyUsed,arrayBuffer:e.arrayBuffer,formData:e.formData,ok:e.ok,redirected:e.redirected,type:e.type,url:e.url,status:e.status,statusText:e.statusText,error:t,data:e.data,headers:this.processHeaders(e),config:s}):r}};function Q(o){let e=o.endpoints,s=new R(o);function t(){return s.getInstance()}function r(n){return console.error(`${n} endpoint must be added to 'endpoints'.`),Promise.resolve(null)}async function l(n,i={},p={},c={}){let f={...e[n]};return await s.request(f.url,i,{...f,...c,urlPathParams:p})}function u(n){return n in a?a[n]:e[n]?a.request.bind(null,n):r.bind(null,n)}let a={config:o,endpoints:e,requestHandler:s,getInstance:t,request:l};return new Proxy(a,{get:(n,i)=>u(i)})}async function B(o,e={}){return new R(e).request(o,e.body||e.data||e.params,e)}export{Q as createApiFetcher,B as fetchf}; //# sourceMappingURL=index.mjs.map \ No newline at end of file diff --git a/dist/browser/index.mjs.map b/dist/browser/index.mjs.map index 9b31beb..b962ff8 100644 --- a/dist/browser/index.mjs.map +++ b/dist/browser/index.mjs.map @@ -1 +1 @@ -{"version":3,"sources":["../src/interceptor-manager.ts","../src/response-error.ts","../src/request-handler.ts","../src/api-handler.ts","../src/index.ts"],"sourcesContent":["import type { RequestHandlerConfig, FetchResponse } from './types';\nimport type {\n RequestInterceptor,\n ResponseInterceptor,\n} from './types/interceptor-manager';\n\n/**\n * Applies a series of request interceptors to the provided configuration.\n * @param {RequestHandlerConfig} config - The initial request configuration.\n * @param {RequestInterceptor | RequestInterceptor[]} interceptors - The request interceptor function(s) to apply.\n * @returns {Promise} - The modified request configuration.\n */\nexport async function interceptRequest(\n config: RequestHandlerConfig,\n interceptors: RequestInterceptor | RequestInterceptor[],\n): Promise {\n if (!interceptors) {\n return config;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedConfig = { ...config };\n\n for (const interceptor of interceptorList) {\n interceptedConfig = await interceptor(interceptedConfig);\n }\n\n return interceptedConfig;\n}\n\n/**\n * Applies a series of response interceptors to the provided response.\n * @param {FetchResponse} response - The initial response object.\n * @param {ResponseInterceptor | ResponseInterceptor[]} interceptors - The response interceptor function(s) to apply.\n * @returns {Promise>} - The modified response object.\n */\nexport async function interceptResponse(\n response: FetchResponse,\n interceptors: ResponseInterceptor | ResponseInterceptor[],\n): Promise> {\n if (!interceptors) {\n return response;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedResponse = response;\n\n for (const interceptor of interceptorList) {\n interceptedResponse = await interceptor(interceptedResponse);\n }\n\n return interceptedResponse;\n}\n","import type { FetchResponse, RequestConfig } from './types';\n\nexport class ResponseErr extends Error {\n response: FetchResponse;\n request: RequestConfig;\n config: RequestConfig;\n status: number;\n statusText: string;\n\n constructor(\n message: string,\n requestInfo: RequestConfig,\n response: FetchResponse,\n ) {\n super(message);\n\n this.name = 'ResponseError';\n this.message = message;\n this.status = response.status;\n this.statusText = response.statusText;\n this.request = requestInfo;\n this.config = requestInfo;\n this.response = response;\n }\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {\n ErrorHandlingStrategy,\n RequestHandlerConfig,\n RequestConfig,\n FetcherInstance,\n Method,\n RetryOptions,\n FetchResponse,\n ResponseError,\n HeadersObject,\n} from './types/request-handler';\nimport type {\n APIResponse,\n QueryParams,\n QueryParamsOrBody,\n UrlPathParams,\n} from './types/api-handler';\nimport { interceptRequest, interceptResponse } from './interceptor-manager';\nimport { ResponseErr } from './response-error';\n\n/**\n * Generic Request Handler\n * It creates an Request Fetcher instance and handles requests within that instance\n * It handles errors depending on a chosen error handling strategy\n */\nexport class RequestHandler {\n /**\n * @var requestInstance Provider's instance\n */\n public requestInstance: FetcherInstance;\n\n /**\n * @var baseURL Base API url\n */\n public baseURL: string = '';\n\n /**\n * @var timeout Request timeout\n */\n public timeout: number = 30000;\n\n /**\n * @var cancellable Response cancellation\n */\n public cancellable: boolean = false;\n\n /**\n * @var rejectCancelled Whether to reject cancelled requests or not\n */\n public rejectCancelled: boolean = false;\n\n /**\n * @var strategy Request timeout\n */\n public strategy: ErrorHandlingStrategy = 'reject';\n\n /**\n * @var method Request method\n */\n public method: Method | string = 'get';\n\n /**\n * @var flattenResponse Response flattening\n */\n public flattenResponse: boolean = false;\n\n /**\n * @var defaultResponse Response flattening\n */\n public defaultResponse: any = null;\n\n /**\n * @var fetcher Request Fetcher instance\n */\n protected fetcher: FetcherInstance;\n\n /**\n * @var logger Logger\n */\n protected logger: any;\n\n /**\n * @var onError HTTP error service\n */\n protected onError: any;\n\n /**\n * @var requestsQueue Queue of requests\n */\n protected requestsQueue: WeakMap;\n\n /**\n * Request Handler Config\n */\n protected retry: RetryOptions = {\n retries: 0,\n delay: 1000,\n maxDelay: 30000,\n backoff: 1.5,\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status\n retryOn: [\n 408, // Request Timeout\n 409, // Conflict\n 425, // Too Early\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n ],\n\n shouldRetry: async () => true,\n };\n\n /**\n * Request Handler Config\n */\n public config: RequestHandlerConfig;\n\n /**\n * Creates an instance of RequestHandler.\n *\n * @param {Object} config - Configuration object for the request.\n * @param {string} config.baseURL - The base URL for the request.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n */\n public constructor({\n fetcher = null,\n timeout = null,\n rejectCancelled = false,\n strategy = null,\n flattenResponse = null,\n defaultResponse = {},\n logger = null,\n onError = null,\n ...config\n }: RequestHandlerConfig) {\n this.fetcher = fetcher;\n this.timeout =\n timeout !== null && timeout !== undefined ? timeout : this.timeout;\n this.strategy = strategy || this.strategy;\n this.cancellable = config.cancellable || this.cancellable;\n this.rejectCancelled = rejectCancelled || this.rejectCancelled;\n this.flattenResponse = flattenResponse || this.flattenResponse;\n this.defaultResponse = defaultResponse;\n this.logger = logger || (globalThis ? globalThis.console : null) || null;\n this.onError = onError;\n this.requestsQueue = new WeakMap();\n this.baseURL = config.baseURL || config.apiUrl || '';\n this.method = config.method || this.method;\n this.config = config;\n this.retry = {\n ...this.retry,\n ...(config.retry || {}),\n };\n\n this.requestInstance = this.isCustomFetcher()\n ? (fetcher as any).create({\n ...config,\n baseURL: this.baseURL,\n timeout: this.timeout,\n })\n : null;\n }\n\n /**\n * Get Provider Instance\n *\n * @returns {FetcherInstance} Provider's instance\n */\n public getInstance(): FetcherInstance {\n return this.requestInstance;\n }\n\n /**\n * Replaces dynamic URI parameters in a URL string with values from the provided `urlPathParams` object.\n * Parameters in the URL are denoted by `:`, where `` is a key in `urlPathParams`.\n *\n * @param {string} url - The URL string containing placeholders in the format `:`.\n * @param {Object} urlPathParams - An object containing the parameter values to replace placeholders.\n * @param {string} urlPathParams.paramName - The value to replace the placeholder `:` in the URL.\n * @returns {string} - The URL string with placeholders replaced by corresponding values from `urlPathParams`.\n */\n public replaceUrlPathParams(\n url: string,\n urlPathParams: UrlPathParams,\n ): string {\n if (!urlPathParams) {\n return url;\n }\n\n return url.replace(/:[a-zA-Z]+/gi, (str): string => {\n const word = str.substring(1);\n\n return String(urlPathParams[word] ? urlPathParams[word] : str);\n });\n }\n\n /**\n * Appends query parameters to the given URL\n *\n * @param {string} url - The base URL to which query parameters will be appended.\n * @param {QueryParams} params - An instance of URLSearchParams containing the query parameters to append.\n * @returns {string} - The URL with the appended query parameters.\n */\n public appendQueryParams(url: string, params: QueryParams): string {\n if (!params) {\n return url;\n }\n\n // We don't use URLSearchParams here as we want to ensure that arrays are properly converted similarily to Axios\n // So { foo: [1, 2] } would become: foo[]=1&foo[]=2\n const queryString = Object.entries(params)\n .flatMap(([key, value]) => {\n if (Array.isArray(value)) {\n return value.map(\n (val) => `${encodeURIComponent(key)}[]=${encodeURIComponent(val)}`,\n );\n }\n return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`;\n })\n .join('&');\n\n return url.includes('?')\n ? `${url}&${queryString}`\n : queryString\n ? `${url}?${queryString}`\n : url;\n }\n\n /**\n * Checks if a value is JSON serializable.\n *\n * JSON serializable values include:\n * - Primitive types: string, number, boolean, null\n * - Arrays\n * - Plain objects (i.e., objects without special methods)\n * - Values with a `toJSON` method\n *\n * @param {any} value - The value to check for JSON serializability.\n * @returns {boolean} - Returns `true` if the value is JSON serializable, otherwise `false`.\n */\n protected isJSONSerializable(value: any): boolean {\n if (value === undefined || value === null) {\n return false;\n }\n\n const t = typeof value;\n if (t === 'string' || t === 'number' || t === 'boolean') {\n return true;\n }\n\n if (t !== 'object') {\n return false; // bigint, function, symbol, undefined\n }\n\n if (Array.isArray(value)) {\n return true;\n }\n\n if (Buffer.isBuffer(value)) {\n return false;\n }\n\n if (value instanceof Date) {\n return false;\n }\n\n const proto = Object.getPrototypeOf(value);\n\n // Check if the prototype is `Object.prototype` or `null` (plain object)\n if (proto === Object.prototype || proto === null) {\n return true;\n }\n\n // Check if the object has a toJSON method\n if (typeof value.toJSON === 'function') {\n return true;\n }\n\n return false;\n }\n\n /**\n * Build request configuration\n *\n * @param {string} url Request url\n * @param {QueryParamsOrBody} data Request data\n * @param {RequestConfig} config Request config\n * @returns {RequestConfig} Provider's instance\n */\n protected buildConfig(\n url: string,\n data: QueryParamsOrBody,\n config: RequestConfig,\n ): RequestConfig {\n const method = config.method || this.method;\n const methodLowerCase = method.toLowerCase();\n const isGetAlikeMethod =\n methodLowerCase === 'get' || methodLowerCase === 'head';\n\n const dynamicUrl = this.replaceUrlPathParams(\n url,\n config.urlPathParams || this.config.urlPathParams,\n );\n\n // Bonus: Specifying it here brings support for \"body\" in Axios\n const configData =\n config.body || config.data || this.config.body || this.config.data;\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n return {\n ...config,\n url: dynamicUrl,\n method: methodLowerCase,\n\n ...(isGetAlikeMethod ? { params: data } : {}),\n\n // For POST requests body payload is the first param for convenience (\"data\")\n // In edge cases we want to split so to treat it as query params, and use \"body\" coming from the config instead\n ...(!isGetAlikeMethod && data && configData ? { params: data } : {}),\n\n // Only applicable for request methods 'PUT', 'POST', 'DELETE', and 'PATCH'\n ...(!isGetAlikeMethod && data && !configData ? { data } : {}),\n ...(!isGetAlikeMethod && configData ? { data: configData } : {}),\n };\n }\n\n // Native fetch\n const payload = configData || data;\n const credentials =\n config.withCredentials || this.config.withCredentials\n ? 'include'\n : config.credentials;\n\n delete config.data;\n delete config.withCredentials;\n\n const urlPath =\n (!isGetAlikeMethod && data && !config.body) || !data\n ? dynamicUrl\n : this.appendQueryParams(dynamicUrl, data);\n const isFullUrl = urlPath.includes('://');\n const baseURL = isFullUrl\n ? ''\n : typeof config.baseURL !== 'undefined'\n ? config.baseURL\n : this.baseURL;\n\n return {\n ...config,\n credentials,\n\n // Native fetch generally requires query params to be appended in the URL\n // Do not append query params only if it's a POST-alike request with only \"data\" specified as it's treated as body payload\n url: baseURL + urlPath,\n\n // Uppercase method name\n method: method.toUpperCase(),\n\n // For convenience, add the same default headers as Axios does\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json;charset=utf-8',\n ...(config.headers || this.config.headers || {}),\n },\n\n // Automatically JSON stringify request bodies, if possible and when not dealing with strings\n ...(!isGetAlikeMethod\n ? {\n body:\n !(payload instanceof URLSearchParams) &&\n this.isJSONSerializable(payload)\n ? typeof payload === 'string'\n ? payload\n : JSON.stringify(payload)\n : payload,\n }\n : {}),\n };\n }\n\n /**\n * Process global Request Error\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {void}\n */\n protected processError(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): void {\n if (this.isRequestCancelled(error)) {\n return;\n }\n\n if (this.logger?.warn) {\n this.logger.warn('API ERROR', error);\n }\n\n // Invoke per request \"onError\" interceptor\n if (requestConfig.onError && typeof requestConfig.onError === 'function') {\n requestConfig.onError(error);\n }\n\n // Invoke global \"onError\" interceptor\n if (this.onError && typeof this.onError === 'function') {\n this.onError(error);\n }\n }\n\n /**\n * Output default response in case of an error, depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {*} Error response\n */\n protected async outputErrorResponse(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): Promise {\n const isRequestCancelled = this.isRequestCancelled(error);\n const errorHandlingStrategy = requestConfig.strategy || this.strategy;\n const rejectCancelled =\n typeof requestConfig.rejectCancelled !== 'undefined'\n ? requestConfig.rejectCancelled\n : this.rejectCancelled;\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n // Output full response with the error object\n if (errorHandlingStrategy === 'softFail') {\n return this.outputResponse(error.response, requestConfig, error);\n }\n\n // By default cancelled requests aren't rejected\n if (isRequestCancelled && !rejectCancelled) {\n return defaultResponse;\n }\n\n // Hang the promise\n if (errorHandlingStrategy === 'silent') {\n await new Promise(() => null);\n\n return defaultResponse;\n }\n\n // Reject the promise\n if (errorHandlingStrategy === 'reject') {\n return Promise.reject(error);\n }\n\n return defaultResponse;\n }\n\n /**\n * Output error response depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @returns {boolean} True if request is aborted\n */\n public isRequestCancelled(error: ResponseError): boolean {\n return error.name === 'AbortError' || error.name === 'CanceledError';\n }\n\n /**\n * Detects if a custom fetcher is utilized\n *\n * @returns {boolean} True if it's a custom fetcher\n */\n protected isCustomFetcher(): boolean {\n return this.fetcher !== null;\n }\n\n /**\n * Automatically Cancel Previous Requests using AbortController when \"cancellable\" is defined\n *\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {Object} Controller Signal to abort\n */\n protected addCancellationToken(\n requestConfig: RequestConfig,\n ): Partial> {\n // Both disabled\n if (!this.cancellable && !requestConfig.cancellable) {\n return {};\n }\n\n // Explicitly disabled per request\n if (\n typeof requestConfig.cancellable !== 'undefined' &&\n !requestConfig.cancellable\n ) {\n return {};\n }\n\n // Check if AbortController is available\n if (typeof AbortController === 'undefined') {\n console.error('AbortController is unavailable.');\n\n return {};\n }\n\n // Generate unique key as a cancellation token\n const previousRequest = this.requestsQueue.get(requestConfig);\n\n if (previousRequest) {\n previousRequest.abort();\n }\n\n const controller = new AbortController();\n\n // Introduce timeout for native fetch\n if (!this.isCustomFetcher() && this.timeout > 0) {\n const abortTimeout = setTimeout(() => {\n const error = new Error(\n `[TimeoutError]: The ${requestConfig.url} request was aborted due to timeout`,\n );\n\n error.name = 'TimeoutError';\n (error as any).code = 23; // DOMException.TIMEOUT_ERR\n controller.abort(error);\n clearTimeout(abortTimeout);\n throw error;\n }, requestConfig.timeout || this.timeout);\n }\n\n this.requestsQueue.set(requestConfig, controller);\n\n return {\n signal: controller.signal,\n };\n }\n\n /**\n * Handle Request depending on used strategy\n *\n * @param {string} url - Request url\n * @param {QueryParamsOrBody} data - Request data\n * @param {RequestConfig} config - Request config\n * @param {RequestConfig} payload.config Request config\n * @throws {ResponseError}\n * @returns {Promise>} Response Data\n */\n public async request(\n url: string,\n data: QueryParamsOrBody = null,\n config: RequestConfig = null,\n ): Promise> {\n let response: FetchResponse = null;\n const _config = config || {};\n const _requestConfig = this.buildConfig(url, data, _config);\n\n let requestConfig: RequestConfig = {\n ...this.addCancellationToken(_requestConfig),\n ..._requestConfig,\n };\n\n const { retries, delay, backoff, retryOn, shouldRetry, maxDelay } = {\n ...this.retry,\n ...(requestConfig?.retry || {}),\n };\n\n let attempt = 0;\n let waitTime = delay;\n\n while (attempt <= retries) {\n try {\n // Local interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n requestConfig.onRequest,\n );\n\n // Global interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n this.config.onRequest,\n );\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n response = (await (this.requestInstance as any).request(\n requestConfig,\n )) as FetchResponse;\n } else {\n response = (await globalThis.fetch(\n requestConfig.url,\n requestConfig,\n )) as FetchResponse;\n\n // Attempt to collect response data regardless of response status\n const contentType = String(\n (response as Response)?.headers?.get('Content-Type') || '',\n );\n let data;\n const responseClone = response.clone();\n\n // Handle edge case of no content type being provided... We assume json here.\n if (!contentType) {\n try {\n data = await responseClone.json();\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (_error) {\n //\n }\n }\n\n if (typeof data === 'undefined') {\n if (\n contentType &&\n (contentType.includes('application/json') ||\n // This Media Type Suffix is standardizded by IETF in RFC 6839\n contentType.includes('+json'))\n ) {\n data = await response.json(); // Parse JSON response\n } else if (contentType.includes('multipart/form-data')) {\n data = await response.formData(); // Parse as FormData\n } else if (contentType.includes('application/octet-stream')) {\n data = await response.blob(); // Parse as blob\n } else if (\n contentType.includes('application/x-www-form-urlencoded')\n ) {\n data = await response.formData(); // Handle URL-encoded forms\n } else if (typeof response.text !== 'undefined') {\n data = await response.text();\n } else {\n // Handle streams\n data = response.body || response.data || null;\n }\n }\n\n // Add more information to response object\n response.config = requestConfig;\n response.data = data;\n\n // Check if the response status is not outside the range 200-299 and if so, output error\n if (!response.ok) {\n throw new ResponseErr(\n `${requestConfig.url} failed! Status: ${response.status || null}`,\n requestConfig,\n response,\n );\n }\n }\n\n // Local interceptors\n response = await interceptResponse(response, requestConfig.onResponse);\n\n // Global interceptors\n response = await interceptResponse(response, this.config.onResponse);\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n } catch (error) {\n if (\n attempt === retries ||\n !(await shouldRetry(error, attempt)) ||\n !retryOn?.includes(error?.response?.status || error?.status)\n ) {\n this.processError(error, requestConfig);\n\n return this.outputErrorResponse(error, requestConfig);\n }\n\n if (this.logger?.warn) {\n this.logger.warn(\n `Attempt ${attempt + 1} failed. Retrying in ${waitTime}ms...`,\n );\n }\n\n await this.delay(waitTime);\n\n waitTime *= backoff;\n waitTime = Math.min(waitTime, maxDelay);\n attempt++;\n }\n }\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n }\n\n public async delay(ms: number): Promise {\n return new Promise((resolve) =>\n setTimeout(() => {\n return resolve(true);\n }, ms),\n );\n }\n\n public processHeaders(\n response: FetchResponse,\n ): HeadersObject {\n if (!response.headers) {\n return {};\n }\n\n let headersObject: HeadersObject = {};\n\n // Handle Headers object with entries() method\n if (response.headers instanceof Headers) {\n for (const [key, value] of (response.headers as any).entries()) {\n headersObject[key] = value;\n }\n } else {\n // Handle plain object\n headersObject = { ...(response.headers as HeadersObject) };\n }\n\n return headersObject;\n }\n\n /**\n * Output response\n *\n * @param response - Response payload\n * @param {RequestConfig} requestConfig - Request config\n * @param {*} error - whether the response is erroneous\n * @returns {ResponseData | FetchResponse} Response data\n */\n protected outputResponse(\n response: FetchResponse,\n requestConfig: RequestConfig,\n error = null,\n ): ResponseData | FetchResponse {\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n if (!response) {\n return defaultResponse;\n }\n\n if (\n (requestConfig.flattenResponse || this.flattenResponse) &&\n typeof response.data !== 'undefined'\n ) {\n // Special case of only data property within response data object (happens in Axios)\n // This is in fact a proper response but we may want to flatten it\n // To ease developers' lives when obtaining the response\n if (\n response.data !== null &&\n typeof response.data === 'object' &&\n typeof (response.data as any).data !== 'undefined' &&\n Object.keys(response.data).length === 1\n ) {\n return (response.data as any).data;\n }\n\n return response.data;\n }\n\n // If empty object is returned, ensure that the default response is used instead\n if (\n response !== null &&\n typeof response === 'object' &&\n response.constructor === Object &&\n Object.keys(response).length === 0\n ) {\n return defaultResponse;\n }\n\n const isCustomFetcher = this.isCustomFetcher();\n\n if (isCustomFetcher) {\n return response;\n }\n\n if (error !== null) {\n delete error?.response;\n delete error?.request;\n delete error?.config;\n }\n\n // Native fetch()\n return {\n body: response.body,\n blob: response.blob,\n json: response.json,\n text: response.text,\n clone: response.clone,\n bodyUsed: response.bodyUsed,\n arrayBuffer: response.arrayBuffer,\n formData: response.formData,\n ok: response.ok,\n redirected: response.redirected,\n type: response.type,\n url: response.url,\n status: response.status,\n statusText: response.statusText,\n\n // Extend with extra information\n error,\n data: response.data,\n headers: this.processHeaders(response),\n config: requestConfig,\n };\n }\n}\n","import { RequestHandler } from './request-handler';\nimport type {\n FetcherInstance,\n RequestConfig,\n FetchResponse,\n} from './types/request-handler';\nimport type {\n ApiHandlerConfig,\n ApiHandlerMethods,\n ApiHandlerReturnType,\n APIResponse,\n QueryParams,\n UrlPathParams,\n} from './types/api-handler';\n\n/**\n * Creates an instance of API Handler.\n * It creates an API fetcher function using native fetch() or Axios if it is passed as \"fetcher\".\n *\n * @param {Object} config - Configuration object for the API fetcher.\n * @param {string} config.apiUrl - The base URL for the API.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n * @returns API handler functions and endpoints to call\n *\n * @example\n * // Import axios (optional)\n * import axios from 'axios';\n *\n * // Define endpoint paths\n * const endpoints = {\n * getUser: '/user',\n * createPost: '/post',\n * };\n *\n * // Create the API fetcher with configuration\n * const api = createApiFetcher({\n * fetcher: axios, // Axios instance (optional)\n * endpoints,\n * apiUrl: 'https://example.com/api',\n * onError(error) {\n * console.log('Request failed', error);\n * },\n * headers: {\n * 'my-auth-key': 'example-auth-key-32rjjfa',\n * },\n * });\n *\n * // Fetch user data\n * const response = await api.getUser({ userId: 1, ratings: [1, 2] })\n */\nfunction createApiFetcher<\n EndpointsMethods extends object,\n EndpointsCfg = never,\n>(config: ApiHandlerConfig) {\n const endpoints = config.endpoints;\n const requestHandler = new RequestHandler(config);\n\n /**\n * Get Fetcher Provider Instance\n *\n * @returns {FetcherInstance} Request Handler's Fetcher instance\n */\n function getInstance(): FetcherInstance {\n return requestHandler.getInstance();\n }\n\n /**\n * Triggered when trying to use non-existent endpoints\n *\n * @param endpointName Endpoint Name\n * @returns {Promise}\n */\n function handleNonImplemented(endpointName: string): Promise {\n console.error(`${endpointName} endpoint must be added to 'endpoints'.`);\n\n return Promise.resolve(null);\n }\n\n /**\n * Handle Single API Request\n * It considers settings in following order: per-request settings, global per-endpoint settings, global settings.\n *\n * @param {string} endpointName - The name of the API endpoint to call.\n * @param {QueryParams} [queryParams={}] - Query parameters to include in the request.\n * @param {UrlPathParams} [urlPathParams={}] - URI parameters to include in the request.\n * @param {EndpointConfig} [requestConfig={}] - Additional configuration for the request.\n * @returns {Promise} - A promise that resolves with the response from the API provider.\n */\n async function request(\n endpointName: keyof EndpointsMethods | string,\n queryParams: QueryParams = {},\n urlPathParams: UrlPathParams = {},\n requestConfig: RequestConfig = {},\n ): Promise> {\n // Use global per-endpoint settings\n const endpointConfig = endpoints[endpointName as string];\n const endpointSettings = { ...endpointConfig };\n\n const responseData = await requestHandler.request(\n endpointSettings.url,\n queryParams,\n {\n ...endpointSettings,\n ...requestConfig,\n urlPathParams,\n },\n );\n\n return responseData;\n }\n\n /**\n * Maps all API requests using native Proxy\n *\n * @param {*} prop Caller\n */\n function get(prop: string | symbol) {\n if (prop in apiHandler) {\n return apiHandler[prop];\n }\n\n // Prevent handler from triggering non-existent endpoints\n if (!endpoints[prop as string]) {\n return handleNonImplemented.bind(null, prop);\n }\n\n return apiHandler.request.bind(null, prop);\n }\n\n const apiHandler: ApiHandlerMethods = {\n config,\n endpoints,\n requestHandler,\n getInstance,\n request,\n };\n\n return new Proxy(apiHandler, {\n get: (_target, prop) => get(prop),\n }) as ApiHandlerReturnType;\n}\n\nexport { createApiFetcher };\n","import { RequestHandler } from './request-handler';\nimport type { APIResponse, FetchResponse, RequestHandlerConfig } from './types';\n\n/**\n * Simple wrapper for request fetching.\n * It abstracts the creation of RequestHandler, making it easy to perform API requests.\n *\n * @param {string | URL | globalThis.Request} url - Request URL.\n * @param {RequestHandlerConfig} config - Configuration object for the request handler.\n * @returns {Promise>} Response Data.\n */\nexport async function fetchf(\n url: string,\n config: RequestHandlerConfig = {},\n): Promise> {\n return new RequestHandler(config).request(\n url,\n config.body || config.data || config.params,\n config,\n );\n}\n\nexport * from './types';\nexport * from './api-handler';\n"],"mappings":"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","names":["interceptRequest","config","interceptors","interceptorList","interceptedConfig","interceptor","interceptResponse","response","interceptedResponse","ResponseErr","message","requestInfo","response","RequestHandler","fetcher","timeout","rejectCancelled","strategy","flattenResponse","defaultResponse","logger","onError","config","url","urlPathParams","str","word","params","queryString","key","value","val","t","proto","data","method","methodLowerCase","isGetAlikeMethod","dynamicUrl","configData","payload","credentials","urlPath","baseURL","error","requestConfig","_a","isRequestCancelled","errorHandlingStrategy","previousRequest","controller","abortTimeout","_b","_c","response","_config","_requestConfig","retries","delay","backoff","retryOn","shouldRetry","maxDelay","attempt","waitTime","interceptRequest","contentType","responseClone","ResponseErr","interceptResponse","ms","resolve","headersObject","createApiFetcher","config","endpoints","requestHandler","RequestHandler","getInstance","handleNonImplemented","endpointName","request","queryParams","urlPathParams","requestConfig","endpointSettings","get","prop","apiHandler","_target","fetchf","url","config","RequestHandler"]} \ No newline at end of file +{"version":3,"sources":["../src/interceptor-manager.ts","../src/response-error.ts","../src/utils.ts","../src/request-handler.ts","../src/api-handler.ts","../src/index.ts"],"sourcesContent":["import type { RequestHandlerConfig, FetchResponse } from './types';\nimport type {\n RequestInterceptor,\n ResponseInterceptor,\n} from './types/interceptor-manager';\n\n/**\n * Applies a series of request interceptors to the provided configuration.\n * @param {RequestHandlerConfig} config - The initial request configuration.\n * @param {RequestInterceptor | RequestInterceptor[]} interceptors - The request interceptor function(s) to apply.\n * @returns {Promise} - The modified request configuration.\n */\nexport async function interceptRequest(\n config: RequestHandlerConfig,\n interceptors: RequestInterceptor | RequestInterceptor[],\n): Promise {\n if (!interceptors) {\n return config;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedConfig = { ...config };\n\n for (const interceptor of interceptorList) {\n interceptedConfig = await interceptor(interceptedConfig);\n }\n\n return interceptedConfig;\n}\n\n/**\n * Applies a series of response interceptors to the provided response.\n * @param {FetchResponse} response - The initial response object.\n * @param {ResponseInterceptor | ResponseInterceptor[]} interceptors - The response interceptor function(s) to apply.\n * @returns {Promise>} - The modified response object.\n */\nexport async function interceptResponse(\n response: FetchResponse,\n interceptors: ResponseInterceptor | ResponseInterceptor[],\n): Promise> {\n if (!interceptors) {\n return response;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedResponse = response;\n\n for (const interceptor of interceptorList) {\n interceptedResponse = await interceptor(interceptedResponse);\n }\n\n return interceptedResponse;\n}\n","import type { FetchResponse, RequestConfig } from './types';\n\nexport class ResponseErr extends Error {\n response: FetchResponse;\n request: RequestConfig;\n config: RequestConfig;\n status: number;\n statusText: string;\n\n constructor(\n message: string,\n requestInfo: RequestConfig,\n response: FetchResponse,\n ) {\n super(message);\n\n this.name = 'ResponseError';\n this.message = message;\n this.status = response.status;\n this.statusText = response.statusText;\n this.request = requestInfo;\n this.config = requestInfo;\n this.response = response;\n }\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { QueryParams, UrlPathParams } from './types';\n\n/**\n * Appends query parameters to a given URL.\n *\n * @param {string} url - The base URL to which query parameters will be appended.\n * @param {QueryParams} params - An object containing the query parameters to append.\n * @returns {string} - The URL with the appended query parameters.\n */\nexport function appendQueryParams(url: string, params: QueryParams): string {\n if (!params) {\n return url;\n }\n\n // This is exact copy of what JQ used to do. It works much better than URLSearchParams\n const s = [];\n const add = function (k: string, v: any) {\n v = typeof v === 'function' ? v() : v;\n v = v === null ? '' : v === undefined ? '' : v;\n s[s.length] = encodeURIComponent(k) + '=' + encodeURIComponent(v);\n };\n\n const buildParams = (prefix: string, obj: any) => {\n let i: number, len: number, key: string;\n\n if (prefix) {\n if (Array.isArray(obj)) {\n for (i = 0, len = obj.length; i < len; i++) {\n buildParams(\n prefix +\n '[' +\n (typeof obj[i] === 'object' && obj[i] ? i : '') +\n ']',\n obj[i],\n );\n }\n } else if (typeof obj === 'object' && obj !== null) {\n for (key in obj) {\n buildParams(prefix + '[' + key + ']', obj[key]);\n }\n } else {\n add(prefix, obj);\n }\n } else if (Array.isArray(obj)) {\n for (i = 0, len = obj.length; i < len; i++) {\n add(obj[i].name, obj[i].value);\n }\n } else {\n for (key in obj) {\n buildParams(key, obj[key]);\n }\n }\n return s;\n };\n\n const queryStringParts = buildParams('', params).join('&');\n\n // Encode special characters as per RFC 3986, https://datatracker.ietf.org/doc/html/rfc3986\n const encodedQueryString = queryStringParts.replace(/%5B%5D/g, '[]'); // Keep '[]' for arrays\n\n return url.includes('?')\n ? `${url}&${encodedQueryString}`\n : encodedQueryString\n ? `${url}?${encodedQueryString}`\n : url;\n}\n\n/**\n * Replaces dynamic URI parameters in a URL string with values from the provided `urlPathParams` object.\n * Parameters in the URL are denoted by `:`, where `` is a key in `urlPathParams`.\n *\n * @param {string} url - The URL string containing placeholders in the format `:`.\n * @param {Object} urlPathParams - An object containing the parameter values to replace placeholders.\n * @param {string} urlPathParams.paramName - The value to replace the placeholder `:` in the URL.\n * @returns {string} - The URL string with placeholders replaced by corresponding values from `urlPathParams`.\n */\nexport function replaceUrlPathParams(\n url: string,\n urlPathParams: UrlPathParams,\n): string {\n if (!urlPathParams) {\n return url;\n }\n\n return url.replace(/:[a-zA-Z]+/gi, (str): string => {\n const word = str.substring(1);\n\n return String(urlPathParams[word] ? urlPathParams[word] : str);\n });\n}\n\n/**\n * Checks if a value is JSON serializable.\n *\n * JSON serializable values include:\n * - Primitive types: string, number, boolean, null\n * - Arrays\n * - Plain objects (i.e., objects without special methods)\n * - Values with a `toJSON` method\n *\n * @param {any} value - The value to check for JSON serializability.\n * @returns {boolean} - Returns `true` if the value is JSON serializable, otherwise `false`.\n */\nexport function isJSONSerializable(value: any): boolean {\n if (value === undefined || value === null) {\n return false;\n }\n\n const t = typeof value;\n if (t === 'string' || t === 'number' || t === 'boolean') {\n return true;\n }\n\n if (t !== 'object') {\n return false; // bigint, function, symbol, undefined\n }\n\n if (Array.isArray(value)) {\n return true;\n }\n\n if (Buffer.isBuffer(value)) {\n return false;\n }\n\n if (value instanceof Date) {\n return false;\n }\n\n const proto = Object.getPrototypeOf(value);\n\n // Check if the prototype is `Object.prototype` or `null` (plain object)\n if (proto === Object.prototype || proto === null) {\n return true;\n }\n\n // Check if the object has a toJSON method\n if (typeof value.toJSON === 'function') {\n return true;\n }\n\n return false;\n}\n\nexport async function delayInvocation(ms: number): Promise {\n return new Promise((resolve) =>\n setTimeout(() => {\n return resolve(true);\n }, ms),\n );\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {\n ErrorHandlingStrategy,\n RequestHandlerConfig,\n RequestConfig,\n FetcherInstance,\n Method,\n RetryOptions,\n FetchResponse,\n ResponseError,\n HeadersObject,\n} from './types/request-handler';\nimport type { APIResponse, QueryParamsOrBody } from './types/api-handler';\nimport { interceptRequest, interceptResponse } from './interceptor-manager';\nimport { ResponseErr } from './response-error';\nimport {\n appendQueryParams,\n isJSONSerializable,\n replaceUrlPathParams,\n delayInvocation,\n} from './utils';\n\nconst APPLICATION_JSON = 'application/json';\n\n/**\n * Generic Request Handler\n * It creates an Request Fetcher instance and handles requests within that instance\n * It handles errors depending on a chosen error handling strategy\n */\nexport class RequestHandler {\n /**\n * @var requestInstance Provider's instance\n */\n public requestInstance: FetcherInstance;\n\n /**\n * @var baseURL Base API url\n */\n public baseURL: string = '';\n\n /**\n * @var timeout Request timeout\n */\n public timeout: number = 30000;\n\n /**\n * @var cancellable Response cancellation\n */\n public cancellable: boolean = false;\n\n /**\n * @var rejectCancelled Whether to reject cancelled requests or not\n */\n public rejectCancelled: boolean = false;\n\n /**\n * @var strategy Request timeout\n */\n public strategy: ErrorHandlingStrategy = 'reject';\n\n /**\n * @var method Request method\n */\n public method: Method | string = 'get';\n\n /**\n * @var flattenResponse Response flattening\n */\n public flattenResponse: boolean = false;\n\n /**\n * @var defaultResponse Response flattening\n */\n public defaultResponse: any = null;\n\n /**\n * @var fetcher Request Fetcher instance\n */\n protected fetcher: FetcherInstance;\n\n /**\n * @var logger Logger\n */\n protected logger: any;\n\n /**\n * @var onError HTTP error service\n */\n protected onError: any;\n\n /**\n * @var requestsQueue Queue of requests\n */\n protected requestsQueue: WeakMap;\n\n /**\n * Request Handler Config\n */\n protected retry: RetryOptions = {\n retries: 0,\n delay: 1000,\n maxDelay: 30000,\n backoff: 1.5,\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status\n retryOn: [\n 408, // Request Timeout\n 409, // Conflict\n 425, // Too Early\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n ],\n\n shouldRetry: async () => true,\n };\n\n /**\n * Request Handler Config\n */\n public config: RequestHandlerConfig;\n\n /**\n * Creates an instance of RequestHandler.\n *\n * @param {Object} config - Configuration object for the request.\n * @param {string} config.baseURL - The base URL for the request.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n */\n public constructor({\n fetcher = null,\n timeout = null,\n rejectCancelled = false,\n strategy = null,\n flattenResponse = null,\n defaultResponse = {},\n logger = null,\n onError = null,\n ...config\n }: RequestHandlerConfig) {\n this.fetcher = fetcher;\n this.timeout =\n timeout !== null && timeout !== undefined ? timeout : this.timeout;\n this.strategy = strategy || this.strategy;\n this.cancellable = config.cancellable || this.cancellable;\n this.rejectCancelled = rejectCancelled || this.rejectCancelled;\n this.flattenResponse = flattenResponse || this.flattenResponse;\n this.defaultResponse = defaultResponse;\n this.logger = logger || (globalThis ? globalThis.console : null) || null;\n this.onError = onError;\n this.requestsQueue = new WeakMap();\n this.baseURL = config.baseURL || config.apiUrl || '';\n this.method = config.method || this.method;\n this.config = config;\n this.retry = {\n ...this.retry,\n ...(config.retry || {}),\n };\n\n this.requestInstance = this.isCustomFetcher()\n ? (fetcher as any).create({\n ...config,\n baseURL: this.baseURL,\n timeout: this.timeout,\n })\n : null;\n }\n\n /**\n * Get Provider Instance\n *\n * @returns {FetcherInstance} Provider's instance\n */\n public getInstance(): FetcherInstance {\n return this.requestInstance;\n }\n\n /**\n * Build request configuration\n *\n * @param {string} url Request url\n * @param {QueryParamsOrBody} data Request data\n * @param {RequestConfig} config Request config\n * @returns {RequestConfig} Provider's instance\n */\n protected buildConfig(\n url: string,\n data: QueryParamsOrBody,\n config: RequestConfig,\n ): RequestConfig {\n const method = config.method || this.method;\n const methodLowerCase = method.toLowerCase();\n const isGetAlikeMethod =\n methodLowerCase === 'get' || methodLowerCase === 'head';\n\n const dynamicUrl = replaceUrlPathParams(\n url,\n config.urlPathParams || this.config.urlPathParams,\n );\n\n // Bonus: Specifying it here brings support for \"body\" in Axios\n const configData =\n config.body || config.data || this.config.body || this.config.data;\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n return {\n ...config,\n url: dynamicUrl,\n method: methodLowerCase,\n\n ...(isGetAlikeMethod ? { params: data } : {}),\n\n // For POST requests body payload is the first param for convenience (\"data\")\n // In edge cases we want to split so to treat it as query params, and use \"body\" coming from the config instead\n ...(!isGetAlikeMethod && data && configData ? { params: data } : {}),\n\n // Only applicable for request methods 'PUT', 'POST', 'DELETE', and 'PATCH'\n ...(!isGetAlikeMethod && data && !configData ? { data } : {}),\n ...(!isGetAlikeMethod && configData ? { data: configData } : {}),\n };\n }\n\n // Native fetch\n const payload = configData || data;\n const credentials =\n config.withCredentials || this.config.withCredentials\n ? 'include'\n : config.credentials;\n\n delete config.data;\n delete config.withCredentials;\n\n const urlPath =\n (!isGetAlikeMethod && data && !config.body) || !data\n ? dynamicUrl\n : appendQueryParams(dynamicUrl, data);\n const isFullUrl = urlPath.includes('://');\n const baseURL = isFullUrl\n ? ''\n : typeof config.baseURL !== 'undefined'\n ? config.baseURL\n : this.baseURL;\n\n return {\n ...config,\n credentials,\n\n // Native fetch generally requires query params to be appended in the URL\n // Do not append query params only if it's a POST-alike request with only \"data\" specified as it's treated as body payload\n url: baseURL + urlPath,\n\n // Uppercase method name\n method: method.toUpperCase(),\n\n // For convenience, add the same default headers as Axios does\n headers: {\n Accept: APPLICATION_JSON + ', text/plain, */*',\n 'Content-Type': APPLICATION_JSON + ';charset=utf-8',\n ...(config.headers || this.config.headers || {}),\n },\n\n // Automatically JSON stringify request bodies, if possible and when not dealing with strings\n ...(!isGetAlikeMethod\n ? {\n body:\n !(payload instanceof URLSearchParams) &&\n isJSONSerializable(payload)\n ? typeof payload === 'string'\n ? payload\n : JSON.stringify(payload)\n : payload,\n }\n : {}),\n };\n }\n\n /**\n * Process global Request Error\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {void}\n */\n protected processError(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): void {\n if (this.isRequestCancelled(error)) {\n return;\n }\n\n if (this.logger?.warn) {\n this.logger.warn('API ERROR', error);\n }\n\n // Invoke per request \"onError\" interceptor\n if (requestConfig.onError && typeof requestConfig.onError === 'function') {\n requestConfig.onError(error);\n }\n\n // Invoke global \"onError\" interceptor\n if (this.onError && typeof this.onError === 'function') {\n this.onError(error);\n }\n }\n\n /**\n * Output default response in case of an error, depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {*} Error response\n */\n protected async outputErrorResponse(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): Promise {\n const isRequestCancelled = this.isRequestCancelled(error);\n const errorHandlingStrategy = requestConfig.strategy || this.strategy;\n const rejectCancelled =\n typeof requestConfig.rejectCancelled !== 'undefined'\n ? requestConfig.rejectCancelled\n : this.rejectCancelled;\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n // Output full response with the error object\n if (errorHandlingStrategy === 'softFail') {\n return this.outputResponse(error.response, requestConfig, error);\n }\n\n // By default cancelled requests aren't rejected\n if (isRequestCancelled && !rejectCancelled) {\n return defaultResponse;\n }\n\n // Hang the promise\n if (errorHandlingStrategy === 'silent') {\n await new Promise(() => null);\n\n return defaultResponse;\n }\n\n // Reject the promise\n if (errorHandlingStrategy === 'reject') {\n return Promise.reject(error);\n }\n\n return defaultResponse;\n }\n\n /**\n * Output error response depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @returns {boolean} True if request is aborted\n */\n public isRequestCancelled(error: ResponseError): boolean {\n return error.name === 'AbortError' || error.name === 'CanceledError';\n }\n\n /**\n * Detects if a custom fetcher is utilized\n *\n * @returns {boolean} True if it's a custom fetcher\n */\n protected isCustomFetcher(): boolean {\n return this.fetcher !== null;\n }\n\n /**\n * Automatically Cancel Previous Requests using AbortController when \"cancellable\" is defined\n *\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {Object} Controller Signal to abort\n */\n protected addCancellationToken(\n requestConfig: RequestConfig,\n ): Partial> {\n // Both disabled\n if (!this.cancellable && !requestConfig.cancellable) {\n return {};\n }\n\n // Explicitly disabled per request\n if (\n typeof requestConfig.cancellable !== 'undefined' &&\n !requestConfig.cancellable\n ) {\n return {};\n }\n\n // Check if AbortController is available\n if (typeof AbortController === 'undefined') {\n console.error('AbortController is unavailable.');\n\n return {};\n }\n\n // Generate unique key as a cancellation token\n const previousRequest = this.requestsQueue.get(requestConfig);\n\n if (previousRequest) {\n previousRequest.abort();\n }\n\n const controller = new AbortController();\n\n // Introduce timeout for native fetch\n if (!this.isCustomFetcher() && this.timeout > 0) {\n const abortTimeout = setTimeout(() => {\n const error = new Error(\n `[TimeoutError]: The ${requestConfig.url} request was aborted due to timeout`,\n );\n\n error.name = 'TimeoutError';\n (error as any).code = 23; // DOMException.TIMEOUT_ERR\n controller.abort(error);\n clearTimeout(abortTimeout);\n throw error;\n }, requestConfig.timeout || this.timeout);\n }\n\n this.requestsQueue.set(requestConfig, controller);\n\n return {\n signal: controller.signal,\n };\n }\n\n /**\n * Handle Request depending on used strategy\n *\n * @param {string} url - Request url\n * @param {QueryParamsOrBody} data - Request data\n * @param {RequestConfig} config - Request config\n * @param {RequestConfig} payload.config Request config\n * @throws {ResponseError}\n * @returns {Promise>} Response Data\n */\n public async request(\n url: string,\n data: QueryParamsOrBody = null,\n config: RequestConfig = null,\n ): Promise> {\n let response: FetchResponse = null;\n const _config = config || {};\n const _requestConfig = this.buildConfig(url, data, _config);\n\n let requestConfig: RequestConfig = {\n ...this.addCancellationToken(_requestConfig),\n ..._requestConfig,\n };\n\n const { retries, delay, backoff, retryOn, shouldRetry, maxDelay } = {\n ...this.retry,\n ...(requestConfig?.retry || {}),\n };\n\n let attempt = 0;\n let waitTime = delay;\n\n while (attempt <= retries) {\n try {\n // Local interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n requestConfig.onRequest,\n );\n\n // Global interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n this.config.onRequest,\n );\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n response = (await (this.requestInstance as any).request(\n requestConfig,\n )) as FetchResponse;\n } else {\n response = (await globalThis.fetch(\n requestConfig.url,\n requestConfig,\n )) as FetchResponse;\n\n // Add more information to response object\n response.config = requestConfig;\n response.data = await this.parseData(response);\n\n // Check if the response status is not outside the range 200-299 and if so, output error\n if (!response.ok) {\n throw new ResponseErr(\n `${requestConfig.url} failed! Status: ${response.status || null}`,\n requestConfig,\n response,\n );\n }\n }\n\n // Local interceptors\n response = await interceptResponse(response, requestConfig.onResponse);\n\n // Global interceptors\n response = await interceptResponse(response, this.config.onResponse);\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n } catch (error) {\n if (\n attempt === retries ||\n !(await shouldRetry(error, attempt)) ||\n !retryOn?.includes(error?.response?.status || error?.status)\n ) {\n this.processError(error, requestConfig);\n\n return this.outputErrorResponse(error, requestConfig);\n }\n\n if (this.logger?.warn) {\n this.logger.warn(\n `Attempt ${attempt + 1} failed. Retrying in ${waitTime}ms...`,\n );\n }\n\n await delayInvocation(waitTime);\n\n waitTime *= backoff;\n waitTime = Math.min(waitTime, maxDelay);\n attempt++;\n }\n }\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n }\n\n /**\n * Parses the response data based on the Content-Type header.\n *\n * @param response - The Response object to parse.\n * @returns A Promise that resolves to the parsed data.\n */\n public async parseData(\n response: FetchResponse,\n ): Promise {\n const contentType = String(\n (response as Response).headers?.get('Content-Type') || '',\n );\n let data;\n\n // Handle edge case of no content type being provided... We assume JSON here.\n if (!contentType) {\n const responseClone = response.clone();\n try {\n data = await responseClone.json();\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (_error) {\n // JSON parsing failed, fallback to null\n data = null;\n }\n }\n\n if (typeof data === 'undefined') {\n try {\n if (\n contentType.includes(APPLICATION_JSON) ||\n contentType.includes('+json')\n ) {\n data = await response.json(); // Parse JSON response\n } else if (contentType.includes('multipart/form-data')) {\n data = await response.formData(); // Parse as FormData\n } else if (contentType.includes('application/octet-stream')) {\n data = await response.blob(); // Parse as blob\n } else if (contentType.includes('application/x-www-form-urlencoded')) {\n data = await response.formData(); // Handle URL-encoded forms\n } else if (typeof response.text === 'function') {\n data = await response.text(); // Parse as text\n } else {\n // Handle streams\n data = response.body || response.data || null;\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (_error) {\n // Parsing failed, fallback to null\n data = null;\n }\n }\n\n return data;\n }\n\n public processHeaders(\n response: FetchResponse,\n ): HeadersObject {\n if (!response.headers) {\n return {};\n }\n\n let headersObject: HeadersObject = {};\n const headers = response.headers;\n\n // Handle Headers object with entries() method\n if (headers instanceof Headers) {\n for (const [key, value] of (headers as any).entries()) {\n headersObject[key] = value;\n }\n } else {\n // Handle plain object\n headersObject = { ...(headers as HeadersObject) };\n }\n\n return headersObject;\n }\n\n /**\n * Output response\n *\n * @param response - Response payload\n * @param {RequestConfig} requestConfig - Request config\n * @param {*} error - whether the response is erroneous\n * @returns {ResponseData | FetchResponse} Response data\n */\n protected outputResponse(\n response: FetchResponse,\n requestConfig: RequestConfig,\n error = null,\n ): ResponseData | FetchResponse {\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n if (!response) {\n return defaultResponse;\n }\n\n if (\n (requestConfig.flattenResponse || this.flattenResponse) &&\n typeof response.data !== 'undefined'\n ) {\n // Special case of only data property within response data object (happens in Axios)\n // This is in fact a proper response but we may want to flatten it\n // To ease developers' lives when obtaining the response\n if (\n response.data !== null &&\n typeof response.data === 'object' &&\n typeof (response.data as any).data !== 'undefined' &&\n Object.keys(response.data).length === 1\n ) {\n return (response.data as any).data;\n }\n\n return response.data;\n }\n\n // If empty object is returned, ensure that the default response is used instead\n if (\n response !== null &&\n typeof response === 'object' &&\n response.constructor === Object &&\n Object.keys(response).length === 0\n ) {\n return defaultResponse;\n }\n\n const isCustomFetcher = this.isCustomFetcher();\n\n if (isCustomFetcher) {\n return response;\n }\n\n if (error !== null) {\n delete error?.response;\n delete error?.request;\n delete error?.config;\n }\n\n // Native fetch()\n return {\n body: response.body,\n blob: response.blob,\n json: response.json,\n text: response.text,\n clone: response.clone,\n bodyUsed: response.bodyUsed,\n arrayBuffer: response.arrayBuffer,\n formData: response.formData,\n ok: response.ok,\n redirected: response.redirected,\n type: response.type,\n url: response.url,\n status: response.status,\n statusText: response.statusText,\n\n // Extend with extra information\n error,\n data: response.data,\n headers: this.processHeaders(response),\n config: requestConfig,\n };\n }\n}\n","import { RequestHandler } from './request-handler';\nimport type {\n FetcherInstance,\n RequestConfig,\n FetchResponse,\n} from './types/request-handler';\nimport type {\n ApiHandlerConfig,\n ApiHandlerMethods,\n ApiHandlerReturnType,\n APIResponse,\n QueryParams,\n UrlPathParams,\n} from './types/api-handler';\n\n/**\n * Creates an instance of API Handler.\n * It creates an API fetcher function using native fetch() or Axios if it is passed as \"fetcher\".\n *\n * @param {Object} config - Configuration object for the API fetcher.\n * @param {string} config.apiUrl - The base URL for the API.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n * @returns API handler functions and endpoints to call\n *\n * @example\n * // Import axios (optional)\n * import axios from 'axios';\n *\n * // Define endpoint paths\n * const endpoints = {\n * getUser: '/user',\n * createPost: '/post',\n * };\n *\n * // Create the API fetcher with configuration\n * const api = createApiFetcher({\n * fetcher: axios, // Axios instance (optional)\n * endpoints,\n * apiUrl: 'https://example.com/api',\n * onError(error) {\n * console.log('Request failed', error);\n * },\n * headers: {\n * 'my-auth-key': 'example-auth-key-32rjjfa',\n * },\n * });\n *\n * // Fetch user data\n * const response = await api.getUser({ userId: 1, ratings: [1, 2] })\n */\nfunction createApiFetcher<\n EndpointsMethods extends object,\n EndpointsCfg = never,\n>(config: ApiHandlerConfig) {\n const endpoints = config.endpoints;\n const requestHandler = new RequestHandler(config);\n\n /**\n * Get Fetcher Provider Instance\n *\n * @returns {FetcherInstance} Request Handler's Fetcher instance\n */\n function getInstance(): FetcherInstance {\n return requestHandler.getInstance();\n }\n\n /**\n * Triggered when trying to use non-existent endpoints\n *\n * @param endpointName Endpoint Name\n * @returns {Promise}\n */\n function handleNonImplemented(endpointName: string): Promise {\n console.error(`${endpointName} endpoint must be added to 'endpoints'.`);\n\n return Promise.resolve(null);\n }\n\n /**\n * Handle Single API Request\n * It considers settings in following order: per-request settings, global per-endpoint settings, global settings.\n *\n * @param {string} endpointName - The name of the API endpoint to call.\n * @param {QueryParams} [queryParams={}] - Query parameters to include in the request.\n * @param {UrlPathParams} [urlPathParams={}] - URI parameters to include in the request.\n * @param {EndpointConfig} [requestConfig={}] - Additional configuration for the request.\n * @returns {Promise} - A promise that resolves with the response from the API provider.\n */\n async function request(\n endpointName: keyof EndpointsMethods | string,\n queryParams: QueryParams = {},\n urlPathParams: UrlPathParams = {},\n requestConfig: RequestConfig = {},\n ): Promise> {\n // Use global per-endpoint settings\n const endpointConfig = endpoints[endpointName as string];\n const endpointSettings = { ...endpointConfig };\n\n const responseData = await requestHandler.request(\n endpointSettings.url,\n queryParams,\n {\n ...endpointSettings,\n ...requestConfig,\n urlPathParams,\n },\n );\n\n return responseData;\n }\n\n /**\n * Maps all API requests using native Proxy\n *\n * @param {*} prop Caller\n */\n function get(prop: string | symbol) {\n if (prop in apiHandler) {\n return apiHandler[prop];\n }\n\n // Prevent handler from triggering non-existent endpoints\n if (!endpoints[prop as string]) {\n return handleNonImplemented.bind(null, prop);\n }\n\n return apiHandler.request.bind(null, prop);\n }\n\n const apiHandler: ApiHandlerMethods = {\n config,\n endpoints,\n requestHandler,\n getInstance,\n request,\n };\n\n return new Proxy(apiHandler, {\n get: (_target, prop) => get(prop),\n }) as ApiHandlerReturnType;\n}\n\nexport { createApiFetcher };\n","import { RequestHandler } from './request-handler';\nimport type { APIResponse, FetchResponse, RequestHandlerConfig } from './types';\n\n/**\n * Simple wrapper for request fetching.\n * It abstracts the creation of RequestHandler, making it easy to perform API requests.\n *\n * @param {string | URL | globalThis.Request} url - Request URL.\n * @param {RequestHandlerConfig} config - Configuration object for the request handler.\n * @returns {Promise>} Response Data.\n */\nexport async function fetchf(\n url: string,\n config: RequestHandlerConfig = {},\n): Promise> {\n return new RequestHandler(config).request(\n url,\n config.body || config.data || config.params,\n config,\n );\n}\n\nexport * from './types';\nexport * from './api-handler';\n"],"mappings":"AAYA,eAAsBA,EACpBC,EACAC,EAC+B,CAC/B,GAAI,CAACA,EACH,OAAOD,EAGT,IAAME,EAAkB,MAAM,QAAQD,CAAY,EAC9CA,EACA,CAACA,CAAY,EAEbE,EAAoB,CAAE,GAAGH,CAAO,EAEpC,QAAWI,KAAeF,EACxBC,EAAoB,MAAMC,EAAYD,CAAiB,EAGzD,OAAOA,CACT,CAQA,eAAsBE,EACpBC,EACAL,EACsC,CACtC,GAAI,CAACA,EACH,OAAOK,EAGT,IAAMJ,EAAkB,MAAM,QAAQD,CAAY,EAC9CA,EACA,CAACA,CAAY,EAEbM,EAAsBD,EAE1B,QAAWF,KAAeF,EACxBK,EAAsB,MAAMH,EAAYG,CAAmB,EAG7D,OAAOA,CACT,CCxDO,IAAMC,EAAN,cAA0B,KAAM,CACrC,SACA,QACA,OACA,OACA,WAEA,YACEC,EACAC,EACAC,EACA,CACA,MAAMF,CAAO,EAEb,KAAK,KAAO,gBACZ,KAAK,QAAUA,EACf,KAAK,OAASE,EAAS,OACvB,KAAK,WAAaA,EAAS,WAC3B,KAAK,QAAUD,EACf,KAAK,OAASA,EACd,KAAK,SAAWC,CAClB,CACF,ECdO,SAASC,EAAkBC,EAAaC,EAA6B,CAC1E,GAAI,CAACA,EACH,OAAOD,EAIT,IAAM,EAAI,CAAC,EACLE,EAAM,SAAUC,EAAWC,EAAQ,CACvCA,EAAI,OAAOA,GAAM,WAAaA,EAAE,EAAIA,EACpCA,EAAIA,IAAM,MAAYA,IAAM,OAAX,GAA4BA,EAC7C,EAAE,EAAE,MAAM,EAAI,mBAAmBD,CAAC,EAAI,IAAM,mBAAmBC,CAAC,CAClE,EAEMC,EAAc,CAACC,EAAgBC,IAAa,CAChD,IAAI,EAAWC,EAAaC,EAE5B,GAAIH,EACF,GAAI,MAAM,QAAQC,CAAG,EACnB,IAAK,EAAI,EAAGC,EAAMD,EAAI,OAAQ,EAAIC,EAAK,IACrCH,EACEC,EACE,KACC,OAAOC,EAAI,CAAC,GAAM,UAAYA,EAAI,CAAC,EAAI,EAAI,IAC5C,IACFA,EAAI,CAAC,CACP,UAEO,OAAOA,GAAQ,UAAYA,IAAQ,KAC5C,IAAKE,KAAOF,EACVF,EAAYC,EAAS,IAAMG,EAAM,IAAKF,EAAIE,CAAG,CAAC,OAGhDP,EAAII,EAAQC,CAAG,UAER,MAAM,QAAQA,CAAG,EAC1B,IAAK,EAAI,EAAGC,EAAMD,EAAI,OAAQ,EAAIC,EAAK,IACrCN,EAAIK,EAAI,CAAC,EAAE,KAAMA,EAAI,CAAC,EAAE,KAAK,MAG/B,KAAKE,KAAOF,EACVF,EAAYI,EAAKF,EAAIE,CAAG,CAAC,EAG7B,OAAO,CACT,EAKMC,EAHmBL,EAAY,GAAIJ,CAAM,EAAE,KAAK,GAAG,EAGb,QAAQ,UAAW,IAAI,EAEnE,OAAOD,EAAI,SAAS,GAAG,EACnB,GAAGA,CAAG,IAAIU,CAAkB,GAC5BA,EACE,GAAGV,CAAG,IAAIU,CAAkB,GAC5BV,CACR,CAWO,SAASW,EACdX,EACAY,EACQ,CACR,OAAKA,EAIEZ,EAAI,QAAQ,eAAiBa,GAAgB,CAClD,IAAMC,EAAOD,EAAI,UAAU,CAAC,EAE5B,OAAO,OAAOD,EAAcE,CAAI,EAAIF,EAAcE,CAAI,EAAID,CAAG,CAC/D,CAAC,EAPQb,CAQX,CAcO,SAASe,EAAmBC,EAAqB,CACtD,GAA2BA,GAAU,KACnC,MAAO,GAGT,IAAMC,EAAI,OAAOD,EACjB,GAAIC,IAAM,UAAYA,IAAM,UAAYA,IAAM,UAC5C,MAAO,GAGT,GAAIA,IAAM,SACR,MAAO,GAGT,GAAI,MAAM,QAAQD,CAAK,EACrB,MAAO,GAOT,GAJI,OAAO,SAASA,CAAK,GAIrBA,aAAiB,KACnB,MAAO,GAGT,IAAME,EAAQ,OAAO,eAAeF,CAAK,EAQzC,OALIE,IAAU,OAAO,WAAaA,IAAU,MAKxC,OAAOF,EAAM,QAAW,UAK9B,CAEA,eAAsBG,EAAgBC,EAA8B,CAClE,OAAO,IAAI,QAASC,GAClB,WAAW,IACFA,EAAQ,EAAI,EAClBD,CAAE,CACP,CACF,CCjIA,IAAME,EAAmB,mBAOZC,EAAN,KAAqB,CAInB,gBAKA,QAAkB,GAKlB,QAAkB,IAKlB,YAAuB,GAKvB,gBAA2B,GAK3B,SAAkC,SAKlC,OAA0B,MAK1B,gBAA2B,GAK3B,gBAAuB,KAKpB,QAKA,OAKA,QAKA,cAKA,MAAsB,CAC9B,QAAS,EACT,MAAO,IACP,SAAU,IACV,QAAS,IAGT,QAAS,CACP,IACA,IACA,IACA,IACA,IACA,IACA,IACA,GACF,EAEA,YAAa,SAAY,EAC3B,EAKO,OA6BA,YAAY,CACjB,QAAAC,EAAU,KACV,QAAAC,EAAU,KACV,gBAAAC,EAAkB,GAClB,SAAAC,EAAW,KACX,gBAAAC,EAAkB,KAClB,gBAAAC,EAAkB,CAAC,EACnB,OAAAC,EAAS,KACT,QAAAC,EAAU,KACV,GAAGC,CACL,EAAyB,CACvB,KAAK,QAAUR,EACf,KAAK,QACHC,GAAsD,KAAK,QAC7D,KAAK,SAAWE,GAAY,KAAK,SACjC,KAAK,YAAcK,EAAO,aAAe,KAAK,YAC9C,KAAK,gBAAkBN,GAAmB,KAAK,gBAC/C,KAAK,gBAAkBE,GAAmB,KAAK,gBAC/C,KAAK,gBAAkBC,EACvB,KAAK,OAASC,IAAW,WAAa,WAAW,QAAU,OAAS,KACpE,KAAK,QAAUC,EACf,KAAK,cAAgB,IAAI,QACzB,KAAK,QAAUC,EAAO,SAAWA,EAAO,QAAU,GAClD,KAAK,OAASA,EAAO,QAAU,KAAK,OACpC,KAAK,OAASA,EACd,KAAK,MAAQ,CACX,GAAG,KAAK,MACR,GAAIA,EAAO,OAAS,CAAC,CACvB,EAEA,KAAK,gBAAkB,KAAK,gBAAgB,EACvCR,EAAgB,OAAO,CACtB,GAAGQ,EACH,QAAS,KAAK,QACd,QAAS,KAAK,OAChB,CAAC,EACD,IACN,CAOO,aAA+B,CACpC,OAAO,KAAK,eACd,CAUU,YACRC,EACAC,EACAF,EACe,CACf,IAAMG,EAASH,EAAO,QAAU,KAAK,OAC/BI,EAAkBD,EAAO,YAAY,EACrCE,EACJD,IAAoB,OAASA,IAAoB,OAE7CE,EAAaC,EACjBN,EACAD,EAAO,eAAiB,KAAK,OAAO,aACtC,EAGMQ,EACJR,EAAO,MAAQA,EAAO,MAAQ,KAAK,OAAO,MAAQ,KAAK,OAAO,KAGhE,GAAI,KAAK,gBAAgB,EACvB,MAAO,CACL,GAAGA,EACH,IAAKM,EACL,OAAQF,EAER,GAAIC,EAAmB,CAAE,OAAQH,CAAK,EAAI,CAAC,EAI3C,GAAI,CAACG,GAAoBH,GAAQM,EAAa,CAAE,OAAQN,CAAK,EAAI,CAAC,EAGlE,GAAI,CAACG,GAAoBH,GAAQ,CAACM,EAAa,CAAE,KAAAN,CAAK,EAAI,CAAC,EAC3D,GAAI,CAACG,GAAoBG,EAAa,CAAE,KAAMA,CAAW,EAAI,CAAC,CAChE,EAIF,IAAMC,EAAUD,GAAcN,EACxBQ,EACJV,EAAO,iBAAmB,KAAK,OAAO,gBAClC,UACAA,EAAO,YAEb,OAAOA,EAAO,KACd,OAAOA,EAAO,gBAEd,IAAMW,EACH,CAACN,GAAoBH,GAAQ,CAACF,EAAO,MAAS,CAACE,EAC5CI,EACAM,EAAkBN,EAAYJ,CAAI,EAElCW,EADYF,EAAQ,SAAS,KAAK,EAEpC,GACA,OAAOX,EAAO,QAAY,IACxBA,EAAO,QACP,KAAK,QAEX,MAAO,CACL,GAAGA,EACH,YAAAU,EAIA,IAAKG,EAAUF,EAGf,OAAQR,EAAO,YAAY,EAG3B,QAAS,CACP,OAAQb,EAAmB,oBAC3B,eAAgBA,EAAmB,iBACnC,GAAIU,EAAO,SAAW,KAAK,OAAO,SAAW,CAAC,CAChD,EAGA,GAAKK,EAUD,CAAC,EATD,CACE,KACE,EAAEI,aAAmB,kBACrBK,EAAmBL,CAAO,EACtB,OAAOA,GAAY,SACjBA,EACA,KAAK,UAAUA,CAAO,EACxBA,CACR,CAEN,CACF,CASU,aACRM,EACAC,EACM,CArTV,IAAAC,EAsTQ,KAAK,mBAAmBF,CAAK,KAI7BE,EAAA,KAAK,SAAL,MAAAA,EAAa,MACf,KAAK,OAAO,KAAK,YAAaF,CAAK,EAIjCC,EAAc,SAAW,OAAOA,EAAc,SAAY,YAC5DA,EAAc,QAAQD,CAAK,EAIzB,KAAK,SAAW,OAAO,KAAK,SAAY,YAC1C,KAAK,QAAQA,CAAK,EAEtB,CASA,MAAgB,oBACdA,EACAC,EACc,CACd,IAAME,EAAqB,KAAK,mBAAmBH,CAAK,EAClDI,EAAwBH,EAAc,UAAY,KAAK,SACvDtB,EACJ,OAAOsB,EAAc,gBAAoB,IACrCA,EAAc,gBACd,KAAK,gBACLnB,EACJ,OAAOmB,EAAc,gBAAoB,IACrCA,EAAc,gBACd,KAAK,gBAGX,OAAIG,IAA0B,WACrB,KAAK,eAAeJ,EAAM,SAAUC,EAAeD,CAAK,EAI7DG,GAAsB,CAACxB,EAClBG,EAILsB,IAA0B,UAC5B,MAAM,IAAI,QAAQ,IAAM,IAAI,EAErBtB,GAILsB,IAA0B,SACrB,QAAQ,OAAOJ,CAAK,EAGtBlB,CACT,CAQO,mBAAmBkB,EAA+B,CACvD,OAAOA,EAAM,OAAS,cAAgBA,EAAM,OAAS,eACvD,CAOU,iBAA2B,CACnC,OAAO,KAAK,UAAY,IAC1B,CAQU,qBACRC,EACwC,CAExC,GAAI,CAAC,KAAK,aAAe,CAACA,EAAc,YACtC,MAAO,CAAC,EAIV,GACE,OAAOA,EAAc,YAAgB,KACrC,CAACA,EAAc,YAEf,MAAO,CAAC,EAIV,GAAI,OAAO,gBAAoB,IAC7B,eAAQ,MAAM,iCAAiC,EAExC,CAAC,EAIV,IAAMI,EAAkB,KAAK,cAAc,IAAIJ,CAAa,EAExDI,GACFA,EAAgB,MAAM,EAGxB,IAAMC,EAAa,IAAI,gBAGvB,GAAI,CAAC,KAAK,gBAAgB,GAAK,KAAK,QAAU,EAAG,CAC/C,IAAMC,EAAe,WAAW,IAAM,CACpC,IAAMP,EAAQ,IAAI,MAChB,uBAAuBC,EAAc,GAAG,qCAC1C,EAEA,MAAAD,EAAM,KAAO,eACZA,EAAc,KAAO,GACtBM,EAAW,MAAMN,CAAK,EACtB,aAAaO,CAAY,EACnBP,CACR,EAAGC,EAAc,SAAW,KAAK,OAAO,CAC1C,CAEA,YAAK,cAAc,IAAIA,EAAeK,CAAU,EAEzC,CACL,OAAQA,EAAW,MACrB,CACF,CAYA,MAAa,QACXpB,EACAC,EAA0B,KAC1BF,EAAwB,KAC6B,CArdzD,IAAAiB,EAAAM,EAsdI,IAAIC,EAAwC,KACtCC,EAAUzB,GAAU,CAAC,EACrB0B,EAAiB,KAAK,YAAYzB,EAAKC,EAAMuB,CAAO,EAEtDT,EAA+B,CACjC,GAAG,KAAK,qBAAqBU,CAAc,EAC3C,GAAGA,CACL,EAEM,CAAE,QAAAC,EAAS,MAAAC,EAAO,QAAAC,EAAS,QAAAC,EAAS,YAAAC,EAAa,SAAAC,CAAS,EAAI,CAClE,GAAG,KAAK,MACR,IAAIhB,GAAA,YAAAA,EAAe,QAAS,CAAC,CAC/B,EAEIiB,EAAU,EACVC,EAAWN,EAEf,KAAOK,GAAWN,GAChB,GAAI,CAcF,GAZAX,EAAgB,MAAMmB,EACpBnB,EACAA,EAAc,SAChB,EAGAA,EAAgB,MAAMmB,EACpBnB,EACA,KAAK,OAAO,SACd,EAGI,KAAK,gBAAgB,EACvBQ,EAAY,MAAO,KAAK,gBAAwB,QAC9CR,CACF,UAEAQ,EAAY,MAAM,WAAW,MAC3BR,EAAc,IACdA,CACF,EAGAQ,EAAS,OAASR,EAClBQ,EAAS,KAAO,MAAM,KAAK,UAAUA,CAAQ,EAGzC,CAACA,EAAS,GACZ,MAAM,IAAIY,EACR,GAAGpB,EAAc,GAAG,oBAAoBQ,EAAS,QAAU,IAAI,GAC/DR,EACAQ,CACF,EAKJ,OAAAA,EAAW,MAAMa,EAAkBb,EAAUR,EAAc,UAAU,EAGrEQ,EAAW,MAAMa,EAAkBb,EAAU,KAAK,OAAO,UAAU,EAE5D,KAAK,eAAeA,EAAUR,CAAa,CAEpD,OAASD,EAAO,CACd,GACEkB,IAAYN,GACZ,CAAE,MAAMI,EAAYhB,EAAOkB,CAAO,GAClC,EAACH,GAAA,MAAAA,EAAS,WAASb,EAAAF,GAAA,YAAAA,EAAO,WAAP,YAAAE,EAAiB,UAAUF,GAAA,YAAAA,EAAO,UAErD,YAAK,aAAaA,EAAOC,CAAa,EAE/B,KAAK,oBAAoBD,EAAOC,CAAa,GAGlDO,EAAA,KAAK,SAAL,MAAAA,EAAa,MACf,KAAK,OAAO,KACV,WAAWU,EAAU,CAAC,wBAAwBC,CAAQ,OACxD,EAGF,MAAMI,EAAgBJ,CAAQ,EAE9BA,GAAYL,EACZK,EAAW,KAAK,IAAIA,EAAUF,CAAQ,EACtCC,GACF,CAGF,OAAO,KAAK,eAAeT,EAAUR,CAAa,CAEpD,CAQA,MAAa,UACXQ,EACc,CA3jBlB,IAAAP,EA4jBI,IAAMsB,EAAc,SACjBtB,EAAAO,EAAsB,UAAtB,YAAAP,EAA+B,IAAI,kBAAmB,EACzD,EACIf,EAGJ,GAAI,CAACqC,EAAa,CAChB,IAAMC,EAAgBhB,EAAS,MAAM,EACrC,GAAI,CACFtB,EAAO,MAAMsC,EAAc,KAAK,CAElC,MAAiB,CAEftC,EAAO,IACT,CACF,CAEA,GAAI,OAAOA,EAAS,IAClB,GAAI,CAEAqC,EAAY,SAASjD,CAAgB,GACrCiD,EAAY,SAAS,OAAO,EAE5BrC,EAAO,MAAMsB,EAAS,KAAK,EAClBe,EAAY,SAAS,qBAAqB,EACnDrC,EAAO,MAAMsB,EAAS,SAAS,EACtBe,EAAY,SAAS,0BAA0B,EACxDrC,EAAO,MAAMsB,EAAS,KAAK,EAClBe,EAAY,SAAS,mCAAmC,EACjErC,EAAO,MAAMsB,EAAS,SAAS,EACtB,OAAOA,EAAS,MAAS,WAClCtB,EAAO,MAAMsB,EAAS,KAAK,EAG3BtB,EAAOsB,EAAS,MAAQA,EAAS,MAAQ,IAG7C,MAAiB,CAEftB,EAAO,IACT,CAGF,OAAOA,CACT,CAEO,eACLsB,EACe,CACf,GAAI,CAACA,EAAS,QACZ,MAAO,CAAC,EAGV,IAAIiB,EAA+B,CAAC,EAC9BC,EAAUlB,EAAS,QAGzB,GAAIkB,aAAmB,QACrB,OAAW,CAACC,EAAKC,CAAK,IAAMF,EAAgB,QAAQ,EAClDD,EAAcE,CAAG,EAAIC,OAIvBH,EAAgB,CAAE,GAAIC,CAA0B,EAGlD,OAAOD,CACT,CAUU,eACRjB,EACAR,EACAD,EAAQ,KACoC,CAC5C,IAAMlB,EACJ,OAAOmB,EAAc,gBAAoB,IACrCA,EAAc,gBACd,KAAK,gBAEX,OAAKQ,GAKFR,EAAc,iBAAmB,KAAK,kBACvC,OAAOQ,EAAS,KAAS,IAMvBA,EAAS,OAAS,MAClB,OAAOA,EAAS,MAAS,UACzB,OAAQA,EAAS,KAAa,KAAS,KACvC,OAAO,KAAKA,EAAS,IAAI,EAAE,SAAW,EAE9BA,EAAS,KAAa,KAGzBA,EAAS,KAKhBA,IAAa,MACb,OAAOA,GAAa,UACpBA,EAAS,cAAgB,QACzB,OAAO,KAAKA,CAAQ,EAAE,SAAW,EAE1B3B,EAGe,KAAK,gBAAgB,EAGpC2B,GAGLT,IAAU,OACZA,GAAA,aAAAA,EAAc,SACdA,GAAA,aAAAA,EAAc,QACdA,GAAA,aAAAA,EAAc,QAIT,CACL,KAAMS,EAAS,KACf,KAAMA,EAAS,KACf,KAAMA,EAAS,KACf,KAAMA,EAAS,KACf,MAAOA,EAAS,MAChB,SAAUA,EAAS,SACnB,YAAaA,EAAS,YACtB,SAAUA,EAAS,SACnB,GAAIA,EAAS,GACb,WAAYA,EAAS,WACrB,KAAMA,EAAS,KACf,IAAKA,EAAS,IACd,OAAQA,EAAS,OACjB,WAAYA,EAAS,WAGrB,MAAAT,EACA,KAAMS,EAAS,KACf,QAAS,KAAK,eAAeA,CAAQ,EACrC,OAAQR,CACV,GAlESnB,CAmEX,CACF,EClpBA,SAASgD,EAGPC,EAA4C,CAC5C,IAAMC,EAAYD,EAAO,UACnBE,EAAiB,IAAIC,EAAeH,CAAM,EAOhD,SAASI,GAA+B,CACtC,OAAOF,EAAe,YAAY,CACpC,CAQA,SAASG,EAAqBC,EAAqC,CACjE,eAAQ,MAAM,GAAGA,CAAY,yCAAyC,EAE/D,QAAQ,QAAQ,IAAI,CAC7B,CAYA,eAAeC,EACbD,EACAE,EAA2B,CAAC,EAC5BC,EAA+B,CAAC,EAChCC,EAA+B,CAAC,EACa,CAG7C,IAAMC,EAAmB,CAAE,GADJV,EAAUK,CAAsB,CACV,EAY7C,OAVqB,MAAMJ,EAAe,QACxCS,EAAiB,IACjBH,EACA,CACE,GAAGG,EACH,GAAGD,EACH,cAAAD,CACF,CACF,CAGF,CAOA,SAASG,EAAIC,EAAuB,CAClC,OAAIA,KAAQC,EACHA,EAAWD,CAAI,EAInBZ,EAAUY,CAAc,EAItBC,EAAW,QAAQ,KAAK,KAAMD,CAAI,EAHhCR,EAAqB,KAAK,KAAMQ,CAAI,CAI/C,CAEA,IAAMC,EAAkD,CACtD,OAAAd,EACA,UAAAC,EACA,eAAAC,EACA,YAAAE,EACA,QAAAG,CACF,EAEA,OAAO,IAAI,MAAMO,EAAY,CAC3B,IAAK,CAACC,EAASF,IAASD,EAAIC,CAAI,CAClC,CAAC,CACH,CCrJA,eAAsBG,EACpBC,EACAC,EAA+B,CAAC,EACqB,CACrD,OAAO,IAAIC,EAAeD,CAAM,EAAE,QAChCD,EACAC,EAAO,MAAQA,EAAO,MAAQA,EAAO,OACrCA,CACF,CACF","names":["interceptRequest","config","interceptors","interceptorList","interceptedConfig","interceptor","interceptResponse","response","interceptedResponse","ResponseErr","message","requestInfo","response","appendQueryParams","url","params","add","k","v","buildParams","prefix","obj","len","key","encodedQueryString","replaceUrlPathParams","urlPathParams","str","word","isJSONSerializable","value","t","proto","delayInvocation","ms","resolve","APPLICATION_JSON","RequestHandler","fetcher","timeout","rejectCancelled","strategy","flattenResponse","defaultResponse","logger","onError","config","url","data","method","methodLowerCase","isGetAlikeMethod","dynamicUrl","replaceUrlPathParams","configData","payload","credentials","urlPath","appendQueryParams","baseURL","isJSONSerializable","error","requestConfig","_a","isRequestCancelled","errorHandlingStrategy","previousRequest","controller","abortTimeout","_b","response","_config","_requestConfig","retries","delay","backoff","retryOn","shouldRetry","maxDelay","attempt","waitTime","interceptRequest","ResponseErr","interceptResponse","delayInvocation","contentType","responseClone","headersObject","headers","key","value","createApiFetcher","config","endpoints","requestHandler","RequestHandler","getInstance","handleNonImplemented","endpointName","request","queryParams","urlPathParams","requestConfig","endpointSettings","get","prop","apiHandler","_target","fetchf","url","config","RequestHandler"]} \ No newline at end of file diff --git a/dist/node/index.js b/dist/node/index.js index f452582..9fd2be8 100644 --- a/dist/node/index.js +++ b/dist/node/index.js @@ -1,2 +1,2 @@ -var C=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var H=(a,e)=>{for(var t in e)C(a,t,{get:e[t],enumerable:!0})},U=(a,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of E(e))!I.call(a,n)&&n!==t&&C(a,n,{get:()=>e[n],enumerable:!(s=D(e,n))||s.enumerable});return a};var x=a=>U(C({},"__esModule",{value:!0}),a);var S={};H(S,{createApiFetcher:()=>T,fetchf:()=>O});module.exports=x(S);async function w(a,e){if(!e)return a;let t=Array.isArray(e)?e:[e],s={...a};for(let n of t)s=await n(s);return s}async function P(a,e){if(!e)return a;let t=Array.isArray(e)?e:[e],s=a;for(let n of t)s=await n(s);return s}var b=class extends Error{response;request;config;status;statusText;constructor(e,t,s){super(e),this.name="ResponseError",this.message=e,this.status=s.status,this.statusText=s.statusText,this.request=t,this.config=t,this.response=s}};var R=class{requestInstance;baseURL="";timeout=3e4;cancellable=!1;rejectCancelled=!1;strategy="reject";method="get";flattenResponse=!1;defaultResponse=null;fetcher;logger;onError;requestsQueue;retry={retries:0,delay:1e3,maxDelay:3e4,backoff:1.5,retryOn:[408,409,425,429,500,502,503,504],shouldRetry:async()=>!0};config;constructor({fetcher:e=null,timeout:t=null,rejectCancelled:s=!1,strategy:n=null,flattenResponse:o=null,defaultResponse:u={},logger:r=null,onError:i=null,...l}){this.fetcher=e,this.timeout=t??this.timeout,this.strategy=n||this.strategy,this.cancellable=l.cancellable||this.cancellable,this.rejectCancelled=s||this.rejectCancelled,this.flattenResponse=o||this.flattenResponse,this.defaultResponse=u,this.logger=r||(globalThis?globalThis.console:null)||null,this.onError=i,this.requestsQueue=new WeakMap,this.baseURL=l.baseURL||l.apiUrl||"",this.method=l.method||this.method,this.config=l,this.retry={...this.retry,...l.retry||{}},this.requestInstance=this.isCustomFetcher()?e.create({...l,baseURL:this.baseURL,timeout:this.timeout}):null}getInstance(){return this.requestInstance}replaceUrlPathParams(e,t){return t?e.replace(/:[a-zA-Z]+/gi,s=>{let n=s.substring(1);return String(t[n]?t[n]:s)}):e}appendQueryParams(e,t){if(!t)return e;let s=Object.entries(t).flatMap(([n,o])=>Array.isArray(o)?o.map(u=>`${encodeURIComponent(n)}[]=${encodeURIComponent(u)}`):`${encodeURIComponent(n)}=${encodeURIComponent(String(o))}`).join("&");return e.includes("?")?`${e}&${s}`:s?`${e}?${s}`:e}isJSONSerializable(e){if(e==null)return!1;let t=typeof e;if(t==="string"||t==="number"||t==="boolean")return!0;if(t!=="object")return!1;if(Array.isArray(e))return!0;if(Buffer.isBuffer(e)||e instanceof Date)return!1;let s=Object.getPrototypeOf(e);return s===Object.prototype||s===null||typeof e.toJSON=="function"}buildConfig(e,t,s){let n=s.method||this.method,o=n.toLowerCase(),u=o==="get"||o==="head",r=this.replaceUrlPathParams(e,s.urlPathParams||this.config.urlPathParams),i=s.body||s.data||this.config.body||this.config.data;if(this.isCustomFetcher())return{...s,url:r,method:o,...u?{params:t}:{},...!u&&t&&i?{params:t}:{},...!u&&t&&!i?{data:t}:{},...!u&&i?{data:i}:{}};let l=i||t,y=s.withCredentials||this.config.withCredentials?"include":s.credentials;delete s.data,delete s.withCredentials;let d=!u&&t&&!s.body||!t?r:this.appendQueryParams(r,t),h=d.includes("://")?"":typeof s.baseURL<"u"?s.baseURL:this.baseURL;return{...s,credentials:y,url:h+d,method:n.toUpperCase(),headers:{Accept:"application/json, text/plain, */*","Content-Type":"application/json;charset=utf-8",...s.headers||this.config.headers||{}},...u?{}:{body:!(l instanceof URLSearchParams)&&this.isJSONSerializable(l)?typeof l=="string"?l:JSON.stringify(l):l}}}processError(e,t){var s;this.isRequestCancelled(e)||((s=this.logger)!=null&&s.warn&&this.logger.warn("API ERROR",e),t.onError&&typeof t.onError=="function"&&t.onError(e),this.onError&&typeof this.onError=="function"&&this.onError(e))}async outputErrorResponse(e,t){let s=this.isRequestCancelled(e),n=t.strategy||this.strategy,o=typeof t.rejectCancelled<"u"?t.rejectCancelled:this.rejectCancelled,u=typeof t.defaultResponse<"u"?t.defaultResponse:this.defaultResponse;return n==="softFail"?this.outputResponse(e.response,t,e):s&&!o?u:n==="silent"?(await new Promise(()=>null),u):n==="reject"?Promise.reject(e):u}isRequestCancelled(e){return e.name==="AbortError"||e.name==="CanceledError"}isCustomFetcher(){return this.fetcher!==null}addCancellationToken(e){if(!this.cancellable&&!e.cancellable)return{};if(typeof e.cancellable<"u"&&!e.cancellable)return{};if(typeof AbortController>"u")return console.error("AbortController is unavailable."),{};let t=this.requestsQueue.get(e);t&&t.abort();let s=new AbortController;if(!this.isCustomFetcher()&&this.timeout>0){let n=setTimeout(()=>{let o=new Error(`[TimeoutError]: The ${e.url} request was aborted due to timeout`);throw o.name="TimeoutError",o.code=23,s.abort(o),clearTimeout(n),o},e.timeout||this.timeout)}return this.requestsQueue.set(e,s),{signal:s.signal}}async request(e,t=null,s=null){var q,F,j;let n=null,o=s||{},u=this.buildConfig(e,t,o),r={...this.addCancellationToken(u),...u},{retries:i,delay:l,backoff:y,retryOn:d,shouldRetry:g,maxDelay:h}={...this.retry,...(r==null?void 0:r.retry)||{}},f=0,m=l;for(;f<=i;)try{if(r=await w(r,r.onRequest),r=await w(r,this.config.onRequest),this.isCustomFetcher())n=await this.requestInstance.request(r);else{n=await globalThis.fetch(r.url,r);let c=String(((q=n==null?void 0:n.headers)==null?void 0:q.get("Content-Type"))||""),p,A=n.clone();if(!c)try{p=await A.json()}catch{}if(typeof p>"u"&&(c&&(c.includes("application/json")||c.includes("+json"))?p=await n.json():c.includes("multipart/form-data")?p=await n.formData():c.includes("application/octet-stream")?p=await n.blob():c.includes("application/x-www-form-urlencoded")?p=await n.formData():typeof n.text<"u"?p=await n.text():p=n.body||n.data||null),n.config=r,n.data=p,!n.ok)throw new b(`${r.url} failed! Status: ${n.status||null}`,r,n)}return n=await P(n,r.onResponse),n=await P(n,this.config.onResponse),this.outputResponse(n,r)}catch(c){if(f===i||!await g(c,f)||!(d!=null&&d.includes(((F=c==null?void 0:c.response)==null?void 0:F.status)||(c==null?void 0:c.status))))return this.processError(c,r),this.outputErrorResponse(c,r);(j=this.logger)!=null&&j.warn&&this.logger.warn(`Attempt ${f+1} failed. Retrying in ${m}ms...`),await this.delay(m),m*=y,m=Math.min(m,h),f++}return this.outputResponse(n,r)}async delay(e){return new Promise(t=>setTimeout(()=>t(!0),e))}processHeaders(e){if(!e.headers)return{};let t={};if(e.headers instanceof Headers)for(let[s,n]of e.headers.entries())t[s]=n;else t={...e.headers};return t}outputResponse(e,t,s=null){let n=typeof t.defaultResponse<"u"?t.defaultResponse:this.defaultResponse;return e?(t.flattenResponse||this.flattenResponse)&&typeof e.data<"u"?e.data!==null&&typeof e.data=="object"&&typeof e.data.data<"u"&&Object.keys(e.data).length===1?e.data.data:e.data:e!==null&&typeof e=="object"&&e.constructor===Object&&Object.keys(e).length===0?n:this.isCustomFetcher()?e:(s!==null&&(s==null||delete s.response,s==null||delete s.request,s==null||delete s.config),{body:e.body,blob:e.blob,json:e.json,text:e.text,clone:e.clone,bodyUsed:e.bodyUsed,arrayBuffer:e.arrayBuffer,formData:e.formData,ok:e.ok,redirected:e.redirected,type:e.type,url:e.url,status:e.status,statusText:e.statusText,error:s,data:e.data,headers:this.processHeaders(e),config:t}):n}};function T(a){let e=a.endpoints,t=new R(a);function s(){return t.getInstance()}function n(i){return console.error(`${i} endpoint must be added to 'endpoints'.`),Promise.resolve(null)}async function o(i,l={},y={},d={}){let h={...e[i]};return await t.request(h.url,l,{...h,...d,urlPathParams:y})}function u(i){return i in r?r[i]:e[i]?r.request.bind(null,i):n.bind(null,i)}let r={config:a,endpoints:e,requestHandler:t,getInstance:s,request:o};return new Proxy(r,{get:(i,l)=>u(l)})}async function O(a,e={}){return new R(e).request(a,e.body||e.data||e.params,e)}0&&(module.exports={createApiFetcher,fetchf}); +var g=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var H=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var T=(a,e)=>{for(var s in e)g(a,s,{get:e[s],enumerable:!0})},O=(a,e,s,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of H(e))!U.call(a,n)&&n!==s&&g(a,n,{get:()=>e[n],enumerable:!(t=x(e,n))||t.enumerable});return a};var j=a=>O(g({},"__esModule",{value:!0}),a);var L={};T(L,{createApiFetcher:()=>S,fetchf:()=>k});module.exports=j(L);async function C(a,e){if(!e)return a;let s=Array.isArray(e)?e:[e],t={...a};for(let n of s)t=await n(t);return t}async function P(a,e){if(!e)return a;let s=Array.isArray(e)?e:[e],t=a;for(let n of s)t=await n(t);return t}var m=class extends Error{response;request;config;status;statusText;constructor(e,s,t){super(e),this.name="ResponseError",this.message=e,this.status=t.status,this.statusText=t.statusText,this.request=s,this.config=s,this.response=t}};function A(a,e){if(!e)return a;let s=[],t=function(o,r){r=typeof r=="function"?r():r,r=r===null||r===void 0?"":r,s[s.length]=encodeURIComponent(o)+"="+encodeURIComponent(r)},n=(o,r)=>{let i,p,c;if(o)if(Array.isArray(r))for(i=0,p=r.length;i{let t=s.substring(1);return String(e[t]?e[t]:s)}):a}function I(a){if(a==null)return!1;let e=typeof a;if(e==="string"||e==="number"||e==="boolean")return!0;if(e!=="object")return!1;if(Array.isArray(a))return!0;if(Buffer.isBuffer(a)||a instanceof Date)return!1;let s=Object.getPrototypeOf(a);return s===Object.prototype||s===null||typeof a.toJSON=="function"}async function E(a){return new Promise(e=>setTimeout(()=>e(!0),a))}var w="application/json",R=class{requestInstance;baseURL="";timeout=3e4;cancellable=!1;rejectCancelled=!1;strategy="reject";method="get";flattenResponse=!1;defaultResponse=null;fetcher;logger;onError;requestsQueue;retry={retries:0,delay:1e3,maxDelay:3e4,backoff:1.5,retryOn:[408,409,425,429,500,502,503,504],shouldRetry:async()=>!0};config;constructor({fetcher:e=null,timeout:s=null,rejectCancelled:t=!1,strategy:n=null,flattenResponse:l=null,defaultResponse:u={},logger:o=null,onError:r=null,...i}){this.fetcher=e,this.timeout=s??this.timeout,this.strategy=n||this.strategy,this.cancellable=i.cancellable||this.cancellable,this.rejectCancelled=t||this.rejectCancelled,this.flattenResponse=l||this.flattenResponse,this.defaultResponse=u,this.logger=o||(globalThis?globalThis.console:null)||null,this.onError=r,this.requestsQueue=new WeakMap,this.baseURL=i.baseURL||i.apiUrl||"",this.method=i.method||this.method,this.config=i,this.retry={...this.retry,...i.retry||{}},this.requestInstance=this.isCustomFetcher()?e.create({...i,baseURL:this.baseURL,timeout:this.timeout}):null}getInstance(){return this.requestInstance}buildConfig(e,s,t){let n=t.method||this.method,l=n.toLowerCase(),u=l==="get"||l==="head",o=D(e,t.urlPathParams||this.config.urlPathParams),r=t.body||t.data||this.config.body||this.config.data;if(this.isCustomFetcher())return{...t,url:o,method:l,...u?{params:s}:{},...!u&&s&&r?{params:s}:{},...!u&&s&&!r?{data:s}:{},...!u&&r?{data:r}:{}};let i=r||s,p=t.withCredentials||this.config.withCredentials?"include":t.credentials;delete t.data,delete t.withCredentials;let c=!u&&s&&!t.body||!s?o:A(o,s),f=c.includes("://")?"":typeof t.baseURL<"u"?t.baseURL:this.baseURL;return{...t,credentials:p,url:f+c,method:n.toUpperCase(),headers:{Accept:w+", text/plain, */*","Content-Type":w+";charset=utf-8",...t.headers||this.config.headers||{}},...u?{}:{body:!(i instanceof URLSearchParams)&&I(i)?typeof i=="string"?i:JSON.stringify(i):i}}}processError(e,s){var t;this.isRequestCancelled(e)||((t=this.logger)!=null&&t.warn&&this.logger.warn("API ERROR",e),s.onError&&typeof s.onError=="function"&&s.onError(e),this.onError&&typeof this.onError=="function"&&this.onError(e))}async outputErrorResponse(e,s){let t=this.isRequestCancelled(e),n=s.strategy||this.strategy,l=typeof s.rejectCancelled<"u"?s.rejectCancelled:this.rejectCancelled,u=typeof s.defaultResponse<"u"?s.defaultResponse:this.defaultResponse;return n==="softFail"?this.outputResponse(e.response,s,e):t&&!l?u:n==="silent"?(await new Promise(()=>null),u):n==="reject"?Promise.reject(e):u}isRequestCancelled(e){return e.name==="AbortError"||e.name==="CanceledError"}isCustomFetcher(){return this.fetcher!==null}addCancellationToken(e){if(!this.cancellable&&!e.cancellable)return{};if(typeof e.cancellable<"u"&&!e.cancellable)return{};if(typeof AbortController>"u")return console.error("AbortController is unavailable."),{};let s=this.requestsQueue.get(e);s&&s.abort();let t=new AbortController;if(!this.isCustomFetcher()&&this.timeout>0){let n=setTimeout(()=>{let l=new Error(`[TimeoutError]: The ${e.url} request was aborted due to timeout`);throw l.name="TimeoutError",l.code=23,t.abort(l),clearTimeout(n),l},e.timeout||this.timeout)}return this.requestsQueue.set(e,t),{signal:t.signal}}async request(e,s=null,t=null){var q,F;let n=null,l=t||{},u=this.buildConfig(e,s,l),o={...this.addCancellationToken(u),...u},{retries:r,delay:i,backoff:p,retryOn:c,shouldRetry:b,maxDelay:f}={...this.retry,...(o==null?void 0:o.retry)||{}},h=0,y=i;for(;h<=r;)try{if(o=await C(o,o.onRequest),o=await C(o,this.config.onRequest),this.isCustomFetcher())n=await this.requestInstance.request(o);else if(n=await globalThis.fetch(o.url,o),n.config=o,n.data=await this.parseData(n),!n.ok)throw new m(`${o.url} failed! Status: ${n.status||null}`,o,n);return n=await P(n,o.onResponse),n=await P(n,this.config.onResponse),this.outputResponse(n,o)}catch(d){if(h===r||!await b(d,h)||!(c!=null&&c.includes(((q=d==null?void 0:d.response)==null?void 0:q.status)||(d==null?void 0:d.status))))return this.processError(d,o),this.outputErrorResponse(d,o);(F=this.logger)!=null&&F.warn&&this.logger.warn(`Attempt ${h+1} failed. Retrying in ${y}ms...`),await E(y),y*=p,y=Math.min(y,f),h++}return this.outputResponse(n,o)}async parseData(e){var n;let s=String(((n=e.headers)==null?void 0:n.get("Content-Type"))||""),t;if(!s){let l=e.clone();try{t=await l.json()}catch{t=null}}if(typeof t>"u")try{s.includes(w)||s.includes("+json")?t=await e.json():s.includes("multipart/form-data")?t=await e.formData():s.includes("application/octet-stream")?t=await e.blob():s.includes("application/x-www-form-urlencoded")?t=await e.formData():typeof e.text=="function"?t=await e.text():t=e.body||e.data||null}catch{t=null}return t}processHeaders(e){if(!e.headers)return{};let s={},t=e.headers;if(t instanceof Headers)for(let[n,l]of t.entries())s[n]=l;else s={...t};return s}outputResponse(e,s,t=null){let n=typeof s.defaultResponse<"u"?s.defaultResponse:this.defaultResponse;return e?(s.flattenResponse||this.flattenResponse)&&typeof e.data<"u"?e.data!==null&&typeof e.data=="object"&&typeof e.data.data<"u"&&Object.keys(e.data).length===1?e.data.data:e.data:e!==null&&typeof e=="object"&&e.constructor===Object&&Object.keys(e).length===0?n:this.isCustomFetcher()?e:(t!==null&&(t==null||delete t.response,t==null||delete t.request,t==null||delete t.config),{body:e.body,blob:e.blob,json:e.json,text:e.text,clone:e.clone,bodyUsed:e.bodyUsed,arrayBuffer:e.arrayBuffer,formData:e.formData,ok:e.ok,redirected:e.redirected,type:e.type,url:e.url,status:e.status,statusText:e.statusText,error:t,data:e.data,headers:this.processHeaders(e),config:s}):n}};function S(a){let e=a.endpoints,s=new R(a);function t(){return s.getInstance()}function n(r){return console.error(`${r} endpoint must be added to 'endpoints'.`),Promise.resolve(null)}async function l(r,i={},p={},c={}){let f={...e[r]};return await s.request(f.url,i,{...f,...c,urlPathParams:p})}function u(r){return r in o?o[r]:e[r]?o.request.bind(null,r):n.bind(null,r)}let o={config:a,endpoints:e,requestHandler:s,getInstance:t,request:l};return new Proxy(o,{get:(r,i)=>u(i)})}async function k(a,e={}){return new R(e).request(a,e.body||e.data||e.params,e)}0&&(module.exports={createApiFetcher,fetchf}); //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/node/index.js.map b/dist/node/index.js.map index 7709464..45826ef 100644 --- a/dist/node/index.js.map +++ b/dist/node/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/index.ts","../src/interceptor-manager.ts","../src/response-error.ts","../src/request-handler.ts","../src/api-handler.ts"],"sourcesContent":["import { RequestHandler } from './request-handler';\nimport type { APIResponse, FetchResponse, RequestHandlerConfig } from './types';\n\n/**\n * Simple wrapper for request fetching.\n * It abstracts the creation of RequestHandler, making it easy to perform API requests.\n *\n * @param {string | URL | globalThis.Request} url - Request URL.\n * @param {RequestHandlerConfig} config - Configuration object for the request handler.\n * @returns {Promise>} Response Data.\n */\nexport async function fetchf(\n url: string,\n config: RequestHandlerConfig = {},\n): Promise> {\n return new RequestHandler(config).request(\n url,\n config.body || config.data || config.params,\n config,\n );\n}\n\nexport * from './types';\nexport * from './api-handler';\n","import type { RequestHandlerConfig, FetchResponse } from './types';\nimport type {\n RequestInterceptor,\n ResponseInterceptor,\n} from './types/interceptor-manager';\n\n/**\n * Applies a series of request interceptors to the provided configuration.\n * @param {RequestHandlerConfig} config - The initial request configuration.\n * @param {RequestInterceptor | RequestInterceptor[]} interceptors - The request interceptor function(s) to apply.\n * @returns {Promise} - The modified request configuration.\n */\nexport async function interceptRequest(\n config: RequestHandlerConfig,\n interceptors: RequestInterceptor | RequestInterceptor[],\n): Promise {\n if (!interceptors) {\n return config;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedConfig = { ...config };\n\n for (const interceptor of interceptorList) {\n interceptedConfig = await interceptor(interceptedConfig);\n }\n\n return interceptedConfig;\n}\n\n/**\n * Applies a series of response interceptors to the provided response.\n * @param {FetchResponse} response - The initial response object.\n * @param {ResponseInterceptor | ResponseInterceptor[]} interceptors - The response interceptor function(s) to apply.\n * @returns {Promise>} - The modified response object.\n */\nexport async function interceptResponse(\n response: FetchResponse,\n interceptors: ResponseInterceptor | ResponseInterceptor[],\n): Promise> {\n if (!interceptors) {\n return response;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedResponse = response;\n\n for (const interceptor of interceptorList) {\n interceptedResponse = await interceptor(interceptedResponse);\n }\n\n return interceptedResponse;\n}\n","import type { FetchResponse, RequestConfig } from './types';\n\nexport class ResponseErr extends Error {\n response: FetchResponse;\n request: RequestConfig;\n config: RequestConfig;\n status: number;\n statusText: string;\n\n constructor(\n message: string,\n requestInfo: RequestConfig,\n response: FetchResponse,\n ) {\n super(message);\n\n this.name = 'ResponseError';\n this.message = message;\n this.status = response.status;\n this.statusText = response.statusText;\n this.request = requestInfo;\n this.config = requestInfo;\n this.response = response;\n }\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {\n ErrorHandlingStrategy,\n RequestHandlerConfig,\n RequestConfig,\n FetcherInstance,\n Method,\n RetryOptions,\n FetchResponse,\n ResponseError,\n HeadersObject,\n} from './types/request-handler';\nimport type {\n APIResponse,\n QueryParams,\n QueryParamsOrBody,\n UrlPathParams,\n} from './types/api-handler';\nimport { interceptRequest, interceptResponse } from './interceptor-manager';\nimport { ResponseErr } from './response-error';\n\n/**\n * Generic Request Handler\n * It creates an Request Fetcher instance and handles requests within that instance\n * It handles errors depending on a chosen error handling strategy\n */\nexport class RequestHandler {\n /**\n * @var requestInstance Provider's instance\n */\n public requestInstance: FetcherInstance;\n\n /**\n * @var baseURL Base API url\n */\n public baseURL: string = '';\n\n /**\n * @var timeout Request timeout\n */\n public timeout: number = 30000;\n\n /**\n * @var cancellable Response cancellation\n */\n public cancellable: boolean = false;\n\n /**\n * @var rejectCancelled Whether to reject cancelled requests or not\n */\n public rejectCancelled: boolean = false;\n\n /**\n * @var strategy Request timeout\n */\n public strategy: ErrorHandlingStrategy = 'reject';\n\n /**\n * @var method Request method\n */\n public method: Method | string = 'get';\n\n /**\n * @var flattenResponse Response flattening\n */\n public flattenResponse: boolean = false;\n\n /**\n * @var defaultResponse Response flattening\n */\n public defaultResponse: any = null;\n\n /**\n * @var fetcher Request Fetcher instance\n */\n protected fetcher: FetcherInstance;\n\n /**\n * @var logger Logger\n */\n protected logger: any;\n\n /**\n * @var onError HTTP error service\n */\n protected onError: any;\n\n /**\n * @var requestsQueue Queue of requests\n */\n protected requestsQueue: WeakMap;\n\n /**\n * Request Handler Config\n */\n protected retry: RetryOptions = {\n retries: 0,\n delay: 1000,\n maxDelay: 30000,\n backoff: 1.5,\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status\n retryOn: [\n 408, // Request Timeout\n 409, // Conflict\n 425, // Too Early\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n ],\n\n shouldRetry: async () => true,\n };\n\n /**\n * Request Handler Config\n */\n public config: RequestHandlerConfig;\n\n /**\n * Creates an instance of RequestHandler.\n *\n * @param {Object} config - Configuration object for the request.\n * @param {string} config.baseURL - The base URL for the request.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n */\n public constructor({\n fetcher = null,\n timeout = null,\n rejectCancelled = false,\n strategy = null,\n flattenResponse = null,\n defaultResponse = {},\n logger = null,\n onError = null,\n ...config\n }: RequestHandlerConfig) {\n this.fetcher = fetcher;\n this.timeout =\n timeout !== null && timeout !== undefined ? timeout : this.timeout;\n this.strategy = strategy || this.strategy;\n this.cancellable = config.cancellable || this.cancellable;\n this.rejectCancelled = rejectCancelled || this.rejectCancelled;\n this.flattenResponse = flattenResponse || this.flattenResponse;\n this.defaultResponse = defaultResponse;\n this.logger = logger || (globalThis ? globalThis.console : null) || null;\n this.onError = onError;\n this.requestsQueue = new WeakMap();\n this.baseURL = config.baseURL || config.apiUrl || '';\n this.method = config.method || this.method;\n this.config = config;\n this.retry = {\n ...this.retry,\n ...(config.retry || {}),\n };\n\n this.requestInstance = this.isCustomFetcher()\n ? (fetcher as any).create({\n ...config,\n baseURL: this.baseURL,\n timeout: this.timeout,\n })\n : null;\n }\n\n /**\n * Get Provider Instance\n *\n * @returns {FetcherInstance} Provider's instance\n */\n public getInstance(): FetcherInstance {\n return this.requestInstance;\n }\n\n /**\n * Replaces dynamic URI parameters in a URL string with values from the provided `urlPathParams` object.\n * Parameters in the URL are denoted by `:`, where `` is a key in `urlPathParams`.\n *\n * @param {string} url - The URL string containing placeholders in the format `:`.\n * @param {Object} urlPathParams - An object containing the parameter values to replace placeholders.\n * @param {string} urlPathParams.paramName - The value to replace the placeholder `:` in the URL.\n * @returns {string} - The URL string with placeholders replaced by corresponding values from `urlPathParams`.\n */\n public replaceUrlPathParams(\n url: string,\n urlPathParams: UrlPathParams,\n ): string {\n if (!urlPathParams) {\n return url;\n }\n\n return url.replace(/:[a-zA-Z]+/gi, (str): string => {\n const word = str.substring(1);\n\n return String(urlPathParams[word] ? urlPathParams[word] : str);\n });\n }\n\n /**\n * Appends query parameters to the given URL\n *\n * @param {string} url - The base URL to which query parameters will be appended.\n * @param {QueryParams} params - An instance of URLSearchParams containing the query parameters to append.\n * @returns {string} - The URL with the appended query parameters.\n */\n public appendQueryParams(url: string, params: QueryParams): string {\n if (!params) {\n return url;\n }\n\n // We don't use URLSearchParams here as we want to ensure that arrays are properly converted similarily to Axios\n // So { foo: [1, 2] } would become: foo[]=1&foo[]=2\n const queryString = Object.entries(params)\n .flatMap(([key, value]) => {\n if (Array.isArray(value)) {\n return value.map(\n (val) => `${encodeURIComponent(key)}[]=${encodeURIComponent(val)}`,\n );\n }\n return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`;\n })\n .join('&');\n\n return url.includes('?')\n ? `${url}&${queryString}`\n : queryString\n ? `${url}?${queryString}`\n : url;\n }\n\n /**\n * Checks if a value is JSON serializable.\n *\n * JSON serializable values include:\n * - Primitive types: string, number, boolean, null\n * - Arrays\n * - Plain objects (i.e., objects without special methods)\n * - Values with a `toJSON` method\n *\n * @param {any} value - The value to check for JSON serializability.\n * @returns {boolean} - Returns `true` if the value is JSON serializable, otherwise `false`.\n */\n protected isJSONSerializable(value: any): boolean {\n if (value === undefined || value === null) {\n return false;\n }\n\n const t = typeof value;\n if (t === 'string' || t === 'number' || t === 'boolean') {\n return true;\n }\n\n if (t !== 'object') {\n return false; // bigint, function, symbol, undefined\n }\n\n if (Array.isArray(value)) {\n return true;\n }\n\n if (Buffer.isBuffer(value)) {\n return false;\n }\n\n if (value instanceof Date) {\n return false;\n }\n\n const proto = Object.getPrototypeOf(value);\n\n // Check if the prototype is `Object.prototype` or `null` (plain object)\n if (proto === Object.prototype || proto === null) {\n return true;\n }\n\n // Check if the object has a toJSON method\n if (typeof value.toJSON === 'function') {\n return true;\n }\n\n return false;\n }\n\n /**\n * Build request configuration\n *\n * @param {string} url Request url\n * @param {QueryParamsOrBody} data Request data\n * @param {RequestConfig} config Request config\n * @returns {RequestConfig} Provider's instance\n */\n protected buildConfig(\n url: string,\n data: QueryParamsOrBody,\n config: RequestConfig,\n ): RequestConfig {\n const method = config.method || this.method;\n const methodLowerCase = method.toLowerCase();\n const isGetAlikeMethod =\n methodLowerCase === 'get' || methodLowerCase === 'head';\n\n const dynamicUrl = this.replaceUrlPathParams(\n url,\n config.urlPathParams || this.config.urlPathParams,\n );\n\n // Bonus: Specifying it here brings support for \"body\" in Axios\n const configData =\n config.body || config.data || this.config.body || this.config.data;\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n return {\n ...config,\n url: dynamicUrl,\n method: methodLowerCase,\n\n ...(isGetAlikeMethod ? { params: data } : {}),\n\n // For POST requests body payload is the first param for convenience (\"data\")\n // In edge cases we want to split so to treat it as query params, and use \"body\" coming from the config instead\n ...(!isGetAlikeMethod && data && configData ? { params: data } : {}),\n\n // Only applicable for request methods 'PUT', 'POST', 'DELETE', and 'PATCH'\n ...(!isGetAlikeMethod && data && !configData ? { data } : {}),\n ...(!isGetAlikeMethod && configData ? { data: configData } : {}),\n };\n }\n\n // Native fetch\n const payload = configData || data;\n const credentials =\n config.withCredentials || this.config.withCredentials\n ? 'include'\n : config.credentials;\n\n delete config.data;\n delete config.withCredentials;\n\n const urlPath =\n (!isGetAlikeMethod && data && !config.body) || !data\n ? dynamicUrl\n : this.appendQueryParams(dynamicUrl, data);\n const isFullUrl = urlPath.includes('://');\n const baseURL = isFullUrl\n ? ''\n : typeof config.baseURL !== 'undefined'\n ? config.baseURL\n : this.baseURL;\n\n return {\n ...config,\n credentials,\n\n // Native fetch generally requires query params to be appended in the URL\n // Do not append query params only if it's a POST-alike request with only \"data\" specified as it's treated as body payload\n url: baseURL + urlPath,\n\n // Uppercase method name\n method: method.toUpperCase(),\n\n // For convenience, add the same default headers as Axios does\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json;charset=utf-8',\n ...(config.headers || this.config.headers || {}),\n },\n\n // Automatically JSON stringify request bodies, if possible and when not dealing with strings\n ...(!isGetAlikeMethod\n ? {\n body:\n !(payload instanceof URLSearchParams) &&\n this.isJSONSerializable(payload)\n ? typeof payload === 'string'\n ? payload\n : JSON.stringify(payload)\n : payload,\n }\n : {}),\n };\n }\n\n /**\n * Process global Request Error\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {void}\n */\n protected processError(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): void {\n if (this.isRequestCancelled(error)) {\n return;\n }\n\n if (this.logger?.warn) {\n this.logger.warn('API ERROR', error);\n }\n\n // Invoke per request \"onError\" interceptor\n if (requestConfig.onError && typeof requestConfig.onError === 'function') {\n requestConfig.onError(error);\n }\n\n // Invoke global \"onError\" interceptor\n if (this.onError && typeof this.onError === 'function') {\n this.onError(error);\n }\n }\n\n /**\n * Output default response in case of an error, depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {*} Error response\n */\n protected async outputErrorResponse(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): Promise {\n const isRequestCancelled = this.isRequestCancelled(error);\n const errorHandlingStrategy = requestConfig.strategy || this.strategy;\n const rejectCancelled =\n typeof requestConfig.rejectCancelled !== 'undefined'\n ? requestConfig.rejectCancelled\n : this.rejectCancelled;\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n // Output full response with the error object\n if (errorHandlingStrategy === 'softFail') {\n return this.outputResponse(error.response, requestConfig, error);\n }\n\n // By default cancelled requests aren't rejected\n if (isRequestCancelled && !rejectCancelled) {\n return defaultResponse;\n }\n\n // Hang the promise\n if (errorHandlingStrategy === 'silent') {\n await new Promise(() => null);\n\n return defaultResponse;\n }\n\n // Reject the promise\n if (errorHandlingStrategy === 'reject') {\n return Promise.reject(error);\n }\n\n return defaultResponse;\n }\n\n /**\n * Output error response depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @returns {boolean} True if request is aborted\n */\n public isRequestCancelled(error: ResponseError): boolean {\n return error.name === 'AbortError' || error.name === 'CanceledError';\n }\n\n /**\n * Detects if a custom fetcher is utilized\n *\n * @returns {boolean} True if it's a custom fetcher\n */\n protected isCustomFetcher(): boolean {\n return this.fetcher !== null;\n }\n\n /**\n * Automatically Cancel Previous Requests using AbortController when \"cancellable\" is defined\n *\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {Object} Controller Signal to abort\n */\n protected addCancellationToken(\n requestConfig: RequestConfig,\n ): Partial> {\n // Both disabled\n if (!this.cancellable && !requestConfig.cancellable) {\n return {};\n }\n\n // Explicitly disabled per request\n if (\n typeof requestConfig.cancellable !== 'undefined' &&\n !requestConfig.cancellable\n ) {\n return {};\n }\n\n // Check if AbortController is available\n if (typeof AbortController === 'undefined') {\n console.error('AbortController is unavailable.');\n\n return {};\n }\n\n // Generate unique key as a cancellation token\n const previousRequest = this.requestsQueue.get(requestConfig);\n\n if (previousRequest) {\n previousRequest.abort();\n }\n\n const controller = new AbortController();\n\n // Introduce timeout for native fetch\n if (!this.isCustomFetcher() && this.timeout > 0) {\n const abortTimeout = setTimeout(() => {\n const error = new Error(\n `[TimeoutError]: The ${requestConfig.url} request was aborted due to timeout`,\n );\n\n error.name = 'TimeoutError';\n (error as any).code = 23; // DOMException.TIMEOUT_ERR\n controller.abort(error);\n clearTimeout(abortTimeout);\n throw error;\n }, requestConfig.timeout || this.timeout);\n }\n\n this.requestsQueue.set(requestConfig, controller);\n\n return {\n signal: controller.signal,\n };\n }\n\n /**\n * Handle Request depending on used strategy\n *\n * @param {string} url - Request url\n * @param {QueryParamsOrBody} data - Request data\n * @param {RequestConfig} config - Request config\n * @param {RequestConfig} payload.config Request config\n * @throws {ResponseError}\n * @returns {Promise>} Response Data\n */\n public async request(\n url: string,\n data: QueryParamsOrBody = null,\n config: RequestConfig = null,\n ): Promise> {\n let response: FetchResponse = null;\n const _config = config || {};\n const _requestConfig = this.buildConfig(url, data, _config);\n\n let requestConfig: RequestConfig = {\n ...this.addCancellationToken(_requestConfig),\n ..._requestConfig,\n };\n\n const { retries, delay, backoff, retryOn, shouldRetry, maxDelay } = {\n ...this.retry,\n ...(requestConfig?.retry || {}),\n };\n\n let attempt = 0;\n let waitTime = delay;\n\n while (attempt <= retries) {\n try {\n // Local interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n requestConfig.onRequest,\n );\n\n // Global interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n this.config.onRequest,\n );\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n response = (await (this.requestInstance as any).request(\n requestConfig,\n )) as FetchResponse;\n } else {\n response = (await globalThis.fetch(\n requestConfig.url,\n requestConfig,\n )) as FetchResponse;\n\n // Attempt to collect response data regardless of response status\n const contentType = String(\n (response as Response)?.headers?.get('Content-Type') || '',\n );\n let data;\n const responseClone = response.clone();\n\n // Handle edge case of no content type being provided... We assume json here.\n if (!contentType) {\n try {\n data = await responseClone.json();\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (_error) {\n //\n }\n }\n\n if (typeof data === 'undefined') {\n if (\n contentType &&\n (contentType.includes('application/json') ||\n // This Media Type Suffix is standardizded by IETF in RFC 6839\n contentType.includes('+json'))\n ) {\n data = await response.json(); // Parse JSON response\n } else if (contentType.includes('multipart/form-data')) {\n data = await response.formData(); // Parse as FormData\n } else if (contentType.includes('application/octet-stream')) {\n data = await response.blob(); // Parse as blob\n } else if (\n contentType.includes('application/x-www-form-urlencoded')\n ) {\n data = await response.formData(); // Handle URL-encoded forms\n } else if (typeof response.text !== 'undefined') {\n data = await response.text();\n } else {\n // Handle streams\n data = response.body || response.data || null;\n }\n }\n\n // Add more information to response object\n response.config = requestConfig;\n response.data = data;\n\n // Check if the response status is not outside the range 200-299 and if so, output error\n if (!response.ok) {\n throw new ResponseErr(\n `${requestConfig.url} failed! Status: ${response.status || null}`,\n requestConfig,\n response,\n );\n }\n }\n\n // Local interceptors\n response = await interceptResponse(response, requestConfig.onResponse);\n\n // Global interceptors\n response = await interceptResponse(response, this.config.onResponse);\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n } catch (error) {\n if (\n attempt === retries ||\n !(await shouldRetry(error, attempt)) ||\n !retryOn?.includes(error?.response?.status || error?.status)\n ) {\n this.processError(error, requestConfig);\n\n return this.outputErrorResponse(error, requestConfig);\n }\n\n if (this.logger?.warn) {\n this.logger.warn(\n `Attempt ${attempt + 1} failed. Retrying in ${waitTime}ms...`,\n );\n }\n\n await this.delay(waitTime);\n\n waitTime *= backoff;\n waitTime = Math.min(waitTime, maxDelay);\n attempt++;\n }\n }\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n }\n\n public async delay(ms: number): Promise {\n return new Promise((resolve) =>\n setTimeout(() => {\n return resolve(true);\n }, ms),\n );\n }\n\n public processHeaders(\n response: FetchResponse,\n ): HeadersObject {\n if (!response.headers) {\n return {};\n }\n\n let headersObject: HeadersObject = {};\n\n // Handle Headers object with entries() method\n if (response.headers instanceof Headers) {\n for (const [key, value] of (response.headers as any).entries()) {\n headersObject[key] = value;\n }\n } else {\n // Handle plain object\n headersObject = { ...(response.headers as HeadersObject) };\n }\n\n return headersObject;\n }\n\n /**\n * Output response\n *\n * @param response - Response payload\n * @param {RequestConfig} requestConfig - Request config\n * @param {*} error - whether the response is erroneous\n * @returns {ResponseData | FetchResponse} Response data\n */\n protected outputResponse(\n response: FetchResponse,\n requestConfig: RequestConfig,\n error = null,\n ): ResponseData | FetchResponse {\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n if (!response) {\n return defaultResponse;\n }\n\n if (\n (requestConfig.flattenResponse || this.flattenResponse) &&\n typeof response.data !== 'undefined'\n ) {\n // Special case of only data property within response data object (happens in Axios)\n // This is in fact a proper response but we may want to flatten it\n // To ease developers' lives when obtaining the response\n if (\n response.data !== null &&\n typeof response.data === 'object' &&\n typeof (response.data as any).data !== 'undefined' &&\n Object.keys(response.data).length === 1\n ) {\n return (response.data as any).data;\n }\n\n return response.data;\n }\n\n // If empty object is returned, ensure that the default response is used instead\n if (\n response !== null &&\n typeof response === 'object' &&\n response.constructor === Object &&\n Object.keys(response).length === 0\n ) {\n return defaultResponse;\n }\n\n const isCustomFetcher = this.isCustomFetcher();\n\n if (isCustomFetcher) {\n return response;\n }\n\n if (error !== null) {\n delete error?.response;\n delete error?.request;\n delete error?.config;\n }\n\n // Native fetch()\n return {\n body: response.body,\n blob: response.blob,\n json: response.json,\n text: response.text,\n clone: response.clone,\n bodyUsed: response.bodyUsed,\n arrayBuffer: response.arrayBuffer,\n formData: response.formData,\n ok: response.ok,\n redirected: response.redirected,\n type: response.type,\n url: response.url,\n status: response.status,\n statusText: response.statusText,\n\n // Extend with extra information\n error,\n data: response.data,\n headers: this.processHeaders(response),\n config: requestConfig,\n };\n }\n}\n","import { RequestHandler } from './request-handler';\nimport type {\n FetcherInstance,\n RequestConfig,\n FetchResponse,\n} from './types/request-handler';\nimport type {\n ApiHandlerConfig,\n ApiHandlerMethods,\n ApiHandlerReturnType,\n APIResponse,\n QueryParams,\n UrlPathParams,\n} from './types/api-handler';\n\n/**\n * Creates an instance of API Handler.\n * It creates an API fetcher function using native fetch() or Axios if it is passed as \"fetcher\".\n *\n * @param {Object} config - Configuration object for the API fetcher.\n * @param {string} config.apiUrl - The base URL for the API.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n * @returns API handler functions and endpoints to call\n *\n * @example\n * // Import axios (optional)\n * import axios from 'axios';\n *\n * // Define endpoint paths\n * const endpoints = {\n * getUser: '/user',\n * createPost: '/post',\n * };\n *\n * // Create the API fetcher with configuration\n * const api = createApiFetcher({\n * fetcher: axios, // Axios instance (optional)\n * endpoints,\n * apiUrl: 'https://example.com/api',\n * onError(error) {\n * console.log('Request failed', error);\n * },\n * headers: {\n * 'my-auth-key': 'example-auth-key-32rjjfa',\n * },\n * });\n *\n * // Fetch user data\n * const response = await api.getUser({ userId: 1, ratings: [1, 2] })\n */\nfunction createApiFetcher<\n EndpointsMethods extends object,\n EndpointsCfg = never,\n>(config: ApiHandlerConfig) {\n const endpoints = config.endpoints;\n const requestHandler = new RequestHandler(config);\n\n /**\n * Get Fetcher Provider Instance\n *\n * @returns {FetcherInstance} Request Handler's Fetcher instance\n */\n function getInstance(): FetcherInstance {\n return requestHandler.getInstance();\n }\n\n /**\n * Triggered when trying to use non-existent endpoints\n *\n * @param endpointName Endpoint Name\n * @returns {Promise}\n */\n function handleNonImplemented(endpointName: string): Promise {\n console.error(`${endpointName} endpoint must be added to 'endpoints'.`);\n\n return Promise.resolve(null);\n }\n\n /**\n * Handle Single API Request\n * It considers settings in following order: per-request settings, global per-endpoint settings, global settings.\n *\n * @param {string} endpointName - The name of the API endpoint to call.\n * @param {QueryParams} [queryParams={}] - Query parameters to include in the request.\n * @param {UrlPathParams} [urlPathParams={}] - URI parameters to include in the request.\n * @param {EndpointConfig} [requestConfig={}] - Additional configuration for the request.\n * @returns {Promise} - A promise that resolves with the response from the API provider.\n */\n async function request(\n endpointName: keyof EndpointsMethods | string,\n queryParams: QueryParams = {},\n urlPathParams: UrlPathParams = {},\n requestConfig: RequestConfig = {},\n ): Promise> {\n // Use global per-endpoint settings\n const endpointConfig = endpoints[endpointName as string];\n const endpointSettings = { ...endpointConfig };\n\n const responseData = await requestHandler.request(\n endpointSettings.url,\n queryParams,\n {\n ...endpointSettings,\n ...requestConfig,\n urlPathParams,\n },\n );\n\n return responseData;\n }\n\n /**\n * Maps all API requests using native Proxy\n *\n * @param {*} prop Caller\n */\n function get(prop: string | symbol) {\n if (prop in apiHandler) {\n return apiHandler[prop];\n }\n\n // Prevent handler from triggering non-existent endpoints\n if (!endpoints[prop as string]) {\n return handleNonImplemented.bind(null, prop);\n }\n\n return apiHandler.request.bind(null, prop);\n }\n\n const apiHandler: ApiHandlerMethods = {\n config,\n endpoints,\n requestHandler,\n getInstance,\n request,\n };\n\n return new Proxy(apiHandler, {\n get: (_target, prop) => get(prop),\n }) as ApiHandlerReturnType;\n}\n\nexport { createApiFetcher };\n"],"mappings":"4ZAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,sBAAAE,EAAA,WAAAC,IAAA,eAAAC,EAAAJ,GCYA,eAAsBK,EACpBC,EACAC,EAC+B,CAC/B,GAAI,CAACA,EACH,OAAOD,EAGT,IAAME,EAAkB,MAAM,QAAQD,CAAY,EAC9CA,EACA,CAACA,CAAY,EAEbE,EAAoB,CAAE,GAAGH,CAAO,EAEpC,QAAWI,KAAeF,EACxBC,EAAoB,MAAMC,EAAYD,CAAiB,EAGzD,OAAOA,CACT,CAQA,eAAsBE,EACpBC,EACAL,EACsC,CACtC,GAAI,CAACA,EACH,OAAOK,EAGT,IAAMJ,EAAkB,MAAM,QAAQD,CAAY,EAC9CA,EACA,CAACA,CAAY,EAEbM,EAAsBD,EAE1B,QAAWF,KAAeF,EACxBK,EAAsB,MAAMH,EAAYG,CAAmB,EAG7D,OAAOA,CACT,CCxDO,IAAMC,EAAN,cAA0B,KAAM,CACrC,SACA,QACA,OACA,OACA,WAEA,YACEC,EACAC,EACAC,EACA,CACA,MAAMF,CAAO,EAEb,KAAK,KAAO,gBACZ,KAAK,QAAUA,EACf,KAAK,OAASE,EAAS,OACvB,KAAK,WAAaA,EAAS,WAC3B,KAAK,QAAUD,EACf,KAAK,OAASA,EACd,KAAK,SAAWC,CAClB,CACF,ECEO,IAAMC,EAAN,KAAqB,CAInB,gBAKA,QAAkB,GAKlB,QAAkB,IAKlB,YAAuB,GAKvB,gBAA2B,GAK3B,SAAkC,SAKlC,OAA0B,MAK1B,gBAA2B,GAK3B,gBAAuB,KAKpB,QAKA,OAKA,QAKA,cAKA,MAAsB,CAC9B,QAAS,EACT,MAAO,IACP,SAAU,IACV,QAAS,IAGT,QAAS,CACP,IACA,IACA,IACA,IACA,IACA,IACA,IACA,GACF,EAEA,YAAa,SAAY,EAC3B,EAKO,OA6BA,YAAY,CACjB,QAAAC,EAAU,KACV,QAAAC,EAAU,KACV,gBAAAC,EAAkB,GAClB,SAAAC,EAAW,KACX,gBAAAC,EAAkB,KAClB,gBAAAC,EAAkB,CAAC,EACnB,OAAAC,EAAS,KACT,QAAAC,EAAU,KACV,GAAGC,CACL,EAAyB,CACvB,KAAK,QAAUR,EACf,KAAK,QACHC,GAAsD,KAAK,QAC7D,KAAK,SAAWE,GAAY,KAAK,SACjC,KAAK,YAAcK,EAAO,aAAe,KAAK,YAC9C,KAAK,gBAAkBN,GAAmB,KAAK,gBAC/C,KAAK,gBAAkBE,GAAmB,KAAK,gBAC/C,KAAK,gBAAkBC,EACvB,KAAK,OAASC,IAAW,WAAa,WAAW,QAAU,OAAS,KACpE,KAAK,QAAUC,EACf,KAAK,cAAgB,IAAI,QACzB,KAAK,QAAUC,EAAO,SAAWA,EAAO,QAAU,GAClD,KAAK,OAASA,EAAO,QAAU,KAAK,OACpC,KAAK,OAASA,EACd,KAAK,MAAQ,CACX,GAAG,KAAK,MACR,GAAIA,EAAO,OAAS,CAAC,CACvB,EAEA,KAAK,gBAAkB,KAAK,gBAAgB,EACvCR,EAAgB,OAAO,CACtB,GAAGQ,EACH,QAAS,KAAK,QACd,QAAS,KAAK,OAChB,CAAC,EACD,IACN,CAOO,aAA+B,CACpC,OAAO,KAAK,eACd,CAWO,qBACLC,EACAC,EACQ,CACR,OAAKA,EAIED,EAAI,QAAQ,eAAiBE,GAAgB,CAClD,IAAMC,EAAOD,EAAI,UAAU,CAAC,EAE5B,OAAO,OAAOD,EAAcE,CAAI,EAAIF,EAAcE,CAAI,EAAID,CAAG,CAC/D,CAAC,EAPQF,CAQX,CASO,kBAAkBA,EAAaI,EAA6B,CACjE,GAAI,CAACA,EACH,OAAOJ,EAKT,IAAMK,EAAc,OAAO,QAAQD,CAAM,EACtC,QAAQ,CAAC,CAACE,EAAKC,CAAK,IACf,MAAM,QAAQA,CAAK,EACdA,EAAM,IACVC,GAAQ,GAAG,mBAAmBF,CAAG,CAAC,MAAM,mBAAmBE,CAAG,CAAC,EAClE,EAEK,GAAG,mBAAmBF,CAAG,CAAC,IAAI,mBAAmB,OAAOC,CAAK,CAAC,CAAC,EACvE,EACA,KAAK,GAAG,EAEX,OAAOP,EAAI,SAAS,GAAG,EACnB,GAAGA,CAAG,IAAIK,CAAW,GACrBA,EACE,GAAGL,CAAG,IAAIK,CAAW,GACrBL,CACR,CAcU,mBAAmBO,EAAqB,CAChD,GAA2BA,GAAU,KACnC,MAAO,GAGT,IAAM,EAAI,OAAOA,EACjB,GAAI,IAAM,UAAY,IAAM,UAAY,IAAM,UAC5C,MAAO,GAGT,GAAI,IAAM,SACR,MAAO,GAGT,GAAI,MAAM,QAAQA,CAAK,EACrB,MAAO,GAOT,GAJI,OAAO,SAASA,CAAK,GAIrBA,aAAiB,KACnB,MAAO,GAGT,IAAME,EAAQ,OAAO,eAAeF,CAAK,EAQzC,OALIE,IAAU,OAAO,WAAaA,IAAU,MAKxC,OAAOF,EAAM,QAAW,UAK9B,CAUU,YACRP,EACAU,EACAX,EACe,CACf,IAAMY,EAASZ,EAAO,QAAU,KAAK,OAC/Ba,EAAkBD,EAAO,YAAY,EACrCE,EACJD,IAAoB,OAASA,IAAoB,OAE7CE,EAAa,KAAK,qBACtBd,EACAD,EAAO,eAAiB,KAAK,OAAO,aACtC,EAGMgB,EACJhB,EAAO,MAAQA,EAAO,MAAQ,KAAK,OAAO,MAAQ,KAAK,OAAO,KAGhE,GAAI,KAAK,gBAAgB,EACvB,MAAO,CACL,GAAGA,EACH,IAAKe,EACL,OAAQF,EAER,GAAIC,EAAmB,CAAE,OAAQH,CAAK,EAAI,CAAC,EAI3C,GAAI,CAACG,GAAoBH,GAAQK,EAAa,CAAE,OAAQL,CAAK,EAAI,CAAC,EAGlE,GAAI,CAACG,GAAoBH,GAAQ,CAACK,EAAa,CAAE,KAAAL,CAAK,EAAI,CAAC,EAC3D,GAAI,CAACG,GAAoBE,EAAa,CAAE,KAAMA,CAAW,EAAI,CAAC,CAChE,EAIF,IAAMC,EAAUD,GAAcL,EACxBO,EACJlB,EAAO,iBAAmB,KAAK,OAAO,gBAClC,UACAA,EAAO,YAEb,OAAOA,EAAO,KACd,OAAOA,EAAO,gBAEd,IAAMmB,EACH,CAACL,GAAoBH,GAAQ,CAACX,EAAO,MAAS,CAACW,EAC5CI,EACA,KAAK,kBAAkBA,EAAYJ,CAAI,EAEvCS,EADYD,EAAQ,SAAS,KAAK,EAEpC,GACA,OAAOnB,EAAO,QAAY,IACxBA,EAAO,QACP,KAAK,QAEX,MAAO,CACL,GAAGA,EACH,YAAAkB,EAIA,IAAKE,EAAUD,EAGf,OAAQP,EAAO,YAAY,EAG3B,QAAS,CACP,OAAQ,oCACR,eAAgB,iCAChB,GAAIZ,EAAO,SAAW,KAAK,OAAO,SAAW,CAAC,CAChD,EAGA,GAAKc,EAUD,CAAC,EATD,CACE,KACE,EAAEG,aAAmB,kBACrB,KAAK,mBAAmBA,CAAO,EAC3B,OAAOA,GAAY,SACjBA,EACA,KAAK,UAAUA,CAAO,EACxBA,CACR,CAEN,CACF,CASU,aACRI,EACAC,EACM,CA/ZV,IAAAC,EAgaQ,KAAK,mBAAmBF,CAAK,KAI7BE,EAAA,KAAK,SAAL,MAAAA,EAAa,MACf,KAAK,OAAO,KAAK,YAAaF,CAAK,EAIjCC,EAAc,SAAW,OAAOA,EAAc,SAAY,YAC5DA,EAAc,QAAQD,CAAK,EAIzB,KAAK,SAAW,OAAO,KAAK,SAAY,YAC1C,KAAK,QAAQA,CAAK,EAEtB,CASA,MAAgB,oBACdA,EACAC,EACc,CACd,IAAME,EAAqB,KAAK,mBAAmBH,CAAK,EAClDI,EAAwBH,EAAc,UAAY,KAAK,SACvD5B,EACJ,OAAO4B,EAAc,gBAAoB,IACrCA,EAAc,gBACd,KAAK,gBACLzB,EACJ,OAAOyB,EAAc,gBAAoB,IACrCA,EAAc,gBACd,KAAK,gBAGX,OAAIG,IAA0B,WACrB,KAAK,eAAeJ,EAAM,SAAUC,EAAeD,CAAK,EAI7DG,GAAsB,CAAC9B,EAClBG,EAIL4B,IAA0B,UAC5B,MAAM,IAAI,QAAQ,IAAM,IAAI,EAErB5B,GAIL4B,IAA0B,SACrB,QAAQ,OAAOJ,CAAK,EAGtBxB,CACT,CAQO,mBAAmBwB,EAA+B,CACvD,OAAOA,EAAM,OAAS,cAAgBA,EAAM,OAAS,eACvD,CAOU,iBAA2B,CACnC,OAAO,KAAK,UAAY,IAC1B,CAQU,qBACRC,EACwC,CAExC,GAAI,CAAC,KAAK,aAAe,CAACA,EAAc,YACtC,MAAO,CAAC,EAIV,GACE,OAAOA,EAAc,YAAgB,KACrC,CAACA,EAAc,YAEf,MAAO,CAAC,EAIV,GAAI,OAAO,gBAAoB,IAC7B,eAAQ,MAAM,iCAAiC,EAExC,CAAC,EAIV,IAAMI,EAAkB,KAAK,cAAc,IAAIJ,CAAa,EAExDI,GACFA,EAAgB,MAAM,EAGxB,IAAMC,EAAa,IAAI,gBAGvB,GAAI,CAAC,KAAK,gBAAgB,GAAK,KAAK,QAAU,EAAG,CAC/C,IAAMC,EAAe,WAAW,IAAM,CACpC,IAAMP,EAAQ,IAAI,MAChB,uBAAuBC,EAAc,GAAG,qCAC1C,EAEA,MAAAD,EAAM,KAAO,eACZA,EAAc,KAAO,GACtBM,EAAW,MAAMN,CAAK,EACtB,aAAaO,CAAY,EACnBP,CACR,EAAGC,EAAc,SAAW,KAAK,OAAO,CAC1C,CAEA,YAAK,cAAc,IAAIA,EAAeK,CAAU,EAEzC,CACL,OAAQA,EAAW,MACrB,CACF,CAYA,MAAa,QACX1B,EACAU,EAA0B,KAC1BX,EAAwB,KAC6B,CA/jBzD,IAAAuB,EAAAM,EAAAC,EAgkBI,IAAIC,EAAwC,KACtCC,EAAUhC,GAAU,CAAC,EACrBiC,EAAiB,KAAK,YAAYhC,EAAKU,EAAMqB,CAAO,EAEtDV,EAA+B,CACjC,GAAG,KAAK,qBAAqBW,CAAc,EAC3C,GAAGA,CACL,EAEM,CAAE,QAAAC,EAAS,MAAAC,EAAO,QAAAC,EAAS,QAAAC,EAAS,YAAAC,EAAa,SAAAC,CAAS,EAAI,CAClE,GAAG,KAAK,MACR,IAAIjB,GAAA,YAAAA,EAAe,QAAS,CAAC,CAC/B,EAEIkB,EAAU,EACVC,EAAWN,EAEf,KAAOK,GAAWN,GAChB,GAAI,CAcF,GAZAZ,EAAgB,MAAMoB,EACpBpB,EACAA,EAAc,SAChB,EAGAA,EAAgB,MAAMoB,EACpBpB,EACA,KAAK,OAAO,SACd,EAGI,KAAK,gBAAgB,EACvBS,EAAY,MAAO,KAAK,gBAAwB,QAC9CT,CACF,MACK,CACLS,EAAY,MAAM,WAAW,MAC3BT,EAAc,IACdA,CACF,EAGA,IAAMqB,EAAc,SACjBpB,EAAAQ,GAAA,YAAAA,EAAuB,UAAvB,YAAAR,EAAgC,IAAI,kBAAmB,EAC1D,EACIZ,EACEiC,EAAgBb,EAAS,MAAM,EAGrC,GAAI,CAACY,EACH,GAAI,CACFhC,EAAO,MAAMiC,EAAc,KAAK,CAElC,MAAiB,CAEjB,CAgCF,GA7BI,OAAOjC,EAAS,MAEhBgC,IACCA,EAAY,SAAS,kBAAkB,GAEtCA,EAAY,SAAS,OAAO,GAE9BhC,EAAO,MAAMoB,EAAS,KAAK,EAClBY,EAAY,SAAS,qBAAqB,EACnDhC,EAAO,MAAMoB,EAAS,SAAS,EACtBY,EAAY,SAAS,0BAA0B,EACxDhC,EAAO,MAAMoB,EAAS,KAAK,EAE3BY,EAAY,SAAS,mCAAmC,EAExDhC,EAAO,MAAMoB,EAAS,SAAS,EACtB,OAAOA,EAAS,KAAS,IAClCpB,EAAO,MAAMoB,EAAS,KAAK,EAG3BpB,EAAOoB,EAAS,MAAQA,EAAS,MAAQ,MAK7CA,EAAS,OAAST,EAClBS,EAAS,KAAOpB,EAGZ,CAACoB,EAAS,GACZ,MAAM,IAAIc,EACR,GAAGvB,EAAc,GAAG,oBAAoBS,EAAS,QAAU,IAAI,GAC/DT,EACAS,CACF,CAEJ,CAGA,OAAAA,EAAW,MAAMe,EAAkBf,EAAUT,EAAc,UAAU,EAGrES,EAAW,MAAMe,EAAkBf,EAAU,KAAK,OAAO,UAAU,EAE5D,KAAK,eAAeA,EAAUT,CAAa,CAEpD,OAASD,EAAO,CACd,GACEmB,IAAYN,GACZ,CAAE,MAAMI,EAAYjB,EAAOmB,CAAO,GAClC,EAACH,GAAA,MAAAA,EAAS,WAASR,EAAAR,GAAA,YAAAA,EAAO,WAAP,YAAAQ,EAAiB,UAAUR,GAAA,YAAAA,EAAO,UAErD,YAAK,aAAaA,EAAOC,CAAa,EAE/B,KAAK,oBAAoBD,EAAOC,CAAa,GAGlDQ,EAAA,KAAK,SAAL,MAAAA,EAAa,MACf,KAAK,OAAO,KACV,WAAWU,EAAU,CAAC,wBAAwBC,CAAQ,OACxD,EAGF,MAAM,KAAK,MAAMA,CAAQ,EAEzBA,GAAYL,EACZK,EAAW,KAAK,IAAIA,EAAUF,CAAQ,EACtCC,GACF,CAGF,OAAO,KAAK,eAAeT,EAAUT,CAAa,CAEpD,CAEA,MAAa,MAAMyB,EAA8B,CAC/C,OAAO,IAAI,QAASC,GAClB,WAAW,IACFA,EAAQ,EAAI,EAClBD,CAAE,CACP,CACF,CAEO,eACLhB,EACe,CACf,GAAI,CAACA,EAAS,QACZ,MAAO,CAAC,EAGV,IAAIkB,EAA+B,CAAC,EAGpC,GAAIlB,EAAS,mBAAmB,QAC9B,OAAW,CAACxB,EAAKC,CAAK,IAAMuB,EAAS,QAAgB,QAAQ,EAC3DkB,EAAc1C,CAAG,EAAIC,OAIvByC,EAAgB,CAAE,GAAIlB,EAAS,OAA0B,EAG3D,OAAOkB,CACT,CAUU,eACRlB,EACAT,EACAD,EAAQ,KACoC,CAC5C,IAAMxB,EACJ,OAAOyB,EAAc,gBAAoB,IACrCA,EAAc,gBACd,KAAK,gBAEX,OAAKS,GAKFT,EAAc,iBAAmB,KAAK,kBACvC,OAAOS,EAAS,KAAS,IAMvBA,EAAS,OAAS,MAClB,OAAOA,EAAS,MAAS,UACzB,OAAQA,EAAS,KAAa,KAAS,KACvC,OAAO,KAAKA,EAAS,IAAI,EAAE,SAAW,EAE9BA,EAAS,KAAa,KAGzBA,EAAS,KAKhBA,IAAa,MACb,OAAOA,GAAa,UACpBA,EAAS,cAAgB,QACzB,OAAO,KAAKA,CAAQ,EAAE,SAAW,EAE1BlC,EAGe,KAAK,gBAAgB,EAGpCkC,GAGLV,IAAU,OACZA,GAAA,aAAAA,EAAc,SACdA,GAAA,aAAAA,EAAc,QACdA,GAAA,aAAAA,EAAc,QAIT,CACL,KAAMU,EAAS,KACf,KAAMA,EAAS,KACf,KAAMA,EAAS,KACf,KAAMA,EAAS,KACf,MAAOA,EAAS,MAChB,SAAUA,EAAS,SACnB,YAAaA,EAAS,YACtB,SAAUA,EAAS,SACnB,GAAIA,EAAS,GACb,WAAYA,EAAS,WACrB,KAAMA,EAAS,KACf,IAAKA,EAAS,IACd,OAAQA,EAAS,OACjB,WAAYA,EAAS,WAGrB,MAAAV,EACA,KAAMU,EAAS,KACf,QAAS,KAAK,eAAeA,CAAQ,EACrC,OAAQT,CACV,GAlESzB,CAmEX,CACF,ECrvBA,SAASqD,EAGPC,EAA4C,CAC5C,IAAMC,EAAYD,EAAO,UACnBE,EAAiB,IAAIC,EAAeH,CAAM,EAOhD,SAASI,GAA+B,CACtC,OAAOF,EAAe,YAAY,CACpC,CAQA,SAASG,EAAqBC,EAAqC,CACjE,eAAQ,MAAM,GAAGA,CAAY,yCAAyC,EAE/D,QAAQ,QAAQ,IAAI,CAC7B,CAYA,eAAeC,EACbD,EACAE,EAA2B,CAAC,EAC5BC,EAA+B,CAAC,EAChCC,EAA+B,CAAC,EACa,CAG7C,IAAMC,EAAmB,CAAE,GADJV,EAAUK,CAAsB,CACV,EAY7C,OAVqB,MAAMJ,EAAe,QACxCS,EAAiB,IACjBH,EACA,CACE,GAAGG,EACH,GAAGD,EACH,cAAAD,CACF,CACF,CAGF,CAOA,SAASG,EAAIC,EAAuB,CAClC,OAAIA,KAAQC,EACHA,EAAWD,CAAI,EAInBZ,EAAUY,CAAc,EAItBC,EAAW,QAAQ,KAAK,KAAMD,CAAI,EAHhCR,EAAqB,KAAK,KAAMQ,CAAI,CAI/C,CAEA,IAAMC,EAAkD,CACtD,OAAAd,EACA,UAAAC,EACA,eAAAC,EACA,YAAAE,EACA,QAAAG,CACF,EAEA,OAAO,IAAI,MAAMO,EAAY,CAC3B,IAAK,CAACC,EAASF,IAASD,EAAIC,CAAI,CAClC,CAAC,CACH,CJrJA,eAAsBG,EACpBC,EACAC,EAA+B,CAAC,EACqB,CACrD,OAAO,IAAIC,EAAeD,CAAM,EAAE,QAChCD,EACAC,EAAO,MAAQA,EAAO,MAAQA,EAAO,OACrCA,CACF,CACF","names":["src_exports","__export","createApiFetcher","fetchf","__toCommonJS","interceptRequest","config","interceptors","interceptorList","interceptedConfig","interceptor","interceptResponse","response","interceptedResponse","ResponseErr","message","requestInfo","response","RequestHandler","fetcher","timeout","rejectCancelled","strategy","flattenResponse","defaultResponse","logger","onError","config","url","urlPathParams","str","word","params","queryString","key","value","val","proto","data","method","methodLowerCase","isGetAlikeMethod","dynamicUrl","configData","payload","credentials","urlPath","baseURL","error","requestConfig","_a","isRequestCancelled","errorHandlingStrategy","previousRequest","controller","abortTimeout","_b","_c","response","_config","_requestConfig","retries","delay","backoff","retryOn","shouldRetry","maxDelay","attempt","waitTime","interceptRequest","contentType","responseClone","ResponseErr","interceptResponse","ms","resolve","headersObject","createApiFetcher","config","endpoints","requestHandler","RequestHandler","getInstance","handleNonImplemented","endpointName","request","queryParams","urlPathParams","requestConfig","endpointSettings","get","prop","apiHandler","_target","fetchf","url","config","RequestHandler"]} \ No newline at end of file +{"version":3,"sources":["../src/index.ts","../src/interceptor-manager.ts","../src/response-error.ts","../src/utils.ts","../src/request-handler.ts","../src/api-handler.ts"],"sourcesContent":["import { RequestHandler } from './request-handler';\nimport type { APIResponse, FetchResponse, RequestHandlerConfig } from './types';\n\n/**\n * Simple wrapper for request fetching.\n * It abstracts the creation of RequestHandler, making it easy to perform API requests.\n *\n * @param {string | URL | globalThis.Request} url - Request URL.\n * @param {RequestHandlerConfig} config - Configuration object for the request handler.\n * @returns {Promise>} Response Data.\n */\nexport async function fetchf(\n url: string,\n config: RequestHandlerConfig = {},\n): Promise> {\n return new RequestHandler(config).request(\n url,\n config.body || config.data || config.params,\n config,\n );\n}\n\nexport * from './types';\nexport * from './api-handler';\n","import type { RequestHandlerConfig, FetchResponse } from './types';\nimport type {\n RequestInterceptor,\n ResponseInterceptor,\n} from './types/interceptor-manager';\n\n/**\n * Applies a series of request interceptors to the provided configuration.\n * @param {RequestHandlerConfig} config - The initial request configuration.\n * @param {RequestInterceptor | RequestInterceptor[]} interceptors - The request interceptor function(s) to apply.\n * @returns {Promise} - The modified request configuration.\n */\nexport async function interceptRequest(\n config: RequestHandlerConfig,\n interceptors: RequestInterceptor | RequestInterceptor[],\n): Promise {\n if (!interceptors) {\n return config;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedConfig = { ...config };\n\n for (const interceptor of interceptorList) {\n interceptedConfig = await interceptor(interceptedConfig);\n }\n\n return interceptedConfig;\n}\n\n/**\n * Applies a series of response interceptors to the provided response.\n * @param {FetchResponse} response - The initial response object.\n * @param {ResponseInterceptor | ResponseInterceptor[]} interceptors - The response interceptor function(s) to apply.\n * @returns {Promise>} - The modified response object.\n */\nexport async function interceptResponse(\n response: FetchResponse,\n interceptors: ResponseInterceptor | ResponseInterceptor[],\n): Promise> {\n if (!interceptors) {\n return response;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedResponse = response;\n\n for (const interceptor of interceptorList) {\n interceptedResponse = await interceptor(interceptedResponse);\n }\n\n return interceptedResponse;\n}\n","import type { FetchResponse, RequestConfig } from './types';\n\nexport class ResponseErr extends Error {\n response: FetchResponse;\n request: RequestConfig;\n config: RequestConfig;\n status: number;\n statusText: string;\n\n constructor(\n message: string,\n requestInfo: RequestConfig,\n response: FetchResponse,\n ) {\n super(message);\n\n this.name = 'ResponseError';\n this.message = message;\n this.status = response.status;\n this.statusText = response.statusText;\n this.request = requestInfo;\n this.config = requestInfo;\n this.response = response;\n }\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { QueryParams, UrlPathParams } from './types';\n\n/**\n * Appends query parameters to a given URL.\n *\n * @param {string} url - The base URL to which query parameters will be appended.\n * @param {QueryParams} params - An object containing the query parameters to append.\n * @returns {string} - The URL with the appended query parameters.\n */\nexport function appendQueryParams(url: string, params: QueryParams): string {\n if (!params) {\n return url;\n }\n\n // This is exact copy of what JQ used to do. It works much better than URLSearchParams\n const s = [];\n const add = function (k: string, v: any) {\n v = typeof v === 'function' ? v() : v;\n v = v === null ? '' : v === undefined ? '' : v;\n s[s.length] = encodeURIComponent(k) + '=' + encodeURIComponent(v);\n };\n\n const buildParams = (prefix: string, obj: any) => {\n let i: number, len: number, key: string;\n\n if (prefix) {\n if (Array.isArray(obj)) {\n for (i = 0, len = obj.length; i < len; i++) {\n buildParams(\n prefix +\n '[' +\n (typeof obj[i] === 'object' && obj[i] ? i : '') +\n ']',\n obj[i],\n );\n }\n } else if (typeof obj === 'object' && obj !== null) {\n for (key in obj) {\n buildParams(prefix + '[' + key + ']', obj[key]);\n }\n } else {\n add(prefix, obj);\n }\n } else if (Array.isArray(obj)) {\n for (i = 0, len = obj.length; i < len; i++) {\n add(obj[i].name, obj[i].value);\n }\n } else {\n for (key in obj) {\n buildParams(key, obj[key]);\n }\n }\n return s;\n };\n\n const queryStringParts = buildParams('', params).join('&');\n\n // Encode special characters as per RFC 3986, https://datatracker.ietf.org/doc/html/rfc3986\n const encodedQueryString = queryStringParts.replace(/%5B%5D/g, '[]'); // Keep '[]' for arrays\n\n return url.includes('?')\n ? `${url}&${encodedQueryString}`\n : encodedQueryString\n ? `${url}?${encodedQueryString}`\n : url;\n}\n\n/**\n * Replaces dynamic URI parameters in a URL string with values from the provided `urlPathParams` object.\n * Parameters in the URL are denoted by `:`, where `` is a key in `urlPathParams`.\n *\n * @param {string} url - The URL string containing placeholders in the format `:`.\n * @param {Object} urlPathParams - An object containing the parameter values to replace placeholders.\n * @param {string} urlPathParams.paramName - The value to replace the placeholder `:` in the URL.\n * @returns {string} - The URL string with placeholders replaced by corresponding values from `urlPathParams`.\n */\nexport function replaceUrlPathParams(\n url: string,\n urlPathParams: UrlPathParams,\n): string {\n if (!urlPathParams) {\n return url;\n }\n\n return url.replace(/:[a-zA-Z]+/gi, (str): string => {\n const word = str.substring(1);\n\n return String(urlPathParams[word] ? urlPathParams[word] : str);\n });\n}\n\n/**\n * Checks if a value is JSON serializable.\n *\n * JSON serializable values include:\n * - Primitive types: string, number, boolean, null\n * - Arrays\n * - Plain objects (i.e., objects without special methods)\n * - Values with a `toJSON` method\n *\n * @param {any} value - The value to check for JSON serializability.\n * @returns {boolean} - Returns `true` if the value is JSON serializable, otherwise `false`.\n */\nexport function isJSONSerializable(value: any): boolean {\n if (value === undefined || value === null) {\n return false;\n }\n\n const t = typeof value;\n if (t === 'string' || t === 'number' || t === 'boolean') {\n return true;\n }\n\n if (t !== 'object') {\n return false; // bigint, function, symbol, undefined\n }\n\n if (Array.isArray(value)) {\n return true;\n }\n\n if (Buffer.isBuffer(value)) {\n return false;\n }\n\n if (value instanceof Date) {\n return false;\n }\n\n const proto = Object.getPrototypeOf(value);\n\n // Check if the prototype is `Object.prototype` or `null` (plain object)\n if (proto === Object.prototype || proto === null) {\n return true;\n }\n\n // Check if the object has a toJSON method\n if (typeof value.toJSON === 'function') {\n return true;\n }\n\n return false;\n}\n\nexport async function delayInvocation(ms: number): Promise {\n return new Promise((resolve) =>\n setTimeout(() => {\n return resolve(true);\n }, ms),\n );\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {\n ErrorHandlingStrategy,\n RequestHandlerConfig,\n RequestConfig,\n FetcherInstance,\n Method,\n RetryOptions,\n FetchResponse,\n ResponseError,\n HeadersObject,\n} from './types/request-handler';\nimport type { APIResponse, QueryParamsOrBody } from './types/api-handler';\nimport { interceptRequest, interceptResponse } from './interceptor-manager';\nimport { ResponseErr } from './response-error';\nimport {\n appendQueryParams,\n isJSONSerializable,\n replaceUrlPathParams,\n delayInvocation,\n} from './utils';\n\nconst APPLICATION_JSON = 'application/json';\n\n/**\n * Generic Request Handler\n * It creates an Request Fetcher instance and handles requests within that instance\n * It handles errors depending on a chosen error handling strategy\n */\nexport class RequestHandler {\n /**\n * @var requestInstance Provider's instance\n */\n public requestInstance: FetcherInstance;\n\n /**\n * @var baseURL Base API url\n */\n public baseURL: string = '';\n\n /**\n * @var timeout Request timeout\n */\n public timeout: number = 30000;\n\n /**\n * @var cancellable Response cancellation\n */\n public cancellable: boolean = false;\n\n /**\n * @var rejectCancelled Whether to reject cancelled requests or not\n */\n public rejectCancelled: boolean = false;\n\n /**\n * @var strategy Request timeout\n */\n public strategy: ErrorHandlingStrategy = 'reject';\n\n /**\n * @var method Request method\n */\n public method: Method | string = 'get';\n\n /**\n * @var flattenResponse Response flattening\n */\n public flattenResponse: boolean = false;\n\n /**\n * @var defaultResponse Response flattening\n */\n public defaultResponse: any = null;\n\n /**\n * @var fetcher Request Fetcher instance\n */\n protected fetcher: FetcherInstance;\n\n /**\n * @var logger Logger\n */\n protected logger: any;\n\n /**\n * @var onError HTTP error service\n */\n protected onError: any;\n\n /**\n * @var requestsQueue Queue of requests\n */\n protected requestsQueue: WeakMap;\n\n /**\n * Request Handler Config\n */\n protected retry: RetryOptions = {\n retries: 0,\n delay: 1000,\n maxDelay: 30000,\n backoff: 1.5,\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status\n retryOn: [\n 408, // Request Timeout\n 409, // Conflict\n 425, // Too Early\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n ],\n\n shouldRetry: async () => true,\n };\n\n /**\n * Request Handler Config\n */\n public config: RequestHandlerConfig;\n\n /**\n * Creates an instance of RequestHandler.\n *\n * @param {Object} config - Configuration object for the request.\n * @param {string} config.baseURL - The base URL for the request.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n */\n public constructor({\n fetcher = null,\n timeout = null,\n rejectCancelled = false,\n strategy = null,\n flattenResponse = null,\n defaultResponse = {},\n logger = null,\n onError = null,\n ...config\n }: RequestHandlerConfig) {\n this.fetcher = fetcher;\n this.timeout =\n timeout !== null && timeout !== undefined ? timeout : this.timeout;\n this.strategy = strategy || this.strategy;\n this.cancellable = config.cancellable || this.cancellable;\n this.rejectCancelled = rejectCancelled || this.rejectCancelled;\n this.flattenResponse = flattenResponse || this.flattenResponse;\n this.defaultResponse = defaultResponse;\n this.logger = logger || (globalThis ? globalThis.console : null) || null;\n this.onError = onError;\n this.requestsQueue = new WeakMap();\n this.baseURL = config.baseURL || config.apiUrl || '';\n this.method = config.method || this.method;\n this.config = config;\n this.retry = {\n ...this.retry,\n ...(config.retry || {}),\n };\n\n this.requestInstance = this.isCustomFetcher()\n ? (fetcher as any).create({\n ...config,\n baseURL: this.baseURL,\n timeout: this.timeout,\n })\n : null;\n }\n\n /**\n * Get Provider Instance\n *\n * @returns {FetcherInstance} Provider's instance\n */\n public getInstance(): FetcherInstance {\n return this.requestInstance;\n }\n\n /**\n * Build request configuration\n *\n * @param {string} url Request url\n * @param {QueryParamsOrBody} data Request data\n * @param {RequestConfig} config Request config\n * @returns {RequestConfig} Provider's instance\n */\n protected buildConfig(\n url: string,\n data: QueryParamsOrBody,\n config: RequestConfig,\n ): RequestConfig {\n const method = config.method || this.method;\n const methodLowerCase = method.toLowerCase();\n const isGetAlikeMethod =\n methodLowerCase === 'get' || methodLowerCase === 'head';\n\n const dynamicUrl = replaceUrlPathParams(\n url,\n config.urlPathParams || this.config.urlPathParams,\n );\n\n // Bonus: Specifying it here brings support for \"body\" in Axios\n const configData =\n config.body || config.data || this.config.body || this.config.data;\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n return {\n ...config,\n url: dynamicUrl,\n method: methodLowerCase,\n\n ...(isGetAlikeMethod ? { params: data } : {}),\n\n // For POST requests body payload is the first param for convenience (\"data\")\n // In edge cases we want to split so to treat it as query params, and use \"body\" coming from the config instead\n ...(!isGetAlikeMethod && data && configData ? { params: data } : {}),\n\n // Only applicable for request methods 'PUT', 'POST', 'DELETE', and 'PATCH'\n ...(!isGetAlikeMethod && data && !configData ? { data } : {}),\n ...(!isGetAlikeMethod && configData ? { data: configData } : {}),\n };\n }\n\n // Native fetch\n const payload = configData || data;\n const credentials =\n config.withCredentials || this.config.withCredentials\n ? 'include'\n : config.credentials;\n\n delete config.data;\n delete config.withCredentials;\n\n const urlPath =\n (!isGetAlikeMethod && data && !config.body) || !data\n ? dynamicUrl\n : appendQueryParams(dynamicUrl, data);\n const isFullUrl = urlPath.includes('://');\n const baseURL = isFullUrl\n ? ''\n : typeof config.baseURL !== 'undefined'\n ? config.baseURL\n : this.baseURL;\n\n return {\n ...config,\n credentials,\n\n // Native fetch generally requires query params to be appended in the URL\n // Do not append query params only if it's a POST-alike request with only \"data\" specified as it's treated as body payload\n url: baseURL + urlPath,\n\n // Uppercase method name\n method: method.toUpperCase(),\n\n // For convenience, add the same default headers as Axios does\n headers: {\n Accept: APPLICATION_JSON + ', text/plain, */*',\n 'Content-Type': APPLICATION_JSON + ';charset=utf-8',\n ...(config.headers || this.config.headers || {}),\n },\n\n // Automatically JSON stringify request bodies, if possible and when not dealing with strings\n ...(!isGetAlikeMethod\n ? {\n body:\n !(payload instanceof URLSearchParams) &&\n isJSONSerializable(payload)\n ? typeof payload === 'string'\n ? payload\n : JSON.stringify(payload)\n : payload,\n }\n : {}),\n };\n }\n\n /**\n * Process global Request Error\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {void}\n */\n protected processError(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): void {\n if (this.isRequestCancelled(error)) {\n return;\n }\n\n if (this.logger?.warn) {\n this.logger.warn('API ERROR', error);\n }\n\n // Invoke per request \"onError\" interceptor\n if (requestConfig.onError && typeof requestConfig.onError === 'function') {\n requestConfig.onError(error);\n }\n\n // Invoke global \"onError\" interceptor\n if (this.onError && typeof this.onError === 'function') {\n this.onError(error);\n }\n }\n\n /**\n * Output default response in case of an error, depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {*} Error response\n */\n protected async outputErrorResponse(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): Promise {\n const isRequestCancelled = this.isRequestCancelled(error);\n const errorHandlingStrategy = requestConfig.strategy || this.strategy;\n const rejectCancelled =\n typeof requestConfig.rejectCancelled !== 'undefined'\n ? requestConfig.rejectCancelled\n : this.rejectCancelled;\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n // Output full response with the error object\n if (errorHandlingStrategy === 'softFail') {\n return this.outputResponse(error.response, requestConfig, error);\n }\n\n // By default cancelled requests aren't rejected\n if (isRequestCancelled && !rejectCancelled) {\n return defaultResponse;\n }\n\n // Hang the promise\n if (errorHandlingStrategy === 'silent') {\n await new Promise(() => null);\n\n return defaultResponse;\n }\n\n // Reject the promise\n if (errorHandlingStrategy === 'reject') {\n return Promise.reject(error);\n }\n\n return defaultResponse;\n }\n\n /**\n * Output error response depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @returns {boolean} True if request is aborted\n */\n public isRequestCancelled(error: ResponseError): boolean {\n return error.name === 'AbortError' || error.name === 'CanceledError';\n }\n\n /**\n * Detects if a custom fetcher is utilized\n *\n * @returns {boolean} True if it's a custom fetcher\n */\n protected isCustomFetcher(): boolean {\n return this.fetcher !== null;\n }\n\n /**\n * Automatically Cancel Previous Requests using AbortController when \"cancellable\" is defined\n *\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {Object} Controller Signal to abort\n */\n protected addCancellationToken(\n requestConfig: RequestConfig,\n ): Partial> {\n // Both disabled\n if (!this.cancellable && !requestConfig.cancellable) {\n return {};\n }\n\n // Explicitly disabled per request\n if (\n typeof requestConfig.cancellable !== 'undefined' &&\n !requestConfig.cancellable\n ) {\n return {};\n }\n\n // Check if AbortController is available\n if (typeof AbortController === 'undefined') {\n console.error('AbortController is unavailable.');\n\n return {};\n }\n\n // Generate unique key as a cancellation token\n const previousRequest = this.requestsQueue.get(requestConfig);\n\n if (previousRequest) {\n previousRequest.abort();\n }\n\n const controller = new AbortController();\n\n // Introduce timeout for native fetch\n if (!this.isCustomFetcher() && this.timeout > 0) {\n const abortTimeout = setTimeout(() => {\n const error = new Error(\n `[TimeoutError]: The ${requestConfig.url} request was aborted due to timeout`,\n );\n\n error.name = 'TimeoutError';\n (error as any).code = 23; // DOMException.TIMEOUT_ERR\n controller.abort(error);\n clearTimeout(abortTimeout);\n throw error;\n }, requestConfig.timeout || this.timeout);\n }\n\n this.requestsQueue.set(requestConfig, controller);\n\n return {\n signal: controller.signal,\n };\n }\n\n /**\n * Handle Request depending on used strategy\n *\n * @param {string} url - Request url\n * @param {QueryParamsOrBody} data - Request data\n * @param {RequestConfig} config - Request config\n * @param {RequestConfig} payload.config Request config\n * @throws {ResponseError}\n * @returns {Promise>} Response Data\n */\n public async request(\n url: string,\n data: QueryParamsOrBody = null,\n config: RequestConfig = null,\n ): Promise> {\n let response: FetchResponse = null;\n const _config = config || {};\n const _requestConfig = this.buildConfig(url, data, _config);\n\n let requestConfig: RequestConfig = {\n ...this.addCancellationToken(_requestConfig),\n ..._requestConfig,\n };\n\n const { retries, delay, backoff, retryOn, shouldRetry, maxDelay } = {\n ...this.retry,\n ...(requestConfig?.retry || {}),\n };\n\n let attempt = 0;\n let waitTime = delay;\n\n while (attempt <= retries) {\n try {\n // Local interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n requestConfig.onRequest,\n );\n\n // Global interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n this.config.onRequest,\n );\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n response = (await (this.requestInstance as any).request(\n requestConfig,\n )) as FetchResponse;\n } else {\n response = (await globalThis.fetch(\n requestConfig.url,\n requestConfig,\n )) as FetchResponse;\n\n // Add more information to response object\n response.config = requestConfig;\n response.data = await this.parseData(response);\n\n // Check if the response status is not outside the range 200-299 and if so, output error\n if (!response.ok) {\n throw new ResponseErr(\n `${requestConfig.url} failed! Status: ${response.status || null}`,\n requestConfig,\n response,\n );\n }\n }\n\n // Local interceptors\n response = await interceptResponse(response, requestConfig.onResponse);\n\n // Global interceptors\n response = await interceptResponse(response, this.config.onResponse);\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n } catch (error) {\n if (\n attempt === retries ||\n !(await shouldRetry(error, attempt)) ||\n !retryOn?.includes(error?.response?.status || error?.status)\n ) {\n this.processError(error, requestConfig);\n\n return this.outputErrorResponse(error, requestConfig);\n }\n\n if (this.logger?.warn) {\n this.logger.warn(\n `Attempt ${attempt + 1} failed. Retrying in ${waitTime}ms...`,\n );\n }\n\n await delayInvocation(waitTime);\n\n waitTime *= backoff;\n waitTime = Math.min(waitTime, maxDelay);\n attempt++;\n }\n }\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n }\n\n /**\n * Parses the response data based on the Content-Type header.\n *\n * @param response - The Response object to parse.\n * @returns A Promise that resolves to the parsed data.\n */\n public async parseData(\n response: FetchResponse,\n ): Promise {\n const contentType = String(\n (response as Response).headers?.get('Content-Type') || '',\n );\n let data;\n\n // Handle edge case of no content type being provided... We assume JSON here.\n if (!contentType) {\n const responseClone = response.clone();\n try {\n data = await responseClone.json();\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (_error) {\n // JSON parsing failed, fallback to null\n data = null;\n }\n }\n\n if (typeof data === 'undefined') {\n try {\n if (\n contentType.includes(APPLICATION_JSON) ||\n contentType.includes('+json')\n ) {\n data = await response.json(); // Parse JSON response\n } else if (contentType.includes('multipart/form-data')) {\n data = await response.formData(); // Parse as FormData\n } else if (contentType.includes('application/octet-stream')) {\n data = await response.blob(); // Parse as blob\n } else if (contentType.includes('application/x-www-form-urlencoded')) {\n data = await response.formData(); // Handle URL-encoded forms\n } else if (typeof response.text === 'function') {\n data = await response.text(); // Parse as text\n } else {\n // Handle streams\n data = response.body || response.data || null;\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (_error) {\n // Parsing failed, fallback to null\n data = null;\n }\n }\n\n return data;\n }\n\n public processHeaders(\n response: FetchResponse,\n ): HeadersObject {\n if (!response.headers) {\n return {};\n }\n\n let headersObject: HeadersObject = {};\n const headers = response.headers;\n\n // Handle Headers object with entries() method\n if (headers instanceof Headers) {\n for (const [key, value] of (headers as any).entries()) {\n headersObject[key] = value;\n }\n } else {\n // Handle plain object\n headersObject = { ...(headers as HeadersObject) };\n }\n\n return headersObject;\n }\n\n /**\n * Output response\n *\n * @param response - Response payload\n * @param {RequestConfig} requestConfig - Request config\n * @param {*} error - whether the response is erroneous\n * @returns {ResponseData | FetchResponse} Response data\n */\n protected outputResponse(\n response: FetchResponse,\n requestConfig: RequestConfig,\n error = null,\n ): ResponseData | FetchResponse {\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n if (!response) {\n return defaultResponse;\n }\n\n if (\n (requestConfig.flattenResponse || this.flattenResponse) &&\n typeof response.data !== 'undefined'\n ) {\n // Special case of only data property within response data object (happens in Axios)\n // This is in fact a proper response but we may want to flatten it\n // To ease developers' lives when obtaining the response\n if (\n response.data !== null &&\n typeof response.data === 'object' &&\n typeof (response.data as any).data !== 'undefined' &&\n Object.keys(response.data).length === 1\n ) {\n return (response.data as any).data;\n }\n\n return response.data;\n }\n\n // If empty object is returned, ensure that the default response is used instead\n if (\n response !== null &&\n typeof response === 'object' &&\n response.constructor === Object &&\n Object.keys(response).length === 0\n ) {\n return defaultResponse;\n }\n\n const isCustomFetcher = this.isCustomFetcher();\n\n if (isCustomFetcher) {\n return response;\n }\n\n if (error !== null) {\n delete error?.response;\n delete error?.request;\n delete error?.config;\n }\n\n // Native fetch()\n return {\n body: response.body,\n blob: response.blob,\n json: response.json,\n text: response.text,\n clone: response.clone,\n bodyUsed: response.bodyUsed,\n arrayBuffer: response.arrayBuffer,\n formData: response.formData,\n ok: response.ok,\n redirected: response.redirected,\n type: response.type,\n url: response.url,\n status: response.status,\n statusText: response.statusText,\n\n // Extend with extra information\n error,\n data: response.data,\n headers: this.processHeaders(response),\n config: requestConfig,\n };\n }\n}\n","import { RequestHandler } from './request-handler';\nimport type {\n FetcherInstance,\n RequestConfig,\n FetchResponse,\n} from './types/request-handler';\nimport type {\n ApiHandlerConfig,\n ApiHandlerMethods,\n ApiHandlerReturnType,\n APIResponse,\n QueryParams,\n UrlPathParams,\n} from './types/api-handler';\n\n/**\n * Creates an instance of API Handler.\n * It creates an API fetcher function using native fetch() or Axios if it is passed as \"fetcher\".\n *\n * @param {Object} config - Configuration object for the API fetcher.\n * @param {string} config.apiUrl - The base URL for the API.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n * @returns API handler functions and endpoints to call\n *\n * @example\n * // Import axios (optional)\n * import axios from 'axios';\n *\n * // Define endpoint paths\n * const endpoints = {\n * getUser: '/user',\n * createPost: '/post',\n * };\n *\n * // Create the API fetcher with configuration\n * const api = createApiFetcher({\n * fetcher: axios, // Axios instance (optional)\n * endpoints,\n * apiUrl: 'https://example.com/api',\n * onError(error) {\n * console.log('Request failed', error);\n * },\n * headers: {\n * 'my-auth-key': 'example-auth-key-32rjjfa',\n * },\n * });\n *\n * // Fetch user data\n * const response = await api.getUser({ userId: 1, ratings: [1, 2] })\n */\nfunction createApiFetcher<\n EndpointsMethods extends object,\n EndpointsCfg = never,\n>(config: ApiHandlerConfig) {\n const endpoints = config.endpoints;\n const requestHandler = new RequestHandler(config);\n\n /**\n * Get Fetcher Provider Instance\n *\n * @returns {FetcherInstance} Request Handler's Fetcher instance\n */\n function getInstance(): FetcherInstance {\n return requestHandler.getInstance();\n }\n\n /**\n * Triggered when trying to use non-existent endpoints\n *\n * @param endpointName Endpoint Name\n * @returns {Promise}\n */\n function handleNonImplemented(endpointName: string): Promise {\n console.error(`${endpointName} endpoint must be added to 'endpoints'.`);\n\n return Promise.resolve(null);\n }\n\n /**\n * Handle Single API Request\n * It considers settings in following order: per-request settings, global per-endpoint settings, global settings.\n *\n * @param {string} endpointName - The name of the API endpoint to call.\n * @param {QueryParams} [queryParams={}] - Query parameters to include in the request.\n * @param {UrlPathParams} [urlPathParams={}] - URI parameters to include in the request.\n * @param {EndpointConfig} [requestConfig={}] - Additional configuration for the request.\n * @returns {Promise} - A promise that resolves with the response from the API provider.\n */\n async function request(\n endpointName: keyof EndpointsMethods | string,\n queryParams: QueryParams = {},\n urlPathParams: UrlPathParams = {},\n requestConfig: RequestConfig = {},\n ): Promise> {\n // Use global per-endpoint settings\n const endpointConfig = endpoints[endpointName as string];\n const endpointSettings = { ...endpointConfig };\n\n const responseData = await requestHandler.request(\n endpointSettings.url,\n queryParams,\n {\n ...endpointSettings,\n ...requestConfig,\n urlPathParams,\n },\n );\n\n return responseData;\n }\n\n /**\n * Maps all API requests using native Proxy\n *\n * @param {*} prop Caller\n */\n function get(prop: string | symbol) {\n if (prop in apiHandler) {\n return apiHandler[prop];\n }\n\n // Prevent handler from triggering non-existent endpoints\n if (!endpoints[prop as string]) {\n return handleNonImplemented.bind(null, prop);\n }\n\n return apiHandler.request.bind(null, prop);\n }\n\n const apiHandler: ApiHandlerMethods = {\n config,\n endpoints,\n requestHandler,\n getInstance,\n request,\n };\n\n return new Proxy(apiHandler, {\n get: (_target, prop) => get(prop),\n }) as ApiHandlerReturnType;\n}\n\nexport { createApiFetcher };\n"],"mappings":"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","names":["src_exports","__export","createApiFetcher","fetchf","__toCommonJS","interceptRequest","config","interceptors","interceptorList","interceptedConfig","interceptor","interceptResponse","response","interceptedResponse","ResponseErr","message","requestInfo","response","appendQueryParams","url","params","add","k","v","buildParams","prefix","obj","len","key","encodedQueryString","replaceUrlPathParams","urlPathParams","str","word","isJSONSerializable","value","t","proto","delayInvocation","ms","resolve","APPLICATION_JSON","RequestHandler","fetcher","timeout","rejectCancelled","strategy","flattenResponse","defaultResponse","logger","onError","config","url","data","method","methodLowerCase","isGetAlikeMethod","dynamicUrl","replaceUrlPathParams","configData","payload","credentials","urlPath","appendQueryParams","baseURL","isJSONSerializable","error","requestConfig","_a","isRequestCancelled","errorHandlingStrategy","previousRequest","controller","abortTimeout","_b","response","_config","_requestConfig","retries","delay","backoff","retryOn","shouldRetry","maxDelay","attempt","waitTime","interceptRequest","ResponseErr","interceptResponse","delayInvocation","contentType","responseClone","headersObject","headers","key","value","createApiFetcher","config","endpoints","requestHandler","RequestHandler","getInstance","handleNonImplemented","endpointName","request","queryParams","urlPathParams","requestConfig","endpointSettings","get","prop","apiHandler","_target","fetchf","url","config","RequestHandler"]} \ No newline at end of file