import { captureException, debug, flushIfServerless, SEMANTIC_ATTRIBUTE_CACHE_HIT, SEMANTIC_ATTRIBUTE_CACHE_KEY, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_STATUS_ERROR, SPAN_STATUS_OK, startSpan } from "@sentry/core"; const CACHE_HIT_METHODS = /* @__PURE__ */ new Set(["hasItem", "getItem", "getItemRaw"]); export async function createStoragePlugin(useStorage, userStorageMounts) { const storage = useStorage(); const userMounts = new Set(userStorageMounts.map((m) => `${m}:`)); debug.log("[storage] Starting to instrument storage drivers..."); userMounts.add("cache:"); userMounts.add(""); const mounts = storage.getMounts(); for (const mount of mounts) { if (!userMounts.has(mount.base)) { continue; } instrumentDriver(mount.driver, mount.base); } storage.mount = wrapStorageMount(storage); } function instrumentDriver(driver, mountBase) { if (driver.__sentry_instrumented__) { debug.log(`[storage] Driver already instrumented: "${driver.name}". Skipping...`); return driver; } debug.log(`[storage] Instrumenting driver: "${driver.name}" on mount: "${mountBase}"`); const methodsToInstrument = [ "hasItem", "getItem", "getItemRaw", "getItems", "setItem", "setItemRaw", "setItems", "removeItem", "getKeys", "clear" ]; for (const methodName of methodsToInstrument) { const original = driver[methodName]; if (typeof original !== "function") { continue; } driver[methodName] = createMethodWrapper(original, methodName, driver, mountBase); } driver.__sentry_instrumented__ = true; return driver; } function createMethodWrapper(original, methodName, driver, mountBase) { return new Proxy(original, { async apply(target, thisArg, args) { const options = createSpanStartOptions(methodName, driver, mountBase, args); debug.log(`[storage] Running method: "${methodName}" on driver: "${driver.name ?? "unknown"}"`); return startSpan(options, async (span) => { try { const result = await target.apply(thisArg, args); span.setStatus({ code: SPAN_STATUS_OK }); if (CACHE_HIT_METHODS.has(methodName)) { span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_HIT, isCacheHit(args[0], result)); } return result; } catch (error) { span.setStatus({ code: SPAN_STATUS_ERROR, message: "internal_error" }); captureException(error, { mechanism: { handled: false, type: options.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] } }); throw error; } finally { await flushIfServerless(); } }); } }); } function wrapStorageMount(storage) { const original = storage.mount; if (original.__sentry_instrumented__) { return original; } function mountWithInstrumentation(base, driver) { debug.log(`[storage] Instrumenting mount: "${base}"`); const instrumentedDriver = instrumentDriver(driver, base); return original(base, instrumentedDriver); } mountWithInstrumentation.__sentry_instrumented__ = true; return mountWithInstrumentation; } function normalizeMethodName(methodName) { return methodName.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`); } function isEmptyValue(value) { return value === null || value === void 0; } function createSpanStartOptions(methodName, driver, mountBase, args) { const keys = getCacheKeys(args?.[0], mountBase); const attributes = { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `cache.${normalizeMethodName(methodName)}`, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.cache.nuxt", [SEMANTIC_ATTRIBUTE_CACHE_KEY]: keys.length > 1 ? keys : keys[0], "db.operation.name": methodName, "db.collection.name": mountBase.replace(/:$/, ""), "db.system.name": driver.name ?? "unknown" }; return { name: keys.join(", "), attributes }; } function getCacheKeys(key, prefix) { if (Array.isArray(key)) { return key.map((k) => normalizeKey(k, prefix)); } return [normalizeKey(key, prefix)]; } function normalizeKey(key, prefix) { if (typeof key === "string") { return `${prefix}${key}`; } if (typeof key === "object" && key !== null && "key" in key) { return `${prefix}${key.key}`; } return `${prefix}${isEmptyValue(key) ? "" : String(key)}`; } const CACHED_FN_HANDLERS_RE = /^nitro:(functions|handlers):/i; function isCacheHit(key, value) { try { const isEmpty = isEmptyValue(value); if (isEmpty || typeof key !== "string" || !CACHED_FN_HANDLERS_RE.test(key)) { return !isEmpty; } return validateCacheEntry(key, JSON.parse(String(value))); } catch { return false; } } function validateCacheEntry(key, entry) { if (isEmptyValue(entry.value)) { return false; } if (Date.now() > (entry.expires || 0)) { return false; } if (isResponseCacheEntry(key, entry)) { if ((entry.value.status ?? 0) >= 400) { return false; } if (entry.value.body === void 0) { return false; } if (entry.value.headers?.etag === "undefined" || entry.value.headers?.["last-modified"] === "undefined") { return false; } } return true; } function isResponseCacheEntry(key, _) { return key.startsWith("nitro:handlers:"); }