create-request 0.0.1-beta.11 → 0.0.1-beta.13

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