Skip to content

feat: per-call cache status and x-cache response header - #40

Merged
pi0 merged 1 commit into
mainfrom
feat/cache-status
Jul 7, 2026
Merged

pi0 merged 1 commit into
mainfrom
feat/cache-status

Conversation

@pi0x

@pi0x pi0x commented Jul 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds per-call cache status tracking and a CDN-style X-Cache response header.

Per-call cache status

  • Exposes entry.status ("hit" / "stale" / "miss") to transform for metrics/observability, describing how the value was served on the current call.
  • Status is attached to the live entry as a non-enumerable property, so it's readable by transform but never persisted to storage β€” object spreads (including expireCache) and JSON/structuredClone serialization all drop it, even for backends (like the built-in memory storage) that return entries by reference.

X-Cache header

  • Adds cacheStatusHeader to defineCachedHandler to emit a CDN-style X-Cache: HIT | MISS | STALE header.
  • Defaults to true (X-Cache); pass a string for a custom header name, or false to disable.
  • Propagated on 304 Not Modified responses.

Types

  • New CacheStatus type exported from the package root.

Tests

Added coverage for the hit/stale/miss status flow (with and without SWR), non-persistence of status (on a hit and through expireCache), and the X-Cache header (default, custom name, disabled, and on 304).

All 108 tests pass; tsgo typecheck clean.

πŸ€– Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added cache status information for cached values, so responses and transforms can tell whether data was a cache hit, miss, or stale result.
    • Added an optional cache-status response header for cached HTTP handlers, with support for a custom header name.
  • Bug Fixes
    • Cache status is now included on 304 Not Modified responses and preserved through cache refresh flows.
    • Cache status is kept out of stored cache data, preventing it from being persisted between requests.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@pi0x, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 41 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1272f2a6-d6e8-411f-a2f3-a86e2075839a

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between c1bd3f3 and c2258ad.

πŸ“’ Files selected for processing (6)
  • README.md
  • src/cache.ts
  • src/http.ts
  • src/index.ts
  • src/types.ts
  • test/index.test.ts
πŸ“ Walkthrough

Walkthrough

This PR introduces a CacheStatus type ("hit" | "stale" | "miss") computed per call in the cache read/refresh flow, attaches it as a non-enumerable, non-persisted property on cache entries, exposes it to transform, and optionally emits it as a CDN-style x-cache header (including on 304 responses) in defineCachedHandler.

Changes

CacheStatus feature

Layer / File(s) Summary
CacheStatus type and public contract
src/types.ts, src/index.ts, README.md
Defines the CacheStatus union, extends CacheEntry with status and CachedEventHandlerOptions with cacheStatusHeader, re-exports the type, and documents it in the README.
Status computation and non-persisted attachment
src/cache.ts
Computes per-call status (miss/hit/stale) using a precomputed validity flag, attaches it as a non-enumerable property on the entry, and strips it before persisting to storage.
x-cache header injection
src/http.ts
Adds a default cacheStatusHeader option, injects an uppercased status header on cache hits and on 304 responses using a derived header name.
Tests
test/index.test.ts
Adds tests covering per-call status exposure, non-persistence, and header behavior (default/custom name, disabling, 304 responses).

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Possibly related PRs

  • unjs/ocache#2: Both PRs modify defaultCacheOptions() merging behavior in src/http.ts that the new cacheStatusHeader default depends on.
  • unjs/ocache#23: Both PRs modify stale/refresh mechanics in src/types.ts/src/cache.ts that drive the new per-call status signaling.

Suggested reviewers: pi0

πŸš₯ Pre-merge checks | βœ… 5
βœ… Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage βœ… Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check βœ… Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check βœ… Passed Check skipped because no linked issues were found for this pull request.
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed The title clearly describes the main change: per-call cache status tracking and the new X-Cache response header.
✨ Finishing Touches
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cache-status

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❀️ Share

Comment @coderabbitai help to get the list of available commands.

@pi0 pi0 changed the title feat(http): cache heuristically-cacheable error statuses (#37) + per-call cache status feat(http): cache heuristically-cacheable error statuses + per-call cache status Jul 7, 2026
@pi0x pi0x changed the title feat(http): cache heuristically-cacheable error statuses + per-call cache status feat: per-call cache status + X-Cache response header Jul 7, 2026
@pi0x
pi0x force-pushed the feat/cache-status branch from 7b55025 to c1bd3f3 Compare July 7, 2026 16:09
@pi0 pi0 changed the title feat: per-call cache status + X-Cache response header feat: per-call cache status and x-cache response header Jul 7, 2026
@pi0x
pi0x force-pushed the feat/cache-status branch from c1bd3f3 to d66234f Compare July 7, 2026 16:16

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cache.ts`:
- Around line 222-234: The live cache entry is being mutated in place when
attaching status, which can corrupt shared references from ref-sharing storage
backends. Update the logic in the cache flow around Object.defineProperty(entry,
"status", ...) to clone the entry first, then attach status to the clone and use
that local copy for subsequent value/transform/X-Cache handling. Keep the change
localized to the cache entry update path so concurrent same-key calls cannot
overwrite each other’s status.
πŸͺ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f181e202-6c74-45e7-b93a-24f85feb1744

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between b16418b and c1bd3f3.

πŸ“’ Files selected for processing (6)
  • README.md
  • src/cache.ts
  • src/http.ts
  • src/index.ts
  • src/types.ts
  • test/index.test.ts

Comment thread src/cache.ts Resolved
@pi0x
pi0x force-pushed the feat/cache-status branch from d66234f to fc0fc1d Compare July 7, 2026 16:19
- Expose `entry.status` ("hit" / "stale" / "revalidated" / "miss") to
  `transform` for metrics/observability, describing how the value was served on
  this call. "revalidated" covers a prior value that was expired/invalid and
  re-resolved in the foreground (no stale served), mirroring Vercel's
  `x-vercel-cache: REVALIDATED`. Attached as a non-enumerable property so it's
  readable by `transform` but never persisted β€” object spreads (incl.
  `expireCache`) and JSON/structuredClone serialization drop it, even for
  backends that return entries by reference.
- Add `cacheStatusHeader` to `defineCachedHandler` to emit a CDN-style
  `X-Cache: HIT | STALE | REVALIDATED | MISS` header. Defaults to `true`
  (`X-Cache`); accepts a custom header name, or `false` to disable. Propagated
  on 304 responses.
- Export a new `CacheStatus` type from the package root.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants