/** * 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; 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 { // 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 { await this.initPromise; await this.webstore.set(cacheKey, entry); } /** * Retrieve a cached response */ public async get(cacheKey: string): Promise { 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 { await this.initPromise; return await this.webstore.check(cacheKey); } /** * Delete a cache entry */ public async delete(cacheKey: string): Promise { await this.initPromise; await this.webstore.delete(cacheKey); } /** * Clear all cache entries */ public async clear(): Promise { 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 { // Clone the response so we can read it multiple times const clonedResponse = response.clone(); const buffer = await clonedResponse.arrayBuffer(); // Extract headers const headers: Record = {}; 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 { 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; } }