@@ -14,6 +14,7 @@ import {
1414} from '@sentry/core' ;
1515// eslint-disable-next-line import/no-extraneous-dependencies
1616import { defineNitroPlugin , useStorage } from 'nitropack/runtime' ;
17+ import type { CacheEntry , ResponseCacheEntry } from 'nitropack/types' ;
1718import type { Driver , Storage } from 'unstorage' ;
1819// @ts -expect-error - This is a virtual module
1920import { userStorageMounts } from '#sentry/storage-config.mjs' ;
@@ -131,7 +132,7 @@ function createMethodWrapper(
131132 span . setStatus ( { code : SPAN_STATUS_OK } ) ;
132133
133134 if ( CACHE_HIT_METHODS . has ( methodName ) ) {
134- span . setAttribute ( SEMANTIC_ATTRIBUTE_CACHE_HIT , ! isEmptyValue ( result ) ) ;
135+ span . setAttribute ( SEMANTIC_ATTRIBUTE_CACHE_HIT , isCacheHit ( args [ 0 ] , result ) ) ;
135136 }
136137
137138 return result ;
@@ -185,7 +186,7 @@ function normalizeMethodName(methodName: string): string {
185186/**
186187 * Checks if the value is empty, used for cache hit detection.
187188 */
188- function isEmptyValue ( value : unknown ) : boolean {
189+ function isEmptyValue ( value : unknown ) : value is null | undefined {
189190 return value === null || value === undefined ;
190191}
191192
@@ -242,3 +243,72 @@ function normalizeKey(key: unknown, prefix: string): string {
242243
243244 return `${ prefix } ${ isEmptyValue ( key ) ? '' : String ( key ) } ` ;
244245}
246+
247+ const CACHED_FN_HANDLERS_RE = / ^ n i t r o : ( f u n c t i o n s | h a n d l e r s ) : / i;
248+
249+ /**
250+ * 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.
251+ * 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.
252+ * So in order to properly report cache hits for `defineCachedFunction` and `defineCachedEventHandler` we need to check the cached value ourselves.
253+ * First we check if the key matches the `defineCachedFunction` or `defineCachedEventHandler` key patterns, and if so we check the cached value.
254+ */
255+ function isCacheHit ( key : string , value : unknown ) : boolean {
256+ const isEmpty = isEmptyValue ( value ) ;
257+ // Empty value means no cache hit either way
258+ // Or if key doesn't match the cached function or handler patterns, we can return the empty value check
259+ if ( isEmpty || ! CACHED_FN_HANDLERS_RE . test ( key ) ) {
260+ return ! isEmpty ;
261+ }
262+
263+ try {
264+ return validateCacheEntry ( key , JSON . parse ( String ( value ) ) as CacheEntry ) ;
265+ } catch ( error ) {
266+ // this is a best effort, so we return false if we can't validate the cache entry
267+ return false ;
268+ }
269+ }
270+
271+ /**
272+ * Validates the cache entry.
273+ */
274+ function validateCacheEntry (
275+ key : string ,
276+ entry : CacheEntry | CacheEntry < ResponseCacheEntry & { status : number } > ,
277+ ) : boolean {
278+ if ( isEmptyValue ( entry . value ) ) {
279+ return false ;
280+ }
281+
282+ // Date.now is used by Nitro internally, so safe to use here.
283+ // https://github.com/nitrojs/nitro/blob/5508f71b77730e967fb131de817725f5aa7c4862/src/runtime/internal/cache.ts#L78
284+ if ( Date . now ( ) > ( entry . expires || 0 ) ) {
285+ return false ;
286+ }
287+
288+ /**
289+ * Pulled from Nitro's cache entry validation
290+ * https://github.com/nitrojs/nitro/blob/5508f71b77730e967fb131de817725f5aa7c4862/src/runtime/internal/cache.ts#L223-L241
291+ */
292+ if ( isResponseCacheEntry ( key , entry ) ) {
293+ if ( entry . value . status >= 400 ) {
294+ return false ;
295+ }
296+
297+ if ( entry . value . body === undefined ) {
298+ return false ;
299+ }
300+
301+ if ( entry . value . headers . etag === 'undefined' || entry . value . headers [ 'last-modified' ] === 'undefined' ) {
302+ return false ;
303+ }
304+ }
305+
306+ return true ;
307+ }
308+
309+ /**
310+ * Checks if the cache entry is a response cache entry.
311+ */
312+ function isResponseCacheEntry ( key : string , _ : CacheEntry ) : _ is CacheEntry < ResponseCacheEntry & { status : number } > {
313+ return key . startsWith ( 'nitro:handlers:' ) ;
314+ }
0 commit comments