create-request 0.0.1-beta.22 → 0.0.1-beta.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1,1863 @@
1
- var e,t,s,r,o,i,n,a,h;!function(e){e.GET="GET",e.PUT="PUT",e.POST="POST",e.HEAD="HEAD",e.PATCH="PATCH",e.DELETE="DELETE",e.OPTIONS="OPTIONS"}(e||(e={})),function(e){e.LOW="low",e.HIGH="high",e.AUTO="auto"}(t||(t={})),function(e){e.OMIT="omit",e.INCLUDE="include",e.SAME_ORIGIN="same-origin"}(s||(s={})),function(e){e.CORS="cors",e.NO_CORS="no-cors",e.SAME_ORIGIN="same-origin",e.NAVIGATE="navigate"}(r||(r={})),function(e){e.ERROR="error",e.FOLLOW="follow",e.MANUAL="manual"}(o||(o={})),function(e){e.DEFAULT="default",e.RELOAD="reload",e.NO_STORE="no-store",e.NO_CACHE="no-cache",e.FORCE_CACHE="force-cache",e.ONLY_IF_CACHED="only-if-cached"}(i||(i={})),function(e){e.LAX="Lax",e.NONE="None",e.STRICT="Strict"}(n||(n={})),function(e){e.JSON="json",e.STRING="string",e.BINARY="binary"}(a||(a={})),function(e){e.ORIGIN="origin",e.UNSAFE_URL="unsafe-url",e.SAME_ORIGIN="same-origin",e.NO_REFERRER="no-referrer",e.STRICT_ORIGIN="strict-origin",e.ORIGIN_WHEN_CROSS_ORIGIN="origin-when-cross-origin",e.NO_REFERRER_WHEN_DOWNGRADE="no-referrer-when-downgrade",e.STRICT_ORIGIN_WHEN_CROSS_ORIGIN="strict-origin-when-cross-origin"}(h||(h={}));class c{constructor(e){this.bodyUsed=!1,this.response=e}get status(){return this.response.status}get statusText(){return this.response.statusText}get headers(){return this.response.headers}get ok(){return this.response.ok}get raw(){return this.response}async getJson(){if(void 0!==this.jsonCache)return this.jsonCache;if(this.bodyUsed){if(void 0!==this.textCache)try{return this.jsonCache=JSON.parse(this.textCache),this.jsonCache}catch(e){throw Error("Cannot parse cached text as JSON")}throw Error("Response body has already been consumed in a different format")}return this.bodyUsed=!0,this.jsonCache=await this.response.json(),this.jsonCache}async getText(){if(void 0!==this.textCache)return this.textCache;if(this.bodyUsed){if(void 0!==this.jsonCache)return this.textCache=JSON.stringify(this.jsonCache),this.textCache;if(void 0!==this.blobCache)return this.textCache=await this.blobCache.text(),this.textCache;throw Error("Response body has already been consumed in a format that cannot be converted to text")}return this.bodyUsed=!0,this.textCache=await this.response.text(),this.textCache}async getBlob(){if(void 0!==this.blobCache)return this.blobCache;if(this.bodyUsed){if(void 0!==this.textCache)return this.blobCache=new Blob([this.textCache],{type:"text/plain"}),this.blobCache;throw Error("Response body has already been consumed in a format that cannot be converted to Blob")}return this.bodyUsed=!0,this.blobCache=await this.response.blob(),this.blobCache}async getArrayBuffer(){if(void 0!==this.arrayBufferCache)return this.arrayBufferCache;if(this.bodyUsed){if(void 0!==this.textCache){const e=(new TextEncoder).encode(this.textCache);return this.arrayBufferCache=e.buffer,this.arrayBufferCache}if(void 0!==this.blobCache)return this.arrayBufferCache=await this.blobCache.arrayBuffer(),this.arrayBufferCache;throw Error("Response body has already been consumed in a format that cannot be converted to ArrayBuffer")}return this.bodyUsed=!0,this.arrayBufferCache=await this.response.arrayBuffer(),this.arrayBufferCache}getBody(){if(this.bodyUsed)throw Error("Response body has already been consumed");return this.bodyUsed=!0,this.response.body}}function u(){const e=new Map;return{get:t=>e.get(t)||null,set(t,s){e.set(t,s)},has:t=>e.has(t),delete(t){e.delete(t)},clear(){e.clear()}}}function f(){return{get:e=>"undefined"==typeof localStorage?null:localStorage.getItem(e),set(e,t){if("undefined"!=typeof localStorage)try{localStorage.setItem(e,t)}catch(s){this.clear();try{localStorage.setItem(e,t)}catch(e){}}},has:e=>"undefined"!=typeof localStorage&&null!==localStorage.getItem(e),delete(e){"undefined"!=typeof localStorage&&localStorage.removeItem(e)},clear(){"undefined"!=typeof localStorage&&localStorage.clear()}}}function d(){return{get:e=>"undefined"==typeof sessionStorage?null:sessionStorage.getItem(e),set(e,t){if("undefined"!=typeof sessionStorage)try{sessionStorage.setItem(e,t)}catch(s){this.clear();try{sessionStorage.setItem(e,t)}catch(e){}}},has:e=>"undefined"!=typeof sessionStorage&&null!==sessionStorage.getItem(e),delete(e){"undefined"!=typeof sessionStorage&&sessionStorage.removeItem(e)},clear(){"undefined"!=typeof sessionStorage&&sessionStorage.clear()}}}class l{constructor(e={}){this.entryCount=0,this.totalSize=0,this.options=e,this.storage=function(e){const{storage:t}=e||{};return t?"function"==typeof t.get&&"function"==typeof t.set?t:t===localStorage?f():t===sessionStorage?d():t instanceof Map?{get:e=>t.get(e)||null,set(e,s,r){t.set(e,s)},has:e=>t.has(e),delete(e){t.delete(e)},clear(){t.clear()}}:u():u()}(e)}generateKey(e,t,s,r){if(this.options.keyGenerator){const o=this.options.keyGenerator(e,t,s,r);return this.options.keyPrefix?`${this.options.keyPrefix}:${o}`:o}let o=`${t}:${e}`;if(this.options.varyByHeaders&&s){const e={};this.options.varyByHeaders.forEach((t=>{const r=t.toLowerCase();Object.keys(s).forEach((t=>{t.toLowerCase()===r&&(e[r]=s[t])}))})),Object.keys(e).length>0&&(o+=":"+JSON.stringify(e))}if("GET"!==t&&null!=r)try{"string"==typeof r||"number"==typeof r||"boolean"==typeof r?o+=":"+r:"object"==typeof r&&(o+=":"+JSON.stringify(r))}catch(e){}return this.options.keyPrefix?`${this.options.keyPrefix}:${o}`:o}async get(e){const t=await Promise.resolve(this.storage.get(e));if(!t)return null;try{const s=JSON.parse(t);return s.expiry&&s.expiry<Date.now()?(await Promise.resolve(this.storage.delete(e)),null):s}catch(t){return await Promise.resolve(this.storage.delete(e)),null}}async set(e,t,s){const r=Date.now(),o=this.options.ttl,i=JSON.stringify({value:t,timestamp:r,expiry:o?r+o:null,headers:s});if(this.options.maxSize){const e=function(e){var t;if("number"==typeof e)return e;const s=e.match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB)?$/i);if(!s)return 0;const r=parseFloat(s[1]);switch(null===(t=s[2])||void 0===t?void 0:t.toUpperCase()){case"KB":return 1024*r;case"MB":return 1048576*r;case"GB":return 1073741824*r;default:return r}}(this.options.maxSize),t=(new TextEncoder).encode(i).length;if(t>e)return;this.totalSize+t>e&&(await Promise.resolve(this.storage.clear()),this.totalSize=0,this.entryCount=0),this.totalSize+=t}this.options.maxEntries&&(this.entryCount++,this.entryCount>this.options.maxEntries&&(await Promise.resolve(this.storage.clear()),this.entryCount=1)),await Promise.resolve(this.storage.set(e,i,o))}async has(e){return Promise.resolve(this.storage.has(e))}async delete(e){return Promise.resolve(this.storage.delete(e))}async clear(){return this.entryCount=0,this.totalSize=0,Promise.resolve(this.storage.clear())}}class y{static formatRequestCookies(e){const t=[];return Object.entries(e).forEach((([e,s])=>{let r;r="string"==typeof s?s:s.value,t.push(`${encodeURIComponent(e)}=${encodeURIComponent(r)}`)})),t.join("; ")}}class p extends Error{constructor(e,t,s,r={}){super(e),this.name="RequestError",this.url=t,this.method=s,this.status=r.status,this.response=r.response,this.timeoutError=r.timeoutError,Error.captureStackTrace&&Error.captureStackTrace(this,p),Object.setPrototypeOf(this,p.prototype)}static timeout(e,t,s){return new p(`Request timed out after ${s}ms`,e,t,{timeoutError:!0})}static fromResponse(e,t,s){return new p("Request failed with status "+e.status,t,s,{status:e.status,response:e})}static networkError(e,t,s){const r=new p("Network error: "+s.message,e,t);return r.stack=s.stack,r}}class m{static getTokenFromMeta(e="csrf-token"){if("undefined"==typeof document)return null;const t=document.querySelector(`meta[name="${e}"]`);return(null==t?void 0:t.getAttribute("content"))||null}static getTokenFromCookie(e="csrf-token"){if("undefined"==typeof document)return null;const t=document.cookie.split(";");for(const s of t){const[t,r]=s.trim().split("=");if(t===e)return decodeURIComponent(r)}return null}static isValidToken(e){return!("string"!=typeof e||8>e.length||e.length>10&&(!/^[A-Za-z0-9\-_=+/.]+$/.test(e)||2>[/[A-Z]/.test(e),/[a-z]/.test(e),/[0-9]/.test(e),/[-_=+/.]/.test(e)].filter(Boolean).length))}}class g{constructor(){this.csrfHeaderName="X-CSRF-Token",this.xsrfCookieName="XSRF-TOKEN",this.xsrfHeaderName="X-XSRF-TOKEN",this.csrfToken=null,this.enableAutoXsrf=!0,this.enableAntiCsrf=!0}static getInstance(){return g.instance||(g.instance=new g),g.instance}setCsrfToken(e){return this.csrfToken=e,this}getCsrfToken(){return this.csrfToken}setCsrfHeaderName(e){return this.csrfHeaderName=e,this}getCsrfHeaderName(){return this.csrfHeaderName}setXsrfCookieName(e){return this.xsrfCookieName=e,this}getXsrfCookieName(){return this.xsrfCookieName}setXsrfHeaderName(e){return this.xsrfHeaderName=e,this}getXsrfHeaderName(){return this.xsrfHeaderName}setEnableAutoXsrf(e){return this.enableAutoXsrf=e,this}isAutoXsrfEnabled(){return this.enableAutoXsrf}setEnableAntiCsrf(e){return this.enableAntiCsrf=e,this}isAntiCsrfEnabled(){return this.enableAntiCsrf}reset(){return this.csrfToken=null,this.csrfHeaderName="X-CSRF-Token",this.enableAntiCsrf=!0,this.xsrfCookieName="XSRF-TOKEN",this.xsrfHeaderName="X-XSRF-TOKEN",this.enableAutoXsrf=!0,this}}class C{constructor(){this.requestOptions={headers:{}},this.queryParams=new URLSearchParams,this.autoApplyCsrfProtection=!0,this.cacheEnabled=!1}withHeaders(e){return this.requestOptions.headers={...this.requestOptions.headers,...e},this}withHeader(e,t){return this.withHeaders({[e]:t})}withTimeout(e){if(!Number.isFinite(e)||0>=e)throw Error("Timeout must be a positive number");return this.requestOptions.timeout=e,this}withRetries(e){if(!Number.isInteger(e)||0>e)throw Error("Retry count must be a non-negative integer");return this.requestOptions.retries=e,this}onRetry(e){return this.requestOptions.onRetry=e,this}withCredentials(e=s.SAME_ORIGIN){return this.requestOptions.credentials=e,this}withAbortController(e){return this.abortController=e,this}withReferrer(e){return this.requestOptions.referrer=e,this}withReferrerPolicy(e){return this.requestOptions.referrerPolicy=e,this}withRedirect(e){return this.requestOptions.redirect=e,this}withKeepAlive(e){return this.requestOptions.keepalive=e,this}withPriority(e){return this.requestOptions.priority=e,this}withQueryParams(e){return Object.entries(e).forEach((([e,t])=>{null!=t&&(Array.isArray(t)?t.forEach((t=>this.queryParams.append(e,t+""))):this.queryParams.append(e,t+""))})),this}withQueryParam(e,t){return null==t||(Array.isArray(t)?t.forEach((t=>this.queryParams.append(e,t+""))):this.queryParams.append(e,t+"")),this}withMode(e){return this.requestOptions.mode=e,this}withContentType(e){return this.withHeaders({"Content-Type":e})}withAuthorization(e){return this.withHeaders({Authorization:e})}withBasicAuth(e,t){const s=this.encodeBase64(`${e}:${t}`);return this.withAuthorization("Basic "+s)}encodeBase64(e){if("undefined"!=typeof TextEncoder&&"function"==typeof btoa){const t=(new TextEncoder).encode(e);return btoa(String.fromCharCode.call(null,...new Uint8Array(t)))}if("function"==typeof btoa)return btoa(e);if("undefined"!=typeof Buffer)return Buffer.from(e).toString("base64");throw Error("Base64 encoding is not supported")}withBearerToken(e){return this.withAuthorization("Bearer "+e)}hasHeader(e,t){return Object.keys(e).some((e=>e.toLowerCase()===t.toLowerCase()))}withCookies(e){if(0===Object.entries(e||{}).length)return this;const t=this.requestOptions.headers||{},s=[],r=[];Object.keys(t).forEach((e=>{"cookie"===e.toLowerCase()&&(t[e]&&s.push(t[e]),r.push(e))}));const o=y.formatRequestCookies(e),i=r.length>0?r[0]:"Cookie",n=[...s,o].filter(Boolean).join("; "),a={...t};return r.forEach((e=>{delete a[e]})),this.requestOptions.headers={...a,[i]:n},this}withCookie(e,t){return this.withCookies({[e]:t})}withCsrfToken(e,t="X-CSRF-Token"){return this.withHeaders({[t]:e})}withoutCsrfProtection(){return this.autoApplyCsrfProtection=!1,this}withAntiCsrfHeaders(){return this.withHeaders({"X-Requested-With":"XMLHttpRequest"})}withCache(e={}){return this.cacheEnabled=!0,this.cacheManager=new l(e),this}sendTo(e){if(e=this.formatUrlWithQueryParams(e),this.autoApplyCsrfProtection){const e=g.getInstance();e.isAntiCsrfEnabled()&&this.withHeaders({"X-Requested-With":"XMLHttpRequest"});const t=e.getCsrfToken();if(t){const s=this.requestOptions.headers,r=e.getCsrfHeaderName();this.hasHeader(s,"X-CSRF-Token")||this.hasHeader(s,r)||this.withHeaders({[r]:t})}if(e.isAutoXsrfEnabled()&&"undefined"!=typeof document){const t=m.getTokenFromCookie(e.getXsrfCookieName());if(t&&m.isValidToken(t)){const s=this.requestOptions.headers,r=e.getXsrfHeaderName();this.hasHeader(s,"X-XSRF-TOKEN")||this.hasHeader(s,r)||this.withHeaders({[r]:t})}}}const t={...this.requestOptions,method:this.method};let s;if(this.cacheEnabled&&this.cacheManager){const r=this.cacheManager.generateKey(e,this.method,this.requestOptions.headers,this.requestOptions.body);s=this.executeWithCache(e,t,r)}else s=this.requestOptions.retries?this.executeWithRetries(e,t):this.executeRequest(e,t);const r=s;return r.getJson=async()=>s.then((e=>e.getJson())),r.getText=async()=>s.then((e=>e.getText())),r.getBlob=async()=>s.then((e=>e.getBlob())),r.getArrayBuffer=async()=>s.then((e=>e.getArrayBuffer())),r.getBody=async()=>s.then((e=>e.getBody())),r.getData=async e=>{try{const t=await s.then((e=>e.getJson()));return e?e(t):t}catch(e){const t=Error(`Data selector failed: ${e instanceof Error?e.message:e+""}. Original data: ${JSON.stringify(await s.then((e=>e.getJson())),null,2)}`);throw e instanceof Error&&e.stack&&(t.stack=e.stack),t}},r}formatUrlWithQueryParams(e){if(!(""+this.queryParams))return e;try{const t=new URL(e);return this.queryParams.forEach(((e,s)=>{t.searchParams.append(s,e)})),""+t}catch(t){const s=e.includes("?");return`${e}${s?"&":"?"}${""+this.queryParams}`}}async executeWithRetries(e,t){const s=this.requestOptions.retries||0;for(let r=0;s>=r;r++)try{return await this.executeRequest(e,t)}catch(o){const i=o instanceof p?o:p.networkError(e,t.method,o);if(r>=s)throw i;this.requestOptions.onRetry&&await this.requestOptions.onRetry({attempt:r+1,error:i})}throw new p("Max retries reached",e,t.method)}async executeRequest(e,t){const s=this.abortController||new AbortController,{signal:r}=s;let o;t.signal=t.signal||r;let i=!1;this.requestOptions.timeout&&(this.abortController?"function"==typeof AbortSignal.timeout?AbortSignal.timeout(this.requestOptions.timeout).addEventListener("abort",(()=>{s.signal.aborted||(i=!0,s.abort())})):o=setTimeout((()=>{s.signal.aborted||(i=!0,s.abort())}),this.requestOptions.timeout):o=setTimeout((()=>{i=!0,s.abort()}),this.requestOptions.timeout));try{let s;try{s=await fetch(e,t)}catch(s){if(s instanceof DOMException&&"AbortError"===s.name&&i)throw p.timeout(e,t.method,this.requestOptions.timeout);throw p.networkError(e,t.method,s)}if(!s.ok)throw p.fromResponse(s,e,t.method);return new c(s)}finally{o&&clearTimeout(o)}}async executeWithCache(e,t,s){if(this.cacheManager){const e=await this.cacheManager.get(s);if(e){const t=new Response(JSON.stringify(e.value),{status:200,headers:new Headers(e.headers||{})});return new c(t)}}const r=await(this.requestOptions.retries?this.executeWithRetries(e,t):this.executeRequest(e,t));if(r.ok&&this.cacheManager)try{const e=await r.getJson(),t={};r.headers.forEach(((e,s)=>{t[s]=e})),await this.cacheManager.set(s,e,t)}catch(e){}return r}}class b extends C{withBody(e){if(this.body=e,"string"==typeof e)this.bodyType=a.STRING,this.setContentTypeIfNeeded("text/plain");else if(null===e||"object"!=typeof e||e instanceof FormData||e instanceof Blob||e instanceof ArrayBuffer||e instanceof URLSearchParams||e instanceof ReadableStream)this.bodyType=a.BINARY;else{this.bodyType=a.JSON,this.setContentTypeIfNeeded("application/json");try{JSON.stringify(e)}catch(e){throw Error("Failed to stringify request body: "+e)}}return this}setContentTypeIfNeeded(e){Object.keys(this.requestOptions.headers).some((e=>"content-type"===e.toLowerCase()))||this.withContentType(e)}sendTo(e){return void 0!==this.body&&(this.requestOptions.body&&delete this.requestOptions.body,this.requestOptions.body=this.bodyType===a.JSON?JSON.stringify(this.body):this.body),super.sendTo(e)}}class w extends C{constructor(){super(...arguments),this.method=e.GET}}class O extends C{constructor(){super(...arguments),this.method=e.HEAD}}class E extends C{constructor(){super(...arguments),this.method=e.OPTIONS}}class S extends C{constructor(){super(...arguments),this.method=e.DELETE}}class R extends b{constructor(){super(...arguments),this.method=e.POST}}class T extends b{constructor(){super(...arguments),this.method=e.PUT}}class N extends b{constructor(){super(...arguments),this.method=e.PATCH}}const x={get:function(){return new w},post:function(){return new R},put:function(){return new T},del:function(){return new S},patch:function(){return new N},head:function(){return new O},options:function(){return new E},config:g.getInstance()};export{l as CacheManager,i as CacheMode,y as CookieUtils,s as CredentialsPolicy,S as DeleteRequest,w as GetRequest,O as HeadRequest,e as HttpMethod,E as OptionsRequest,N as PatchRequest,R as PostRequest,T as PutRequest,o as RedirectMode,h as ReferrerPolicy,p as RequestError,r as RequestMode,t as RequestPriority,c as ResponseWrapper,n as SameSitePolicy,f as createLocalStorageStorage,u as createMemoryStorage,d as createSessionStorageStorage,x as default};//# sourceMappingURL=index.esm.js.map
1
+ /**
2
+ * Enum for HTTP methods
3
+ */
4
+ var HttpMethod;
5
+ (function (HttpMethod) {
6
+ HttpMethod["GET"] = "GET";
7
+ HttpMethod["PUT"] = "PUT";
8
+ HttpMethod["POST"] = "POST";
9
+ HttpMethod["HEAD"] = "HEAD";
10
+ HttpMethod["PATCH"] = "PATCH";
11
+ HttpMethod["DELETE"] = "DELETE";
12
+ HttpMethod["OPTIONS"] = "OPTIONS";
13
+ })(HttpMethod || (HttpMethod = {}));
14
+ /**
15
+ * Enum for request priorities
16
+ */
17
+ var RequestPriority;
18
+ (function (RequestPriority) {
19
+ RequestPriority["LOW"] = "low";
20
+ RequestPriority["HIGH"] = "high";
21
+ RequestPriority["AUTO"] = "auto";
22
+ })(RequestPriority || (RequestPriority = {}));
23
+ /**
24
+ * Enum for credentials policies
25
+ */
26
+ var CredentialsPolicy;
27
+ (function (CredentialsPolicy) {
28
+ CredentialsPolicy["OMIT"] = "omit";
29
+ CredentialsPolicy["INCLUDE"] = "include";
30
+ CredentialsPolicy["SAME_ORIGIN"] = "same-origin";
31
+ })(CredentialsPolicy || (CredentialsPolicy = {}));
32
+ /**
33
+ * Enum for request modes
34
+ */
35
+ var RequestMode;
36
+ (function (RequestMode) {
37
+ RequestMode["CORS"] = "cors";
38
+ RequestMode["NO_CORS"] = "no-cors";
39
+ RequestMode["SAME_ORIGIN"] = "same-origin";
40
+ RequestMode["NAVIGATE"] = "navigate";
41
+ })(RequestMode || (RequestMode = {}));
42
+ /**
43
+ * Enum for redirect modes
44
+ */
45
+ var RedirectMode;
46
+ (function (RedirectMode) {
47
+ RedirectMode["ERROR"] = "error";
48
+ RedirectMode["FOLLOW"] = "follow";
49
+ RedirectMode["MANUAL"] = "manual";
50
+ })(RedirectMode || (RedirectMode = {}));
51
+ /**
52
+ * Enum for cache modes
53
+ */
54
+ var CacheMode;
55
+ (function (CacheMode) {
56
+ CacheMode["DEFAULT"] = "default";
57
+ CacheMode["RELOAD"] = "reload";
58
+ CacheMode["NO_STORE"] = "no-store";
59
+ CacheMode["NO_CACHE"] = "no-cache";
60
+ CacheMode["FORCE_CACHE"] = "force-cache";
61
+ CacheMode["ONLY_IF_CACHED"] = "only-if-cached";
62
+ })(CacheMode || (CacheMode = {}));
63
+ /**
64
+ * Enum for cookie SameSite policies
65
+ */
66
+ var SameSitePolicy;
67
+ (function (SameSitePolicy) {
68
+ SameSitePolicy["LAX"] = "Lax";
69
+ SameSitePolicy["NONE"] = "None";
70
+ SameSitePolicy["STRICT"] = "Strict";
71
+ })(SameSitePolicy || (SameSitePolicy = {}));
72
+ /**
73
+ * Enum for body types
74
+ */
75
+ var BodyType;
76
+ (function (BodyType) {
77
+ BodyType["JSON"] = "json";
78
+ BodyType["STRING"] = "string";
79
+ BodyType["BINARY"] = "binary";
80
+ })(BodyType || (BodyType = {}));
81
+ /**
82
+ * Referrer policies for fetch requests
83
+ */
84
+ var ReferrerPolicy;
85
+ (function (ReferrerPolicy) {
86
+ ReferrerPolicy["ORIGIN"] = "origin";
87
+ ReferrerPolicy["UNSAFE_URL"] = "unsafe-url";
88
+ ReferrerPolicy["SAME_ORIGIN"] = "same-origin";
89
+ ReferrerPolicy["NO_REFERRER"] = "no-referrer";
90
+ ReferrerPolicy["STRICT_ORIGIN"] = "strict-origin";
91
+ ReferrerPolicy["ORIGIN_WHEN_CROSS_ORIGIN"] = "origin-when-cross-origin";
92
+ ReferrerPolicy["NO_REFERRER_WHEN_DOWNGRADE"] = "no-referrer-when-downgrade";
93
+ ReferrerPolicy["STRICT_ORIGIN_WHEN_CROSS_ORIGIN"] = "strict-origin-when-cross-origin";
94
+ })(ReferrerPolicy || (ReferrerPolicy = {}));/**
95
+ * Wrapper for HTTP responses with methods to transform the response data
96
+ * Provides caching and conversion between different response formats.
97
+ */
98
+ class ResponseWrapper {
99
+ constructor(response) {
100
+ this.bodyUsed = false;
101
+ this.response = response;
102
+ }
103
+ // Wrapper properties
104
+ get status() {
105
+ return this.response.status;
106
+ }
107
+ get statusText() {
108
+ return this.response.statusText;
109
+ }
110
+ get headers() {
111
+ return this.response.headers;
112
+ }
113
+ get ok() {
114
+ return this.response.ok;
115
+ }
116
+ get raw() {
117
+ return this.response;
118
+ }
119
+ /**
120
+ * Parse the response body as JSON
121
+ * Caches the result for subsequent calls.
122
+ *
123
+ * @returns The parsed JSON data
124
+ * @throws Error if the response has already been consumed in a different format
125
+ *
126
+ * @example
127
+ * const data = await response.getJson();
128
+ * console.log(data.items);
129
+ */
130
+ async getJson() {
131
+ if (this.jsonCache !== undefined) {
132
+ return this.jsonCache;
133
+ }
134
+ if (this.bodyUsed) {
135
+ // If we already have text, try to parse it as JSON
136
+ if (this.textCache !== undefined) {
137
+ try {
138
+ // Fix unsafe assignment by explicitly casting to unknown first
139
+ this.jsonCache = JSON.parse(this.textCache);
140
+ return this.jsonCache;
141
+ }
142
+ catch (e) {
143
+ throw new Error("Cannot parse cached text as JSON");
144
+ }
145
+ }
146
+ throw new Error("Response body has already been consumed in a different format");
147
+ }
148
+ this.bodyUsed = true;
149
+ this.jsonCache = (await this.response.json());
150
+ return this.jsonCache;
151
+ }
152
+ /**
153
+ * Get the response body as text
154
+ * Caches the result for subsequent calls. Attempts to convert from JSON or Blob
155
+ * if the body has already been consumed in those formats.
156
+ *
157
+ * @returns The response text
158
+ * @throws Error if the response has already been consumed in an incompatible format
159
+ *
160
+ * @example
161
+ * const text = await response.getText();
162
+ */
163
+ async getText() {
164
+ if (this.textCache !== undefined) {
165
+ return this.textCache;
166
+ }
167
+ if (this.bodyUsed) {
168
+ // If we already have JSON, convert it to text
169
+ if (this.jsonCache !== undefined) {
170
+ this.textCache = JSON.stringify(this.jsonCache);
171
+ return this.textCache;
172
+ }
173
+ // If we have blob, convert to text
174
+ if (this.blobCache !== undefined) {
175
+ this.textCache = await this.blobCache.text();
176
+ return this.textCache;
177
+ }
178
+ throw new Error("Response body has already been consumed in a format that cannot be converted to text");
179
+ }
180
+ this.bodyUsed = true;
181
+ this.textCache = await this.response.text();
182
+ return this.textCache;
183
+ }
184
+ /**
185
+ * Get the response body as a Blob
186
+ * Caches the result for subsequent calls. Attempts to convert from text
187
+ * if the body has already been consumed as text.
188
+ *
189
+ * @returns The response as a Blob
190
+ * @throws Error if the response has already been consumed in an incompatible format
191
+ *
192
+ * @example
193
+ * const blob = await response.getBlob();
194
+ * const url = URL.createObjectURL(blob);
195
+ */
196
+ async getBlob() {
197
+ if (this.blobCache !== undefined) {
198
+ return this.blobCache;
199
+ }
200
+ if (this.bodyUsed) {
201
+ // If we have text, convert to Blob
202
+ if (this.textCache !== undefined) {
203
+ this.blobCache = new Blob([this.textCache], { type: "text/plain" });
204
+ return this.blobCache;
205
+ }
206
+ throw new Error("Response body has already been consumed in a format that cannot be converted to Blob");
207
+ }
208
+ this.bodyUsed = true;
209
+ this.blobCache = await this.response.blob();
210
+ return this.blobCache;
211
+ }
212
+ /**
213
+ * Get the response body as an ArrayBuffer
214
+ * Caches the result for subsequent calls. Attempts to convert from text or Blob
215
+ * if the body has already been consumed in those formats.
216
+ *
217
+ * @returns The response as an ArrayBuffer
218
+ * @throws Error if the response has already been consumed in an incompatible format
219
+ *
220
+ * @example
221
+ * const buffer = await response.getArrayBuffer();
222
+ */
223
+ async getArrayBuffer() {
224
+ if (this.arrayBufferCache !== undefined) {
225
+ return this.arrayBufferCache;
226
+ }
227
+ if (this.bodyUsed) {
228
+ // If we have text, convert to ArrayBuffer
229
+ if (this.textCache !== undefined) {
230
+ const encoder = new TextEncoder();
231
+ // Fix ArrayBufferLike by explicitly casting to ArrayBuffer
232
+ const uint8Array = encoder.encode(this.textCache);
233
+ this.arrayBufferCache = uint8Array.buffer;
234
+ return this.arrayBufferCache;
235
+ }
236
+ // If we have blob, convert to ArrayBuffer
237
+ if (this.blobCache !== undefined) {
238
+ this.arrayBufferCache = await this.blobCache.arrayBuffer();
239
+ return this.arrayBufferCache;
240
+ }
241
+ throw new Error("Response body has already been consumed in a format that cannot be converted to ArrayBuffer");
242
+ }
243
+ this.bodyUsed = true;
244
+ this.arrayBufferCache = await this.response.arrayBuffer();
245
+ return this.arrayBufferCache;
246
+ }
247
+ /**
248
+ * Get the raw response body as a ReadableStream
249
+ * This method consumes the body and should only be called once.
250
+ *
251
+ * @returns The response body as a ReadableStream or null
252
+ * @throws Error if the response body has already been consumed
253
+ *
254
+ * @example
255
+ * const stream = response.getBody();
256
+ * if (stream) {
257
+ * const reader = stream.getReader();
258
+ * // Process the stream
259
+ * }
260
+ */
261
+ getBody() {
262
+ if (this.bodyUsed) {
263
+ throw new Error("Response body has already been consumed");
264
+ }
265
+ this.bodyUsed = true;
266
+ return this.response.body;
267
+ }
268
+ }/**
269
+ * Create a memory storage provider using Map
270
+ * This provider stores data in memory and is cleared when the application restarts.
271
+ *
272
+ * @returns A storage provider using an in-memory Map
273
+ *
274
+ * @example
275
+ * const storage = createMemoryStorage();
276
+ * storage.set("key", "value");
277
+ */
278
+ function createMemoryStorage() {
279
+ const store = new Map();
280
+ return {
281
+ get(key) {
282
+ return store.get(key) || null;
283
+ },
284
+ set(key, value) {
285
+ store.set(key, value);
286
+ },
287
+ has(key) {
288
+ return store.has(key);
289
+ },
290
+ delete(key) {
291
+ store.delete(key);
292
+ },
293
+ clear() {
294
+ store.clear();
295
+ },
296
+ };
297
+ }
298
+ /**
299
+ * Create a localStorage-based storage provider
300
+ * This provider persists data between browser sessions.
301
+ *
302
+ * @returns A storage provider using localStorage
303
+ * @throws Console warnings if localStorage is not available or storage quota is exceeded
304
+ *
305
+ * @example
306
+ * const storage = createLocalStorageStorage();
307
+ * storage.set("key", "value");
308
+ */
309
+ function createLocalStorageStorage() {
310
+ return {
311
+ get(key) {
312
+ if (typeof localStorage === "undefined")
313
+ return null;
314
+ return localStorage.getItem(key);
315
+ },
316
+ set(key, value) {
317
+ if (typeof localStorage === "undefined")
318
+ return;
319
+ try {
320
+ localStorage.setItem(key, value);
321
+ }
322
+ catch (e) {
323
+ // Handle quota exceeded errors
324
+ console.warn("Cache storage quota exceeded, clearing cache");
325
+ void this.clear();
326
+ try {
327
+ localStorage.setItem(key, value);
328
+ }
329
+ catch (e) {
330
+ console.error("Failed to set cache item even after clearing cache");
331
+ }
332
+ }
333
+ },
334
+ has(key) {
335
+ if (typeof localStorage === "undefined")
336
+ return false;
337
+ return localStorage.getItem(key) !== null;
338
+ },
339
+ delete(key) {
340
+ if (typeof localStorage === "undefined")
341
+ return;
342
+ localStorage.removeItem(key);
343
+ },
344
+ clear() {
345
+ if (typeof localStorage === "undefined")
346
+ return;
347
+ localStorage.clear();
348
+ },
349
+ };
350
+ }
351
+ /**
352
+ * Create a sessionStorage-based storage provider
353
+ * This provider persists data only for the current browser session.
354
+ *
355
+ * @returns A storage provider using sessionStorage
356
+ * @throws Console warnings if sessionStorage is not available or storage quota is exceeded
357
+ *
358
+ * @example
359
+ * const storage = createSessionStorageStorage();
360
+ * storage.set("key", "value");
361
+ */
362
+ function createSessionStorageStorage() {
363
+ return {
364
+ get(key) {
365
+ if (typeof sessionStorage === "undefined")
366
+ return null;
367
+ return sessionStorage.getItem(key);
368
+ },
369
+ set(key, value) {
370
+ if (typeof sessionStorage === "undefined")
371
+ return;
372
+ try {
373
+ sessionStorage.setItem(key, value);
374
+ }
375
+ catch (e) {
376
+ // Handle quota exceeded errors
377
+ console.warn("Cache storage quota exceeded, clearing cache");
378
+ void this.clear();
379
+ try {
380
+ sessionStorage.setItem(key, value);
381
+ }
382
+ catch (e) {
383
+ console.error("Failed to set cache item even after clearing cache");
384
+ }
385
+ }
386
+ },
387
+ has(key) {
388
+ if (typeof sessionStorage === "undefined")
389
+ return false;
390
+ return sessionStorage.getItem(key) !== null;
391
+ },
392
+ delete(key) {
393
+ if (typeof sessionStorage === "undefined")
394
+ return;
395
+ sessionStorage.removeItem(key);
396
+ },
397
+ clear() {
398
+ if (typeof sessionStorage === "undefined")
399
+ return;
400
+ sessionStorage.clear();
401
+ },
402
+ };
403
+ }
404
+ /**
405
+ * Create a storage provider from cache options
406
+ * Selects the appropriate storage implementation based on the provided options.
407
+ * Falls back to memory storage if no suitable storage is found.
408
+ *
409
+ * @param options - Cache configuration options
410
+ * @returns A storage provider based on the options
411
+ *
412
+ * @example
413
+ * const storage = createStorageProvider({ storage: localStorage });
414
+ */
415
+ function createStorageProvider(options) {
416
+ const { storage } = options || {};
417
+ if (!storage) {
418
+ // Default to memory storage
419
+ return createMemoryStorage();
420
+ }
421
+ // Check if storage is already a StorageProvider
422
+ if (typeof storage.get === "function" && typeof storage.set === "function") {
423
+ return storage;
424
+ }
425
+ // Check if it's localStorage or sessionStorage
426
+ if (storage === localStorage) {
427
+ return createLocalStorageStorage();
428
+ }
429
+ if (storage === sessionStorage) {
430
+ return createSessionStorageStorage();
431
+ }
432
+ // Check if it's a Map
433
+ if (storage instanceof Map) {
434
+ return {
435
+ get(key) {
436
+ return storage.get(key) || null;
437
+ },
438
+ set(key, value, _ttl) {
439
+ storage.set(key, value);
440
+ },
441
+ has(key) {
442
+ return storage.has(key);
443
+ },
444
+ delete(key) {
445
+ storage.delete(key);
446
+ },
447
+ clear() {
448
+ storage.clear();
449
+ },
450
+ };
451
+ }
452
+ // Default to memory storage
453
+ console.warn("Unsupported storage type, falling back to memory storage");
454
+ return createMemoryStorage();
455
+ }/**
456
+ * Parse size strings like "10MB" into bytes
457
+ * Supports KB, MB, and GB units. If no unit is specified, assumes bytes.
458
+ *
459
+ * @param size - Size as number (bytes) or string with units (e.g., "5MB")
460
+ * @returns Size in bytes
461
+ */
462
+ function parseSize(size) {
463
+ var _a;
464
+ if (typeof size === "number")
465
+ return size;
466
+ const match = size.match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB)?$/i);
467
+ if (!match)
468
+ return 0;
469
+ const num = parseFloat(match[1]);
470
+ const unit = (_a = match[2]) === null || _a === void 0 ? void 0 : _a.toUpperCase();
471
+ switch (unit) {
472
+ case "KB":
473
+ return num * 1024;
474
+ case "MB":
475
+ return num * 1024 * 1024;
476
+ case "GB":
477
+ return num * 1024 * 1024 * 1024;
478
+ default:
479
+ return num;
480
+ }
481
+ }/**
482
+ * Cache manager that handles storage operations
483
+ * Provides methods for storing, retrieving, and managing cached API responses.
484
+ */
485
+ class CacheManager {
486
+ constructor(options = {}) {
487
+ this.entryCount = 0;
488
+ this.totalSize = 0;
489
+ this.options = options;
490
+ this.storage = createStorageProvider(options);
491
+ }
492
+ /**
493
+ * Generate a cache key based on request details
494
+ * Keys can vary based on URL, method, headers, and request body.
495
+ *
496
+ * @param url - The request URL
497
+ * @param method - The HTTP method
498
+ * @param headers - Request headers that might affect caching
499
+ * @param body - Request body for non-GET requests
500
+ * @returns A unique cache key string
501
+ *
502
+ * @example
503
+ * const key = cacheManager.generateKey("/api/users", "GET", { "Accept": "application/json" });
504
+ */
505
+ generateKey(url, method, headers, body) {
506
+ // Use custom key generator if provided
507
+ if (this.options.keyGenerator) {
508
+ const customKey = this.options.keyGenerator(url, method, headers, body);
509
+ return this.options.keyPrefix ? `${this.options.keyPrefix}:${customKey}` : customKey;
510
+ }
511
+ // Default key generation
512
+ let key = `${method}:${url}`;
513
+ // Add vary headers to key if specified
514
+ if (this.options.varyByHeaders && headers) {
515
+ const varyHeaders = {};
516
+ this.options.varyByHeaders.forEach(header => {
517
+ const normalizedHeader = header.toLowerCase();
518
+ // Find the header in case-insensitive way
519
+ Object.keys(headers).forEach(h => {
520
+ if (h.toLowerCase() === normalizedHeader) {
521
+ varyHeaders[normalizedHeader] = headers[h];
522
+ }
523
+ });
524
+ });
525
+ if (Object.keys(varyHeaders).length > 0) {
526
+ key += `:${JSON.stringify(varyHeaders)}`;
527
+ }
528
+ }
529
+ // Include body in cache key for non-GET requests if it's a simple value
530
+ if (method !== "GET" && body !== undefined && body !== null) {
531
+ try {
532
+ if (typeof body === "string" || typeof body === "number" || typeof body === "boolean") {
533
+ key += `:${String(body)}`;
534
+ }
535
+ else if (typeof body === "object") {
536
+ key += `:${JSON.stringify(body)}`;
537
+ }
538
+ }
539
+ catch (e) {
540
+ console.warn("Failed to stringify request body for cache key");
541
+ }
542
+ }
543
+ // Add prefix if specified
544
+ return this.options.keyPrefix ? `${this.options.keyPrefix}:${key}` : key;
545
+ }
546
+ /**
547
+ * Get a cached value by key
548
+ * Checks if the entry exists and isn't expired before returning it.
549
+ *
550
+ * @param key - The cache key to retrieve
551
+ * @returns The cached entry or null if not found or expired
552
+ *
553
+ * @example
554
+ * const entry = await cacheManager.get("GET:/api/users");
555
+ */
556
+ async get(key) {
557
+ const value = await Promise.resolve(this.storage.get(key));
558
+ if (!value)
559
+ return null;
560
+ try {
561
+ const entry = JSON.parse(value);
562
+ // Check if the entry has expired
563
+ if (entry.expiry && entry.expiry < Date.now()) {
564
+ await Promise.resolve(this.storage.delete(key));
565
+ return null;
566
+ }
567
+ return entry;
568
+ }
569
+ catch (e) {
570
+ // If we can't parse the cached value, remove it
571
+ await Promise.resolve(this.storage.delete(key));
572
+ return null;
573
+ }
574
+ }
575
+ /**
576
+ * Store a value in the cache
577
+ * Enforces size and entry count limits if configured.
578
+ *
579
+ * @param key - The cache key
580
+ * @param value - The value to cache
581
+ * @param headers - Response headers to store with the cached value
582
+ * @returns Promise that resolves when caching is complete
583
+ *
584
+ * @example
585
+ * await cacheManager.set("GET:/api/users", userData, { "Content-Type": "application/json" });
586
+ */
587
+ async set(key, value, headers) {
588
+ const now = Date.now();
589
+ const ttl = this.options.ttl;
590
+ const entry = {
591
+ value,
592
+ timestamp: now,
593
+ expiry: ttl ? now + ttl : null,
594
+ headers,
595
+ };
596
+ const serialized = JSON.stringify(entry);
597
+ // Check size constraints if applicable
598
+ if (this.options.maxSize) {
599
+ const maxBytes = parseSize(this.options.maxSize);
600
+ const entrySize = new TextEncoder().encode(serialized).length;
601
+ // If a single entry exceeds the max size, reject it without clearing the cache
602
+ if (entrySize > maxBytes) {
603
+ console.warn(`Cache entry size (${entrySize} bytes) exceeds maximum cache size (${maxBytes} bytes)`);
604
+ return;
605
+ }
606
+ // Only clear if adding this entry would exceed the total limit
607
+ if (this.totalSize + entrySize > maxBytes) {
608
+ // Simple strategy: clear everything
609
+ // A more sophisticated strategy would remove oldest entries first
610
+ await Promise.resolve(this.storage.clear());
611
+ this.totalSize = 0;
612
+ this.entryCount = 0;
613
+ }
614
+ this.totalSize += entrySize;
615
+ }
616
+ // Check max entries constraint
617
+ if (this.options.maxEntries) {
618
+ this.entryCount++;
619
+ if (this.entryCount > this.options.maxEntries) {
620
+ // Simple strategy: clear everything
621
+ // A more sophisticated strategy would use LRU cache
622
+ await Promise.resolve(this.storage.clear());
623
+ this.entryCount = 1;
624
+ }
625
+ }
626
+ // Pass ttl to storage provider if available
627
+ await Promise.resolve(this.storage.set(key, serialized, ttl));
628
+ }
629
+ /**
630
+ * Check if a key exists in the cache
631
+ *
632
+ * @param key - The cache key to check
633
+ * @returns Promise that resolves to true if the key exists, false otherwise
634
+ *
635
+ * @example
636
+ * if (await cacheManager.has("GET:/api/users")) {
637
+ * // Use cached value
638
+ * }
639
+ */
640
+ async has(key) {
641
+ return Promise.resolve(this.storage.has(key));
642
+ }
643
+ /**
644
+ * Delete a specific key from the cache
645
+ *
646
+ * @param key - The cache key to delete
647
+ * @returns Promise that resolves when deletion is complete
648
+ *
649
+ * @example
650
+ * await cacheManager.delete("GET:/api/users");
651
+ */
652
+ async delete(key) {
653
+ return Promise.resolve(this.storage.delete(key));
654
+ }
655
+ /**
656
+ * Clear the entire cache
657
+ * Resets internal counters for entry count and size.
658
+ *
659
+ * @returns Promise that resolves when the cache is cleared
660
+ *
661
+ * @example
662
+ * await cacheManager.clear();
663
+ */
664
+ async clear() {
665
+ this.entryCount = 0;
666
+ this.totalSize = 0;
667
+ return Promise.resolve(this.storage.clear());
668
+ }
669
+ }class CookieUtils {
670
+ /**
671
+ * Formats cookies for a request
672
+ * @param cookies Object containing cookie name-value pairs or cookie options
673
+ * @returns Formatted cookie string for the Cookie header
674
+ */
675
+ static formatRequestCookies(cookies) {
676
+ const cookiePairs = [];
677
+ Object.entries(cookies).forEach(([name, valueOrOptions]) => {
678
+ let value;
679
+ if (typeof valueOrOptions === "string") {
680
+ value = valueOrOptions;
681
+ }
682
+ else {
683
+ // Extract value from options object without validation
684
+ value = valueOrOptions.value;
685
+ }
686
+ // Add the cookie to the request
687
+ cookiePairs.push(`${encodeURIComponent(name)}=${encodeURIComponent(value)}`);
688
+ });
689
+ return cookiePairs.join("; ");
690
+ }
691
+ }class RequestError extends Error {
692
+ constructor(message, url, method, options = {}) {
693
+ super(message);
694
+ this.name = "RequestError";
695
+ this.url = url;
696
+ this.method = method;
697
+ this.status = options.status;
698
+ this.response = options.response;
699
+ this.timeoutError = options.timeoutError;
700
+ // For better stack traces in modern environments
701
+ if (Error.captureStackTrace) {
702
+ Error.captureStackTrace(this, RequestError);
703
+ }
704
+ // Maintains proper prototype chain for instanceof checks
705
+ Object.setPrototypeOf(this, RequestError.prototype);
706
+ }
707
+ /**
708
+ * Static methods for creating specific types of RequestError
709
+ */
710
+ static timeout(url, method, timeoutMs) {
711
+ return new RequestError(`Request timed out after ${timeoutMs}ms`, url, method, {
712
+ timeoutError: true,
713
+ });
714
+ }
715
+ static fromResponse(response, url, method) {
716
+ return new RequestError(`Request failed with status ${response.status}`, url, method, {
717
+ status: response.status,
718
+ response,
719
+ });
720
+ }
721
+ static networkError(url, method, originalError) {
722
+ const error = new RequestError(`Network error: ${originalError.message}`, url, method);
723
+ error.stack = originalError.stack;
724
+ return error;
725
+ }
726
+ }/**
727
+ * Utility class for CSRF token management
728
+ */
729
+ class CsrfUtils {
730
+ /**
731
+ * Extracts CSRF token from a meta tag in the document head
732
+ * @param metaName The name attribute of the meta tag (default: "csrf-token")
733
+ * @returns The CSRF token or null if not found
734
+ */
735
+ static getTokenFromMeta(metaName = "csrf-token") {
736
+ if (typeof document === "undefined") {
737
+ return null;
738
+ }
739
+ const meta = document.querySelector(`meta[name="${metaName}"]`);
740
+ return (meta === null || meta === void 0 ? void 0 : meta.getAttribute("content")) || null;
741
+ }
742
+ /**
743
+ * Extracts CSRF token from a cookie
744
+ * @param cookieName The name of the cookie containing the CSRF token
745
+ * @returns The CSRF token or null if not found
746
+ */
747
+ static getTokenFromCookie(cookieName = "csrf-token") {
748
+ if (typeof document === "undefined") {
749
+ return null;
750
+ }
751
+ const cookies = document.cookie.split(";");
752
+ for (const cookie of cookies) {
753
+ const [name, value] = cookie.trim().split("=");
754
+ if (name === cookieName) {
755
+ return decodeURIComponent(value);
756
+ }
757
+ }
758
+ return null;
759
+ }
760
+ /**
761
+ * Validates if the provided string is a potential CSRF token
762
+ * Checks if the token meets security requirements
763
+ * @param token The token to validate
764
+ * @returns Whether the token is valid
765
+ */
766
+ static isValidToken(token) {
767
+ // Basic checks for null/undefined and type
768
+ if (typeof token !== "string") {
769
+ return false;
770
+ }
771
+ if (token.length < 8)
772
+ return false;
773
+ // If token is longer than 10 chars, perform additional security checks
774
+ if (token.length > 10) {
775
+ // Check for valid character set (alphanumeric & common token symbols)
776
+ const validTokenRegex = /^[A-Za-z0-9\-_=+/.]+$/;
777
+ if (!validTokenRegex.test(token)) {
778
+ return false;
779
+ }
780
+ // For longer tokens, check for sufficient entropy (at least 2 character types)
781
+ const hasUpperCase = /[A-Z]/.test(token);
782
+ const hasLowerCase = /[a-z]/.test(token);
783
+ const hasNumbers = /[0-9]/.test(token);
784
+ const hasSpecials = /[-_=+/.]/.test(token);
785
+ const characterTypesCount = [hasUpperCase, hasLowerCase, hasNumbers, hasSpecials].filter(Boolean).length;
786
+ return characterTypesCount >= 2;
787
+ }
788
+ // For shorter tokens (8-10 chars), just return true if we reached here
789
+ return true;
790
+ }
791
+ }/**
792
+ * Global configuration for create-request
793
+ */
794
+ class Config {
795
+ constructor() {
796
+ // CSRF configuration
797
+ this.csrfHeaderName = "X-CSRF-Token";
798
+ this.xsrfCookieName = "XSRF-TOKEN";
799
+ this.xsrfHeaderName = "X-XSRF-TOKEN";
800
+ this.csrfToken = null;
801
+ this.enableAutoXsrf = true;
802
+ this.enableAntiCsrf = true; // X-Requested-With header
803
+ }
804
+ /**
805
+ * Get the singleton instance of the Config class
806
+ *
807
+ * @returns The global configuration instance
808
+ *
809
+ * @example
810
+ * const config = Config.getInstance();
811
+ * config.setCsrfToken('token123');
812
+ */
813
+ static getInstance() {
814
+ if (!Config.instance) {
815
+ Config.instance = new Config();
816
+ }
817
+ return Config.instance;
818
+ }
819
+ /**
820
+ * Set a global CSRF token to be used for all requests
821
+ *
822
+ * @param token - The CSRF token value
823
+ * @returns The config instance for chaining
824
+ *
825
+ * @example
826
+ * Config.getInstance().setCsrfToken('myToken123');
827
+ */
828
+ setCsrfToken(token) {
829
+ this.csrfToken = token;
830
+ return this;
831
+ }
832
+ /**
833
+ * Get the global CSRF token that will be automatically applied to requests
834
+ *
835
+ * @returns The current CSRF token or null if not set
836
+ */
837
+ getCsrfToken() {
838
+ return this.csrfToken;
839
+ }
840
+ /**
841
+ * Set the CSRF header name used when sending the token
842
+ *
843
+ * @param name - The header name to use
844
+ * @returns The config instance for chaining
845
+ *
846
+ * @example
847
+ * Config.getInstance().setCsrfHeaderName('X-My-CSRF-Token');
848
+ */
849
+ setCsrfHeaderName(name) {
850
+ this.csrfHeaderName = name;
851
+ return this;
852
+ }
853
+ /**
854
+ * Get the configured CSRF header name
855
+ *
856
+ * @returns The current CSRF header name
857
+ */
858
+ getCsrfHeaderName() {
859
+ return this.csrfHeaderName;
860
+ }
861
+ /**
862
+ * Set the XSRF cookie name to look for when extracting tokens from cookies
863
+ *
864
+ * @param name - The cookie name to look for
865
+ * @returns The config instance for chaining
866
+ *
867
+ * @example
868
+ * Config.getInstance().setXsrfCookieName('MY-XSRF-COOKIE');
869
+ */
870
+ setXsrfCookieName(name) {
871
+ this.xsrfCookieName = name;
872
+ return this;
873
+ }
874
+ /**
875
+ * Get the configured XSRF cookie name
876
+ *
877
+ * @returns The current XSRF cookie name
878
+ */
879
+ getXsrfCookieName() {
880
+ return this.xsrfCookieName;
881
+ }
882
+ /**
883
+ * Set the XSRF header name for sending tokens extracted from cookies
884
+ *
885
+ * @param name - The header name to use
886
+ * @returns The config instance for chaining
887
+ */
888
+ setXsrfHeaderName(name) {
889
+ this.xsrfHeaderName = name;
890
+ return this;
891
+ }
892
+ /**
893
+ * Get the configured XSRF header name
894
+ *
895
+ * @returns The current XSRF header name
896
+ */
897
+ getXsrfHeaderName() {
898
+ return this.xsrfHeaderName;
899
+ }
900
+ /**
901
+ * Enable or disable automatic extraction of XSRF tokens from cookies
902
+ * When enabled, the library will look for XSRF tokens in cookies and
903
+ * automatically add them to request headers.
904
+ *
905
+ * @param enable - Whether to enable this feature
906
+ * @returns The config instance for chaining
907
+ *
908
+ * @example
909
+ * Config.getInstance().setEnableAutoXsrf(false); // Disable XSRF extraction
910
+ */
911
+ setEnableAutoXsrf(enable) {
912
+ this.enableAutoXsrf = enable;
913
+ return this;
914
+ }
915
+ /**
916
+ * Check if automatic XSRF token extraction is enabled
917
+ *
918
+ * @returns True if automatic XSRF is enabled
919
+ */
920
+ isAutoXsrfEnabled() {
921
+ return this.enableAutoXsrf;
922
+ }
923
+ /**
924
+ * Enable or disable automatic addition of anti-CSRF headers
925
+ * When enabled, X-Requested-With: XMLHttpRequest will be added to all requests.
926
+ *
927
+ * @param enable - Whether to enable this feature
928
+ * @returns The config instance for chaining
929
+ */
930
+ setEnableAntiCsrf(enable) {
931
+ this.enableAntiCsrf = enable;
932
+ return this;
933
+ }
934
+ /**
935
+ * Check if anti-CSRF protection is enabled
936
+ *
937
+ * @returns True if anti-CSRF protection is enabled
938
+ */
939
+ isAntiCsrfEnabled() {
940
+ return this.enableAntiCsrf;
941
+ }
942
+ /**
943
+ * Reset all configuration options to their default values
944
+ *
945
+ * @returns The config instance for chaining
946
+ *
947
+ * @example
948
+ * Config.getInstance().reset();
949
+ */
950
+ reset() {
951
+ this.csrfToken = null;
952
+ this.csrfHeaderName = "X-CSRF-Token";
953
+ this.enableAntiCsrf = true;
954
+ this.xsrfCookieName = "XSRF-TOKEN";
955
+ this.xsrfHeaderName = "X-XSRF-TOKEN";
956
+ this.enableAutoXsrf = true;
957
+ return this;
958
+ }
959
+ }/**
960
+ * Base class with common functionality for all request types
961
+ * Provides the core request building and execution capabilities.
962
+ */
963
+ class BaseRequest {
964
+ constructor() {
965
+ this.requestOptions = {
966
+ headers: {},
967
+ };
968
+ this.queryParams = new URLSearchParams();
969
+ this.autoApplyCsrfProtection = true;
970
+ this.cacheEnabled = false;
971
+ }
972
+ /**
973
+ * Add multiple HTTP headers to the request
974
+ *
975
+ * @param headers - Key-value pairs of header names and values
976
+ * @returns The request instance for chaining
977
+ *
978
+ * @example
979
+ * request.withHeaders({
980
+ * 'Accept': 'application/json',
981
+ * 'X-Custom-Header': 'value'
982
+ * });
983
+ */
984
+ withHeaders(headers) {
985
+ this.requestOptions.headers = {
986
+ ...this.requestOptions.headers,
987
+ ...headers,
988
+ };
989
+ return this;
990
+ }
991
+ /**
992
+ * Add a single HTTP header to the request
993
+ *
994
+ * @param key - The header name
995
+ * @param value - The header value
996
+ * @returns The request instance for chaining
997
+ *
998
+ * @example
999
+ * request.withHeader('Accept', 'application/json');
1000
+ */
1001
+ withHeader(key, value) {
1002
+ return this.withHeaders({ [key]: value });
1003
+ }
1004
+ /**
1005
+ * Set a timeout for the request
1006
+ * If the request takes longer than the specified timeout, it will be aborted.
1007
+ *
1008
+ * @param timeout - The timeout in milliseconds
1009
+ * @returns The request instance for chaining
1010
+ * @throws Error if timeout is not a positive number
1011
+ *
1012
+ * @example
1013
+ * request.withTimeout(5000); // 5 seconds timeout
1014
+ */
1015
+ withTimeout(timeout) {
1016
+ if (!Number.isFinite(timeout) || timeout <= 0)
1017
+ throw new Error("Timeout must be a positive number");
1018
+ this.requestOptions.timeout = timeout;
1019
+ return this;
1020
+ }
1021
+ /**
1022
+ * Configure automatic retry behavior for failed requests
1023
+ *
1024
+ * @param retries - Number of retry attempts before failing
1025
+ * @returns The request instance for chaining
1026
+ * @throws Error if retries is not a non-negative integer
1027
+ *
1028
+ * @example
1029
+ * request.withRetries(3); // Retry up to 3 times
1030
+ */
1031
+ withRetries(retries) {
1032
+ if (!Number.isInteger(retries) || retries < 0)
1033
+ throw new Error("Retry count must be a non-negative integer");
1034
+ this.requestOptions.retries = retries;
1035
+ return this;
1036
+ }
1037
+ /**
1038
+ * Set a callback to be invoked before each retry attempt
1039
+ * Useful for implementing backoff strategies or logging retry attempts.
1040
+ *
1041
+ * @param callback - Function to call before retrying
1042
+ * @returns The request instance for chaining
1043
+ *
1044
+ * @example
1045
+ * request.onRetry(({ attempt, error }) => {
1046
+ * console.log(`Retry attempt ${attempt} after error: ${error.message}`);
1047
+ * return new Promise(resolve => setTimeout(resolve, attempt * 1000));
1048
+ * });
1049
+ */
1050
+ onRetry(callback) {
1051
+ this.requestOptions.onRetry = callback;
1052
+ return this;
1053
+ }
1054
+ /**
1055
+ * Sets the credentials policy for the request
1056
+ * @param credentialsPolicy - Can be 'omit', 'same-origin', or 'include'
1057
+ * @returns The instance for chaining
1058
+ */
1059
+ withCredentials(credentialsPolicy = CredentialsPolicy.SAME_ORIGIN) {
1060
+ this.requestOptions.credentials = credentialsPolicy;
1061
+ return this;
1062
+ }
1063
+ /**
1064
+ * Allows providing an external AbortController to cancel the request
1065
+ * @param controller - The AbortController to use for this request
1066
+ * @returns The instance for chaining
1067
+ */
1068
+ withAbortController(controller) {
1069
+ this.abortController = controller;
1070
+ return this;
1071
+ }
1072
+ /**
1073
+ * Sets the referrer for the request
1074
+ * @param referrer - The referrer URL or policy
1075
+ * @returns The instance for chaining
1076
+ */
1077
+ withReferrer(referrer) {
1078
+ this.requestOptions.referrer = referrer;
1079
+ return this;
1080
+ }
1081
+ /**
1082
+ * Sets the referrer policy for the request
1083
+ * @param policy - The referrer policy to use
1084
+ * @returns The instance for chaining
1085
+ */
1086
+ withReferrerPolicy(policy) {
1087
+ this.requestOptions.referrerPolicy = policy;
1088
+ return this;
1089
+ }
1090
+ /**
1091
+ * Sets the redirect policy for the request
1092
+ * @param redirect - Can be 'follow', 'error', or 'manual'
1093
+ * @returns The instance for chaining
1094
+ */
1095
+ withRedirect(redirect) {
1096
+ this.requestOptions.redirect = redirect;
1097
+ return this;
1098
+ }
1099
+ /**
1100
+ * Sets the keepalive flag for the request
1101
+ * @param keepalive - Whether to allow the request to outlive the page
1102
+ * @returns The instance for chaining
1103
+ */
1104
+ withKeepAlive(keepalive) {
1105
+ this.requestOptions.keepalive = keepalive;
1106
+ return this;
1107
+ }
1108
+ /**
1109
+ * Sets the priority of the request
1110
+ * @param priority - Can be 'high', 'low', or 'auto'
1111
+ * @returns The instance for chaining
1112
+ */
1113
+ withPriority(priority) {
1114
+ this.requestOptions.priority = priority;
1115
+ return this;
1116
+ }
1117
+ /**
1118
+ * Adds query parameters to the request URL
1119
+ * @param params - An object containing the query parameters
1120
+ * @returns The instance for chaining
1121
+ */
1122
+ withQueryParams(params) {
1123
+ Object.entries(params).forEach(([key, value]) => {
1124
+ if (value === null || value === undefined) {
1125
+ return;
1126
+ }
1127
+ if (Array.isArray(value)) {
1128
+ // Handle array values - add multiple entries with the same key
1129
+ value.forEach(v => this.queryParams.append(key, String(v)));
1130
+ }
1131
+ else {
1132
+ this.queryParams.append(key, String(value));
1133
+ }
1134
+ });
1135
+ return this;
1136
+ }
1137
+ /**
1138
+ * Adds a single query parameter
1139
+ * @param key - The parameter name
1140
+ * @param value - The parameter value, can be a single value or array of values
1141
+ * @returns The instance for chaining
1142
+ */
1143
+ withQueryParam(key, value) {
1144
+ if (value === null || value === undefined)
1145
+ return this;
1146
+ if (Array.isArray(value))
1147
+ value.forEach(v => this.queryParams.append(key, String(v)));
1148
+ else
1149
+ this.queryParams.append(key, String(value));
1150
+ return this;
1151
+ }
1152
+ /**
1153
+ * Sets the mode of the request
1154
+ * @param mode - The mode for the request (cors, no-cors, same-origin, navigate)
1155
+ * @returns The instance for chaining
1156
+ */
1157
+ withMode(mode) {
1158
+ this.requestOptions.mode = mode;
1159
+ return this;
1160
+ }
1161
+ /**
1162
+ * Shorthand for setting the Content-Type header
1163
+ * @param contentType - The content type
1164
+ * @returns The instance for chaining
1165
+ */
1166
+ withContentType(contentType) {
1167
+ return this.withHeaders({
1168
+ "Content-Type": contentType,
1169
+ });
1170
+ }
1171
+ /**
1172
+ * Shorthand for setting the Authorization header
1173
+ * @param authValue - The authorization value
1174
+ * @returns The instance for chaining
1175
+ */
1176
+ withAuthorization(authValue) {
1177
+ return this.withHeaders({
1178
+ Authorization: authValue,
1179
+ });
1180
+ }
1181
+ /**
1182
+ * Shorthand for setting up Basic authentication
1183
+ * @param username - The username
1184
+ * @param password - The password
1185
+ * @returns The instance for chaining
1186
+ */
1187
+ withBasicAuth(username, password) {
1188
+ const credentials = this.encodeBase64(`${username}:${password}`);
1189
+ return this.withAuthorization(`Basic ${credentials}`);
1190
+ }
1191
+ /**
1192
+ * Cross-environment base64 encoding
1193
+ * Works in both browser and Node.js environments
1194
+ */
1195
+ encodeBase64(str) {
1196
+ // Modern approach using TextEncoder (available in both modern browsers and Node.js)
1197
+ if (typeof TextEncoder !== "undefined" && typeof btoa === "function") {
1198
+ const encoder = new TextEncoder();
1199
+ const bytes = encoder.encode(str);
1200
+ return btoa(String.fromCharCode.apply(null, [...new Uint8Array(bytes)]));
1201
+ }
1202
+ // Browser environment
1203
+ if (typeof btoa === "function")
1204
+ return btoa(str);
1205
+ // Node.js environment
1206
+ if (typeof Buffer !== "undefined")
1207
+ return Buffer.from(str).toString("base64");
1208
+ // Fallback (should never happen in modern environments)
1209
+ throw new Error("Base64 encoding is not supported");
1210
+ }
1211
+ /**
1212
+ * Sets the bearer token for authentication
1213
+ * @param token - The bearer token
1214
+ * @returns The instance for chaining
1215
+ */
1216
+ withBearerToken(token) {
1217
+ return this.withAuthorization(`Bearer ${token}`);
1218
+ }
1219
+ /**
1220
+ * Helper function to check for header presence in a case-insensitive way
1221
+ * @param headers Headers object
1222
+ * @param headerName Header name to check
1223
+ * @returns Boolean indicating if the header exists (case-insensitive)
1224
+ */
1225
+ hasHeader(headers, headerName) {
1226
+ return Object.keys(headers).some(key => key.toLowerCase() === headerName.toLowerCase());
1227
+ }
1228
+ /**
1229
+ * Sets cookies for the request
1230
+ * @param cookies Object containing cookie name-value pairs or cookie options
1231
+ * @returns The instance for chaining
1232
+ */
1233
+ withCookies(cookies) {
1234
+ const cookieEntries = Object.entries(cookies || {});
1235
+ if (cookieEntries.length === 0) {
1236
+ return this;
1237
+ }
1238
+ // Get current headers or initialize empty object
1239
+ const currentHeaders = this.requestOptions.headers || {};
1240
+ // Get all existing cookie headers with different cases
1241
+ const cookieValues = [];
1242
+ const cookieHeaderKeys = [];
1243
+ Object.keys(currentHeaders).forEach(key => {
1244
+ if (key.toLowerCase() === "cookie") {
1245
+ // Don't add empty cookie values
1246
+ if (currentHeaders[key])
1247
+ cookieValues.push(currentHeaders[key]);
1248
+ cookieHeaderKeys.push(key);
1249
+ }
1250
+ });
1251
+ // Format the new cookies
1252
+ const cookieString = CookieUtils.formatRequestCookies(cookies);
1253
+ // Choose which header name to use - preserve existing case if possible
1254
+ const headerName = cookieHeaderKeys.length > 0 ? cookieHeaderKeys[0] : "Cookie";
1255
+ // Combine all existing cookie values with the new ones
1256
+ const combinedCookieValue = [...cookieValues, cookieString].filter(Boolean).join("; ");
1257
+ // Create a new headers object without any cookie headers
1258
+ const newHeaders = { ...currentHeaders };
1259
+ cookieHeaderKeys.forEach(key => {
1260
+ delete newHeaders[key];
1261
+ });
1262
+ // Set the new combined cookie header
1263
+ this.requestOptions.headers = {
1264
+ ...newHeaders,
1265
+ [headerName]: combinedCookieValue,
1266
+ };
1267
+ return this;
1268
+ }
1269
+ /**
1270
+ * Set a single cookie
1271
+ * @param name Cookie name
1272
+ * @param value Cookie value or options object
1273
+ * @returns The instance for chaining
1274
+ */
1275
+ withCookie(name, value) {
1276
+ return this.withCookies({ [name]: value });
1277
+ }
1278
+ /**
1279
+ * Sets a CSRF token in the request headers
1280
+ * @param token The CSRF token
1281
+ * @param headerName The name of the header to use (default: X-CSRF-Token)
1282
+ * @returns The instance for chaining
1283
+ */
1284
+ withCsrfToken(token, headerName = "X-CSRF-Token") {
1285
+ return this.withHeaders({
1286
+ [headerName]: token,
1287
+ });
1288
+ }
1289
+ /**
1290
+ * Disables automatic anti-CSRF protection.
1291
+ * By default, X-Requested-With: XMLHttpRequest header is sent with all requests.
1292
+ * @returns The instance for chaining
1293
+ */
1294
+ withoutCsrfProtection() {
1295
+ this.autoApplyCsrfProtection = false;
1296
+ return this;
1297
+ }
1298
+ /**
1299
+ * Sets common security headers to help prevent CSRF attacks
1300
+ * @returns The instance for chaining
1301
+ */
1302
+ withAntiCsrfHeaders() {
1303
+ return this.withHeaders({
1304
+ "X-Requested-With": "XMLHttpRequest",
1305
+ });
1306
+ }
1307
+ /**
1308
+ * Configure request caching behavior
1309
+ * When enabled, responses will be cached and reused for subsequent identical requests.
1310
+ *
1311
+ * @param options - Cache configuration options
1312
+ * @returns The request instance for chaining
1313
+ *
1314
+ * @example
1315
+ * request.withCache({
1316
+ * ttl: 60000, // Cache for 1 minute
1317
+ * maxEntries: 100,
1318
+ * storage: localStorage
1319
+ * });
1320
+ */
1321
+ withCache(options = {}) {
1322
+ this.cacheEnabled = true;
1323
+ this.cacheManager = new CacheManager(options);
1324
+ return this;
1325
+ }
1326
+ /**
1327
+ * Send the request to the specified URL
1328
+ * This is the final method in the chain that executes the request.
1329
+ *
1330
+ * @param url - The URL to send the request to
1331
+ * @returns A promise with additional methods for processing the response
1332
+ *
1333
+ * @example
1334
+ * const response = await request.sendTo('/api/users');
1335
+ * const data = await response.getJson();
1336
+ */
1337
+ sendTo(url) {
1338
+ // Format the URL with query parameters
1339
+ url = this.formatUrlWithQueryParams(url);
1340
+ // Apply automatic CSRF protection if enabled
1341
+ if (this.autoApplyCsrfProtection) {
1342
+ const config = Config.getInstance();
1343
+ // Apply anti-CSRF headers if enabled
1344
+ if (config.isAntiCsrfEnabled()) {
1345
+ this.withHeaders({
1346
+ "X-Requested-With": "XMLHttpRequest",
1347
+ });
1348
+ }
1349
+ // Apply global CSRF token if set
1350
+ const globalToken = config.getCsrfToken();
1351
+ if (globalToken) {
1352
+ // Check if local token exists using case-insensitive comparison
1353
+ const headers = this.requestOptions.headers;
1354
+ const csrfHeaderName = config.getCsrfHeaderName();
1355
+ const hasLocalToken = this.hasHeader(headers, "X-CSRF-Token") || this.hasHeader(headers, csrfHeaderName);
1356
+ if (!hasLocalToken) {
1357
+ this.withHeaders({
1358
+ [csrfHeaderName]: globalToken,
1359
+ });
1360
+ }
1361
+ }
1362
+ // check for XSRF token in cookies and send it as a header
1363
+ if (config.isAutoXsrfEnabled() && typeof document !== "undefined") {
1364
+ const xsrfToken = CsrfUtils.getTokenFromCookie(config.getXsrfCookieName());
1365
+ if (xsrfToken && CsrfUtils.isValidToken(xsrfToken)) {
1366
+ const headers = this.requestOptions.headers;
1367
+ const xsrfHeaderName = config.getXsrfHeaderName();
1368
+ const hasLocalToken = this.hasHeader(headers, "X-XSRF-TOKEN") || this.hasHeader(headers, xsrfHeaderName);
1369
+ if (!hasLocalToken) {
1370
+ this.withHeaders({
1371
+ [xsrfHeaderName]: xsrfToken,
1372
+ });
1373
+ }
1374
+ }
1375
+ }
1376
+ }
1377
+ // Cast the request options to RequestInit to ensure compatibility
1378
+ const fetchOptions = {
1379
+ ...this.requestOptions,
1380
+ method: this.method,
1381
+ };
1382
+ // Create the base promise - handle caching if enabled
1383
+ let basePromise;
1384
+ if (this.cacheEnabled && this.cacheManager) {
1385
+ const cacheKey = this.cacheManager.generateKey(url, this.method, this.requestOptions.headers, this.requestOptions.body);
1386
+ basePromise = this.executeWithCache(url, fetchOptions, cacheKey);
1387
+ }
1388
+ else {
1389
+ basePromise = !this.requestOptions.retries ? this.executeRequest(url, fetchOptions) : this.executeWithRetries(url, fetchOptions);
1390
+ }
1391
+ const responsePromise = basePromise;
1392
+ // Attach convenience methods that begin processing immediately
1393
+ responsePromise.getJson = async () => {
1394
+ return basePromise.then(response => response.getJson());
1395
+ };
1396
+ responsePromise.getText = async () => {
1397
+ return basePromise.then(response => response.getText());
1398
+ };
1399
+ responsePromise.getBlob = async () => {
1400
+ return basePromise.then(response => response.getBlob());
1401
+ };
1402
+ responsePromise.getArrayBuffer = async () => {
1403
+ return basePromise.then(response => response.getArrayBuffer());
1404
+ };
1405
+ responsePromise.getBody = async () => {
1406
+ return basePromise.then(response => response.getBody());
1407
+ };
1408
+ responsePromise.getData = async (selector) => {
1409
+ try {
1410
+ const data = await basePromise.then(response => response.getJson());
1411
+ // If no selector is provided, return the raw JSON data
1412
+ if (!selector)
1413
+ return data;
1414
+ // Apply the selector if provided
1415
+ return selector(data);
1416
+ }
1417
+ catch (error) {
1418
+ // Always enhance the error with the original data
1419
+ const errorMessage = error instanceof Error ? error.message : String(error);
1420
+ const enhancedError = new Error(`Data selector failed: ${errorMessage}. Original data: ${JSON.stringify(await basePromise.then(response => response.getJson()), null, 2)}`);
1421
+ // Preserve the stack trace if available
1422
+ if (error instanceof Error && error.stack) {
1423
+ enhancedError.stack = error.stack;
1424
+ }
1425
+ throw enhancedError;
1426
+ }
1427
+ };
1428
+ return responsePromise;
1429
+ }
1430
+ /**
1431
+ * Formats the URL with any query parameters
1432
+ * @param url The base URL
1433
+ * @returns The URL with query parameters appended
1434
+ */
1435
+ formatUrlWithQueryParams(url) {
1436
+ if (!this.queryParams.toString()) {
1437
+ return url;
1438
+ }
1439
+ try {
1440
+ // Try to use the URL constructor (works for absolute URLs)
1441
+ const urlObj = new URL(url);
1442
+ // Merge our query params with any that might be in the URL already
1443
+ this.queryParams.forEach((value, key) => {
1444
+ urlObj.searchParams.append(key, value);
1445
+ });
1446
+ return urlObj.toString();
1447
+ }
1448
+ catch (error) {
1449
+ // Handle relative URLs
1450
+ const hasExistingParams = url.includes("?");
1451
+ const separator = hasExistingParams ? "&" : "?";
1452
+ return `${url}${separator}${this.queryParams.toString()}`;
1453
+ }
1454
+ }
1455
+ /**
1456
+ * Executes a request with configured retry logic
1457
+ * @param url The formatted URL to send the request to
1458
+ * @param fetchOptions The fetch options to use
1459
+ * @returns A wrapped response object
1460
+ * @throws RequestError if the request fails after all retries
1461
+ */
1462
+ async executeWithRetries(url, fetchOptions) {
1463
+ const maxRetries = this.requestOptions.retries || 0;
1464
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1465
+ try {
1466
+ return await this.executeRequest(url, fetchOptions);
1467
+ }
1468
+ catch (error) {
1469
+ const requestError = error instanceof RequestError ? error : RequestError.networkError(url, fetchOptions.method, error);
1470
+ if (attempt >= maxRetries)
1471
+ throw requestError;
1472
+ if (this.requestOptions.onRetry) {
1473
+ await this.requestOptions.onRetry({ attempt: attempt + 1, error: requestError });
1474
+ }
1475
+ }
1476
+ }
1477
+ // This should never happen but is needed for type safety
1478
+ throw new RequestError(`Max retries reached`, url, fetchOptions.method);
1479
+ }
1480
+ async executeRequest(url, fetchOptions) {
1481
+ // Create a request controller for timeout if needed, or use the provided one
1482
+ const controller = this.abortController || new AbortController();
1483
+ const { signal } = controller;
1484
+ // Use the controller signal for the fetch request, but don't overwrite an existing signal
1485
+ fetchOptions.signal = fetchOptions.signal || signal;
1486
+ let timeoutId;
1487
+ // Flag to track if abort was caused by our timeout
1488
+ let abortedByTimeout = false;
1489
+ // Handle timeout regardless of whether an external controller is provided
1490
+ if (this.requestOptions.timeout) {
1491
+ if (this.abortController) {
1492
+ // If we have an external controller, create a separate timeout controller
1493
+ // that won't interfere with the external one
1494
+ // Use AbortSignal.timeout if available (modern browsers)
1495
+ if (typeof AbortSignal.timeout === "function") {
1496
+ const timeoutSignal = AbortSignal.timeout(this.requestOptions.timeout);
1497
+ // Add event listener to propagate the abort to the main controller
1498
+ timeoutSignal.addEventListener("abort", () => {
1499
+ if (!controller.signal.aborted) {
1500
+ abortedByTimeout = true;
1501
+ controller.abort();
1502
+ }
1503
+ });
1504
+ }
1505
+ else {
1506
+ // Fallback for browsers without AbortSignal.timeout
1507
+ timeoutId = setTimeout(() => {
1508
+ if (!controller.signal.aborted) {
1509
+ abortedByTimeout = true;
1510
+ controller.abort();
1511
+ }
1512
+ }, this.requestOptions.timeout);
1513
+ }
1514
+ }
1515
+ else {
1516
+ timeoutId = setTimeout(() => {
1517
+ abortedByTimeout = true;
1518
+ controller.abort();
1519
+ }, this.requestOptions.timeout);
1520
+ }
1521
+ }
1522
+ try {
1523
+ let response;
1524
+ try {
1525
+ response = await fetch(url, fetchOptions);
1526
+ }
1527
+ catch (error) {
1528
+ // Check if this is an abort error that was specifically caused by our timeout
1529
+ if (error instanceof DOMException && error.name === "AbortError" && abortedByTimeout) {
1530
+ throw RequestError.timeout(url, fetchOptions.method, this.requestOptions.timeout);
1531
+ }
1532
+ // Otherwise it's either a user-triggered abort or another network error
1533
+ throw RequestError.networkError(url, fetchOptions.method, error);
1534
+ }
1535
+ if (!response.ok) {
1536
+ throw RequestError.fromResponse(response, url, fetchOptions.method);
1537
+ }
1538
+ // Return a wrapped response instead of parsing it
1539
+ return new ResponseWrapper(response);
1540
+ }
1541
+ finally {
1542
+ if (timeoutId) {
1543
+ clearTimeout(timeoutId);
1544
+ }
1545
+ }
1546
+ }
1547
+ /**
1548
+ * Executes a request using cache when possible
1549
+ * @param url The formatted URL to send the request to
1550
+ * @param fetchOptions The fetch options to use
1551
+ * @param cacheKey The cache key for this request
1552
+ */
1553
+ async executeWithCache(url, fetchOptions, cacheKey) {
1554
+ // Try to get from cache first
1555
+ if (this.cacheManager) {
1556
+ const cachedEntry = await this.cacheManager.get(cacheKey);
1557
+ if (cachedEntry) {
1558
+ // Create a synthetic Response object from cached data
1559
+ const cachedResponse = new Response(JSON.stringify(cachedEntry.value), {
1560
+ status: 200,
1561
+ headers: new Headers(cachedEntry.headers || {}),
1562
+ });
1563
+ return new ResponseWrapper(cachedResponse);
1564
+ }
1565
+ }
1566
+ // Not in cache, make the actual request
1567
+ const response = await (!this.requestOptions.retries ? this.executeRequest(url, fetchOptions) : this.executeWithRetries(url, fetchOptions));
1568
+ // Cache the successful response if caching is enabled
1569
+ // Only cache successful responses
1570
+ if (response.ok && this.cacheManager) {
1571
+ // Clone the response so we don't consume it
1572
+ try {
1573
+ const responseData = await response.getJson();
1574
+ // Extract headers to store in cache
1575
+ const headers = {};
1576
+ response.headers.forEach((value, key) => {
1577
+ headers[key] = value;
1578
+ });
1579
+ // Store in cache
1580
+ await this.cacheManager.set(cacheKey, responseData, headers);
1581
+ }
1582
+ catch (error) {
1583
+ // Failed to cache, but we can still return the response
1584
+ console.warn("Failed to cache response:", error);
1585
+ }
1586
+ }
1587
+ return response;
1588
+ }
1589
+ }/**
1590
+ * Base class for requests that can have a body (POST, PUT, PATCH)
1591
+ */
1592
+ class BodyRequest extends BaseRequest {
1593
+ /**
1594
+ * Sets the body of the request
1595
+ * @param body - The request body
1596
+ */
1597
+ withBody(body) {
1598
+ this.body = body;
1599
+ // Set body type and validate
1600
+ if (typeof body === "string") {
1601
+ this.bodyType = BodyType.STRING;
1602
+ this.setContentTypeIfNeeded("text/plain");
1603
+ }
1604
+ else if (body !== null &&
1605
+ typeof body === "object" &&
1606
+ !(body instanceof FormData || body instanceof Blob || body instanceof ArrayBuffer || body instanceof URLSearchParams || body instanceof ReadableStream)) {
1607
+ this.bodyType = BodyType.JSON;
1608
+ this.setContentTypeIfNeeded("application/json");
1609
+ // Validate JSON is stringifiable early
1610
+ try {
1611
+ JSON.stringify(body);
1612
+ }
1613
+ catch (error) {
1614
+ throw new Error(`Failed to stringify request body: ${String(error)}`);
1615
+ }
1616
+ }
1617
+ else {
1618
+ this.bodyType = BodyType.BINARY;
1619
+ }
1620
+ return this;
1621
+ }
1622
+ setContentTypeIfNeeded(contentType) {
1623
+ // Only set if not already set
1624
+ const headers = this.requestOptions.headers;
1625
+ if (!Object.keys(headers).some(header => header.toLowerCase() === "content-type")) {
1626
+ this.withContentType(contentType);
1627
+ }
1628
+ }
1629
+ /**
1630
+ * Send the request to the specified URL
1631
+ * Overrides the base implementation to add body handling
1632
+ */
1633
+ sendTo(url) {
1634
+ if (this.body !== undefined) {
1635
+ // Remove previous body if it exists
1636
+ if (this.requestOptions.body)
1637
+ delete this.requestOptions.body;
1638
+ // Process the body based on its type
1639
+ if (this.bodyType === BodyType.JSON) {
1640
+ this.requestOptions.body = JSON.stringify(this.body);
1641
+ }
1642
+ else {
1643
+ this.requestOptions.body = this.body;
1644
+ }
1645
+ }
1646
+ return super.sendTo(url);
1647
+ }
1648
+ }/**
1649
+ * HTTP GET request implementation
1650
+ * Used for retrieving data from an API endpoint.
1651
+ *
1652
+ * @example
1653
+ * const request = new GetRequest()
1654
+ * .withQueryParams({ id: '123' })
1655
+ * .sendTo('/api/users');
1656
+ */
1657
+ class GetRequest extends BaseRequest {
1658
+ constructor() {
1659
+ super(...arguments);
1660
+ this.method = HttpMethod.GET;
1661
+ }
1662
+ }
1663
+ /**
1664
+ * HTTP HEAD request implementation
1665
+ * Similar to GET but returns only headers without a response body.
1666
+ *
1667
+ * @example
1668
+ * const request = new HeadRequest()
1669
+ * .sendTo('/api/users/123');
1670
+ */
1671
+ class HeadRequest extends BaseRequest {
1672
+ constructor() {
1673
+ super(...arguments);
1674
+ this.method = HttpMethod.HEAD;
1675
+ }
1676
+ }
1677
+ /**
1678
+ * HTTP OPTIONS request implementation
1679
+ * Used to describe the communication options for the target resource.
1680
+ *
1681
+ * @example
1682
+ * const request = new OptionsRequest()
1683
+ * .sendTo('/api/users');
1684
+ */
1685
+ class OptionsRequest extends BaseRequest {
1686
+ constructor() {
1687
+ super(...arguments);
1688
+ this.method = HttpMethod.OPTIONS;
1689
+ }
1690
+ }
1691
+ /**
1692
+ * HTTP DELETE request implementation
1693
+ * Used to delete a resource on the server.
1694
+ *
1695
+ * @example
1696
+ * const request = new DeleteRequest()
1697
+ * .sendTo('/api/users/123');
1698
+ */
1699
+ class DeleteRequest extends BaseRequest {
1700
+ constructor() {
1701
+ super(...arguments);
1702
+ this.method = HttpMethod.DELETE;
1703
+ }
1704
+ }
1705
+ /**
1706
+ * HTTP POST request implementation
1707
+ * Used to create a new resource or submit data for processing.
1708
+ *
1709
+ * @example
1710
+ * const request = new PostRequest()
1711
+ * .withJson({ name: 'John', email: 'john@example.com' })
1712
+ * .sendTo('/api/users');
1713
+ */
1714
+ class PostRequest extends BodyRequest {
1715
+ constructor() {
1716
+ super(...arguments);
1717
+ this.method = HttpMethod.POST;
1718
+ }
1719
+ }
1720
+ /**
1721
+ * HTTP PUT request implementation
1722
+ * Used to replace or update an existing resource.
1723
+ *
1724
+ * @example
1725
+ * const request = new PutRequest()
1726
+ * .withJson({ id: '123', name: 'John', email: 'john@example.com' })
1727
+ * .sendTo('/api/users/123');
1728
+ */
1729
+ class PutRequest extends BodyRequest {
1730
+ constructor() {
1731
+ super(...arguments);
1732
+ this.method = HttpMethod.PUT;
1733
+ }
1734
+ }
1735
+ /**
1736
+ * HTTP PATCH request implementation
1737
+ * Used to apply partial modifications to a resource.
1738
+ *
1739
+ * @example
1740
+ * const request = new PatchRequest()
1741
+ * .withJson({ email: 'new.email@example.com' })
1742
+ * .sendTo('/api/users/123');
1743
+ */
1744
+ class PatchRequest extends BodyRequest {
1745
+ constructor() {
1746
+ super(...arguments);
1747
+ this.method = HttpMethod.PATCH;
1748
+ }
1749
+ }/**
1750
+ * Create a GET request
1751
+ * Used for retrieving resources from the server.
1752
+ *
1753
+ * @returns A new GET request instance
1754
+ *
1755
+ * @example
1756
+ * const users = await get()
1757
+ * .withQueryParams({ limit: 10 })
1758
+ * .sendTo('/api/users')
1759
+ * .getJson();
1760
+ */
1761
+ function get() {
1762
+ return new GetRequest();
1763
+ }
1764
+ /**
1765
+ * Create a POST request
1766
+ * Used for creating new resources or submitting data.
1767
+ *
1768
+ * @returns A new POST request instance
1769
+ *
1770
+ * @example
1771
+ * const newUser = await post()
1772
+ * .withJson({ name: 'John', email: 'john@example.com' })
1773
+ * .sendTo('/api/users')
1774
+ * .getJson();
1775
+ */
1776
+ function post() {
1777
+ return new PostRequest();
1778
+ }
1779
+ /**
1780
+ * Create a PUT request
1781
+ * Used for replacing or updating an existing resource.
1782
+ *
1783
+ * @returns A new PUT request instance
1784
+ *
1785
+ * @example
1786
+ * const updatedUser = await put()
1787
+ * .withJson({ name: 'John Updated', email: 'john@example.com' })
1788
+ * .sendTo('/api/users/123')
1789
+ * .getJson();
1790
+ */
1791
+ function put() {
1792
+ return new PutRequest();
1793
+ }
1794
+ /**
1795
+ * Create a DELETE request
1796
+ * Used for removing resources from the server.
1797
+ *
1798
+ * @returns A new DELETE request instance
1799
+ *
1800
+ * @example
1801
+ * await del()
1802
+ * .sendTo('/api/users/123')
1803
+ * .getJson();
1804
+ */
1805
+ function del() {
1806
+ return new DeleteRequest();
1807
+ }
1808
+ /**
1809
+ * Create a PATCH request
1810
+ * Used for applying partial modifications to a resource.
1811
+ *
1812
+ * @returns A new PATCH request instance
1813
+ *
1814
+ * @example
1815
+ * const patchedUser = await patch()
1816
+ * .withJson({ status: 'active' })
1817
+ * .sendTo('/api/users/123')
1818
+ * .getJson();
1819
+ */
1820
+ function patch() {
1821
+ return new PatchRequest();
1822
+ }
1823
+ /**
1824
+ * Create a HEAD request
1825
+ * Similar to GET but returns only headers without a body.
1826
+ *
1827
+ * @returns A new HEAD request instance
1828
+ *
1829
+ * @example
1830
+ * const response = await head()
1831
+ * .sendTo('/api/users/123');
1832
+ * console.log(response.headers.get('Last-Modified'));
1833
+ */
1834
+ function head() {
1835
+ return new HeadRequest();
1836
+ }
1837
+ /**
1838
+ * Create an OPTIONS request
1839
+ * Used to describe the communication options for a resource.
1840
+ *
1841
+ * @returns A new OPTIONS request instance
1842
+ *
1843
+ * @example
1844
+ * const response = await options()
1845
+ * .sendTo('/api/users');
1846
+ * console.log(response.headers.get('Allow'));
1847
+ */
1848
+ function options() {
1849
+ return new OptionsRequest();
1850
+ }/**
1851
+ * Main API object for creating HTTP requests
1852
+ * Provides factory methods for all HTTP methods and access to global configuration.
1853
+ */
1854
+ const create = {
1855
+ get,
1856
+ post,
1857
+ put,
1858
+ del,
1859
+ patch,
1860
+ head,
1861
+ options,
1862
+ config: Config.getInstance(),
1863
+ };export{CacheManager,CacheMode,CookieUtils,CredentialsPolicy,DeleteRequest,GetRequest,HeadRequest,HttpMethod,OptionsRequest,PatchRequest,PostRequest,PutRequest,RedirectMode,ReferrerPolicy,RequestError,RequestMode,RequestPriority,ResponseWrapper,SameSitePolicy,createLocalStorageStorage,createMemoryStorage,createSessionStorageStorage,create as default};//# sourceMappingURL=index.esm.js.map