soon-fetch
Version:
a request lib alternative to axios with timeout, request reusing, race, response cache ...
2 lines (1 loc) • 5.69 kB
JavaScript
;const t=t=>{const e=[],r=t.match(/:([^:/\d]+)\/?/g);return r&&r.forEach((t=>{e.push(t.replace(/\//g,"").replace(/:/g,""))})),e},e=(t="")=>t.endsWith("/")?t.slice(0,-1):t,r=(t="")=>t.startsWith("/")?t:"/"+t,n=(t="")=>t.startsWith("http"),o=t=>{if(!t)return[];if(t instanceof URLSearchParams||"string"==typeof t||Array.isArray(t))return Array.from(new URLSearchParams(t).entries());const e=[];return Object.keys(t).forEach((r=>{const n=t[r];(Array.isArray(n)?n:[n]).forEach((t=>{e.push([r,t??""])}))})),e},s=(s,a)=>{const{query:i,params:c,baseURL:f}=a;let p=s.trim();t(s).forEach((t=>{c&&(p=p.replace(":"+t,""+c[t]))}));const[u,l]=p.split("?"),y=new URLSearchParams([...o(l),...o(i)]);let h=((t,o)=>{if(n(t))return t;const s=n(o)?o:r(o);return e(s)+e(r(t))})(u,f);return y.size&&(h=h+"?"+y),h},a=(...t)=>{const e=new Headers;return t.forEach((t=>{t&&new Headers(t).forEach(((t,r)=>{e.set(r,t)}))})),e};function i(t,e){const r=(t??[]).filter((t=>!!t));return e&&r.push(AbortSignal.timeout(e)),r.length?AbortSignal.any(r):void 0}function c(t){return!(!t||"object"!=typeof t||(t instanceof Blob||t instanceof ArrayBuffer||t instanceof FormData||t instanceof File||t instanceof DataView||t instanceof URLSearchParams||t instanceof ReadableStream||(e=t,e instanceof Int8Array||e instanceof Uint8Array||e instanceof Uint8ClampedArray||e instanceof Int16Array||e instanceof Uint16Array||e instanceof Int32Array||e instanceof Uint32Array||e instanceof Float32Array||e instanceof Float64Array||e instanceof BigInt64Array||e instanceof BigUint64Array)));var e}function f(t){const e=new FormData;return t&&"object"==typeof t&&!Array.isArray(t)&&Object.entries(t).forEach((([t,r])=>{if(null==r||"function"==typeof r)return;let n;n=r instanceof Blob?r:"object"==typeof r?JSON.stringify(r):String(r),e.append(t,n)})),e}function p(...t){const e=Object.assign({},...t),r=a(...t.map((t=>t?.headers)));return e.headers=r,e.signal=i(t.map((t=>t?.signal)),e.timeout),e}function u(t){const{url:e,options:r,baseURL:n,baseOptions:o}=t,a=p(o,r),f=s(e,{...a,baseURL:n}),u=a?.body,l=c(u);a.body=l?JSON.stringify(u):u,l&&a.headers.append("Content-Type","application/json");const y=new AbortController;return a.signal=i([a.signal,y.signal]),{url:f,options:a,is_body_json:l,abortController:y}}const l=["get","post","put","delete","patch"];function y(t,e){const r={};return t.forEach((t=>{r[t]=e(t)})),r}function h(e){const r=(r,n,o)=>{const s=!!t(r).length;return(...t)=>{const a=[...t],{hasBody:i,hasQuery:c,isFormData:p}=o||{},u=s?a.shift():void 0,l=c?a.shift():void 0;let y=i?a.shift():void 0;p&&(y=f(y));const h=a.shift();return e(r,n,u,l,y,h,o?.options)}},n={};return l.forEach((t=>{const e=t.toUpperCase();n[e]=e=>({Send:n=>r(e,t,{options:n}),Body:()=>({Send:n=>r(e,t,{hasBody:!0,options:n})}),FormData:()=>({Send:n=>r(e,t,{hasBody:!0,isFormData:!0,options:n})}),Query:()=>({Send:n=>r(e,t,{hasQuery:!0,options:n}),Body:()=>({Send:n=>r(e,t,{hasBody:!0,hasQuery:!0,options:n})}),FormData:()=>({Send:n=>r(e,t,{hasBody:!0,hasQuery:!0,isFormData:!0,options:n})})})})})),n}function d(t,e){e&&(e.pop()?.abort("race abort"),e.push(t))}function m(t){if(Array.isArray(t))return t.map(m);if("object"==typeof t&&null!==t){const e={};return Object.keys(t).sort().forEach((r=>{e[r]=m(t[r])})),e}return t}function g(t){const{url:e,options:r}=t,{headers:n,method:o,body:s,query:a,params:i}=r??{},c=m(Object.fromEntries(new Headers(n).entries()??[]));return(o??"get").toLowerCase()+e+JSON.stringify(m(a)??"")+JSON.stringify(m(i)??"")+JSON.stringify(c)+("object"==typeof s&&null!=s?JSON.stringify(m(s)):s??"")}function b(){const t={},e=[];setInterval((()=>{const r=Date.now();for(let n=e.length-1;n>=0;n--)e[n].expiredTime<r&&(delete t[e[n].key],e.splice(n,1))}),6e4);const r=t=>{if(t instanceof Response)return t.clone();if("function"==typeof t||t instanceof Promise)return t;try{return structuredClone(t)}catch(e){return JSON.parse(JSON.stringify(t))}};function n(r){delete t[r];for(let t=e.length-1;t>=0;t--)if(e[t].key===r){e.splice(t,1);break}}return{get:function(e){const o=t[e];if(void 0!==o)return o.expiredTime>Date.now()?r(o.data):void n(e)},set:function(n,o,s){t[n]={data:r(o),expiredTime:s},e.push({key:n,expiredTime:s})},remove:n}}function S(){const t={},e=e=>t[e]=void 0;return{get:e=>t[e],set:(r,n)=>{t[r]=n,n.finally((()=>e(r)))}}}exports.createCache=b,exports.createShare=S,exports.createShortApi=h,exports.createShortMethods=y,exports.createSilentRefresh=function(t){let e=[],r=!1;return(n,o)=>{e.push({success:n,fail:o}),r||(r=!0,t().then((()=>{e.forEach((t=>t.success()))})).catch((t=>{e.forEach((t=>t.fail()))})).finally((()=>{r=!1,e=[]})))}},exports.createSoon=function(t,e){const r=b(),n=S(),o=(o,s)=>new Promise(((a,i)=>{const c=t(o,s),f=u({url:o,options:s,...c}),p=g(f),{abortController:l}=f,y=new AbortController;y.signal.addEventListener("abort",(()=>{i(y.signal.reason)}));const h=e({parsed:{...f,requestKey:p}});if(f.options?.share){const t=n.get(p);if(t)return a(t)}if(d(f.options.share?y:l,f.options?.aborts),f.options?.staleTime){const t=r.get(p);if(void 0!==t)return a(t)}const m=h(o,s);f.options?.share&&n.set(p,m),m.then((t=>{a(t),f.options?.staleTime&&r.set(p,t,(new Date).getTime()+f.options.staleTime)})).catch((t=>i(t)))})),s=h(((t,e,r,n,s,a,i)=>o(t,{...i,...a,method:e,params:r,query:n,body:s}))),a=y([...l,"head","options"],(t=>(e,r)=>o(e,{...r,method:t})));return{request:o,...s,...a}},exports.deepSort=m,exports.genRequestKey=g,exports.isBodyJson=c,exports.mergeHeaders=a,exports.mergeOptions=p,exports.mergeSignals=i,exports.mergeUrl=s,exports.parseUrlOptions=function(t){const{url:e,options:r}=u(t);return[e,r]},exports.parseWithBase=u,exports.raceAbort=d,exports.toFormData=f;