create-request 0.0.1-beta.6 → 0.0.1-beta.7

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.
package/README.md CHANGED
@@ -14,6 +14,7 @@
14
14
  - 🔐 **Auth Helpers** - Simple methods for common authentication patterns
15
15
  - 📦 **Typed Responses** - Full TypeScript support for response data
16
16
  - 🚧 **Error Handling** - Detailed error info with custom error class
17
+ - 📝 **Response Caching** - Flexible caching system with multiple storage options
17
18
 
18
19
  ## Why create-request?
19
20
 
@@ -206,6 +207,39 @@ try {
206
207
  }
207
208
  ```
208
209
 
210
+ ### Caching Requests
211
+
212
+ ```typescript
213
+ import create, { createMemoryStorage, createLocalStorageStorage, createSessionStorageStorage } from 'create-request';
214
+
215
+ // Simple in-memory caching
216
+ const request = create.get()
217
+ .withCache({
218
+ storage: createMemoryStorage(),
219
+ ttl: 60000 // 1 minute in milliseconds
220
+ })
221
+ .sendTo('https://api.example.com/data');
222
+
223
+ // Using localStorage storage
224
+ const request = create.get()
225
+ .withCache({
226
+ storage: createLocalStorageStorage(),
227
+ ttl: 5 * 60 * 1000, // 5 minutes
228
+ maxSize: '1MB',
229
+ keyPrefix: 'user-data'
230
+ })
231
+ .sendTo('https://api.example.com/users');
232
+
233
+ // Using sessionStorage for per-session caching
234
+ const request = create.get()
235
+ .withCache({
236
+ storage: createSessionStorageStorage(),
237
+ maxEntries: 50,
238
+ varyByHeaders: ['x-api-version']
239
+ })
240
+ .sendTo('https://api.example.com/data');
241
+ ```
242
+
209
243
  ## Advanced Usage
210
244
 
211
245
  ### Request Reuse
@@ -277,6 +311,117 @@ request.sendTo('https://api.example.com/data')
277
311
  });
278
312
  ```
279
313
 
314
+ ### Advanced Caching
315
+
316
+ ```typescript
317
+ import create, { createMemoryStorage, StorageProvider } from 'create-request';
318
+
319
+ // Custom key generation
320
+ const request = create.get()
321
+ .withCache({
322
+ storage: createMemoryStorage(),
323
+ keyGenerator: (url, method, headers) => {
324
+ // Include user ID from authorization header in the cache key
325
+ const authHeader = headers?.['Authorization'] || '';
326
+ const userId = extractUserIdFromToken(authHeader);
327
+ return `${method}:${url}:${userId}`;
328
+ }
329
+ });
330
+
331
+ // Simple custom storage provider example
332
+ const customStorage: StorageProvider = {
333
+ get(key: string): string | null {
334
+ // Get from your custom storage
335
+ return localStorage.getItem(`my-app:${key}`);
336
+ },
337
+ set(key: string, value: string): void {
338
+ // Set in your custom storage
339
+ localStorage.setItem(`my-app:${key}`, value);
340
+ },
341
+ has(key: string): boolean {
342
+ // Check if exists in your custom storage
343
+ return localStorage.getItem(`my-app:${key}`) !== null;
344
+ },
345
+ delete(key: string): void {
346
+ // Remove from your custom storage
347
+ localStorage.removeItem(`my-app:${key}`);
348
+ },
349
+ clear(): void {
350
+ // Clear all items from your namespace
351
+ Object.keys(localStorage)
352
+ .filter(key => key.startsWith('my-app:'))
353
+ .forEach(key => localStorage.removeItem(key));
354
+ }
355
+ };
356
+
357
+ const request = create.get()
358
+ .withCache({ storage: customStorage })
359
+ .sendTo('https://api.example.com/data');
360
+ ```
361
+
362
+ ### Implementing Custom Storage Providers
363
+
364
+ You can create your own storage provider by implementing the `StorageProvider` interface:
365
+
366
+ ```typescript
367
+ import { StorageProvider } from 'create-request';
368
+
369
+ // Create a simple custom storage provider
370
+ export function createNamespacedStorage(namespace: string): StorageProvider {
371
+ return {
372
+ get(key: string): string | null {
373
+ return localStorage.getItem(`${namespace}:${key}`);
374
+ },
375
+
376
+ set(key: string, value: string): void {
377
+ try {
378
+ localStorage.setItem(`${namespace}:${key}`, value);
379
+ } catch (e) {
380
+ // Handle quota exceeded errors
381
+ console.warn("Storage quota exceeded, clearing cache");
382
+ this.clear();
383
+ try {
384
+ localStorage.setItem(`${namespace}:${key}`, value);
385
+ } catch (e) {
386
+ console.error("Failed to set cache item even after clearing cache");
387
+ }
388
+ }
389
+ },
390
+
391
+ has(key: string): boolean {
392
+ return localStorage.getItem(`${namespace}:${key}`) !== null;
393
+ },
394
+
395
+ delete(key: string): void {
396
+ localStorage.removeItem(`${namespace}:${key}`);
397
+ },
398
+
399
+ clear(): void {
400
+ // Only clear items in our namespace
401
+ Object.keys(localStorage)
402
+ .filter(key => key.startsWith(`${namespace}:`))
403
+ .forEach(key => localStorage.removeItem(key));
404
+ }
405
+ };
406
+ }
407
+
408
+ // Usage:
409
+ const request = create.get()
410
+ .withCache({
411
+ storage: createNamespacedStorage('api-cache'),
412
+ ttl: 24 * 60 * 60 * 1000 // 24 hours
413
+ })
414
+ .sendTo('https://api.example.com/data');
415
+ ```
416
+
417
+ With custom storage providers, you can integrate with any storage system, such as:
418
+
419
+ - Custom browser storage solutions
420
+ - Memory caches with expiration policies
421
+ - Session-based storage mechanisms
422
+ - Encrypted storage providers
423
+ - Third-party storage libraries
424
+
280
425
  ## CSRF Protection
281
426
 
282
427
  ### Global CSRF Configuration
@@ -1,12 +1,16 @@
1
1
  import { type HttpMethod, type RedirectMode, type RequestMode, type RequestPriority, CredentialsPolicy } from "./enums";
2
2
  import { type ResponsePromise } from "./ResponseWrapper";
3
3
  import type { RequestOptions, RetryCallback, CookiesRecord, CookieOptions } from "./types";
4
+ import type { CacheOptions } from "./types/cache";
5
+ import { CacheManager } from "./utils/CacheManager";
4
6
  export declare abstract class BaseRequest {
5
7
  protected abstract method: HttpMethod;
6
8
  protected requestOptions: RequestOptions;
7
9
  protected abortController?: AbortController;
8
10
  protected queryParams: URLSearchParams;
9
11
  protected autoApplyCsrfProtection: boolean;
12
+ protected cacheManager?: CacheManager;
13
+ protected cacheEnabled: boolean;
10
14
  constructor();
11
15
  withHeaders(headers: Record<string, string>): this;
12
16
  withTimeout(timeout: number): this;
@@ -135,6 +139,12 @@ export declare abstract class BaseRequest {
135
139
  * @returns The instance for chaining
136
140
  */
137
141
  withAntiCsrfHeaders(): this;
142
+ /**
143
+ * Enables caching for the request with the specified options
144
+ * @param options The cache configuration options
145
+ * @returns The instance for chaining
146
+ */
147
+ withCache(options?: CacheOptions): this;
138
148
  /**
139
149
  * Send the request to the specified URL
140
150
  * @param url The URL to send the request to
@@ -154,4 +164,11 @@ export declare abstract class BaseRequest {
154
164
  */
155
165
  private executeWithRetries;
156
166
  private executeRequest;
167
+ /**
168
+ * Executes a request using cache when possible
169
+ * @param url The formatted URL to send the request to
170
+ * @param fetchOptions The fetch options to use
171
+ * @param cacheKey The cache key for this request
172
+ */
173
+ private executeWithCache;
157
174
  }
@@ -6,6 +6,10 @@ export { RequestError } from "./RequestError";
6
6
  export { ResponseWrapper } from "./ResponseWrapper";
7
7
  export { CookieUtils } from "./utils/CookieUtils";
8
8
  export { GetRequest, PostRequest, PutRequest, DeleteRequest, PatchRequest, HeadRequest, OptionsRequest } from "./requestMethods";
9
+ export { createMemoryStorage, createLocalStorageStorage, createSessionStorageStorage } from "./utils/CacheStorage";
10
+ export { CacheManager } from "./utils/CacheManager";
11
+ export type { CacheOptions } from "./types/cache";
12
+ export type { StorageProvider } from "./utils/StorageProvider";
9
13
  declare const create: {
10
14
  readonly get: typeof get;
11
15
  readonly post: typeof post;
@@ -233,6 +233,331 @@ class ResponseWrapper {
233
233
  }
234
234
  }
235
235
 
236
+ /**
237
+ * Parse size strings like "10MB" into bytes
238
+ * @param size Size as number or string with units
239
+ */
240
+ function parseSize(size) {
241
+ var _a;
242
+ if (typeof size === "number")
243
+ return size;
244
+ const match = size.match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB)?$/i);
245
+ if (!match)
246
+ return 0;
247
+ const num = parseFloat(match[1]);
248
+ const unit = (_a = match[2]) === null || _a === void 0 ? void 0 : _a.toUpperCase();
249
+ switch (unit) {
250
+ case "KB":
251
+ return num * 1024;
252
+ case "MB":
253
+ return num * 1024 * 1024;
254
+ case "GB":
255
+ return num * 1024 * 1024 * 1024;
256
+ default:
257
+ return num;
258
+ }
259
+ }
260
+ /**
261
+ * Create a memory storage provider using Map
262
+ */
263
+ function createMemoryStorage() {
264
+ const store = new Map();
265
+ return {
266
+ get(key) {
267
+ return store.get(key) || null;
268
+ },
269
+ set(key, value) {
270
+ store.set(key, value);
271
+ },
272
+ has(key) {
273
+ return store.has(key);
274
+ },
275
+ delete(key) {
276
+ store.delete(key);
277
+ },
278
+ clear() {
279
+ store.clear();
280
+ },
281
+ };
282
+ }
283
+ /**
284
+ * Create a localStorage-based storage provider
285
+ */
286
+ function createLocalStorageStorage() {
287
+ return {
288
+ get(key) {
289
+ if (typeof localStorage === "undefined")
290
+ return null;
291
+ return localStorage.getItem(key);
292
+ },
293
+ set(key, value) {
294
+ if (typeof localStorage === "undefined")
295
+ return;
296
+ try {
297
+ localStorage.setItem(key, value);
298
+ }
299
+ catch (e) {
300
+ // Handle quota exceeded errors
301
+ console.warn("Cache storage quota exceeded, clearing cache");
302
+ void this.clear();
303
+ try {
304
+ localStorage.setItem(key, value);
305
+ }
306
+ catch (e) {
307
+ console.error("Failed to set cache item even after clearing cache");
308
+ }
309
+ }
310
+ },
311
+ has(key) {
312
+ if (typeof localStorage === "undefined")
313
+ return false;
314
+ return localStorage.getItem(key) !== null;
315
+ },
316
+ delete(key) {
317
+ if (typeof localStorage === "undefined")
318
+ return;
319
+ localStorage.removeItem(key);
320
+ },
321
+ clear() {
322
+ if (typeof localStorage === "undefined")
323
+ return;
324
+ localStorage.clear();
325
+ },
326
+ };
327
+ }
328
+ /**
329
+ * Create a sessionStorage-based storage provider
330
+ */
331
+ function createSessionStorageStorage() {
332
+ return {
333
+ get(key) {
334
+ if (typeof sessionStorage === "undefined")
335
+ return null;
336
+ return sessionStorage.getItem(key);
337
+ },
338
+ set(key, value) {
339
+ if (typeof sessionStorage === "undefined")
340
+ return;
341
+ try {
342
+ sessionStorage.setItem(key, value);
343
+ }
344
+ catch (e) {
345
+ // Handle quota exceeded errors
346
+ console.warn("Cache storage quota exceeded, clearing cache");
347
+ void this.clear();
348
+ try {
349
+ sessionStorage.setItem(key, value);
350
+ }
351
+ catch (e) {
352
+ console.error("Failed to set cache item even after clearing cache");
353
+ }
354
+ }
355
+ },
356
+ has(key) {
357
+ if (typeof sessionStorage === "undefined")
358
+ return false;
359
+ return sessionStorage.getItem(key) !== null;
360
+ },
361
+ delete(key) {
362
+ if (typeof sessionStorage === "undefined")
363
+ return;
364
+ sessionStorage.removeItem(key);
365
+ },
366
+ clear() {
367
+ if (typeof sessionStorage === "undefined")
368
+ return;
369
+ sessionStorage.clear();
370
+ },
371
+ };
372
+ }
373
+ /**
374
+ * Create a storage provider from a cache options object
375
+ */
376
+ function createStorageProvider(options) {
377
+ const { storage } = options || {};
378
+ if (!storage) {
379
+ // Default to memory storage
380
+ return createMemoryStorage();
381
+ }
382
+ // Check if storage is already a StorageProvider
383
+ if (typeof storage.get === "function" && typeof storage.set === "function") {
384
+ return storage;
385
+ }
386
+ // Check if it's localStorage or sessionStorage
387
+ if (storage === localStorage) {
388
+ return createLocalStorageStorage();
389
+ }
390
+ if (storage === sessionStorage) {
391
+ return createSessionStorageStorage();
392
+ }
393
+ // Check if it's a Map
394
+ if (storage instanceof Map) {
395
+ return {
396
+ get(key) {
397
+ return storage.get(key) || null;
398
+ },
399
+ set(key, value, _ttl) {
400
+ storage.set(key, value);
401
+ },
402
+ has(key) {
403
+ return storage.has(key);
404
+ },
405
+ delete(key) {
406
+ storage.delete(key);
407
+ },
408
+ clear() {
409
+ storage.clear();
410
+ },
411
+ };
412
+ }
413
+ // Default to memory storage
414
+ console.warn("Unsupported storage type, falling back to memory storage");
415
+ return createMemoryStorage();
416
+ }
417
+
418
+ /**
419
+ * Cache manager that handles storage operations
420
+ */
421
+ class CacheManager {
422
+ constructor(options = {}) {
423
+ this.entryCount = 0;
424
+ this.totalSize = 0;
425
+ this.options = options;
426
+ this.storage = createStorageProvider(options);
427
+ }
428
+ /**
429
+ * Generate a cache key based on request details
430
+ */
431
+ generateKey(url, method, headers, body) {
432
+ // Use custom key generator if provided
433
+ if (this.options.keyGenerator) {
434
+ const customKey = this.options.keyGenerator(url, method, headers, body);
435
+ return this.options.keyPrefix ? `${this.options.keyPrefix}:${customKey}` : customKey;
436
+ }
437
+ // Default key generation
438
+ let key = `${method}:${url}`;
439
+ // Add vary headers to key if specified
440
+ if (this.options.varyByHeaders && headers) {
441
+ const varyHeaders = {};
442
+ this.options.varyByHeaders.forEach(header => {
443
+ const normalizedHeader = header.toLowerCase();
444
+ // Find the header in case-insensitive way
445
+ Object.keys(headers).forEach(h => {
446
+ if (h.toLowerCase() === normalizedHeader) {
447
+ varyHeaders[normalizedHeader] = headers[h];
448
+ }
449
+ });
450
+ });
451
+ if (Object.keys(varyHeaders).length > 0) {
452
+ key += `:${JSON.stringify(varyHeaders)}`;
453
+ }
454
+ }
455
+ // Include body in cache key for non-GET requests if it's a simple value
456
+ if (method !== "GET" && body !== undefined && body !== null) {
457
+ try {
458
+ if (typeof body === "string" || typeof body === "number" || typeof body === "boolean") {
459
+ key += `:${String(body)}`;
460
+ }
461
+ else if (typeof body === "object") {
462
+ key += `:${JSON.stringify(body)}`;
463
+ }
464
+ }
465
+ catch (e) {
466
+ console.warn("Failed to stringify request body for cache key");
467
+ }
468
+ }
469
+ // Add prefix if specified
470
+ return this.options.keyPrefix ? `${this.options.keyPrefix}:${key}` : key;
471
+ }
472
+ /**
473
+ * Get a value from cache
474
+ */
475
+ async get(key) {
476
+ const value = await Promise.resolve(this.storage.get(key));
477
+ if (!value)
478
+ return null;
479
+ try {
480
+ const entry = JSON.parse(value);
481
+ // Check if the entry has expired
482
+ if (entry.expiry && entry.expiry < Date.now()) {
483
+ await Promise.resolve(this.storage.delete(key));
484
+ return null;
485
+ }
486
+ return entry;
487
+ }
488
+ catch (e) {
489
+ // If we can't parse the cached value, remove it
490
+ await Promise.resolve(this.storage.delete(key));
491
+ return null;
492
+ }
493
+ }
494
+ /**
495
+ * Set a value in cache
496
+ */
497
+ async set(key, value, headers) {
498
+ const now = Date.now();
499
+ const ttl = this.options.ttl;
500
+ const entry = {
501
+ value,
502
+ timestamp: now,
503
+ expiry: ttl ? now + ttl : null,
504
+ headers,
505
+ };
506
+ const serialized = JSON.stringify(entry);
507
+ // Check size constraints if applicable
508
+ if (this.options.maxSize) {
509
+ const maxBytes = parseSize(this.options.maxSize);
510
+ const entrySize = new TextEncoder().encode(serialized).length;
511
+ // If a single entry exceeds the max size, reject it without clearing the cache
512
+ if (entrySize > maxBytes) {
513
+ console.warn(`Cache entry size (${entrySize} bytes) exceeds maximum cache size (${maxBytes} bytes)`);
514
+ return;
515
+ }
516
+ // Only clear if adding this entry would exceed the total limit
517
+ if (this.totalSize + entrySize > maxBytes) {
518
+ // Simple strategy: clear everything
519
+ // A more sophisticated strategy would remove oldest entries first
520
+ await Promise.resolve(this.storage.clear());
521
+ this.totalSize = 0;
522
+ this.entryCount = 0;
523
+ }
524
+ this.totalSize += entrySize;
525
+ }
526
+ // Check max entries constraint
527
+ if (this.options.maxEntries) {
528
+ this.entryCount++;
529
+ if (this.entryCount > this.options.maxEntries) {
530
+ // Simple strategy: clear everything
531
+ // A more sophisticated strategy would use LRU cache
532
+ await Promise.resolve(this.storage.clear());
533
+ this.entryCount = 1;
534
+ }
535
+ }
536
+ // Pass ttl to storage provider if available
537
+ await Promise.resolve(this.storage.set(key, serialized, ttl));
538
+ }
539
+ /**
540
+ * Check if a key exists in the cache
541
+ */
542
+ async has(key) {
543
+ return Promise.resolve(this.storage.has(key));
544
+ }
545
+ /**
546
+ * Delete a key from the cache
547
+ */
548
+ async delete(key) {
549
+ return Promise.resolve(this.storage.delete(key));
550
+ }
551
+ /**
552
+ * Clear the entire cache
553
+ */
554
+ async clear() {
555
+ this.entryCount = 0;
556
+ this.totalSize = 0;
557
+ return Promise.resolve(this.storage.clear());
558
+ }
559
+ }
560
+
236
561
  /**
237
562
  * Global configuration for create-request
238
563
  */
@@ -446,6 +771,7 @@ class BaseRequest {
446
771
  };
447
772
  this.queryParams = new URLSearchParams();
448
773
  this.autoApplyCsrfProtection = true;
774
+ this.cacheEnabled = false;
449
775
  }
450
776
  withHeaders(headers) {
451
777
  this.requestOptions.headers = {
@@ -708,6 +1034,16 @@ class BaseRequest {
708
1034
  "X-Requested-With": "XMLHttpRequest",
709
1035
  });
710
1036
  }
1037
+ /**
1038
+ * Enables caching for the request with the specified options
1039
+ * @param options The cache configuration options
1040
+ * @returns The instance for chaining
1041
+ */
1042
+ withCache(options = {}) {
1043
+ this.cacheEnabled = true;
1044
+ this.cacheManager = new CacheManager(options);
1045
+ return this;
1046
+ }
711
1047
  /**
712
1048
  * Send the request to the specified URL
713
1049
  * @param url The URL to send the request to
@@ -757,8 +1093,15 @@ class BaseRequest {
757
1093
  ...this.requestOptions,
758
1094
  method: this.method,
759
1095
  };
760
- // Create the base promise
761
- const basePromise = !this.requestOptions.retries ? this.executeRequest(url, fetchOptions) : this.executeWithRetries(url, fetchOptions);
1096
+ // Create the base promise - handle caching if enabled
1097
+ let basePromise;
1098
+ if (this.cacheEnabled && this.cacheManager) {
1099
+ const cacheKey = this.cacheManager.generateKey(url, this.method, this.requestOptions.headers, this.requestOptions.body);
1100
+ basePromise = this.executeWithCache(url, fetchOptions, cacheKey);
1101
+ }
1102
+ else {
1103
+ basePromise = !this.requestOptions.retries ? this.executeRequest(url, fetchOptions) : this.executeWithRetries(url, fetchOptions);
1104
+ }
762
1105
  const responsePromise = basePromise;
763
1106
  // Attach convenience methods that begin processing immediately
764
1107
  responsePromise.getJson = async () => {
@@ -897,6 +1240,48 @@ class BaseRequest {
897
1240
  }
898
1241
  }
899
1242
  }
1243
+ /**
1244
+ * Executes a request using cache when possible
1245
+ * @param url The formatted URL to send the request to
1246
+ * @param fetchOptions The fetch options to use
1247
+ * @param cacheKey The cache key for this request
1248
+ */
1249
+ async executeWithCache(url, fetchOptions, cacheKey) {
1250
+ // Try to get from cache first
1251
+ if (this.cacheManager) {
1252
+ const cachedEntry = await this.cacheManager.get(cacheKey);
1253
+ if (cachedEntry) {
1254
+ // Create a synthetic Response object from cached data
1255
+ const cachedResponse = new Response(JSON.stringify(cachedEntry.value), {
1256
+ status: 200,
1257
+ headers: new Headers(cachedEntry.headers || {}),
1258
+ });
1259
+ return new ResponseWrapper(cachedResponse);
1260
+ }
1261
+ }
1262
+ // Not in cache, make the actual request
1263
+ const response = await (!this.requestOptions.retries ? this.executeRequest(url, fetchOptions) : this.executeWithRetries(url, fetchOptions));
1264
+ // Cache the successful response if caching is enabled
1265
+ // Only cache successful responses
1266
+ if (response.ok && this.cacheManager) {
1267
+ // Clone the response so we don't consume it
1268
+ try {
1269
+ const responseData = await response.getJson();
1270
+ // Extract headers to store in cache
1271
+ const headers = {};
1272
+ response.headers.forEach((value, key) => {
1273
+ headers[key] = value;
1274
+ });
1275
+ // Store in cache
1276
+ await this.cacheManager.set(cacheKey, responseData, headers);
1277
+ }
1278
+ catch (error) {
1279
+ // Failed to cache, but we can still return the response
1280
+ console.warn("Failed to cache response:", error);
1281
+ }
1282
+ }
1283
+ return response;
1284
+ }
900
1285
  }
901
1286
 
902
1287
  /**
@@ -1062,4 +1447,4 @@ const create = {
1062
1447
  config: Config.getInstance(),
1063
1448
  };
1064
1449
 
1065
- export { CacheMode, CookieUtils, CredentialsPolicy, DeleteRequest, GetRequest, HeadRequest, HttpMethod, OptionsRequest, PatchRequest, PostRequest, PutRequest, RedirectMode, RequestError, RequestMode, RequestPriority, ResponseWrapper, SameSitePolicy, create as default };
1450
+ export { CacheManager, CacheMode, CookieUtils, CredentialsPolicy, DeleteRequest, GetRequest, HeadRequest, HttpMethod, OptionsRequest, PatchRequest, PostRequest, PutRequest, RedirectMode, RequestError, RequestMode, RequestPriority, ResponseWrapper, SameSitePolicy, createLocalStorageStorage, createMemoryStorage, createSessionStorageStorage, create as default };
@@ -239,6 +239,331 @@
239
239
  }
240
240
  }
241
241
 
242
+ /**
243
+ * Parse size strings like "10MB" into bytes
244
+ * @param size Size as number or string with units
245
+ */
246
+ function parseSize(size) {
247
+ var _a;
248
+ if (typeof size === "number")
249
+ return size;
250
+ const match = size.match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB)?$/i);
251
+ if (!match)
252
+ return 0;
253
+ const num = parseFloat(match[1]);
254
+ const unit = (_a = match[2]) === null || _a === void 0 ? void 0 : _a.toUpperCase();
255
+ switch (unit) {
256
+ case "KB":
257
+ return num * 1024;
258
+ case "MB":
259
+ return num * 1024 * 1024;
260
+ case "GB":
261
+ return num * 1024 * 1024 * 1024;
262
+ default:
263
+ return num;
264
+ }
265
+ }
266
+ /**
267
+ * Create a memory storage provider using Map
268
+ */
269
+ function createMemoryStorage() {
270
+ const store = new Map();
271
+ return {
272
+ get(key) {
273
+ return store.get(key) || null;
274
+ },
275
+ set(key, value) {
276
+ store.set(key, value);
277
+ },
278
+ has(key) {
279
+ return store.has(key);
280
+ },
281
+ delete(key) {
282
+ store.delete(key);
283
+ },
284
+ clear() {
285
+ store.clear();
286
+ },
287
+ };
288
+ }
289
+ /**
290
+ * Create a localStorage-based storage provider
291
+ */
292
+ function createLocalStorageStorage() {
293
+ return {
294
+ get(key) {
295
+ if (typeof localStorage === "undefined")
296
+ return null;
297
+ return localStorage.getItem(key);
298
+ },
299
+ set(key, value) {
300
+ if (typeof localStorage === "undefined")
301
+ return;
302
+ try {
303
+ localStorage.setItem(key, value);
304
+ }
305
+ catch (e) {
306
+ // Handle quota exceeded errors
307
+ console.warn("Cache storage quota exceeded, clearing cache");
308
+ void this.clear();
309
+ try {
310
+ localStorage.setItem(key, value);
311
+ }
312
+ catch (e) {
313
+ console.error("Failed to set cache item even after clearing cache");
314
+ }
315
+ }
316
+ },
317
+ has(key) {
318
+ if (typeof localStorage === "undefined")
319
+ return false;
320
+ return localStorage.getItem(key) !== null;
321
+ },
322
+ delete(key) {
323
+ if (typeof localStorage === "undefined")
324
+ return;
325
+ localStorage.removeItem(key);
326
+ },
327
+ clear() {
328
+ if (typeof localStorage === "undefined")
329
+ return;
330
+ localStorage.clear();
331
+ },
332
+ };
333
+ }
334
+ /**
335
+ * Create a sessionStorage-based storage provider
336
+ */
337
+ function createSessionStorageStorage() {
338
+ return {
339
+ get(key) {
340
+ if (typeof sessionStorage === "undefined")
341
+ return null;
342
+ return sessionStorage.getItem(key);
343
+ },
344
+ set(key, value) {
345
+ if (typeof sessionStorage === "undefined")
346
+ return;
347
+ try {
348
+ sessionStorage.setItem(key, value);
349
+ }
350
+ catch (e) {
351
+ // Handle quota exceeded errors
352
+ console.warn("Cache storage quota exceeded, clearing cache");
353
+ void this.clear();
354
+ try {
355
+ sessionStorage.setItem(key, value);
356
+ }
357
+ catch (e) {
358
+ console.error("Failed to set cache item even after clearing cache");
359
+ }
360
+ }
361
+ },
362
+ has(key) {
363
+ if (typeof sessionStorage === "undefined")
364
+ return false;
365
+ return sessionStorage.getItem(key) !== null;
366
+ },
367
+ delete(key) {
368
+ if (typeof sessionStorage === "undefined")
369
+ return;
370
+ sessionStorage.removeItem(key);
371
+ },
372
+ clear() {
373
+ if (typeof sessionStorage === "undefined")
374
+ return;
375
+ sessionStorage.clear();
376
+ },
377
+ };
378
+ }
379
+ /**
380
+ * Create a storage provider from a cache options object
381
+ */
382
+ function createStorageProvider(options) {
383
+ const { storage } = options || {};
384
+ if (!storage) {
385
+ // Default to memory storage
386
+ return createMemoryStorage();
387
+ }
388
+ // Check if storage is already a StorageProvider
389
+ if (typeof storage.get === "function" && typeof storage.set === "function") {
390
+ return storage;
391
+ }
392
+ // Check if it's localStorage or sessionStorage
393
+ if (storage === localStorage) {
394
+ return createLocalStorageStorage();
395
+ }
396
+ if (storage === sessionStorage) {
397
+ return createSessionStorageStorage();
398
+ }
399
+ // Check if it's a Map
400
+ if (storage instanceof Map) {
401
+ return {
402
+ get(key) {
403
+ return storage.get(key) || null;
404
+ },
405
+ set(key, value, _ttl) {
406
+ storage.set(key, value);
407
+ },
408
+ has(key) {
409
+ return storage.has(key);
410
+ },
411
+ delete(key) {
412
+ storage.delete(key);
413
+ },
414
+ clear() {
415
+ storage.clear();
416
+ },
417
+ };
418
+ }
419
+ // Default to memory storage
420
+ console.warn("Unsupported storage type, falling back to memory storage");
421
+ return createMemoryStorage();
422
+ }
423
+
424
+ /**
425
+ * Cache manager that handles storage operations
426
+ */
427
+ class CacheManager {
428
+ constructor(options = {}) {
429
+ this.entryCount = 0;
430
+ this.totalSize = 0;
431
+ this.options = options;
432
+ this.storage = createStorageProvider(options);
433
+ }
434
+ /**
435
+ * Generate a cache key based on request details
436
+ */
437
+ generateKey(url, method, headers, body) {
438
+ // Use custom key generator if provided
439
+ if (this.options.keyGenerator) {
440
+ const customKey = this.options.keyGenerator(url, method, headers, body);
441
+ return this.options.keyPrefix ? `${this.options.keyPrefix}:${customKey}` : customKey;
442
+ }
443
+ // Default key generation
444
+ let key = `${method}:${url}`;
445
+ // Add vary headers to key if specified
446
+ if (this.options.varyByHeaders && headers) {
447
+ const varyHeaders = {};
448
+ this.options.varyByHeaders.forEach(header => {
449
+ const normalizedHeader = header.toLowerCase();
450
+ // Find the header in case-insensitive way
451
+ Object.keys(headers).forEach(h => {
452
+ if (h.toLowerCase() === normalizedHeader) {
453
+ varyHeaders[normalizedHeader] = headers[h];
454
+ }
455
+ });
456
+ });
457
+ if (Object.keys(varyHeaders).length > 0) {
458
+ key += `:${JSON.stringify(varyHeaders)}`;
459
+ }
460
+ }
461
+ // Include body in cache key for non-GET requests if it's a simple value
462
+ if (method !== "GET" && body !== undefined && body !== null) {
463
+ try {
464
+ if (typeof body === "string" || typeof body === "number" || typeof body === "boolean") {
465
+ key += `:${String(body)}`;
466
+ }
467
+ else if (typeof body === "object") {
468
+ key += `:${JSON.stringify(body)}`;
469
+ }
470
+ }
471
+ catch (e) {
472
+ console.warn("Failed to stringify request body for cache key");
473
+ }
474
+ }
475
+ // Add prefix if specified
476
+ return this.options.keyPrefix ? `${this.options.keyPrefix}:${key}` : key;
477
+ }
478
+ /**
479
+ * Get a value from cache
480
+ */
481
+ async get(key) {
482
+ const value = await Promise.resolve(this.storage.get(key));
483
+ if (!value)
484
+ return null;
485
+ try {
486
+ const entry = JSON.parse(value);
487
+ // Check if the entry has expired
488
+ if (entry.expiry && entry.expiry < Date.now()) {
489
+ await Promise.resolve(this.storage.delete(key));
490
+ return null;
491
+ }
492
+ return entry;
493
+ }
494
+ catch (e) {
495
+ // If we can't parse the cached value, remove it
496
+ await Promise.resolve(this.storage.delete(key));
497
+ return null;
498
+ }
499
+ }
500
+ /**
501
+ * Set a value in cache
502
+ */
503
+ async set(key, value, headers) {
504
+ const now = Date.now();
505
+ const ttl = this.options.ttl;
506
+ const entry = {
507
+ value,
508
+ timestamp: now,
509
+ expiry: ttl ? now + ttl : null,
510
+ headers,
511
+ };
512
+ const serialized = JSON.stringify(entry);
513
+ // Check size constraints if applicable
514
+ if (this.options.maxSize) {
515
+ const maxBytes = parseSize(this.options.maxSize);
516
+ const entrySize = new TextEncoder().encode(serialized).length;
517
+ // If a single entry exceeds the max size, reject it without clearing the cache
518
+ if (entrySize > maxBytes) {
519
+ console.warn(`Cache entry size (${entrySize} bytes) exceeds maximum cache size (${maxBytes} bytes)`);
520
+ return;
521
+ }
522
+ // Only clear if adding this entry would exceed the total limit
523
+ if (this.totalSize + entrySize > maxBytes) {
524
+ // Simple strategy: clear everything
525
+ // A more sophisticated strategy would remove oldest entries first
526
+ await Promise.resolve(this.storage.clear());
527
+ this.totalSize = 0;
528
+ this.entryCount = 0;
529
+ }
530
+ this.totalSize += entrySize;
531
+ }
532
+ // Check max entries constraint
533
+ if (this.options.maxEntries) {
534
+ this.entryCount++;
535
+ if (this.entryCount > this.options.maxEntries) {
536
+ // Simple strategy: clear everything
537
+ // A more sophisticated strategy would use LRU cache
538
+ await Promise.resolve(this.storage.clear());
539
+ this.entryCount = 1;
540
+ }
541
+ }
542
+ // Pass ttl to storage provider if available
543
+ await Promise.resolve(this.storage.set(key, serialized, ttl));
544
+ }
545
+ /**
546
+ * Check if a key exists in the cache
547
+ */
548
+ async has(key) {
549
+ return Promise.resolve(this.storage.has(key));
550
+ }
551
+ /**
552
+ * Delete a key from the cache
553
+ */
554
+ async delete(key) {
555
+ return Promise.resolve(this.storage.delete(key));
556
+ }
557
+ /**
558
+ * Clear the entire cache
559
+ */
560
+ async clear() {
561
+ this.entryCount = 0;
562
+ this.totalSize = 0;
563
+ return Promise.resolve(this.storage.clear());
564
+ }
565
+ }
566
+
242
567
  /**
243
568
  * Global configuration for create-request
244
569
  */
@@ -452,6 +777,7 @@
452
777
  };
453
778
  this.queryParams = new URLSearchParams();
454
779
  this.autoApplyCsrfProtection = true;
780
+ this.cacheEnabled = false;
455
781
  }
456
782
  withHeaders(headers) {
457
783
  this.requestOptions.headers = {
@@ -714,6 +1040,16 @@
714
1040
  "X-Requested-With": "XMLHttpRequest",
715
1041
  });
716
1042
  }
1043
+ /**
1044
+ * Enables caching for the request with the specified options
1045
+ * @param options The cache configuration options
1046
+ * @returns The instance for chaining
1047
+ */
1048
+ withCache(options = {}) {
1049
+ this.cacheEnabled = true;
1050
+ this.cacheManager = new CacheManager(options);
1051
+ return this;
1052
+ }
717
1053
  /**
718
1054
  * Send the request to the specified URL
719
1055
  * @param url The URL to send the request to
@@ -763,8 +1099,15 @@
763
1099
  ...this.requestOptions,
764
1100
  method: this.method,
765
1101
  };
766
- // Create the base promise
767
- const basePromise = !this.requestOptions.retries ? this.executeRequest(url, fetchOptions) : this.executeWithRetries(url, fetchOptions);
1102
+ // Create the base promise - handle caching if enabled
1103
+ let basePromise;
1104
+ if (this.cacheEnabled && this.cacheManager) {
1105
+ const cacheKey = this.cacheManager.generateKey(url, this.method, this.requestOptions.headers, this.requestOptions.body);
1106
+ basePromise = this.executeWithCache(url, fetchOptions, cacheKey);
1107
+ }
1108
+ else {
1109
+ basePromise = !this.requestOptions.retries ? this.executeRequest(url, fetchOptions) : this.executeWithRetries(url, fetchOptions);
1110
+ }
768
1111
  const responsePromise = basePromise;
769
1112
  // Attach convenience methods that begin processing immediately
770
1113
  responsePromise.getJson = async () => {
@@ -903,6 +1246,48 @@
903
1246
  }
904
1247
  }
905
1248
  }
1249
+ /**
1250
+ * Executes a request using cache when possible
1251
+ * @param url The formatted URL to send the request to
1252
+ * @param fetchOptions The fetch options to use
1253
+ * @param cacheKey The cache key for this request
1254
+ */
1255
+ async executeWithCache(url, fetchOptions, cacheKey) {
1256
+ // Try to get from cache first
1257
+ if (this.cacheManager) {
1258
+ const cachedEntry = await this.cacheManager.get(cacheKey);
1259
+ if (cachedEntry) {
1260
+ // Create a synthetic Response object from cached data
1261
+ const cachedResponse = new Response(JSON.stringify(cachedEntry.value), {
1262
+ status: 200,
1263
+ headers: new Headers(cachedEntry.headers || {}),
1264
+ });
1265
+ return new ResponseWrapper(cachedResponse);
1266
+ }
1267
+ }
1268
+ // Not in cache, make the actual request
1269
+ const response = await (!this.requestOptions.retries ? this.executeRequest(url, fetchOptions) : this.executeWithRetries(url, fetchOptions));
1270
+ // Cache the successful response if caching is enabled
1271
+ // Only cache successful responses
1272
+ if (response.ok && this.cacheManager) {
1273
+ // Clone the response so we don't consume it
1274
+ try {
1275
+ const responseData = await response.getJson();
1276
+ // Extract headers to store in cache
1277
+ const headers = {};
1278
+ response.headers.forEach((value, key) => {
1279
+ headers[key] = value;
1280
+ });
1281
+ // Store in cache
1282
+ await this.cacheManager.set(cacheKey, responseData, headers);
1283
+ }
1284
+ catch (error) {
1285
+ // Failed to cache, but we can still return the response
1286
+ console.warn("Failed to cache response:", error);
1287
+ }
1288
+ }
1289
+ return response;
1290
+ }
906
1291
  }
907
1292
 
908
1293
  /**
@@ -1068,6 +1453,7 @@
1068
1453
  config: Config.getInstance(),
1069
1454
  };
1070
1455
 
1456
+ exports.CacheManager = CacheManager;
1071
1457
  exports.CookieUtils = CookieUtils;
1072
1458
  exports.DeleteRequest = DeleteRequest;
1073
1459
  exports.GetRequest = GetRequest;
@@ -1078,6 +1464,9 @@
1078
1464
  exports.PutRequest = PutRequest;
1079
1465
  exports.RequestError = RequestError;
1080
1466
  exports.ResponseWrapper = ResponseWrapper;
1467
+ exports.createLocalStorageStorage = createLocalStorageStorage;
1468
+ exports.createMemoryStorage = createMemoryStorage;
1469
+ exports.createSessionStorageStorage = createSessionStorageStorage;
1081
1470
  exports["default"] = create;
1082
1471
 
1083
1472
  Object.defineProperty(exports, '__esModule', { value: true });
@@ -0,0 +1,62 @@
1
+ import type { StorageProvider } from "../utils/StorageProvider";
2
+ /**
3
+ * Cache entry with metadata
4
+ */
5
+ export interface CacheEntry {
6
+ value: any;
7
+ expiry: number | null;
8
+ timestamp: number;
9
+ headers?: Record<string, string>;
10
+ }
11
+ /**
12
+ * Options for cache configuration
13
+ */
14
+ export interface CacheOptions {
15
+ /**
16
+ * Storage provider to use for caching
17
+ * Default is memory cache (new Map())
18
+ */
19
+ storage?: StorageProvider | Storage | Map<string, any>;
20
+ /**
21
+ * Time to live in milliseconds
22
+ * Default is no expiration
23
+ */
24
+ ttl?: number;
25
+ /**
26
+ * Custom key generator function
27
+ * @param url Request URL
28
+ * @param method Request method
29
+ * @param headers Request headers
30
+ * @param body Request body
31
+ */
32
+ keyGenerator?: (url: string, method: string, headers?: Record<string, string>, body?: any) => string;
33
+ /**
34
+ * Headers to vary the cache by
35
+ * If these headers change, a new cache entry will be created
36
+ */
37
+ varyByHeaders?: string[];
38
+ /**
39
+ * Maximum number of entries in the cache
40
+ * Only applicable for memory cache (Map)
41
+ */
42
+ maxEntries?: number;
43
+ /**
44
+ * Maximum size of the cache in bytes or with units (e.g. "10MB")
45
+ * String format supports KB, MB, GB (case-insensitive)
46
+ */
47
+ maxSize?: number | string;
48
+ /**
49
+ * Prefix for cache keys
50
+ * Useful for namespacing cache entries
51
+ */
52
+ keyPrefix?: string;
53
+ }
54
+ /**
55
+ * Type for cacheable response data
56
+ */
57
+ export type CacheableResponse = {
58
+ data: any;
59
+ headers: Record<string, string>;
60
+ status: number;
61
+ statusText: string;
62
+ };
@@ -0,0 +1,35 @@
1
+ import type { CacheEntry, CacheOptions } from "../types/cache";
2
+ /**
3
+ * Cache manager that handles storage operations
4
+ */
5
+ export declare class CacheManager {
6
+ private storage;
7
+ private options;
8
+ private entryCount;
9
+ private totalSize;
10
+ constructor(options?: CacheOptions);
11
+ /**
12
+ * Generate a cache key based on request details
13
+ */
14
+ generateKey(url: string, method: string, headers?: Record<string, string>, body?: unknown): string;
15
+ /**
16
+ * Get a value from cache
17
+ */
18
+ get(key: string): Promise<CacheEntry | null>;
19
+ /**
20
+ * Set a value in cache
21
+ */
22
+ set(key: string, value: unknown, headers?: Record<string, string>): Promise<void>;
23
+ /**
24
+ * Check if a key exists in the cache
25
+ */
26
+ has(key: string): Promise<boolean>;
27
+ /**
28
+ * Delete a key from the cache
29
+ */
30
+ delete(key: string): Promise<void>;
31
+ /**
32
+ * Clear the entire cache
33
+ */
34
+ clear(): Promise<void>;
35
+ }
@@ -0,0 +1,57 @@
1
+ import type { CacheEntry, CacheOptions } from "../types/cache";
2
+ import type { StorageProvider } from "./StorageProvider";
3
+ /**
4
+ * Parse size strings like "10MB" into bytes
5
+ * @param size Size as number or string with units
6
+ */
7
+ export declare function parseSize(size: string | number): number;
8
+ /**
9
+ * Create a memory storage provider using Map
10
+ */
11
+ export declare function createMemoryStorage(): StorageProvider;
12
+ /**
13
+ * Create a localStorage-based storage provider
14
+ */
15
+ export declare function createLocalStorageStorage(): StorageProvider;
16
+ /**
17
+ * Create a sessionStorage-based storage provider
18
+ */
19
+ export declare function createSessionStorageStorage(): StorageProvider;
20
+ /**
21
+ * Create a storage provider from a cache options object
22
+ */
23
+ export declare function createStorageProvider(options?: CacheOptions): StorageProvider;
24
+ /**
25
+ * Cache manager that handles storage operations
26
+ */
27
+ export declare class CacheManager {
28
+ private storage;
29
+ private options;
30
+ private entryCount;
31
+ private totalSize;
32
+ constructor(options?: CacheOptions);
33
+ /**
34
+ * Generate a cache key based on request details
35
+ */
36
+ generateKey(url: string, method: string, headers?: Record<string, string>, body?: any): string;
37
+ /**
38
+ * Get a value from cache
39
+ */
40
+ get(key: string): Promise<CacheEntry | null>;
41
+ /**
42
+ * Set a value in cache
43
+ */
44
+ set(key: string, value: unknown, headers?: Record<string, string>): Promise<void>;
45
+ /**
46
+ * Check if a key exists in the cache
47
+ */
48
+ has(key: string): Promise<boolean>;
49
+ /**
50
+ * Delete a key from the cache
51
+ */
52
+ delete(key: string): Promise<void>;
53
+ /**
54
+ * Clear the entire cache
55
+ */
56
+ clear(): Promise<void>;
57
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Interface for storage providers used by the cache system
3
+ */
4
+ export interface StorageProvider {
5
+ /**
6
+ * Get a value from storage
7
+ * @param key The key to retrieve
8
+ * @returns The stored value or null if not found
9
+ */
10
+ get(key: string): Promise<string | null> | string | null;
11
+ /**
12
+ * Set a value in storage
13
+ * @param key The key to store
14
+ * @param value The value to store
15
+ * @param ttl Optional TTL in milliseconds
16
+ * @returns Promise or void
17
+ */
18
+ set(key: string, value: string, ttl?: number): Promise<void> | void;
19
+ /**
20
+ * Check if a key exists in storage
21
+ * @param key The key to check
22
+ */
23
+ has(key: string): Promise<boolean> | boolean;
24
+ /**
25
+ * Delete a value from storage
26
+ * @param key The key to delete
27
+ */
28
+ delete(key: string): Promise<void> | void;
29
+ /**
30
+ * Clear all values from storage
31
+ */
32
+ clear(): Promise<void> | void;
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-request",
3
- "version": "0.0.1-beta.6",
3
+ "version": "0.0.1-beta.7",
4
4
  "description": "A modern, chainable wrapper for fetch with automatic retries, timeouts, comprehensive error handling, and first-class TypeScript support",
5
5
  "main": "dist/library/index.js",
6
6
  "module": "dist/library/index.esm.js",