Skip to content

Commit 4e5a8fe

Browse files
committed
feat: implement better cache hit detection
1 parent 4f26f70 commit 4e5a8fe

File tree

1 file changed

+72
-2
lines changed

1 file changed

+72
-2
lines changed

packages/nuxt/src/runtime/plugins/storage.server.ts

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from '@sentry/core';
1414
// eslint-disable-next-line import/no-extraneous-dependencies
1515
import { defineNitroPlugin, useStorage } from 'nitropack/runtime';
16+
import type { CacheEntry, ResponseCacheEntry } from 'nitropack/types';
1617
import type { Driver, Storage } from 'unstorage';
1718
// @ts-expect-error - This is a virtual module
1819
import { userStorageMounts } from '#sentry/storage-config.mjs';
@@ -153,7 +154,7 @@ function createMethodWrapper(
153154
span.setStatus({ code: SPAN_STATUS_OK });
154155

155156
if (CACHE_HIT_METHODS.has(methodName)) {
156-
span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_HIT, !isEmptyValue(result));
157+
span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_HIT, isCacheHit(args?.[0] ?? '', result));
157158
}
158159

159160
return result;
@@ -229,6 +230,75 @@ function normalizeMethodName(methodName: string): string {
229230
/**
230231
* Checks if the value is empty, used for cache hit detection.
231232
*/
232-
function isEmptyValue(value: unknown): boolean {
233+
function isEmptyValue(value: unknown): value is null | undefined {
233234
return value === null || value === undefined;
234235
}
236+
237+
const CACHED_FN_HANDLERS_RE = /^nitro:(functions|handlers):/i;
238+
239+
/**
240+
* Since Nitro's cache may not utilize the driver's TTL, it is possible that the value is present in the cache but won't be used by Nitro.
241+
* The maxAge and expires values is serialized by Nitro in the cache entry. This means the value presence does not necessarily mean a cache hit.
242+
* So in order to properly report cache hits for `defineCachedFunction` and `defineCachedEventHandler` we need to check the cached value ourselves.
243+
* First we check if the key matches the `defineCachedFunction` or `defineCachedEventHandler` key patterns, and if so we check the cached value.
244+
*/
245+
function isCacheHit(key: string, value: unknown): boolean {
246+
const isEmpty = isEmptyValue(value);
247+
// Empty value means no cache hit either way
248+
// Or if key doesn't match the cached function or handler patterns, we can return the empty value check
249+
if (isEmpty || !CACHED_FN_HANDLERS_RE.test(key)) {
250+
return !isEmpty;
251+
}
252+
253+
try {
254+
return validateCacheEntry(key, JSON.parse(String(value)) as CacheEntry);
255+
} catch (error) {
256+
// this is a best effort, so we return false if we can't validate the cache entry
257+
return false;
258+
}
259+
}
260+
261+
/**
262+
* Validates the cache entry.
263+
*/
264+
function validateCacheEntry(
265+
key: string,
266+
entry: CacheEntry | CacheEntry<ResponseCacheEntry & { status: number }>,
267+
): boolean {
268+
if (isEmptyValue(entry.value)) {
269+
return false;
270+
}
271+
272+
// Date.now is used by Nitro internally, so safe to use here.
273+
// https://github.com/nitrojs/nitro/blob/5508f71b77730e967fb131de817725f5aa7c4862/src/runtime/internal/cache.ts#L78
274+
if (Date.now() > (entry.expires || 0)) {
275+
return false;
276+
}
277+
278+
/**
279+
* Pulled from Nitro's cache entry validation
280+
* https://github.com/nitrojs/nitro/blob/5508f71b77730e967fb131de817725f5aa7c4862/src/runtime/internal/cache.ts#L223-L241
281+
*/
282+
if (isResponseCacheEntry(key, entry)) {
283+
if (entry.value.status >= 400) {
284+
return false;
285+
}
286+
287+
if (entry.value.body === undefined) {
288+
return false;
289+
}
290+
291+
if (entry.value.headers.etag === 'undefined' || entry.value.headers['last-modified'] === 'undefined') {
292+
return false;
293+
}
294+
}
295+
296+
return true;
297+
}
298+
299+
/**
300+
* Checks if the cache entry is a response cache entry.
301+
*/
302+
function isResponseCacheEntry(key: string, _: CacheEntry): _ is CacheEntry<ResponseCacheEntry & { status: number }> {
303+
return key.startsWith('nitro:handlers:');
304+
}

0 commit comments

Comments
 (0)