Skip to content

Cache Metadata

Sometimes you need to know what actually happened when you queried the cache. Was it a hit? A miss? Did you get stale data while a fresh fetch runs in the background? The .entries property gives you this visibility.

By default, queries just return your data:

const user = await cache.getUser('123');
// user is User, but was this from cache? Fresh? Stale?

This is usually fine. But sometimes you need to:

  • Show a “cached” indicator in your UI
  • Log cache performance metrics
  • Trigger different behavior for stale vs fresh data
  • Debug caching issues

Every query has an .entries property that returns cache metadata instead of just the value:

const result = await cache.getUser('123').entries;
result.before // CacheEntry<User> | null
result.after // CacheEntry<User>

The before entry is the cache state before the query ran. The after entry is the cache state after. Together they tell you exactly what happened.

before = null
after = { value: ..., createdAt: now }

No cached entry existed. Fresh data was fetched and cached.

before = { value: ..., createdAt: earlier }
after = same object as before

A valid cached entry existed and was returned. before === after (same reference).

before = { value: ..., createdAt: earlier, expiresAt: past }
after = same object as before

The cached entry was expired but within grace period. Stale data was returned immediately while a background refresh runs. before === after.

before = { value: old, createdAt: earlier }
after = { value: new, createdAt: now }

The cached entry was expired with no grace period, or outside grace. Fresh data was fetched synchronously. before !== after.

Each entry contains:

interface CacheEntry<T> {
value: T; // The cached data
tags: Tag[]; // Tags for invalidation
createdAt: number; // Unix timestamp (ms)
expiresAt: number; // When TTL expires
graceUntil: number | null; // When grace period ends
}
async function getUserWithStatus(id: string) {
const result = await cache.getUser(id).entries;
const now = Date.now();
let status: 'miss' | 'hit' | 'stale';
if (result.before === null) {
status = 'miss';
} else if (result.after.expiresAt > now) {
status = 'hit';
} else {
status = 'stale';
}
return {
user: result.after.value,
cacheStatus: status,
cachedAt: new Date(result.after.createdAt),
};
}

Use .entries when you need cache visibility. Don’t use it when you just want your data—the regular query is simpler and avoids allocating the result wrapper.

// Just need the data? Use the regular query.
const user = await cache.getUser('123');
// Need cache metadata? Use .entries.
const { before, after } = await cache.getUser('123').entries;

Both forms are efficient—.entries doesn’t do extra work, it just exposes what was already computed.