- Added cache strategies: NetworkFirst, CacheFirst, StaleWhileRevalidate, NetworkOnly, and CacheOnly. - Introduced InterceptorManager for managing request, response, and error interceptors. - Developed RetryManager for handling request retries with customizable backoff strategies. - Implemented RequestDeduplicator to prevent simultaneous identical requests. - Created timeout utilities for handling request timeouts. - Enhanced WebrequestClient to support global interceptors, caching, and retry logic. - Added convenience methods for common HTTP methods (GET, POST, PUT, DELETE) with JSON handling. - Established a fetch-compatible webrequest function for seamless integration. - Defined core type structures for caching, retry options, interceptors, and web request configurations.
		
			
				
	
	
		
			155 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			155 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * Cache storage layer using IndexedDB via @push.rocks/webstore
 | |
|  */
 | |
| 
 | |
| import * as plugins from '../webrequest.plugins.js';
 | |
| import type { ICacheEntry } from '../webrequest.types.js';
 | |
| 
 | |
| export class CacheStore {
 | |
|   private webstore: plugins.webstore.WebStore;
 | |
|   private initPromise: Promise<void>;
 | |
| 
 | |
|   constructor(dbName: string = 'webrequest-v4', storeName: string = 'cache') {
 | |
|     this.webstore = new plugins.webstore.WebStore({
 | |
|       dbName,
 | |
|       storeName,
 | |
|     });
 | |
| 
 | |
|     // Initialize the store
 | |
|     this.initPromise = this.init();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Initialize the store
 | |
|    */
 | |
|   private async init(): Promise<void> {
 | |
|     // WebStore handles initialization internally
 | |
|     // This method exists for future extension if needed
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Generate a cache key from a request
 | |
|    */
 | |
|   public generateCacheKey(request: Request): string {
 | |
|     // Use URL + method as the base key
 | |
|     const url = request.url;
 | |
|     const method = request.method;
 | |
| 
 | |
|     // For GET requests, just use the URL
 | |
|     if (method === 'GET') {
 | |
|       return url;
 | |
|     }
 | |
| 
 | |
|     // For other methods, include the method
 | |
|     return `${method}:${url}`;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Store a response in the cache
 | |
|    */
 | |
|   public async set(cacheKey: string, entry: ICacheEntry): Promise<void> {
 | |
|     await this.initPromise;
 | |
|     await this.webstore.set(cacheKey, entry);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Retrieve a cached response
 | |
|    */
 | |
|   public async get(cacheKey: string): Promise<ICacheEntry | null> {
 | |
|     await this.initPromise;
 | |
| 
 | |
|     try {
 | |
|       const entry = (await this.webstore.get(cacheKey)) as ICacheEntry;
 | |
|       return entry || null;
 | |
|     } catch (error) {
 | |
|       // If entry doesn't exist or is corrupted, return null
 | |
|       return null;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Check if a cache entry exists
 | |
|    */
 | |
|   public async has(cacheKey: string): Promise<boolean> {
 | |
|     await this.initPromise;
 | |
|     return await this.webstore.check(cacheKey);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Delete a cache entry
 | |
|    */
 | |
|   public async delete(cacheKey: string): Promise<void> {
 | |
|     await this.initPromise;
 | |
|     await this.webstore.delete(cacheKey);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Clear all cache entries
 | |
|    */
 | |
|   public async clear(): Promise<void> {
 | |
|     await this.initPromise;
 | |
|     await this.webstore.clear();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Create a Response object from a cache entry
 | |
|    */
 | |
|   public responseFromCacheEntry(entry: ICacheEntry): Response {
 | |
|     const headers = new Headers(entry.headers);
 | |
| 
 | |
|     return new Response(entry.response, {
 | |
|       status: entry.status,
 | |
|       statusText: entry.statusText,
 | |
|       headers,
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Create a cache entry from a Response object
 | |
|    */
 | |
|   public async cacheEntryFromResponse(
 | |
|     url: string,
 | |
|     response: Response,
 | |
|     metadata?: { maxAge?: number; etag?: string; lastModified?: string },
 | |
|   ): Promise<ICacheEntry> {
 | |
|     // Clone the response so we can read it multiple times
 | |
|     const clonedResponse = response.clone();
 | |
|     const buffer = await clonedResponse.arrayBuffer();
 | |
| 
 | |
|     // Extract headers
 | |
|     const headers: Record<string, string> = {};
 | |
|     clonedResponse.headers.forEach((value, key) => {
 | |
|       headers[key] = value;
 | |
|     });
 | |
| 
 | |
|     return {
 | |
|       response: buffer,
 | |
|       headers,
 | |
|       timestamp: Date.now(),
 | |
|       etag: metadata?.etag || clonedResponse.headers.get('etag') || undefined,
 | |
|       lastModified:
 | |
|         metadata?.lastModified ||
 | |
|         clonedResponse.headers.get('last-modified') ||
 | |
|         undefined,
 | |
|       maxAge: metadata?.maxAge,
 | |
|       url,
 | |
|       status: clonedResponse.status,
 | |
|       statusText: clonedResponse.statusText,
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Prune expired entries (garbage collection)
 | |
|    * Returns the number of entries deleted
 | |
|    */
 | |
|   public async pruneExpired(): Promise<number> {
 | |
|     await this.initPromise;
 | |
| 
 | |
|     // Note: WebStore doesn't provide a way to list all keys
 | |
|     // This would need to be implemented if we want automatic cleanup
 | |
|     // For now, we rely on individual entry checks
 | |
| 
 | |
|     return 0;
 | |
|   }
 | |
| }
 |