{"id":16059,"library":"http-cache-semantics","title":"HTTP Cache Semantics Policy","description":"This library provides a robust implementation of HTTP caching logic as defined by RFC 7234/9111 (for user agents and shared caches) and RFC 5861 (for `stale-if-error` and `stale-while-revalidate`). It allows developers to determine the cacheability of HTTP responses and whether a stored response can satisfy a new request, taking into account complex factors like the `Vary` header, proxy revalidation, and authenticated responses. The current stable version is 4.2.0. The library helps in building correct HTTP caches and proxies by abstracting away the intricacies of cache control directives, providing methods like `storable()` to check if a response can be cached, and `satisfiesWithoutRevalidation()` to validate if a cached entry is suitable for a subsequent request. It offers fine-grained control via constructor options like `shared`, `cacheHeuristic`, and `immutableMinTimeToLive`.","status":"active","version":"4.2.0","language":"javascript","source_language":"en","source_url":"https://github.com/kornelski/http-cache-semantics","tags":["javascript","typescript"],"install":[{"cmd":"npm install http-cache-semantics","lang":"bash","label":"npm"},{"cmd":"yarn add http-cache-semantics","lang":"bash","label":"yarn"},{"cmd":"pnpm add http-cache-semantics","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"Main class for evaluating HTTP cache policy. The library ships with TypeScript types and supports both ESM and CommonJS.","wrong":"const CachePolicy = require('http-cache-semantics').CachePolicy;","symbol":"CachePolicy","correct":"import { CachePolicy } from 'http-cache-semantics';"},{"note":"TypeScript type for the constructor options of CachePolicy, useful for type-checking.","symbol":"CachePolicyOptions","correct":"import type { CachePolicyOptions } from 'http-cache-semantics';"},{"note":"CachePolicy is a named export, not a default export. Incorrectly using a default import will result in a TypeError.","wrong":"import CachePolicy from 'http-cache-semantics';","symbol":"CachePolicy","correct":"import { CachePolicy } from 'http-cache-semantics';"}],"quickstart":{"code":"import { CachePolicy } from 'http-cache-semantics';\n\n// Simulate an initial request and its corresponding response\nconst initialRequest = {\n    url: 'https://api.example.com/data/items',\n    method: 'GET',\n    headers: {\n        'accept': 'application/json',\n        'authorization': 'Bearer mysecrettoken'\n    },\n};\n\nconst initialResponse = {\n    status: 200,\n    headers: {\n        'cache-control': 'public, max-age=3600, s-maxage=600',\n        'content-type': 'application/json',\n        'vary': 'Accept, Authorization', // Note: Library expects lowercase, but here we simulate a common server response\n        'date': new Date().toUTCString(),\n    },\n    body: '{\"id\": 1, \"name\": \"Cached Item\"}',\n};\n\n// Options for the cache policy evaluation\nconst options = {\n    shared: true, // Evaluate from a shared cache perspective (e.g., a proxy)\n    cacheHeuristic: 0.1, // 10% of response's age as fallback TTL\n    immutableMinTimeToLive: 24 * 3600 * 1000, // 24 hours for 'immutable'\n};\n\n// --- Step 1: Create and store the cache policy for an incoming response ---\n// Ensure headers are lowercase before passing to CachePolicy\nconst lowercaseInitialRequest = { ...initialRequest, headers: Object.fromEntries(Object.entries(initialRequest.headers).map(([k, v]) => [k.toLowerCase(), v])) };\nconst lowercaseInitialResponse = { ...initialResponse, headers: Object.fromEntries(Object.entries(initialResponse.headers).map(([k, v]) => [k.toLowerCase(), v])) };\n\nconst policy = new CachePolicy(lowercaseInitialRequest, lowercaseInitialResponse, options);\n\nif (!policy.storable()) {\n    console.log(\"Initial response is not storable in the cache. Discarding.\");\n} else {\n    const letsPretendThisIsSomeCache = new Map<string, { policy: CachePolicy, body: string }>();\n    const timeToLive = policy.timeToLive();\n    console.log(`Response is storable. Estimated time to live: ${timeToLive}ms`);\n\n    // Store the policy object along with the response body\n    letsPretendThisIsSomeCache.set(initialRequest.url, { policy, body: initialResponse.body });\n\n    // --- Step 2: Later, an identical new request comes in ---\n    const newRequest = {\n        url: 'https://api.example.com/data/items',\n        method: 'GET',\n        headers: {\n            'accept': 'application/json',\n            'authorization': 'Bearer mysecrettoken'\n        },\n    };\n\n    const cachedEntry = letsPretendThisIsSomeCache.get(newRequest.url);\n\n    if (cachedEntry) {\n        // Ensure headers are lowercase for the new request too\n        const lowercaseNewRequest = { ...newRequest, headers: Object.fromEntries(Object.entries(newRequest.headers).map(([k, v]) => [k.toLowerCase(), v])) };\n\n        if (cachedEntry.policy.satisfiesWithoutRevalidation(lowercaseNewRequest)) {\n            // The cached response is valid for the new request without revalidation\n            const responseHeaders = cachedEntry.policy.responseHeaders();\n            console.log(\"Cached response can be used without revalidation.\");\n            console.log(\"Updated response headers for client (includes Age, removes private headers):\", responseHeaders);\n            // In a real application, you would send { headers: responseHeaders, body: cachedEntry.body } to the client.\n        } else {\n            console.log(\"Cache hit, but revalidation is required or response is not suitable for this new request.\");\n            // Implement revalidation logic using policy.revalidationHeaders() and policy.revalidatedPolicy()\n        }\n    } else {\n        console.log(\"Cache miss. No entry found for this URL.\");\n        // Fetch fresh data\n    }\n}","lang":"typescript","description":"Demonstrates the basic workflow of `http-cache-semantics`: creating a `CachePolicy` from an initial request/response, checking its storability, caching it, and then using `satisfiesWithoutRevalidation()` to determine if a subsequent request can be served from the cache, respecting `Vary` headers and other HTTP rules."},"warnings":[{"fix":"Ensure all header keys in your request and response objects are converted to lowercase before passing them to the CachePolicy constructor, e.g., `{'Content-Type': '...'}` should be `{'content-type': '...'}`.","message":"All request and response header names passed to `CachePolicy` must be provided in lowercase. Failing to do so will result in incorrect caching behavior, as the library expects standardized header processing.","severity":"gotcha","affected_versions":">=1.0"},{"fix":"Explicitly set `shared: true` for HTTP clients (like proxies) and `shared: false` for single-user browser-like caches, based on your application's caching context and intended behavior.","message":"Misunderstanding the `shared` option can lead to incorrect cacheability decisions. If `options.shared` is `true` (the default), `private` responses are not cacheable, and `s-maxage` takes precedence. If `false`, then `private` is cacheable, and `s-maxage` is ignored. This is critical for distinguishing between shared proxies and single-user caches.","severity":"gotcha","affected_versions":">=1.0"},{"fix":"Always use `policy.satisfiesWithoutRevalidation(newRequest)` to check if a cached response is valid for an incoming request. Only rely on `policy.storable()` for initial cache-eligibility checks.","message":"The `storable()` method only indicates if a response *can* be stored in the cache. To determine if a *cached* response can satisfy a *new* request, you *must* use `satisfiesWithoutRevalidation(newRequest)`. This method accounts for `Vary` headers, request-specific cache directives, and other critical HTTP rules.","severity":"gotcha","affected_versions":">=1.0"},{"fix":"Ensure your cache key for `Vary`-dependent responses is sufficiently granular, or rely on `satisfiesWithoutRevalidation()` to perform the necessary checks. The library handles `Vary` internally, but client-side logic should respect its implications.","message":"Improper handling of the `Vary` header is a common cause of incorrect caching. `http-cache-semantics` automatically considers `Vary` in `satisfiesWithoutRevalidation()`, but developers must be aware that a cached response might not be valid if the `Vary` header fields on the new request do not match the original cached request's headers.","severity":"gotcha","affected_versions":">=1.0"},{"fix":"Only enable `ignoreCargoCult: true` when explicitly dealing with servers known to send misleading or anti-pattern `Cache-Control` directives, and after careful consideration of caching implications.","message":"Using `ignoreCargoCult: true` can override common anti-cache directives (like `pre-check` and `post-check`) if non-standard directives are present. While useful for problematic origins, indiscriminately enabling this option might lead to over-caching of responses intended to be fresh.","severity":"gotcha","affected_versions":">=1.0"}],"env_vars":null,"last_verified":"2026-04-21T00:00:00.000Z","next_check":"2026-07-20T00:00:00.000Z","problems":[{"fix":"Ensure both `request` and `response` objects always include a `headers` property, even if it's an empty object (`{}`), before being used to instantiate `CachePolicy`.","cause":"The `request` or `response` object passed to the `CachePolicy` constructor is missing the `headers` property, or it is `null`/`undefined`.","error":"TypeError: Cannot read properties of undefined (reading 'headers')"},{"fix":"Always use `policy.satisfiesWithoutRevalidation(newRequest)` to check if a cached response can be validly served for a new request. This method correctly evaluates `Vary` headers and other request-specific conditions, which simple freshness checks like `timeToLive()` do not.","cause":"The cached response has a `Vary` header, and the headers of the new request do not match those of the original request used to create the cache entry, or the new request contains restrictive cache control directives (e.g., `Cache-Control: no-cache`).","error":"Cached response not served, even though 'Cache-Control: max-age' indicates it should be fresh."},{"fix":"Set `options.shared: true` (the default) for shared caches (e.g., HTTP proxies) to respect `s-maxage` and treat `private` as non-cacheable. Set `options.shared: false` for single-user caches (e.g., browser-like caches) to ignore `s-maxage` and allow caching of `private` responses.","cause":"The `shared` option in the `CachePolicy` constructor is not correctly configured for the caching environment (e.g., `shared: false` for a proxy, or `shared: true` for a single-user cache leading to private data exposure).","error":"Incorrect caching of `private` or `s-maxage` responses in a shared/single-user context."}],"ecosystem":"npm"}