UNPKG

hyperwiz

Version:

Next-generation TypeScript HTTP client with built-in retry, smart caching, and seamless authentication. Effortlessly handle API requests with advanced features for modern web development.

3 lines 15.6 kB
/*! hyperwiz (c) 2025 Parth Tyagi - Apache 2.0 License */ function W(n,t){try{if(t.startsWith("http://")||t.startsWith("https://"))return t;let e=n.replace(/\/+$/,""),s=t.replace(/^\/+/,""),o=`${e}/${s}`;return new URL(o),o}catch{throw new Error(`Invalid URL: ${n}/${t}`)}}function _(n){if(!n)return false;let t=n.toLowerCase().trim();return t==="application/json"||t.startsWith("application/json;")}async function J(n){let t=n.headers.get("Content-Type");try{if(_(t)){let e=await n.text();return e?JSON.parse(e):null}else return await n.text()}catch(e){return console.warn("Failed to parse response:",e),null}}var S=class{constructor(t,e,s){this.timeoutControllers=new Map;this.credentials="same-origin";if(typeof AbortController=="undefined")throw new Error("AbortController is not available. Please use a polyfill for older browsers.");this.baseUrl=t,this.interceptors=e||{},this.credentials=s||"same-origin";}setCredentials(t){this.credentials=t;}async runBeforeHandlers(t,e){let s={...t};if(this.interceptors.before)for(let o of this.interceptors.before)try{s=await o(s,e);}catch(r){console.error("Request interceptor error:",r);}return s}async runAfterHandlers(t,e,s){let o=e;if(this.interceptors.after)for(let r of this.interceptors.after)try{o=await r(t,o,s);}catch(a){console.error("Response interceptor error:",a);}return o}async runErrorHandlers(t,e,s){let o=t;if(this.interceptors.onError)for(let r of this.interceptors.onError)try{o=await r(o,e,s);}catch(a){console.error("Error interceptor error:",a);}return o}async request(t,e){let s=W(this.baseUrl,t),o=`${e.method}-${s}-${Date.now()}`;try{let r=await this.runBeforeHandlers(e,s);if(this.enableLogging){let h={};r.headers&&typeof r.headers=="object"&&!Array.isArray(r.headers)&&Object.assign(h,r.headers),r.body&&["POST","PUT","PATCH"].includes(r.method)&&(h["Content-Type"]="application/json"),console.log(`\u{1F680} ${r.method} ${s}`,{method:r.method,headers:h,body:r.body?typeof r.body=="string"?r.body:JSON.stringify(r.body):void 0});}let a={};r.headers&&typeof r.headers=="object"&&!Array.isArray(r.headers)&&Object.assign(a,r.headers),r.body&&["POST","PUT","PATCH"].includes(r.method)&&(a["Content-Type"]="application/json");let i=new AbortController;if(this.timeoutControllers.set(o,i),typeof fetch=="undefined")throw new Error("Fetch API is not available. Please use a polyfill for older browsers.");let u=await fetch(s,{method:r.method,headers:a,body:r.body?JSON.stringify(r.body):void 0,signal:r.signal||i.signal,credentials:this.credentials});this.timeoutControllers.delete(o);let l;try{l=await J(u);}catch(h){console.warn("Failed to parse response:",h),l=null;}if(!u.ok){let h=typeof l=="string"?l:"Request failed",v=await this.runErrorHandlers(h,s,r);return {success:!1,status:u.status,error:String(v)}}let g;try{g=await this.runAfterHandlers(u,l,s);}catch(h){console.warn("Response interceptor failed:",h),g=l;}return {success:!0,data:g}}catch(r){this.timeoutControllers.delete(o);let a;r instanceof Error?a=r.message:typeof r=="string"?a=r:r&&typeof r=="object"&&"message"in r?a=String(r.message):a="Unknown network error";let i=await this.runErrorHandlers(a,s,e);return {success:false,error:String(i)}}}get(t,e){return this.request(t,{method:"GET",headers:e})}post(t,e,s){return this.request(t,{method:"POST",body:e,headers:s})}put(t,e,s){return this.request(t,{method:"PUT",body:e,headers:s})}patch(t,e,s){return this.request(t,{method:"PATCH",body:e,headers:s})}delete(t,e){return this.request(t,{method:"DELETE",headers:e})}head(t,e){return this.request(t,{method:"HEAD",headers:e})}options(t,e){return this.request(t,{method:"OPTIONS",headers:e})}purge(t,e){return this.request(t,{method:"PURGE",headers:e})}async fetch(t,e){let s={method:(e==null?void 0:e.method)||"GET",headers:e==null?void 0:e.headers,body:e==null?void 0:e.body,signal:(e==null?void 0:e.signal)||void 0};return this.request(t,s)}addBefore(t){this.interceptors.before||(this.interceptors.before=[]),this.interceptors.before.push(t);}addAfter(t){this.interceptors.after||(this.interceptors.after=[]),this.interceptors.after.push(t);}addErrorHandler(t){this.interceptors.onError||(this.interceptors.onError=[]),this.interceptors.onError.push(t);}removeBefore(t){if(this.interceptors.before){let e=this.interceptors.before.indexOf(t);e>-1&&this.interceptors.before.splice(e,1);}}removeAfter(t){if(this.interceptors.after){let e=this.interceptors.after.indexOf(t);e>-1&&this.interceptors.after.splice(e,1);}}removeErrorHandler(t){if(this.interceptors.onError){let e=this.interceptors.onError.indexOf(t);e>-1&&this.interceptors.onError.splice(e,1);}}clearInterceptors(){this.interceptors={},this.timeoutHandler=void 0;}cancelAllRequests(){this.timeoutControllers.forEach(t=>{t.abort();}),this.timeoutControllers.clear();}setTimeout(t){this.timeoutHandler&&this.removeBefore(this.timeoutHandler),this.timeoutHandler=(e,s)=>{let o=new AbortController,r=setTimeout(()=>{o.abort();},t),a=()=>{clearTimeout(r);},i=e.signal;return i&&i.addEventListener("abort",a,{once:true}),o.signal.addEventListener("abort",a,{once:true}),{...e,signal:o.signal}},this.addBefore(this.timeoutHandler);}};function E(n,t){return t.includes(n)}function q(n){if(!n||typeof n!="object")return false;let t=n;if(typeof t.data=="undefined"||typeof t.status!="number"||typeof t.statusText!="string"||typeof t.timestamp!="number"||typeof t.url!="string"||typeof t.method!="string"||t.status<100||t.status>599||t.timestamp<=0||t.timestamp>Date.now()+864e5)return false;try{new URL(t.url);}catch{return false}return !!["GET","POST","PUT","PATCH","DELETE"].includes(t.method.toUpperCase())}function I(n,t){return !n.timestamp||isNaN(n.timestamp)?true:Date.now()-n.timestamp>t}function N(n){return {generate(t,e,s){let o=t.toUpperCase(),r;try{if(e.startsWith("http")){let a=new URL(e),i=Array.from(a.searchParams.entries()).sort(([u],[l])=>u.localeCompare(l)).map(([u,l])=>`${u}=${l}`).join("&");r=`${o}:${a.pathname}${i?`?${i}`:""}`;}else {let[a,i]=e.split("?");if(i){let u=new URLSearchParams(i),l=Array.from(u.entries()).sort(([g],[h])=>g.localeCompare(h)).map(([g,h])=>`${g}=${h}`).join("&");r=`${o}:${a}?${l}`;}else r=`${o}:${a}`;}}catch{r=`${o}:${e}`;}if(s&&o!=="GET")try{r+=`:${JSON.stringify(s)}`;}catch{r+=`:${String(s)}`;}return r}}}var k=class{constructor(t=100){this.cache=new Map;this.hitCount=0;this.missCount=0;this.maxSize=t;}async get(t){try{let e=this.cache.get(t);if(!e)return this.missCount++,null;if(!q(e))return this.cache.delete(t),this.missCount++,null;if(this.cache.size>1){let s=Array.from(this.cache.keys()),o=s[s.length-1];t!==o&&(this.cache.delete(t),this.cache.set(t,e));}return this.hitCount++,e}catch{return this.missCount++,null}}async set(t,e){for(this.cache.has(t)&&this.cache.delete(t);this.cache.size>=this.maxSize;){let s=this.cache.keys().next().value;if(s)this.cache.delete(s);else break}this.cache.set(t,e);}async delete(t){this.cache.delete(t);}async clear(){this.cache.clear();}async keys(){return Array.from(this.cache.keys())}getHitRate(){let t=this.hitCount+this.missCount;return t>0?Math.round(this.hitCount/t*100):0}getStats(){return {size:this.cache.size,maxSize:this.maxSize,hitCount:this.hitCount,missCount:this.missCount,hitRate:this.getHitRate()}}},H=class{constructor(){this.dbName="hyperwiz-cache";this.storeName="responses";this.version=1;if(typeof window=="undefined"||!window.indexedDB)throw new Error("IndexedDB is not available in this environment");this.initDB();}async initDB(){return new Promise((t,e)=>{let s=indexedDB.open(this.dbName,this.version);s.onerror=()=>e(s.error),s.onsuccess=()=>t(s.result),s.onupgradeneeded=o=>{let r=o.target.result;r.objectStoreNames.contains(this.storeName)||r.createObjectStore(this.storeName,{keyPath:"key"});};})}async get(t){try{let e=await this.initDB();return new Promise((s,o)=>{let i=e.transaction([this.storeName],"readonly").objectStore(this.storeName).get(t);i.onerror=()=>o(i.error),i.onsuccess=()=>{try{let u=i.result;if(!u){s(null);return}let l=u.value;if(!q(l)){this.delete(t).catch(()=>{}),console.warn("Removed invalid IndexedDB cache entry:",t),s(null);return}s(l);}catch(u){console.warn("Error parsing cached response:",u),s(null);}};})}catch(e){return console.warn("IndexedDB not available, falling back to memory cache:",e),null}}async set(t,e){try{let s=await this.initDB();return new Promise((o,r)=>{let u=s.transaction([this.storeName],"readwrite").objectStore(this.storeName).put({key:t,value:e});u.onerror=()=>r(u.error),u.onsuccess=()=>o();})}catch(s){console.warn("IndexedDB not available, falling back to memory cache:",s);}}async delete(t){try{let e=await this.initDB();return new Promise((s,o)=>{let i=e.transaction([this.storeName],"readwrite").objectStore(this.storeName).delete(t);i.onerror=()=>o(i.error),i.onsuccess=()=>s();})}catch(e){console.warn("IndexedDB not available:",e);}}async clear(){try{let t=await this.initDB();return new Promise((e,s)=>{let a=t.transaction([this.storeName],"readwrite").objectStore(this.storeName).clear();a.onerror=()=>s(a.error),a.onsuccess=()=>e();})}catch(t){console.warn("IndexedDB not available:",t);}}async keys(){try{let t=await this.initDB();return new Promise((e,s)=>{let a=t.transaction([this.storeName],"readonly").objectStore(this.storeName).getAllKeys();a.onerror=()=>s(a.error),a.onsuccess=()=>{let i=a.result;e(i);};})}catch(t){return console.warn("IndexedDB not available:",t),[]}}};function B(n,t=100){switch(n){case "indexeddb":return new H;case "memory":default:return new k(t)}}var P=new Map,T=new Map,O=new Map,D=new Map,$={circuitBreakerTimeout:3600*1e3,retryAttemptsTimeout:1800*1e3,pendingRequestsTimeout:300*1e3,cleanupInterval:600*1e3};function Q(){let n=Date.now();for(let[t,e]of P.entries())n-e.lastFailureTime>$.circuitBreakerTimeout&&P.delete(t);for(let[t,e]of D.entries())n-e.timestamp>$.retryAttemptsTimeout&&D.delete(t);for(let[t,e]of T.entries())n-Date.now()>$.pendingRequestsTimeout&&T.delete(t);}var L=false;function V(){!L&&typeof setInterval!="undefined"&&(setInterval(Q,$.cleanupInterval),L=true);}var U={maxRetries:3,retryDelay:1e3,maxDelay:1e4,backoffMultiplier:2,retryOnStatus:[408,429,500,502,503,504],retryOnNetworkError:true},j={enabled:true,maxAge:300*1e3,maxSize:100,storage:"memory",includeQueryParams:true,cacheableMethods:["GET","HEAD"],cacheableStatusCodes:[200]},Y={logging:false,timeout:3e4,credentials:"same-origin"};function X(n){return n instanceof Error?n.name==="TypeError"||n.message.includes("fetch")||n.message.includes("network")||n.message.includes("Failed to fetch"):typeof n=="string"?n.includes("fetch")||n.includes("network")||n.includes("Failed to fetch")||n.includes("ECONNREFUSED")||n.includes("ENOTFOUND")||n.includes("ETIMEDOUT"):false}function K(n){if(typeof n=="object"&&n!==null&&"status"in n)return true;if(typeof n=="object"&&n!==null&&"success"in n){let t=n;return !t.success&&t.status!==void 0}return false}function Z(n){return K(n)?(n.status||0):0}function ee(n,t){if(t.retryOnNetworkError&&X(n))return true;if(K(n)&&t.retryOnStatus){let e=Z(n);return t.retryOnStatus.includes(e)}if(t.retryOnNetworkError&&typeof n=="string"){let e=n.toLowerCase();return e.includes("fetch")||e.includes("network")||e.includes("connection")||e.includes("timeout")||e.includes("econnrefused")||e.includes("enotfound")||e.includes("etimedout")}return false}function te(n,t){let e=t.retryDelay,s=t.backoffMultiplier,o=t.maxDelay,r=e*Math.pow(s,n),a=Math.random()*.1*r;return Math.min(r+a,o)}function se(n){return new Promise(t=>setTimeout(t,n))}var F=(n,t)=>{V();let e={...Y,...t},s=new S(n,e.interceptors,e.credentials);e.logging&&(s.enableLogging=true,s.addAfter((r,a,i)=>(console.log(`\u2705 ${r.status} ${i}`,a),a)),s.addErrorHandler((r,a,i)=>(console.error(`\u274C Error for ${a}:`,r),r))),e.timeout&&s.setTimeout(e.timeout);let o=null;if(e.retry&&(o=typeof e.retry=="boolean"?e.retry?U:null:{...U,...e.retry}),e.cache!==void 0){let r=typeof e.cache=="boolean"?e.cache?j:null:{...j,...e.cache};if(r&&r.enabled!==false){let a=`${r.storage||"memory"}-${r.maxSize||100}`,i=O.get(a);i||(i=B(r.storage||"memory",r.maxSize||100),O.set(a,i));let u=N(r.includeQueryParams||true),l=Math.random().toString(36).substring(7),g=s.get.bind(s),h=s.post.bind(s),v=s.put.bind(s),z=s.patch.bind(s),G=s.delete.bind(s);s.get=async(c,p)=>{let y=Date.now(),f=`${l}:${u.generate("GET",c)}`,d=await i.get(f);if(d&&!I(d,r.maxAge||300*1e3)){let b=Date.now()-y;if(e.logging&&(console.log(`\u{1F4BE} Cache HIT for ${c} (${b}ms)`),"getStats"in i)){let C=i.getStats();console.log(`\u{1F4CA} Cache stats: ${C.hitRate}% hit rate, ${C.size}/${C.maxSize} items`);}return {success:true,data:d.data}}let m=`GET:${c}:${JSON.stringify(p||{})}`,w=T.get(m);if(w)return e.logging&&console.log(`\u{1F504} Returning pending request for ${c}`),w;let R=(async()=>{try{let b=await g(c,p),C=0,M=o?o.maxRetries+1:1;for(;!b.success&&C<M&&o;){if(C++,C>1){if(!ee(b.error,o))break;let x=te(C-2,o);await se(x),e.logging&&console.log(`\u{1F504} Cache wrapper retry attempt ${C} for ${c}`);}b=await g(c,p);}let A=Date.now()-y;if(e.logging&&console.log(`\u{1F4E1} Cache MISS for ${c} (${A}ms)`),b.success&&E(200,r.cacheableStatusCodes||[200])){let x={data:b.data,status:200,statusText:"OK",headers:{},timestamp:Date.now(),url:c.startsWith("http")?c:`${n}${c}`,method:"GET"};await i.set(f,x),e.logging&&console.log(`\u{1F4BE} Cached response for ${c} (${A}ms)`);}return b}catch(b){throw b}})().finally(()=>{T.delete(m),e.logging&&T.size>100&&console.warn(`\u26A0\uFE0F High number of pending requests: ${T.size}`);});return T.set(m,R),R},s.post=async(c,p,y)=>{let f=Date.now(),d=await h(c,p,y),m=Date.now()-f;if(d.success&&E(200,r.cacheableStatusCodes||[200])){let w=`${l}:${u.generate("POST",c,p)}`,R={data:d.data,status:200,statusText:"OK",headers:{},timestamp:Date.now(),url:c.startsWith("http")?c:`${n}${c}`,method:"POST"};await i.set(w,R),e.logging&&console.log(`\u{1F4BE} Cached response for ${c} (${m}ms)`);}return d},s.put=async(c,p,y)=>{let f=Date.now(),d=await v(c,p,y),m=Date.now()-f;if(d.success&&E(200,r.cacheableStatusCodes||[200])){let w=`${l}:${u.generate("PUT",c,p)}`,R={data:d.data,status:200,statusText:"OK",headers:{},timestamp:Date.now(),url:c.startsWith("http")?c:`${n}${c}`,method:"PUT"};await i.set(w,R),e.logging&&console.log(`\u{1F4BE} Cached response for ${c} (${m}ms)`);}return d},s.patch=async(c,p,y)=>{let f=Date.now(),d=await z(c,p,y),m=Date.now()-f;if(d.success&&E(200,r.cacheableStatusCodes||[200])){let w=`${l}:${u.generate("PATCH",c,p)}`,R={data:d.data,status:200,statusText:"OK",headers:{},timestamp:Date.now(),url:c.startsWith("http")?c:`${n}${c}`,method:"PATCH"};await i.set(w,R),e.logging&&console.log(`\u{1F4BE} Cached response for ${c} (${m}ms)`);}return d},s.delete=async(c,p)=>{let y=Date.now(),f=await G(c,p),d=Date.now()-y;if(f.success&&E(200,r.cacheableStatusCodes||[200])){let m=`${l}:${u.generate("DELETE",c)}`,w={data:f.data,status:200,statusText:"OK",headers:{},timestamp:Date.now(),url:c.startsWith("http")?c:`${n}${c}`,method:"DELETE"};await i.set(m,w),e.logging&&console.log(`\u{1F4BE} Cached response for ${c} (${d}ms)`);}return f};}}return s},re=n=>F(n,{logging:true}),ne=()=>{let n=Date.now();for(let[t,e]of P.entries())n-e.lastFailureTime>$.circuitBreakerTimeout&&P.delete(t);},oe=()=>{let n=Date.now();for(let[t,e]of D.entries())n-e.timestamp>$.retryAttemptsTimeout&&D.delete(t);},ae=()=>{T.clear();},ie=()=>({circuitBreakers:P.size,retryAttempts:D.size,pendingRequests:T.size,globalCacheStorages:O.size}); export{ne as cleanupCircuitBreakers,ae as cleanupPendingRequests,oe as cleanupRetryAttempts,F as createClient,re as createPublicClient,ie as getMemoryStats};