@remix-run/headers
Version:
A toolkit for working with HTTP headers in JavaScript
312 lines (305 loc) • 12 kB
text/typescript
import { type HeaderValue } from './header-value.ts';
import { parseParams } from './param-values.ts';
// Taken from https://github.com/jjenzz/pretty-cache-header by jjenzz
// License: MIT https://github.com/jjenzz/pretty-cache-header/blob/main/LICENSE
export interface CacheControlInit {
/**
* The `max-age=N` **request directive** indicates that the client allows a stored response that
* is generated on the origin server within _N_ seconds — where _N_ may be any non-negative
* integer (including `0`).
*
* The `max-age=N` **response directive** indicates that the response remains
* [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age)
* until _N_ seconds after the response is generated.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#max-age)
*/
maxAge?: number;
/**
* The `max-stale=N` **request directive** indicates that the client allows a stored response
* that is [stale](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age)
* within _N_ seconds.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#max-stale)
*/
maxStale?: number;
/**
* The `min-fresh=N` **request directive** indicates that the client allows a stored response
* that is [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age)
* for at least _N_ seconds.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#min-fresh)
*/
minFresh?: number;
/**
* The `s-maxage` **response directive** also indicates how long the response is
* [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age) for (similar to `max-age`) —
* but it is specific to shared caches, and they will ignore `max-age` when it is present.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#s-maxage)
*/
sMaxage?: number;
/**
* The `no-cache` **request directive** asks caches to validate the response with the origin
* server before reuse. If you want caches to always check for content updates while reusing
* stored content, `no-cache` is the directive to use.
*
* The `no-cache` **response directive** indicates that the response can be stored in caches, but
* the response must be validated with the origin server before each reuse, even when the cache
* is disconnected from the origin server.
*
* `no-cache` allows clients to request the most up-to-date response even if the cache has a
* [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age)
* response.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#no-cache)
*/
noCache?: true;
/**
* The `no-store` **request directive** allows a client to request that caches refrain from
* storing the request and corresponding response — even if the origin server's response could
* be stored.
*
* The `no-store` **response directive** indicates that any caches of any kind (private or shared)
* should not store this response.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#no-store)
*/
noStore?: true;
/**
* `no-transform` indicates that any intermediary (regardless of whether it implements a cache)
* shouldn't transform the response contents.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#no-transform)
*/
noTransform?: true;
/**
* The client indicates that cache should obtain an already-cached response. If a cache has
* stored a response, it's reused.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#only-if-cached)
*/
onlyIfCached?: true;
/**
* The `must-revalidate` **response directive** indicates that the response can be stored in
* caches and can be reused while [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age).
* If the response becomes [stale](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age),
* it must be validated with the origin server before reuse.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#must-revalidate)
*/
mustRevalidate?: true;
/**
* The `proxy-revalidate` **response directive** is the equivalent of `must-revalidate`, but
* specifically for shared caches only.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#proxy-revalidate)
*/
proxyRevalidate?: true;
/**
* The `must-understand` **response directive** indicates that a cache should store the response
* only if it understands the requirements for caching based on status code.
*
* `must-understand` should be coupled with `no-store` for fallback behavior.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#must-understand)
*/
mustUnderstand?: true;
/**
* The `private` **response directive** indicates that the response can be stored only in a
* private cache (e.g. local caches in browsers).
*
* You should add the `private` directive for user-personalized content, especially for responses
* received after login and for sessions managed via cookies.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#private)
*/
private?: true;
/**
* The `public` **response directive** indicates that the response can be stored in a shared
* cache. Responses for requests with `Authorization` header fields must not be stored in a
* shared cache; however, the `public` directive will cause such responses to be stored in a
* shared cache.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#public)
*/
public?: true;
/**
* The `immutable` **response directive** indicates that the response will not be updated while
* it's [fresh](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age).
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#public)
*/
immutable?: true;
/**
* The `stale-while-revalidate` **response directive** indicates that the cache could reuse a
* stale response while it revalidates it to a cache.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#stale-while-revalidate)
*/
staleWhileRevalidate?: number;
/**
* The `stale-if-error` **response directive** indicates that the cache can reuse a
* [stale response](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#fresh_and_stale_based_on_age)
* when an upstream server generates an error, or when the error is generated locally. Here, an
* error is considered any response with a status code of 500, 502, 503, or 504.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#stale-if-error)
*/
staleIfError?: number;
}
/**
* The value of a `Cache-Control` HTTP header.
*
* [MDN `Cache-Control` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)
*
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7234#section-5.2)
*/
export class CacheControl implements HeaderValue, CacheControlInit {
maxAge?: number;
maxStale?: number;
minFresh?: number;
sMaxage?: number;
noCache?: true;
noStore?: true;
noTransform?: true;
onlyIfCached?: true;
mustRevalidate?: true;
proxyRevalidate?: true;
mustUnderstand?: true;
private?: true;
public?: true;
immutable?: true;
staleWhileRevalidate?: number;
staleIfError?: number;
constructor(init?: string | CacheControlInit) {
if (init) {
if (typeof init === 'string') {
let params = parseParams(init, ',');
if (params.length > 0) {
for (let [name, value] of params) {
switch (name) {
case 'max-age':
this.maxAge = Number(value);
break;
case 'max-stale':
this.maxStale = Number(value);
break;
case 'min-fresh':
this.minFresh = Number(value);
break;
case 's-maxage':
this.sMaxage = Number(value);
break;
case 'no-cache':
this.noCache = true;
break;
case 'no-store':
this.noStore = true;
break;
case 'no-transform':
this.noTransform = true;
break;
case 'only-if-cached':
this.onlyIfCached = true;
break;
case 'must-revalidate':
this.mustRevalidate = true;
break;
case 'proxy-revalidate':
this.proxyRevalidate = true;
break;
case 'must-understand':
this.mustUnderstand = true;
break;
case 'private':
this.private = true;
break;
case 'public':
this.public = true;
break;
case 'immutable':
this.immutable = true;
break;
case 'stale-while-revalidate':
this.staleWhileRevalidate = Number(value);
break;
case 'stale-if-error':
this.staleIfError = Number(value);
break;
}
}
}
} else {
this.maxAge = init.maxAge;
this.maxStale = init.maxStale;
this.minFresh = init.minFresh;
this.sMaxage = init.sMaxage;
this.noCache = init.noCache;
this.noStore = init.noStore;
this.noTransform = init.noTransform;
this.onlyIfCached = init.onlyIfCached;
this.mustRevalidate = init.mustRevalidate;
this.proxyRevalidate = init.proxyRevalidate;
this.mustUnderstand = init.mustUnderstand;
this.private = init.private;
this.public = init.public;
this.immutable = init.immutable;
this.staleWhileRevalidate = init.staleWhileRevalidate;
this.staleIfError = init.staleIfError;
}
}
}
toString(): string {
let parts = [];
if (this.public) {
parts.push('public');
}
if (this.private) {
parts.push('private');
}
if (typeof this.maxAge === 'number') {
parts.push(`max-age=${this.maxAge}`);
}
if (typeof this.sMaxage === 'number') {
parts.push(`s-maxage=${this.sMaxage}`);
}
if (this.noCache) {
parts.push('no-cache');
}
if (this.noStore) {
parts.push('no-store');
}
if (this.noTransform) {
parts.push('no-transform');
}
if (this.onlyIfCached) {
parts.push('only-if-cached');
}
if (this.mustRevalidate) {
parts.push('must-revalidate');
}
if (this.proxyRevalidate) {
parts.push('proxy-revalidate');
}
if (this.mustUnderstand) {
parts.push('must-understand');
}
if (this.immutable) {
parts.push('immutable');
}
if (typeof this.staleWhileRevalidate === 'number') {
parts.push(`stale-while-revalidate=${this.staleWhileRevalidate}`);
}
if (typeof this.staleIfError === 'number') {
parts.push(`stale-if-error=${this.staleIfError}`);
}
if (typeof this.maxStale === 'number') {
parts.push(`max-stale=${this.maxStale}`);
}
if (typeof this.minFresh === 'number') {
parts.push(`min-fresh=${this.minFresh}`);
}
return parts.join(', ');
}
}