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 +145 -0
- package/dist/library/BaseRequest.d.ts +17 -0
- package/dist/library/index.d.ts +4 -0
- package/dist/library/index.esm.js +388 -3
- package/dist/library/index.js +391 -2
- package/dist/library/types/cache.d.ts +62 -0
- package/dist/library/utils/CacheManager.d.ts +35 -0
- package/dist/library/utils/CacheStorage.d.ts +57 -0
- package/dist/library/utils/StorageProvider.d.ts +33 -0
- package/package.json +1 -1
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
|
}
|
package/dist/library/index.d.ts
CHANGED
|
@@ -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
|
-
|
|
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 };
|
package/dist/library/index.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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",
|