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

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,2097 @@
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 cookie SameSite policies
53
+ */
54
+ var SameSitePolicy;
55
+ (function (SameSitePolicy) {
56
+ SameSitePolicy["LAX"] = "Lax";
57
+ SameSitePolicy["NONE"] = "None";
58
+ SameSitePolicy["STRICT"] = "Strict";
59
+ })(SameSitePolicy || (SameSitePolicy = {}));
60
+ /**
61
+ * Enum for body types
62
+ */
63
+ var BodyType;
64
+ (function (BodyType) {
65
+ BodyType["JSON"] = "json";
66
+ BodyType["STRING"] = "string";
67
+ BodyType["BINARY"] = "binary";
68
+ })(BodyType || (BodyType = {}));
69
+ /**
70
+ * Referrer policies for fetch requests
71
+ */
72
+ var ReferrerPolicy;
73
+ (function (ReferrerPolicy) {
74
+ ReferrerPolicy["ORIGIN"] = "origin";
75
+ ReferrerPolicy["UNSAFE_URL"] = "unsafe-url";
76
+ ReferrerPolicy["SAME_ORIGIN"] = "same-origin";
77
+ ReferrerPolicy["NO_REFERRER"] = "no-referrer";
78
+ ReferrerPolicy["STRICT_ORIGIN"] = "strict-origin";
79
+ ReferrerPolicy["ORIGIN_WHEN_CROSS_ORIGIN"] = "origin-when-cross-origin";
80
+ ReferrerPolicy["NO_REFERRER_WHEN_DOWNGRADE"] = "no-referrer-when-downgrade";
81
+ ReferrerPolicy["STRICT_ORIGIN_WHEN_CROSS_ORIGIN"] = "strict-origin-when-cross-origin";
82
+ })(ReferrerPolicy || (ReferrerPolicy = {}));
83
+
84
+ class RequestError extends Error {
85
+ constructor(message, url, method, options = {}) {
86
+ super(message);
87
+ this.name = "RequestError";
88
+ this.url = url;
89
+ this.method = method;
90
+ this.status = options.status;
91
+ this.response = options.response;
92
+ this.isTimeout = options.isTimeout;
93
+ this.isAborted = options.isAborted;
94
+ // For better stack traces in modern environments
95
+ if (Error.captureStackTrace) {
96
+ Error.captureStackTrace(this, RequestError);
97
+ }
98
+ // Maintains proper prototype chain for instanceof checks
99
+ Object.setPrototypeOf(this, RequestError.prototype);
100
+ }
101
+ /**
102
+ * Static methods for creating specific types of RequestError
103
+ */
104
+ static timeout(url, method, timeoutMs) {
105
+ return new RequestError(`Timeout: ${timeoutMs}ms`, url, method, {
106
+ isTimeout: true,
107
+ });
108
+ }
109
+ static fromResponse(response, url, method) {
110
+ return new RequestError(`HTTP ${response.status}`, url, method, {
111
+ status: response.status,
112
+ response,
113
+ });
114
+ }
115
+ static networkError(url, method, originalError) {
116
+ // Provide more descriptive error messages for common network errors
117
+ let message = originalError.message;
118
+ // Check for Node.js error codes (e.g., from undici/dns errors)
119
+ const errorCode = originalError.code;
120
+ const errorName = originalError.name;
121
+ const stack = originalError.stack || "";
122
+ const errorMessageLower = message.toLowerCase();
123
+ // Check for timeout errors (Node.js/undici TimeoutError)
124
+ const isTimeoutError = errorName === "TimeoutError" ||
125
+ errorMessageLower.includes("timeout") ||
126
+ errorMessageLower.includes("aborted due to timeout") ||
127
+ errorCode === "ETIMEDOUT" ||
128
+ stack.includes("TimeoutError") ||
129
+ stack.includes("timeout");
130
+ // If the error message is generic "fetch failed", provide more context
131
+ if (message === "fetch failed" || message === "Failed to fetch") {
132
+ // Check for DNS resolution errors
133
+ const isDnsError = errorCode === "ENOTFOUND" ||
134
+ errorCode === "EAI_AGAIN" ||
135
+ errorCode === "EAI_NODATA" ||
136
+ stack.includes("getaddrinfo") ||
137
+ stack.includes("ENOTFOUND") ||
138
+ stack.includes("EAI_AGAIN");
139
+ // Check for connection errors (but not timeout errors)
140
+ const isConnectionError = !isTimeoutError && (errorCode === "ECONNREFUSED" || errorCode === "ECONNRESET" || stack.includes("ECONNREFUSED") || stack.includes("connect"));
141
+ if (isTimeoutError) {
142
+ message = `Network error: Request timeout for ${url}`;
143
+ }
144
+ else if (isDnsError) {
145
+ message = `Network error: Unable to resolve hostname for ${url}`;
146
+ }
147
+ else if (isConnectionError) {
148
+ message = `Network error: Connection refused for ${url}`;
149
+ }
150
+ else {
151
+ message = `Network error: Failed to fetch ${url}`;
152
+ }
153
+ }
154
+ const error = new RequestError(message, url, method, {
155
+ ...(isTimeoutError ? { isTimeout: true } : {}),
156
+ });
157
+ // Create a proper RequestError stack trace, but append the original stack for debugging
158
+ // This way Node.js will show "RequestError: ..." instead of "TypeError: ..."
159
+ if (originalError.stack) {
160
+ // Get the current stack (which will start with RequestError)
161
+ const currentStack = error.stack || "";
162
+ // Append the original error's stack as "Caused by:" for debugging context
163
+ error.stack = `${currentStack}\n\nCaused by: ${originalError.stack}`;
164
+ }
165
+ return error;
166
+ }
167
+ static abortError(url, method) {
168
+ return new RequestError("Aborted", url, method, {
169
+ isAborted: true,
170
+ });
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Wrapper for HTTP responses with methods to transform the response data
176
+ * Provides caching and conversion between different response formats.
177
+ */
178
+ class ResponseWrapper {
179
+ constructor(response, url, method) {
180
+ this.bodyUsed = false;
181
+ this.response = response;
182
+ this.url = url;
183
+ this.method = method;
184
+ }
185
+ // Wrapper properties
186
+ get status() {
187
+ return this.response.status;
188
+ }
189
+ get statusText() {
190
+ return this.response.statusText;
191
+ }
192
+ get headers() {
193
+ return this.response.headers;
194
+ }
195
+ get ok() {
196
+ return this.response.ok;
197
+ }
198
+ get raw() {
199
+ return this.response;
200
+ }
201
+ /**
202
+ * Parse the response body as JSON
203
+ * Caches the result for subsequent calls.
204
+ *
205
+ * @returns The parsed JSON data
206
+ * @throws Error if the response has already been consumed in a different format
207
+ *
208
+ * @example
209
+ * const data = await response.getJson();
210
+ * console.log(data.items);
211
+ */
212
+ async getJson() {
213
+ if (this.jsonCache !== undefined) {
214
+ return this.jsonCache;
215
+ }
216
+ if (this.bodyUsed) {
217
+ // If we already have text, try to parse it as JSON
218
+ if (this.textCache !== undefined) {
219
+ try {
220
+ const parsed = JSON.parse(this.textCache);
221
+ this.jsonCache = parsed;
222
+ return parsed;
223
+ }
224
+ catch (e) {
225
+ const errorMessage = e instanceof Error ? e.message : String(e);
226
+ if (this.url && this.method) {
227
+ throw new RequestError(`Invalid JSON: ${errorMessage}`, this.url, this.method, {
228
+ response: this.response,
229
+ });
230
+ }
231
+ throw new Error(`Invalid JSON: ${errorMessage}`);
232
+ }
233
+ }
234
+ if (this.url && this.method) {
235
+ throw new RequestError("Body already consumed", this.url, this.method, {
236
+ response: this.response,
237
+ });
238
+ }
239
+ throw new Error("Body already consumed");
240
+ }
241
+ this.bodyUsed = true;
242
+ try {
243
+ const parsed = await this.response.json();
244
+ this.jsonCache = parsed;
245
+ return parsed;
246
+ }
247
+ catch (e) {
248
+ const errorMessage = e instanceof Error ? e.message : String(e);
249
+ if (this.url && this.method) {
250
+ throw new RequestError(`Invalid JSON: ${errorMessage}`, this.url, this.method, {
251
+ status: this.response.status,
252
+ response: this.response,
253
+ });
254
+ }
255
+ throw e;
256
+ }
257
+ }
258
+ /**
259
+ * Get the response body as text
260
+ * Caches the result for subsequent calls. Attempts to convert from JSON or Blob
261
+ * if the body has already been consumed in those formats.
262
+ *
263
+ * @returns The response text
264
+ * @throws Error if the response has already been consumed in an incompatible format
265
+ *
266
+ * @example
267
+ * const text = await response.getText();
268
+ */
269
+ async getText() {
270
+ if (this.textCache !== undefined) {
271
+ return this.textCache;
272
+ }
273
+ if (this.bodyUsed) {
274
+ // If we already have JSON, convert it to text
275
+ if (this.jsonCache !== undefined) {
276
+ this.textCache = JSON.stringify(this.jsonCache);
277
+ return this.textCache;
278
+ }
279
+ // If we have blob, convert to text
280
+ if (this.blobCache !== undefined) {
281
+ try {
282
+ this.textCache = await this.blobCache.text();
283
+ return this.textCache;
284
+ }
285
+ catch (e) {
286
+ const errorMessage = e instanceof Error ? e.message : String(e);
287
+ if (this.url && this.method) {
288
+ throw new RequestError(`Cannot convert to text: ${errorMessage}`, this.url, this.method, {
289
+ response: this.response,
290
+ });
291
+ }
292
+ throw e;
293
+ }
294
+ }
295
+ if (this.url && this.method) {
296
+ throw new RequestError("Cannot convert to text", this.url, this.method, {
297
+ response: this.response,
298
+ });
299
+ }
300
+ throw new Error("Cannot convert to text");
301
+ }
302
+ this.bodyUsed = true;
303
+ try {
304
+ this.textCache = await this.response.text();
305
+ return this.textCache;
306
+ }
307
+ catch (e) {
308
+ const errorMessage = e instanceof Error ? e.message : String(e);
309
+ if (this.url && this.method) {
310
+ throw new RequestError(`Read failed: ${errorMessage}`, this.url, this.method, {
311
+ status: this.response.status,
312
+ response: this.response,
313
+ });
314
+ }
315
+ throw e;
316
+ }
317
+ }
318
+ /**
319
+ * Get the response body as a Blob
320
+ * Caches the result for subsequent calls. Attempts to convert from text
321
+ * if the body has already been consumed as text.
322
+ *
323
+ * @returns The response as a Blob
324
+ * @throws Error if the response has already been consumed in an incompatible format
325
+ *
326
+ * @example
327
+ * const blob = await response.getBlob();
328
+ * const url = URL.createObjectURL(blob);
329
+ */
330
+ async getBlob() {
331
+ if (this.blobCache !== undefined) {
332
+ return this.blobCache;
333
+ }
334
+ if (this.bodyUsed) {
335
+ // If we have text, convert to Blob
336
+ if (this.textCache !== undefined) {
337
+ this.blobCache = new Blob([this.textCache], { type: "text/plain" });
338
+ return this.blobCache;
339
+ }
340
+ if (this.url && this.method) {
341
+ throw new RequestError("Cannot convert to Blob", this.url, this.method, {
342
+ response: this.response,
343
+ });
344
+ }
345
+ throw new Error("Cannot convert to Blob");
346
+ }
347
+ this.bodyUsed = true;
348
+ try {
349
+ this.blobCache = await this.response.blob();
350
+ return this.blobCache;
351
+ }
352
+ catch (e) {
353
+ const errorMessage = e instanceof Error ? e.message : String(e);
354
+ if (this.url && this.method) {
355
+ throw new RequestError(`Read failed: ${errorMessage}`, this.url, this.method, {
356
+ status: this.response.status,
357
+ response: this.response,
358
+ });
359
+ }
360
+ throw e;
361
+ }
362
+ }
363
+ /**
364
+ * Get the raw response body as a ReadableStream
365
+ * This method consumes the body and should only be called once.
366
+ *
367
+ * @returns The response body as a ReadableStream or null
368
+ * @throws Error if the response body has already been consumed
369
+ *
370
+ * @example
371
+ * const stream = response.getBody();
372
+ * if (stream) {
373
+ * const reader = stream.getReader();
374
+ * // Process the stream
375
+ * }
376
+ */
377
+ getBody() {
378
+ if (this.bodyUsed) {
379
+ if (this.url && this.method) {
380
+ throw new RequestError("Body already consumed", this.url, this.method, {
381
+ response: this.response,
382
+ });
383
+ }
384
+ throw new Error("Body already consumed");
385
+ }
386
+ this.bodyUsed = true;
387
+ return this.response.body;
388
+ }
389
+ /**
390
+ * Extract specific data using a selector function
391
+ * If no selector is provided, returns the full JSON response.
392
+ *
393
+ * @param selector - Optional function to extract and transform data
394
+ * @returns A promise that resolves to the selected data
395
+ *
396
+ * @example
397
+ * // Get full response
398
+ * const data = await response.getData();
399
+ *
400
+ * // Extract specific data
401
+ * const users = await response.getData(data => data.results.users);
402
+ */
403
+ async getData(selector) {
404
+ try {
405
+ const data = await this.getJson();
406
+ // If no selector is provided, return the raw JSON data
407
+ if (!selector)
408
+ return data;
409
+ // Apply the selector if provided
410
+ return selector(data);
411
+ }
412
+ catch (error) {
413
+ // If it's already a RequestError, re-throw it
414
+ if (error instanceof RequestError) {
415
+ throw error;
416
+ }
417
+ // Enhance selector errors with context
418
+ if (selector) {
419
+ const errorMessage = error instanceof Error ? error.message : String(error);
420
+ if (this.url && this.method) {
421
+ throw new RequestError(`Data selector failed: ${errorMessage}`, this.url, this.method, {
422
+ response: this.response,
423
+ });
424
+ }
425
+ throw new Error(`Data selector failed: ${errorMessage}`);
426
+ }
427
+ // If we get here and it's not a RequestError, wrap it
428
+ // This should rarely happen as getJson() should throw RequestError
429
+ const errorObj = error instanceof Error ? error : new Error(String(error));
430
+ if (this.url && this.method) {
431
+ throw RequestError.networkError(this.url, this.method, errorObj);
432
+ }
433
+ throw errorObj;
434
+ }
435
+ }
436
+ }
437
+
438
+ class CookieUtils {
439
+ /**
440
+ * Formats cookies for a request
441
+ * @param cookies Object containing cookie name-value pairs or cookie options
442
+ * @returns Formatted cookie string for the Cookie header
443
+ */
444
+ static formatRequestCookies(cookies) {
445
+ const cookiePairs = [];
446
+ Object.entries(cookies).forEach(([name, valueOrOptions]) => {
447
+ let value;
448
+ if (typeof valueOrOptions === "string") {
449
+ value = valueOrOptions;
450
+ }
451
+ else {
452
+ // Extract value from options object without validation
453
+ value = valueOrOptions.value;
454
+ }
455
+ // Add the cookie to the request
456
+ cookiePairs.push(`${encodeURIComponent(name)}=${encodeURIComponent(value)}`);
457
+ });
458
+ return cookiePairs.join("; ");
459
+ }
460
+ }
461
+
462
+ /**
463
+ * Utility class for CSRF token management
464
+ */
465
+ class CsrfUtils {
466
+ /**
467
+ * Extracts CSRF token from a meta tag in the document head
468
+ * @param metaName The name attribute of the meta tag (default: "csrf-token")
469
+ * @returns The CSRF token or null if not found
470
+ */
471
+ static getTokenFromMeta(metaName = "csrf-token") {
472
+ if (typeof document === "undefined") {
473
+ return null;
474
+ }
475
+ const meta = document.querySelector(`meta[name="${metaName}"]`);
476
+ return (meta === null || meta === void 0 ? void 0 : meta.getAttribute("content")) || null;
477
+ }
478
+ /**
479
+ * Extracts CSRF token from a cookie
480
+ * @param cookieName The name of the cookie containing the CSRF token
481
+ * @returns The CSRF token or null if not found
482
+ */
483
+ static getTokenFromCookie(cookieName = "csrf-token") {
484
+ if (typeof document === "undefined") {
485
+ return null;
486
+ }
487
+ const cookies = document.cookie.split(";");
488
+ for (const cookie of cookies) {
489
+ const [name, value] = cookie.trim().split("=");
490
+ if (name === cookieName) {
491
+ return decodeURIComponent(value);
492
+ }
493
+ }
494
+ return null;
495
+ }
496
+ /**
497
+ * Validates if the provided string is a potential CSRF token
498
+ * Checks if the token meets security requirements
499
+ * @param token The token to validate
500
+ * @returns Whether the token is valid
501
+ */
502
+ static isValidToken(token) {
503
+ // Basic checks for null/undefined and type
504
+ if (typeof token !== "string") {
505
+ return false;
506
+ }
507
+ if (token.length < 8)
508
+ return false;
509
+ // If token is longer than 10 chars, perform additional security checks
510
+ if (token.length > 10) {
511
+ // Check for valid character set (alphanumeric & common token symbols)
512
+ const validTokenRegex = /^[A-Za-z0-9\-_=+/.]+$/;
513
+ if (!validTokenRegex.test(token)) {
514
+ return false;
515
+ }
516
+ // For longer tokens, check for sufficient entropy (at least 2 character types)
517
+ const hasUpperCase = /[A-Z]/.test(token);
518
+ const hasLowerCase = /[a-z]/.test(token);
519
+ const hasNumbers = /[0-9]/.test(token);
520
+ const hasSpecials = /[-_=+/.]/.test(token);
521
+ const characterTypesCount = [hasUpperCase, hasLowerCase, hasNumbers, hasSpecials].filter(Boolean).length;
522
+ return characterTypesCount >= 2;
523
+ }
524
+ // For shorter tokens (8-10 chars), just return true if we reached here
525
+ return true;
526
+ }
527
+ }
528
+
529
+ /**
530
+ * Global configuration for create-request
531
+ */
532
+ class Config {
533
+ constructor() {
534
+ // CSRF configuration
535
+ this.csrfHeaderName = "X-CSRF-Token";
536
+ this.xsrfCookieName = "XSRF-TOKEN";
537
+ this.xsrfHeaderName = "X-XSRF-TOKEN";
538
+ this.csrfToken = null;
539
+ this.enableAutoXsrf = true;
540
+ this.enableAntiCsrf = true; // X-Requested-With header
541
+ // Interceptor configuration
542
+ this.requestInterceptors = [];
543
+ this.responseInterceptors = [];
544
+ this.errorInterceptors = [];
545
+ this.nextInterceptorId = 1;
546
+ }
547
+ /**
548
+ * Get the singleton instance of the Config class
549
+ *
550
+ * @returns The global configuration instance
551
+ *
552
+ * @example
553
+ * const config = Config.getInstance();
554
+ * config.setCsrfToken('token123');
555
+ */
556
+ static getInstance() {
557
+ if (!Config.instance) {
558
+ Config.instance = new Config();
559
+ }
560
+ return Config.instance;
561
+ }
562
+ /**
563
+ * Set a global CSRF token to be used for all requests
564
+ *
565
+ * @param token - The CSRF token value
566
+ * @returns The config instance for chaining
567
+ *
568
+ * @example
569
+ * Config.getInstance().setCsrfToken('myToken123');
570
+ */
571
+ setCsrfToken(token) {
572
+ this.csrfToken = token;
573
+ return this;
574
+ }
575
+ /**
576
+ * Get the global CSRF token that will be automatically applied to requests
577
+ *
578
+ * @returns The current CSRF token or null if not set
579
+ */
580
+ getCsrfToken() {
581
+ return this.csrfToken;
582
+ }
583
+ /**
584
+ * Set the CSRF header name used when sending the token
585
+ *
586
+ * @param name - The header name to use
587
+ * @returns The config instance for chaining
588
+ *
589
+ * @example
590
+ * Config.getInstance().setCsrfHeaderName('X-My-CSRF-Token');
591
+ */
592
+ setCsrfHeaderName(name) {
593
+ this.csrfHeaderName = name;
594
+ return this;
595
+ }
596
+ /**
597
+ * Get the configured CSRF header name
598
+ *
599
+ * @returns The current CSRF header name
600
+ */
601
+ getCsrfHeaderName() {
602
+ return this.csrfHeaderName;
603
+ }
604
+ /**
605
+ * Set the XSRF cookie name to look for when extracting tokens from cookies
606
+ *
607
+ * @param name - The cookie name to look for
608
+ * @returns The config instance for chaining
609
+ *
610
+ * @example
611
+ * Config.getInstance().setXsrfCookieName('MY-XSRF-COOKIE');
612
+ */
613
+ setXsrfCookieName(name) {
614
+ this.xsrfCookieName = name;
615
+ return this;
616
+ }
617
+ /**
618
+ * Get the configured XSRF cookie name
619
+ *
620
+ * @returns The current XSRF cookie name
621
+ */
622
+ getXsrfCookieName() {
623
+ return this.xsrfCookieName;
624
+ }
625
+ /**
626
+ * Set the XSRF header name for sending tokens extracted from cookies
627
+ *
628
+ * @param name - The header name to use
629
+ * @returns The config instance for chaining
630
+ */
631
+ setXsrfHeaderName(name) {
632
+ this.xsrfHeaderName = name;
633
+ return this;
634
+ }
635
+ /**
636
+ * Get the configured XSRF header name
637
+ *
638
+ * @returns The current XSRF header name
639
+ */
640
+ getXsrfHeaderName() {
641
+ return this.xsrfHeaderName;
642
+ }
643
+ /**
644
+ * Enable or disable automatic extraction of XSRF tokens from cookies
645
+ * When enabled, the library will look for XSRF tokens in cookies and
646
+ * automatically add them to request headers.
647
+ *
648
+ * @param enable - Whether to enable this feature
649
+ * @returns The config instance for chaining
650
+ *
651
+ * @example
652
+ * Config.getInstance().setEnableAutoXsrf(false); // Disable XSRF extraction
653
+ */
654
+ setEnableAutoXsrf(enable) {
655
+ this.enableAutoXsrf = enable;
656
+ return this;
657
+ }
658
+ /**
659
+ * Check if automatic XSRF token extraction is enabled
660
+ *
661
+ * @returns True if automatic XSRF is enabled
662
+ */
663
+ isAutoXsrfEnabled() {
664
+ return this.enableAutoXsrf;
665
+ }
666
+ /**
667
+ * Enable or disable automatic addition of anti-CSRF headers
668
+ * When enabled, X-Requested-With: XMLHttpRequest will be added to all requests.
669
+ *
670
+ * @param enable - Whether to enable this feature
671
+ * @returns The config instance for chaining
672
+ */
673
+ setEnableAntiCsrf(enable) {
674
+ this.enableAntiCsrf = enable;
675
+ return this;
676
+ }
677
+ /**
678
+ * Check if anti-CSRF protection is enabled
679
+ *
680
+ * @returns True if anti-CSRF protection is enabled
681
+ */
682
+ isAntiCsrfEnabled() {
683
+ return this.enableAntiCsrf;
684
+ }
685
+ /**
686
+ * Add a global request interceptor
687
+ * Request interceptors can modify the request configuration or return an early response
688
+ *
689
+ * @param interceptor - The request interceptor function
690
+ * @returns The interceptor ID for later removal
691
+ *
692
+ * @example
693
+ * const id = Config.getInstance().addRequestInterceptor((config) => {
694
+ * config.headers['X-Custom'] = 'value';
695
+ * return config;
696
+ * });
697
+ */
698
+ addRequestInterceptor(interceptor) {
699
+ const id = this.nextInterceptorId++;
700
+ this.requestInterceptors.push({ id, interceptor });
701
+ return id;
702
+ }
703
+ /**
704
+ * Add a global response interceptor
705
+ * Response interceptors can transform the response
706
+ *
707
+ * @param interceptor - The response interceptor function
708
+ * @returns The interceptor ID for later removal
709
+ *
710
+ * @example
711
+ * const id = Config.getInstance().addResponseInterceptor((response) => {
712
+ * console.log('Response received:', response.status);
713
+ * return response;
714
+ * });
715
+ */
716
+ addResponseInterceptor(interceptor) {
717
+ const id = this.nextInterceptorId++;
718
+ this.responseInterceptors.push({ id, interceptor });
719
+ return id;
720
+ }
721
+ /**
722
+ * Add a global error interceptor
723
+ * Error interceptors can handle or transform errors
724
+ *
725
+ * @param interceptor - The error interceptor function
726
+ * @returns The interceptor ID for later removal
727
+ *
728
+ * @example
729
+ * const id = Config.getInstance().addErrorInterceptor((error) => {
730
+ * console.error('Request failed:', error);
731
+ * throw error;
732
+ * });
733
+ */
734
+ addErrorInterceptor(interceptor) {
735
+ const id = this.nextInterceptorId++;
736
+ this.errorInterceptors.push({ id, interceptor });
737
+ return id;
738
+ }
739
+ /**
740
+ * Remove a request interceptor by its ID
741
+ *
742
+ * @param id - The interceptor ID returned from addRequestInterceptor
743
+ *
744
+ * @example
745
+ * Config.getInstance().removeRequestInterceptor(id);
746
+ */
747
+ removeRequestInterceptor(id) {
748
+ this.requestInterceptors = this.requestInterceptors.filter(item => item.id !== id);
749
+ }
750
+ /**
751
+ * Remove a response interceptor by its ID
752
+ *
753
+ * @param id - The interceptor ID returned from addResponseInterceptor
754
+ *
755
+ * @example
756
+ * Config.getInstance().removeResponseInterceptor(id);
757
+ */
758
+ removeResponseInterceptor(id) {
759
+ this.responseInterceptors = this.responseInterceptors.filter(item => item.id !== id);
760
+ }
761
+ /**
762
+ * Remove an error interceptor by its ID
763
+ *
764
+ * @param id - The interceptor ID returned from addErrorInterceptor
765
+ *
766
+ * @example
767
+ * Config.getInstance().removeErrorInterceptor(id);
768
+ */
769
+ removeErrorInterceptor(id) {
770
+ this.errorInterceptors = this.errorInterceptors.filter(item => item.id !== id);
771
+ }
772
+ /**
773
+ * Clear all interceptors (request, response, and error)
774
+ *
775
+ * @example
776
+ * Config.getInstance().clearInterceptors();
777
+ */
778
+ clearInterceptors() {
779
+ this.requestInterceptors = [];
780
+ this.responseInterceptors = [];
781
+ this.errorInterceptors = [];
782
+ }
783
+ /**
784
+ * Get all global request interceptors (in registration order)
785
+ * @internal
786
+ */
787
+ getRequestInterceptors() {
788
+ return this.requestInterceptors.map(item => item.interceptor);
789
+ }
790
+ /**
791
+ * Get all global response interceptors (in registration order)
792
+ * @internal
793
+ */
794
+ getResponseInterceptors() {
795
+ return this.responseInterceptors.map(item => item.interceptor);
796
+ }
797
+ /**
798
+ * Get all global error interceptors (in registration order)
799
+ * @internal
800
+ */
801
+ getErrorInterceptors() {
802
+ return this.errorInterceptors.map(item => item.interceptor);
803
+ }
804
+ /**
805
+ * Reset all configuration options to their default values
806
+ *
807
+ * @returns The config instance for chaining
808
+ *
809
+ * @example
810
+ * Config.getInstance().reset();
811
+ */
812
+ reset() {
813
+ this.csrfToken = null;
814
+ this.csrfHeaderName = "X-CSRF-Token";
815
+ this.enableAntiCsrf = true;
816
+ this.xsrfCookieName = "XSRF-TOKEN";
817
+ this.xsrfHeaderName = "X-XSRF-TOKEN";
818
+ this.enableAutoXsrf = true;
819
+ this.clearInterceptors();
820
+ return this;
821
+ }
822
+ }
823
+
824
+ /**
825
+ * Base class with common functionality for all request types
826
+ * Provides the core request building and execution capabilities.
827
+ */
828
+ class BaseRequest {
829
+ constructor(url) {
830
+ this.requestOptions = {
831
+ headers: {},
832
+ };
833
+ this.queryParams = new URLSearchParams();
834
+ this.autoApplyCsrfProtection = true;
835
+ // Per-request interceptors
836
+ this.requestInterceptors = [];
837
+ this.responseInterceptors = [];
838
+ this.errorInterceptors = [];
839
+ this.url = url;
840
+ }
841
+ /**
842
+ * Creates a fluent API for setting enum-based options
843
+ * Combines direct setter with convenience methods
844
+ */
845
+ createFluentSetter(optionName, options) {
846
+ const fluent = {};
847
+ // Create convenience methods for each enum value
848
+ Object.entries(options).forEach(([key, value]) => {
849
+ fluent[key] = () => {
850
+ this.requestOptions[optionName] = value;
851
+ return this;
852
+ };
853
+ });
854
+ // Create the callable setter
855
+ const callable = (value) => {
856
+ this.requestOptions[optionName] = value;
857
+ return this;
858
+ };
859
+ return Object.assign(callable, fluent);
860
+ }
861
+ validateUrl(url) {
862
+ if (!(url === null || url === void 0 ? void 0 : url.trim()))
863
+ throw new RequestError("URL cannot be empty", url, this.method);
864
+ if (url.includes("\0") || url.includes("\r") || url.includes("\n")) {
865
+ throw new RequestError("Invalid URL (control chars)", url, this.method);
866
+ }
867
+ const trimmed = url.trim();
868
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
869
+ try {
870
+ new URL(trimmed);
871
+ }
872
+ catch (_a) {
873
+ throw new RequestError(`Invalid URL: ${trimmed}`, trimmed, this.method);
874
+ }
875
+ }
876
+ }
877
+ /**
878
+ * Add multiple HTTP headers to the request
879
+ *
880
+ * @param headers - Key-value pairs of header names and values
881
+ * @returns The request instance for chaining
882
+ *
883
+ * @example
884
+ * request.withHeaders({
885
+ * 'Accept': 'application/json',
886
+ * 'X-Custom-Header': 'value'
887
+ * });
888
+ */
889
+ withHeaders(headers) {
890
+ // Filter out null and undefined values
891
+ const filteredHeaders = {};
892
+ Object.entries(headers).forEach(([key, value]) => {
893
+ if (value !== null && value !== undefined) {
894
+ filteredHeaders[key] = value;
895
+ }
896
+ });
897
+ this.requestOptions.headers = {
898
+ ...this.getHeadersRecord(),
899
+ ...filteredHeaders,
900
+ };
901
+ return this;
902
+ }
903
+ /**
904
+ * Add a single HTTP header to the request
905
+ *
906
+ * @param key - The header name
907
+ * @param value - The header value
908
+ * @returns The request instance for chaining
909
+ *
910
+ * @example
911
+ * request.withHeader('Accept', 'application/json');
912
+ */
913
+ withHeader(key, value) {
914
+ // Ignore null and undefined values
915
+ if (value !== null && value !== undefined) {
916
+ return this.withHeaders({ [key]: value });
917
+ }
918
+ return this;
919
+ }
920
+ /**
921
+ * Set a timeout for the request
922
+ * If the request takes longer than the specified timeout, it will be aborted.
923
+ *
924
+ * @param timeout - The timeout in milliseconds
925
+ * @returns The request instance for chaining
926
+ * @throws Error if timeout is not a positive number
927
+ *
928
+ * @example
929
+ * request.withTimeout(5000); // 5 seconds timeout
930
+ */
931
+ withTimeout(timeout) {
932
+ if (!Number.isFinite(timeout) || timeout <= 0)
933
+ throw new Error("Timeout must be a positive number");
934
+ this.requestOptions.timeout = timeout;
935
+ return this;
936
+ }
937
+ /**
938
+ * Configure automatic retry behavior for failed requests
939
+ *
940
+ * @param retries - Number of retry attempts before failing
941
+ * @returns The request instance for chaining
942
+ * @throws Error if retries is not a non-negative integer
943
+ *
944
+ * @example
945
+ * request.withRetries(3); // Retry up to 3 times
946
+ */
947
+ withRetries(retries) {
948
+ if (!Number.isInteger(retries) || retries < 0)
949
+ throw new Error("Retry count must be a non-negative integer");
950
+ this.requestOptions.retries = retries;
951
+ return this;
952
+ }
953
+ /**
954
+ * Set a callback to be invoked before each retry attempt
955
+ * Useful for implementing backoff strategies or logging retry attempts.
956
+ *
957
+ * @param callback - Function to call before retrying
958
+ * @returns The request instance for chaining
959
+ *
960
+ * @example
961
+ * request.onRetry(({ attempt, error }) => {
962
+ * console.log(`Retry attempt ${attempt} after error: ${error.message}`);
963
+ * return new Promise(resolve => setTimeout(resolve, attempt * 1000));
964
+ * });
965
+ */
966
+ onRetry(callback) {
967
+ this.requestOptions.onRetry = callback;
968
+ return this;
969
+ }
970
+ /**
971
+ * Sets credentials policy - supports both direct call and fluent API
972
+ * @example
973
+ * request.withCredentials("include")
974
+ * request.withCredentials(CredentialsPolicy.INCLUDE)
975
+ * request.withCredentials.INCLUDE()
976
+ */
977
+ get withCredentials() {
978
+ return this.createFluentSetter("credentials", {
979
+ INCLUDE: CredentialsPolicy.INCLUDE,
980
+ OMIT: CredentialsPolicy.OMIT,
981
+ SAME_ORIGIN: CredentialsPolicy.SAME_ORIGIN,
982
+ });
983
+ }
984
+ /**
985
+ * Allows providing an external AbortController to cancel the request
986
+ * @param controller - The AbortController to use for this request
987
+ * @returns The instance for chaining
988
+ */
989
+ withAbortController(controller) {
990
+ this.abortController = controller;
991
+ return this;
992
+ }
993
+ /**
994
+ * Sets the referrer for the request
995
+ * @param referrer - The referrer URL or policy
996
+ * @returns The instance for chaining
997
+ */
998
+ withReferrer(referrer) {
999
+ this.requestOptions.referrer = referrer;
1000
+ return this;
1001
+ }
1002
+ /**
1003
+ * Sets referrer policy - supports both direct call and fluent API
1004
+ * @example
1005
+ * request.withReferrerPolicy("no-referrer")
1006
+ * request.withReferrerPolicy(ReferrerPolicy.NO_REFERRER)
1007
+ * request.withReferrerPolicy.NO_REFERRER()
1008
+ */
1009
+ get withReferrerPolicy() {
1010
+ return this.createFluentSetter("referrerPolicy", {
1011
+ ORIGIN: ReferrerPolicy.ORIGIN,
1012
+ UNSAFE_URL: ReferrerPolicy.UNSAFE_URL,
1013
+ SAME_ORIGIN: ReferrerPolicy.SAME_ORIGIN,
1014
+ NO_REFERRER: ReferrerPolicy.NO_REFERRER,
1015
+ STRICT_ORIGIN: ReferrerPolicy.STRICT_ORIGIN,
1016
+ ORIGIN_WHEN_CROSS_ORIGIN: ReferrerPolicy.ORIGIN_WHEN_CROSS_ORIGIN,
1017
+ NO_REFERRER_WHEN_DOWNGRADE: ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE,
1018
+ STRICT_ORIGIN_WHEN_CROSS_ORIGIN: ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
1019
+ });
1020
+ }
1021
+ /**
1022
+ * Sets redirect mode - supports both direct call and fluent API
1023
+ * @example
1024
+ * request.withRedirect("follow")
1025
+ * request.withRedirect(RedirectMode.FOLLOW)
1026
+ * request.withRedirect.FOLLOW()
1027
+ */
1028
+ get withRedirect() {
1029
+ return this.createFluentSetter("redirect", {
1030
+ FOLLOW: RedirectMode.FOLLOW,
1031
+ ERROR: RedirectMode.ERROR,
1032
+ MANUAL: RedirectMode.MANUAL,
1033
+ });
1034
+ }
1035
+ /**
1036
+ * Sets the keepalive flag for the request
1037
+ * @param keepalive - Whether to allow the request to outlive the page
1038
+ * @returns The instance for chaining
1039
+ */
1040
+ withKeepAlive(keepalive) {
1041
+ this.requestOptions.keepalive = keepalive;
1042
+ return this;
1043
+ }
1044
+ /**
1045
+ * Sets request priority - supports both direct call and fluent API
1046
+ * @example
1047
+ * request.withPriority("high")
1048
+ * request.withPriority(RequestPriority.HIGH)
1049
+ * request.withPriority.HIGH()
1050
+ */
1051
+ get withPriority() {
1052
+ return this.createFluentSetter("priority", {
1053
+ HIGH: RequestPriority.HIGH,
1054
+ LOW: RequestPriority.LOW,
1055
+ AUTO: RequestPriority.AUTO,
1056
+ });
1057
+ }
1058
+ /**
1059
+ * Adds query parameters to the request URL
1060
+ * @param params - An object containing the query parameters
1061
+ * @returns The instance for chaining
1062
+ */
1063
+ withQueryParams(params) {
1064
+ Object.entries(params).forEach(([key, value]) => {
1065
+ if (value === null || value === undefined) {
1066
+ return;
1067
+ }
1068
+ if (Array.isArray(value)) {
1069
+ // Handle array values - add multiple entries with the same key
1070
+ value.forEach(v => this.queryParams.append(key, String(v)));
1071
+ }
1072
+ else {
1073
+ this.queryParams.append(key, String(value));
1074
+ }
1075
+ });
1076
+ return this;
1077
+ }
1078
+ /**
1079
+ * Adds a single query parameter
1080
+ * @param key - The parameter name
1081
+ * @param value - The parameter value, can be a single value or array of values
1082
+ * @returns The instance for chaining
1083
+ */
1084
+ withQueryParam(key, value) {
1085
+ if (value === null || value === undefined)
1086
+ return this;
1087
+ if (Array.isArray(value))
1088
+ value.forEach(v => this.queryParams.append(key, String(v)));
1089
+ else
1090
+ this.queryParams.append(key, String(value));
1091
+ return this;
1092
+ }
1093
+ /**
1094
+ * Sets request mode - supports both direct call and fluent API
1095
+ * @example
1096
+ * request.withMode("cors")
1097
+ * request.withMode(RequestMode.CORS)
1098
+ * request.withMode.CORS()
1099
+ */
1100
+ get withMode() {
1101
+ return this.createFluentSetter("mode", {
1102
+ CORS: RequestMode.CORS,
1103
+ NO_CORS: RequestMode.NO_CORS,
1104
+ SAME_ORIGIN: RequestMode.SAME_ORIGIN,
1105
+ NAVIGATE: RequestMode.NAVIGATE,
1106
+ });
1107
+ }
1108
+ /**
1109
+ * Shorthand for setting the Content-Type header
1110
+ * @param contentType - The content type
1111
+ * @returns The instance for chaining
1112
+ */
1113
+ withContentType(contentType) {
1114
+ return this.withHeaders({
1115
+ "Content-Type": contentType,
1116
+ });
1117
+ }
1118
+ /**
1119
+ * Shorthand for setting the Authorization header
1120
+ * @param authValue - The authorization value
1121
+ * @returns The instance for chaining
1122
+ */
1123
+ withAuthorization(authValue) {
1124
+ return this.withHeaders({
1125
+ Authorization: authValue,
1126
+ });
1127
+ }
1128
+ /**
1129
+ * Shorthand for setting up Basic authentication
1130
+ * @param username - The username
1131
+ * @param password - The password
1132
+ * @returns The instance for chaining
1133
+ */
1134
+ withBasicAuth(username, password) {
1135
+ const credentials = this.encodeBase64(`${username}:${password}`);
1136
+ return this.withAuthorization(`Basic ${credentials}`);
1137
+ }
1138
+ /**
1139
+ * Cross-environment base64 encoding
1140
+ * Works in both browser and Node.js environments
1141
+ */
1142
+ encodeBase64(str) {
1143
+ // Modern approach using TextEncoder (available in both modern browsers and Node.js)
1144
+ if (typeof TextEncoder !== "undefined" && typeof btoa === "function") {
1145
+ const encoder = new TextEncoder();
1146
+ const bytes = encoder.encode(str);
1147
+ return btoa(String.fromCharCode.apply(null, [...new Uint8Array(bytes)]));
1148
+ }
1149
+ // Browser environment
1150
+ if (typeof btoa === "function")
1151
+ return btoa(str);
1152
+ // Node.js environment
1153
+ if (typeof Buffer !== "undefined")
1154
+ return Buffer.from(str).toString("base64");
1155
+ // Fallback (should never happen in modern environments)
1156
+ throw new Error("Base64 encoding is not supported");
1157
+ }
1158
+ /**
1159
+ * Sets the bearer token for authentication
1160
+ * @param token - The bearer token
1161
+ * @returns The instance for chaining
1162
+ */
1163
+ withBearerToken(token) {
1164
+ return this.withAuthorization(`Bearer ${token}`);
1165
+ }
1166
+ /**
1167
+ * Safely get headers as a Record<string, string>
1168
+ * @returns The headers object
1169
+ */
1170
+ getHeadersRecord() {
1171
+ if (typeof this.requestOptions.headers === "object" && this.requestOptions.headers !== null) {
1172
+ return this.requestOptions.headers;
1173
+ }
1174
+ return {};
1175
+ }
1176
+ /**
1177
+ * Helper function to check for header presence in a case-insensitive way
1178
+ * @param headerName Header name to check
1179
+ * @returns Boolean indicating if the header exists (case-insensitive)
1180
+ */
1181
+ hasHeader(headerName) {
1182
+ const headers = this.getHeadersRecord();
1183
+ return Object.keys(headers).some(key => key.toLowerCase() === headerName.toLowerCase());
1184
+ }
1185
+ /**
1186
+ * Sets cookies for the request
1187
+ * @param cookies Object containing cookie name-value pairs or cookie options
1188
+ * @returns The instance for chaining
1189
+ */
1190
+ withCookies(cookies) {
1191
+ const cookieEntries = Object.entries(cookies || {});
1192
+ if (cookieEntries.length === 0) {
1193
+ return this;
1194
+ }
1195
+ const currentHeaders = this.getHeadersRecord();
1196
+ // Get all existing cookie headers with different cases
1197
+ const cookieValues = [];
1198
+ const cookieHeaderKeys = [];
1199
+ Object.keys(currentHeaders).forEach(key => {
1200
+ if (key.toLowerCase() === "cookie") {
1201
+ // Don't add empty cookie values
1202
+ if (currentHeaders[key])
1203
+ cookieValues.push(currentHeaders[key]);
1204
+ cookieHeaderKeys.push(key);
1205
+ }
1206
+ });
1207
+ // Format the new cookies
1208
+ const cookieString = CookieUtils.formatRequestCookies(cookies);
1209
+ // Choose which header name to use - preserve existing case if possible
1210
+ const headerName = cookieHeaderKeys.length > 0 ? cookieHeaderKeys[0] : "Cookie";
1211
+ // Combine all existing cookie values with the new ones
1212
+ const combinedCookieValue = [...cookieValues, cookieString].filter(Boolean).join("; ");
1213
+ // Create a new headers object without any cookie headers
1214
+ const newHeaders = { ...currentHeaders };
1215
+ cookieHeaderKeys.forEach(key => {
1216
+ delete newHeaders[key];
1217
+ });
1218
+ // Set the new combined cookie header
1219
+ this.requestOptions.headers = {
1220
+ ...newHeaders,
1221
+ [headerName]: combinedCookieValue,
1222
+ };
1223
+ return this;
1224
+ }
1225
+ /**
1226
+ * Set a single cookie
1227
+ * @param name Cookie name
1228
+ * @param value Cookie value or options object
1229
+ * @returns The instance for chaining
1230
+ */
1231
+ withCookie(name, value) {
1232
+ return this.withCookies({ [name]: value });
1233
+ }
1234
+ /**
1235
+ * Sets a CSRF token in the request headers
1236
+ * @param token The CSRF token
1237
+ * @param headerName The name of the header to use (default: X-CSRF-Token)
1238
+ * @returns The instance for chaining
1239
+ */
1240
+ withCsrfToken(token, headerName = "X-CSRF-Token") {
1241
+ return this.withHeaders({
1242
+ [headerName]: token,
1243
+ });
1244
+ }
1245
+ /**
1246
+ * Disables automatic anti-CSRF protection.
1247
+ * By default, X-Requested-With: XMLHttpRequest header is sent with all requests.
1248
+ * @returns The instance for chaining
1249
+ */
1250
+ withoutCsrfProtection() {
1251
+ this.autoApplyCsrfProtection = false;
1252
+ return this;
1253
+ }
1254
+ /**
1255
+ * Sets common security headers to help prevent CSRF attacks
1256
+ * @returns The instance for chaining
1257
+ */
1258
+ withAntiCsrfHeaders() {
1259
+ return this.withHeaders({
1260
+ "X-Requested-With": "XMLHttpRequest",
1261
+ });
1262
+ }
1263
+ /**
1264
+ * Add a request interceptor for this specific request
1265
+ * Request interceptors can modify the request configuration or return an early response
1266
+ *
1267
+ * @param interceptor - The request interceptor function
1268
+ * @returns The instance for chaining
1269
+ *
1270
+ * @example
1271
+ * request.withRequestInterceptor((config) => {
1272
+ * config.headers['X-Custom'] = 'value';
1273
+ * return config;
1274
+ * });
1275
+ */
1276
+ withRequestInterceptor(interceptor) {
1277
+ this.requestInterceptors.push(interceptor);
1278
+ return this;
1279
+ }
1280
+ /**
1281
+ * Add a response interceptor for this specific request
1282
+ * Response interceptors can transform the response
1283
+ *
1284
+ * @param interceptor - The response interceptor function
1285
+ * @returns The instance for chaining
1286
+ *
1287
+ * @example
1288
+ * request.withResponseInterceptor((response) => {
1289
+ * console.log('Status:', response.status);
1290
+ * return response;
1291
+ * });
1292
+ */
1293
+ withResponseInterceptor(interceptor) {
1294
+ this.responseInterceptors.push(interceptor);
1295
+ return this;
1296
+ }
1297
+ /**
1298
+ * Add an error interceptor for this specific request
1299
+ * Error interceptors can handle or transform errors
1300
+ *
1301
+ * @param interceptor - The error interceptor function
1302
+ * @returns The instance for chaining
1303
+ *
1304
+ * @example
1305
+ * request.withErrorInterceptor((error) => {
1306
+ * console.error('Request failed:', error);
1307
+ * throw error;
1308
+ * });
1309
+ */
1310
+ withErrorInterceptor(interceptor) {
1311
+ this.errorInterceptors.push(interceptor);
1312
+ return this;
1313
+ }
1314
+ /**
1315
+ * Execute the request and return the ResponseWrapper
1316
+ * This is the base method for getting the full response.
1317
+ *
1318
+ * @returns A promise that resolves to the ResponseWrapper
1319
+ *
1320
+ * @example
1321
+ * const response = await request.getResponse();
1322
+ * console.log(response.status);
1323
+ */
1324
+ async getResponse() {
1325
+ const url = this.formatUrlWithQueryParams(this.url);
1326
+ this.applyCsrfProtection();
1327
+ const fetchOptions = {
1328
+ ...this.requestOptions,
1329
+ method: this.method,
1330
+ };
1331
+ return !this.requestOptions.retries ? this.executeRequest(url, fetchOptions) : this.executeWithRetries(url, fetchOptions);
1332
+ }
1333
+ /**
1334
+ * Execute the request and parse the response as JSON
1335
+ *
1336
+ * @returns A promise that resolves to the parsed JSON data
1337
+ *
1338
+ * @example
1339
+ * const users = await request.getJson<User[]>();
1340
+ */
1341
+ async getJson() {
1342
+ const response = await this.getResponse();
1343
+ return response.getJson();
1344
+ }
1345
+ /**
1346
+ * Execute the request and get the response as text
1347
+ *
1348
+ * @returns A promise that resolves to the response text
1349
+ *
1350
+ * @example
1351
+ * const text = await request.getText();
1352
+ */
1353
+ async getText() {
1354
+ const response = await this.getResponse();
1355
+ return response.getText();
1356
+ }
1357
+ /**
1358
+ * Execute the request and get the response as a Blob
1359
+ *
1360
+ * @returns A promise that resolves to the response Blob
1361
+ *
1362
+ * @example
1363
+ * const blob = await request.getBlob();
1364
+ */
1365
+ async getBlob() {
1366
+ const response = await this.getResponse();
1367
+ return response.getBlob();
1368
+ }
1369
+ /**
1370
+ * Execute the request and get the response body as a ReadableStream
1371
+ *
1372
+ * @returns A promise that resolves to the response body stream
1373
+ *
1374
+ * @example
1375
+ * const stream = await request.getBody();
1376
+ */
1377
+ async getBody() {
1378
+ const response = await this.getResponse();
1379
+ return response.getBody();
1380
+ }
1381
+ /**
1382
+ * Execute the request and extract specific data using a selector function
1383
+ * If no selector is provided, returns the full JSON response.
1384
+ *
1385
+ * @param selector - Optional function to extract and transform data
1386
+ * @returns A promise that resolves to the selected data
1387
+ *
1388
+ * @example
1389
+ * // Get full response
1390
+ * const data = await request.getData();
1391
+ *
1392
+ * // Extract specific data
1393
+ * const users = await request.getData(data => data.results.users);
1394
+ */
1395
+ async getData(selector) {
1396
+ const response = await this.getResponse();
1397
+ return response.getData(selector);
1398
+ }
1399
+ /**
1400
+ * Apply CSRF protection headers based on configuration
1401
+ */
1402
+ applyCsrfProtection() {
1403
+ if (!this.autoApplyCsrfProtection)
1404
+ return;
1405
+ const config = Config.getInstance();
1406
+ // Apply anti-CSRF headers if enabled
1407
+ if (config.isAntiCsrfEnabled()) {
1408
+ this.withHeaders({
1409
+ "X-Requested-With": "XMLHttpRequest",
1410
+ });
1411
+ }
1412
+ // Apply global CSRF token if set
1413
+ const globalToken = config.getCsrfToken();
1414
+ if (globalToken) {
1415
+ const csrfHeaderName = config.getCsrfHeaderName();
1416
+ const hasLocalToken = this.hasHeader("X-CSRF-Token") || this.hasHeader(csrfHeaderName);
1417
+ if (!hasLocalToken) {
1418
+ this.withHeaders({
1419
+ [csrfHeaderName]: globalToken,
1420
+ });
1421
+ }
1422
+ }
1423
+ // Check for XSRF token in cookies and send it as a header
1424
+ if (config.isAutoXsrfEnabled() && typeof document !== "undefined") {
1425
+ const xsrfToken = CsrfUtils.getTokenFromCookie(config.getXsrfCookieName());
1426
+ if (xsrfToken && CsrfUtils.isValidToken(xsrfToken)) {
1427
+ const xsrfHeaderName = config.getXsrfHeaderName();
1428
+ const hasLocalToken = this.hasHeader("X-XSRF-TOKEN") || this.hasHeader(xsrfHeaderName);
1429
+ if (!hasLocalToken) {
1430
+ this.withHeaders({
1431
+ [xsrfHeaderName]: xsrfToken,
1432
+ });
1433
+ }
1434
+ }
1435
+ }
1436
+ }
1437
+ /**
1438
+ * Formats the URL with any query parameters
1439
+ * @param url The base URL
1440
+ * @returns The URL with query parameters appended
1441
+ */
1442
+ formatUrlWithQueryParams(url) {
1443
+ if (!this.queryParams.toString()) {
1444
+ return url;
1445
+ }
1446
+ try {
1447
+ // Try to use the URL constructor (works for absolute URLs)
1448
+ const urlObj = new URL(url);
1449
+ // Merge our query params with any that might be in the URL already
1450
+ this.queryParams.forEach((value, key) => {
1451
+ urlObj.searchParams.append(key, value);
1452
+ });
1453
+ return urlObj.toString();
1454
+ }
1455
+ catch (error) {
1456
+ // Handle relative URLs
1457
+ const hasExistingParams = url.includes("?");
1458
+ const separator = hasExistingParams ? "&" : "?";
1459
+ return `${url}${separator}${this.queryParams.toString()}`;
1460
+ }
1461
+ }
1462
+ /**
1463
+ * Executes a request with configured retry logic
1464
+ * @param url The formatted URL to send the request to
1465
+ * @param fetchOptions The fetch options to use
1466
+ * @returns A wrapped response object
1467
+ * @throws RequestError if the request fails after all retries
1468
+ */
1469
+ async executeWithRetries(url, fetchOptions) {
1470
+ const maxRetries = this.requestOptions.retries || 0;
1471
+ const method = typeof fetchOptions.method === "string" ? fetchOptions.method : "GET";
1472
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1473
+ try {
1474
+ return await this.executeRequest(url, fetchOptions);
1475
+ }
1476
+ catch (error) {
1477
+ const requestError = error instanceof RequestError ? error : RequestError.networkError(url, method, error instanceof Error ? error : new Error(String(error)));
1478
+ if (attempt >= maxRetries)
1479
+ throw requestError;
1480
+ if (this.requestOptions.onRetry) {
1481
+ await this.requestOptions.onRetry({ attempt: attempt + 1, error: requestError });
1482
+ }
1483
+ }
1484
+ }
1485
+ // This should never happen but is needed for type safety
1486
+ throw new RequestError(`Max retries reached`, url, method);
1487
+ }
1488
+ /**
1489
+ * Run request interceptors in order: global interceptors first, then per-request
1490
+ * @param configParam - The request configuration
1491
+ * @returns Modified config or a Response to short-circuit
1492
+ */
1493
+ async runRequestInterceptors(configParam) {
1494
+ const globalConfig = Config.getInstance();
1495
+ const allInterceptors = [...globalConfig.getRequestInterceptors(), ...this.requestInterceptors];
1496
+ let currentConfig = configParam;
1497
+ for (let i = 0; i < allInterceptors.length; i++) {
1498
+ try {
1499
+ const result = await allInterceptors[i](currentConfig);
1500
+ // If interceptor returns a Response, short-circuit
1501
+ if (result instanceof Response) {
1502
+ return result;
1503
+ }
1504
+ currentConfig = result;
1505
+ }
1506
+ catch (error) {
1507
+ const errorMessage = error instanceof Error ? error.message : String(error);
1508
+ // Use RequestError when we have request context
1509
+ throw new RequestError(`Interceptor failed: ${errorMessage}`, currentConfig.url, currentConfig.method);
1510
+ }
1511
+ }
1512
+ return currentConfig;
1513
+ }
1514
+ /**
1515
+ * Run response interceptors in reverse order: per-request interceptors first, then global in reverse
1516
+ * @param response - The response wrapper
1517
+ * @returns Modified response wrapper
1518
+ */
1519
+ async runResponseInterceptors(response) {
1520
+ const globalConfig = Config.getInstance();
1521
+ const globalInterceptors = globalConfig.getResponseInterceptors();
1522
+ // Per-request in order, then global in reverse
1523
+ const allInterceptors = [...this.responseInterceptors, ...globalInterceptors.reverse()];
1524
+ let currentResponse = response;
1525
+ for (let i = 0; i < allInterceptors.length; i++) {
1526
+ try {
1527
+ currentResponse = await allInterceptors[i](currentResponse);
1528
+ }
1529
+ catch (error) {
1530
+ const errorMessage = error instanceof Error ? error.message : String(error);
1531
+ // Use RequestError when we have response context (url/method from ResponseWrapper)
1532
+ if (currentResponse.url && currentResponse.method) {
1533
+ throw new RequestError(`Interceptor failed: ${errorMessage}`, currentResponse.url, currentResponse.method);
1534
+ }
1535
+ // Fallback to generic Error if no context available (shouldn't happen in practice)
1536
+ throw new Error(`Interceptor failed: ${errorMessage}`);
1537
+ }
1538
+ }
1539
+ return currentResponse;
1540
+ }
1541
+ /**
1542
+ * Run error interceptors in reverse order: per-request interceptors first, then global in reverse
1543
+ * @param error - The error that occurred
1544
+ * @returns Modified error or a ResponseWrapper to recover
1545
+ */
1546
+ async runErrorInterceptors(error) {
1547
+ const globalConfig = Config.getInstance();
1548
+ const globalInterceptors = globalConfig.getErrorInterceptors();
1549
+ // Per-request in order, then global in reverse
1550
+ const allInterceptors = [...this.errorInterceptors, ...globalInterceptors.reverse()];
1551
+ let currentError = error;
1552
+ for (let i = 0; i < allInterceptors.length; i++) {
1553
+ try {
1554
+ // If previous interceptor recovered with a response, stop processing
1555
+ if (currentError instanceof ResponseWrapper) {
1556
+ return currentError;
1557
+ }
1558
+ const result = await allInterceptors[i](currentError);
1559
+ currentError = result;
1560
+ }
1561
+ catch (interceptorError) {
1562
+ // If an error interceptor throws, that becomes the new error
1563
+ if (interceptorError instanceof RequestError) {
1564
+ currentError = interceptorError;
1565
+ }
1566
+ else {
1567
+ const errorMessage = interceptorError instanceof Error ? interceptorError.message : String(interceptorError);
1568
+ // Always wrap in RequestError when we have context
1569
+ if (currentError instanceof RequestError) {
1570
+ currentError = new RequestError(`Error interceptor ${i + 1} failed: ${errorMessage}`, currentError.url, currentError.method);
1571
+ }
1572
+ else if (currentError instanceof ResponseWrapper && currentError.url && currentError.method) {
1573
+ // If it's a ResponseWrapper, we can still create a proper RequestError from its context
1574
+ currentError = new RequestError(`Error interceptor ${i + 1} failed: ${errorMessage}`, currentError.url, currentError.method);
1575
+ }
1576
+ else {
1577
+ // Last resort: if we have no context at all, use the original error's context
1578
+ // This shouldn't happen in practice, but handle it gracefully
1579
+ const errorObj = interceptorError instanceof Error ? interceptorError : new Error(String(interceptorError));
1580
+ currentError = RequestError.networkError(error.url, error.method, errorObj);
1581
+ }
1582
+ }
1583
+ }
1584
+ }
1585
+ return currentError;
1586
+ }
1587
+ /**
1588
+ * Helper to create an abort signal with timeout support
1589
+ * Handles various AbortSignal API levels gracefully
1590
+ */
1591
+ createAbortSignal(timeoutMs, externalController) {
1592
+ let timeoutId;
1593
+ let timeoutController;
1594
+ let isTimeout = false;
1595
+ // No timeout - just use external controller
1596
+ if (!timeoutMs) {
1597
+ return {
1598
+ signal: externalController === null || externalController === void 0 ? void 0 : externalController.signal,
1599
+ cleanup: () => { },
1600
+ wasTimeout: () => false,
1601
+ };
1602
+ }
1603
+ // Check if AbortSignal.any() is available (modern browsers)
1604
+ const hasAbortSignalAny = typeof AbortSignal.any === "function";
1605
+ const hasAbortSignalTimeout = typeof AbortSignal.timeout === "function";
1606
+ // Create timeout signal
1607
+ const createTimeoutSignal = () => {
1608
+ if (hasAbortSignalTimeout) {
1609
+ const signal = AbortSignal.timeout(timeoutMs);
1610
+ signal.addEventListener("abort", () => (isTimeout = true), { once: true });
1611
+ return signal;
1612
+ }
1613
+ else {
1614
+ timeoutController = new AbortController();
1615
+ timeoutId = setTimeout(() => {
1616
+ isTimeout = true;
1617
+ timeoutController.abort();
1618
+ }, timeoutMs);
1619
+ return timeoutController.signal;
1620
+ }
1621
+ };
1622
+ const timeoutSignal = createTimeoutSignal();
1623
+ // Combine signals if we have both timeout and external controller
1624
+ const finalSignal = externalController && hasAbortSignalAny
1625
+ ? AbortSignal.any([externalController.signal, timeoutSignal])
1626
+ : externalController
1627
+ ? this.combineSignalsManually(externalController.signal, timeoutSignal)
1628
+ : timeoutSignal;
1629
+ return {
1630
+ signal: finalSignal,
1631
+ cleanup: () => {
1632
+ if (timeoutId !== undefined)
1633
+ clearTimeout(timeoutId);
1634
+ },
1635
+ wasTimeout: () => isTimeout,
1636
+ };
1637
+ }
1638
+ /**
1639
+ * Manually combine two abort signals for older environments
1640
+ * Returns the first signal and listens to the second
1641
+ */
1642
+ combineSignalsManually(signal1, signal2) {
1643
+ // If either is already aborted, use that one
1644
+ if (signal1.aborted)
1645
+ return signal1;
1646
+ if (signal2.aborted)
1647
+ return signal2;
1648
+ // Use a controller to create a combined signal
1649
+ const controller = new AbortController();
1650
+ const abort = () => controller.abort();
1651
+ signal1.addEventListener("abort", abort, { once: true });
1652
+ signal2.addEventListener("abort", abort, { once: true });
1653
+ return controller.signal;
1654
+ }
1655
+ /**
1656
+ * Convert fetchOptions to RequestConfig with proper typing
1657
+ */
1658
+ createRequestConfig(url, fetchOptions) {
1659
+ const method = typeof fetchOptions.method === "string" ? fetchOptions.method : "GET";
1660
+ const headers = this.getHeadersRecord();
1661
+ const extendedOptions = fetchOptions;
1662
+ return {
1663
+ url,
1664
+ method,
1665
+ headers,
1666
+ body: fetchOptions.body,
1667
+ signal: fetchOptions.signal || undefined,
1668
+ credentials: fetchOptions.credentials,
1669
+ mode: fetchOptions.mode,
1670
+ redirect: fetchOptions.redirect,
1671
+ referrer: fetchOptions.referrer,
1672
+ referrerPolicy: fetchOptions.referrerPolicy,
1673
+ keepalive: fetchOptions.keepalive,
1674
+ priority: extendedOptions.priority,
1675
+ };
1676
+ }
1677
+ /**
1678
+ * Apply interceptor results back to fetchOptions
1679
+ */
1680
+ applyRequestConfig(config, fetchOptions) {
1681
+ fetchOptions.headers = config.headers;
1682
+ if (config.body !== undefined) {
1683
+ fetchOptions.body = config.body;
1684
+ }
1685
+ }
1686
+ async executeRequest(url, fetchOptions) {
1687
+ const method = typeof fetchOptions.method === "string" ? fetchOptions.method : "GET";
1688
+ // Setup abort signal with timeout
1689
+ const abortSignal = this.createAbortSignal(this.requestOptions.timeout, this.abortController);
1690
+ try {
1691
+ // Run request interceptors before making the request
1692
+ const requestConfig = this.createRequestConfig(url, fetchOptions);
1693
+ const interceptorResult = await this.runRequestInterceptors(requestConfig);
1694
+ // If interceptor returned a Response, short-circuit and wrap it
1695
+ if (interceptorResult instanceof Response) {
1696
+ const wrappedResponse = new ResponseWrapper(interceptorResult, this.url, this.method);
1697
+ return await this.runResponseInterceptors(wrappedResponse);
1698
+ }
1699
+ // Update fetchOptions with interceptor modifications
1700
+ url = interceptorResult.url;
1701
+ this.validateUrl(url);
1702
+ this.applyRequestConfig(interceptorResult, fetchOptions);
1703
+ // Set the combined abort signal
1704
+ fetchOptions.signal = abortSignal.signal;
1705
+ // Execute fetch
1706
+ let response;
1707
+ try {
1708
+ response = await fetch(url, fetchOptions);
1709
+ }
1710
+ catch (error) {
1711
+ // Check if it's an abort error (DOMException in browsers, or AbortSignal abort)
1712
+ if (error instanceof DOMException && error.name === "AbortError") {
1713
+ if (abortSignal.wasTimeout()) {
1714
+ throw RequestError.timeout(url, method, this.requestOptions.timeout);
1715
+ }
1716
+ throw RequestError.abortError(url, method);
1717
+ }
1718
+ // Check for Node.js/undici TimeoutError
1719
+ const errorObj = error instanceof Error ? error : new Error(String(error));
1720
+ const errorName = errorObj.name;
1721
+ const errorMessage = errorObj.message.toLowerCase();
1722
+ // Detect timeout errors from Node.js/undici (TimeoutError)
1723
+ const isTimeoutError = errorName === "TimeoutError" || errorMessage.includes("timeout") || errorMessage.includes("aborted due to timeout") || abortSignal.wasTimeout();
1724
+ if (isTimeoutError && this.requestOptions.timeout) {
1725
+ throw RequestError.timeout(url, method, this.requestOptions.timeout);
1726
+ }
1727
+ throw RequestError.networkError(url, method, errorObj);
1728
+ }
1729
+ if (!response.ok) {
1730
+ throw RequestError.fromResponse(response, url, method);
1731
+ }
1732
+ const wrappedResponse = new ResponseWrapper(response, url, method);
1733
+ return await this.runResponseInterceptors(wrappedResponse);
1734
+ }
1735
+ catch (error) {
1736
+ // Convert to RequestError if needed
1737
+ let requestError;
1738
+ if (error instanceof RequestError) {
1739
+ requestError = error;
1740
+ }
1741
+ else {
1742
+ const errorObj = error instanceof Error ? error : new Error(String(error));
1743
+ requestError = RequestError.networkError(url, method, errorObj);
1744
+ }
1745
+ // Run error interceptors
1746
+ const interceptorResult = await this.runErrorInterceptors(requestError);
1747
+ // If error interceptor returned a ResponseWrapper, recover from error
1748
+ if (interceptorResult instanceof ResponseWrapper) {
1749
+ return interceptorResult;
1750
+ }
1751
+ throw interceptorResult;
1752
+ }
1753
+ finally {
1754
+ abortSignal.cleanup();
1755
+ }
1756
+ }
1757
+ }
1758
+
1759
+ /**
1760
+ * Base class for requests that can have a body (POST, PUT, PATCH)
1761
+ */
1762
+ class BodyRequest extends BaseRequest {
1763
+ constructor(url) {
1764
+ super(url);
1765
+ }
1766
+ /**
1767
+ * Sets the body of the request
1768
+ * @param body - The request body
1769
+ */
1770
+ withBody(body) {
1771
+ this.body = body;
1772
+ // Set body type and validate
1773
+ if (typeof body === "string") {
1774
+ this.bodyType = BodyType.STRING;
1775
+ this.setContentTypeIfNeeded("text/plain");
1776
+ }
1777
+ else if (body !== null &&
1778
+ typeof body === "object" &&
1779
+ !(body instanceof FormData || body instanceof Blob || body instanceof ArrayBuffer || body instanceof URLSearchParams || body instanceof ReadableStream)) {
1780
+ this.bodyType = BodyType.JSON;
1781
+ this.setContentTypeIfNeeded("application/json");
1782
+ // Validate JSON is stringifiable early
1783
+ try {
1784
+ JSON.stringify(body);
1785
+ }
1786
+ catch (error) {
1787
+ throw new RequestError(`Stringify failed: ${String(error)}`, this.url, this.method);
1788
+ }
1789
+ }
1790
+ else {
1791
+ this.bodyType = BodyType.BINARY;
1792
+ }
1793
+ return this;
1794
+ }
1795
+ /**
1796
+ * Sets a GraphQL query or mutation as the request body
1797
+ * Automatically formats the body as JSON and sets Content-Type to application/json
1798
+ *
1799
+ * @param query - The GraphQL query or mutation string
1800
+ * @param variables - Optional variables object to pass with the query
1801
+ * @returns The request instance for chaining
1802
+ *
1803
+ * @example
1804
+ * const request = new PostRequest('/graphql')
1805
+ * .withGraphQL('query { user(id: $id) { name email } }', { id: '123' });
1806
+ *
1807
+ * @example
1808
+ * const request = new PostRequest('/graphql')
1809
+ * .withGraphQL('mutation { createUser(name: $name) { id } }', { name: 'John' });
1810
+ */
1811
+ withGraphQL(query, variables) {
1812
+ if (typeof query !== "string" || query.trim().length === 0) {
1813
+ throw new RequestError("Invalid GraphQL query", this.url, this.method);
1814
+ }
1815
+ const graphQLBody = {
1816
+ query: query.trim(),
1817
+ };
1818
+ if (variables !== undefined) {
1819
+ if (typeof variables !== "object" || variables === null || Array.isArray(variables)) {
1820
+ throw new RequestError("Invalid GraphQL variables", this.url, this.method);
1821
+ }
1822
+ graphQLBody.variables = variables;
1823
+ }
1824
+ // Validate JSON is stringifiable early
1825
+ try {
1826
+ JSON.stringify(graphQLBody);
1827
+ }
1828
+ catch (error) {
1829
+ throw new RequestError(`Stringify failed: ${String(error)}`, this.url, this.method);
1830
+ }
1831
+ this.body = graphQLBody;
1832
+ this.bodyType = BodyType.JSON;
1833
+ this.setContentTypeIfNeeded("application/json");
1834
+ return this;
1835
+ }
1836
+ /**
1837
+ * Check if Content-Type header is already set (case-insensitive)
1838
+ */
1839
+ hasContentType() {
1840
+ const headers = this.requestOptions.headers;
1841
+ if (typeof headers === "object" && headers !== null) {
1842
+ const headersObj = headers;
1843
+ return Object.keys(headersObj).some(header => header.toLowerCase() === "content-type");
1844
+ }
1845
+ return false;
1846
+ }
1847
+ setContentTypeIfNeeded(contentType) {
1848
+ if (!this.hasContentType()) {
1849
+ this.withContentType(contentType);
1850
+ }
1851
+ }
1852
+ /**
1853
+ * Execute the request and return the ResponseWrapper
1854
+ * Overrides the base implementation to add body handling
1855
+ */
1856
+ async getResponse() {
1857
+ if (this.body !== undefined) {
1858
+ // Remove previous body if it exists
1859
+ if (this.requestOptions.body)
1860
+ delete this.requestOptions.body;
1861
+ // Process the body based on its type
1862
+ if (this.bodyType === BodyType.JSON) {
1863
+ this.requestOptions.body = JSON.stringify(this.body);
1864
+ }
1865
+ else {
1866
+ this.requestOptions.body = this.body;
1867
+ }
1868
+ }
1869
+ return super.getResponse();
1870
+ }
1871
+ }
1872
+
1873
+ /**
1874
+ * HTTP GET request implementation
1875
+ * Used for retrieving data from an API endpoint.
1876
+ *
1877
+ * @example
1878
+ * const request = new GetRequest('/api/users')
1879
+ * .withQueryParams({ id: '123' });
1880
+ * const data = await request.getData();
1881
+ */
1882
+ class GetRequest extends BaseRequest {
1883
+ constructor(url) {
1884
+ super(url);
1885
+ this.method = HttpMethod.GET;
1886
+ }
1887
+ }
1888
+ /**
1889
+ * HTTP HEAD request implementation
1890
+ * Similar to GET but returns only headers without a response body.
1891
+ *
1892
+ * @example
1893
+ * const request = new HeadRequest('/api/users/123');
1894
+ * const response = await request.getResponse();
1895
+ */
1896
+ class HeadRequest extends BaseRequest {
1897
+ constructor(url) {
1898
+ super(url);
1899
+ this.method = HttpMethod.HEAD;
1900
+ }
1901
+ }
1902
+ /**
1903
+ * HTTP OPTIONS request implementation
1904
+ * Used to describe the communication options for the target resource.
1905
+ *
1906
+ * @example
1907
+ * const request = new OptionsRequest('/api/users');
1908
+ * const response = await request.getResponse();
1909
+ */
1910
+ class OptionsRequest extends BaseRequest {
1911
+ constructor(url) {
1912
+ super(url);
1913
+ this.method = HttpMethod.OPTIONS;
1914
+ }
1915
+ }
1916
+ /**
1917
+ * HTTP DELETE request implementation
1918
+ * Used to delete a resource on the server.
1919
+ *
1920
+ * @example
1921
+ * const request = new DeleteRequest('/api/users/123');
1922
+ * await request.getData();
1923
+ */
1924
+ class DeleteRequest extends BaseRequest {
1925
+ constructor(url) {
1926
+ super(url);
1927
+ this.method = HttpMethod.DELETE;
1928
+ }
1929
+ }
1930
+ /**
1931
+ * HTTP POST request implementation
1932
+ * Used to create a new resource or submit data for processing.
1933
+ *
1934
+ * @example
1935
+ * const request = new PostRequest('/api/users')
1936
+ * .withJson({ name: 'John', email: 'john@example.com' });
1937
+ * const data = await request.getData();
1938
+ */
1939
+ class PostRequest extends BodyRequest {
1940
+ constructor(url) {
1941
+ super(url);
1942
+ this.method = HttpMethod.POST;
1943
+ }
1944
+ }
1945
+ /**
1946
+ * HTTP PUT request implementation
1947
+ * Used to replace or update an existing resource.
1948
+ *
1949
+ * @example
1950
+ * const request = new PutRequest('/api/users/123')
1951
+ * .withJson({ id: '123', name: 'John', email: 'john@example.com' });
1952
+ * const data = await request.getData();
1953
+ */
1954
+ class PutRequest extends BodyRequest {
1955
+ constructor(url) {
1956
+ super(url);
1957
+ this.method = HttpMethod.PUT;
1958
+ }
1959
+ }
1960
+ /**
1961
+ * HTTP PATCH request implementation
1962
+ * Used to apply partial modifications to a resource.
1963
+ *
1964
+ * @example
1965
+ * const request = new PatchRequest('/api/users/123')
1966
+ * .withJson({ email: 'new.email@example.com' });
1967
+ * const data = await request.getData();
1968
+ */
1969
+ class PatchRequest extends BodyRequest {
1970
+ constructor(url) {
1971
+ super(url);
1972
+ this.method = HttpMethod.PATCH;
1973
+ }
1974
+ }
1975
+
1976
+ /**
1977
+ * Create a GET request
1978
+ * Used for retrieving resources from the server.
1979
+ *
1980
+ * @param url - The URL to send the request to
1981
+ * @returns A new GET request instance
1982
+ *
1983
+ * @example
1984
+ * const request = get('/api/users')
1985
+ * .withQueryParams({ limit: 10 });
1986
+ * const users = await request.getData();
1987
+ */
1988
+ function get(url) {
1989
+ return new GetRequest(url);
1990
+ }
1991
+ /**
1992
+ * Create a POST request
1993
+ * Used for creating new resources or submitting data.
1994
+ *
1995
+ * @param url - The URL to send the request to
1996
+ * @returns A new POST request instance
1997
+ *
1998
+ * @example
1999
+ * const request = post('/api/users')
2000
+ * .withJson({ name: 'John', email: 'john@example.com' });
2001
+ * const newUser = await request.getData();
2002
+ */
2003
+ function post(url) {
2004
+ return new PostRequest(url);
2005
+ }
2006
+ /**
2007
+ * Create a PUT request
2008
+ * Used for replacing or updating an existing resource.
2009
+ *
2010
+ * @param url - The URL to send the request to
2011
+ * @returns A new PUT request instance
2012
+ *
2013
+ * @example
2014
+ * const request = put('/api/users/123')
2015
+ * .withJson({ name: 'John Updated', email: 'john@example.com' });
2016
+ * const updatedUser = await request.getData();
2017
+ */
2018
+ function put(url) {
2019
+ return new PutRequest(url);
2020
+ }
2021
+ /**
2022
+ * Create a DELETE request
2023
+ * Used for removing resources from the server.
2024
+ *
2025
+ * @param url - The URL to send the request to
2026
+ * @returns A new DELETE request instance
2027
+ *
2028
+ * @example
2029
+ * const request = del('/api/users/123');
2030
+ * await request.getData();
2031
+ */
2032
+ function del(url) {
2033
+ return new DeleteRequest(url);
2034
+ }
2035
+ /**
2036
+ * Create a PATCH request
2037
+ * Used for applying partial modifications to a resource.
2038
+ *
2039
+ * @param url - The URL to send the request to
2040
+ * @returns A new PATCH request instance
2041
+ *
2042
+ * @example
2043
+ * const request = patch('/api/users/123')
2044
+ * .withJson({ status: 'active' });
2045
+ * const patchedUser = await request.getData();
2046
+ */
2047
+ function patch(url) {
2048
+ return new PatchRequest(url);
2049
+ }
2050
+ /**
2051
+ * Create a HEAD request
2052
+ * Similar to GET but returns only headers without a body.
2053
+ *
2054
+ * @param url - The URL to send the request to
2055
+ * @returns A new HEAD request instance
2056
+ *
2057
+ * @example
2058
+ * const request = head('/api/users/123');
2059
+ * const response = await request.getResponse();
2060
+ * console.log(response.headers.get('Last-Modified'));
2061
+ */
2062
+ function head(url) {
2063
+ return new HeadRequest(url);
2064
+ }
2065
+ /**
2066
+ * Create an OPTIONS request
2067
+ * Used to describe the communication options for a resource.
2068
+ *
2069
+ * @param url - The URL to send the request to
2070
+ * @returns A new OPTIONS request instance
2071
+ *
2072
+ * @example
2073
+ * const request = options('/api/users');
2074
+ * const response = await request.getResponse();
2075
+ * console.log(response.headers.get('Allow'));
2076
+ */
2077
+ function options(url) {
2078
+ return new OptionsRequest(url);
2079
+ }
2080
+
2081
+ /**
2082
+ * Main API object for creating HTTP requests
2083
+ * Provides factory methods for all HTTP methods and access to global configuration.
2084
+ */
2085
+ const create = {
2086
+ get,
2087
+ post,
2088
+ put,
2089
+ del,
2090
+ patch,
2091
+ head,
2092
+ options,
2093
+ config: Config.getInstance(),
2094
+ };
2095
+
2096
+ export { CookieUtils, CredentialsPolicy, DeleteRequest, GetRequest, HeadRequest, HttpMethod, OptionsRequest, PatchRequest, PostRequest, PutRequest, RedirectMode, ReferrerPolicy, RequestError, RequestMode, RequestPriority, ResponseWrapper, SameSitePolicy, create as default };
2097
+ //# sourceMappingURL=index.esm.js.map