/** * 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, ): Promise { // 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 { await this.cacheStore.clear(); } /** * Delete a specific cache entry */ public async delete(cacheKey: string): Promise { await this.cacheStore.delete(cacheKey); } /** * Check if a cache entry exists */ public async has(cacheKey: string): Promise { return await this.cacheStore.has(cacheKey); } /** * Get the underlying cache store */ public getStore(): CacheStore { return this.cacheStore; } }