diff --git a/src/runtime/internal/cache.ts b/src/runtime/internal/cache.ts index 76aad1ce83..ad6a7e1074 100644 --- a/src/runtime/internal/cache.ts +++ b/src/runtime/internal/cache.ts @@ -13,6 +13,16 @@ import type { CacheOptions, CachedEventHandlerOptions } from "nitro/types"; let _storageReady = false; +/** + * Sanitize a cache group or name so it is safe for filesystem-based storage + * drivers (fs, fs-lite). Unstorage maps ":" to "/" on disk, so any "/" already + * present in the value would create unexpected directory nesting and cause + * ENOTDIR errors when a path is both a file and a prefix of another path. + */ +function sanitizeCacheKey(value: string | undefined): string | undefined { + return value?.replace(/\//g, "_"); +} + function ensureStorage() { if (_storageReady) { return; @@ -37,9 +47,10 @@ export function defineCachedFunction( ): (...args: ArgsT) => Promise { ensureStorage(); return _defineCachedFunction(fn, { - group: "nitro/functions", onError: defaultOnError, ...opts, + group: sanitizeCacheKey(opts.group || "nitro-functions"), + name: sanitizeCacheKey(opts.name), }); } @@ -49,12 +60,13 @@ export function defineCachedHandler( ): EventHandler { ensureStorage(); const ocacheHandler = _defineCachedHandler(handler as any, { - group: "nitro/handlers", onError: defaultOnError, toResponse: (value, event) => toResponse(value, event as H3Event), createResponse: (body, init) => new FastResponse(body, init), handleCacheHeaders: (event, conditions) => handleCacheHeaders(event as H3Event, conditions), ...opts, + group: sanitizeCacheKey(opts.group || "nitro-handlers"), + name: sanitizeCacheKey(opts.name), }); return defineHandler((event) => ocacheHandler(event as any)); } diff --git a/src/runtime/internal/route-rules.ts b/src/runtime/internal/route-rules.ts index 1100bc50a6..482e76b657 100644 --- a/src/runtime/internal/route-rules.ts +++ b/src/runtime/internal/route-rules.ts @@ -70,9 +70,14 @@ export const cache: RouteRuleCtor<"cache"> = ((m) => const key = `${m.route}:${route}`; let cachedHandler = cachedHandlers.get(key); if (!cachedHandler) { + // Sanitize the name to avoid filesystem path conflicts. + // Route paths contain "/" which unstorage's fs drivers interpret as + // directory separators, causing ENOTDIR when a path like "/foo" is + // cached as a file but "/foo/bar" needs it to be a directory. + const safeName = key.replace(/\//g, "_"); cachedHandler = defineCachedHandler(handler, { - group: "nitro/route-rules", - name: key, + group: "nitro-route-rules", + name: safeName, ...m.options, }); cachedHandlers.set(key, cachedHandler);