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; | ||
|  |   } | ||
|  | } |