Comparing version 0.0.6 to 0.0.7
@@ -19,2 +19,3 @@ export { merge } from 'ts-deepmerge'; | ||
data?: any; | ||
/** encoded url with params */ | ||
_url?: string; | ||
@@ -37,2 +38,41 @@ } | ||
/** Code Ref: https://github.com/jacobheun/any-signal/pull/40/files */ | ||
interface ClearableSignal extends AbortSignal { | ||
clear: () => void; | ||
} | ||
/** | ||
* Takes an array of AbortSignals and returns a single signal. | ||
* If any signals are aborted, the returned signal will be aborted. | ||
*/ | ||
declare function anySignal(signals: (AbortSignal | undefined | null)[], cleanCb?: Function): ClearableSignal; | ||
declare function buildSortedURL(_url: string, data: any, encode: (obj: Record<string, any>) => string): string; | ||
declare function delay(ms: number): Promise<unknown>; | ||
type ICacheLike<T> = { | ||
get(key: string): T | undefined; | ||
set(key: string, value: T): void; | ||
} & ({ | ||
del(key: string): void; | ||
} | { | ||
delete(key: string): void; | ||
}); | ||
declare function encodeParams<T = any>(params: T, encodeURI?: boolean, parentKey?: string | null): string; | ||
/** | ||
* Determines whether the specified URL is absolute | ||
* | ||
* @param {string} url The URL to test | ||
* | ||
* @returns {boolean} True if the specified URL is absolute, otherwise false | ||
*/ | ||
declare function isAbsoluteURL(url: string): boolean; | ||
declare class XiorTimeoutError extends Error { | ||
} | ||
declare class XiorError extends Error { | ||
request?: XiorRequestConfig; | ||
response?: XiorResponse; | ||
constructor(message: string, request?: XiorRequestConfig, response?: XiorResponse); | ||
} | ||
declare class xior { | ||
@@ -46,3 +86,5 @@ static create(options?: XiorRequestConfig): xior; | ||
request: { | ||
use: (fn: (config: XiorInterceptorRequestConfig) => Promise<XiorInterceptorRequestConfig> | XiorInterceptorRequestConfig, onRejected?: ((error: any) => any) | undefined) => void; | ||
use: (fn: (config: XiorInterceptorRequestConfig) => Promise<XiorInterceptorRequestConfig> | XiorInterceptorRequestConfig, onRejected?: ((error: any) => any) | undefined) => (config: XiorInterceptorRequestConfig) => Promise<XiorInterceptorRequestConfig> | XiorInterceptorRequestConfig; | ||
eject: (fn: (config: XiorInterceptorRequestConfig) => Promise<XiorInterceptorRequestConfig> | XiorInterceptorRequestConfig) => void; | ||
clear: () => void; | ||
}; | ||
@@ -62,3 +104,29 @@ response: { | ||
response: Response; | ||
}, onRejected?: ((error: any) => any) | undefined) => void; | ||
}, onRejected?: ((error: XiorError) => any) | undefined) => (config: { | ||
data: any; | ||
request: XiorInterceptorRequestConfig; | ||
response: Response; | ||
}) => Promise<{ | ||
data: any; | ||
request: XiorInterceptorRequestConfig; | ||
response: Response; | ||
}> | { | ||
data: any; | ||
request: XiorInterceptorRequestConfig; | ||
response: Response; | ||
}; | ||
eject: (fn: (config: { | ||
data: any; | ||
request: XiorInterceptorRequestConfig; | ||
response: Response; | ||
}) => Promise<{ | ||
data: any; | ||
request: XiorInterceptorRequestConfig; | ||
response: Response; | ||
}> | { | ||
data: any; | ||
request: XiorInterceptorRequestConfig; | ||
response: Response; | ||
}) => void; | ||
clear: () => void; | ||
}; | ||
@@ -68,3 +136,5 @@ }; | ||
get plugins(): { | ||
use: (plugin: XiorPlugin) => void; | ||
use: (plugin: XiorPlugin) => XiorPlugin; | ||
eject: (plugin: XiorPlugin) => void; | ||
clear: () => void; | ||
}; | ||
@@ -84,29 +154,2 @@ request<T>(options?: XiorRequestConfig | string): Promise<XiorResponse<T>>; | ||
/** Code Ref: https://github.com/jacobheun/any-signal/pull/40/files */ | ||
interface ClearableSignal extends AbortSignal { | ||
clear: () => void; | ||
} | ||
/** | ||
* Takes an array of AbortSignals and returns a single signal. | ||
* If any signals are aborted, the returned signal will be aborted. | ||
*/ | ||
declare function anySignal(signals: (AbortSignal | undefined | null)[], cleanCb?: Function): ClearableSignal; | ||
declare function encode(obj: Record<string, any>, encodeURI?: boolean): string; | ||
/** | ||
* Determines whether the specified URL is absolute | ||
* | ||
* @param {string} url The URL to test | ||
* | ||
* @returns {boolean} True if the specified URL is absolute, otherwise false | ||
*/ | ||
declare function isAbsoluteURL(url: string): boolean; | ||
declare class XiorTimeoutError extends Error { | ||
} | ||
declare class XiorError extends Error { | ||
request?: XiorRequestConfig; | ||
response?: XiorResponse; | ||
constructor(message: string, request?: XiorRequestConfig, response?: XiorResponse); | ||
} | ||
export { type ClearableSignal, XiorError, type XiorInterceptorRequestConfig, type XiorPlugin, type XiorRequestConfig, type XiorResponse, XiorTimeoutError, anySignal, xior as default, encode, isAbsoluteURL, xior }; | ||
export { type ClearableSignal, type ICacheLike, XiorError, type XiorInterceptorRequestConfig, type XiorPlugin, type XiorRequestConfig, type XiorResponse, XiorTimeoutError, anySignal, buildSortedURL, xior as default, delay, encodeParams, isAbsoluteURL, xior }; |
@@ -1,1 +0,1 @@ | ||
import{merge as e}from"ts-deepmerge";export{merge}from"ts-deepmerge";function t(e,t){let r=new globalThis.AbortController;function n(e){r.abort(e),o()}let s=[];for(let t of e){if((null==t?void 0:t.aborted)===!0){n(t.reason);break}if((null==t?void 0:t.addEventListener)!=null){let e=()=>{n(t.reason)};s.push(()=>{(null==t?void 0:t.removeEventListener)!=null&&t.removeEventListener("abort",e)}),t.addEventListener("abort",e)}}function o(){s.forEach(e=>{e()}),null==t||t()}let a=r.signal;return a.clear=o,a}function r(e,t=!0){let r=[];return"object"==typeof e&&null!==e&&Object.keys(e).map(n=>(function(e,t,r,n){let s=e[t];if(null===s||s instanceof Function)return;let o=n?encodeURIComponent(t)+"="+encodeURIComponent(s):t+"="+s;r.push(o)})(e,n,r,t)),r.join("&")}function n(e){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(e)}class s extends Error{}class o extends Error{constructor(e,t,r){super(e),this.request=t,this.response=r}}function a(e,t,r,n,s,o,a){try{var i=e[o](a),u=i.value}catch(e){r(e);return}i.done?t(u):Promise.resolve(u).then(n,s)}function i(){return(i=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e}).apply(this,arguments)}let u="application/x-www-form-urlencoded",l="application/json";function c(e){return d.apply(this,arguments)}function d(){var t;return t=function*(t){let n=t.encode||r,s=!1!==t.encodeURI,o=t.method?t.method.toUpperCase():"GET",a=t.url||"",c=a,d=t.data,p=!1,h=(null==t?void 0:t.headers)||{};if(d&&!(d instanceof FormData)){let r="";if(null==t?void 0:t.headers){let e=Object.keys(t.headers).find(e=>"content-type"===e.toLowerCase());e&&(r=t.headers[e])}if(r||(r="GET"===o||"HEAD"===o?u:l,h["Content-Type"]=r),r===u&&("object"==typeof d||t.params)){p=!0;let r=n(e(d||{},t.params||{}),s);r&&(a=a.includes("?")?a+=`&${r}`:`${a}?${r}`),d=null}else r===l&&(d=JSON.stringify(d))}return!p&&t.params&&Object.keys(t.params).length>0&&(a=a.includes("?")?a+=`&${n(t.params,s)}`:`${a}?${n(t.params,s)}`),i({},t,{data:d,url:c,_url:a,method:o,headers:h})},(d=function(){var e=this,r=arguments;return new Promise(function(n,s){var o=t.apply(e,r);function i(e){a(o,n,s,i,u,"next",e)}function u(e){a(o,n,s,i,u,"throw",e)}i(void 0)})}).apply(this,arguments)}function p(e,t,r,n,s,o,a){try{var i=e[o](a),u=i.value}catch(e){r(e);return}i.done?t(u):Promise.resolve(u).then(n,s)}function h(e){return function(){var t=this,r=arguments;return new Promise(function(n,s){var o=e.apply(t,r);function a(e){p(o,n,s,a,i,"next",e)}function i(e){p(o,n,s,a,i,"throw",e)}a(void 0)})}}function f(){return(f=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e}).apply(this,arguments)}let v="undefined"!=typeof AbortController;class y{static create(e){return new y(e)}get interceptors(){return{request:{use:(e,t)=>{this.requestInterceptors.push(e)}},response:{use:(e,t)=>{this.responseInterceptors.push({fn:e,onRejected:t})}}}}get plugins(){return{use:e=>{this._plugins.push(e)}}}request(t){var r=this;return h(function*(){let n=e(r.config||{},"string"==typeof t?{url:t}:t||{},{headers:{},params:{}});for(let e of r.requestInterceptors)n=yield e(n);let s=r.handlerFetch.bind(r);return r._plugins.forEach(e=>{s=e(s)}),s(n)})()}handlerFetch(e){var r=this;return h(function*(){var a;let i,u;let{url:l,method:c,headers:d,timeout:p,signal:h,data:y}=e,m=function(e,t){if(null==e)return{};var r,n,s={},o=Object.keys(e);for(n=0;n<o.length;n++)t.indexOf(r=o[n])>=0||(s[r]=e[r]);return s}(e,["url","method","headers","timeout","signal","data"]),g=[];if(p&&v){let e=new AbortController;u=setTimeout(()=>{e.abort(new s(`timeout of ${p}ms exceeded`))},p),g.push(e.signal)}h&&g.push(h),i=g[0],g.length>1&&(i=t(g,()=>{clearTimeout(u)}));let T=e._url||l||"";e.baseURL&&!n(T)&&(T=(e.baseURL.endsWith("/")?e.baseURL:e.baseURL+"/")+(T.startsWith("/")?T.slice(1):T));let b=yield fetch(T,f({body:["HEAD","GET"].includes(e.method||"GET")?void 0:y},m,{signal:i,method:c,headers:d}));if(u&&clearTimeout(u),null==i||null==(a=i.clear)||a.call(i),!b.ok){let t;try{(t=yield b.text())&&(t=JSON.parse(t))}catch(e){}let n=new o(b.status?`Request failed with status code ${b.status}`:"Network error",e,{response:b,data:t,config:e,status:b.status,statusText:b.statusText,headers:b.headers});for(let e of r.responseInterceptors)e.onRejected&&(yield e.onRejected(n));throw n}if("HEAD"===e.method)return{data:void 0,request:e,config:e,response:b,headers:b.headers,status:b.status,statusText:b.statusText};if(!e.responseType||"json"===e.responseType||"text"===e.responseType){let t;try{t={data:yield b.text()};try{t.data&&"text"!==e.responseType&&(t.data=JSON.parse(t.data))}catch(e){}}catch(e){t={data:void 0}}let n={data:t.data,request:e,response:b};for(let e of r.responseInterceptors)n=yield e.fn(n);return{data:n.data,request:e,config:e,response:b,headers:b.headers,status:b.status,statusText:b.statusText}}return{data:void 0,request:e,config:e,response:b,headers:b.headers,status:b.status,statusText:b.statusText}})()}createGetHandler(e){return(t,r)=>this.request(r?f({},r,{method:e,url:t}):{method:e,url:t})}createPostHandler(e){return(t,r,n)=>this.request(n?f({},n,{method:e,url:t,data:r}):{method:e,url:t,data:r})}get(e,t){return this.createGetHandler("GET")(e,t)}head(e,t){return this.createGetHandler("HEAD")(e,t)}post(e,t,r){return this.createPostHandler("POST")(e,t,r)}put(e,t,r){return this.createPostHandler("PUT")(e,t,r)}patch(e,t,r){return this.createPostHandler("PATCH")(e,t,r)}delete(e,t,r){return this.createPostHandler("DELETE")(e,t,r)}options(e,t,r){return this.createPostHandler("OPTIONS")(e,t,r)}constructor(e){this.requestInterceptors=[c],this.responseInterceptors=[],this._plugins=[],this.config=e}}export{o as XiorError,s as XiorTimeoutError,t as anySignal,y as default,r as encode,n as isAbsoluteURL,y as xior}; | ||
import{merge as e}from"ts-deepmerge";export{merge}from"ts-deepmerge";function t(e,t){let r=new globalThis.AbortController;function s(e){r.abort(e),o()}let n=[];for(let t of e){if((null==t?void 0:t.aborted)===!0){s(t.reason);break}if((null==t?void 0:t.addEventListener)!=null){let e=()=>{s(t.reason)};n.push(()=>{(null==t?void 0:t.removeEventListener)!=null&&t.removeEventListener("abort",e)}),t.addEventListener("abort",e)}}function o(){n.forEach(e=>{e()}),null==t||t()}let a=r.signal;return a.clear=o,a}function r(e,t,r){let s=t?r(t):"";s&&(s=e.includes("?")?e+"&"+s:e+"?"+s);let[n,o]=s.split("?");if(o){let e=o.split("&");return`${n}?${e.sort().join("&")}`}return s||e}function s(e){return new Promise(t=>{setTimeout(()=>{t("ok")},e)})}function n(e,t=!0,r=null){if(null==e)return"";let s=[],o=t?encodeURIComponent:e=>e;for(let a in e)if(Object.prototype.hasOwnProperty.call(e,a)){let i=e[a],u=r?`${r}[${o(a)}]`:o(a);if("object"==typeof i){let e=n(i,t,u);""!==e&&s.push(e)}else Array.isArray(i)?i.forEach((e,t)=>{let r=`${u}[${t}]`;s.push(`${o(r)}=${o(e)}`)}):s.push(`${o(u)}=${o(i)}`)}return s.join("&")}function o(e){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(e)}class a extends Error{}class i extends Error{constructor(e,t,r){super(e),this.request=t,this.response=r}}function u(e,t,r,s,n,o,a){try{var i=e[o](a),u=i.value}catch(e){r(e);return}i.done?t(u):Promise.resolve(u).then(s,n)}function l(){return(l=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var s in r)Object.prototype.hasOwnProperty.call(r,s)&&(e[s]=r[s])}return e}).apply(this,arguments)}let c="application/x-www-form-urlencoded",p="application/json";function d(e){return h.apply(this,arguments)}function h(){var t;return t=function*(t){let r=t.encode||n,s=!1!==t.encodeURI,o=t.method?t.method.toUpperCase():"GET",a=t.url||"",i=a,u=t.data,d=!1,h=(null==t?void 0:t.headers)||{};if(u&&!(u instanceof FormData)){let n="";if(null==t?void 0:t.headers){let e=Object.keys(t.headers).find(e=>"content-type"===e.toLowerCase());e&&(n=t.headers[e])}if(n||(n="GET"===o||"HEAD"===o?c:p,h["Content-Type"]=n),n===c&&("object"==typeof u||t.params)){d=!0;let n=r(e(u||{},t.params||{}),s);n&&(a=a.includes("?")?a+=`&${n}`:`${a}?${n}`),u=null}else n===p&&(u=JSON.stringify(u))}return!d&&t.params&&Object.keys(t.params).length>0&&(a=a.includes("?")?a+=`&${r(t.params,s)}`:`${a}?${r(t.params,s)}`),l({},t,{data:u,url:i,_url:a,method:o,headers:h})},(h=function(){var e=this,r=arguments;return new Promise(function(s,n){var o=t.apply(e,r);function a(e){u(o,s,n,a,i,"next",e)}function i(e){u(o,s,n,a,i,"throw",e)}a(void 0)})}).apply(this,arguments)}function f(e,t,r,s,n,o,a){try{var i=e[o](a),u=i.value}catch(e){r(e);return}i.done?t(u):Promise.resolve(u).then(s,n)}function y(e){return function(){var t=this,r=arguments;return new Promise(function(s,n){var o=e.apply(t,r);function a(e){f(o,s,n,a,i,"next",e)}function i(e){f(o,s,n,a,i,"throw",e)}a(void 0)})}}function v(){return(v=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var s in r)Object.prototype.hasOwnProperty.call(r,s)&&(e[s]=r[s])}return e}).apply(this,arguments)}let m="undefined"!=typeof AbortController;class g{static create(e){return new g(e)}get interceptors(){return{request:{use:(e,t)=>(this.requestInterceptors.push(e),e),eject:e=>{this.requestInterceptors=this.requestInterceptors.filter(t=>t!==e)},clear:()=>{this.requestInterceptors=[this.requestInterceptors[0]]}},response:{use:(e,t)=>(this.responseInterceptors.push({fn:e,onRejected:t}),e),eject:e=>{this.responseInterceptors=this.responseInterceptors.filter(t=>t.fn!==e)},clear:()=>{this.responseInterceptors=[]}}}}get plugins(){return{use:e=>(this._plugins.push(e),e),eject:e=>{this._plugins=this._plugins.filter(t=>t!==e)},clear:()=>{this._plugins=[]}}}request(t){var r=this;return y(function*(){let s=e(r.config||{},"string"==typeof t?{url:t}:t||{},{headers:{},params:{}});for(let e of r.requestInterceptors)s=yield e(s);let n=r.handlerFetch.bind(r);return r._plugins.forEach(e=>{n=e(n)}),n(s)})()}handlerFetch(e){var r=this;return y(function*(){var s;let n,u;let{url:l,method:c,headers:p,timeout:d,signal:h,data:f}=e,y=function(e,t){if(null==e)return{};var r,s,n={},o=Object.keys(e);for(s=0;s<o.length;s++)t.indexOf(r=o[s])>=0||(n[r]=e[r]);return n}(e,["url","method","headers","timeout","signal","data"]),g=[];if(d&&m){let e=new AbortController;u=setTimeout(()=>{e.abort(new a(`timeout of ${d}ms exceeded`))},d),g.push(e.signal)}h&&g.push(h),n=g[0],g.length>1&&(n=t(g,()=>{clearTimeout(u)}));let T=e._url||l||"";e.baseURL&&!o(T)&&(T=(e.baseURL.endsWith("/")?e.baseURL:e.baseURL+"/")+(T.startsWith("/")?T.slice(1):T));let b=yield fetch(T,v({body:["HEAD","GET"].includes(e.method||"GET")?void 0:f},y,{signal:n,method:c,headers:p}));if(u&&clearTimeout(u),null==n||null==(s=n.clear)||s.call(n),!b.ok){let t;try{(t=yield b.text())&&(t=JSON.parse(t))}catch(e){}let s=new i(b.status?`Request failed with status code ${b.status}`:"Network error",e,{response:b,data:t,config:e,status:b.status,statusText:b.statusText,headers:b.headers});for(let e of r.responseInterceptors)e.onRejected&&(yield e.onRejected(s));throw s}if("HEAD"===e.method)return{data:void 0,request:e,config:e,response:b,headers:b.headers,status:b.status,statusText:b.statusText};if(!e.responseType||"json"===e.responseType||"text"===e.responseType){let t;try{t={data:yield b.text()};try{t.data&&"text"!==e.responseType&&(t.data=JSON.parse(t.data))}catch(e){}}catch(e){t={data:void 0}}let s={data:t.data,request:e,response:b};for(let e of r.responseInterceptors)s=yield e.fn(s);return{data:s.data,request:e,config:e,response:b,headers:b.headers,status:b.status,statusText:b.statusText}}return{data:void 0,request:e,config:e,response:b,headers:b.headers,status:b.status,statusText:b.statusText}})()}createGetHandler(e){return(t,r)=>this.request(r?v({},r,{method:e,url:t}):{method:e,url:t})}createPostHandler(e){return(t,r,s)=>this.request(s?v({},s,{method:e,url:t,data:r}):{method:e,url:t,data:r})}get(e,t){return this.createGetHandler("GET")(e,t)}head(e,t){return this.createGetHandler("HEAD")(e,t)}post(e,t,r){return this.createPostHandler("POST")(e,t,r)}put(e,t,r){return this.createPostHandler("PUT")(e,t,r)}patch(e,t,r){return this.createPostHandler("PATCH")(e,t,r)}delete(e,t,r){return this.createPostHandler("DELETE")(e,t,r)}options(e,t,r){return this.createPostHandler("OPTIONS")(e,t,r)}constructor(e){this.requestInterceptors=[d],this.responseInterceptors=[],this._plugins=[],this.config=e}}export{i as XiorError,a as XiorTimeoutError,t as anySignal,r as buildSortedURL,g as default,s as delay,n as encodeParams,o as isAbsoluteURL,g as xior}; |
@@ -19,2 +19,3 @@ export { merge } from 'ts-deepmerge'; | ||
data?: any; | ||
/** encoded url with params */ | ||
_url?: string; | ||
@@ -37,2 +38,41 @@ } | ||
/** Code Ref: https://github.com/jacobheun/any-signal/pull/40/files */ | ||
interface ClearableSignal extends AbortSignal { | ||
clear: () => void; | ||
} | ||
/** | ||
* Takes an array of AbortSignals and returns a single signal. | ||
* If any signals are aborted, the returned signal will be aborted. | ||
*/ | ||
declare function anySignal(signals: (AbortSignal | undefined | null)[], cleanCb?: Function): ClearableSignal; | ||
declare function buildSortedURL(_url: string, data: any, encode: (obj: Record<string, any>) => string): string; | ||
declare function delay(ms: number): Promise<unknown>; | ||
type ICacheLike<T> = { | ||
get(key: string): T | undefined; | ||
set(key: string, value: T): void; | ||
} & ({ | ||
del(key: string): void; | ||
} | { | ||
delete(key: string): void; | ||
}); | ||
declare function encodeParams<T = any>(params: T, encodeURI?: boolean, parentKey?: string | null): string; | ||
/** | ||
* Determines whether the specified URL is absolute | ||
* | ||
* @param {string} url The URL to test | ||
* | ||
* @returns {boolean} True if the specified URL is absolute, otherwise false | ||
*/ | ||
declare function isAbsoluteURL(url: string): boolean; | ||
declare class XiorTimeoutError extends Error { | ||
} | ||
declare class XiorError extends Error { | ||
request?: XiorRequestConfig; | ||
response?: XiorResponse; | ||
constructor(message: string, request?: XiorRequestConfig, response?: XiorResponse); | ||
} | ||
declare class xior { | ||
@@ -46,3 +86,5 @@ static create(options?: XiorRequestConfig): xior; | ||
request: { | ||
use: (fn: (config: XiorInterceptorRequestConfig) => Promise<XiorInterceptorRequestConfig> | XiorInterceptorRequestConfig, onRejected?: ((error: any) => any) | undefined) => void; | ||
use: (fn: (config: XiorInterceptorRequestConfig) => Promise<XiorInterceptorRequestConfig> | XiorInterceptorRequestConfig, onRejected?: ((error: any) => any) | undefined) => (config: XiorInterceptorRequestConfig) => Promise<XiorInterceptorRequestConfig> | XiorInterceptorRequestConfig; | ||
eject: (fn: (config: XiorInterceptorRequestConfig) => Promise<XiorInterceptorRequestConfig> | XiorInterceptorRequestConfig) => void; | ||
clear: () => void; | ||
}; | ||
@@ -62,3 +104,29 @@ response: { | ||
response: Response; | ||
}, onRejected?: ((error: any) => any) | undefined) => void; | ||
}, onRejected?: ((error: XiorError) => any) | undefined) => (config: { | ||
data: any; | ||
request: XiorInterceptorRequestConfig; | ||
response: Response; | ||
}) => Promise<{ | ||
data: any; | ||
request: XiorInterceptorRequestConfig; | ||
response: Response; | ||
}> | { | ||
data: any; | ||
request: XiorInterceptorRequestConfig; | ||
response: Response; | ||
}; | ||
eject: (fn: (config: { | ||
data: any; | ||
request: XiorInterceptorRequestConfig; | ||
response: Response; | ||
}) => Promise<{ | ||
data: any; | ||
request: XiorInterceptorRequestConfig; | ||
response: Response; | ||
}> | { | ||
data: any; | ||
request: XiorInterceptorRequestConfig; | ||
response: Response; | ||
}) => void; | ||
clear: () => void; | ||
}; | ||
@@ -68,3 +136,5 @@ }; | ||
get plugins(): { | ||
use: (plugin: XiorPlugin) => void; | ||
use: (plugin: XiorPlugin) => XiorPlugin; | ||
eject: (plugin: XiorPlugin) => void; | ||
clear: () => void; | ||
}; | ||
@@ -84,29 +154,2 @@ request<T>(options?: XiorRequestConfig | string): Promise<XiorResponse<T>>; | ||
/** Code Ref: https://github.com/jacobheun/any-signal/pull/40/files */ | ||
interface ClearableSignal extends AbortSignal { | ||
clear: () => void; | ||
} | ||
/** | ||
* Takes an array of AbortSignals and returns a single signal. | ||
* If any signals are aborted, the returned signal will be aborted. | ||
*/ | ||
declare function anySignal(signals: (AbortSignal | undefined | null)[], cleanCb?: Function): ClearableSignal; | ||
declare function encode(obj: Record<string, any>, encodeURI?: boolean): string; | ||
/** | ||
* Determines whether the specified URL is absolute | ||
* | ||
* @param {string} url The URL to test | ||
* | ||
* @returns {boolean} True if the specified URL is absolute, otherwise false | ||
*/ | ||
declare function isAbsoluteURL(url: string): boolean; | ||
declare class XiorTimeoutError extends Error { | ||
} | ||
declare class XiorError extends Error { | ||
request?: XiorRequestConfig; | ||
response?: XiorResponse; | ||
constructor(message: string, request?: XiorRequestConfig, response?: XiorResponse); | ||
} | ||
export { type ClearableSignal, XiorError, type XiorInterceptorRequestConfig, type XiorPlugin, type XiorRequestConfig, type XiorResponse, XiorTimeoutError, anySignal, xior as default, encode, isAbsoluteURL, xior }; | ||
export { type ClearableSignal, type ICacheLike, XiorError, type XiorInterceptorRequestConfig, type XiorPlugin, type XiorRequestConfig, type XiorResponse, XiorTimeoutError, anySignal, buildSortedURL, xior as default, delay, encodeParams, isAbsoluteURL, xior }; |
{ | ||
"name": "xior", | ||
"version": "0.0.6", | ||
"description": "Axios similiar API request library but based on fetch", | ||
"version": "0.0.7", | ||
"description": "Axios similiar API request library based on fetch and support plugins", | ||
"repository": "suhaotian/xior", | ||
@@ -12,9 +12,35 @@ "bugs": "https://github.com/suhaotian/xior/issues", | ||
"exports": { | ||
"types": "./dist/types/index.d.ts", | ||
"require": "./dist/index.cjs", | ||
"import": "./dist/index.mjs", | ||
"module": "./dist/index.esm.js" | ||
"./plugins/error-retry": { | ||
"types": "./dist/plugins/error-retry/index.d.ts", | ||
"require": "./dist/plugins/error-retry/index.cjs", | ||
"import": "./dist/plugins/error-retry/index.mjs", | ||
"module": "./dist/plugins/error-retry/index.esm.js" | ||
}, | ||
"./plugins/throttle": { | ||
"types": "./dist/plugins/throttle/index.d.ts", | ||
"require": "./dist/plugins/throttle/index.cjs", | ||
"import": "./dist/plugins/throttle/index.mjs", | ||
"module": "./dist/plugins/throttle/index.esm.js" | ||
}, | ||
"./plugins/cache": { | ||
"types": "./dist/plugins/cache/index.d.ts", | ||
"require": "./dist/plugins/cache/index.cjs", | ||
"import": "./dist/plugins/cache/index.mjs", | ||
"module": "./dist/plugins/cache/index.esm.js" | ||
}, | ||
"./plugins/progress": { | ||
"types": "./dist/plugins/progress/index.d.ts", | ||
"require": "./dist/plugins/progress/index.cjs", | ||
"import": "./dist/plugins/progress/index.mjs", | ||
"module": "./dist/plugins/progress/index.esm.js" | ||
}, | ||
".": { | ||
"types": "./dist/types/index.d.ts", | ||
"require": "./dist/index.cjs", | ||
"import": "./dist/index.mjs", | ||
"module": "./dist/index.esm.js" | ||
} | ||
}, | ||
"scripts": { | ||
"build": "npm run build:lib && bunchee ./src/index.ts -m", | ||
"build": "bunchee -m", | ||
"build:lib": "rm -rf lib && tsc --project tsconfig.json", | ||
@@ -25,3 +51,4 @@ "test": "pnpm build:lib && node --test", | ||
"push": "git push && git lfs push --all origin", | ||
"prepare": "is-ci || pnpm build && husky" | ||
"prepare": "is-ci || pnpm build && husky", | ||
"checksize": "pnpm --filter=vite-example build" | ||
}, | ||
@@ -49,3 +76,3 @@ "dependencies": { | ||
"@types/qs": "^6.9.11", | ||
"bunchee": "^4.4.6", | ||
"bunchee": "^4.4.8", | ||
"lfs-auto-track": "^1.1.0", | ||
@@ -81,4 +108,2 @@ "isomorphic-unfetch": "^4.0.2", | ||
"files": [ | ||
"lib", | ||
"!lib/tests", | ||
"dist" | ||
@@ -88,7 +113,11 @@ ], | ||
"fetch", | ||
"axios", | ||
"axios fetch", | ||
"axios alternatives", | ||
"adapter", | ||
"plugins", | ||
"http", | ||
"edge runtime", | ||
"https", | ||
"network", | ||
"axios", | ||
"axios alternatives", | ||
"url", | ||
@@ -100,3 +129,3 @@ "uri", | ||
"request cache", | ||
"repeat requests filter" | ||
"request throttle" | ||
], | ||
@@ -103,0 +132,0 @@ "author": "suhaotian", |
1154
README.md
[![Build](https://github.com/suhaotian/xior/actions/workflows/check.yml/badge.svg)](https://github.com/suhaotian/xior/actions/workflows/check.yml) | ||
[![minzipped size](https://badgen.net/badge/gzip/2.6kb/green)](https://bundlephobia.com/package/xior) | ||
[![npm version](https://badgen.net/npm/v/xior?color=green)](https://www.npmjs.com/package/xior) | ||
[![minzipped size](https://badgen.net/badge/gzip/2.6kb/green)](https://bundlephobia.com/package/xior) | ||
[![tree shaking](https://badgen.net/bundlephobia/tree-shaking/xior)](https://bundlephobia.com/package/xior) | ||
![Downloads](https://img.shields.io/npm/dm/xior.svg?style=flat) | ||
![typescript](https://badgen.net/badge/icon/typescript?icon=typescript&label&color=blue) | ||
[![dependency](https://badgen.net/bundlephobia/dependency-count/xior)](https://bundlephobia.com/package/xior) | ||
![license](https://badgen.net/npm/license/xior?color=blue) | ||
# Xior | ||
## Intro | ||
An axios similar API request library but use fetch, and more. | ||
A request lib based on **fetch** with plugins support. | ||
Features: | ||
**Features:** | ||
- 🫡 Similiar `axios.create` / `axios.interceptors.request.use` / `axios.interceptors.response.use` / `.get/post/put/patch/delete/head/options` | ||
- 🔥 Use fetch [why `fetch` instead of `axios`?](#why-use-xior) | ||
- 🚀 Lightweight ~6KB, Gzip ~2.6KB | ||
- 🤙 Support timeout and cancel request | ||
- 👊 Unit tests | ||
- 💪 100% Write in TypeScript | ||
- [ ] **❗️❗️❗️WIP** 🥷 Plugins support: error retry, cache, repeat requests filter and easy custom your own plugins 😎 | ||
- 🔥 Use **fetch** | ||
- 🫡 **Similar axios API**: `axios.create` / `axios.interceptors` / `.get/post/put/patch/delete/head/options` | ||
- 🤙 Support timeout and cancel requests | ||
- 🥷 Plugin support: error retry, cache, throttling, and easily create custom plugins | ||
- 🚀 Lightweight (~6KB, Gzip ~2.6KB) | ||
- 👊 Unit tested and strongly typed 💪 | ||
## Table of Contents | ||
- [Intro](#intro) | ||
- [Table of Contents](#table-of-contents) | ||
- [Why Choose **xior**?](#why-choose-xior) | ||
- [Why not just use `axios`?](#why-not-just-use-axios) | ||
- [Why choose **xior** over Custom Fetch Wrappers?](#why-choose-xior-over-custom-fetch-wrappers) | ||
- [Getting Started](#getting-started) | ||
- [Installing](#installing) | ||
- [Create instance](#create-instance) | ||
- [GET / POST / DELETE / PUT / PATCH / OPTIONS / HEAD](#get--post--delete--put--patch--options--head) | ||
- [Upload file](#upload-file) | ||
- [Using interceptors](#using-interceptors) | ||
- [Timeout and Cancel request](#timeout-and-cancel-request) | ||
- [Plugins](#plugins) | ||
- [Error retry plugin](#error-retry-plugin) | ||
- [Request throttle plugin](#request-throttle-plugin) | ||
- [Cache plugin](#cache-plugin) | ||
- [Upload and download progress plugin](#upload-and-download-progress-plugin) | ||
- [Create your own custom plugin](#create-your-own-custom-plugin) | ||
- [Helper functions](#helper-functions) | ||
- [FAQ](#faq) | ||
- [1. Is **xior** 100% compatiable with `axios`?](#1-is-xior-100-compatiable-with-axios) | ||
- [2. Can I use xior in projects like Bun, Expo, React Native, Next.js, Vue, or Nuxt.js?](#2-can-i-use-xior-in-projects-like-bun-expo-react-native-nextjs-vue-or-nuxtjs) | ||
- [3. How do I handle responses with types like `'stream'`, `'document'`, `'arraybuffer'`, or `'blob'`?](#3-how-do-i-handle-responses-with-types-like-stream-document-arraybuffer-or-blob) | ||
- [5. How do I support older browsers?](#5-how-do-i-support-older-browsers) | ||
- [6. Why is xior named "xior"?](#6-why-is-xior-named-xior) | ||
- [7. Where can I ask additional questions?](#7-where-can-i-ask-additional-questions) | ||
- [Migrate from `axios` to **xior**](#migrate-from-axios-to-xior) | ||
- [GET](#get) | ||
- [POST](#post) | ||
- [Creating an instance](#creating-an-instance) | ||
- [Download file with `responseType: 'stream'`](#download-file-with-responsetype-stream) | ||
- [Migrate from `fetch` to **xior**](#migrate-from-fetch-to-xior) | ||
- [GET](#get-1) | ||
- [POST](#post-1) | ||
- [Abort a fetch](#abort-a-fetch) | ||
- [Sending a request with credentials included](#sending-a-request-with-credentials-included) | ||
- [Uploading a file](#uploading-a-file) | ||
- [Processing a text file line by line](#processing-a-text-file-line-by-line) | ||
- [Thanks](#thanks) | ||
## Why Choose **xior**? | ||
**xior** use the native **fetch API**, offering several advantages: | ||
- **Web Standard:** Fetch is a widely supported web standard, ensuring compatibility across different environments. | ||
- **Built-in Availability:** Both Node.js and browsers have built-in fetch implementations, eliminating the need for external dependencies. | ||
- **Edge Compatibility:** Unlike Axios, xior works seamlessly in edge runtimes, making it suitable for serverless functions and Next.js middleware. | ||
- **Convenient API and Plugin Support:** xior provides a familiar API similar to Axios, while also offering plugin support for customization and extending functionalities. | ||
### Why not just use `axios`? | ||
While popular and convenient, Axios currently lacks native edge runtime support (see: [https://github.com/axios/axios/issues/5523](https://github.com/axios/axios/issues/5523)). This can be an issue for specific use cases like Next.js serverless functions and middleware files, where fetch offers built-in caching and revalidation mechanisms (see: [https://nextjs.org/docs/app/api-reference/functions/fetch](https://nextjs.org/docs/app/api-reference/functions/fetch)). | ||
### Why choose **xior** over Custom Fetch Wrappers? | ||
While you can certainly create your own wrapper library around fetch, **xior** offers a pre-built solution with a familiar API, plugin support for extensibility, and potentially a more streamlined development experience. | ||
## Getting Started | ||
Install | ||
### Installing | ||
```bash | ||
npm i xior | ||
```sh | ||
# npm | ||
npm install xior | ||
# pnpm | ||
pnpm add xior | ||
# bun | ||
bun add xior | ||
# yarn | ||
yarn add xior | ||
``` | ||
Quick start | ||
### Create instance | ||
@@ -36,118 +103,138 @@ ```ts | ||
const http = xior.create({ baseURL: 'https://exmaple.com', timeout: 120 * 1000 }); | ||
http.interceptors.request.use((config) => { | ||
// do something | ||
return config; | ||
export const xiorInstance = xior.create({ | ||
baseURL: 'https://apiexampledomian.com/api', | ||
headers: { | ||
// put your common custom headers here | ||
}, | ||
}); | ||
``` | ||
http.interceptors.response.use((result) => { | ||
const { config, response, data } = result; | ||
// do something | ||
return result; | ||
}); | ||
### GET / POST / DELETE / PUT / PATCH / OPTIONS / HEAD | ||
// GET | ||
async function getList() { | ||
const { data } = await http.get('/list', { params: { page: 1, perPage: 20 } }); | ||
return data; | ||
GET | ||
> `HEAD` method is same usage with `GET` | ||
```ts | ||
async function run() { | ||
const { data } = await xiorInstance.get('/'); | ||
// with params | ||
const { data: data2 } = await xiorInstance.get('/', { params: { a: 1, b: 2 } }); | ||
// with headers | ||
const { data: data3 } = await xiorInstance.get('/', { | ||
params: { a: 1, b: 2 }, | ||
headers: { | ||
'content-type': 'application/x-www-form-urlencoded', | ||
}, | ||
}); | ||
// types | ||
const { data: data4 } = await xiorInstance.get<{ field1: string; field2: number }>('/'); | ||
} | ||
``` | ||
// POST | ||
async function create() { | ||
const { data } = await http.post( | ||
'/create', | ||
{ name: 'test', desc: 'foobar..foobar' }, | ||
{ params: { redirect: '/list' } } | ||
POST | ||
> `DELETE`/`PUT`/`PATCH`/`OPTIONS` methods are same usage with `POST` | ||
```ts | ||
async function run() { | ||
const { data: data3 } = await xiorInstance.post<{ field1: string; field2: number }>( | ||
'/', | ||
{ a: 1, b: '2' }, | ||
{ | ||
params: { id: 1 }, | ||
headers: { | ||
'content-type': 'application/json', | ||
}, | ||
} | ||
); | ||
return data; | ||
} | ||
``` | ||
## Usage | ||
### Upload file | ||
### GET / POST | ||
**xior** supports file uploads using the `FormData` API and provides an optional `'xior/plugins/progress'` plugin for simulating upload progress, usage similar to Axios. | ||
```ts | ||
import xior from 'xior'; | ||
import Xior from 'xior'; | ||
import uploadDownloadProgressPlugin from 'xior/plugins/progress'; | ||
const instance = xior.create({}); | ||
const http = Xior.create({}); | ||
await instance.get('http://httpbin.org', { | ||
params: { | ||
a: 1, | ||
b: 2, | ||
http.plugins.use( | ||
uploadDownloadProgressPlugin({ | ||
progressDuration: 5 * 1000, | ||
}) | ||
); | ||
const formData = FormData(); | ||
formData.append('file', fileObject); | ||
formData.append('field1', 'val1'); | ||
formData.append('field2', 'val2'); | ||
http.post('/upload', formData, { | ||
onUploadProgress(e) { | ||
console.log(`Upload progress: ${e.progress}%`); | ||
}, | ||
// progressDuration: 10 * 1000 | ||
}); | ||
await instance.post('http://httpbin.org', { | ||
a: 1, | ||
b: 2, | ||
}); | ||
``` | ||
### URL encode support nested objects | ||
### Using interceptors | ||
In **xior**, URI encoded strings default use lite encode, means if your `params` is nested object, it will be `[object object]`: | ||
**xior** supports interceptors similar to Axios, allowing you to modify requests and handle responses programmatically. | ||
Request inteceptors: | ||
```ts | ||
import xior from 'xior'; | ||
import xior, { merge } from 'xior'; | ||
const instance = xior.create({}); | ||
instance.get('http://httpbin.org', { | ||
params: { | ||
a: 1, | ||
b: { | ||
c: 2, | ||
}, | ||
}, | ||
const http = xior.create({ | ||
// ...options | ||
}); | ||
``` | ||
The final request url will be like: `http://httpbin.org?a=1&b=[object object]`, so we need use `qs`'s `stringify` module to support nested objects url encoded: | ||
http.inteceptors.request.use((config) => { | ||
const token = localStorage.getItem('REQUEST_TOKEN'); | ||
if (!token) return config; | ||
```ts | ||
import xior from 'xior'; | ||
// @ts-ignore | ||
import stringify from 'qs/lib/stringify'; | ||
const instance = xior.create({ | ||
encode: (params: Record<string, any>) => stringify(params), | ||
}); | ||
instance.get('http://httpbin.org', { | ||
params: { | ||
a: 1, | ||
b: { | ||
c: 2, | ||
return merge(config, { | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
}, | ||
}, | ||
}); | ||
}); | ||
// http://httpbin.org?a=1&b[c]=2 | ||
// One more inteceptor for request | ||
http.inteceptors.request.use((config) => { | ||
return config; | ||
}); | ||
``` | ||
### Upload file | ||
Response inteceptors: | ||
> Not like axios, xior doesn't support upload progess or download progress. | ||
Use FormData to upload files. | ||
```ts | ||
import xior from 'xior'; | ||
import xior, { merge } from 'xior'; | ||
const instance = xior.create(); | ||
const http = xior.create({}); | ||
const formData = new FormData(); | ||
http.inteceptors.response.use( | ||
(result) => { | ||
const { data, request: config, response: originalResponse } = result; | ||
return result; | ||
}, | ||
async (error) => { | ||
if (error?.response?.status === 401) { | ||
localStorage.removeItem('REQUEST_TOKEN'); | ||
} | ||
} | ||
); | ||
``` | ||
formData.append('file', fileObject); | ||
formData.append('filed1', 'val1'); | ||
formData.append('filed2', 'val2'); | ||
### Timeout and Cancel request | ||
instance.post('/upload', formData).then((res) => { | ||
console.log(res.data); | ||
}); | ||
``` | ||
Timeout: | ||
### Timeout | ||
```ts | ||
@@ -157,3 +244,3 @@ import xior from 'xior'; | ||
const instance = xior.create({ | ||
timeout: 120 * 1000, // default timeout | ||
timeout: 120 * 1000, // set default timeout | ||
}); | ||
@@ -173,3 +260,3 @@ | ||
### Cancel request | ||
Cancel request: | ||
@@ -190,72 +277,255 @@ ```ts | ||
### xior.interceptors.request.use | ||
## Plugins | ||
**xior** offers a variety of built-in plugins to enhance its functionality: | ||
- [Error retry plugin](#error-retry-plugin) | ||
- [Request throttle plugin](#request-throttle-plugin) | ||
- [Cache plugin](#cache-plugin) | ||
- [Upload and download progress plugin](#upload-and-download-progress-plugin) | ||
Usage: | ||
```ts | ||
import xior, { merge as deepMerge } from 'xior'; | ||
import xior from 'xior'; | ||
import errorRetryPlugin from 'xior/plugins/error-retry'; | ||
import throttlePlugin from 'xior/plugins/throttle'; | ||
import cachePlugin from 'xior/plugins/cache'; | ||
import uploadDownloadProgressPlugin from 'xior/plugins/progress'; | ||
const instance = xior.create(); | ||
instance.interceptors.request.use((config) => { | ||
return deepMerge(config, { | ||
headers: { | ||
token: localStorage.getItem('token') || '', | ||
}, | ||
}); | ||
}); | ||
const http = xior.create(); | ||
http.plugins.use(errorRetryPlugin()); | ||
http.plugins.use(throttlePlugin()); | ||
http.plugins.use(cachePlugin()); | ||
http.plugins.use(uploadDownloadProgressPlugin()); | ||
``` | ||
### xior.interceptors.response.use | ||
### Error retry plugin | ||
> Retry the failed request with special times | ||
API: | ||
```ts | ||
import xior, { merge as deepMerge } from 'xior'; | ||
function errorRetryPlugin(options: { | ||
retryTimes?: number; | ||
retryInterval?: number; | ||
enableRetry?: boolean | (error: Xiorconfig, error: XiorRequestConfig) => boolean; | ||
}): XiorPlugin; | ||
``` | ||
const instance = xior.create(); | ||
instance.interceptors.response.use( | ||
(res) => { | ||
const { data, request, response } = res; | ||
console.log(request, resposne, data); | ||
return res; | ||
}, | ||
function onRejected(error) { | ||
throw error; | ||
} | ||
The `options` object: | ||
| Param | Type | Default value | Description | | ||
| ------------- | ---------------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------- | | ||
| retryTimes | number | 2 | Set the retry times for failed request | | ||
| retryInterval | number | 3000 | After first time retry, the next retries interval time, default interval is 3 seconds | | ||
| enableRetry | boolean \| ((config: Xiorconfig, error: XiorRequestConfig) => boolean) | (config, error) => config.method === 'GET' | Default only retry if `GET` request error and `retryTimes > 0` | | ||
Basic usage: | ||
```ts | ||
import xior from 'xior'; | ||
import errorRetryPlugin from 'xior/plugins/error-retry'; | ||
const http = xior.create(); | ||
http.plugins.use( | ||
errorRetryPlugin({ | ||
retryTimes: 3, | ||
retryInterval: 3000, | ||
}) | ||
); | ||
// if request error, max retry 3 times until success | ||
http.get('/api1'); | ||
// if request error, will not retry, because `retryTimes: 0` | ||
http.get('/api2', { retryTimes: 0 }); | ||
// if POST request error, will not retry | ||
http.post('/api1'); | ||
// Use `enableRetry: true` to support post method, max retry 5 times until success | ||
http.post('/api1', null, { retryTimes: 5, enableRetry: true }); | ||
``` | ||
### stream | ||
### Request throttle plugin | ||
> if the options `responseType` is `responseType: 'stream' | 'document' | 'arraybuffer' | 'blob'`, then xior will just return the original response: `{ response }`, you can do anthing with response you like: | ||
> Throttle GET requests(or custom) most once per threshold milliseconds, filter repeat requests in certain time. | ||
API: | ||
```ts | ||
import xior, { merge as deepMerge } from 'xior'; | ||
const instance = xior.create({ | ||
baseURL: 'http://httpbin.org', | ||
function throttleRequestPlugin(options: { | ||
/** threshold in milliseconds, default: 1000ms */ | ||
threshold?: number; | ||
/** | ||
* check if we need enable throttle, default only `GET` method enable | ||
*/ | ||
enableThrottle?: boolean | ((config?: XiorRequestConfig) => boolean); | ||
throttleCache?: ICacheLike<RecordedCache>; | ||
}): XiorPlugin; | ||
``` | ||
The `options` object: | ||
> You can override default value in each request's own config (Except `throttleCache`) | ||
| Param | Type | Default value | Description | | ||
| -------------- | -------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------ | | ||
| threshold | number | 1000 | The number of milliseconds to throttle request invocations to | | ||
| enableThrottle | boolean \| ((config: Xiorconfig) => boolean) | (config) => config.method === 'GET' | Default only enabled in `GET` request | | ||
| throttleCache | CacheLike | lru(10) | CacheLike instance that will be used for storing throttled requests, use `tiny-lru` module | | ||
Basic usage: | ||
```ts | ||
import xior from 'xior'; | ||
import throttlePlugin from 'xior/plugins/throttle'; | ||
const http = xior.create(); | ||
http.plugins.use(throttlePlugin()); | ||
http.get('/'); // make real http request | ||
http.get('/'); // response from cache | ||
http.get('/'); // response from cache | ||
http.post('/'); // make real http request | ||
http.post('/'); // make real http request | ||
http.post('/'); // make real http request | ||
http.post('/', null, { | ||
enableThrottle: true, | ||
}); // make real http request | ||
http.post('/', null, { | ||
enableThrottle: true, | ||
}); // response from cache | ||
http.post('/', null, { | ||
enableThrottle: true, | ||
}); // response from cache | ||
``` | ||
### Cache plugin | ||
> Makes xior cacheable | ||
> Good to know: Next.js already support cache for fetch in server side. [More detail](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#fetching-data-on-the-server-with-fetch) | ||
API: | ||
```ts | ||
function cachePlugin(options: { | ||
enableCache?: boolean | ((config?: XiorRequestConfig) => boolean); | ||
defaultCache?: ICacheLike<XiorPromise>; | ||
}): XiorPlugin; | ||
``` | ||
The `options` object: | ||
| Param | Type | Default value | Description | | ||
| ------------ | -------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | ||
| enableCache | boolean \| ((config: Xiorconfig) => boolean) | (config) => config.method === 'GET' | Default only enabled in `GET` request | | ||
| defaultCache | CacheLike | lru(100, 1000\*60\*5) | will used for storing requests by default, except you define a custom Cache with your request config, use `tiny-lru` module | | ||
Basic usage: | ||
```ts | ||
import xior from 'xior'; | ||
import cachePlugin from 'xior/plugins/cache'; | ||
const http = xior.create(); | ||
http.plugins.use(cachePlugin()); | ||
http.get('/users'); // make real http request | ||
http.get('/users'); // get cache from previous request | ||
http.get('/users', { enableCache: false }); // disable cache manually and the the real http request | ||
http.post('/users'); // default no cache for post | ||
// enable cache manually in post request | ||
http.post('/users', { enableCache: true }); // make real http request | ||
http.post('/users', { enableCache: true }); // get cache from previous request | ||
``` | ||
Advanced: | ||
```ts | ||
import xior from 'xior'; | ||
import cachePlugin from 'xior/plugins/cache'; | ||
import { lru } from 'tiny-lru'; | ||
const http = xior.create({ | ||
baseURL: 'https://example-domain.com/api', | ||
headers: { 'Cache-Control': 'no-cache' }, | ||
}); | ||
http.plugins.use( | ||
cachePlugin({ | ||
// disable the default cache | ||
enableCache: false, | ||
}) | ||
); | ||
instance.get('/stream', { responseType: 'stream' }).then(({ response }) => { | ||
// `response` is the original response, | ||
// like fetch('/stream').then(response => { console.log(response)}) | ||
}); | ||
http.get('/users', { enableCache: true }); // manually enable cache for this request | ||
http.get('/users', { enableCache: true }); // get cache from previous request | ||
const cacheA = lru(100); | ||
// a actual request made and cached due to force update configured | ||
http.get('/users', { enableCache: true, defaultCache: cacheA, forceUpdate: true }); | ||
``` | ||
## Use plugins | ||
### Upload and download progress plugin | ||
**❗️❗️❗️ WIP (Work in Progress) ❗️❗️❗️** | ||
> Enable upload and download progress like axios, but the progress is simulated, | ||
> This means it doesn't represent the actual progress but offers a user experience similar to libraries like axios. | ||
API: | ||
```ts | ||
function progressPlugin(options: { | ||
/** default: 5*1000 ms */ | ||
progressDuration?: number; | ||
}): XiorPlugin; | ||
``` | ||
The `options` object: | ||
| Param | Type | Default value | Description | | ||
| ---------------- | ------ | ------------- | ---------------------------------------------------- | | ||
| progressDuration | number | 5000 | The upload or download progress grow to 99% duration | | ||
Basic usage: | ||
```ts | ||
import xior from 'xior'; | ||
import xiorCachePlugin from 'xior/lib/plugins/cache'; | ||
import xiorErrorRetryPlugin from 'xior/lib/plugins/error-retry'; | ||
import xiorRepeatRequestsFilterPlugin from 'xior/lib/plugins/repeat-requests-filter'; | ||
import uploadDownloadProgressPlugin from 'xior/plugins/progress'; | ||
const instance = xior.create(); | ||
const http = xior.create({}); | ||
http.plugins.use(uploadDownloadProgressPlugin()); | ||
instance.plugins.use(xiorCachePlugin()); | ||
instance.plugins.use(xiorErrorRetryPlugin()); | ||
instance.plugins.use(xiorAvoidRepeatRequestsPlugin()); | ||
const formData = FormData(); | ||
formData.append('file', fileObject); | ||
formData.append('field1', 'val1'); | ||
formData.append('field2', 'val2'); | ||
http.post('/upload', formData, { | ||
// simulate upload progress to 99% in 10 seconds, default is 5 seconds | ||
progressDuration: 10 * 1000, | ||
onUploadProgress(e) { | ||
console.log(`Upload progress: ${e.progress}%`); | ||
}, | ||
// onDownloadProgress(e) { | ||
// console.log(`Download progress: ${e.progress}%`); | ||
// }, | ||
}); | ||
``` | ||
## Custom plugin | ||
### Create your own custom plugin | ||
Let's implement a simple custom logs plugin: | ||
**xior** let you easily to create custom plugins. | ||
Here are examples: | ||
1. Simple Logging plugin: | ||
```ts | ||
@@ -269,3 +539,3 @@ import xior from 'xior'; | ||
const res = await adapter(config); | ||
console.log('%s %s take %sms', config.method, config.url, Date.now() - start); | ||
console.log('%s %s %s take %sms', config.method, config.url, res.status, Date.now() - start); | ||
return res; | ||
@@ -276,21 +546,597 @@ }; | ||
2. Check built-in plugins get more inspiration: | ||
Check [src/plugins](./src/plugins) | ||
## Helper functions | ||
**xior** has built-in helper functions, may useful for you: | ||
```ts | ||
import lru from 'tiny-lru'; | ||
import { | ||
encodeParams, | ||
merge as deepMerge, | ||
delay as sleep, | ||
buildSortedURL, | ||
isAbsoluteURL, | ||
} from 'xior'; | ||
``` | ||
## FAQ | ||
- Is `xior` 100% compatiable with `axios`? No | ||
- How to upload files? Use `FormData` | ||
- How to show upload progress like axios? Doesn't support. | ||
- What about response of `'stream' | 'document' | 'arraybuffer' | 'blob'` ? Use `responseType: 'stream' | 'document' | 'arraybuffer' | 'blob'`, will return original `{ response }` | ||
- How to support old browser? use polyfill, check `src/tests/polyfill.test.ts` | ||
- More: Anything else? create new issues let me know! | ||
**xior** frequently asked questions. | ||
### Why use xior? | ||
### 1. Is **xior** 100% compatiable with `axios`? | ||
Xior based on `fetch`, and here are some reasons **why `fetch` instead of `axios`**: | ||
**No**, but **xior** offers a similar API like axios: `axios.create` / `axios.interceptors` / `.get/post/put/patch/delete/head/options`. | ||
- Built in to node and the browser | ||
- Edge compatible | ||
- Next.js extends the native fetch to [support caching and revalidating](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating) | ||
### 2. Can I use xior in projects like Bun, Expo, React Native, Next.js, Vue, or Nuxt.js? | ||
Why don't just use `fetch`? | ||
**Yes**, **xior** works anywhere where the native `fetch` API is supported. | ||
Even if the environment doesn't support `fetch`, you can use a `fetch` polyfill like for older browsers. | ||
Yeah, you can. But xior's API similiar with axios, it's more make sense than the original `fetch`. | ||
### 3. How do I handle responses with types like `'stream'`, `'document'`, `'arraybuffer'`, or `'blob'`? | ||
To handle such responses, use the `responseType: 'stream'` option in your request: | ||
```ts | ||
import xior from 'xior'; | ||
const http = xior.create({ baseURL }); | ||
const { response } = await http.post<{ file: any; body: Record<string, string> }>( | ||
'/stream/10', | ||
null, | ||
{ responseType: 'stream' } | ||
); | ||
const reader = response.body!.getReader(); | ||
let chunk; | ||
for await (chunk of readChunks(reader)) { | ||
console.log(`received chunk of size ${chunk.length}`); | ||
} | ||
``` | ||
### 5. How do I support older browsers? | ||
You can use a polyfill for the `fetch` API. Check the file `src/tests/polyfill.test.ts` for a potential example. | ||
### 6. Why is xior named "xior"? | ||
The original name `axior` was unavailable on npm, so when removed the "a": ~~a~~**xior**. | ||
### 7. Where can I ask additional questions? | ||
If you have any questions, feel free to create issues. | ||
## Migrate from `axios` to **xior** | ||
### GET | ||
axios: | ||
```ts | ||
import axios from 'axios'; | ||
// Make a request for a user with a given ID | ||
axios | ||
.get('/user?ID=12345') | ||
.then(function (response) { | ||
// handle success | ||
console.log(response); | ||
}) | ||
.catch(function (error) { | ||
// handle error | ||
console.log(error); | ||
}) | ||
.finally(function () { | ||
// always executed | ||
}); | ||
// Optionally the request above could also be done as | ||
axios | ||
.get('/user', { | ||
params: { | ||
ID: 12345, | ||
}, | ||
}) | ||
.then(function (response) { | ||
console.log(response); | ||
}) | ||
.catch(function (error) { | ||
console.log(error); | ||
}) | ||
.finally(function () { | ||
// always executed | ||
}); | ||
// Want to use async/await? Add the `async` keyword to your outer function/method. | ||
async function getUser() { | ||
try { | ||
const response = await axios.get('/user?ID=12345'); | ||
console.log(response); | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
} | ||
``` | ||
xior: | ||
```ts | ||
import xior from 'xior'; | ||
// or import { xior } from 'xior'; | ||
const axios = xior.create(); | ||
// Make a request for a user with a given ID | ||
axios | ||
.get('/user?ID=12345') | ||
.then(function (response) { | ||
// handle success | ||
console.log(response); | ||
}) | ||
.catch(function (error) { | ||
// handle error | ||
console.log(error); | ||
}) | ||
.finally(function () { | ||
// always executed | ||
}); | ||
// Optionally the request above could also be done as | ||
axios | ||
.get('/user', { | ||
params: { | ||
ID: 12345, | ||
}, | ||
}) | ||
.then(function (response) { | ||
console.log(response); | ||
}) | ||
.catch(function (error) { | ||
console.log(error); | ||
}) | ||
.finally(function () { | ||
// always executed | ||
}); | ||
// Want to use async/await? Add the `async` keyword to your outer function/method. | ||
async function getUser() { | ||
try { | ||
const response = await axios.get('/user?ID=12345'); | ||
console.log(response); | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
} | ||
``` | ||
### POST | ||
axios: | ||
```ts | ||
import axios from 'axios'; | ||
axios | ||
.post('/user', { | ||
firstName: 'Fred', | ||
lastName: 'Flintstone', | ||
}) | ||
.then(function (response) { | ||
console.log(response); | ||
}) | ||
.catch(function (error) { | ||
console.log(error); | ||
}); | ||
``` | ||
xior: | ||
```ts | ||
import xior from 'xior'; | ||
// or import { xior } from 'xior'; | ||
const axios = xior.create(); | ||
axios | ||
.post('/user', { | ||
firstName: 'Fred', | ||
lastName: 'Flintstone', | ||
}) | ||
.then(function (response) { | ||
console.log(response); | ||
}) | ||
.catch(function (error) { | ||
console.log(error); | ||
}); | ||
``` | ||
### Creating an instance | ||
axios: | ||
```ts | ||
import axios from 'axios'; | ||
const instance = axios.create({ | ||
baseURL: 'https://some-domain.com/api/', | ||
timeout: 1000, | ||
headers: { 'X-Custom-Header': 'foobar' }, | ||
}); | ||
``` | ||
xior: | ||
```ts | ||
import axios from 'xior'; | ||
const instance = axios.create({ | ||
baseURL: 'https://some-domain.com/api/', | ||
timeout: 1000, | ||
headers: { 'X-Custom-Header': 'foobar' }, | ||
}); | ||
``` | ||
### Download file with `responseType: 'stream'` | ||
axios: | ||
```ts | ||
import axios from 'axios'; | ||
// GET request for remote image in node.js | ||
axios({ | ||
method: 'get', | ||
url: 'https://bit.ly/2mTM3nY', | ||
responseType: 'stream', | ||
}).then(function (response) { | ||
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg')); | ||
}); | ||
``` | ||
xior: | ||
```ts | ||
import xior from 'xior'; | ||
const axios = xior.create(); | ||
axios | ||
.get('https://bit.ly/2mTM3nY', { | ||
responseType: 'stream', | ||
}) | ||
.then(async function ({ response, config }) { | ||
const buffer = Buffer.from(await response.arrayBuffer()); | ||
return writeFile('ada_lovelace.jpg', buffer); | ||
}); | ||
``` | ||
## Migrate from `fetch` to **xior** | ||
### GET | ||
fetch: | ||
```ts | ||
async function logMovies() { | ||
const response = await fetch('http://example.com/movies.json?page=1&perPage=10'); | ||
const movies = await response.json(); | ||
console.log(movies); | ||
} | ||
``` | ||
xior: | ||
```ts | ||
import xior from 'xior'; | ||
const http = xior.create({ | ||
baseURL: 'http://example.com', | ||
}); | ||
async function logMovies() { | ||
const { data: movies } = await http.get('/movies.json', { | ||
params: { | ||
page: 1, | ||
perPage: 10, | ||
}, | ||
}); | ||
console.log(movies); | ||
} | ||
``` | ||
### POST | ||
fetch: | ||
```ts | ||
// Example POST method implementation: | ||
async function postData(url = '', data = {}) { | ||
// Default options are marked with * | ||
const response = await fetch(url, { | ||
method: 'POST', // *GET, POST, PUT, DELETE, etc. | ||
mode: 'cors', // no-cors, *cors, same-origin | ||
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached | ||
credentials: 'same-origin', // include, *same-origin, omit | ||
headers: { | ||
'Content-Type': 'application/json', | ||
// 'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
redirect: 'follow', // manual, *follow, error | ||
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url | ||
body: JSON.stringify(data), // body data type must match "Content-Type" header | ||
}); | ||
return response.json(); // parses JSON response into native JavaScript objects | ||
} | ||
postData('https://example.com/answer', { answer: 42 }).then((data) => { | ||
console.log(data); // JSON data parsed by `data.json()` call | ||
}); | ||
``` | ||
xior: | ||
```ts | ||
import xior from 'xior'; | ||
const http = xior.create({ | ||
baseURL: 'http://example.com', | ||
}); | ||
http | ||
.post( | ||
'/answer', | ||
{ answer: 42 }, | ||
{ | ||
mode: 'cors', // no-cors, *cors, same-origin | ||
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached | ||
credentials: 'same-origin', // include, *same-origin, omit | ||
headers: { | ||
'Content-Type': 'application/json', | ||
// 'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
redirect: 'follow', // manual, *follow, error | ||
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url | ||
} | ||
) | ||
.then(({ data }) => { | ||
console.log(data); | ||
}); | ||
``` | ||
### Abort a fetch | ||
fetch: | ||
```ts | ||
const controller = new AbortController(); | ||
const signal = controller.signal; | ||
const url = 'video.mp4'; | ||
const downloadBtn = document.querySelector('#download'); | ||
const abortBtn = document.querySelector('#abort'); | ||
downloadBtn.addEventListener('click', async () => { | ||
try { | ||
const response = await fetch(url, { signal }); | ||
console.log('Download complete', response); | ||
} catch (error) { | ||
console.error(`Download error: ${error.message}`); | ||
} | ||
}); | ||
abortBtn.addEventListener('click', () => { | ||
controller.abort(); | ||
console.log('Download aborted'); | ||
}); | ||
``` | ||
xior: | ||
```ts | ||
import xior from 'xior'; | ||
const http = xior.create(); | ||
const controller = new AbortController(); | ||
const signal = controller.signal; | ||
const url = 'video.mp4'; | ||
const downloadBtn = document.querySelector('#download'); | ||
const abortBtn = document.querySelector('#abort'); | ||
downloadBtn.addEventListener('click', async () => { | ||
try { | ||
const response = await http.get(url, { signal }); | ||
console.log('Download complete', response); | ||
} catch (error) { | ||
console.error(`Download error: ${error.message}`); | ||
} | ||
}); | ||
abortBtn.addEventListener('click', () => { | ||
controller.abort(); | ||
console.log('Download aborted'); | ||
}); | ||
``` | ||
### Sending a request with credentials included | ||
fetch: | ||
```ts | ||
fetch('https://example.com', { | ||
credentials: 'include', | ||
}); | ||
``` | ||
xior: | ||
```ts | ||
import xior from 'xior'; | ||
const http = xior.create(); | ||
http.get('https://example.com', { | ||
credentials: 'include', | ||
}); | ||
``` | ||
### Uploading a file | ||
fetch: | ||
```ts | ||
async function upload(formData) { | ||
try { | ||
const response = await fetch('https://example.com/profile/avatar', { | ||
method: 'PUT', | ||
body: formData, | ||
}); | ||
const result = await response.json(); | ||
console.log('Success:', result); | ||
} catch (error) { | ||
console.error('Error:', error); | ||
} | ||
} | ||
const formData = new FormData(); | ||
const fileField = document.querySelector('input[type="file"]'); | ||
formData.append('username', 'abc123'); | ||
formData.append('avatar', fileField.files[0]); | ||
upload(formData); | ||
``` | ||
xior: | ||
> Add `{responseType: 'stream'}` will tell xior no need process response, and return original response in format `{response}` | ||
```ts | ||
import xior from 'xior'; | ||
const http = xior.create({ | ||
baseURL: 'https://example.com', | ||
}); | ||
async function upload(formData) { | ||
try { | ||
const { data: result } = await http.put('/profile/avatar', formData); | ||
console.log('Success:', result); | ||
} catch (error) { | ||
console.error('Error:', error); | ||
} | ||
} | ||
const formData = new FormData(); | ||
const fileField = document.querySelector('input[type="file"]'); | ||
formData.append('username', 'abc123'); | ||
formData.append('avatar', fileField.files[0]); | ||
upload(formData); | ||
``` | ||
### Processing a text file line by line | ||
fetch: | ||
```ts | ||
async function* makeTextFileLineIterator(fileURL) { | ||
const utf8Decoder = new TextDecoder('utf-8'); | ||
const response = await fetch(fileURL); | ||
const reader = response.body.getReader(); | ||
let { value: chunk, done: readerDone } = await reader.read(); | ||
chunk = chunk ? utf8Decoder.decode(chunk) : ''; | ||
const newline = /\r?\n/gm; | ||
let startIndex = 0; | ||
let result; | ||
while (true) { | ||
const result = newline.exec(chunk); | ||
if (!result) { | ||
if (readerDone) break; | ||
const remainder = chunk.substr(startIndex); | ||
({ value: chunk, done: readerDone } = await reader.read()); | ||
chunk = remainder + (chunk ? utf8Decoder.decode(chunk) : ''); | ||
startIndex = newline.lastIndex = 0; | ||
continue; | ||
} | ||
yield chunk.substring(startIndex, result.index); | ||
startIndex = newline.lastIndex; | ||
} | ||
if (startIndex < chunk.length) { | ||
// Last line didn't end in a newline char | ||
yield chunk.substr(startIndex); | ||
} | ||
} | ||
async function run() { | ||
for await (const line of makeTextFileLineIterator(urlOfFile)) { | ||
processLine(line); | ||
} | ||
} | ||
run(); | ||
``` | ||
xior: | ||
> Add `{responseType: 'stream'}` will tell xior no need process response, and return original response in format `{response}` | ||
```ts | ||
import xior from 'xior'; | ||
const http = xior.create(); | ||
async function* makeTextFileLineIterator(fileURL) { | ||
const utf8Decoder = new TextDecoder('utf-8'); | ||
const { response } = await http.get(fileURL, { responseType: 'stream' }); | ||
const reader = response.body.getReader(); | ||
let { value: chunk, done: readerDone } = await reader.read(); | ||
chunk = chunk ? utf8Decoder.decode(chunk) : ''; | ||
const newline = /\r?\n/gm; | ||
let startIndex = 0; | ||
let result; | ||
while (true) { | ||
const result = newline.exec(chunk); | ||
if (!result) { | ||
if (readerDone) break; | ||
const remainder = chunk.substr(startIndex); | ||
({ value: chunk, done: readerDone } = await reader.read()); | ||
chunk = remainder + (chunk ? utf8Decoder.decode(chunk) : ''); | ||
startIndex = newline.lastIndex = 0; | ||
continue; | ||
} | ||
yield chunk.substring(startIndex, result.index); | ||
startIndex = newline.lastIndex; | ||
} | ||
if (startIndex < chunk.length) { | ||
// Last line didn't end in a newline char | ||
yield chunk.substr(startIndex); | ||
} | ||
} | ||
async function run() { | ||
for await (const line of makeTextFileLineIterator(urlOfFile)) { | ||
processLine(line); | ||
} | ||
} | ||
run(); | ||
``` | ||
## Thanks | ||
Without the support of these resources, xior wouldn't be possible: | ||
- [axios](https://github.com/axios/axios) | ||
- [axios-extensions](https://github.com/kuitos/axios-extensions) | ||
- [ts-deepmerge](https://github.com/voodoocreation/ts-deepmerge) | ||
- [tiny-lru](https://github.com/avoidwork/tiny-lru) | ||
- [bunchee](https://github.com/huozhi/bunchee) | ||
- [fetch MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/fetch) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
124057
37
1137
896
5