Files
webrequest/ts/cache/cache.store.ts
Juergen Kunz 54afcc46e2 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.
2025-10-20 09:59:24 +00:00

155 lines
3.8 KiB
TypeScript

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