npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cache-control-parse

v0.1.0

Published

Zero-dependency RFC 9111 Cache-Control header parser and serializer with ordering, extension support, and strict error reporting

Readme

cache-control-parse

Zero-dependency RFC 9111 Cache-Control header parser and serializer for Node.js — preserves ordering, supports no-cache="Set-Cookie" field-name arguments, surfaces extension directives, and throws on malformed input.

npm License: MIT

Quick Start

npm install cache-control-parse
import { parseCacheControl, serializeCacheControl, classifyDirectives } from 'cache-control-parse';

// Parse
const directives = parseCacheControl('max-age=60, public, no-cache="Set-Cookie"');
// → [{ name:'max-age', kind:'delta-seconds', value:60, position:0 }, ...]

// Serialize
serializeCacheControl(directives);
// → 'max-age=60, public, no-cache="Set-Cookie"'

// Classify into request / response buckets
classifyDirectives(directives, { direction: 'request' });
// → { request: { 'max-age': 60, 'public': true, 'no-cache': ['Set-Cookie'] }, response: {} }

Performance & Benchmarks

| Workload | parse (ms) | serialize (ms) | classify (ms) | |---|---|---|---| | simple | 13.98ms | 2.65ms | 3.70ms | | moderate | 64.04ms | 8.17ms | 10.95ms | | complex | 151.69ms | 16.98ms | 18.00ms | | fieldNames | 31.74ms | 6.42ms | 3.32ms | | extensions | 43.14ms | 10.21ms | 0.71ms |

50,000 iterations per workload, best of 5 runs. Node.js 3.11.15. Replicate locally: node benchmarks/run_benchmark.js

Why cache-control-parse?

The leading npm package (cache-control-parser, etienne-martin, 124k weekly downloads) flattens parsed directives into an unordered object, silently drops unknown directives, has no support for no-cache="Set-Cookie" field-name arguments, and reports no errors on malformed input. Every Node.js HTTP cache layer (Express cache middleware, Fastify caching, CDN edge workers, AWS Lambda@Edge caching code) re-implements this parsing inline.

Trade-off decisions:

  • Ordering preserved — directive order is meaningful (e.g., Cache-Control: max-age=0, no-cache is different from no-cache, max-age=0). The position field on each directive lets callers apply order-sensitive semantics.
  • Strict parsing — throws InvalidCacheControlError on malformed input; no silent fallbacks that mask configuration errors.
  • Extension directives surfaced — unknown directives are returned as kind: 'extension', not dropped.
  • Field-name argumentsprivate="X" and no-cache="Set-Cookie" are parsed as kind: 'field-names' with the field names as a string array.

Key Features

  • Zero dependencies — no transitive risk, tree-shakeable, works in any ESM environment
  • RFC 9111 compliant — parses all standard directives, quoted strings with escapes, and extension tokens
  • no-cache="Field-Name" — field-name arguments parsed as kind: 'field-names' arrays, not dropped
  • Ordering preservedposition index on each directive
  • Error reportingInvalidCacheControlError with header, position, and message on bad input
  • SerializerserializeCacheControl(directives) → canonical header string
  • ClassifierclassifyDirectives(parsed, { direction }){ request, response }
  • TypeScript typesindex.d.ts included

API Reference

parseCacheControl(header)

Parses a Cache-Control header string. Returns an array of directive objects.

const directives = parseCacheControl('max-age=60, public, no-cache="Set-Cookie"');

Each directive object:

| Property | Type | Description | |---|---|---| | name | string | Directive name (lowercase, original case preserved) | | kind | 'delta-seconds' \| 'flag' \| 'field-names' \| 'extension' | Directive category | | value | number \| boolean \| string[] \| string \| null | Parsed value | | position | number | Zero-based index in the original header |

Kinds:

  • delta-seconds: max-age, s-maxage, max-stale, min-fresh, stale-while-revalidate, stale-if-errorvalue: number
  • flag: public, private, no-cache, no-store, no-transform, only-if-cached, must-revalidate, proxy-revalidate, immutable, must-understandvalue: null
  • field-names: private="...", no-cache="..."value: string[]
  • extension: everything else → value: string | null

Throws InvalidCacheControlError on malformed input (invalid token, unclosed quote, adjacent tokens without comma, non-numeric delta-seconds).

serializeCacheControl(directives)

Serializes an array of directive objects back to a canonical header string.

serializeCacheControl([
  { name: 'max-age', kind: 'delta-seconds', value: 60, position: 0 },
  { name: 'public', kind: 'flag', value: null, position: 1 },
]);
// → 'max-age=60, public'

Throws InvalidCacheControlError if directives is not an array.

classifyDirectives(directives, options?)

Classifies directives into request-specific and response-specific buckets.

const { request, response } = classifyDirectives(parsed, { direction: 'auto' });

Options: | Option | Type | Default | Description | |---|---|---|---| | direction | 'auto' \| 'request' \| 'response' | 'auto' | Which bucket set to populate |

  • auto (default): shared directives (max-age, no-cache, etc.) go to request only; response-only go to response only
  • request: populates request only; all recognized request directives included
  • response: populates response only; all recognized response directives included

Unknown extensions are skipped (they go in neither bucket; inspect the raw directives array).

InvalidCacheControlError

import { InvalidCacheControlError } from 'cache-control-parse';

try {
  parseCacheControl('max-age=abc');
} catch (e) {
  if (e instanceof InvalidCacheControlError) {
    console.log(e.header);    // 'max-age=abc'
    console.log(e.position);  // 0
    console.log(e.message);   // '...'
  }
}

Properties: name, message, header, position, stack.

CLI

# Parse and classify as request
node cli.mjs "max-age=60, no-cache" --classify --direction request

# JSON output
node cli.mjs "max-age=60" --json

# Classify response directives
node cli.mjs "public, s-maxage=60" --classify --direction response

Limitations

  • private="X" field-name argument is parsed but not validated against known response headers.
  • Extension directives are preserved as opaque tokens; no semantic interpretation.
  • s-maxage in a request header is treated as an extension (RFC 9111 says response-only).
  • The parser does not enforce HTTP semantics (e.g., duplicate directives are preserved per input, not deduplicated).
  • HTTP freshness lifetime calculation (RFC 9111 §4.2) is out of scope — caller computes from parsed directives.

Non-Goals

  • Freshness / expiry calculation (out of scope — use a separate HTTP date library)
  • Header list deduplication (out of scope — caller handles multi-header Cache-Control values)
  • Node.js HTTP response header object coercion (out of scope — the string API is universal)
  • Browser-native Headers object integration (out of scope — parses any string, caller does the wire format)
  • HTTP/2 push promises, trailers, or alternative representations (out of scope)

License

MIT — see LICENSE.