feat: Implement comprehensive web request handling with caching, retry, and interceptors
- Added cache strategies: NetworkFirst, CacheFirst, StaleWhileRevalidate, NetworkOnly, and CacheOnly. - Introduced InterceptorManager for managing request, response, and error interceptors. - Developed RetryManager for handling request retries with customizable backoff strategies. - Implemented RequestDeduplicator to prevent simultaneous identical requests. - Created timeout utilities for handling request timeouts. - Enhanced WebrequestClient to support global interceptors, caching, and retry logic. - Added convenience methods for common HTTP methods (GET, POST, PUT, DELETE) with JSON handling. - Established a fetch-compatible webrequest function for seamless integration. - Defined core type structures for caching, retry options, interceptors, and web request configurations.
This commit is contained in:
156
ts/cache/cache.manager.ts
vendored
Normal file
156
ts/cache/cache.manager.ts
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user