UNPKG

@alwatr/fetch

Version:

`@alwatr/fetch` is an enhanced, lightweight, and dependency-free wrapper for the native `fetch` API. It provides modern features like caching strategies, request retries, timeouts, and intelligent duplicate request handling, all in a compact package.

6 lines (4 loc) 5.15 kB
/* 📦 @alwatr/fetch v9.25.0 */ import{delay as C}from"@alwatr/delay";import{getGlobalThis as J}from"@alwatr/global-this";import{hasOwn as j}from"@alwatr/has-own";import{HttpStatusCodes as D,MimeTypes as O}from"@alwatr/http-primer";import{createLogger as P}from"@alwatr/logger";import{parseDuration as v}from"@alwatr/parse-duration";class G extends Error{response;data;reason;constructor(B,X,z,Q){super(X);this.name="FetchError",this.reason=B,this.response=z,this.data=Q}}var Z=P("@alwatr/fetch"),W=J(),$=j(W,"caches"),U={},x={method:"GET",headers:{},timeout:8000,retry:3,retryDelay:1000,removeDuplicate:"never",cacheStrategy:"network_only",cacheStorageName:"fetch_cache"};function N(B,X){Z.logMethodArgs?.("_processOptions",{url:B,options:X});let z={...x,...X,url:B};if(z.window??=null,z.removeDuplicate==="auto")z.removeDuplicate=$?"until_load":"always";if(z.url.lastIndexOf("?")===-1&&z.queryParams!=null){let Q=z.queryParams,V=Object.keys(Q).map((Y)=>`${encodeURIComponent(Y)}=${encodeURIComponent(String(Q[Y]))}`);if(V.length>0)z.url+="?"+V.join("&")}if(z.bodyJson!==void 0)z.body=JSON.stringify(z.bodyJson),z.headers["content-type"]=O.JSON;if(z.bearerToken!==void 0)z.headers.authorization=`Bearer ${z.bearerToken}`;else if(z.alwatrAuth!==void 0)z.headers.authorization=`Alwatr ${z.alwatrAuth.userId}:${z.alwatrAuth.userToken}`;return Z.logProperty?.("fetch.options",z),z}async function K(B){if(B.cacheStrategy==="network_only")return M(B);if(Z.logMethod?.("handleCacheStrategy_"),!$)return Z.incident?.("fetch","fetch_cache_strategy_unsupported",{cacheSupported:$}),B.cacheStrategy="network_only",M(B);let X=await caches.open(B.cacheStorageName),z=new Request(B.url,B);switch(B.cacheStrategy){case"cache_first":{let Q=await X.match(z);if(Q!=null)return Q;let V=await M(B);if(V.ok)X.put(z,V.clone());return V}case"cache_only":{let Q=await X.match(z);if(Q==null)throw new G("cache_not_found","Resource not found in cache");return Q}case"network_first":try{let Q=await M(B);if(Q.ok)X.put(z,Q.clone());return Q}catch(Q){let V=await X.match(z);if(V!=null)return V;throw Q}case"update_cache":{let Q=await M(B);if(Q.ok)X.put(z,Q.clone());return Q}case"stale_while_revalidate":{let Q=await X.match(z),V=M(B).then((Y)=>{if(Y.ok){if(X.put(z,Y.clone()),typeof B.revalidateCallback==="function")setTimeout(B.revalidateCallback,0,Y.clone())}return Y});return Q??V}default:return M(B)}}async function M(B){if(B.removeDuplicate==="never")return I(B);Z.logMethod?.("handleRemoveDuplicate_");let X=typeof B.body==="string"?B.body:"",z=`${B.method} ${B.url} ${X}`;U[z]??=I(B);try{let Q=await U[z];if(U[z]!=null){if(Q.ok!==!0||B.removeDuplicate==="until_load")delete U[z]}return Q.clone()}catch(Q){throw delete U[z],Q}}async function I(B){if(!(B.retry>1))return L(B);Z.logMethod?.("handleRetryPattern_"),B.retry--;let X=B.signal;try{let z=await L(B);if(!z.ok&&z.status>=D.Error_Server_500_Internal_Server_Error)throw new G("http_error",`HTTP error! status: ${z.status} ${z.statusText}`,z);return z}catch(z){if(Z.accident("fetch","fetch_failed_retry",z),W.navigator?.onLine===!1)throw Z.accident("handleRetryPattern_","offline","Skip retry because offline"),z;return await C.by(B.retryDelay),B.signal=X,I(B)}}function L(B){if(B.timeout===0)return W.fetch(B.url,B);return Z.logMethod?.("handleTimeout_"),new Promise((X,z)=>{let Q=typeof AbortController==="function"?new AbortController:null,V=B.signal;if(B.signal=Q?.signal,Q!==null&&V!=null)V.addEventListener("abort",()=>Q.abort(),{once:!0});let Y=setTimeout(()=>{z(new G("timeout","fetch_timeout")),Q?.abort("fetch_timeout")},v(B.timeout));W.fetch(B.url,B).then((H)=>X(H)).catch((H)=>z(H)).finally(()=>{clearTimeout(Y)})})}async function A(B,X={}){Z.logMethodArgs?.("fetch",{url:B,options:X});let z=N(B,X);try{let Q=await K(z);if(!Q.ok)throw new G("http_error",`HTTP error! status: ${Q.status} ${Q.statusText}`,Q);return[Q,null]}catch(Q){let V;if(Q instanceof G){if(V=Q,V.response!==void 0&&V.data===void 0){let Y=await V.response.text().catch(()=>"");if(Y.trim().length>0)try{V.data=JSON.parse(Y)}catch{V.data=Y}}}else if(Q instanceof Error)if(Q.name==="AbortError")V=new G("aborted",Q.message);else V=new G("network_error",Q.message);else V=new G("unknown_error",String(Q??"unknown_error"));return Z.error("fetch",V.reason,{error:V}),[null,V]}}A.version="9.25.0";async function R(B,X={}){Z.logMethodArgs?.("fetchJson",{url:B,options:X});let[z,Q]=await A(B,X);if(Q)return[null,Q];let V=await z.text().catch(()=>"");if(V.trim().length===0){let Y=new G("json_parse_error","Response body is empty, cannot parse JSON",z,V);return Z.error("fetchJson",Y.reason,{error:Y}),[null,Y]}try{let Y=JSON.parse(V);if(X.requireJsonResponseWithOkTrue&&Y.ok!==!0){let H=new G("json_response_error",'Response JSON "ok" property is not true',z,Y);return Z.error("fetchJson",H.reason,{error:H}),[null,H]}return[Y,null]}catch(Y){let H=new G("json_parse_error",Y instanceof Error?Y.message:"Failed to parse JSON response",z,V);return Z.error("fetchJson",H.reason,{error:H}),[null,H]}}export{R as fetchJson,A as fetch,$ as cacheSupported,G as FetchError}; //# debugId=33A2EED8C25B8F1E64756E2164756E21 //# sourceMappingURL=main.js.map