UNPKG

@push.rocks/webrequest

Version:

Modern, fetch-compatible web request library with intelligent HTTP caching, retry strategies, and fault tolerance.

262 lines 19.7 kB
/** * Cache strategy implementations */ import { CacheStore } from './cache.store.js'; import { extractCacheMetadata, isFresh, requiresRevalidation, createConditionalHeaders, headersToObject, } from './cache.headers.js'; /** * Network-First Strategy * Try network first, fallback to cache on failure */ export class NetworkFirstStrategy { async execute(context) { try { // Try network first const response = await context.fetchFn(context.request); // If successful, cache it if (response.ok) { await this.cacheResponse(context, response); } return { response, fromCache: false, revalidated: false, }; } catch (error) { // Network failed, try cache if (context.logging) { console.log('[webrequest] Network failed, trying cache:', error); } const cachedEntry = await context.cacheStore.get(context.cacheKey); if (cachedEntry) { return { response: context.cacheStore.responseFromCacheEntry(cachedEntry), fromCache: true, revalidated: false, }; } // No cache available, re-throw error throw error; } } async cacheResponse(context, response) { const metadata = extractCacheMetadata(response.headers); // Don't cache if no-store if (metadata.noStore) { return; } const entry = await context.cacheStore.cacheEntryFromResponse(context.request.url, response, metadata); await context.cacheStore.set(context.cacheKey, entry); } } /** * Cache-First Strategy * Check cache first, fetch if miss or stale */ export class CacheFirstStrategy { async execute(context) { // Check cache first const cachedEntry = await context.cacheStore.get(context.cacheKey); if (cachedEntry) { const metadata = extractCacheMetadata(new Headers(cachedEntry.headers)); // Check if cache is fresh if (isFresh(cachedEntry, metadata)) { if (context.logging) { console.log('[webrequest] Cache hit (fresh):', context.request.url); } return { response: context.cacheStore.responseFromCacheEntry(cachedEntry), fromCache: true, revalidated: false, }; } // If requires revalidation, check with server if (requiresRevalidation(metadata) && (cachedEntry.etag || cachedEntry.lastModified)) { return await this.revalidate(context, cachedEntry); } } // Cache miss or stale, fetch from network if (context.logging) { console.log('[webrequest] Cache miss, fetching:', context.request.url); } const response = await context.fetchFn(context.request); // Cache the response const metadata = extractCacheMetadata(response.headers); if (!metadata.noStore) { const entry = await context.cacheStore.cacheEntryFromResponse(context.request.url, response, metadata); await context.cacheStore.set(context.cacheKey, entry); } return { response, fromCache: false, revalidated: false, }; } async revalidate(context, cachedEntry) { const conditionalHeaders = createConditionalHeaders(cachedEntry); // Create a new request with conditional headers const revalidateRequest = new Request(context.request.url, { method: context.request.method, headers: { ...headersToObject(context.request.headers), ...conditionalHeaders, }, }); try { const response = await context.fetchFn(revalidateRequest); // 304 Not Modified - cache is still valid if (response.status === 304) { if (context.logging) { console.log('[webrequest] Cache revalidated (304):', context.request.url); } // Update timestamp cachedEntry.timestamp = Date.now(); await context.cacheStore.set(context.cacheKey, cachedEntry); return { response: context.cacheStore.responseFromCacheEntry(cachedEntry), fromCache: true, revalidated: true, }; } // Response changed, cache the new one if (response.ok) { const metadata = extractCacheMetadata(response.headers); if (!metadata.noStore) { const entry = await context.cacheStore.cacheEntryFromResponse(context.request.url, response, metadata); await context.cacheStore.set(context.cacheKey, entry); } } return { response, fromCache: false, revalidated: true, }; } catch (error) { // Revalidation failed, use cached response if (context.logging) { console.log('[webrequest] Revalidation failed, using cache:', error); } return { response: context.cacheStore.responseFromCacheEntry(cachedEntry), fromCache: true, revalidated: false, }; } } } /** * Stale-While-Revalidate Strategy * Return cache immediately, update in background */ export class StaleWhileRevalidateStrategy { async execute(context) { const cachedEntry = await context.cacheStore.get(context.cacheKey); if (cachedEntry) { // Return cached response immediately const cachedResponse = context.cacheStore.responseFromCacheEntry(cachedEntry); // Revalidate in background this.revalidateInBackground(context, cachedEntry).catch((error) => { if (context.logging) { console.warn('[webrequest] Background revalidation failed:', error); } }); return { response: cachedResponse, fromCache: true, revalidated: false, }; } // No cache, fetch from network const response = await context.fetchFn(context.request); // Cache the response const metadata = extractCacheMetadata(response.headers); if (!metadata.noStore && response.ok) { const entry = await context.cacheStore.cacheEntryFromResponse(context.request.url, response, metadata); await context.cacheStore.set(context.cacheKey, entry); } return { response, fromCache: false, revalidated: false, }; } async revalidateInBackground(context, cachedEntry) { const metadata = extractCacheMetadata(new Headers(cachedEntry.headers)); // Check if revalidation is needed if (isFresh(cachedEntry, metadata) && !requiresRevalidation(metadata)) { return; } try { const response = await context.fetchFn(context.request); if (response.ok) { const newMetadata = extractCacheMetadata(response.headers); if (!newMetadata.noStore) { const entry = await context.cacheStore.cacheEntryFromResponse(context.request.url, response, newMetadata); await context.cacheStore.set(context.cacheKey, entry); if (context.logging) { console.log('[webrequest] Background revalidation complete:', context.request.url); } } } } catch (error) { // Background revalidation failed, keep existing cache if (context.logging) { console.warn('[webrequest] Background revalidation failed:', error); } } } } /** * Network-Only Strategy * Never use cache */ export class NetworkOnlyStrategy { async execute(context) { const response = await context.fetchFn(context.request); return { response, fromCache: false, revalidated: false, }; } } /** * Cache-Only Strategy * Only use cache, fail if miss */ export class CacheOnlyStrategy { async execute(context) { const cachedEntry = await context.cacheStore.get(context.cacheKey); if (!cachedEntry) { throw new Error(`Cache miss for ${context.request.url} (cache-only mode)`); } return { response: context.cacheStore.responseFromCacheEntry(cachedEntry), fromCache: true, revalidated: false, }; } } /** * Get strategy handler for a given strategy type */ export function getStrategyHandler(strategy) { switch (strategy) { case 'network-first': return new NetworkFirstStrategy(); case 'cache-first': return new CacheFirstStrategy(); case 'stale-while-revalidate': return new StaleWhileRevalidateStrategy(); case 'network-only': return new NetworkOnlyStrategy(); case 'cache-only': return new CacheOnlyStrategy(); default: return new NetworkFirstStrategy(); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2FjaGUuc3RyYXRlZ2llcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2NhY2hlL2NhY2hlLnN0cmF0ZWdpZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFPSCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDOUMsT0FBTyxFQUNMLG9CQUFvQixFQUNwQixPQUFPLEVBQ1Asb0JBQW9CLEVBQ3BCLHdCQUF3QixFQUN4QixlQUFlLEdBQ2hCLE1BQU0sb0JBQW9CLENBQUM7QUF1QjVCOzs7R0FHRztBQUNILE1BQU0sT0FBTyxvQkFBb0I7SUFDL0IsS0FBSyxDQUFDLE9BQU8sQ0FBQyxPQUF5QjtRQUNyQyxJQUFJLENBQUM7WUFDSCxvQkFBb0I7WUFDcEIsTUFBTSxRQUFRLEdBQUcsTUFBTSxPQUFPLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUV4RCwwQkFBMEI7WUFDMUIsSUFBSSxRQUFRLENBQUMsRUFBRSxFQUFFLENBQUM7Z0JBQ2hCLE1BQU0sSUFBSSxDQUFDLGFBQWEsQ0FBQyxPQUFPLEVBQUUsUUFBUSxDQUFDLENBQUM7WUFDOUMsQ0FBQztZQUVELE9BQU87Z0JBQ0wsUUFBUTtnQkFDUixTQUFTLEVBQUUsS0FBSztnQkFDaEIsV0FBVyxFQUFFLEtBQUs7YUFDbkIsQ0FBQztRQUNKLENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsNEJBQTRCO1lBQzVCLElBQUksT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUNwQixPQUFPLENBQUMsR0FBRyxDQUFDLDRDQUE0QyxFQUFFLEtBQUssQ0FBQyxDQUFDO1lBQ25FLENBQUM7WUFFRCxNQUFNLFdBQVcsR0FBRyxNQUFNLE9BQU8sQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUNuRSxJQUFJLFdBQVcsRUFBRSxDQUFDO2dCQUNoQixPQUFP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