@push.rocks/webrequest
Version:
Modern, fetch-compatible web request library with intelligent HTTP caching, retry strategies, and fault tolerance.
262 lines • 19.7 kB
JavaScript
/**
* 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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