- 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.
		
			
				
	
	
		
			157 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			157 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * Cache manager - orchestrates caching logic
 | |
|  */
 | |
| 
 | |
| import type {
 | |
|   ICacheOptions,
 | |
|   TCacheStrategy,
 | |
|   TStandardCacheMode,
 | |
| } from '../webrequest.types.js';
 | |
| import { CacheStore } from './cache.store.js';
 | |
| import {
 | |
|   getStrategyHandler,
 | |
|   type IStrategyContext,
 | |
|   type IStrategyResult,
 | |
| } from './cache.strategies.js';
 | |
| import { extractCacheMetadata } from './cache.headers.js';
 | |
| 
 | |
| export class CacheManager {
 | |
|   private cacheStore: CacheStore;
 | |
| 
 | |
|   constructor(dbName?: string, storeName?: string) {
 | |
|     this.cacheStore = new CacheStore(dbName, storeName);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Execute a request with caching
 | |
|    */
 | |
|   public async execute(
 | |
|     request: Request,
 | |
|     options: ICacheOptions & { logging?: boolean },
 | |
|     fetchFn: (request: Request) => Promise<Response>,
 | |
|   ): Promise<IStrategyResult> {
 | |
|     // Determine the cache strategy
 | |
|     const strategy = this.determineStrategy(request, options);
 | |
| 
 | |
|     // If no caching (no-store or network-only), bypass cache
 | |
|     if (strategy === 'network-only') {
 | |
|       const response = await fetchFn(request);
 | |
|       return {
 | |
|         response,
 | |
|         fromCache: false,
 | |
|         revalidated: false,
 | |
|       };
 | |
|     }
 | |
| 
 | |
|     // Generate cache key
 | |
|     const cacheKey = this.generateCacheKey(request, options);
 | |
| 
 | |
|     // Get strategy handler
 | |
|     const handler = getStrategyHandler(strategy);
 | |
| 
 | |
|     // Execute strategy
 | |
|     const context: IStrategyContext = {
 | |
|       request,
 | |
|       cacheKey,
 | |
|       cacheStore: this.cacheStore,
 | |
|       fetchFn,
 | |
|       logging: options.logging,
 | |
|     };
 | |
| 
 | |
|     return await handler.execute(context);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Determine the caching strategy based on options and request
 | |
|    */
 | |
|   private determineStrategy(
 | |
|     request: Request,
 | |
|     options: ICacheOptions,
 | |
|   ): TCacheStrategy {
 | |
|     // If explicit strategy provided, use it
 | |
|     if (options.cacheStrategy) {
 | |
|       return options.cacheStrategy;
 | |
|     }
 | |
| 
 | |
|     // Map standard cache modes to strategies
 | |
|     if (options.cache) {
 | |
|       return this.mapCacheModeToStrategy(options.cache);
 | |
|     }
 | |
| 
 | |
|     // Check request cache mode
 | |
|     if (request.cache) {
 | |
|       return this.mapCacheModeToStrategy(request.cache as TStandardCacheMode);
 | |
|     }
 | |
| 
 | |
|     // Default strategy
 | |
|     return 'network-first';
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Map standard fetch cache modes to our strategies
 | |
|    */
 | |
|   private mapCacheModeToStrategy(
 | |
|     cacheMode: TStandardCacheMode,
 | |
|   ): TCacheStrategy {
 | |
|     switch (cacheMode) {
 | |
|       case 'default':
 | |
|         return 'network-first';
 | |
|       case 'no-store':
 | |
|       case 'reload':
 | |
|         return 'network-only';
 | |
|       case 'no-cache':
 | |
|         return 'network-first'; // Will use revalidation
 | |
|       case 'force-cache':
 | |
|         return 'cache-first';
 | |
|       case 'only-if-cached':
 | |
|         return 'cache-only';
 | |
|       default:
 | |
|         return 'network-first';
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Generate cache key
 | |
|    */
 | |
|   private generateCacheKey(request: Request, options: ICacheOptions): string {
 | |
|     // If custom cache key provided
 | |
|     if (options.cacheKey) {
 | |
|       if (typeof options.cacheKey === 'function') {
 | |
|         return options.cacheKey(request);
 | |
|       }
 | |
|       return options.cacheKey;
 | |
|     }
 | |
| 
 | |
|     // Default cache key generation
 | |
|     return this.cacheStore.generateCacheKey(request);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Clear the cache
 | |
|    */
 | |
|   public async clear(): Promise<void> {
 | |
|     await this.cacheStore.clear();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Delete a specific cache entry
 | |
|    */
 | |
|   public async delete(cacheKey: string): Promise<void> {
 | |
|     await this.cacheStore.delete(cacheKey);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Check if a cache entry exists
 | |
|    */
 | |
|   public async has(cacheKey: string): Promise<boolean> {
 | |
|     return await this.cacheStore.has(cacheKey);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Get the underlying cache store
 | |
|    */
 | |
|   public getStore(): CacheStore {
 | |
|     return this.cacheStore;
 | |
|   }
 | |
| }
 |