UNPKG

@web-widget/shared-cache

Version:

Standards-compliant HTTP cache implementation for server-side JavaScript with RFC 7234 compliance and cross-runtime support

541 lines (531 loc) 22.6 kB
/** * Generic logger interface for any logging implementation. * Provides standardized logging methods compatible with console, winston, pino, etc. */ interface Logger { /** * Log informational messages about normal operations. */ info(message?: unknown, ...optionalParams: unknown[]): void; /** * Log warning messages about potentially problematic situations. */ warn(message?: unknown, ...optionalParams: unknown[]): void; /** * Log detailed debugging information. */ debug(message?: unknown, ...optionalParams: unknown[]): void; /** * Log error messages about failed operations. */ error(message?: unknown, ...optionalParams: unknown[]): void; } /** * Log levels in order of priority (lowest to highest) */ declare enum LogLevel { DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3 } /** * Structured logger utility class that provides consistent logging format and optional level filtering * @template TContext - The log context type structure, defaults to a flexible object type */ declare class StructuredLogger<TContext = Record<string, unknown>> { private logger?; private minLevel; private prefix?; constructor(logger?: Logger, minLevel?: LogLevel, prefix?: string); /** * Log debug information about operations */ debug(operation: string, context?: TContext, details?: string): void; /** * Log informational messages about successful operations */ info(operation: string, context?: TContext, details?: string): void; /** * Log warning messages about potentially problematic situations */ warn(operation: string, context?: TContext, details?: string): void; /** * Log error messages about failed operations */ error(operation: string, context?: TContext, details?: string): void; /** * Handle promise rejections with proper error logging */ handleAsyncError: (operation: string, context?: TContext) => (error: unknown) => void; /** * Check if a log level should be output based on minimum level setting */ private shouldLog; /** * Create a new logger instance with a different minimum level */ withLevel(minLevel: LogLevel): StructuredLogger<TContext>; /** * Create a new logger instance with a different prefix */ withPrefix(prefix: string): StructuredLogger<TContext>; } /** * Helper function to create a structured logger instance * @template TContext - The log context type structure */ declare function createLogger<TContext = Record<string, unknown>>(logger?: Logger, minLevel?: LogLevel, prefix?: string): StructuredLogger<TContext>; /** * HTTP header names and cache-status values for SharedCache. * * Cache-key domain constants (`DEFAULT_CACHE_KEY_RULES`, `CANNOT_INCLUDE_HEADERS`) * live in `key.ts` and are re-exported from the package entry. */ /** HTTP header name for cache status information. */ declare const CACHE_STATUS_HEADER_NAME = "x-cache-status"; /** HTTP header name for debugging cache key information. */ declare const CACHE_KEY_HEADER_NAME = "x-cache-key"; /** Canonical cache status literals. */ declare const SHARED_CACHE_STATUS: { /** Response served from cache without validation */ readonly HIT: "HIT"; /** Response not found in cache, fetched from origin */ readonly MISS: "MISS"; /** Cached response was expired, fresh response fetched */ readonly EXPIRED: "EXPIRED"; /** Stale response served when origin is unreachable (stale-if-error) */ readonly STALE: "STALE"; /** Expired response served while revalidating in the background */ readonly UPDATING: "UPDATING"; /** Cache was bypassed due to cache-control directives */ readonly BYPASS: "BYPASS"; /** Cached response was revalidated and determined still fresh */ readonly REVALIDATED: "REVALIDATED"; /** Response is dynamic and cannot be cached */ readonly DYNAMIC: "DYNAMIC"; }; /** Cache status values as defined in HTTP caching standards. */ type CacheStatus = (typeof SHARED_CACHE_STATUS)[keyof typeof SHARED_CACHE_STATUS]; declare const HIT: "HIT"; declare const MISS: "MISS"; declare const EXPIRED: "EXPIRED"; declare const STALE: "STALE"; declare const UPDATING: "UPDATING"; declare const BYPASS: "BYPASS"; declare const REVALIDATED: "REVALIDATED"; declare const DYNAMIC: "DYNAMIC"; /** * Package-level type barrel. Domain types (cache key, logger) are defined in * their modules and re-exported here; `index.ts` exposes types only via this file. * * Constants: HTTP/status → `constants.ts`; key defaults → `key.ts` (`DEFAULT_CACHE_KEY_RULES`). */ /** * Filter options for controlling which keys to include/exclude in cache key generation. */ interface KeyFilterOptions { /** Array of keys to explicitly include in the cache key */ include?: string[]; /** Array of keys to explicitly exclude from the cache key */ exclude?: string[]; /** Array of keys to check for presence only (value set to empty string) */ checkPresence?: string[]; } /** * Configuration rules for generating cache keys. * Each property can be `true`, `false`, or {@link KeyFilterOptions}. */ interface CacheKeyRules { cookie?: KeyFilterOptions | boolean; device?: KeyFilterOptions | boolean; header?: KeyFilterOptions | boolean; search?: KeyFilterOptions | boolean; } /** Cache key generator with an optional synchronous fast path for URL-only rules. */ interface CacheKeyGenerator { (request: Request, cacheKeyRules?: CacheKeyRules): Promise<string>; sync: (request: Request, cacheKeyRules?: CacheKeyRules) => string | undefined; } /** * Log context structure for SharedCache operations */ interface CacheLogContext { /** The URL being processed */ url?: string; /** Cache key involved in the operation */ cacheKey?: string; /** HTTP status code */ status?: number; /** Operation duration in milliseconds */ duration?: number; /** Error object if applicable */ error?: unknown; /** Cache hit/miss/stale status */ cacheStatus?: string; /** TTL value in seconds */ ttl?: number; /** Request method */ method?: string; /** Additional context data */ [key: string]: unknown; } type WebCache = globalThis.Cache; type WebCacheQueryOptions = globalThis.CacheQueryOptions; type WebCacheStorage = globalThis.CacheStorage; type WebRequest = globalThis.Request; type WebRequestInit = globalThis.RequestInit; /** * Configuration options for SharedCache instances. * These options control caching behavior and key generation. */ interface SharedCacheOptions { /** * Rules for generating cache keys from requests. * Controls which parts of the request are used in the cache key. */ cacheKeyRules?: CacheKeyRules; /** * Custom logger for debugging and monitoring cache operations. */ logger?: Logger; } /** * Key-Value storage interface for cache persistence. * This abstraction allows different storage backends (memory, Redis, etc.). */ interface KVStorage { /** * Retrieve a value from storage. * @param cacheKey - The key to retrieve * @returns The stored value or undefined if not found */ get: (cacheKey: string) => Promise<unknown | undefined>; /** * Store a value in storage with optional TTL. * @param cacheKey - The key to store * @param value - The value to store * @param ttl - Time to live in seconds (optional) */ set: (cacheKey: string, value: unknown, ttl?: number) => Promise<void>; /** * Delete a value from storage. * @param cacheKey - The key to delete * @returns True if the key was deleted, false if it didn't exist */ delete: (cacheKey: string) => Promise<boolean>; } /** * Extended cache query options for shared cache operations. * Extends standard SharedCacheQueryOptions with shared cache specific options. */ type SharedCacheQueryOptions = WebCacheQueryOptions & {}; /** * Type alias for fetch function compatible with shared cache. */ type SharedCacheFetch = (input: SharedCacheRequestInfo | URL, init?: SharedCacheRequestInit) => Promise<Response>; type SharedCacheRequestInfo = Request | string; type SharedCacheRequestInit = WebRequestInit & { sharedCache?: SharedCacheRequestInitProperties; }; type SharedCacheRequest = WebRequest & { sharedCache?: SharedCacheRequestInitProperties; }; /** * Shared cache specific request properties. * These properties control cache behavior on a per-request basis. */ interface SharedCacheRequestInitProperties { /** * Whether to expose the computed cache key via response header. * When true, the response includes the `x-cache-key` header for debugging. * Non-ASCII and control characters in the key are percent-encoded for valid HTTP headers. */ debugCacheKey?: boolean; /** * Override the cache-control header for caching decisions. * This allows forcing specific cache behavior regardless of origin headers. */ cacheControlOverride?: string; /** * Custom cache key rules for this specific request. * Overrides default cache key generation rules. */ cacheKeyRules?: CacheKeyRules; /** * Whether to ignore request cache-control headers. * When true, request cache-control directives are ignored. */ ignoreRequestCacheControl?: boolean; /** * Whether to ignore Vary header processing. * When true, Vary header is not considered for cache key generation. */ ignoreVary?: boolean; /** * Override the vary header for this request. * Allows custom vary behavior regardless of response headers. */ varyOverride?: string; /** * Event instance to handle background operations (like stale-while-revalidate). * The event.waitUntil() method will be called with promises that should be awaited in the background. */ event?: ExtendableEvent; /** * Function to handle background operations (like stale-while-revalidate). * Called with promises that should be awaited in the background. * @deprecated Use event instead. This option will be removed in a future version. */ waitUntil?: (promise: Promise<unknown>) => void; } /** * Phase indicating why a cache origin handler is invoked. */ type CacheOriginPhase = 'miss' | 'revalidate'; /** * Context passed to middleware-friendly cache origin handlers. */ interface CacheOriginContext { /** Why the origin is being invoked. */ phase: CacheOriginPhase; /** Present when invoked from a conditional revalidation request. */ revalidationRequest?: Request; /** Abort signal from the outer resolve call or request. */ signal?: AbortSignal; } /** * In-process origin handler for middleware integrations. * * @remarks * - **miss**: throws propagate to the caller (framework error handling). * - **revalidate**: throws are converted to 5xx responses for stale-if-error. */ type CacheOriginHandler = (request: Request, context: CacheOriginContext) => Response | Promise<Response>; /** * Options for {@link resolveWithCache} and {@link createCacheHandler}. */ type CacheResolveOptions = SharedCacheRequestInitProperties & { signal?: AbortSignal; }; interface CacheHandler { resolve(request: Request, origin: CacheOriginHandler, options?: CacheResolveOptions): Promise<Response>; } /** * SharedCache implements the Cache interface with additional features for shared caching. * It provides HTTP-compliant caching with support for revalidation, stale-while-revalidate, * and custom cache key generation. * * This implementation follows HTTP caching semantics as defined in RFC 7234 and related specifications. */ declare class SharedCache implements WebCache { #private; /** * Creates a new SharedCache instance. * * @param storage - The key-value storage backend for persistence * @param options - Configuration options for cache behavior * @throws {TypeError} When storage is not provided */ constructor(storage: KVStorage, options?: SharedCacheOptions); /** * Computes the cache key for a request using the current cache key rules. * Useful for debugging and diagnostics in callers that need to surface the key. * * @param request - Request to compute key for * @returns Promise resolving to the computed cache key */ getCacheKey(request: SharedCacheRequestInfo): Promise<string>; /** * The add() method is not implemented in this cache implementation. * This method is part of the Cache interface but not commonly used in practice. * * @param _request - The request to add (unused) * @throws {Error} Always throws as this method is not implemented */ add(_request: SharedCacheRequestInfo): Promise<void>; /** * The addAll() method is not implemented in this cache implementation. * This method is part of the Cache interface but not commonly used in practice. * * @param _requests - The requests to add (unused) * @throws {Error} Always throws as this method is not implemented */ addAll(_requests: SharedCacheRequestInfo[]): Promise<void>; /** * The delete() method of the Cache interface finds the Cache entry whose key * matches the request, and if found, deletes the Cache entry and returns a Promise * that resolves to true. If no Cache entry is found, it resolves to false. * * This implementation follows the algorithm specified in the Cache API specification: * https://w3c.github.io/ServiceWorker/#cache-delete * * @param request - The Request for which you are looking to delete. This can be a Request object or a URL. * @param options - An object whose properties control how matching is done in the delete operation. * @returns A Promise that resolves to true if the cache entry is deleted, or false otherwise. */ delete(request: SharedCacheRequestInfo, options?: SharedCacheQueryOptions): Promise<boolean>; /** * The keys() method is not implemented in this cache implementation. * This method would return all Request objects that serve as keys for cached responses. * * @param _request - Optional request to match against (unused) * @param _options - Optional query options (unused) * @throws {Error} Always throws as this method is not implemented */ keys(_request?: SharedCacheRequestInfo, _options?: SharedCacheQueryOptions): Promise<readonly SharedCacheRequest[]>; /** * The match() method of the Cache interface returns a Promise that resolves * to the Response associated with the first matching request in the Cache * object. If no match is found, the Promise resolves to undefined. * * This implementation includes advanced features: * - HTTP cache validation (ETag, Last-Modified) * - Stale-while-revalidate support * - Custom cache key generation * - Proper Vary header handling * * @param request - The Request for which you are attempting to find responses in the Cache. * This can be a Request object or a URL. * @param options - An object that sets options for the match operation. * @returns A Promise that resolves to the first Response that matches the request * or to undefined if no match is found. */ match(request: SharedCacheRequestInfo, options?: SharedCacheQueryOptions): Promise<Response | undefined>; /** * The matchAll() method is not implemented in this cache implementation. * This method would return all matching responses for a given request. * * @param _request - Optional request to match against (unused) * @param _options - Optional query options (unused) * @throws {Error} Always throws as this method is not implemented */ matchAll(_request?: SharedCacheRequestInfo, _options?: SharedCacheQueryOptions): Promise<readonly Response[]>; /** * The put() method of the Cache interface allows key/value pairs to be added * to the current Cache object. * * This implementation includes several HTTP-compliant validations: * - Only HTTP/HTTPS schemes are supported for GET requests * - 206 (Partial Content) responses are rejected * - Vary: * responses are rejected * - Body usage validation to prevent corruption * * @param request - The Request object or URL that you want to add to the cache. * @param response - The Response you want to match up to the request. * @throws {TypeError} For various validation failures as per Cache API specification */ put(request: SharedCacheRequestInfo, response: Response): Promise<void>; } /** * Named cache registry backed by a shared KV storage. * Only `open()` is implemented; other CacheStorage methods throw. */ declare class SharedCacheStorage implements WebCacheStorage { #private; constructor(storage: KVStorage, options?: SharedCacheOptions); delete(_cacheName: string): Promise<boolean>; has(_cacheName: string): Promise<boolean>; keys(): Promise<string[]>; match(_request: RequestInfo, _options?: MultiCacheQueryOptions): Promise<Response | undefined>; open(cacheName: string): Promise<SharedCache>; } /** * Creates a fetch function with shared caching capabilities. * * This is the internal implementation that powers the `createFetch` export. * Users should import and use `createFetch` instead of this function directly. * * This function implements HTTP caching semantics on top of the standard fetch API, * providing automatic cache management with support for: * - HTTP cache semantics (RFC 7234) * - Conditional requests and revalidation * - Stale-while-revalidate patterns * - Custom cache control and vary header overrides * * The returned fetch function is compatible with the standard fetch API while * adding transparent caching capabilities. * * @param cache - Optional SharedCache instance (defaults to global cache if available) * @param options - Configuration options * @param options.fetch - Custom fetch implementation (defaults to global fetch) * @param options.defaults - Default shared cache options to apply to all requests * @returns A fetch function with caching capabilities * * @example * ```typescript * import { createFetch, CacheStorage } from '@web-widget/shared-cache'; * import { LRUCache } from 'lru-cache'; * * // Set up cache storage * const caches = new CacheStorage(createLRUStorage()); * const cache = await caches.open('api-cache'); * * // Create cached fetch with default configuration * const fetch = createFetch(cache, { * defaults: { * cacheControlOverride: 's-maxage=300', * ignoreRequestCacheControl: true, * } * }); * * // Use the cached fetch * const response = await fetch('/api/data'); * console.log(response.headers.get('x-cache-status')); // "MISS" or "HIT" * ``` */ declare function createSharedCacheFetch(cache?: SharedCache, options?: { /** Custom fetch implementation to use as the underlying fetcher */ fetch?: typeof globalThis.fetch; /** Default shared cache options to apply to all requests */ defaults?: Partial<SharedCacheRequestInitProperties>; }): SharedCacheFetch; /** * Default shared cache fetch instance using global cache. * * This is a convenience export that creates a shared cache fetch function * using the default configuration. It will automatically use the global * cache storage if available. * * @deprecated */ declare const sharedCacheFetch: SharedCacheFetch; /** * Resolves a request through shared cache using an in-process origin handler. * * @remarks * Origin error contract: * - **miss**: throws propagate to the caller (framework `onError`). * - **revalidate**: throws are converted to 5xx responses for `stale-if-error`. * * @throws When the origin throws during a cache miss. */ declare function resolveWithCache(cache: SharedCache, request: SharedCacheRequest, origin: CacheOriginHandler, options?: CacheResolveOptions): Promise<Response>; /** * Creates a reusable cache resolver for middleware-style origin handlers. * * Prefer this over `createFetch` when the origin is an in-process handler such as * middleware `next()` rather than an outbound HTTP `fetch`. */ declare function createCacheHandler(cache: SharedCache, defaults?: CacheResolveOptions): CacheHandler; /** * List of HTTP headers that should not be included in cache keys. * * These headers are excluded for the following reasons: * - High cardinality: Risk of cache fragmentation (Accept-*, User-Agent, Referer) * - Cache/proxy features: Would interfere with caching logic (Cache-Control, If-*) * - Covered by other features: Handled by dedicated cache key components (Cookie, Host) * - Implementation details: Not relevant for cache key generation (Content-Length, Connection) * * Based on best practices from CDN implementations and HTTP caching specifications. */ declare const CANNOT_INCLUDE_HEADERS: readonly ["accept", "accept-charset", "accept-encoding", "accept-datetime", "accept-language", "referer", "user-agent", "connection", "content-length", "cache-control", "if-match", "if-modified-since", "if-none-match", "if-unmodified-since", "range", "upgrade", "cookie", "host", "vary", "x-cache-status", "x-cache-key"]; /** * Default cache key generation rules. */ declare const DEFAULT_CACHE_KEY_RULES: CacheKeyRules; /** * Creates a cache key generator function with customizable rules. */ declare function createCacheKeyGenerator(cacheKeyNormalize?: boolean | CacheKeyNormalizeOptions): CacheKeyGenerator; export { BYPASS, CACHE_KEY_HEADER_NAME, CACHE_STATUS_HEADER_NAME, CANNOT_INCLUDE_HEADERS, SharedCache as Cache, type CacheHandler, type CacheKeyGenerator, type CacheKeyRules, type CacheLogContext, type CacheOriginContext, type CacheOriginHandler, type CacheOriginPhase, type CacheResolveOptions, type CacheStatus, SharedCacheStorage as CacheStorage, DEFAULT_CACHE_KEY_RULES, DYNAMIC, EXPIRED, HIT, type KVStorage, type KeyFilterOptions, LogLevel, type Logger, MISS, REVALIDATED, SHARED_CACHE_STATUS, STALE, type SharedCacheFetch, type SharedCacheOptions, type SharedCacheQueryOptions, type SharedCacheRequestInitProperties, StructuredLogger, UPDATING, createCacheHandler, createCacheKeyGenerator, createSharedCacheFetch as createFetch, createLogger, sharedCacheFetch as fetch, resolveWithCache };