create-request 0.0.1-beta.8 → 1.0.0

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