- 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.
155 lines
3.8 KiB
TypeScript
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;
|
|
}
|
|
}
|