evlog 2.19.0 → 2.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +14 -16
  2. package/dist/adapters/axiom.d.mts +3 -3
  3. package/dist/adapters/axiom.d.mts.map +1 -1
  4. package/dist/adapters/axiom.mjs +10 -14
  5. package/dist/adapters/axiom.mjs.map +1 -1
  6. package/dist/adapters/better-stack.d.mts +2 -2
  7. package/dist/adapters/better-stack.d.mts.map +1 -1
  8. package/dist/adapters/better-stack.mjs +9 -13
  9. package/dist/adapters/better-stack.mjs.map +1 -1
  10. package/dist/adapters/datadog.d.mts +3 -3
  11. package/dist/adapters/datadog.mjs +9 -5
  12. package/dist/adapters/datadog.mjs.map +1 -1
  13. package/dist/adapters/fs.d.mts +1 -1
  14. package/dist/adapters/fs.d.mts.map +1 -1
  15. package/dist/adapters/fs.mjs +14 -1
  16. package/dist/adapters/fs.mjs.map +1 -1
  17. package/dist/adapters/hyperdx.d.mts +2 -2
  18. package/dist/adapters/hyperdx.mjs +3 -3
  19. package/dist/adapters/hyperdx.mjs.map +1 -1
  20. package/dist/adapters/memory.d.mts +2 -3
  21. package/dist/adapters/memory.d.mts.map +1 -1
  22. package/dist/adapters/memory.mjs +2 -3
  23. package/dist/adapters/memory.mjs.map +1 -1
  24. package/dist/adapters/otlp.d.mts +4 -4
  25. package/dist/adapters/otlp.mjs +17 -9
  26. package/dist/adapters/otlp.mjs.map +1 -1
  27. package/dist/adapters/posthog.d.mts +2 -2
  28. package/dist/adapters/posthog.mjs +5 -5
  29. package/dist/adapters/posthog.mjs.map +1 -1
  30. package/dist/adapters/sentry.d.mts +3 -3
  31. package/dist/adapters/sentry.mjs +5 -5
  32. package/dist/adapters/sentry.mjs.map +1 -1
  33. package/dist/ai/index.d.mts +1 -1
  34. package/dist/{audit-BFwTUxBJ.mjs → audit-BQt8yAHo.mjs} +28 -24
  35. package/dist/audit-BQt8yAHo.mjs.map +1 -0
  36. package/dist/{audit-BUAajsPU.d.mts → audit-D7v6JHj0.d.mts} +8 -1
  37. package/dist/audit-D7v6JHj0.d.mts.map +1 -0
  38. package/dist/better-auth/index.d.mts +1 -1
  39. package/dist/browser.d.mts +1 -1
  40. package/dist/deferred-drain-jeajC8QF.mjs +36 -0
  41. package/dist/deferred-drain-jeajC8QF.mjs.map +1 -0
  42. package/dist/{define-DGwZkZ7x.d.mts → define-DTQpu4f6.d.mts} +3 -3
  43. package/dist/{define-DGwZkZ7x.d.mts.map → define-DTQpu4f6.d.mts.map} +1 -1
  44. package/dist/{drain-D_fy7m0n.mjs → drain-fDb-eNwz.mjs} +34 -3
  45. package/dist/drain-fDb-eNwz.mjs.map +1 -0
  46. package/dist/elysia/index.d.mts +2 -2
  47. package/dist/elysia/index.mjs +2 -2
  48. package/dist/enrich-error-stack-next.node-Dgm_rCf5.mjs +120 -0
  49. package/dist/enrich-error-stack-next.node-Dgm_rCf5.mjs.map +1 -0
  50. package/dist/{enricher-CuMbbdqp.d.mts → enricher-CBRmQw6e.d.mts} +2 -2
  51. package/dist/{enricher-CuMbbdqp.d.mts.map → enricher-CBRmQw6e.d.mts.map} +1 -1
  52. package/dist/enrichers.d.mts +2 -2
  53. package/dist/{error-DwajXSKM.d.mts → error-CpghjrkY.d.mts} +2 -2
  54. package/dist/{error-DwajXSKM.d.mts.map → error-CpghjrkY.d.mts.map} +1 -1
  55. package/dist/error.d.mts +1 -1
  56. package/dist/{errors-CAq8pYpW.d.mts → errors-BLU4Tfpe.d.mts} +2 -2
  57. package/dist/{errors-CAq8pYpW.d.mts.map → errors-BLU4Tfpe.d.mts.map} +1 -1
  58. package/dist/express/index.d.mts +2 -2
  59. package/dist/express/index.mjs +1 -1
  60. package/dist/fastify/index.d.mts +2 -2
  61. package/dist/fastify/index.mjs +1 -1
  62. package/dist/{fork-CYm453dq.mjs → fork-CgGlAaHa.mjs} +2 -2
  63. package/dist/{fork-CYm453dq.mjs.map → fork-CgGlAaHa.mjs.map} +1 -1
  64. package/dist/hono/index.d.mts +2 -2
  65. package/dist/hono/index.mjs +1 -1
  66. package/dist/{http-Bept5EIC.mjs → http-ChVS9GYc.mjs} +2 -2
  67. package/dist/{http-Bept5EIC.mjs.map → http-ChVS9GYc.mjs.map} +1 -1
  68. package/dist/http.d.mts +1 -1
  69. package/dist/{index-CE7kH0II.d.mts → index-EvnrXvQM.d.mts} +3 -3
  70. package/dist/{index-CE7kH0II.d.mts.map → index-EvnrXvQM.d.mts.map} +1 -1
  71. package/dist/index.d.mts +8 -8
  72. package/dist/index.mjs +1 -1
  73. package/dist/instrumentation-create-BrjQtSKD.d.mts +115 -0
  74. package/dist/instrumentation-create-BrjQtSKD.d.mts.map +1 -0
  75. package/dist/{integration-CR601uyW.mjs → integration-DYp2uw8O.mjs} +2 -2
  76. package/dist/{integration-CR601uyW.mjs.map → integration-DYp2uw8O.mjs.map} +1 -1
  77. package/dist/{logger-BccCJUyD.d.mts → logger-mHIWxBhJ.d.mts} +7 -3
  78. package/dist/logger-mHIWxBhJ.d.mts.map +1 -0
  79. package/dist/logger.d.mts +2 -2
  80. package/dist/logger.mjs +1 -1
  81. package/dist/{middleware-DQ6-h8h0.d.mts → middleware-B_k4Mrzg.d.mts} +2 -2
  82. package/dist/{middleware-DQ6-h8h0.d.mts.map → middleware-B_k4Mrzg.d.mts.map} +1 -1
  83. package/dist/nestjs/index.d.mts +2 -2
  84. package/dist/nestjs/index.mjs +1 -1
  85. package/dist/next/client.d.mts +1 -1
  86. package/dist/next/index.d.mts +4 -4
  87. package/dist/next/index.d.mts.map +1 -1
  88. package/dist/next/index.mjs +16 -3
  89. package/dist/next/index.mjs.map +1 -1
  90. package/dist/next/instrumentation/create.d.mts +2 -0
  91. package/dist/next/instrumentation/create.mjs +155 -0
  92. package/dist/next/instrumentation/create.mjs.map +1 -0
  93. package/dist/next/instrumentation.d.mts +2 -77
  94. package/dist/next/instrumentation.mjs +32 -81
  95. package/dist/next/instrumentation.mjs.map +1 -1
  96. package/dist/next/stream.d.mts +1 -1
  97. package/dist/next/stream.mjs +2 -2
  98. package/dist/next/stream.mjs.map +1 -1
  99. package/dist/nitro/errorHandler.mjs +15 -4
  100. package/dist/nitro/errorHandler.mjs.map +1 -1
  101. package/dist/nitro/module.d.mts +2 -2
  102. package/dist/nitro/plugin.mjs +89 -4
  103. package/dist/nitro/plugin.mjs.map +1 -1
  104. package/dist/nitro/v3/index.d.mts +2 -2
  105. package/dist/nitro/v3/module.d.mts +1 -1
  106. package/dist/nitro/v3/plugin.mjs +4 -4
  107. package/dist/nitro/v3/plugin.mjs.map +1 -1
  108. package/dist/nitro/v3/useLogger.d.mts +1 -1
  109. package/dist/{nitro-zCXTylj4.d.mts → nitro-_Hda8Deo.d.mts} +2 -2
  110. package/dist/{nitro-zCXTylj4.d.mts.map → nitro-_Hda8Deo.d.mts.map} +1 -1
  111. package/dist/nuxt/module.d.mts +1 -1
  112. package/dist/nuxt/module.mjs +2 -2
  113. package/dist/orpc/index.d.mts +2 -2
  114. package/dist/orpc/index.mjs +1 -1
  115. package/dist/{package-CUhII9DA.mjs → package-CNV_CXs8.mjs} +2 -2
  116. package/dist/package-CNV_CXs8.mjs.map +1 -0
  117. package/dist/{parseError-Cagr-Ctc.d.mts → parseError-BeBXEd2V.d.mts} +2 -2
  118. package/dist/parseError-BeBXEd2V.d.mts.map +1 -0
  119. package/dist/pipeline.d.mts +0 -19
  120. package/dist/pipeline.d.mts.map +1 -1
  121. package/dist/pipeline.mjs +12 -0
  122. package/dist/pipeline.mjs.map +1 -1
  123. package/dist/{pretty-error-CVVgwlTn.mjs → pretty-error-THg0U0w9.mjs} +37 -27
  124. package/dist/pretty-error-THg0U0w9.mjs.map +1 -0
  125. package/dist/{pretty-error-snippet.node-c_bzjg7g.mjs → pretty-error-snippet.node-itfCajBM.mjs} +3 -2
  126. package/dist/{pretty-error-snippet.node-c_bzjg7g.mjs.map → pretty-error-snippet.node-itfCajBM.mjs.map} +1 -1
  127. package/dist/react-router/index.d.mts +2 -2
  128. package/dist/react-router/index.mjs +1 -1
  129. package/dist/runtime/client/log.d.mts +1 -1
  130. package/dist/runtime/server/routes/_evlog/ingest.post.d.mts +7 -0
  131. package/dist/runtime/server/routes/_evlog/ingest.post.mjs +39 -3
  132. package/dist/runtime/server/routes/_evlog/ingest.post.mjs.map +1 -1
  133. package/dist/runtime/server/useLogger.d.mts +1 -1
  134. package/dist/runtime/utils/parseError.d.mts +2 -2
  135. package/dist/{source-location-xkDGiERl.mjs → source-location-CHOPF2nd.mjs} +2 -1
  136. package/dist/{source-location-xkDGiERl.mjs.map → source-location-CHOPF2nd.mjs.map} +1 -1
  137. package/dist/stream.d.mts +1 -1
  138. package/dist/stream.mjs +1 -1
  139. package/dist/sveltekit/index.d.mts +2 -2
  140. package/dist/sveltekit/index.mjs +1 -1
  141. package/dist/toolkit.d.mts +20 -6
  142. package/dist/toolkit.d.mts.map +1 -1
  143. package/dist/toolkit.mjs +6 -6
  144. package/dist/types.d.mts +1 -1
  145. package/dist/{useLogger-Dv52PDpH.d.mts → useLogger-Cfv8Ck8b.d.mts} +2 -2
  146. package/dist/{useLogger-Dv52PDpH.d.mts.map → useLogger-Cfv8Ck8b.d.mts.map} +1 -1
  147. package/dist/{utils-DmNbZwBZ.d.mts → utils-CJJG0ZYW.d.mts} +2 -2
  148. package/dist/{utils-DmNbZwBZ.d.mts.map → utils-CJJG0ZYW.d.mts.map} +1 -1
  149. package/dist/utils.d.mts +1 -1
  150. package/dist/utils.mjs +2 -1
  151. package/dist/utils.mjs.map +1 -1
  152. package/dist/vite/index.d.mts +1 -1
  153. package/dist/vite/index.mjs +1 -1
  154. package/dist/workers.d.mts +1 -1
  155. package/dist/workers.mjs +1 -1
  156. package/package.json +12 -1
  157. package/dist/audit-BFwTUxBJ.mjs.map +0 -1
  158. package/dist/audit-BUAajsPU.d.mts.map +0 -1
  159. package/dist/drain-D_fy7m0n.mjs.map +0 -1
  160. package/dist/enrich-drain-CG_2Nix-.mjs +0 -122
  161. package/dist/enrich-drain-CG_2Nix-.mjs.map +0 -1
  162. package/dist/logger-BccCJUyD.d.mts.map +0 -1
  163. package/dist/next/instrumentation.d.mts.map +0 -1
  164. package/dist/package-CUhII9DA.mjs.map +0 -1
  165. package/dist/parseError-Cagr-Ctc.d.mts.map +0 -1
  166. package/dist/pretty-error-CVVgwlTn.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.mjs","names":[],"sources":["../../../src/nitro-v3/plugin.ts"],"sourcesContent":["import { definePlugin } from 'nitro'\nimport type { CaptureError } from 'nitro/types'\nimport type { HTTPEvent } from 'nitro/h3'\nimport { parseURL } from 'ufo'\nimport { createRequestLogger, getGlobalPluginRunner, initLogger, isEnabled, markWideEventDrainStarted } from '../logger'\nimport { registerPrettyErrorSnippetReader } from '../shared/pretty-error'\nimport { readCodeSnippetFromDisk } from '../shared/pretty-error-snippet.node'\nimport { enrichErrorStackForDev } from '../shared/enrich-error-stack.node'\nimport { shouldLog, getServiceForPath, extractErrorStatus } from '../nitro'\nimport { extendDeferredDrain } from '../nitro/enrich-drain'\nimport { normalizeRedactConfig } from '../redact'\nimport { resolveEvlogConfigForNitroPlugin, setActiveNitroRuntime } from '../shared/nitroConfigBridge'\nimport { bindStreamingResponseLifecycle, shouldDeferEmitForResponse } from '../shared/streamResponse'\nimport type { EnrichContext, RequestLogger, TailSamplingContext, WideEvent } from '../types'\nimport { filterSafeHeaders } from '../utils'\n\n// Nitro v3 doesn't fully export hook types yet\n// https://github.com/nitrojs/nitro/blob/8882bc9e1dbf2d342e73097f22a2156f70f50575/src/types/runtime/nitro.ts#L48-L53\ninterface NitroV3Hooks {\n close: () => void\n error: CaptureError\n request: (event: HTTPEvent) => void | Promise<void>\n response: (res: Response, event: HTTPEvent) => void | Promise<void>\n 'evlog:emit:keep': (ctx: TailSamplingContext) => void | Promise<void>\n 'evlog:enrich': (ctx: EnrichContext) => void | Promise<void>\n 'evlog:drain': (ctx: { event: WideEvent; request?: { method?: string; path: string; requestId?: string }; headers?: Record<string, string> }) => void | Promise<void>\n}\n\ntype Hooks = {\n hook: <T extends keyof NitroV3Hooks>(name: T, listener: NitroV3Hooks[T]) => void\n callHook: <T extends keyof NitroV3Hooks>(name: T, ...args: Parameters<NitroV3Hooks[T]>) => Promise<void>\n}\n\nfunction getContext(event: HTTPEvent): Record<string, unknown> {\n if (!event.req.context) {\n event.req.context = {}\n }\n return event.req.context\n}\n\nfunction getSafeRequestHeaders(event: HTTPEvent): Record<string, string> {\n const headers: Record<string, string> = {}\n event.req.headers.forEach((value, key) => {\n headers[key] = value\n })\n return filterSafeHeaders(headers)\n}\n\nfunction getSafeResponseHeaders(res: Response): Record<string, string> | undefined {\n const headers: Record<string, string> = {}\n res.headers.forEach((value, key) => {\n headers[key] = value\n })\n if (Object.keys(headers).length === 0) return undefined\n return filterSafeHeaders(headers)\n}\n\nfunction buildHookContext(\n event: HTTPEvent,\n res?: Response,\n): Omit<EnrichContext, 'event'> {\n const { pathname } = parseURL(event.req.url)\n const responseHeaders = res ? getSafeResponseHeaders(res) : undefined\n return {\n request: { method: event.req.method, path: pathname },\n headers: getSafeRequestHeaders(event),\n response: {\n status: res?.status ?? 200,\n headers: responseHeaders,\n },\n }\n}\n\nasync function callDrainHook(\n hooks: Hooks,\n emittedEvent: WideEvent | null,\n event: HTTPEvent,\n hookContext: Omit<EnrichContext, 'event'>,\n options?: { deferDrain?: boolean },\n): Promise<void> {\n if (!emittedEvent) return\n\n const drainCtx = {\n event: emittedEvent,\n request: hookContext.request,\n headers: hookContext.headers,\n }\n\n const drainTasks: Array<Promise<unknown>> = []\n try {\n const result = hooks.callHook('evlog:drain', drainCtx)\n if (result?.catch) {\n drainTasks.push(\n result.catch((err: unknown) => {\n console.error('[evlog] drain failed:', err)\n }),\n )\n }\n } catch (err) {\n console.error('[evlog] drain failed:', err)\n }\n\n const runner = getGlobalPluginRunner()\n if (runner.hasDrain) {\n drainTasks.push(runner.runDrain(drainCtx))\n }\n\n if (drainTasks.length === 0) return\n const drainPromise = Promise.all(drainTasks)\n\n // deferDrain: never block Nitro Node responses; extend lifetime on Cloudflare only.\n if (options?.deferDrain) {\n const waitUntil = globalThis.navigator?.userAgent === 'Cloudflare-Workers' && typeof event.req.waitUntil === 'function'\n ? event.req.waitUntil.bind(event.req)\n : undefined\n extendDeferredDrain(drainPromise, waitUntil)\n return\n }\n\n if (typeof event.req.waitUntil === 'function') {\n event.req.waitUntil(drainPromise)\n } else {\n await drainPromise\n }\n}\n\nasync function callEnrichAndDrain(\n hooks: Hooks,\n emittedEvent: WideEvent | null,\n event: HTTPEvent,\n res?: Response,\n options?: { deferDrain?: boolean },\n): Promise<void> {\n if (!emittedEvent) return\n\n const hookContext = buildHookContext(event, res)\n const enrichCtx: EnrichContext = { event: emittedEvent, ...hookContext }\n\n try {\n await hooks.callHook('evlog:enrich', enrichCtx)\n } catch (err) {\n console.error('[evlog] enrich failed:', err)\n }\n\n const runner = getGlobalPluginRunner()\n if (runner.hasEnrich) {\n await runner.runEnrich(enrichCtx)\n }\n\n markWideEventDrainStarted(emittedEvent)\n\n await callDrainHook(hooks, emittedEvent, event, hookContext, options)\n}\n\n/**\n * Nitro v3 plugin entry point.\n *\n * Usage in Nitro v3:\n * ```ts\n * // plugins/evlog.ts\n * export { default } from 'evlog/nitro/v3'\n * ```\n */\nexport default definePlugin(async (nitroApp) => {\n setActiveNitroRuntime('v3')\n const evlogConfig = await resolveEvlogConfigForNitroPlugin()\n\n const redact = normalizeRedactConfig(evlogConfig?.redact as boolean | Record<string, unknown> | undefined)\n\n registerPrettyErrorSnippetReader(readCodeSnippetFromDisk)\n\n initLogger({\n enabled: evlogConfig?.enabled,\n env: evlogConfig?.env,\n pretty: evlogConfig?.pretty,\n dev: evlogConfig?.dev,\n silent: evlogConfig?.silent,\n sampling: evlogConfig?.sampling,\n minLevel: evlogConfig?.minLevel,\n redact,\n _suppressDrainWarning: true,\n })\n\n const hooks = nitroApp.hooks as unknown as Hooks\n\n // When globally disabled, createRequestLogger returns a no-op logger — still\n // attach it so handlers can call useLogger without throwing.\n if (!isEnabled()) {\n hooks.hook('request', (event) => {\n const { pathname } = parseURL(event.req.url)\n const ctx = getContext(event)\n let requestIdOverride: string | undefined\n if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') {\n const cfRay = event.req.headers.get('cf-ray')\n if (cfRay) requestIdOverride = cfRay\n }\n ctx.log = createRequestLogger({\n method: event.req.method,\n path: pathname,\n requestId: requestIdOverride || ctx.requestId as string | undefined || crypto.randomUUID(),\n }, { _deferDrain: true })\n })\n return\n }\n\n hooks.hook('request', (event) => {\n const { pathname } = parseURL(event.req.url)\n const ctx = getContext(event)\n\n // Evaluate route filtering but always create the logger so that server\n // middleware (which runs for every request) can call useLogger(event)\n // without throwing. Filtering is enforced at emit time instead.\n ctx._evlogShouldEmit = shouldLog(pathname, evlogConfig?.include, evlogConfig?.exclude)\n\n // Store start time for duration calculation in tail sampling\n ctx._evlogStartTime = Date.now()\n\n let requestIdOverride: string | undefined = undefined\n if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') {\n const cfRay = event.req.headers.get('cf-ray')\n if (cfRay) requestIdOverride = cfRay\n }\n\n const log = createRequestLogger({\n method: event.req.method,\n path: pathname,\n requestId: requestIdOverride || ctx.requestId as string | undefined || crypto.randomUUID(),\n }, { _deferDrain: true })\n\n // Apply route-based service configuration if a matching route is found\n const routeService = getServiceForPath(pathname, evlogConfig?.routes)\n if (routeService) {\n log.set({ service: routeService })\n }\n\n ctx.log = log\n })\n\n hooks.hook('response', async (res, event) => {\n const ctx = event.req.context\n // Skip if already emitted by error hook or route was filtered out\n if (ctx?._evlogEmitted || ctx?._evlogEmitting || !ctx?._evlogShouldEmit) return\n\n const log = ctx?.log as RequestLogger | undefined\n if (!log || !ctx) return\n\n const emitSuccessResponse = async (responseStatus: number) => {\n log.set({ status: responseStatus })\n\n const startTime = ctx._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const { pathname } = parseURL(event.req.url)\n\n const tailCtx: TailSamplingContext = {\n status: responseStatus,\n duration: durationMs,\n path: pathname,\n method: event.req.method,\n context: log.getContext(),\n shouldKeep: false,\n }\n\n await hooks.callHook('evlog:emit:keep', tailCtx)\n const runner = getGlobalPluginRunner()\n if (runner.hasKeep) await runner.runKeep(tailCtx)\n\n const emittedEvent = log.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(hooks, emittedEvent, event, res)\n }\n\n if (shouldDeferEmitForResponse(res)) {\n const wrapped = bindStreamingResponseLifecycle(res, async (meta) => {\n if (meta.error) {\n log.error(meta.error)\n }\n await emitSuccessResponse(meta.status ?? res.status)\n })\n if (wrapped !== res && 'res' in event) {\n (event as { res: Response }).res = wrapped\n }\n return\n }\n\n await emitSuccessResponse(res.status)\n })\n\n hooks.hook('error', async (error, { event }) => {\n if (!event) return\n const e = event as HTTPEvent\n\n const ctx = e.req.context\n if (!ctx?._evlogShouldEmit) return\n const log = ctx.log as RequestLogger | undefined\n if (!log) return\n\n ctx._evlogEmitting = true\n try {\n const actualError = (error.cause as Error)?.name === 'EvlogError'\n ? error.cause as Error\n : error as Error\n\n void enrichErrorStackForDev(actualError, { pretty: evlogConfig?.pretty })\n log.error(actualError)\n\n const errorStatus = extractErrorStatus(actualError)\n log.set({ status: errorStatus })\n\n const { pathname } = parseURL(e.req.url)\n const startTime = ctx._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const tailCtx: TailSamplingContext = {\n status: errorStatus,\n duration: durationMs,\n path: pathname,\n method: e.req.method,\n context: log.getContext(),\n shouldKeep: false,\n }\n\n await hooks.callHook('evlog:emit:keep', tailCtx)\n const runner = getGlobalPluginRunner()\n if (runner.hasKeep) await runner.runKeep(tailCtx)\n\n const emittedEvent = log.emit({ _forceKeep: tailCtx.shouldKeep })\n if (emittedEvent) {\n ctx._evlogEmitted = true\n void callEnrichAndDrain(hooks, emittedEvent, e, undefined, { deferDrain: true }).catch((err) => {\n console.error('[evlog] background enrich/drain failed:', err)\n })\n }\n } finally {\n delete ctx._evlogEmitting\n }\n })\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.mjs","names":[],"sources":["../../../src/nitro-v3/plugin.ts"],"sourcesContent":["import { definePlugin } from 'nitro'\nimport type { CaptureError } from 'nitro/types'\nimport type { HTTPEvent } from 'nitro/h3'\nimport { parseURL } from 'ufo'\nimport { createRequestLogger, getGlobalPluginRunner, initLogger, isEnabled, markWideEventDrainStarted } from '../logger'\nimport { registerPrettyErrorSnippetReader } from '../shared/pretty-error'\nimport { readCodeSnippetFromDisk } from '../shared/pretty-error-snippet.node'\nimport { enrichErrorStackForDev } from '../shared/enrich-error-stack.node'\nimport { shouldLog, getServiceForPath, extractErrorStatus } from '../nitro'\nimport { extendDeferredDrain } from '../nitro/deferred-drain'\nimport { normalizeRedactConfig } from '../redact'\nimport { resolveEvlogConfigForNitroPlugin, setActiveNitroRuntime } from '../shared/nitroConfigBridge'\nimport { bindStreamingResponseLifecycle, shouldDeferEmitForResponse } from '../shared/streamResponse'\nimport type { EnrichContext, RequestLogger, TailSamplingContext, WideEvent } from '../types'\nimport { filterSafeHeaders } from '../utils'\n\n// Nitro v3 doesn't fully export hook types yet\n// https://github.com/nitrojs/nitro/blob/8882bc9e1dbf2d342e73097f22a2156f70f50575/src/types/runtime/nitro.ts#L48-L53\ninterface NitroV3Hooks {\n close: () => void\n error: CaptureError\n request: (event: HTTPEvent) => void | Promise<void>\n response: (res: Response, event: HTTPEvent) => void | Promise<void>\n 'evlog:emit:keep': (ctx: TailSamplingContext) => void | Promise<void>\n 'evlog:enrich': (ctx: EnrichContext) => void | Promise<void>\n 'evlog:drain': (ctx: { event: WideEvent; request?: { method?: string; path: string; requestId?: string }; headers?: Record<string, string> }) => void | Promise<void>\n}\n\ntype Hooks = {\n hook: <T extends keyof NitroV3Hooks>(name: T, listener: NitroV3Hooks[T]) => void\n callHook: <T extends keyof NitroV3Hooks>(name: T, ...args: Parameters<NitroV3Hooks[T]>) => Promise<void>\n}\n\nfunction getContext(event: HTTPEvent): Record<string, unknown> {\n if (!event.req.context) {\n event.req.context = {}\n }\n return event.req.context\n}\n\nfunction getSafeRequestHeaders(event: HTTPEvent): Record<string, string> {\n const headers: Record<string, string> = {}\n event.req.headers.forEach((value, key) => {\n headers[key] = value\n })\n return filterSafeHeaders(headers)\n}\n\nfunction getSafeResponseHeaders(res: Response): Record<string, string> | undefined {\n const headers: Record<string, string> = {}\n res.headers.forEach((value, key) => {\n headers[key] = value\n })\n if (Object.keys(headers).length === 0) return undefined\n return filterSafeHeaders(headers)\n}\n\nfunction buildHookContext(\n event: HTTPEvent,\n res?: Response,\n): Omit<EnrichContext, 'event'> {\n const { pathname } = parseURL(event.req.url)\n const responseHeaders = res ? getSafeResponseHeaders(res) : undefined\n return {\n request: { method: event.req.method, path: pathname },\n headers: getSafeRequestHeaders(event),\n response: {\n status: res?.status ?? 200,\n headers: responseHeaders,\n },\n }\n}\n\nasync function callDrainHook(\n hooks: Hooks,\n emittedEvent: WideEvent | null,\n event: HTTPEvent,\n hookContext: Omit<EnrichContext, 'event'>,\n options?: { deferDrain?: boolean },\n): Promise<void> {\n if (!emittedEvent) return\n\n const drainCtx = {\n event: emittedEvent,\n request: hookContext.request,\n headers: hookContext.headers,\n }\n\n const drainTasks: Array<Promise<unknown>> = []\n try {\n const result = hooks.callHook('evlog:drain', drainCtx)\n if (result?.catch) {\n drainTasks.push(\n result.catch((err: unknown) => {\n console.error('[evlog] drain failed:', err)\n }),\n )\n }\n } catch (err) {\n console.error('[evlog] drain failed:', err)\n }\n\n const runner = getGlobalPluginRunner()\n if (runner.hasDrain) {\n drainTasks.push(runner.runDrain(drainCtx))\n }\n\n if (drainTasks.length === 0) return\n const drainPromise = Promise.all(drainTasks)\n\n // deferDrain: never block Nitro Node responses; extend lifetime on Cloudflare only.\n if (options?.deferDrain) {\n const waitUntil = globalThis.navigator?.userAgent === 'Cloudflare-Workers' && typeof event.req.waitUntil === 'function'\n ? event.req.waitUntil.bind(event.req)\n : undefined\n extendDeferredDrain(drainPromise, waitUntil)\n return\n }\n\n if (typeof event.req.waitUntil === 'function') {\n event.req.waitUntil(drainPromise)\n } else {\n await drainPromise\n }\n}\n\nasync function callEnrichAndDrain(\n hooks: Hooks,\n emittedEvent: WideEvent | null,\n event: HTTPEvent,\n res?: Response,\n options?: { deferDrain?: boolean },\n): Promise<void> {\n if (!emittedEvent) return\n\n const hookContext = buildHookContext(event, res)\n const enrichCtx: EnrichContext = { event: emittedEvent, ...hookContext }\n\n try {\n await hooks.callHook('evlog:enrich', enrichCtx)\n } catch (err) {\n console.error('[evlog] enrich failed:', err)\n }\n\n const runner = getGlobalPluginRunner()\n if (runner.hasEnrich) {\n await runner.runEnrich(enrichCtx)\n }\n\n markWideEventDrainStarted(emittedEvent)\n\n await callDrainHook(hooks, emittedEvent, event, hookContext, options)\n}\n\n/**\n * Nitro v3 plugin entry point.\n *\n * Usage in Nitro v3:\n * ```ts\n * // plugins/evlog.ts\n * export { default } from 'evlog/nitro/v3'\n * ```\n */\nexport default definePlugin(async (nitroApp) => {\n setActiveNitroRuntime('v3')\n const evlogConfig = await resolveEvlogConfigForNitroPlugin()\n\n const redact = normalizeRedactConfig(evlogConfig?.redact as boolean | Record<string, unknown> | undefined)\n\n registerPrettyErrorSnippetReader(readCodeSnippetFromDisk)\n\n initLogger({\n enabled: evlogConfig?.enabled,\n env: evlogConfig?.env,\n pretty: evlogConfig?.pretty,\n dev: evlogConfig?.dev,\n silent: evlogConfig?.silent,\n sampling: evlogConfig?.sampling,\n minLevel: evlogConfig?.minLevel,\n redact,\n _suppressDrainWarning: true,\n })\n\n const hooks = nitroApp.hooks as unknown as Hooks\n\n // When globally disabled, createRequestLogger returns a no-op logger — still\n // attach it so handlers can call useLogger without throwing.\n if (!isEnabled()) {\n hooks.hook('request', (event) => {\n const { pathname } = parseURL(event.req.url)\n const ctx = getContext(event)\n let requestIdOverride: string | undefined\n if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') {\n const cfRay = event.req.headers.get('cf-ray')\n if (cfRay) requestIdOverride = cfRay\n }\n ctx.log = createRequestLogger({\n method: event.req.method,\n path: pathname,\n requestId: requestIdOverride || ctx.requestId as string | undefined || crypto.randomUUID(),\n }, { _deferDrain: true })\n })\n return\n }\n\n hooks.hook('request', (event) => {\n const { pathname } = parseURL(event.req.url)\n const ctx = getContext(event)\n\n // Evaluate route filtering but always create the logger so that server\n // middleware (which runs for every request) can call useLogger(event)\n // without throwing. Filtering is enforced at emit time instead.\n ctx._evlogShouldEmit = shouldLog(pathname, evlogConfig?.include, evlogConfig?.exclude)\n\n // Store start time for duration calculation in tail sampling\n ctx._evlogStartTime = Date.now()\n\n let requestIdOverride: string | undefined = undefined\n if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') {\n const cfRay = event.req.headers.get('cf-ray')\n if (cfRay) requestIdOverride = cfRay\n }\n\n const log = createRequestLogger({\n method: event.req.method,\n path: pathname,\n requestId: requestIdOverride || ctx.requestId as string | undefined || crypto.randomUUID(),\n }, { _deferDrain: true })\n\n // Apply route-based service configuration if a matching route is found\n const routeService = getServiceForPath(pathname, evlogConfig?.routes)\n if (routeService) {\n log.set({ service: routeService })\n }\n\n ctx.log = log\n })\n\n hooks.hook('response', async (res, event) => {\n const ctx = event.req.context\n // Skip if already emitted by error hook or route was filtered out\n if (ctx?._evlogEmitted || ctx?._evlogEmitting || !ctx?._evlogShouldEmit) return\n\n const log = ctx?.log as RequestLogger | undefined\n if (!log || !ctx) return\n\n const emitSuccessResponse = async (responseStatus: number) => {\n log.set({ status: responseStatus })\n\n const startTime = ctx._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const { pathname } = parseURL(event.req.url)\n\n const tailCtx: TailSamplingContext = {\n status: responseStatus,\n duration: durationMs,\n path: pathname,\n method: event.req.method,\n context: log.getContext(),\n shouldKeep: false,\n }\n\n await hooks.callHook('evlog:emit:keep', tailCtx)\n const runner = getGlobalPluginRunner()\n if (runner.hasKeep) await runner.runKeep(tailCtx)\n\n const emittedEvent = log.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(hooks, emittedEvent, event, res)\n }\n\n if (shouldDeferEmitForResponse(res)) {\n const wrapped = bindStreamingResponseLifecycle(res, async (meta) => {\n if (meta.error) {\n log.error(meta.error)\n }\n await emitSuccessResponse(meta.status ?? res.status)\n })\n if (wrapped !== res && 'res' in event) {\n (event as { res: Response }).res = wrapped\n }\n return\n }\n\n await emitSuccessResponse(res.status)\n })\n\n hooks.hook('error', async (error, { event }) => {\n if (!event) return\n const e = event as HTTPEvent\n\n const ctx = e.req.context\n if (!ctx?._evlogShouldEmit) return\n const log = ctx.log as RequestLogger | undefined\n if (!log) return\n\n ctx._evlogEmitting = true\n try {\n const actualError = (error.cause as Error)?.name === 'EvlogError'\n ? error.cause as Error\n : error as Error\n\n void enrichErrorStackForDev(actualError, { pretty: evlogConfig?.pretty })\n log.error(actualError)\n\n const errorStatus = extractErrorStatus(actualError)\n log.set({ status: errorStatus })\n\n const { pathname } = parseURL(e.req.url)\n const startTime = ctx._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const tailCtx: TailSamplingContext = {\n status: errorStatus,\n duration: durationMs,\n path: pathname,\n method: e.req.method,\n context: log.getContext(),\n shouldKeep: false,\n }\n\n await hooks.callHook('evlog:emit:keep', tailCtx)\n const runner = getGlobalPluginRunner()\n if (runner.hasKeep) await runner.runKeep(tailCtx)\n\n const emittedEvent = log.emit({ _forceKeep: tailCtx.shouldKeep })\n if (emittedEvent) {\n ctx._evlogEmitted = true\n void callEnrichAndDrain(hooks, emittedEvent, e, undefined, { deferDrain: true }).catch((err) => {\n console.error('[evlog] background enrich/drain failed:', err)\n })\n }\n } finally {\n delete ctx._evlogEmitting\n }\n })\n})\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { rt as RequestLogger } from "../../audit-BUAajsPU.mjs";
1
+ import { rt as RequestLogger } from "../../audit-D7v6JHj0.mjs";
2
2
  import { HTTPEvent } from "nitro/h3";
3
3
 
4
4
  //#region src/nitro-v3/useLogger.d.ts
@@ -1,4 +1,4 @@
1
- import { $ as RedactConfig, H as EnvironmentContext, X as LogLevel, at as RouteConfig, ot as SamplingConfig, pt as DevTerminalInput } from "./audit-BUAajsPU.mjs";
1
+ import { $ as RedactConfig, H as EnvironmentContext, X as LogLevel, at as RouteConfig, ot as SamplingConfig, pt as DevTerminalInput } from "./audit-D7v6JHj0.mjs";
2
2
  //#region src/nitro.d.ts
3
3
  interface NitroModuleOptions {
4
4
  /**
@@ -62,4 +62,4 @@ interface NitroModuleOptions {
62
62
  }
63
63
  //#endregion
64
64
  export { NitroModuleOptions as t };
65
- //# sourceMappingURL=nitro-zCXTylj4.d.mts.map
65
+ //# sourceMappingURL=nitro-_Hda8Deo.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"nitro-zCXTylj4.d.mts","names":[],"sources":["../src/nitro.ts"],"mappings":";;UAWiB,kBAAA;;;;;EAKf,OAAA;EA6CS;;;EAxCT,GAAA,GAAM,OAAA,CAAQ,kBAAA;EA0DiB;;;;EApD/B,MAAA;EANc;;;;EAYd,GAAA,GAAM,gBAAA;EAgBN;;;;;;;EAPA,MAAA;EA+BW;;;;;EAxBX,OAAA;;;;;;EAOA,OAAA;;;;EAKA,MAAA,GAAS,MAAA,SAAe,WAAA;;;;EAKxB,QAAA,GAAW,cAAA;;;;;;EAOX,QAAA,GAAW,QAAA;;;;;EAMX,MAAA,aAAmB,YAAA;AAAA"}
1
+ {"version":3,"file":"nitro-_Hda8Deo.d.mts","names":[],"sources":["../src/nitro.ts"],"mappings":";;UAWiB,kBAAA;;;;;EAKf,OAAA;EA6CS;;;EAxCT,GAAA,GAAM,OAAA,CAAQ,kBAAA;EA0DiB;;;;EApD/B,MAAA;EANc;;;;EAYd,GAAA,GAAM,gBAAA;EAgBN;;;;;;;EAPA,MAAA;EA+BW;;;;;EAxBX,OAAA;;;;;;EAOA,OAAA;;;;EAKA,MAAA,GAAS,MAAA,SAAe,WAAA;;;;EAKxB,QAAA,GAAW,cAAA;;;;;;EAOX,QAAA,GAAW,QAAA;;;;;EAMX,MAAA,aAAmB,YAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- import { $ as RedactConfig, H as EnvironmentContext, X as LogLevel, at as RouteConfig, dt as TransportConfig, ot as SamplingConfig, pt as DevTerminalInput } from "../audit-BUAajsPU.mjs";
1
+ import { $ as RedactConfig, H as EnvironmentContext, X as LogLevel, at as RouteConfig, dt as TransportConfig, ot as SamplingConfig, pt as DevTerminalInput } from "../audit-D7v6JHj0.mjs";
2
2
  import { StreamServerOptions } from "../stream.mjs";
3
3
  import * as _$_nuxt_schema0 from "@nuxt/schema";
4
4
 
@@ -1,6 +1,6 @@
1
1
  import { r as prependNitroErrorHandler } from "../nitro-ClRZLD1g.mjs";
2
- import { n as createStripPlugin, t as createSourceLocationPlugin } from "../source-location-xkDGiERl.mjs";
3
- import { n as version, t as name } from "../package-CUhII9DA.mjs";
2
+ import { n as createStripPlugin, t as createSourceLocationPlugin } from "../source-location-CHOPF2nd.mjs";
3
+ import { n as version, t as name } from "../package-CNV_CXs8.mjs";
4
4
  import { addImports, addPlugin, addServerHandler, addServerImports, addServerPlugin, addVitePlugin, createResolver, defineNuxtModule } from "@nuxt/kit";
5
5
  //#region src/nuxt/module.ts
6
6
  var module_default = defineNuxtModule({
@@ -1,5 +1,5 @@
1
- import { rt as RequestLogger } from "../audit-BUAajsPU.mjs";
2
- import { t as BaseEvlogOptions } from "../middleware-DQ6-h8h0.mjs";
1
+ import { rt as RequestLogger } from "../audit-D7v6JHj0.mjs";
2
+ import { t as BaseEvlogOptions } from "../middleware-B_k4Mrzg.mjs";
3
3
  import { Context, MiddlewareOptions, MiddlewareResult } from "@orpc/server";
4
4
 
5
5
  //#region src/orpc/index.d.ts
@@ -1,6 +1,6 @@
1
1
  import { EvlogError } from "../error.mjs";
2
2
  import { parseError } from "../runtime/utils/parseError.mjs";
3
- import { t as defineFrameworkIntegration } from "../integration-CR601uyW.mjs";
3
+ import { t as defineFrameworkIntegration } from "../integration-DYp2uw8O.mjs";
4
4
  import { t as createLoggerStorage } from "../storage-7X37OToT.mjs";
5
5
  import { ORPCError } from "@orpc/server";
6
6
  //#region src/orpc/index.ts
@@ -1,7 +1,7 @@
1
1
  //#region package.json
2
2
  var name = "evlog";
3
- var version = "2.19.0";
3
+ var version = "2.19.1";
4
4
  //#endregion
5
5
  export { version as n, name as t };
6
6
 
7
- //# sourceMappingURL=package-CUhII9DA.mjs.map
7
+ //# sourceMappingURL=package-CNV_CXs8.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-CNV_CXs8.mjs","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}
@@ -1,7 +1,7 @@
1
- import { Q as ParsedError } from "./audit-BUAajsPU.mjs";
1
+ import { Q as ParsedError } from "./audit-D7v6JHj0.mjs";
2
2
 
3
3
  //#region src/runtime/utils/parseError.d.ts
4
4
  declare function parseError(error: unknown): ParsedError;
5
5
  //#endregion
6
6
  export { parseError as t };
7
- //# sourceMappingURL=parseError-Cagr-Ctc.d.mts.map
7
+ //# sourceMappingURL=parseError-BeBXEd2V.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseError-BeBXEd2V.d.mts","names":[],"sources":["../src/runtime/utils/parseError.ts"],"mappings":";;;iBAcgB,UAAA,CAAW,KAAA,YAAiB,WAAA"}
@@ -21,25 +21,6 @@ interface PipelineDrainFn<T> {
21
21
  flush: () => Promise<void>;
22
22
  readonly pending: number;
23
23
  }
24
- /**
25
- * Create a drain pipeline that batches events, retries on failure, and manages buffer overflow.
26
- *
27
- * Returns a higher-order function: pass your drain adapter to get a hook-compatible function.
28
- *
29
- * @example
30
- * ```ts
31
- * const pipeline = createDrainPipeline({ batch: { size: 50 } })
32
- * const drain = pipeline(async (batch) => {
33
- * await sendToBackend(batch)
34
- * })
35
- *
36
- * // Use as a hook
37
- * nitroApp.hooks.hook('evlog:drain', drain)
38
- *
39
- * // Flush on shutdown
40
- * nitroApp.hooks.hook('close', () => drain.flush())
41
- * ```
42
- */
43
24
  declare function createDrainPipeline<T = unknown>(options?: DrainPipelineOptions<T>): (drain: (batch: T[]) => void | Promise<void>) => PipelineDrainFn<T>;
44
25
  //#endregion
45
26
  export { DrainPipelineOptions, PipelineDrainFn, createDrainPipeline };
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.mts","names":[],"sources":["../src/pipeline.ts"],"mappings":";UAAiB,oBAAA;EACf,KAAA;IADmC,iFAGjC,IAAA,WAiBqC;IAfrC,UAAA;EAAA;EAEF,KAAA;IAFE,iGAIA,WAAA,WAAA;IAEA,OAAA,uCAEA;IAAA,cAAA,WAKF;IAHE,UAAA;EAAA;EAKW;EAFb,aAAA;EAE0B;EAA1B,SAAA,IAAa,MAAA,EAAQ,CAAA,IAAK,KAAA,GAAQ,KAAA;AAAA;AAAA,UAGnB,eAAA;EAAA,CACd,GAAA,EAAK,CAAA;EADwB;EAG9B,KAAA,QAAa,OAAA;EAAA,SACJ,OAAA;AAAA;;;;;;;AAsBX;;;;;;;;;;;;;iBAAgB,mBAAA,aAAA,CAAiC,OAAA,GAAU,oBAAA,CAAqB,CAAA,KAAM,KAAA,GAAQ,KAAA,EAAO,CAAA,cAAe,OAAA,WAAkB,eAAA,CAAgB,CAAA"}
1
+ {"version":3,"file":"pipeline.d.mts","names":[],"sources":["../src/pipeline.ts"],"mappings":";UAAiB,oBAAA;EACf,KAAA;IADmC,iFAGjC,IAAA,WAiBqC;IAfrC,UAAA;EAAA;EAEF,KAAA;IAFE,iGAIA,WAAA,WAAA;IAEA,OAAA,uCAEA;IAAA,cAAA,WAKF;IAHE,UAAA;EAAA;EAKW;EAFb,aAAA;EAE0B;EAA1B,SAAA,IAAa,MAAA,EAAQ,CAAA,IAAK,KAAA,GAAQ,KAAA;AAAA;AAAA,UAGnB,eAAA;EAAA,CACd,GAAA,EAAK,CAAA;EADwB;EAG9B,KAAA,QAAa,OAAA;EAAA,SACJ,OAAA;AAAA;AAAA,iBAkCK,mBAAA,aAAA,CAAiC,OAAA,GAAU,oBAAA,CAAqB,CAAA,KAAM,KAAA,GAAQ,KAAA,EAAO,CAAA,cAAe,OAAA,WAAkB,eAAA,CAAgB,CAAA"}
package/dist/pipeline.mjs CHANGED
@@ -18,6 +18,17 @@
18
18
  * nitroApp.hooks.hook('close', () => drain.flush())
19
19
  * ```
20
20
  */
21
+ /**
22
+ * Unref a timer on runtimes that support it (Node, Bun) so an idle flush
23
+ * scheduling timer never holds the process open. Buffered events are delivered
24
+ * on shutdown via the documented `flush()` contract, not by keeping timers
25
+ * alive. Retry backoff timers are intentionally left ref'd: an in-flight batch
26
+ * is active work, and an unref'd timer awaited by `flush()` would let the
27
+ * process exit mid-retry.
28
+ */
29
+ function unrefTimer(timer) {
30
+ timer.unref?.();
31
+ }
21
32
  function createDrainPipeline(options) {
22
33
  const batchSize = options?.batch?.size ?? 50;
23
34
  const intervalMs = options?.batch?.intervalMs ?? 5e3;
@@ -49,6 +60,7 @@ function createDrainPipeline(options) {
49
60
  timer = null;
50
61
  if (!activeFlush) startFlush();
51
62
  }, intervalMs);
63
+ unrefTimer(timer);
52
64
  }
53
65
  function getRetryDelay(attempt) {
54
66
  let delay;
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.mjs","names":[],"sources":["../src/pipeline.ts"],"sourcesContent":["export interface DrainPipelineOptions<T = unknown> {\n batch?: {\n /** Maximum number of events per batch sent to the drain function. @default 50 */\n size?: number\n /** Maximum time (ms) an event can stay buffered before a flush is triggered, even if the batch is not full. @default 5000 */\n intervalMs?: number\n }\n retry?: {\n /** Total number of attempts (including the initial one) before dropping the batch. @default 3 */\n maxAttempts?: number\n /** Delay strategy between retry attempts. @default 'exponential' */\n backoff?: 'exponential' | 'linear' | 'fixed'\n /** Base delay (ms) for the first retry. Scaled by the backoff strategy on subsequent retries. @default 1000 */\n initialDelayMs?: number\n /** Upper bound (ms) for any single retry delay. @default 30000 */\n maxDelayMs?: number\n }\n /** Maximum number of events held in the buffer. When exceeded, the oldest event is dropped. @default 1000 */\n maxBufferSize?: number\n /** Called when a batch is dropped after all retry attempts are exhausted, or when the buffer overflows. */\n onDropped?: (events: T[], error?: Error) => void\n}\n\nexport interface PipelineDrainFn<T> {\n (ctx: T): void\n /** Flush all buffered events. Call on server shutdown. */\n flush: () => Promise<void>\n readonly pending: number\n}\n\n/**\n * Create a drain pipeline that batches events, retries on failure, and manages buffer overflow.\n *\n * Returns a higher-order function: pass your drain adapter to get a hook-compatible function.\n *\n * @example\n * ```ts\n * const pipeline = createDrainPipeline({ batch: { size: 50 } })\n * const drain = pipeline(async (batch) => {\n * await sendToBackend(batch)\n * })\n *\n * // Use as a hook\n * nitroApp.hooks.hook('evlog:drain', drain)\n *\n * // Flush on shutdown\n * nitroApp.hooks.hook('close', () => drain.flush())\n * ```\n */\nexport function createDrainPipeline<T = unknown>(options?: DrainPipelineOptions<T>): (drain: (batch: T[]) => void | Promise<void>) => PipelineDrainFn<T> {\n const batchSize = options?.batch?.size ?? 50\n const intervalMs = options?.batch?.intervalMs ?? 5000\n const maxBufferSize = options?.maxBufferSize ?? 1000\n const maxAttempts = options?.retry?.maxAttempts ?? 3\n const backoffStrategy = options?.retry?.backoff ?? 'exponential'\n const initialDelayMs = options?.retry?.initialDelayMs ?? 1000\n const maxDelayMs = options?.retry?.maxDelayMs ?? 30000\n const onDropped = options?.onDropped\n\n if (batchSize <= 0 || !Number.isFinite(batchSize)) {\n throw new Error(`[evlog/pipeline] batch.size must be a positive finite number, got: ${batchSize}`)\n }\n if (intervalMs <= 0 || !Number.isFinite(intervalMs)) {\n throw new Error(`[evlog/pipeline] batch.intervalMs must be a positive finite number, got: ${intervalMs}`)\n }\n if (maxBufferSize <= 0 || !Number.isFinite(maxBufferSize)) {\n throw new Error(`[evlog/pipeline] maxBufferSize must be a positive finite number, got: ${maxBufferSize}`)\n }\n if (maxAttempts <= 0 || !Number.isFinite(maxAttempts)) {\n throw new Error(`[evlog/pipeline] retry.maxAttempts must be a positive finite number, got: ${maxAttempts}`)\n }\n if (initialDelayMs < 0 || !Number.isFinite(initialDelayMs)) {\n throw new Error(`[evlog/pipeline] retry.initialDelayMs must be a non-negative finite number, got: ${initialDelayMs}`)\n }\n if (maxDelayMs < 0 || !Number.isFinite(maxDelayMs)) {\n throw new Error(`[evlog/pipeline] retry.maxDelayMs must be a non-negative finite number, got: ${maxDelayMs}`)\n }\n\n return (drain: (batch: T[]) => void | Promise<void>): PipelineDrainFn<T> => {\n const buffer: T[] = []\n let timer: ReturnType<typeof setTimeout> | null = null\n let activeFlush: Promise<void> | null = null\n\n function clearTimer(): void {\n if (timer !== null) {\n clearTimeout(timer)\n timer = null\n }\n }\n\n function scheduleFlush(): void {\n if (timer !== null || activeFlush) return\n timer = setTimeout(() => {\n timer = null\n if (!activeFlush) startFlush()\n }, intervalMs)\n }\n\n function getRetryDelay(attempt: number): number {\n let delay: number\n switch (backoffStrategy) {\n case 'linear':\n delay = initialDelayMs * attempt\n break\n case 'fixed':\n delay = initialDelayMs\n break\n case 'exponential':\n default:\n delay = initialDelayMs * 2 ** (attempt - 1)\n break\n }\n return Math.min(delay, maxDelayMs)\n }\n\n async function sendWithRetry(batch: T[]): Promise<void> {\n let lastError: Error | undefined\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n await drain(batch)\n return\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error))\n if (attempt < maxAttempts) {\n await new Promise<void>(r => setTimeout(r, getRetryDelay(attempt)))\n }\n }\n }\n onDropped?.(batch, lastError)\n }\n\n async function drainBuffer(): Promise<void> {\n while (buffer.length > 0) {\n const batch = buffer.splice(0, batchSize)\n await sendWithRetry(batch)\n }\n }\n\n function startFlush(): void {\n if (activeFlush) return\n activeFlush = drainBuffer().finally(() => {\n activeFlush = null\n if (buffer.length >= batchSize) {\n startFlush()\n } else if (buffer.length > 0) {\n scheduleFlush()\n }\n })\n }\n\n function push(ctx: T): void {\n if (buffer.length >= maxBufferSize) {\n const dropped = buffer.splice(0, 1)\n onDropped?.(dropped)\n }\n buffer.push(ctx)\n\n if (buffer.length >= batchSize) {\n clearTimer()\n startFlush()\n } else if (!activeFlush) {\n scheduleFlush()\n }\n }\n\n async function flush(): Promise<void> {\n clearTimer()\n if (activeFlush) {\n await activeFlush\n }\n // Snapshot the buffer length to avoid infinite loop if push() is called during flush\n const snapshot = buffer.length\n if (snapshot > 0) {\n const toFlush = buffer.splice(0, snapshot)\n while (toFlush.length > 0) {\n const batch = toFlush.splice(0, batchSize)\n await sendWithRetry(batch)\n }\n }\n }\n\n const hookFn = push as PipelineDrainFn<T>\n hookFn.flush = flush\n Object.defineProperty(hookFn, 'pending', {\n get: () => buffer.length,\n enumerable: true,\n })\n\n return hookFn\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pipeline.mjs","names":[],"sources":["../src/pipeline.ts"],"sourcesContent":["export interface DrainPipelineOptions<T = unknown> {\n batch?: {\n /** Maximum number of events per batch sent to the drain function. @default 50 */\n size?: number\n /** Maximum time (ms) an event can stay buffered before a flush is triggered, even if the batch is not full. @default 5000 */\n intervalMs?: number\n }\n retry?: {\n /** Total number of attempts (including the initial one) before dropping the batch. @default 3 */\n maxAttempts?: number\n /** Delay strategy between retry attempts. @default 'exponential' */\n backoff?: 'exponential' | 'linear' | 'fixed'\n /** Base delay (ms) for the first retry. Scaled by the backoff strategy on subsequent retries. @default 1000 */\n initialDelayMs?: number\n /** Upper bound (ms) for any single retry delay. @default 30000 */\n maxDelayMs?: number\n }\n /** Maximum number of events held in the buffer. When exceeded, the oldest event is dropped. @default 1000 */\n maxBufferSize?: number\n /** Called when a batch is dropped after all retry attempts are exhausted, or when the buffer overflows. */\n onDropped?: (events: T[], error?: Error) => void\n}\n\nexport interface PipelineDrainFn<T> {\n (ctx: T): void\n /** Flush all buffered events. Call on server shutdown. */\n flush: () => Promise<void>\n readonly pending: number\n}\n\n/**\n * Create a drain pipeline that batches events, retries on failure, and manages buffer overflow.\n *\n * Returns a higher-order function: pass your drain adapter to get a hook-compatible function.\n *\n * @example\n * ```ts\n * const pipeline = createDrainPipeline({ batch: { size: 50 } })\n * const drain = pipeline(async (batch) => {\n * await sendToBackend(batch)\n * })\n *\n * // Use as a hook\n * nitroApp.hooks.hook('evlog:drain', drain)\n *\n * // Flush on shutdown\n * nitroApp.hooks.hook('close', () => drain.flush())\n * ```\n */\n/**\n * Unref a timer on runtimes that support it (Node, Bun) so an idle flush\n * scheduling timer never holds the process open. Buffered events are delivered\n * on shutdown via the documented `flush()` contract, not by keeping timers\n * alive. Retry backoff timers are intentionally left ref'd: an in-flight batch\n * is active work, and an unref'd timer awaited by `flush()` would let the\n * process exit mid-retry.\n */\nfunction unrefTimer(timer: ReturnType<typeof setTimeout>): void {\n (timer as { unref?: () => void }).unref?.()\n}\n\nexport function createDrainPipeline<T = unknown>(options?: DrainPipelineOptions<T>): (drain: (batch: T[]) => void | Promise<void>) => PipelineDrainFn<T> {\n const batchSize = options?.batch?.size ?? 50\n const intervalMs = options?.batch?.intervalMs ?? 5000\n const maxBufferSize = options?.maxBufferSize ?? 1000\n const maxAttempts = options?.retry?.maxAttempts ?? 3\n const backoffStrategy = options?.retry?.backoff ?? 'exponential'\n const initialDelayMs = options?.retry?.initialDelayMs ?? 1000\n const maxDelayMs = options?.retry?.maxDelayMs ?? 30000\n const onDropped = options?.onDropped\n\n if (batchSize <= 0 || !Number.isFinite(batchSize)) {\n throw new Error(`[evlog/pipeline] batch.size must be a positive finite number, got: ${batchSize}`)\n }\n if (intervalMs <= 0 || !Number.isFinite(intervalMs)) {\n throw new Error(`[evlog/pipeline] batch.intervalMs must be a positive finite number, got: ${intervalMs}`)\n }\n if (maxBufferSize <= 0 || !Number.isFinite(maxBufferSize)) {\n throw new Error(`[evlog/pipeline] maxBufferSize must be a positive finite number, got: ${maxBufferSize}`)\n }\n if (maxAttempts <= 0 || !Number.isFinite(maxAttempts)) {\n throw new Error(`[evlog/pipeline] retry.maxAttempts must be a positive finite number, got: ${maxAttempts}`)\n }\n if (initialDelayMs < 0 || !Number.isFinite(initialDelayMs)) {\n throw new Error(`[evlog/pipeline] retry.initialDelayMs must be a non-negative finite number, got: ${initialDelayMs}`)\n }\n if (maxDelayMs < 0 || !Number.isFinite(maxDelayMs)) {\n throw new Error(`[evlog/pipeline] retry.maxDelayMs must be a non-negative finite number, got: ${maxDelayMs}`)\n }\n\n return (drain: (batch: T[]) => void | Promise<void>): PipelineDrainFn<T> => {\n const buffer: T[] = []\n let timer: ReturnType<typeof setTimeout> | null = null\n let activeFlush: Promise<void> | null = null\n\n function clearTimer(): void {\n if (timer !== null) {\n clearTimeout(timer)\n timer = null\n }\n }\n\n function scheduleFlush(): void {\n if (timer !== null || activeFlush) return\n timer = setTimeout(() => {\n timer = null\n if (!activeFlush) startFlush()\n }, intervalMs)\n unrefTimer(timer)\n }\n\n function getRetryDelay(attempt: number): number {\n let delay: number\n switch (backoffStrategy) {\n case 'linear':\n delay = initialDelayMs * attempt\n break\n case 'fixed':\n delay = initialDelayMs\n break\n case 'exponential':\n default:\n delay = initialDelayMs * 2 ** (attempt - 1)\n break\n }\n return Math.min(delay, maxDelayMs)\n }\n\n async function sendWithRetry(batch: T[]): Promise<void> {\n let lastError: Error | undefined\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n await drain(batch)\n return\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error))\n if (attempt < maxAttempts) {\n await new Promise<void>(r => setTimeout(r, getRetryDelay(attempt)))\n }\n }\n }\n onDropped?.(batch, lastError)\n }\n\n async function drainBuffer(): Promise<void> {\n while (buffer.length > 0) {\n const batch = buffer.splice(0, batchSize)\n await sendWithRetry(batch)\n }\n }\n\n function startFlush(): void {\n if (activeFlush) return\n activeFlush = drainBuffer().finally(() => {\n activeFlush = null\n if (buffer.length >= batchSize) {\n startFlush()\n } else if (buffer.length > 0) {\n scheduleFlush()\n }\n })\n }\n\n function push(ctx: T): void {\n if (buffer.length >= maxBufferSize) {\n const dropped = buffer.splice(0, 1)\n onDropped?.(dropped)\n }\n buffer.push(ctx)\n\n if (buffer.length >= batchSize) {\n clearTimer()\n startFlush()\n } else if (!activeFlush) {\n scheduleFlush()\n }\n }\n\n async function flush(): Promise<void> {\n clearTimer()\n if (activeFlush) {\n await activeFlush\n }\n // Snapshot the buffer length to avoid infinite loop if push() is called during flush\n const snapshot = buffer.length\n if (snapshot > 0) {\n const toFlush = buffer.splice(0, snapshot)\n while (toFlush.length > 0) {\n const batch = toFlush.splice(0, batchSize)\n await sendWithRetry(batch)\n }\n }\n }\n\n const hookFn = push as PipelineDrainFn<T>\n hookFn.flush = flush\n Object.defineProperty(hookFn, 'pending', {\n get: () => buffer.length,\n enumerable: true,\n })\n\n return hookFn\n }\n}\n"],"mappings":"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"}
@@ -13,7 +13,7 @@ const PRETTY_ERROR_TREE_SPACER = "__EVLOG_TREE_SPACER__";
13
13
  function pushTreeSpacer(children) {
14
14
  children.push(PRETTY_ERROR_TREE_SPACER);
15
15
  }
16
- const SKIP_PATH_RE = /(?:^|[/\\])(?:node_modules|\.nuxt|\.output)(?:[/\\]|$)/;
16
+ const SKIP_PATH_RE = /(?:^|[/\\])(?:node_modules|\.nuxt|\.next|\.output)(?:[/\\]|$)/;
17
17
  const SKIP_FRAME_PATH_RE = /(?:^|[/\\])(?:packages[/\\]evlog|evlog[/\\](?:dist|src))(?:[/\\]|$)/;
18
18
  const SKIP_FRAME_FN_RE = /^(?:createError|EvlogError|new EvlogError)$/;
19
19
  function isPlainObject(val) {
@@ -74,9 +74,11 @@ function decodeFileUrl(file) {
74
74
  return file;
75
75
  }
76
76
  function isAppPath(file) {
77
- const normalized = file.replace(/\\/g, "/");
77
+ const normalized = decodeFileUrl(file).replace(/\\/g, "/");
78
+ if (normalized.startsWith("node:")) return false;
78
79
  if (SKIP_PATH_RE.test(normalized)) return false;
79
80
  if (normalized.includes("/node_modules/")) return false;
81
+ if (isFrameworkRuntimePath(normalized)) return false;
80
82
  return true;
81
83
  }
82
84
  function formatDisplayPath(file, cwd) {
@@ -93,6 +95,18 @@ function formatDisplayPath(file, cwd) {
93
95
  return decoded;
94
96
  }
95
97
  /**
98
+ * Compact a stack trace for wide-event storage (drains, NDJSON).
99
+ * Drops node_modules, build output, and evlog internals; keeps up to five useful frames.
100
+ * Returns the stack unchanged when no app frame survives the filter.
101
+ */
102
+ function compactStackForStorage(stack, maxFrames = 5) {
103
+ if (!stack) return void 0;
104
+ const head = stack.split("\n")[0] ?? "";
105
+ const useful = parseStackFrames(stack).filter((f) => f.file && f.line && f.isApp && !isInternalErrorFrame(f)).slice(0, maxFrames);
106
+ if (useful.length === 0) return stack;
107
+ return [head, ...useful.map((f) => f.raw)].join("\n");
108
+ }
109
+ /**
96
110
  * Parse a V8 stack trace string into frames.
97
111
  */
98
112
  function parseStackFrames(stack) {
@@ -142,7 +156,17 @@ function parseStackFrames(stack) {
142
156
  }
143
157
  return frames;
144
158
  }
159
+ /** True for Next.js runtime internals (route-modules, next/dist, bundled next-server). */
160
+ function isFrameworkRuntimePath(path) {
161
+ const normalized = path.replace(/\\/g, "/");
162
+ return normalized.includes("route-modules/") || normalized.includes("webpack://next/") || normalized.includes("/next/dist/") || normalized.includes("/compiled/next-server/");
163
+ }
164
+ function isFrameworkRuntimeFrame(frame) {
165
+ if (!frame.file) return false;
166
+ return isFrameworkRuntimePath(decodeFileUrl(frame.file));
167
+ }
145
168
  function isInternalErrorFrame(frame) {
169
+ if (isFrameworkRuntimeFrame(frame)) return true;
146
170
  if (frame.fn) {
147
171
  const fn = frame.fn.replace(/^async /, "");
148
172
  if (SKIP_FRAME_FN_RE.test(fn)) return true;
@@ -151,37 +175,22 @@ function isInternalErrorFrame(frame) {
151
175
  const path = decodeFileUrl(frame.file).replace(/\\/g, "/");
152
176
  if (SKIP_FRAME_PATH_RE.test(path)) return true;
153
177
  if (path.includes(".nuxt/")) return true;
178
+ if (path.includes(".next/")) return true;
154
179
  return false;
155
180
  }
156
181
  /**
157
- * Pick the most useful frame for code snippets (prefer app source over bundles).
182
+ * Pick the most useful frame for code snippets (topmost app throw site).
158
183
  */
159
184
  function pickPrimaryFrame(frames) {
160
- const appFrames = frames.filter((f) => f.isApp && f.file && f.line && !isInternalErrorFrame(f));
161
- if (appFrames.length === 0) return void 0;
162
- const scored = appFrames.map((frame) => {
163
- const path = decodeFileUrl(frame.file).replace(/\\/g, "/");
164
- let score = 0;
165
- if (path.includes("/server/")) score += 8;
166
- if (/\.(?:ts|tsx|vue)$/.test(path)) score += 6;
167
- if (path.includes("/src/")) score += 3;
168
- if (path.startsWith("./")) score += 2;
169
- if (/\.(?:js|jsx|mjs)$/.test(path)) score += 1;
170
- if (path.includes(".nuxt/")) score -= 20;
171
- if (path.includes("/packages/evlog/")) score -= 20;
172
- return {
173
- frame,
174
- score
175
- };
176
- });
177
- scored.sort((a, b) => b.score - a.score);
178
- return scored[0]?.frame ?? appFrames[0];
185
+ for (const frame of frames) if (frame.isApp && frame.file && frame.line && !isInternalErrorFrame(frame)) return frame;
179
186
  }
180
187
  /**
181
188
  * Read source lines around a stack frame when a server snippet reader is registered.
182
189
  */
183
190
  function readCodeSnippet(file, line, contextLines = 2) {
184
191
  if (!isDev() || isBrowser() || !snippetReader) return null;
192
+ const path = decodeFileUrl(file).replace(/\\/g, "/");
193
+ if (path.includes(".next/") || path.includes(".nuxt/") || path.includes(".output/")) return null;
185
194
  return snippetReader(file, line, contextLines);
186
195
  }
187
196
  function formatSnippetLines(snippet) {
@@ -196,14 +205,15 @@ function formatSnippetLines(snippet) {
196
205
  function formatFrameLocation(frame, cwd) {
197
206
  const file = frame.file ? formatDisplayPath(frame.file, cwd) : "unknown";
198
207
  const loc = frame.line ? `${file}:${frame.line}` : file;
199
- return frame.fn ? `at ${frame.fn} (${loc})` : `at ${loc}`;
208
+ const fn = frame.fn && frame.fn !== "<unknown>" ? frame.fn : void 0;
209
+ return fn ? `at ${fn} (${loc})` : `at ${loc}`;
200
210
  }
201
211
  function formatCollapsedFrame(frame, cwd) {
202
212
  const file = frame.file ? formatDisplayPath(frame.file, cwd) : "unknown";
203
213
  const loc = frame.line ? `${file}:${frame.line}` : file;
204
214
  const prefix = frame.fn?.startsWith("async") ? "at async " : "at ";
205
- const fn = frame.fn?.replace(/^async /, "") ?? loc;
206
- if (frame.fn && frame.fn !== loc) return `${prefix}${fn} (${loc})`;
215
+ const fn = frame.fn?.replace(/^async /, "");
216
+ if (fn && fn !== "<unknown>" && fn !== loc) return `${prefix}${fn} (${loc})`;
207
217
  return `${prefix}${loc}`;
208
218
  }
209
219
  const GUIDANCE_WRAP_WIDTH = 76;
@@ -273,6 +283,6 @@ function buildErrorEntries(error, options = {}) {
273
283
  }];
274
284
  }
275
285
  //#endregion
276
- export { registerPrettyErrorSnippetReader as i, buildErrorEntries as n, decodeFileUrl as r, PRETTY_ERROR_TREE_SPACER as t };
286
+ export { isFrameworkRuntimePath as a, decodeFileUrl as i, buildErrorEntries as n, registerPrettyErrorSnippetReader as o, compactStackForStorage as r, PRETTY_ERROR_TREE_SPACER as t };
277
287
 
278
- //# sourceMappingURL=pretty-error-CVVgwlTn.mjs.map
288
+ //# sourceMappingURL=pretty-error-THg0U0w9.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pretty-error-THg0U0w9.mjs","names":[],"sources":["../src/shared/pretty-error.ts"],"sourcesContent":["import { colors, isBrowser, isDev } from '../utils'\nimport type { ResolvedPrettyError } from './dev-terminal'\n\n/** @internal Server-only snippet reader registered by Nitro plugin or initLogger. */\ntype SnippetReader = (file: string, line: number, contextLines?: number) => CodeSnippetLine[] | null\n\nlet snippetReader: SnippetReader | null = null\n\n/**\n * Register a disk-backed snippet reader (Node.js integrations only).\n * @internal\n */\nexport function registerPrettyErrorSnippetReader(reader: SnippetReader | null): void {\n snippetReader = reader\n}\n\n/** Tree-only breathing line (connector without content). */\nexport const PRETTY_ERROR_TREE_SPACER = '__EVLOG_TREE_SPACER__'\n\nfunction pushTreeSpacer(children: string[]) {\n children.push(PRETTY_ERROR_TREE_SPACER)\n}\n\n/** Pretty-print tree node for error sections. */\nexport interface PrettyErrorTreeEntry {\n key: string\n value: string\n /** Optional ANSI color for the value (server only). */\n valueColor?: string\n children?: string[]\n}\n\n/** Normalized error fields extracted from wide-event `error` context. */\nexport interface NormalizedErrorContext {\n message: string\n name?: string\n code?: string\n why?: string\n fix?: string\n link?: string\n status?: number\n stack?: string\n cause?: string\n}\n\n/** Parsed V8 stack frame. */\nexport interface StackFrame {\n raw: string\n file?: string\n line?: number\n column?: number\n fn?: string\n /** True for application source (not node_modules / build output). */\n isApp: boolean\n}\n\n/** Options for {@link buildErrorEntries}. */\nexport type PrettyErrorOptions = Partial<ResolvedPrettyError> & {\n /** Project root for relative paths in snippets. @default process.cwd() */\n cwd?: string\n}\n\nexport interface CodeSnippetLine {\n line: number\n content: string\n isErrorLine: boolean\n}\n\nconst SKIP_PATH_RE = /(?:^|[/\\\\])(?:node_modules|\\.nuxt|\\.next|\\.output)(?:[/\\\\]|$)/\nconst SKIP_FRAME_PATH_RE = /(?:^|[/\\\\])(?:packages[/\\\\]evlog|evlog[/\\\\](?:dist|src))(?:[/\\\\]|$)/\nconst SKIP_FRAME_FN_RE = /^(?:createError|EvlogError|new EvlogError)$/\n\nfunction isPlainObject(val: unknown): val is Record<string, unknown> {\n return val !== null && typeof val === 'object' && !Array.isArray(val)\n}\n\nfunction pickString(obj: Record<string, unknown>, key: string): string | undefined {\n const val = obj[key]\n return typeof val === 'string' && val.length > 0 ? val : undefined\n}\n\nfunction pickNumber(obj: Record<string, unknown>, key: string): number | undefined {\n const val = obj[key]\n return typeof val === 'number' ? val : undefined\n}\n\nfunction extractGuidance(data: Record<string, unknown>): Pick<NormalizedErrorContext, 'code' | 'why' | 'fix' | 'link'> {\n return {\n code: pickString(data, 'code'),\n why: pickString(data, 'why'),\n fix: pickString(data, 'fix'),\n link: pickString(data, 'link'),\n }\n}\n\n/**\n * Extract structured error fields from a wide-event `error` value.\n */\nexport function normalizeErrorContext(error: unknown): NormalizedErrorContext | null {\n if (error === null || error === undefined) return null\n\n if (typeof error === 'string') {\n return { message: error }\n }\n\n if (!isPlainObject(error)) {\n return { message: String(error) }\n }\n\n const message = pickString(error, 'message')\n ?? pickString(error, 'statusText')\n ?? pickString(error, 'statusMessage')\n ?? 'Unknown error'\n\n const result: NormalizedErrorContext = {\n message,\n name: pickString(error, 'name'),\n code: pickString(error, 'code'),\n why: pickString(error, 'why'),\n fix: pickString(error, 'fix'),\n link: pickString(error, 'link'),\n status: pickNumber(error, 'status') ?? pickNumber(error, 'statusCode'),\n stack: pickString(error, 'stack'),\n }\n\n const { data, cause } = error\n if (isPlainObject(data)) {\n const guidance = extractGuidance(data)\n if (!result.code) result.code = guidance.code\n if (!result.why) result.why = guidance.why\n if (!result.fix) result.fix = guidance.fix\n if (!result.link) result.link = guidance.link\n }\n\n if (cause instanceof Error) {\n result.cause = cause.message\n } else if (isPlainObject(cause) && pickString(cause, 'message')) {\n result.cause = pickString(cause, 'message')\n }\n\n return result\n}\n\n/** Decode a `file://` URL or path for display and snippet lookup. */\nexport function decodeFileUrl(file: string): string {\n if (file.startsWith('file://')) {\n try {\n return decodeURIComponent(new URL(file).pathname)\n } catch {\n return file.slice('file://'.length)\n }\n }\n return file\n}\n\nfunction isAppPath(file: string): boolean {\n const normalized = decodeFileUrl(file).replace(/\\\\/g, '/')\n if (normalized.startsWith('node:')) return false\n if (SKIP_PATH_RE.test(normalized)) return false\n if (normalized.includes('/node_modules/')) return false\n if (isFrameworkRuntimePath(normalized)) return false\n return true\n}\n\nfunction formatDisplayPath(file: string, cwd: string): string {\n const decoded = decodeFileUrl(file).replace(/\\\\/g, '/')\n const cwdNorm = cwd.replace(/\\\\/g, '/').replace(/\\/$/, '')\n if (cwdNorm && decoded.startsWith(`${cwdNorm}/`)) {\n const rel = decoded.slice(cwdNorm.length + 1)\n return rel.startsWith('./') ? rel.slice(2) : rel\n }\n const serverIdx = decoded.indexOf('/server/')\n if (serverIdx >= 0) return decoded.slice(serverIdx + 1)\n const srcIdx = decoded.indexOf('/src/')\n if (srcIdx >= 0) return decoded.slice(srcIdx + 1)\n return decoded\n}\n\n/**\n * Compact a stack trace for wide-event storage (drains, NDJSON).\n * Drops node_modules, build output, and evlog internals; keeps up to five useful frames.\n * Returns the stack unchanged when no app frame survives the filter.\n */\nexport function compactStackForStorage(stack: string | undefined, maxFrames = 5): string | undefined {\n if (!stack) return undefined\n\n const head = stack.split('\\n')[0] ?? ''\n const frames = parseStackFrames(stack)\n const useful = frames.filter(f => f.file && f.line && f.isApp && !isInternalErrorFrame(f)).slice(0, maxFrames)\n\n if (useful.length === 0) return stack\n\n return [head, ...useful.map(f => f.raw)].join('\\n')\n}\n\n/**\n * Parse a V8 stack trace string into frames.\n */\nexport function parseStackFrames(stack: string | undefined): StackFrame[] {\n if (!stack) return []\n\n const lines = stack.split('\\n')\n const frames: StackFrame[] = []\n\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed.startsWith('at ')) continue\n\n const withFn = trimmed.match(/^at (.+?) \\((.+):(\\d+):(\\d+)\\)$/)\n if (withFn) {\n const [, fn, file, lineStr, colStr] = withFn\n frames.push({\n raw: trimmed,\n fn,\n file,\n line: Number(lineStr),\n column: Number(colStr),\n isApp: isAppPath(file!),\n })\n continue\n }\n\n const withoutFn = trimmed.match(/^at (.+):(\\d+):(\\d+)$/)\n if (withoutFn) {\n const [, file, lineStr, colStr] = withoutFn\n frames.push({\n raw: trimmed,\n file,\n line: Number(lineStr),\n column: Number(colStr),\n isApp: isAppPath(file!),\n })\n continue\n }\n\n const asyncFn = trimmed.match(/^at async (.+?) \\((.+):(\\d+):(\\d+)\\)$/)\n if (asyncFn) {\n const [, fn, file, lineStr, colStr] = asyncFn\n frames.push({\n raw: trimmed,\n fn: `async ${fn}`,\n file,\n line: Number(lineStr),\n column: Number(colStr),\n isApp: isAppPath(file!),\n })\n }\n }\n\n return frames\n}\n\n/** True for Next.js runtime internals (route-modules, next/dist, bundled next-server). */\nexport function isFrameworkRuntimePath(path: string): boolean {\n const normalized = path.replace(/\\\\/g, '/')\n return normalized.includes('route-modules/')\n || normalized.includes('webpack://next/')\n || normalized.includes('/next/dist/')\n || normalized.includes('/compiled/next-server/')\n}\n\nfunction isFrameworkRuntimeFrame(frame: StackFrame): boolean {\n if (!frame.file) return false\n return isFrameworkRuntimePath(decodeFileUrl(frame.file))\n}\n\nfunction isInternalErrorFrame(frame: StackFrame): boolean {\n if (isFrameworkRuntimeFrame(frame)) return true\n if (frame.fn) {\n const fn = frame.fn.replace(/^async /, '')\n if (SKIP_FRAME_FN_RE.test(fn)) return true\n }\n if (!frame.file) return true\n const path = decodeFileUrl(frame.file).replace(/\\\\/g, '/')\n if (SKIP_FRAME_PATH_RE.test(path)) return true\n if (path.includes('.nuxt/')) return true\n if (path.includes('.next/')) return true\n return false\n}\n\n/**\n * Pick the most useful frame for code snippets (topmost app throw site).\n */\nexport function pickPrimaryFrame(frames: StackFrame[]): StackFrame | undefined {\n for (const frame of frames) {\n if (frame.isApp && frame.file && frame.line && !isInternalErrorFrame(frame)) {\n return frame\n }\n }\n return undefined\n}\n\n/**\n * Read source lines around a stack frame when a server snippet reader is registered.\n */\nexport function readCodeSnippet(\n file: string,\n line: number,\n contextLines = 2,\n): CodeSnippetLine[] | null {\n if (!isDev() || isBrowser() || !snippetReader) return null\n const path = decodeFileUrl(file).replace(/\\\\/g, '/')\n if (path.includes('.next/') || path.includes('.nuxt/') || path.includes('.output/')) return null\n return snippetReader(file, line, contextLines)\n}\n\nfunction formatSnippetLines(snippet: CodeSnippetLine[]): string[] {\n const width = String(snippet[snippet.length - 1]?.line ?? 0).length\n return snippet.map(({ line, content, isErrorLine }) => {\n const marker = isErrorLine ? `${colors.red}❯${colors.reset}` : `${colors.dim} ${colors.reset}`\n const numColor = isErrorLine ? colors.red : colors.gray\n const trimmed = content.length > 120 ? `${content.slice(0, 117)}…` : content\n return `${marker} ${numColor}${String(line).padStart(width, ' ')}${colors.reset} ${colors.dim}┃${colors.reset} ${colors.dim}${trimmed}${colors.reset}`\n })\n}\n\nfunction formatFrameLocation(frame: StackFrame, cwd: string): string {\n const file = frame.file ? formatDisplayPath(frame.file, cwd) : 'unknown'\n const loc = frame.line ? `${file}:${frame.line}` : file\n const fn = frame.fn && frame.fn !== '<unknown>' ? frame.fn : undefined\n return fn ? `at ${fn} (${loc})` : `at ${loc}`\n}\n\nfunction formatCollapsedFrame(frame: StackFrame, cwd: string): string {\n const file = frame.file ? formatDisplayPath(frame.file, cwd) : 'unknown'\n const loc = frame.line ? `${file}:${frame.line}` : file\n const prefix = frame.fn?.startsWith('async') ? 'at async ' : 'at '\n const fn = frame.fn?.replace(/^async /, '')\n if (fn && fn !== '<unknown>' && fn !== loc) {\n return `${prefix}${fn} (${loc})`\n }\n return `${prefix}${loc}`\n}\n\nconst GUIDANCE_WRAP_WIDTH = 76\nconst GUIDANCE_CONTINUATION = ' '\n\n/** Wrap guidance text with hanging indent for long Why/Fix lines. */\nfunction formatGuidanceLine(label: string, text: string, labelColor: string): string[] {\n const prefix = `${labelColor}${label}:${colors.reset} `\n const lines: string[] = []\n let remaining = text.trim()\n let first = true\n\n while (remaining.length > 0) {\n const budget = first\n ? Math.max(24, GUIDANCE_WRAP_WIDTH - prefix.length)\n : Math.max(24, GUIDANCE_WRAP_WIDTH - GUIDANCE_CONTINUATION.length)\n if (remaining.length <= budget) {\n lines.push(first ? `${prefix}${remaining}` : `${GUIDANCE_CONTINUATION}${remaining}`)\n break\n }\n let split = remaining.lastIndexOf(' ', budget)\n if (split <= 0) split = budget\n const chunk = remaining.slice(0, split).trimEnd()\n lines.push(first ? `${prefix}${chunk}` : `${GUIDANCE_CONTINUATION}${chunk}`)\n remaining = remaining.slice(split).trimStart()\n first = false\n }\n\n return lines\n}\n\n/**\n * Build pretty-print tree entries for a wide-event `error` field.\n */\nexport function buildErrorEntries(\n error: unknown,\n options: PrettyErrorOptions = {},\n): PrettyErrorTreeEntry[] {\n const normalized = normalizeErrorContext(error)\n if (!normalized) return []\n\n const cwd = options.cwd ?? (typeof process !== 'undefined' && typeof process.cwd === 'function' ? process.cwd() : '.')\n const compact = options.compact ?? isDev()\n const detail = options.detail ?? 'full'\n const guidanceOnly = detail === 'guidance'\n const showFrames = !guidanceOnly && !isBrowser() && (options.snippet ?? isDev())\n const stackDepth = guidanceOnly\n ? 0\n : (options.stackDepth ?? (compact ? 2 : 3))\n const snippetContextLines = compact ? 1 : 2\n\n const children: string[] = []\n const frames = guidanceOnly ? [] : parseStackFrames(normalized.stack)\n const primary = guidanceOnly ? undefined : pickPrimaryFrame(frames)\n\n if (!guidanceOnly && primary?.file && primary.line) {\n pushTreeSpacer(children)\n if (showFrames) {\n const snippet = readCodeSnippet(primary.file, primary.line, snippetContextLines)\n children.push(`${colors.dim} ${formatFrameLocation(primary, cwd)}${colors.reset}`)\n if (snippet) {\n children.push(...formatSnippetLines(snippet))\n }\n } else {\n children.push(`${colors.dim} ${formatFrameLocation(primary, cwd)}${colors.reset}`)\n }\n }\n\n if (normalized.code) {\n children.push(`${colors.dim}Code:${colors.reset} ${normalized.code}`)\n }\n\n const hasGuidance = Boolean(normalized.why || normalized.fix || normalized.link)\n if (hasGuidance) {\n pushTreeSpacer(children)\n }\n\n if (normalized.why) {\n children.push(...formatGuidanceLine('Why', normalized.why, colors.yellow))\n }\n if (normalized.fix) {\n children.push(...formatGuidanceLine('Fix', normalized.fix, colors.cyan))\n }\n if (normalized.link) {\n children.push(`${colors.dim}More:${colors.reset} ${normalized.link}`)\n }\n\n if (normalized.cause && normalized.cause !== normalized.message) {\n children.push(`${colors.dim}Caused by:${colors.reset} ${normalized.cause}`)\n }\n\n const hiddenCount = guidanceOnly ? 0 : frames.filter(f => !f.isApp || isInternalErrorFrame(f)).length\n const tailFrames = stackDepth > 0\n ? frames.filter(f => f !== primary && !isInternalErrorFrame(f)).slice(0, stackDepth)\n : []\n\n if (!guidanceOnly && (hiddenCount > 0 || tailFrames.length > 0)) {\n pushTreeSpacer(children)\n if (hiddenCount > 0) {\n children.push(`${colors.gray}stack (${hiddenCount} frame${hiddenCount === 1 ? '' : 's'} hidden in node_modules)${colors.reset}`)\n } else {\n children.push(`${colors.gray}stack${colors.reset}`)\n }\n for (const frame of tailFrames) {\n children.push(`${colors.gray} ${formatCollapsedFrame(frame, cwd)}${colors.reset}`)\n }\n }\n\n return [\n {\n key: 'error',\n value: `${colors.red}${colors.bold}${normalized.message}${colors.reset}`,\n children: children.length > 0 ? children : undefined,\n },\n ]\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import { r as decodeFileUrl } from "./pretty-error-CVVgwlTn.mjs";
1
+ import { i as decodeFileUrl } from "./pretty-error-THg0U0w9.mjs";
2
+ import "node:module";
2
3
  import { resolve } from "node:path";
3
4
  import { readFileSync } from "node:fs";
4
5
  //#region \0rolldown/runtime.js
@@ -44,4 +45,4 @@ function readCodeSnippetFromDisk(file, line, contextLines = 2) {
44
45
  //#endregion
45
46
  export { readCodeSnippetFromDisk as n, pretty_error_snippet_node_exports as t };
46
47
 
47
- //# sourceMappingURL=pretty-error-snippet.node-c_bzjg7g.mjs.map
48
+ //# sourceMappingURL=pretty-error-snippet.node-itfCajBM.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"pretty-error-snippet.node-c_bzjg7g.mjs","names":[],"sources":["../src/shared/pretty-error-snippet.node.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport type { CodeSnippetLine } from './pretty-error'\nimport { decodeFileUrl } from './pretty-error'\n\n/**\n * Read source lines around a stack frame from disk (Node.js only).\n */\nexport function readCodeSnippetFromDisk(\n file: string,\n line: number,\n contextLines = 2,\n): CodeSnippetLine[] | null {\n const decoded = decodeFileUrl(file)\n let content: string\n try {\n content = readFileSync(decoded, 'utf8')\n } catch {\n try {\n content = readFileSync(resolve(process.cwd(), decoded), 'utf8')\n } catch {\n return null\n }\n }\n\n const lines = content.split('\\n')\n const start = Math.max(0, line - contextLines - 1)\n const end = Math.min(lines.length, line + contextLines)\n const snippet: CodeSnippetLine[] = []\n\n for (let i = start; i < end; i++) {\n snippet.push({\n line: i + 1,\n content: lines[i] ?? '',\n isErrorLine: i + 1 === line,\n })\n }\n\n return snippet.length > 0 ? snippet : null\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAQA,SAAgB,wBACd,MACA,MACA,eAAe,GACW;CAC1B,MAAM,UAAU,cAAc,KAAK;CACnC,IAAI;AACJ,KAAI;AACF,YAAU,aAAa,SAAS,OAAO;SACjC;AACN,MAAI;AACF,aAAU,aAAa,QAAQ,QAAQ,KAAK,EAAE,QAAQ,EAAE,OAAO;UACzD;AACN,UAAO;;;CAIX,MAAM,QAAQ,QAAQ,MAAM,KAAK;CACjC,MAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,eAAe,EAAE;CAClD,MAAM,MAAM,KAAK,IAAI,MAAM,QAAQ,OAAO,aAAa;CACvD,MAAM,UAA6B,EAAE;AAErC,MAAK,IAAI,IAAI,OAAO,IAAI,KAAK,IAC3B,SAAQ,KAAK;EACX,MAAM,IAAI;EACV,SAAS,MAAM,MAAM;EACrB,aAAa,IAAI,MAAM;EACxB,CAAC;AAGJ,QAAO,QAAQ,SAAS,IAAI,UAAU"}
1
+ {"version":3,"file":"pretty-error-snippet.node-itfCajBM.mjs","names":[],"sources":["../src/shared/pretty-error-snippet.node.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport type { CodeSnippetLine } from './pretty-error'\nimport { decodeFileUrl } from './pretty-error'\n\n/**\n * Read source lines around a stack frame from disk (Node.js only).\n */\nexport function readCodeSnippetFromDisk(\n file: string,\n line: number,\n contextLines = 2,\n): CodeSnippetLine[] | null {\n const decoded = decodeFileUrl(file)\n let content: string\n try {\n content = readFileSync(decoded, 'utf8')\n } catch {\n try {\n content = readFileSync(resolve(process.cwd(), decoded), 'utf8')\n } catch {\n return null\n }\n }\n\n const lines = content.split('\\n')\n const start = Math.max(0, line - contextLines - 1)\n const end = Math.min(lines.length, line + contextLines)\n const snippet: CodeSnippetLine[] = []\n\n for (let i = start; i < end; i++) {\n snippet.push({\n line: i + 1,\n content: lines[i] ?? '',\n isErrorLine: i + 1 === line,\n })\n }\n\n return snippet.length > 0 ? snippet : null\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAQA,SAAgB,wBACd,MACA,MACA,eAAe,GACW;CAC1B,MAAM,UAAU,cAAc,KAAK;CACnC,IAAI;AACJ,KAAI;AACF,YAAU,aAAa,SAAS,OAAO;SACjC;AACN,MAAI;AACF,aAAU,aAAa,QAAQ,QAAQ,KAAK,EAAE,QAAQ,EAAE,OAAO;UACzD;AACN,UAAO;;;CAIX,MAAM,QAAQ,QAAQ,MAAM,KAAK;CACjC,MAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,eAAe,EAAE;CAClD,MAAM,MAAM,KAAK,IAAI,MAAM,QAAQ,OAAO,aAAa;CACvD,MAAM,UAA6B,EAAE;AAErC,MAAK,IAAI,IAAI,OAAO,IAAI,KAAK,IAC3B,SAAQ,KAAK;EACX,MAAM,IAAI;EACV,SAAS,MAAM,MAAM;EACrB,aAAa,IAAI,MAAM;EACxB,CAAC;AAGJ,QAAO,QAAQ,SAAS,IAAI,UAAU"}
@@ -1,5 +1,5 @@
1
- import { rt as RequestLogger } from "../audit-BUAajsPU.mjs";
2
- import { t as BaseEvlogOptions } from "../middleware-DQ6-h8h0.mjs";
1
+ import { rt as RequestLogger } from "../audit-D7v6JHj0.mjs";
2
+ import { t as BaseEvlogOptions } from "../middleware-B_k4Mrzg.mjs";
3
3
  import * as _$react_router0 from "react-router";
4
4
 
5
5
  //#region src/react-router/index.d.ts
@@ -1,5 +1,5 @@
1
1
  import { t as extractSafeHeaders } from "../headers-VtmnWcfn.mjs";
2
- import { r as createMiddlewareLogger, t as attachForkToLogger } from "../fork-CYm453dq.mjs";
2
+ import { r as createMiddlewareLogger, t as attachForkToLogger } from "../fork-CgGlAaHa.mjs";
3
3
  import { t as createLoggerStorage } from "../storage-7X37OToT.mjs";
4
4
  import { createContext } from "react-router";
5
5
  //#region src/react-router/index.ts
@@ -1,4 +1,4 @@
1
- import { X as LogLevel, Y as Log, dt as TransportConfig } from "../../audit-BUAajsPU.mjs";
1
+ import { X as LogLevel, Y as Log, dt as TransportConfig } from "../../audit-D7v6JHj0.mjs";
2
2
 
3
3
  //#region src/runtime/client/log.d.ts
4
4
  declare function setIdentity(identity: Record<string, unknown>): void;
@@ -1,6 +1,13 @@
1
1
  import * as _$h3 from "h3";
2
2
 
3
3
  //#region src/runtime/server/routes/_evlog/ingest.post.d.ts
4
+ /**
5
+ * Client log ingestion endpoint.
6
+ *
7
+ * The origin check is CSRF-level protection only: it blocks cross-site browser
8
+ * requests but is trivially satisfied by non-browser clients. Treat ingested
9
+ * events as untrusted input — this endpoint is intentionally unauthenticated.
10
+ */
4
11
  declare const _default: _$h3.EventHandler<_$h3.EventHandlerRequest, Promise<null>>;
5
12
  //#endregion
6
13
  export { _default as default };
@@ -1,6 +1,6 @@
1
1
  import { filterSafeHeaders } from "../../../../utils.mjs";
2
- import { _ as getEnvironment, y as getGlobalPluginRunner } from "../../../../audit-BFwTUxBJ.mjs";
3
- import { createError, defineEventHandler, getHeader, getHeaders, getRequestHost, readBody, setResponseStatus } from "h3";
2
+ import { _ as getEnvironment, y as getGlobalPluginRunner } from "../../../../audit-BQt8yAHo.mjs";
3
+ import { createError, defineEventHandler, getHeader, getHeaders, getRequestHost, readRawBody, setResponseStatus } from "h3";
4
4
  import { useNitroApp } from "nitropack/runtime";
5
5
  //#region src/runtime/server/routes/_evlog/ingest.post.ts
6
6
  const VALID_LEVELS = [
@@ -29,6 +29,35 @@ function validateOrigin(event) {
29
29
  message: "Invalid origin"
30
30
  });
31
31
  }
32
+ /**
33
+ * Maximum accepted ingest body size in bytes. Client wide events are small;
34
+ * anything larger is rejected before it reaches the enrich/drain pipeline.
35
+ */
36
+ const MAX_BODY_BYTES = 32 * 1024;
37
+ async function readJsonBody(event) {
38
+ const contentLength = Number(getHeader(event, "content-length"));
39
+ if (Number.isFinite(contentLength) && contentLength > MAX_BODY_BYTES) throw createError({
40
+ statusCode: 413,
41
+ message: "Payload too large"
42
+ });
43
+ const raw = await readRawBody(event, "utf8");
44
+ if (!raw) throw createError({
45
+ statusCode: 400,
46
+ message: "Invalid request body"
47
+ });
48
+ if (new TextEncoder().encode(raw).byteLength > MAX_BODY_BYTES) throw createError({
49
+ statusCode: 413,
50
+ message: "Payload too large"
51
+ });
52
+ try {
53
+ return JSON.parse(raw);
54
+ } catch {
55
+ throw createError({
56
+ statusCode: 400,
57
+ message: "Invalid request body"
58
+ });
59
+ }
60
+ }
32
61
  const ISO_8601_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/;
33
62
  function isValidISOTimestamp(value) {
34
63
  if (!ISO_8601_REGEX.test(value)) return false;
@@ -93,9 +122,16 @@ function resolveWaitUntilContext(event) {
93
122
  if (isRecord(cloudflare) && isRecord(cloudflare.context)) return cloudflare.context;
94
123
  return context;
95
124
  }
125
+ /**
126
+ * Client log ingestion endpoint.
127
+ *
128
+ * The origin check is CSRF-level protection only: it blocks cross-site browser
129
+ * requests but is trivially satisfied by non-browser clients. Treat ingested
130
+ * events as untrusted input — this endpoint is intentionally unauthenticated.
131
+ */
96
132
  var ingest_post_default = defineEventHandler(async (event) => {
97
133
  validateOrigin(event);
98
- const payload = validatePayload(await readBody(event));
134
+ const payload = validatePayload(await readJsonBody(event));
99
135
  const nitroApp = useNitroApp();
100
136
  const env = getEnvironment();
101
137
  const { service: _clientService, ...sanitizedPayload } = payload;