evlog 2.13.0 → 2.14.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 (132) hide show
  1. package/README.md +118 -15
  2. package/dist/{_http-CHSsrWDJ.mjs → _http-BY1e9pwC.mjs} +9 -2
  3. package/dist/_http-BY1e9pwC.mjs.map +1 -0
  4. package/dist/adapters/axiom.d.mts +1 -1
  5. package/dist/adapters/axiom.mjs +1 -1
  6. package/dist/adapters/axiom.mjs.map +1 -1
  7. package/dist/adapters/better-stack.d.mts +1 -1
  8. package/dist/adapters/better-stack.mjs +1 -1
  9. package/dist/adapters/better-stack.mjs.map +1 -1
  10. package/dist/adapters/datadog.d.mts +1 -1
  11. package/dist/adapters/datadog.mjs +1 -1
  12. package/dist/adapters/datadog.mjs.map +1 -1
  13. package/dist/adapters/fs.d.mts +1 -1
  14. package/dist/adapters/fs.mjs.map +1 -1
  15. package/dist/adapters/hyperdx.d.mts +1 -1
  16. package/dist/adapters/hyperdx.mjs +1 -1
  17. package/dist/adapters/otlp.d.mts +1 -1
  18. package/dist/adapters/otlp.mjs +1 -1
  19. package/dist/adapters/otlp.mjs.map +1 -1
  20. package/dist/adapters/posthog.d.mts +1 -1
  21. package/dist/adapters/posthog.mjs +1 -1
  22. package/dist/adapters/posthog.mjs.map +1 -1
  23. package/dist/adapters/sentry.d.mts +1 -1
  24. package/dist/adapters/sentry.mjs +1 -1
  25. package/dist/adapters/sentry.mjs.map +1 -1
  26. package/dist/ai/index.d.mts +106 -5
  27. package/dist/ai/index.d.mts.map +1 -1
  28. package/dist/ai/index.mjs +28 -5
  29. package/dist/ai/index.mjs.map +1 -1
  30. package/dist/{types-DbzDln7O.d.mts → audit-CTIviX3P.d.mts} +509 -3
  31. package/dist/audit-CTIviX3P.d.mts.map +1 -0
  32. package/dist/{logger-DnobymUQ.mjs → audit-DQoBo7Dl.mjs} +758 -16
  33. package/dist/audit-DQoBo7Dl.mjs.map +1 -0
  34. package/dist/better-auth/index.d.mts +1 -1
  35. package/dist/better-auth/index.mjs.map +1 -1
  36. package/dist/browser.d.mts +1 -1
  37. package/dist/elysia/index.d.mts +2 -2
  38. package/dist/elysia/index.mjs +2 -2
  39. package/dist/enrichers.d.mts +1 -1
  40. package/dist/enrichers.mjs.map +1 -1
  41. package/dist/{error-B9CiGK_i.d.mts → error-C7gSQVqk.d.mts} +2 -2
  42. package/dist/{error-B9CiGK_i.d.mts.map → error-C7gSQVqk.d.mts.map} +1 -1
  43. package/dist/error.d.mts +1 -1
  44. package/dist/{errors-Dr0r4OpR.d.mts → errors-4MPmTzjY.d.mts} +2 -2
  45. package/dist/{errors-Dr0r4OpR.d.mts.map → errors-4MPmTzjY.d.mts.map} +1 -1
  46. package/dist/express/index.d.mts +2 -2
  47. package/dist/express/index.mjs +2 -2
  48. package/dist/fastify/index.d.mts +2 -2
  49. package/dist/fastify/index.mjs +2 -2
  50. package/dist/{fork-Y4z8iHti.mjs → fork-D1j1Fuzy.mjs} +3 -3
  51. package/dist/{fork-Y4z8iHti.mjs.map → fork-D1j1Fuzy.mjs.map} +1 -1
  52. package/dist/hono/index.d.mts +2 -2
  53. package/dist/hono/index.mjs +1 -1
  54. package/dist/http.d.mts +1 -1
  55. package/dist/http.d.mts.map +1 -1
  56. package/dist/http.mjs +3 -2
  57. package/dist/http.mjs.map +1 -1
  58. package/dist/index.d.mts +7 -7
  59. package/dist/index.mjs +2 -2
  60. package/dist/{logger-Dp6nYWjH.d.mts → logger-DttRJRGa.d.mts} +23 -4
  61. package/dist/logger-DttRJRGa.d.mts.map +1 -0
  62. package/dist/logger.d.mts +1 -1
  63. package/dist/logger.mjs +1 -1
  64. package/dist/{middleware-FgC1OdOD.d.mts → middleware-CTnDsST-.d.mts} +2 -2
  65. package/dist/{middleware-FgC1OdOD.d.mts.map → middleware-CTnDsST-.d.mts.map} +1 -1
  66. package/dist/{middleware-BtBuosFV.mjs → middleware-oAccqyPp.mjs} +2 -2
  67. package/dist/{middleware-BtBuosFV.mjs.map → middleware-oAccqyPp.mjs.map} +1 -1
  68. package/dist/nestjs/index.d.mts +2 -2
  69. package/dist/nestjs/index.mjs +2 -2
  70. package/dist/next/client.d.mts +1 -1
  71. package/dist/next/index.d.mts +4 -4
  72. package/dist/next/index.mjs +2 -2
  73. package/dist/next/index.mjs.map +1 -1
  74. package/dist/next/instrumentation.d.mts +1 -1
  75. package/dist/next/instrumentation.mjs +1 -1
  76. package/dist/next/instrumentation.mjs.map +1 -1
  77. package/dist/nitro/errorHandler.mjs.map +1 -1
  78. package/dist/nitro/module.d.mts +2 -2
  79. package/dist/nitro/plugin.mjs +1 -1
  80. package/dist/nitro/plugin.mjs.map +1 -1
  81. package/dist/nitro/v3/index.d.mts +2 -2
  82. package/dist/nitro/v3/middleware.mjs.map +1 -1
  83. package/dist/nitro/v3/module.d.mts +1 -1
  84. package/dist/nitro/v3/plugin.mjs +1 -1
  85. package/dist/nitro/v3/plugin.mjs.map +1 -1
  86. package/dist/nitro/v3/useLogger.d.mts +1 -1
  87. package/dist/nitro/v3/useLogger.mjs.map +1 -1
  88. package/dist/{nitro-CDHLfRdw.d.mts → nitro-CPPRCPbG.d.mts} +2 -2
  89. package/dist/{nitro-CDHLfRdw.d.mts.map → nitro-CPPRCPbG.d.mts.map} +1 -1
  90. package/dist/nuxt/module.d.mts +1 -1
  91. package/dist/nuxt/module.mjs +1 -1
  92. package/dist/{parseError-DM-lyezZ.d.mts → parseError-o1GpZEOR.d.mts} +2 -2
  93. package/dist/parseError-o1GpZEOR.d.mts.map +1 -0
  94. package/dist/pipeline.mjs.map +1 -1
  95. package/dist/react-router/index.d.mts +2 -2
  96. package/dist/react-router/index.mjs +2 -2
  97. package/dist/runtime/client/log.d.mts +1 -1
  98. package/dist/runtime/client/log.mjs +2 -2
  99. package/dist/runtime/client/log.mjs.map +1 -1
  100. package/dist/runtime/client/plugin.mjs.map +1 -1
  101. package/dist/runtime/server/routes/_evlog/ingest.post.mjs +1 -1
  102. package/dist/runtime/server/routes/_evlog/ingest.post.mjs.map +1 -1
  103. package/dist/runtime/server/useLogger.d.mts +1 -1
  104. package/dist/runtime/server/useLogger.mjs.map +1 -1
  105. package/dist/runtime/utils/parseError.d.mts +2 -2
  106. package/dist/source-location-DRvDDqfq.mjs.map +1 -1
  107. package/dist/sveltekit/index.d.mts +2 -2
  108. package/dist/sveltekit/index.mjs +2 -2
  109. package/dist/sveltekit/index.mjs.map +1 -1
  110. package/dist/toolkit.d.mts +3 -3
  111. package/dist/toolkit.mjs +2 -2
  112. package/dist/types.d.mts +2 -2
  113. package/dist/{useLogger-N5A-d5l9.d.mts → useLogger-CyPP1sVB.d.mts} +2 -2
  114. package/dist/{useLogger-N5A-d5l9.d.mts.map → useLogger-CyPP1sVB.d.mts.map} +1 -1
  115. package/dist/{utils-DnX6VMNi.d.mts → utils-Dmin7wVL.d.mts} +4 -3
  116. package/dist/utils-Dmin7wVL.d.mts.map +1 -0
  117. package/dist/utils.d.mts +2 -2
  118. package/dist/utils.mjs +7 -1
  119. package/dist/utils.mjs.map +1 -1
  120. package/dist/vite/index.d.mts +1 -1
  121. package/dist/vite/index.mjs.map +1 -1
  122. package/dist/workers.d.mts +48 -4
  123. package/dist/workers.d.mts.map +1 -1
  124. package/dist/workers.mjs +32 -5
  125. package/dist/workers.mjs.map +1 -1
  126. package/package.json +17 -21
  127. package/dist/_http-CHSsrWDJ.mjs.map +0 -1
  128. package/dist/logger-DnobymUQ.mjs.map +0 -1
  129. package/dist/logger-Dp6nYWjH.d.mts.map +0 -1
  130. package/dist/parseError-DM-lyezZ.d.mts.map +0 -1
  131. package/dist/types-DbzDln7O.d.mts.map +0 -1
  132. package/dist/utils-DnX6VMNi.d.mts.map +0 -1
package/README.md CHANGED
@@ -336,30 +336,40 @@ async function processSyncJob(job: Job) {
336
336
 
337
337
  ## Cloudflare Workers
338
338
 
339
- Use the Workers adapter for structured logs and correct platform severity.
339
+ Use the Workers adapter for structured logs and correct platform severity. With `initWorkersLogger({ drain })`, use **`defineWorkerFetch`** so async drains are registered with `waitUntil` automatically (Cloudflare only passes `ExecutionContext` as the third `fetch` argument — there is no global).
340
340
 
341
341
  ```typescript
342
342
  // src/index.ts
343
- import { initWorkersLogger, createWorkersLogger } from 'evlog/workers'
343
+ import { defineWorkerFetch, initWorkersLogger } from 'evlog/workers'
344
344
 
345
345
  initWorkersLogger({
346
346
  env: { service: 'edge-api' },
347
347
  })
348
348
 
349
+ export default defineWorkerFetch(async (request, _env, _ctx, log) => {
350
+ try {
351
+ log.set({ route: 'health' })
352
+ const response = new Response('ok', { status: 200 })
353
+ log.emit({ status: response.status })
354
+ return response
355
+ } catch (error) {
356
+ log.error(error as Error)
357
+ log.emit({ status: 500 })
358
+ throw error
359
+ }
360
+ })
361
+ ```
362
+
363
+ If you keep a raw `export default { fetch }`, pass `{ executionCtx: ctx }` to `createWorkersLogger` or `waitUntil` on `createRequestLogger`.
364
+
365
+ ```typescript
366
+ // Lower-level (equivalent)
367
+ import { createWorkersLogger } from 'evlog/workers'
368
+
349
369
  export default {
350
- async fetch(request: Request) {
351
- const log = createWorkersLogger(request)
352
-
353
- try {
354
- log.set({ route: 'health' })
355
- const response = new Response('ok', { status: 200 })
356
- log.emit({ status: response.status })
357
- return response
358
- } catch (error) {
359
- log.error(error as Error)
360
- log.emit({ status: 500 })
361
- throw error
362
- }
370
+ async fetch(request: Request, _env: unknown, ctx: ExecutionContext) {
371
+ const log = createWorkersLogger(request, { executionCtx: ctx })
372
+ // ...
363
373
  },
364
374
  }
365
375
  ```
@@ -373,6 +383,7 @@ invocation_logs = false
373
383
  ```
374
384
 
375
385
  Notes:
386
+ - Prefer **`defineWorkerFetch`** so you do not have to pass `executionCtx` yourself when using a drain
376
387
  - `requestId` defaults to `cf-ray` when available
377
388
  - `request.cf` is included (colo, country, asn) unless disabled
378
389
  - Use `headerAllowlist` to avoid logging sensitive headers
@@ -693,6 +704,62 @@ export default defineNitroPlugin((nitroApp) => {
693
704
  })
694
705
  ```
695
706
 
707
+ ## Audit Logs
708
+
709
+ Audit logs are not a parallel system: they are a typed `audit` field on the wide event plus a few helpers. Add 1 enricher + 1 drain wrapper + `log.audit()` and you get tamper-evident, redact-aware, force-kept audit events through the same pipeline.
710
+
711
+ ```typescript
712
+ // server/plugins/evlog.ts
713
+ import { auditEnricher, auditOnly, signed } from 'evlog'
714
+ import { createAxiomDrain } from 'evlog/axiom'
715
+ import { createFsDrain } from 'evlog/fs'
716
+
717
+ export default defineNitroPlugin((nitroApp) => {
718
+ const enrich = [auditEnricher({ tenantId: ctx => ctx.headers?.['x-tenant-id'] })]
719
+ const audits = auditOnly(signed(createFsDrain({ path: '.audit/' }), { strategy: 'hash-chain' }), { await: true })
720
+ const main = createAxiomDrain()
721
+
722
+ nitroApp.hooks.hook('evlog:enrich', async ctx => { for (const e of enrich) await e(ctx) })
723
+ nitroApp.hooks.hook('evlog:drain', async ctx => { await Promise.all([main(ctx), audits(ctx)]) })
724
+ })
725
+ ```
726
+
727
+ ```typescript
728
+ // server/api/invoice/[id]/refund.post.ts
729
+ import { auditDiff } from 'evlog'
730
+
731
+ export default defineEventHandler(async (event) => {
732
+ const log = useLogger(event)
733
+ const before = await db.invoice.get(id)
734
+ const after = await db.invoice.refund(id)
735
+
736
+ log.audit?.({
737
+ action: 'invoice.refund',
738
+ actor: { type: 'user', id: user.id, email: user.email },
739
+ target: { type: 'invoice', id: after.id },
740
+ outcome: 'success',
741
+ changes: auditDiff(before, after),
742
+ })
743
+ })
744
+ ```
745
+
746
+ | Symbol | Kind | Purpose |
747
+ |--------|------|---------|
748
+ | `log.audit(fields)` / `log.audit.deny(reason, fields)` | method | Sugar over `log.set({ audit })` + force-keep |
749
+ | `audit(fields)` | function | Standalone for jobs / scripts |
750
+ | `withAudit({ action, target })(fn)` | wrapper | Auto-emit success / failure / denied |
751
+ | `defineAuditAction(name, opts?)` | factory | Typed action registry |
752
+ | `auditDiff(before, after)` | helper | Redact-aware JSON Patch for `changes` |
753
+ | `mockAudit()` | test util | Capture and assert audits in tests |
754
+ | `auditEnricher({ tenantId? })` | enricher | Auto-fill `req`/`trace`/`ip`/`ua`/`tenantId` context |
755
+ | `auditOnly(drain, { await? })` | wrapper | Routes only events with `event.audit` |
756
+ | `signed(drain, { strategy: 'hmac' \| 'hash-chain', ... })` | wrapper | Tamper-evident integrity |
757
+ | `auditRedactPreset` | preset | Strict PII for audit events |
758
+
759
+ `AuditFields` is exported and merges with `BaseWideEvent` — augment it with `declare module` if you need extra typed fields. Audit events are always force-kept by tail sampling and get a deterministic `idempotencyKey` so retries are safe across drains.
760
+
761
+ See [the Audit Logs guide](https://evlog.dev/logging/audit) for compliance, GDPR, and recipe details.
762
+
696
763
  ## AI SDK Integration
697
764
 
698
765
  Capture token usage, tool calls, model info, and streaming metrics from the [Vercel AI SDK](https://ai-sdk.dev) into wide events. Requires `ai >= 6.0.0`.
@@ -719,6 +786,23 @@ The middleware captures: `inputTokens`, `outputTokens`, `cacheReadTokens`, `reas
719
786
 
720
787
  For embeddings: `ai.captureEmbed({ usage })`.
721
788
 
789
+ The same metadata is also exposed as a public API for custom analytics, billing, or user-facing dashboards:
790
+
791
+ ```typescript
792
+ const ai = createAILogger(log, {
793
+ cost: { 'claude-sonnet-4.6': { input: 3, output: 15 } },
794
+ })
795
+
796
+ await generateText({ model: ai.wrap('anthropic/claude-sonnet-4.6'), prompt })
797
+
798
+ const metadata = ai.getMetadata() // structured snapshot (AIMetadata)
799
+ const cost = ai.getEstimatedCost() // dollars, or undefined
800
+
801
+ ai.onUpdate((metadata) => { // incremental updates per step
802
+ pushToClient({ tokens: metadata.totalTokens, cost: metadata.estimatedCost })
803
+ })
804
+ ```
805
+
722
806
  ## Adapters
723
807
 
724
808
  Send your logs to external observability platforms with built-in adapters.
@@ -1112,6 +1196,21 @@ initWorkersLogger({
1112
1196
  })
1113
1197
  ```
1114
1198
 
1199
+ ### `defineWorkerFetch(handler)`
1200
+
1201
+ Recommended for Workers when using **`initWorkersLogger({ drain })`**. Wraps your handler so `createWorkersLogger` always receives `executionCtx` — you do not pass `ctx` into the factory yourself. Cloudflare does not expose `ExecutionContext` globally (only as `fetch`’s third argument), so this is the “automatic” option for plain Workers scripts.
1202
+
1203
+ ```typescript
1204
+ import { defineWorkerFetch, initWorkersLogger } from 'evlog/workers'
1205
+
1206
+ initWorkersLogger({ env: { service: 'edge-api' }, drain })
1207
+
1208
+ export default defineWorkerFetch(async (request, env, ctx, log) => {
1209
+ log.emit({ status: 200 })
1210
+ return new Response('ok')
1211
+ })
1212
+ ```
1213
+
1115
1214
  ### `createWorkersLogger(request, options?)`
1116
1215
 
1117
1216
  Create a request-scoped logger for Workers. Auto-extracts `cf-ray`, `request.cf`, method, and path.
@@ -1119,11 +1218,15 @@ Create a request-scoped logger for Workers. Auto-extracts `cf-ray`, `request.cf`
1119
1218
  ```typescript
1120
1219
  import { createWorkersLogger } from 'evlog/workers'
1121
1220
 
1221
+ // ctx is the third argument to fetch(request, env, ctx)
1122
1222
  const log = createWorkersLogger(request, {
1123
1223
  requestId: 'custom-id', // Override cf-ray (default: cf-ray header)
1124
1224
  headers: ['x-request-id'], // Headers to include (default: none)
1225
+ executionCtx: ctx, // With initWorkersLogger({ drain }), registers async drain via waitUntil
1125
1226
  })
1126
1227
 
1228
+ // Or pass waitUntil directly: waitUntil: ctx.waitUntil.bind(ctx)
1229
+
1127
1230
  log.set({ user: { id: '123' } })
1128
1231
  log.emit({ status: 200 })
1129
1232
  ```
@@ -10,13 +10,20 @@ function getRuntimeConfig() {
10
10
  return getNitroRuntimeConfigRecord();
11
11
  }
12
12
  async function resolveAdapterConfig(namespace, fields, overrides) {
13
- const runtimeConfig = await getRuntimeConfig();
13
+ const runtimeConfig = shouldProbeRuntimeConfig(fields, overrides) ? await getRuntimeConfig() : void 0;
14
14
  const evlogNs = runtimeConfig?.evlog?.[namespace];
15
15
  const rootNs = runtimeConfig?.[namespace];
16
16
  const config = {};
17
17
  for (const { key, env } of fields) config[key] = overrides?.[key] ?? evlogNs?.[key] ?? rootNs?.[key] ?? resolveEnv(env);
18
18
  return config;
19
19
  }
20
+ function shouldProbeRuntimeConfig(fields, overrides) {
21
+ return fields.some(({ key, env }) => {
22
+ if (overrides?.[key] !== void 0) return false;
23
+ if (!env) return false;
24
+ return resolveEnv(env) === void 0;
25
+ });
26
+ }
20
27
  function resolveEnv(envKeys) {
21
28
  if (!envKeys) return void 0;
22
29
  for (const key of envKeys) {
@@ -68,4 +75,4 @@ async function httpPost({ url, headers, body, timeout, label, retries = 2 }) {
68
75
  //#endregion
69
76
  export { resolveAdapterConfig as n, httpPost as t };
70
77
 
71
- //# sourceMappingURL=_http-CHSsrWDJ.mjs.map
78
+ //# sourceMappingURL=_http-BY1e9pwC.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_http-BY1e9pwC.mjs","names":[],"sources":["../src/adapters/_config.ts","../src/adapters/_http.ts"],"sourcesContent":["import { getNitroRuntimeConfigRecord } from '../shared/nitroConfigBridge'\n\n/**\n * Adapter runtime-config reads go through `getNitroRuntimeConfigRecord` in\n * `shared/nitroConfigBridge.ts` (documented there — Workers-safe dynamic imports).\n *\n * Drain handlers remain non-blocking when the host provides `waitUntil`.\n */\n\nexport function getRuntimeConfig(): Promise<Record<string, any> | undefined> {\n return getNitroRuntimeConfigRecord()\n}\n\nexport interface ConfigField<T> {\n key: keyof T & string\n env?: string[]\n}\n\nexport async function resolveAdapterConfig<T>(\n namespace: string,\n fields: ConfigField<T>[],\n overrides?: Partial<T>,\n): Promise<Partial<T>> {\n const runtimeConfig = shouldProbeRuntimeConfig(fields, overrides)\n ? await getRuntimeConfig()\n : undefined\n const evlogNs = runtimeConfig?.evlog?.[namespace]\n const rootNs = runtimeConfig?.[namespace]\n\n const config: Record<string, unknown> = {}\n\n for (const { key, env } of fields) {\n config[key] =\n overrides?.[key]\n ?? evlogNs?.[key]\n ?? rootNs?.[key]\n ?? resolveEnv(env)\n }\n\n return config as Partial<T>\n}\n\nfunction shouldProbeRuntimeConfig<T>(\n fields: ConfigField<T>[],\n overrides?: Partial<T>,\n): boolean {\n // Optional tuning fields (e.g. timeout/retries) should not trigger Nitro\n // virtual-module imports when env/overrides already resolve the env-backed\n // adapter fields in non-Nitro runtimes.\n return fields.some(({ key, env }) => {\n if (overrides?.[key] !== undefined) return false\n if (!env) return false\n return resolveEnv(env) === undefined\n })\n}\n\nfunction resolveEnv(envKeys?: string[]): string | undefined {\n if (!envKeys) return undefined\n for (const key of envKeys) {\n const val = process.env[key]\n if (val) return val\n }\n return undefined\n}\n","export interface HttpPostOptions {\n url: string\n headers: Record<string, string>\n body: string\n timeout: number\n label: string\n retries?: number\n}\n\nfunction isRetryable(error: unknown): boolean {\n if (error instanceof DOMException && error.name === 'AbortError') return true\n if (error instanceof TypeError) return true\n if (error instanceof Error) {\n const match = error.message.match(/API error: (\\d+)/)\n if (match) return Number.parseInt(match[1]) >= 500\n }\n return false\n}\n\nexport async function httpPost({ url, headers, body, timeout, label, retries = 2 }: HttpPostOptions): Promise<void> {\n const normalizedRetries = Number.isFinite(retries) && retries >= 0 ? Math.floor(retries) : 2\n\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt <= normalizedRetries; attempt++) {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body,\n signal: controller.signal,\n })\n\n if (!response.ok) {\n const text = await response.text().catch(() => 'Unknown error')\n const safeText = text.length > 200 ? `${text.slice(0, 200)}...[truncated]` : text\n throw new Error(`${label} API error: ${response.status} ${response.statusText} - ${safeText}`)\n }\n\n clearTimeout(timeoutId)\n return\n } catch (error) {\n clearTimeout(timeoutId)\n\n if (error instanceof DOMException && error.name === 'AbortError') {\n lastError = new Error(`${label} request timed out after ${timeout}ms`)\n } else {\n lastError = error as Error\n }\n\n if (!isRetryable(error) || attempt === normalizedRetries) {\n throw lastError\n }\n\n await new Promise<void>(r => setTimeout(r, 200 * 2 ** attempt))\n }\n }\n\n throw lastError!\n}\n"],"mappings":";;;;;;;;AASA,SAAgB,mBAA6D;AAC3E,QAAO,6BAA6B;;AAQtC,eAAsB,qBACpB,WACA,QACA,WACqB;CACrB,MAAM,gBAAgB,yBAAyB,QAAQ,UAAU,GAC7D,MAAM,kBAAkB,GACxB,KAAA;CACJ,MAAM,UAAU,eAAe,QAAQ;CACvC,MAAM,SAAS,gBAAgB;CAE/B,MAAM,SAAkC,EAAE;AAE1C,MAAK,MAAM,EAAE,KAAK,SAAS,OACzB,QAAO,OACL,YAAY,QACT,UAAU,QACV,SAAS,QACT,WAAW,IAAI;AAGtB,QAAO;;AAGT,SAAS,yBACP,QACA,WACS;AAIT,QAAO,OAAO,MAAM,EAAE,KAAK,UAAU;AACnC,MAAI,YAAY,SAAS,KAAA,EAAW,QAAO;AAC3C,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,WAAW,IAAI,KAAK,KAAA;GAC3B;;AAGJ,SAAS,WAAW,SAAwC;AAC1D,KAAI,CAAC,QAAS,QAAO,KAAA;AACrB,MAAK,MAAM,OAAO,SAAS;EACzB,MAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,IAAK,QAAO;;;;;ACnDpB,SAAS,YAAY,OAAyB;AAC5C,KAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAAc,QAAO;AACzE,KAAI,iBAAiB,UAAW,QAAO;AACvC,KAAI,iBAAiB,OAAO;EAC1B,MAAM,QAAQ,MAAM,QAAQ,MAAM,mBAAmB;AACrD,MAAI,MAAO,QAAO,OAAO,SAAS,MAAM,GAAG,IAAI;;AAEjD,QAAO;;AAGT,eAAsB,SAAS,EAAE,KAAK,SAAS,MAAM,SAAS,OAAO,UAAU,KAAqC;CAClH,MAAM,oBAAoB,OAAO,SAAS,QAAQ,IAAI,WAAW,IAAI,KAAK,MAAM,QAAQ,GAAG;CAE3F,IAAI;AAEJ,MAAK,IAAI,UAAU,GAAG,WAAW,mBAAmB,WAAW;EAC7D,MAAM,aAAa,IAAI,iBAAiB;EACxC,MAAM,YAAY,iBAAiB,WAAW,OAAO,EAAE,QAAQ;AAE/D,MAAI;GACF,MAAM,WAAW,MAAM,MAAM,KAAK;IAChC,QAAQ;IACR;IACA;IACA,QAAQ,WAAW;IACpB,CAAC;AAEF,OAAI,CAAC,SAAS,IAAI;IAChB,MAAM,OAAO,MAAM,SAAS,MAAM,CAAC,YAAY,gBAAgB;IAC/D,MAAM,WAAW,KAAK,SAAS,MAAM,GAAG,KAAK,MAAM,GAAG,IAAI,CAAC,kBAAkB;AAC7E,UAAM,IAAI,MAAM,GAAG,MAAM,cAAc,SAAS,OAAO,GAAG,SAAS,WAAW,KAAK,WAAW;;AAGhG,gBAAa,UAAU;AACvB;WACO,OAAO;AACd,gBAAa,UAAU;AAEvB,OAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,6BAAY,IAAI,MAAM,GAAG,MAAM,2BAA2B,QAAQ,IAAI;OAEtE,aAAY;AAGd,OAAI,CAAC,YAAY,MAAM,IAAI,YAAY,kBACrC,OAAM;AAGR,SAAM,IAAI,SAAc,MAAK,WAAW,GAAG,MAAM,KAAK,QAAQ,CAAC;;;AAInE,OAAM"}
@@ -1,4 +1,4 @@
1
- import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
1
+ import { F as DrainContext, it as WideEvent } from "../audit-CTIviX3P.mjs";
2
2
  //#region src/adapters/axiom.d.ts
3
3
  interface BaseAxiomConfig {
4
4
  /** Axiom dataset name */
@@ -1,4 +1,4 @@
1
- import { n as resolveAdapterConfig, t as httpPost } from "../_http-CHSsrWDJ.mjs";
1
+ import { n as resolveAdapterConfig, t as httpPost } from "../_http-BY1e9pwC.mjs";
2
2
  import { t as defineDrain } from "../_drain-CmCtsuF6.mjs";
3
3
  //#region src/adapters/axiom.ts
4
4
  const AXIOM_FIELDS = [
@@ -1 +1 @@
1
- {"version":3,"file":"axiom.mjs","names":[],"sources":["../../src/adapters/axiom.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\n\ninterface BaseAxiomConfig {\n /** Axiom dataset name */\n dataset: string\n /** Axiom API token */\n token: string\n /** Organization ID (required for Personal Access Tokens) */\n orgId?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\ninterface EdgeAxiomConfig {\n /**\n * Edge URL for Axiom ingest/query endpoints.\n * If no path is provided, uses /v1/ingest/{dataset}.\n * If a custom path is provided, it is used as-is (trailing slash trimmed).\n */\n edgeUrl: string\n /** Mutually exclusive with edgeUrl. */\n baseUrl?: never\n}\n\ninterface EndpointAxiomConfig {\n /** Base URL for Axiom API. Uses /v1/datasets/{dataset}/ingest. */\n baseUrl?: string\n /** Mutually exclusive with baseUrl. */\n edgeUrl?: never\n}\n\nexport type AxiomConfig = BaseAxiomConfig & (EdgeAxiomConfig | EndpointAxiomConfig)\n\ntype ResolvedAxiomConfig = BaseAxiomConfig & {\n edgeUrl?: string\n baseUrl?: string\n}\n\nconst AXIOM_FIELDS: ConfigField<ResolvedAxiomConfig>[] = [\n { key: 'dataset', env: ['NUXT_AXIOM_DATASET', 'AXIOM_DATASET'] },\n { key: 'token', env: ['NUXT_AXIOM_TOKEN', 'AXIOM_TOKEN'] },\n { key: 'orgId', env: ['NUXT_AXIOM_ORG_ID', 'AXIOM_ORG_ID'] },\n { key: 'edgeUrl', env: ['NUXT_AXIOM_EDGE_URL', 'AXIOM_EDGE_URL'] },\n { key: 'baseUrl', env: ['NUXT_AXIOM_URL', 'AXIOM_URL'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\n/**\n * Create a drain function for sending logs to Axiom.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createAxiomDrain()\n * 2. runtimeConfig.evlog.axiom\n * 3. runtimeConfig.axiom\n * 4. Environment variables: NUXT_AXIOM_*, AXIOM_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_AXIOM_TOKEN and NUXT_AXIOM_DATASET env vars\n * nitroApp.hooks.hook('evlog:drain', createAxiomDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createAxiomDrain({\n * dataset: 'my-dataset',\n * }))\n * ```\n */\nexport function createAxiomDrain(overrides?: Partial<AxiomConfig>) {\n return defineDrain<AxiomConfig>({\n name: 'axiom',\n resolve: async () => {\n const config = await resolveAdapterConfig<ResolvedAxiomConfig>(\n 'axiom',\n AXIOM_FIELDS,\n overrides as Partial<ResolvedAxiomConfig>,\n )\n if (!config.dataset || !config.token) {\n console.error('[evlog/axiom] Missing dataset or token. Set NUXT_AXIOM_TOKEN/NUXT_AXIOM_DATASET env vars or pass to createAxiomDrain()')\n return null\n }\n\n if (config.edgeUrl && config.baseUrl) {\n console.warn('[evlog/axiom] Both edgeUrl and baseUrl are set. edgeUrl takes precedence for ingest.')\n delete config.baseUrl\n }\n\n return config as AxiomConfig\n },\n send: sendBatchToAxiom,\n })\n}\n\n/**\n * Send a single event to Axiom.\n *\n * @example\n * ```ts\n * await sendToAxiom(event, {\n * dataset: 'my-logs',\n * token: process.env.AXIOM_TOKEN!,\n * })\n * ```\n */\nexport async function sendToAxiom(event: WideEvent, config: AxiomConfig): Promise<void> {\n await sendBatchToAxiom([event], config)\n}\n\n/**\n * Send a batch of events to Axiom.\n *\n * @example\n * ```ts\n * await sendBatchToAxiom(events, {\n * dataset: 'my-logs',\n * token: process.env.AXIOM_TOKEN!,\n * })\n * ```\n */\nexport async function sendBatchToAxiom(events: WideEvent[], config: AxiomConfig): Promise<void> {\n const url = resolveIngestUrl(config)\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.token}`,\n }\n\n if (config.orgId) {\n headers['X-Axiom-Org-Id'] = config.orgId\n }\n\n await httpPost({\n url,\n headers,\n body: JSON.stringify(events),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Axiom',\n })\n}\n\nfunction resolveIngestUrl(config: AxiomConfig): string {\n const encodedDataset = encodeURIComponent(config.dataset)\n\n if (!config.edgeUrl) {\n const baseUrl = config.baseUrl ?? 'https://api.axiom.co'\n return `${baseUrl}/v1/datasets/${encodedDataset}/ingest`\n }\n\n try {\n const parsed = new URL(config.edgeUrl)\n\n if (parsed.pathname === '' || parsed.pathname === '/') {\n parsed.pathname = `/v1/ingest/${encodedDataset}`\n return parsed.toString()\n }\n\n parsed.pathname = parsed.pathname.replace(/\\/+$/, '')\n return parsed.toString()\n } catch {\n console.warn(`[evlog/axiom] edgeUrl \"${config.edgeUrl}\" is not a valid URL, falling back to string concatenation.`)\n const trimmed = config.edgeUrl.replace(/\\/+$/, '')\n return `${trimmed}/v1/ingest/${encodedDataset}`\n }\n}\n"],"mappings":";;;AA4CA,MAAM,eAAmD;CACvD;EAAE,KAAK;EAAW,KAAK,CAAC,sBAAsB,gBAAgB;EAAE;CAChE;EAAE,KAAK;EAAS,KAAK,CAAC,oBAAoB,cAAc;EAAE;CAC1D;EAAE,KAAK;EAAS,KAAK,CAAC,qBAAqB,eAAe;EAAE;CAC5D;EAAE,KAAK;EAAW,KAAK,CAAC,uBAAuB,iBAAiB;EAAE;CAClE;EAAE,KAAK;EAAW,KAAK,CAAC,kBAAkB,YAAY;EAAE;CACxD,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,WAAW;CACnB;;;;;;;;;;;;;;;;;;;;;AAsBD,SAAgB,iBAAiB,WAAkC;AACjE,QAAO,YAAyB;EAC9B,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBACnB,SACA,cACA,UACD;AACD,OAAI,CAAC,OAAO,WAAW,CAAC,OAAO,OAAO;AACpC,YAAQ,MAAM,yHAAyH;AACvI,WAAO;;AAGT,OAAI,OAAO,WAAW,OAAO,SAAS;AACpC,YAAQ,KAAK,uFAAuF;AACpG,WAAO,OAAO;;AAGhB,UAAO;;EAET,MAAM;EACP,CAAC;;;;;;;;;;;;;AAcJ,eAAsB,YAAY,OAAkB,QAAoC;AACtF,OAAM,iBAAiB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;;AAczC,eAAsB,iBAAiB,QAAqB,QAAoC;CAC9F,MAAM,MAAM,iBAAiB,OAAO;CAEpC,MAAM,UAAkC;EACtC,gBAAgB;EAChB,iBAAiB,UAAU,OAAO;EACnC;AAED,KAAI,OAAO,MACT,SAAQ,oBAAoB,OAAO;AAGrC,OAAM,SAAS;EACb;EACA;EACA,MAAM,KAAK,UAAU,OAAO;EAC5B,SAAS,OAAO,WAAW;EAC3B,SAAS,OAAO;EAChB,OAAO;EACR,CAAC;;AAGJ,SAAS,iBAAiB,QAA6B;CACrD,MAAM,iBAAiB,mBAAmB,OAAO,QAAQ;AAEzD,KAAI,CAAC,OAAO,QAEV,QAAO,GADS,OAAO,WAAW,uBAChB,eAAe,eAAe;AAGlD,KAAI;EACF,MAAM,SAAS,IAAI,IAAI,OAAO,QAAQ;AAEtC,MAAI,OAAO,aAAa,MAAM,OAAO,aAAa,KAAK;AACrD,UAAO,WAAW,cAAc;AAChC,UAAO,OAAO,UAAU;;AAG1B,SAAO,WAAW,OAAO,SAAS,QAAQ,QAAQ,GAAG;AACrD,SAAO,OAAO,UAAU;SAClB;AACN,UAAQ,KAAK,0BAA0B,OAAO,QAAQ,6DAA6D;AAEnH,SAAO,GADS,OAAO,QAAQ,QAAQ,QAAQ,GAAG,CAChC,aAAa"}
1
+ {"version":3,"file":"axiom.mjs","names":[],"sources":["../../src/adapters/axiom.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\n\ninterface BaseAxiomConfig {\n /** Axiom dataset name */\n dataset: string\n /** Axiom API token */\n token: string\n /** Organization ID (required for Personal Access Tokens) */\n orgId?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\ninterface EdgeAxiomConfig {\n /**\n * Edge URL for Axiom ingest/query endpoints.\n * If no path is provided, uses /v1/ingest/{dataset}.\n * If a custom path is provided, it is used as-is (trailing slash trimmed).\n */\n edgeUrl: string\n /** Mutually exclusive with edgeUrl. */\n baseUrl?: never\n}\n\ninterface EndpointAxiomConfig {\n /** Base URL for Axiom API. Uses /v1/datasets/{dataset}/ingest. */\n baseUrl?: string\n /** Mutually exclusive with baseUrl. */\n edgeUrl?: never\n}\n\nexport type AxiomConfig = BaseAxiomConfig & (EdgeAxiomConfig | EndpointAxiomConfig)\n\ntype ResolvedAxiomConfig = BaseAxiomConfig & {\n edgeUrl?: string\n baseUrl?: string\n}\n\nconst AXIOM_FIELDS: ConfigField<ResolvedAxiomConfig>[] = [\n { key: 'dataset', env: ['NUXT_AXIOM_DATASET', 'AXIOM_DATASET'] },\n { key: 'token', env: ['NUXT_AXIOM_TOKEN', 'AXIOM_TOKEN'] },\n { key: 'orgId', env: ['NUXT_AXIOM_ORG_ID', 'AXIOM_ORG_ID'] },\n { key: 'edgeUrl', env: ['NUXT_AXIOM_EDGE_URL', 'AXIOM_EDGE_URL'] },\n { key: 'baseUrl', env: ['NUXT_AXIOM_URL', 'AXIOM_URL'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\n/**\n * Create a drain function for sending logs to Axiom.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createAxiomDrain()\n * 2. runtimeConfig.evlog.axiom\n * 3. runtimeConfig.axiom\n * 4. Environment variables: NUXT_AXIOM_*, AXIOM_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_AXIOM_TOKEN and NUXT_AXIOM_DATASET env vars\n * nitroApp.hooks.hook('evlog:drain', createAxiomDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createAxiomDrain({\n * dataset: 'my-dataset',\n * }))\n * ```\n */\nexport function createAxiomDrain(overrides?: Partial<AxiomConfig>) {\n return defineDrain<AxiomConfig>({\n name: 'axiom',\n resolve: async () => {\n const config = await resolveAdapterConfig<ResolvedAxiomConfig>(\n 'axiom',\n AXIOM_FIELDS,\n overrides as Partial<ResolvedAxiomConfig>,\n )\n if (!config.dataset || !config.token) {\n console.error('[evlog/axiom] Missing dataset or token. Set NUXT_AXIOM_TOKEN/NUXT_AXIOM_DATASET env vars or pass to createAxiomDrain()')\n return null\n }\n\n if (config.edgeUrl && config.baseUrl) {\n console.warn('[evlog/axiom] Both edgeUrl and baseUrl are set. edgeUrl takes precedence for ingest.')\n delete config.baseUrl\n }\n\n return config as AxiomConfig\n },\n send: sendBatchToAxiom,\n })\n}\n\n/**\n * Send a single event to Axiom.\n *\n * @example\n * ```ts\n * await sendToAxiom(event, {\n * dataset: 'my-logs',\n * token: process.env.AXIOM_TOKEN!,\n * })\n * ```\n */\nexport async function sendToAxiom(event: WideEvent, config: AxiomConfig): Promise<void> {\n await sendBatchToAxiom([event], config)\n}\n\n/**\n * Send a batch of events to Axiom.\n *\n * @example\n * ```ts\n * await sendBatchToAxiom(events, {\n * dataset: 'my-logs',\n * token: process.env.AXIOM_TOKEN!,\n * })\n * ```\n */\nexport async function sendBatchToAxiom(events: WideEvent[], config: AxiomConfig): Promise<void> {\n const url = resolveIngestUrl(config)\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.token}`,\n }\n\n if (config.orgId) {\n headers['X-Axiom-Org-Id'] = config.orgId\n }\n\n await httpPost({\n url,\n headers,\n body: JSON.stringify(events),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Axiom',\n })\n}\n\nfunction resolveIngestUrl(config: AxiomConfig): string {\n const encodedDataset = encodeURIComponent(config.dataset)\n\n if (!config.edgeUrl) {\n const baseUrl = config.baseUrl ?? 'https://api.axiom.co'\n return `${baseUrl}/v1/datasets/${encodedDataset}/ingest`\n }\n\n try {\n const parsed = new URL(config.edgeUrl)\n\n if (parsed.pathname === '' || parsed.pathname === '/') {\n parsed.pathname = `/v1/ingest/${encodedDataset}`\n return parsed.toString()\n }\n\n parsed.pathname = parsed.pathname.replace(/\\/+$/, '')\n return parsed.toString()\n } catch {\n console.warn(`[evlog/axiom] edgeUrl \"${config.edgeUrl}\" is not a valid URL, falling back to string concatenation.`)\n const trimmed = config.edgeUrl.replace(/\\/+$/, '')\n return `${trimmed}/v1/ingest/${encodedDataset}`\n }\n}\n"],"mappings":";;;AA4CA,MAAM,eAAmD;CACvD;EAAE,KAAK;EAAW,KAAK,CAAC,sBAAsB,gBAAgB;EAAE;CAChE;EAAE,KAAK;EAAS,KAAK,CAAC,oBAAoB,cAAc;EAAE;CAC1D;EAAE,KAAK;EAAS,KAAK,CAAC,qBAAqB,eAAe;EAAE;CAC5D;EAAE,KAAK;EAAW,KAAK,CAAC,uBAAuB,iBAAiB;EAAE;CAClE;EAAE,KAAK;EAAW,KAAK,CAAC,kBAAkB,YAAY;EAAE;CACxD,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,WAAW;CACnB;;;;;;;;;;;;;;;;;;;;;AAsBD,SAAgB,iBAAiB,WAAkC;AACjE,QAAO,YAAyB;EAC9B,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBACnB,SACA,cACA,UACD;AACD,OAAI,CAAC,OAAO,WAAW,CAAC,OAAO,OAAO;AACpC,YAAQ,MAAM,yHAAyH;AACvI,WAAO;;AAGT,OAAI,OAAO,WAAW,OAAO,SAAS;AACpC,YAAQ,KAAK,uFAAuF;AACpG,WAAO,OAAO;;AAGhB,UAAO;;EAET,MAAM;EACP,CAAC;;;;;;;;;;;;;AAcJ,eAAsB,YAAY,OAAkB,QAAoC;AACtF,OAAM,iBAAiB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;;AAczC,eAAsB,iBAAiB,QAAqB,QAAoC;CAC9F,MAAM,MAAM,iBAAiB,OAAO;CAEpC,MAAM,UAAkC;EACtC,gBAAgB;EAChB,iBAAiB,UAAU,OAAO;EACnC;AAED,KAAI,OAAO,MACT,SAAQ,oBAAoB,OAAO;AAGrC,OAAM,SAAS;EACb;EACA;EACA,MAAM,KAAK,UAAU,OAAO;EAC5B,SAAS,OAAO,WAAW;EAC3B,SAAS,OAAO;EAChB,OAAO;EACR,CAAC;;AAGJ,SAAS,iBAAiB,QAA6B;CACrD,MAAM,iBAAiB,mBAAmB,OAAO,QAAQ;AAEzD,KAAI,CAAC,OAAO,QAEV,QAAO,GADS,OAAO,WAAW,uBAChB,eAAe,eAAe;AAGlD,KAAI;EACF,MAAM,SAAS,IAAI,IAAI,OAAO,QAAQ;AAEtC,MAAI,OAAO,aAAa,MAAM,OAAO,aAAa,KAAK;AACrD,UAAO,WAAW,cAAc;AAChC,UAAO,OAAO,UAAU;;AAG1B,SAAO,WAAW,OAAO,SAAS,QAAQ,QAAQ,GAAG;AACrD,SAAO,OAAO,UAAU;SAClB;AACN,UAAQ,KAAK,0BAA0B,OAAO,QAAQ,6DAA6D;AAEnH,SAAO,GADS,OAAO,QAAQ,QAAQ,QAAQ,GAC9B,CAAC,aAAa"}
@@ -1,4 +1,4 @@
1
- import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
1
+ import { F as DrainContext, it as WideEvent } from "../audit-CTIviX3P.mjs";
2
2
  //#region src/adapters/better-stack.d.ts
3
3
  interface BetterStackConfig {
4
4
  /** Better Stack source token */
@@ -1,4 +1,4 @@
1
- import { n as resolveAdapterConfig, t as httpPost } from "../_http-CHSsrWDJ.mjs";
1
+ import { n as resolveAdapterConfig, t as httpPost } from "../_http-BY1e9pwC.mjs";
2
2
  import { t as defineDrain } from "../_drain-CmCtsuF6.mjs";
3
3
  //#region src/adapters/better-stack.ts
4
4
  const BETTER_STACK_FIELDS = [
@@ -1 +1 @@
1
- {"version":3,"file":"better-stack.mjs","names":[],"sources":["../../src/adapters/better-stack.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\n\nexport interface BetterStackConfig {\n /** Better Stack source token */\n sourceToken: string\n /** Logtail ingestion endpoint. Default: https://in.logs.betterstack.com */\n endpoint?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\nconst BETTER_STACK_FIELDS: ConfigField<BetterStackConfig>[] = [\n { key: 'sourceToken', env: ['NUXT_BETTER_STACK_SOURCE_TOKEN', 'BETTER_STACK_SOURCE_TOKEN'] },\n { key: 'endpoint', env: ['NUXT_BETTER_STACK_ENDPOINT', 'BETTER_STACK_ENDPOINT'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\n/**\n * Transform an evlog wide event into a Better Stack event.\n * Maps `timestamp` to `dt` (Better Stack's expected field).\n */\nexport function toBetterStackEvent(event: WideEvent): Record<string, unknown> {\n const { timestamp, ...rest } = event\n return { ...rest, dt: timestamp }\n}\n\n/**\n * Create a drain function for sending logs to Better Stack.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createBetterStackDrain()\n * 2. runtimeConfig.evlog.betterStack\n * 3. runtimeConfig.betterStack\n * 4. Environment variables: NUXT_BETTER_STACK_*, BETTER_STACK_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_BETTER_STACK_SOURCE_TOKEN env var\n * nitroApp.hooks.hook('evlog:drain', createBetterStackDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createBetterStackDrain({\n * sourceToken: 'my-token',\n * }))\n * ```\n */\nexport function createBetterStackDrain(overrides?: Partial<BetterStackConfig>) {\n return defineDrain<BetterStackConfig>({\n name: 'better-stack',\n resolve: async () => {\n const config = await resolveAdapterConfig<BetterStackConfig>('betterStack', BETTER_STACK_FIELDS, overrides)\n if (!config.sourceToken) {\n console.error('[evlog/better-stack] Missing source token. Set NUXT_BETTER_STACK_SOURCE_TOKEN env var or pass to createBetterStackDrain()')\n return null\n }\n return config as BetterStackConfig\n },\n send: sendBatchToBetterStack,\n })\n}\n\n/**\n * Send a single event to Better Stack.\n *\n * @example\n * ```ts\n * await sendToBetterStack(event, {\n * sourceToken: process.env.BETTER_STACK_SOURCE_TOKEN!,\n * })\n * ```\n */\nexport async function sendToBetterStack(event: WideEvent, config: BetterStackConfig): Promise<void> {\n await sendBatchToBetterStack([event], config)\n}\n\n/**\n * Send a batch of events to Better Stack.\n *\n * @example\n * ```ts\n * await sendBatchToBetterStack(events, {\n * sourceToken: process.env.BETTER_STACK_SOURCE_TOKEN!,\n * })\n * ```\n */\nexport async function sendBatchToBetterStack(events: WideEvent[], config: BetterStackConfig): Promise<void> {\n const endpoint = (config.endpoint ?? 'https://in.logs.betterstack.com').replace(/\\/+$/, '')\n\n await httpPost({\n url: endpoint,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.sourceToken}`,\n },\n body: JSON.stringify(events.map(toBetterStackEvent)),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Better Stack',\n })\n}\n"],"mappings":";;;AAiBA,MAAM,sBAAwD;CAC5D;EAAE,KAAK;EAAe,KAAK,CAAC,kCAAkC,4BAA4B;EAAE;CAC5F;EAAE,KAAK;EAAY,KAAK,CAAC,8BAA8B,wBAAwB;EAAE;CACjF,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,WAAW;CACnB;;;;;AAMD,SAAgB,mBAAmB,OAA2C;CAC5E,MAAM,EAAE,WAAW,GAAG,SAAS;AAC/B,QAAO;EAAE,GAAG;EAAM,IAAI;EAAW;;;;;;;;;;;;;;;;;;;;;;AAuBnC,SAAgB,uBAAuB,WAAwC;AAC7E,QAAO,YAA+B;EACpC,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBAAwC,eAAe,qBAAqB,UAAU;AAC3G,OAAI,CAAC,OAAO,aAAa;AACvB,YAAQ,MAAM,4HAA4H;AAC1I,WAAO;;AAET,UAAO;;EAET,MAAM;EACP,CAAC;;;;;;;;;;;;AAaJ,eAAsB,kBAAkB,OAAkB,QAA0C;AAClG,OAAM,uBAAuB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;AAa/C,eAAsB,uBAAuB,QAAqB,QAA0C;AAG1G,OAAM,SAAS;EACb,MAHgB,OAAO,YAAY,mCAAmC,QAAQ,QAAQ,GAAG;EAIzF,SAAS;GACP,gBAAgB;GAChB,iBAAiB,UAAU,OAAO;GACnC;EACD,MAAM,KAAK,UAAU,OAAO,IAAI,mBAAmB,CAAC;EACpD,SAAS,OAAO,WAAW;EAC3B,SAAS,OAAO;EAChB,OAAO;EACR,CAAC"}
1
+ {"version":3,"file":"better-stack.mjs","names":[],"sources":["../../src/adapters/better-stack.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\n\nexport interface BetterStackConfig {\n /** Better Stack source token */\n sourceToken: string\n /** Logtail ingestion endpoint. Default: https://in.logs.betterstack.com */\n endpoint?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\nconst BETTER_STACK_FIELDS: ConfigField<BetterStackConfig>[] = [\n { key: 'sourceToken', env: ['NUXT_BETTER_STACK_SOURCE_TOKEN', 'BETTER_STACK_SOURCE_TOKEN'] },\n { key: 'endpoint', env: ['NUXT_BETTER_STACK_ENDPOINT', 'BETTER_STACK_ENDPOINT'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\n/**\n * Transform an evlog wide event into a Better Stack event.\n * Maps `timestamp` to `dt` (Better Stack's expected field).\n */\nexport function toBetterStackEvent(event: WideEvent): Record<string, unknown> {\n const { timestamp, ...rest } = event\n return { ...rest, dt: timestamp }\n}\n\n/**\n * Create a drain function for sending logs to Better Stack.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createBetterStackDrain()\n * 2. runtimeConfig.evlog.betterStack\n * 3. runtimeConfig.betterStack\n * 4. Environment variables: NUXT_BETTER_STACK_*, BETTER_STACK_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_BETTER_STACK_SOURCE_TOKEN env var\n * nitroApp.hooks.hook('evlog:drain', createBetterStackDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createBetterStackDrain({\n * sourceToken: 'my-token',\n * }))\n * ```\n */\nexport function createBetterStackDrain(overrides?: Partial<BetterStackConfig>) {\n return defineDrain<BetterStackConfig>({\n name: 'better-stack',\n resolve: async () => {\n const config = await resolveAdapterConfig<BetterStackConfig>('betterStack', BETTER_STACK_FIELDS, overrides)\n if (!config.sourceToken) {\n console.error('[evlog/better-stack] Missing source token. Set NUXT_BETTER_STACK_SOURCE_TOKEN env var or pass to createBetterStackDrain()')\n return null\n }\n return config as BetterStackConfig\n },\n send: sendBatchToBetterStack,\n })\n}\n\n/**\n * Send a single event to Better Stack.\n *\n * @example\n * ```ts\n * await sendToBetterStack(event, {\n * sourceToken: process.env.BETTER_STACK_SOURCE_TOKEN!,\n * })\n * ```\n */\nexport async function sendToBetterStack(event: WideEvent, config: BetterStackConfig): Promise<void> {\n await sendBatchToBetterStack([event], config)\n}\n\n/**\n * Send a batch of events to Better Stack.\n *\n * @example\n * ```ts\n * await sendBatchToBetterStack(events, {\n * sourceToken: process.env.BETTER_STACK_SOURCE_TOKEN!,\n * })\n * ```\n */\nexport async function sendBatchToBetterStack(events: WideEvent[], config: BetterStackConfig): Promise<void> {\n const endpoint = (config.endpoint ?? 'https://in.logs.betterstack.com').replace(/\\/+$/, '')\n\n await httpPost({\n url: endpoint,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.sourceToken}`,\n },\n body: JSON.stringify(events.map(toBetterStackEvent)),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Better Stack',\n })\n}\n"],"mappings":";;;AAiBA,MAAM,sBAAwD;CAC5D;EAAE,KAAK;EAAe,KAAK,CAAC,kCAAkC,4BAA4B;EAAE;CAC5F;EAAE,KAAK;EAAY,KAAK,CAAC,8BAA8B,wBAAwB;EAAE;CACjF,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,WAAW;CACnB;;;;;AAMD,SAAgB,mBAAmB,OAA2C;CAC5E,MAAM,EAAE,WAAW,GAAG,SAAS;AAC/B,QAAO;EAAE,GAAG;EAAM,IAAI;EAAW;;;;;;;;;;;;;;;;;;;;;;AAuBnC,SAAgB,uBAAuB,WAAwC;AAC7E,QAAO,YAA+B;EACpC,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBAAwC,eAAe,qBAAqB,UAAU;AAC3G,OAAI,CAAC,OAAO,aAAa;AACvB,YAAQ,MAAM,4HAA4H;AAC1I,WAAO;;AAET,UAAO;;EAET,MAAM;EACP,CAAC;;;;;;;;;;;;AAaJ,eAAsB,kBAAkB,OAAkB,QAA0C;AAClG,OAAM,uBAAuB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;AAa/C,eAAsB,uBAAuB,QAAqB,QAA0C;AAG1G,OAAM,SAAS;EACb,MAHgB,OAAO,YAAY,mCAAmC,QAAQ,QAAQ,GAGzE;EACb,SAAS;GACP,gBAAgB;GAChB,iBAAiB,UAAU,OAAO;GACnC;EACD,MAAM,KAAK,UAAU,OAAO,IAAI,mBAAmB,CAAC;EACpD,SAAS,OAAO,WAAW;EAC3B,SAAS,OAAO;EAChB,OAAO;EACR,CAAC"}
@@ -1,4 +1,4 @@
1
- import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
1
+ import { F as DrainContext, it as WideEvent } from "../audit-CTIviX3P.mjs";
2
2
  //#region src/adapters/datadog.d.ts
3
3
  interface DatadogConfig {
4
4
  /** Datadog API key with Logs intake permission */
@@ -1,4 +1,4 @@
1
- import { n as resolveAdapterConfig, t as httpPost } from "../_http-CHSsrWDJ.mjs";
1
+ import { n as resolveAdapterConfig, t as httpPost } from "../_http-BY1e9pwC.mjs";
2
2
  import { t as defineDrain } from "../_drain-CmCtsuF6.mjs";
3
3
  //#region src/adapters/datadog.ts
4
4
  const DATADOG_FIELDS = [
@@ -1 +1 @@
1
- {"version":3,"file":"datadog.mjs","names":[],"sources":["../../src/adapters/datadog.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\n\nexport interface DatadogConfig {\n /** Datadog API key with Logs intake permission */\n apiKey: string\n /**\n * Datadog site hostname (e.g. `datadoghq.com`, `datadoghq.eu`, `us3.datadoghq.com`, `ddog-gov.com`).\n * Ignored when `intakeUrl` is set. Default: `datadoghq.com`\n */\n site?: string\n /**\n * Full Logs HTTP intake URL. When set, overrides the URL derived from `site`.\n * Default: `https://http-intake.logs.${site}/api/v2/logs`\n */\n intakeUrl?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\nconst DATADOG_FIELDS: ConfigField<DatadogConfig>[] = [\n { key: 'apiKey', env: ['NUXT_DATADOG_API_KEY', 'DATADOG_API_KEY', 'DD_API_KEY'] },\n { key: 'site', env: ['NUXT_DATADOG_SITE', 'DATADOG_SITE', 'DD_SITE'] },\n { key: 'intakeUrl', env: ['NUXT_DATADOG_LOGS_URL', 'DATADOG_LOGS_URL'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nconst DEFAULT_SITE = 'datadoghq.com'\n\n/**\n * Datadog treats **`status`** as log severity. evlog uses **`status`** for HTTP response codes on the wide event and\n * inside **`error`** (structured errors). Rename every **numeric** `status` at any depth to **`httpStatusCode`** so\n * nothing in the payload collides with reserved severity when Datadog processes attributes.\n *\n * Does not mutate the original {@link WideEvent} (builds new objects).\n */\nexport function sanitizeWideEventForDatadog(event: WideEvent): Record<string, unknown> {\n return deepRenameNumericHttpStatus(event as Record<string, unknown>) as Record<string, unknown>\n}\n\nfunction deepRenameNumericHttpStatus(value: unknown): unknown {\n if (value === null || typeof value !== 'object') return value\n if (Array.isArray(value)) return value.map(deepRenameNumericHttpStatus)\n const obj = value as Record<string, unknown>\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(obj)) {\n if (k === 'status' && typeof v === 'number') {\n out.httpStatusCode = v\n } else {\n out[k] = deepRenameNumericHttpStatus(v)\n }\n }\n return out\n}\n\n/**\n * Single-line summary for Datadog’s `message` column (Live Tail / Explorer list view).\n * Full context stays under {@link toDatadogLog}'s `evlog` object.\n */\nexport function formatDatadogMessageLine(event: WideEvent): string {\n const levelU = event.level.toUpperCase()\n const method = typeof event.method === 'string' ? event.method : ''\n const path = typeof event.path === 'string' ? event.path : ''\n const code = typeof event.status === 'number' ? event.status : undefined\n\n const head = [levelU, method, path].filter(p => p.length > 0).join(' ')\n let line = code !== undefined\n ? (head ? `${head} (${code})` : `${levelU} (${code})`)\n : (head || levelU)\n\n if (!method && !path && line === levelU && event.service) {\n line = `${levelU} ${event.service}`\n }\n return line\n}\n\n/**\n * Severity for Datadog’s reserved `status` field (drives Live Tail coloring and facets).\n *\n * Uses the wide event’s **`level`** first (`log.error()` / `log.warn()`). If the level is\n * still `info`, falls back to the HTTP **`status`** on the wide event (`status: 4xx` → `warn`,\n * `5xx` → `error`) so client/server error responses are visible even when no `log.error()`\n * ran. Purely business errors on **HTTP 200** only change Datadog if you call `log.error()`.\n */\nexport function resolveDatadogLogStatus(event: WideEvent): 'error' | 'warn' | 'info' | 'debug' {\n if (event.level === 'error') return 'error'\n if (event.level === 'warn') return 'warn'\n if (event.level === 'debug') return 'debug'\n const code = typeof event.status === 'number' ? event.status : undefined\n if (code !== undefined && code >= 500) return 'error'\n if (code !== undefined && code >= 400) return 'warn'\n return 'info'\n}\n\n/**\n * Map an evlog wide event to a [Datadog Logs API v2](https://docs.datadoghq.com/api/latest/logs/) log object.\n *\n * Shape:\n * - **`message`** — short line for the list view (`formatDatadogMessageLine`)\n * - **`evlog`** — full sanitized wide event (HTTP codes as `httpStatusCode`); use facets like `@evlog.path`\n * - **`status`**, **`service`**, **`ddsource`**, **`ddtags`**, **`timestamp`** — Datadog standard fields\n */\nexport function toDatadogLog(event: WideEvent): Record<string, unknown> {\n const ms = Date.parse(event.timestamp)\n const tags = [`env:${event.environment}`]\n const versionTag = event.version\n if (versionTag !== undefined && versionTag !== null && versionTag !== '') {\n tags.push(`version:${String(versionTag)}`)\n }\n\n return {\n message: formatDatadogMessageLine(event),\n evlog: sanitizeWideEventForDatadog(event),\n service: event.service,\n status: resolveDatadogLogStatus(event),\n ddsource: 'evlog',\n ddtags: tags.join(','),\n ...(Number.isFinite(ms) ? { timestamp: ms } : {}),\n }\n}\n\n/**\n * Resolve the Logs intake URL from configuration.\n */\nexport function resolveDatadogIntakeUrl(config: Pick<DatadogConfig, 'site' | 'intakeUrl'>): string {\n if (config.intakeUrl) {\n return config.intakeUrl.replace(/\\/+$/, '')\n }\n const site = (config.site ?? DEFAULT_SITE).replace(/^\\./, '').replace(/\\/+$/, '')\n return `https://http-intake.logs.${site}/api/v2/logs`\n}\n\n/**\n * Create a drain function for sending logs to Datadog via the HTTP Logs intake API.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to `createDatadogDrain()`\n * 2. `runtimeConfig.evlog.datadog`\n * 3. `runtimeConfig.datadog`\n * 4. Environment variables: `NUXT_DATADOG_*`, `DATADOG_*`, and common `DD_*` aliases\n *\n * @example\n * ```ts\n * // Zero config — set DD_API_KEY (or NUXT_DATADOG_API_KEY) and optionally DD_SITE\n * nitroApp.hooks.hook('evlog:drain', createDatadogDrain())\n *\n * nitroApp.hooks.hook('evlog:drain', createDatadogDrain({\n * site: 'datadoghq.eu',\n * }))\n * ```\n */\nexport function createDatadogDrain(overrides?: Partial<DatadogConfig>) {\n return defineDrain<DatadogConfig>({\n name: 'datadog',\n resolve: async () => {\n const config = await resolveAdapterConfig<DatadogConfig>('datadog', DATADOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/datadog] Missing API key. Set NUXT_DATADOG_API_KEY, DATADOG_API_KEY, or DD_API_KEY, or pass apiKey to createDatadogDrain()')\n return null\n }\n return config as DatadogConfig\n },\n send: sendBatchToDatadog,\n })\n}\n\n/**\n * Send a single wide event to Datadog.\n */\nexport async function sendToDatadog(event: WideEvent, config: DatadogConfig): Promise<void> {\n await sendBatchToDatadog([event], config)\n}\n\n/**\n * Send a batch of wide events to Datadog in one request.\n */\nexport async function sendBatchToDatadog(events: WideEvent[], config: DatadogConfig): Promise<void> {\n if (events.length === 0) return\n\n const url = resolveDatadogIntakeUrl(config)\n\n await httpPost({\n url,\n headers: {\n 'Content-Type': 'application/json',\n 'DD-API-KEY': config.apiKey,\n },\n body: JSON.stringify(events.map(toDatadogLog)),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Datadog',\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"datadog.mjs","names":[],"sources":["../../src/adapters/datadog.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\n\nexport interface DatadogConfig {\n /** Datadog API key with Logs intake permission */\n apiKey: string\n /**\n * Datadog site hostname (e.g. `datadoghq.com`, `datadoghq.eu`, `us3.datadoghq.com`, `ddog-gov.com`).\n * Ignored when `intakeUrl` is set. Default: `datadoghq.com`\n */\n site?: string\n /**\n * Full Logs HTTP intake URL. When set, overrides the URL derived from `site`.\n * Default: `https://http-intake.logs.${site}/api/v2/logs`\n */\n intakeUrl?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\nconst DATADOG_FIELDS: ConfigField<DatadogConfig>[] = [\n { key: 'apiKey', env: ['NUXT_DATADOG_API_KEY', 'DATADOG_API_KEY', 'DD_API_KEY'] },\n { key: 'site', env: ['NUXT_DATADOG_SITE', 'DATADOG_SITE', 'DD_SITE'] },\n { key: 'intakeUrl', env: ['NUXT_DATADOG_LOGS_URL', 'DATADOG_LOGS_URL'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nconst DEFAULT_SITE = 'datadoghq.com'\n\n/**\n * Datadog treats **`status`** as log severity. evlog uses **`status`** for HTTP response codes on the wide event and\n * inside **`error`** (structured errors). Rename every **numeric** `status` at any depth to **`httpStatusCode`** so\n * nothing in the payload collides with reserved severity when Datadog processes attributes.\n *\n * Does not mutate the original {@link WideEvent} (builds new objects).\n */\nexport function sanitizeWideEventForDatadog(event: WideEvent): Record<string, unknown> {\n return deepRenameNumericHttpStatus(event as Record<string, unknown>) as Record<string, unknown>\n}\n\nfunction deepRenameNumericHttpStatus(value: unknown): unknown {\n if (value === null || typeof value !== 'object') return value\n if (Array.isArray(value)) return value.map(deepRenameNumericHttpStatus)\n const obj = value as Record<string, unknown>\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(obj)) {\n if (k === 'status' && typeof v === 'number') {\n out.httpStatusCode = v\n } else {\n out[k] = deepRenameNumericHttpStatus(v)\n }\n }\n return out\n}\n\n/**\n * Single-line summary for Datadog’s `message` column (Live Tail / Explorer list view).\n * Full context stays under {@link toDatadogLog}'s `evlog` object.\n */\nexport function formatDatadogMessageLine(event: WideEvent): string {\n const levelU = event.level.toUpperCase()\n const method = typeof event.method === 'string' ? event.method : ''\n const path = typeof event.path === 'string' ? event.path : ''\n const code = typeof event.status === 'number' ? event.status : undefined\n\n const head = [levelU, method, path].filter(p => p.length > 0).join(' ')\n let line = code !== undefined\n ? (head ? `${head} (${code})` : `${levelU} (${code})`)\n : (head || levelU)\n\n if (!method && !path && line === levelU && event.service) {\n line = `${levelU} ${event.service}`\n }\n return line\n}\n\n/**\n * Severity for Datadog’s reserved `status` field (drives Live Tail coloring and facets).\n *\n * Uses the wide event’s **`level`** first (`log.error()` / `log.warn()`). If the level is\n * still `info`, falls back to the HTTP **`status`** on the wide event (`status: 4xx` → `warn`,\n * `5xx` → `error`) so client/server error responses are visible even when no `log.error()`\n * ran. Purely business errors on **HTTP 200** only change Datadog if you call `log.error()`.\n */\nexport function resolveDatadogLogStatus(event: WideEvent): 'error' | 'warn' | 'info' | 'debug' {\n if (event.level === 'error') return 'error'\n if (event.level === 'warn') return 'warn'\n if (event.level === 'debug') return 'debug'\n const code = typeof event.status === 'number' ? event.status : undefined\n if (code !== undefined && code >= 500) return 'error'\n if (code !== undefined && code >= 400) return 'warn'\n return 'info'\n}\n\n/**\n * Map an evlog wide event to a [Datadog Logs API v2](https://docs.datadoghq.com/api/latest/logs/) log object.\n *\n * Shape:\n * - **`message`** — short line for the list view (`formatDatadogMessageLine`)\n * - **`evlog`** — full sanitized wide event (HTTP codes as `httpStatusCode`); use facets like `@evlog.path`\n * - **`status`**, **`service`**, **`ddsource`**, **`ddtags`**, **`timestamp`** — Datadog standard fields\n */\nexport function toDatadogLog(event: WideEvent): Record<string, unknown> {\n const ms = Date.parse(event.timestamp)\n const tags = [`env:${event.environment}`]\n const versionTag = event.version\n if (versionTag !== undefined && versionTag !== null && versionTag !== '') {\n tags.push(`version:${String(versionTag)}`)\n }\n\n return {\n message: formatDatadogMessageLine(event),\n evlog: sanitizeWideEventForDatadog(event),\n service: event.service,\n status: resolveDatadogLogStatus(event),\n ddsource: 'evlog',\n ddtags: tags.join(','),\n ...(Number.isFinite(ms) ? { timestamp: ms } : {}),\n }\n}\n\n/**\n * Resolve the Logs intake URL from configuration.\n */\nexport function resolveDatadogIntakeUrl(config: Pick<DatadogConfig, 'site' | 'intakeUrl'>): string {\n if (config.intakeUrl) {\n return config.intakeUrl.replace(/\\/+$/, '')\n }\n const site = (config.site ?? DEFAULT_SITE).replace(/^\\./, '').replace(/\\/+$/, '')\n return `https://http-intake.logs.${site}/api/v2/logs`\n}\n\n/**\n * Create a drain function for sending logs to Datadog via the HTTP Logs intake API.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to `createDatadogDrain()`\n * 2. `runtimeConfig.evlog.datadog`\n * 3. `runtimeConfig.datadog`\n * 4. Environment variables: `NUXT_DATADOG_*`, `DATADOG_*`, and common `DD_*` aliases\n *\n * @example\n * ```ts\n * // Zero config — set DD_API_KEY (or NUXT_DATADOG_API_KEY) and optionally DD_SITE\n * nitroApp.hooks.hook('evlog:drain', createDatadogDrain())\n *\n * nitroApp.hooks.hook('evlog:drain', createDatadogDrain({\n * site: 'datadoghq.eu',\n * }))\n * ```\n */\nexport function createDatadogDrain(overrides?: Partial<DatadogConfig>) {\n return defineDrain<DatadogConfig>({\n name: 'datadog',\n resolve: async () => {\n const config = await resolveAdapterConfig<DatadogConfig>('datadog', DATADOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/datadog] Missing API key. Set NUXT_DATADOG_API_KEY, DATADOG_API_KEY, or DD_API_KEY, or pass apiKey to createDatadogDrain()')\n return null\n }\n return config as DatadogConfig\n },\n send: sendBatchToDatadog,\n })\n}\n\n/**\n * Send a single wide event to Datadog.\n */\nexport async function sendToDatadog(event: WideEvent, config: DatadogConfig): Promise<void> {\n await sendBatchToDatadog([event], config)\n}\n\n/**\n * Send a batch of wide events to Datadog in one request.\n */\nexport async function sendBatchToDatadog(events: WideEvent[], config: DatadogConfig): Promise<void> {\n if (events.length === 0) return\n\n const url = resolveDatadogIntakeUrl(config)\n\n await httpPost({\n url,\n headers: {\n 'Content-Type': 'application/json',\n 'DD-API-KEY': config.apiKey,\n },\n body: JSON.stringify(events.map(toDatadogLog)),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Datadog',\n })\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
1
+ import { F as DrainContext, it as WideEvent } from "../audit-CTIviX3P.mjs";
2
2
  //#region src/adapters/fs.d.ts
3
3
  interface FsConfig {
4
4
  /** Directory for log files */
@@ -1 +1 @@
1
- {"version":3,"file":"fs.mjs","names":[],"sources":["../../src/adapters/fs.ts"],"sourcesContent":["import { appendFile, mkdir, readdir, stat, unlink, writeFile } from 'node:fs/promises'\nimport { join, sep } from 'node:path'\nimport type { WideEvent } from '../types'\nimport { defineDrain } from './_drain'\n\nexport interface FsConfig {\n /** Directory for log files */\n dir: string\n /** Max number of log files to keep (auto-deletes oldest when exceeded) */\n maxFiles?: number\n /** Max bytes per file before rotating to a new suffixed file */\n maxSizePerFile?: number\n /** Pretty-print JSON instead of compact NDJSON */\n pretty: boolean\n}\n\nconst gitignoreWritten = new Set<string>()\n\nasync function ensureGitignore(dir: string): Promise<void> {\n const normalized = dir.replace(/[\\\\/]/g, sep)\n const segments = normalized.split(sep)\n const evlogIndex = segments.findIndex(s => s === '.evlog')\n const targetDir = evlogIndex !== -1 ? segments.slice(0, evlogIndex + 1).join(sep) : dir\n\n if (gitignoreWritten.has(targetDir)) return\n\n const gitignorePath = join(targetDir, '.gitignore')\n try {\n await stat(gitignorePath)\n } catch {\n await writeFile(gitignorePath, '*\\n', 'utf-8')\n }\n gitignoreWritten.add(targetDir)\n}\n\nfunction getDateString(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nasync function resolveFilePath(dir: string, maxSizePerFile?: number): Promise<string> {\n const date = getDateString()\n const basePath = join(dir, `${date}.jsonl`)\n\n if (!maxSizePerFile) return basePath\n\n try {\n const stats = await stat(basePath)\n if (stats.size < maxSizePerFile) return basePath\n } catch {\n return basePath\n }\n\n for (let i = 1; i < 1000; i++) {\n const rotatedPath = join(dir, `${date}.${i}.jsonl`)\n try {\n const stats = await stat(rotatedPath)\n if (stats.size < maxSizePerFile) return rotatedPath\n } catch {\n return rotatedPath\n }\n }\n\n return join(dir, `${date}.999.jsonl`)\n}\n\nfunction parseLogFilename(filename: string): { date: string; index: number } {\n const match = filename.match(/^(\\d{4}-\\d{2}-\\d{2})(?:\\.(\\d+))?\\.jsonl$/)\n if (!match) return { date: '', index: 0 }\n return { date: match[1], index: match[2] ? Number.parseInt(match[2], 10) : 0 }\n}\n\nasync function cleanupOldFiles(dir: string, maxFiles: number): Promise<void> {\n const files = await readdir(dir)\n const jsonlFiles = files.filter(f => f.endsWith('.jsonl')).sort((a, b) => {\n const pa = parseLogFilename(a)\n const pb = parseLogFilename(b)\n return pa.date.localeCompare(pb.date) || pa.index - pb.index\n })\n\n if (jsonlFiles.length <= maxFiles) return\n\n const toDelete = jsonlFiles.slice(0, jsonlFiles.length - maxFiles)\n await Promise.allSettled(toDelete.map(f => unlink(join(dir, f))))\n}\n\nexport async function writeToFs(event: WideEvent, config: FsConfig): Promise<void> {\n await writeBatchToFs([event], config)\n}\n\nexport async function writeBatchToFs(events: WideEvent[], config: FsConfig): Promise<void> {\n if (events.length === 0) return\n\n await mkdir(config.dir, { recursive: true })\n await ensureGitignore(config.dir)\n\n const filePath = await resolveFilePath(config.dir, config.maxSizePerFile)\n const lines = `${events\n .map(e => config.pretty ? JSON.stringify(e, null, 2) : JSON.stringify(e))\n .join('\\n') }\\n`\n\n await appendFile(filePath, lines, 'utf-8')\n\n if (config.maxFiles) {\n await cleanupOldFiles(config.dir, config.maxFiles)\n }\n}\n\n/**\n * Create a drain function that writes logs to the local file system as NDJSON.\n *\n * Files are organized by date (`2026-03-14.jsonl`) with optional size-based\n * rotation and automatic cleanup of old files.\n *\n * @example\n * ```ts\n * // Default: writes to .evlog/logs/\n * nitroApp.hooks.hook('evlog:drain', createFsDrain())\n *\n * // With options\n * nitroApp.hooks.hook('evlog:drain', createFsDrain({\n * dir: '.evlog/logs',\n * maxFiles: 7,\n * pretty: true,\n * }))\n * ```\n */\nexport function createFsDrain(overrides?: Partial<FsConfig>) {\n return defineDrain<FsConfig>({\n name: 'fs',\n resolve: () => ({\n dir: overrides?.dir ?? '.evlog/logs',\n pretty: overrides?.pretty ?? false,\n maxFiles: overrides?.maxFiles,\n maxSizePerFile: overrides?.maxSizePerFile,\n }),\n send: writeBatchToFs,\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"fs.mjs","names":[],"sources":["../../src/adapters/fs.ts"],"sourcesContent":["import { appendFile, mkdir, readdir, stat, unlink, writeFile } from 'node:fs/promises'\nimport { join, sep } from 'node:path'\nimport type { WideEvent } from '../types'\nimport { defineDrain } from './_drain'\n\nexport interface FsConfig {\n /** Directory for log files */\n dir: string\n /** Max number of log files to keep (auto-deletes oldest when exceeded) */\n maxFiles?: number\n /** Max bytes per file before rotating to a new suffixed file */\n maxSizePerFile?: number\n /** Pretty-print JSON instead of compact NDJSON */\n pretty: boolean\n}\n\nconst gitignoreWritten = new Set<string>()\n\nasync function ensureGitignore(dir: string): Promise<void> {\n const normalized = dir.replace(/[\\\\/]/g, sep)\n const segments = normalized.split(sep)\n const evlogIndex = segments.findIndex(s => s === '.evlog')\n const targetDir = evlogIndex !== -1 ? segments.slice(0, evlogIndex + 1).join(sep) : dir\n\n if (gitignoreWritten.has(targetDir)) return\n\n const gitignorePath = join(targetDir, '.gitignore')\n try {\n await stat(gitignorePath)\n } catch {\n await writeFile(gitignorePath, '*\\n', 'utf-8')\n }\n gitignoreWritten.add(targetDir)\n}\n\nfunction getDateString(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nasync function resolveFilePath(dir: string, maxSizePerFile?: number): Promise<string> {\n const date = getDateString()\n const basePath = join(dir, `${date}.jsonl`)\n\n if (!maxSizePerFile) return basePath\n\n try {\n const stats = await stat(basePath)\n if (stats.size < maxSizePerFile) return basePath\n } catch {\n return basePath\n }\n\n for (let i = 1; i < 1000; i++) {\n const rotatedPath = join(dir, `${date}.${i}.jsonl`)\n try {\n const stats = await stat(rotatedPath)\n if (stats.size < maxSizePerFile) return rotatedPath\n } catch {\n return rotatedPath\n }\n }\n\n return join(dir, `${date}.999.jsonl`)\n}\n\nfunction parseLogFilename(filename: string): { date: string; index: number } {\n const match = filename.match(/^(\\d{4}-\\d{2}-\\d{2})(?:\\.(\\d+))?\\.jsonl$/)\n if (!match) return { date: '', index: 0 }\n return { date: match[1], index: match[2] ? Number.parseInt(match[2], 10) : 0 }\n}\n\nasync function cleanupOldFiles(dir: string, maxFiles: number): Promise<void> {\n const files = await readdir(dir)\n const jsonlFiles = files.filter(f => f.endsWith('.jsonl')).sort((a, b) => {\n const pa = parseLogFilename(a)\n const pb = parseLogFilename(b)\n return pa.date.localeCompare(pb.date) || pa.index - pb.index\n })\n\n if (jsonlFiles.length <= maxFiles) return\n\n const toDelete = jsonlFiles.slice(0, jsonlFiles.length - maxFiles)\n await Promise.allSettled(toDelete.map(f => unlink(join(dir, f))))\n}\n\nexport async function writeToFs(event: WideEvent, config: FsConfig): Promise<void> {\n await writeBatchToFs([event], config)\n}\n\nexport async function writeBatchToFs(events: WideEvent[], config: FsConfig): Promise<void> {\n if (events.length === 0) return\n\n await mkdir(config.dir, { recursive: true })\n await ensureGitignore(config.dir)\n\n const filePath = await resolveFilePath(config.dir, config.maxSizePerFile)\n const lines = `${events\n .map(e => config.pretty ? JSON.stringify(e, null, 2) : JSON.stringify(e))\n .join('\\n') }\\n`\n\n await appendFile(filePath, lines, 'utf-8')\n\n if (config.maxFiles) {\n await cleanupOldFiles(config.dir, config.maxFiles)\n }\n}\n\n/**\n * Create a drain function that writes logs to the local file system as NDJSON.\n *\n * Files are organized by date (`2026-03-14.jsonl`) with optional size-based\n * rotation and automatic cleanup of old files.\n *\n * @example\n * ```ts\n * // Default: writes to .evlog/logs/\n * nitroApp.hooks.hook('evlog:drain', createFsDrain())\n *\n * // With options\n * nitroApp.hooks.hook('evlog:drain', createFsDrain({\n * dir: '.evlog/logs',\n * maxFiles: 7,\n * pretty: true,\n * }))\n * ```\n */\nexport function createFsDrain(overrides?: Partial<FsConfig>) {\n return defineDrain<FsConfig>({\n name: 'fs',\n resolve: () => ({\n dir: overrides?.dir ?? '.evlog/logs',\n pretty: overrides?.pretty ?? false,\n maxFiles: overrides?.maxFiles,\n maxSizePerFile: overrides?.maxSizePerFile,\n }),\n send: writeBatchToFs,\n })\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
1
+ import { F as DrainContext, it as WideEvent } from "../audit-CTIviX3P.mjs";
2
2
  import { OTLPConfig } from "./otlp.mjs";
3
3
 
4
4
  //#region src/adapters/hyperdx.d.ts
@@ -1,4 +1,4 @@
1
- import { n as resolveAdapterConfig } from "../_http-CHSsrWDJ.mjs";
1
+ import { n as resolveAdapterConfig } from "../_http-BY1e9pwC.mjs";
2
2
  import { t as defineDrain } from "../_drain-CmCtsuF6.mjs";
3
3
  import { sendBatchToOTLP } from "./otlp.mjs";
4
4
  //#region src/adapters/hyperdx.ts
@@ -1,4 +1,4 @@
1
- import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
1
+ import { F as DrainContext, it as WideEvent } from "../audit-CTIviX3P.mjs";
2
2
  //#region src/adapters/otlp.d.ts
3
3
  interface OTLPConfig {
4
4
  /** OTLP HTTP endpoint (e.g., http://localhost:4318) */
@@ -1,4 +1,4 @@
1
- import { n as resolveAdapterConfig, t as httpPost } from "../_http-CHSsrWDJ.mjs";
1
+ import { n as resolveAdapterConfig, t as httpPost } from "../_http-BY1e9pwC.mjs";
2
2
  import { t as defineDrain } from "../_drain-CmCtsuF6.mjs";
3
3
  import { n as OTEL_SEVERITY_TEXT, t as OTEL_SEVERITY_NUMBER } from "../_severity-CQijvfhU.mjs";
4
4
  //#region src/adapters/otlp.ts
@@ -1 +1 @@
1
- {"version":3,"file":"otlp.mjs","names":[],"sources":["../../src/adapters/otlp.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\nimport { OTEL_SEVERITY_NUMBER, OTEL_SEVERITY_TEXT } from './_severity'\n\nexport interface OTLPConfig {\n /** OTLP HTTP endpoint (e.g., http://localhost:4318) */\n endpoint: string\n /** Override service name (defaults to event.service) */\n serviceName?: string\n /** Additional resource attributes */\n resourceAttributes?: Record<string, string | number | boolean>\n /** Custom headers (e.g., for authentication) */\n headers?: Record<string, string>\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\n/** OTLP Log Record structure */\nexport interface OTLPLogRecord {\n timeUnixNano: string\n severityNumber: number\n severityText: string\n body: { stringValue: string }\n attributes: Array<{\n key: string\n value: { stringValue?: string, intValue?: string, boolValue?: boolean }\n }>\n traceId?: string\n spanId?: string\n}\n\n/** OTLP Resource structure */\ninterface OTLPResource {\n attributes: Array<{\n key: string\n value: { stringValue?: string, intValue?: string, boolValue?: boolean }\n }>\n}\n\n/** OTLP Scope structure */\ninterface OTLPScope {\n name: string\n version?: string\n}\n\n/** OTLP ExportLogsServiceRequest structure */\ninterface ExportLogsServiceRequest {\n resourceLogs: Array<{\n resource: OTLPResource\n scopeLogs: Array<{\n scope: OTLPScope\n logRecords: OTLPLogRecord[]\n }>\n }>\n}\n\nconst OTLP_FIELDS: ConfigField<OTLPConfig>[] = [\n { key: 'endpoint', env: ['NUXT_OTLP_ENDPOINT', 'OTEL_EXPORTER_OTLP_ENDPOINT'] },\n { key: 'serviceName', env: ['NUXT_OTLP_SERVICE_NAME', 'OTEL_SERVICE_NAME'] },\n { key: 'headers' },\n { key: 'resourceAttributes' },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\n/**\n * Convert a value to OTLP attribute value format.\n */\nfunction toAttributeValue(value: unknown): { stringValue?: string, intValue?: string, boolValue?: boolean } {\n if (typeof value === 'boolean') {\n return { boolValue: value }\n }\n if (typeof value === 'number' && Number.isInteger(value)) {\n return { intValue: String(value) }\n }\n if (typeof value === 'string') {\n return { stringValue: value }\n }\n // For complex types, serialize to JSON string\n return { stringValue: JSON.stringify(value) }\n}\n\n/**\n * Convert an evlog WideEvent to an OTLP LogRecord.\n */\nexport function toOTLPLogRecord(event: WideEvent): OTLPLogRecord {\n const timestamp = new Date(event.timestamp).getTime() * 1_000_000 // Convert to nanoseconds\n\n // Extract known fields, rest goes to attributes\n const { level, traceId, spanId, ...rest } = event\n // Remove base fields from rest (they're handled as resource attributes)\n delete (rest as Record<string, unknown>).timestamp\n delete (rest as Record<string, unknown>).service\n delete (rest as Record<string, unknown>).environment\n delete (rest as Record<string, unknown>).version\n delete (rest as Record<string, unknown>).commitHash\n delete (rest as Record<string, unknown>).region\n\n const attributes: OTLPLogRecord['attributes'] = []\n\n // Add all remaining event fields as attributes\n for (const [key, value] of Object.entries(rest)) {\n if (value !== undefined && value !== null) {\n attributes.push({\n key,\n value: toAttributeValue(value),\n })\n }\n }\n\n const record: OTLPLogRecord = {\n timeUnixNano: String(timestamp),\n severityNumber: OTEL_SEVERITY_NUMBER[level] ?? 9,\n severityText: OTEL_SEVERITY_TEXT[level] ?? 'INFO',\n body: { stringValue: JSON.stringify(event) },\n attributes,\n }\n\n // Add trace context if present\n if (typeof traceId === 'string') {\n record.traceId = traceId\n }\n if (typeof spanId === 'string') {\n record.spanId = spanId\n }\n\n return record\n}\n\n/**\n * Build OTLP resource attributes from event and config.\n */\nfunction buildResourceAttributes(\n event: WideEvent,\n config: OTLPConfig,\n): OTLPResource['attributes'] {\n const attributes: OTLPResource['attributes'] = []\n\n // Service name\n attributes.push({\n key: 'service.name',\n value: { stringValue: config.serviceName ?? event.service },\n })\n\n // Environment\n if (event.environment) {\n attributes.push({\n key: 'deployment.environment',\n value: { stringValue: event.environment },\n })\n }\n\n // Version\n if (event.version) {\n attributes.push({\n key: 'service.version',\n value: { stringValue: event.version },\n })\n }\n\n // Region\n if (event.region) {\n attributes.push({\n key: 'cloud.region',\n value: { stringValue: event.region },\n })\n }\n\n // Commit hash\n if (event.commitHash) {\n attributes.push({\n key: 'vcs.commit.id',\n value: { stringValue: event.commitHash },\n })\n }\n\n // Custom resource attributes from config\n if (config.resourceAttributes) {\n for (const [key, value] of Object.entries(config.resourceAttributes)) {\n attributes.push({\n key,\n value: toAttributeValue(value),\n })\n }\n }\n\n return attributes\n}\n\n/**\n * Build headers from OTEL env vars.\n * Kept inline as OTLP-specific (parses OTEL_EXPORTER_OTLP_HEADERS=key=val,key=val).\n */\nfunction getHeadersFromEnv(): Record<string, string> | undefined {\n const headersEnv = process.env.OTEL_EXPORTER_OTLP_HEADERS || process.env.NUXT_OTLP_HEADERS\n if (headersEnv) {\n const headers: Record<string, string> = {}\n const decoded = decodeURIComponent(headersEnv)\n for (const pair of decoded.split(',')) {\n const eqIndex = pair.indexOf('=')\n if (eqIndex > 0) {\n const key = pair.slice(0, eqIndex).trim()\n const value = pair.slice(eqIndex + 1).trim()\n if (key && value) {\n headers[key] = value\n }\n }\n }\n if (Object.keys(headers).length > 0) return headers\n }\n\n const auth = process.env.NUXT_OTLP_AUTH\n if (auth) {\n return { Authorization: auth }\n }\n\n return undefined\n}\n\n/**\n * Create a drain function for sending logs to an OTLP endpoint.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createOTLPDrain()\n * 2. runtimeConfig.evlog.otlp (NUXT_EVLOG_OTLP_*)\n * 3. runtimeConfig.otlp (NUXT_OTLP_*)\n * 4. Environment variables: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME\n *\n * @example\n * ```ts\n * // Zero config - reads from runtimeConfig or env vars\n * nitroApp.hooks.hook('evlog:drain', createOTLPDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createOTLPDrain({\n * endpoint: 'http://localhost:4318',\n * }))\n * ```\n */\nexport function createOTLPDrain(overrides?: Partial<OTLPConfig>) {\n return defineDrain<OTLPConfig>({\n name: 'otlp',\n resolve: async () => {\n const config = await resolveAdapterConfig<OTLPConfig>('otlp', OTLP_FIELDS, overrides)\n\n // OTLP-specific: resolve headers from env if not provided via config\n if (!config.headers) {\n config.headers = getHeadersFromEnv()\n }\n\n if (!config.endpoint) {\n console.error('[evlog/otlp] Missing endpoint. Set NUXT_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT env var, or pass to createOTLPDrain()')\n return null\n }\n return config as OTLPConfig\n },\n send: sendBatchToOTLP,\n })\n}\n\n/**\n * Send a single event to an OTLP endpoint.\n *\n * @example\n * ```ts\n * await sendToOTLP(event, {\n * endpoint: 'http://localhost:4318',\n * })\n * ```\n */\nexport async function sendToOTLP(event: WideEvent, config: OTLPConfig): Promise<void> {\n await sendBatchToOTLP([event], config)\n}\n\n/**\n * Send a batch of events to an OTLP endpoint.\n *\n * @example\n * ```ts\n * await sendBatchToOTLP(events, {\n * endpoint: 'http://localhost:4318',\n * })\n * ```\n */\nexport async function sendBatchToOTLP(events: WideEvent[], config: OTLPConfig): Promise<void> {\n if (events.length === 0) return\n\n const url = `${config.endpoint.replace(/\\/$/, '')}/v1/logs`\n\n // Group events by (service, environment) so each gets correct OTLP resource attributes\n const grouped = new Map<string, WideEvent[]>()\n for (const event of events) {\n const key = `${event.service}::${event.environment}`\n const group = grouped.get(key)\n if (group) {\n group.push(event)\n } else {\n grouped.set(key, [event])\n }\n }\n\n const payload: ExportLogsServiceRequest = {\n resourceLogs: Array.from(grouped.values()).map((groupEvents) => ({\n resource: { attributes: buildResourceAttributes(groupEvents[0]!, config) },\n scopeLogs: [\n {\n scope: { name: 'evlog', version: '1.0.0' },\n logRecords: groupEvents.map(toOTLPLogRecord),\n },\n ],\n })),\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...config.headers,\n }\n\n await httpPost({\n url,\n headers,\n body: JSON.stringify(payload),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'OTLP',\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"otlp.mjs","names":[],"sources":["../../src/adapters/otlp.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\nimport { OTEL_SEVERITY_NUMBER, OTEL_SEVERITY_TEXT } from './_severity'\n\nexport interface OTLPConfig {\n /** OTLP HTTP endpoint (e.g., http://localhost:4318) */\n endpoint: string\n /** Override service name (defaults to event.service) */\n serviceName?: string\n /** Additional resource attributes */\n resourceAttributes?: Record<string, string | number | boolean>\n /** Custom headers (e.g., for authentication) */\n headers?: Record<string, string>\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\n/** OTLP Log Record structure */\nexport interface OTLPLogRecord {\n timeUnixNano: string\n severityNumber: number\n severityText: string\n body: { stringValue: string }\n attributes: Array<{\n key: string\n value: { stringValue?: string, intValue?: string, boolValue?: boolean }\n }>\n traceId?: string\n spanId?: string\n}\n\n/** OTLP Resource structure */\ninterface OTLPResource {\n attributes: Array<{\n key: string\n value: { stringValue?: string, intValue?: string, boolValue?: boolean }\n }>\n}\n\n/** OTLP Scope structure */\ninterface OTLPScope {\n name: string\n version?: string\n}\n\n/** OTLP ExportLogsServiceRequest structure */\ninterface ExportLogsServiceRequest {\n resourceLogs: Array<{\n resource: OTLPResource\n scopeLogs: Array<{\n scope: OTLPScope\n logRecords: OTLPLogRecord[]\n }>\n }>\n}\n\nconst OTLP_FIELDS: ConfigField<OTLPConfig>[] = [\n { key: 'endpoint', env: ['NUXT_OTLP_ENDPOINT', 'OTEL_EXPORTER_OTLP_ENDPOINT'] },\n { key: 'serviceName', env: ['NUXT_OTLP_SERVICE_NAME', 'OTEL_SERVICE_NAME'] },\n { key: 'headers' },\n { key: 'resourceAttributes' },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\n/**\n * Convert a value to OTLP attribute value format.\n */\nfunction toAttributeValue(value: unknown): { stringValue?: string, intValue?: string, boolValue?: boolean } {\n if (typeof value === 'boolean') {\n return { boolValue: value }\n }\n if (typeof value === 'number' && Number.isInteger(value)) {\n return { intValue: String(value) }\n }\n if (typeof value === 'string') {\n return { stringValue: value }\n }\n // For complex types, serialize to JSON string\n return { stringValue: JSON.stringify(value) }\n}\n\n/**\n * Convert an evlog WideEvent to an OTLP LogRecord.\n */\nexport function toOTLPLogRecord(event: WideEvent): OTLPLogRecord {\n const timestamp = new Date(event.timestamp).getTime() * 1_000_000 // Convert to nanoseconds\n\n // Extract known fields, rest goes to attributes\n const { level, traceId, spanId, ...rest } = event\n // Remove base fields from rest (they're handled as resource attributes)\n delete (rest as Record<string, unknown>).timestamp\n delete (rest as Record<string, unknown>).service\n delete (rest as Record<string, unknown>).environment\n delete (rest as Record<string, unknown>).version\n delete (rest as Record<string, unknown>).commitHash\n delete (rest as Record<string, unknown>).region\n\n const attributes: OTLPLogRecord['attributes'] = []\n\n // Add all remaining event fields as attributes\n for (const [key, value] of Object.entries(rest)) {\n if (value !== undefined && value !== null) {\n attributes.push({\n key,\n value: toAttributeValue(value),\n })\n }\n }\n\n const record: OTLPLogRecord = {\n timeUnixNano: String(timestamp),\n severityNumber: OTEL_SEVERITY_NUMBER[level] ?? 9,\n severityText: OTEL_SEVERITY_TEXT[level] ?? 'INFO',\n body: { stringValue: JSON.stringify(event) },\n attributes,\n }\n\n // Add trace context if present\n if (typeof traceId === 'string') {\n record.traceId = traceId\n }\n if (typeof spanId === 'string') {\n record.spanId = spanId\n }\n\n return record\n}\n\n/**\n * Build OTLP resource attributes from event and config.\n */\nfunction buildResourceAttributes(\n event: WideEvent,\n config: OTLPConfig,\n): OTLPResource['attributes'] {\n const attributes: OTLPResource['attributes'] = []\n\n // Service name\n attributes.push({\n key: 'service.name',\n value: { stringValue: config.serviceName ?? event.service },\n })\n\n // Environment\n if (event.environment) {\n attributes.push({\n key: 'deployment.environment',\n value: { stringValue: event.environment },\n })\n }\n\n // Version\n if (event.version) {\n attributes.push({\n key: 'service.version',\n value: { stringValue: event.version },\n })\n }\n\n // Region\n if (event.region) {\n attributes.push({\n key: 'cloud.region',\n value: { stringValue: event.region },\n })\n }\n\n // Commit hash\n if (event.commitHash) {\n attributes.push({\n key: 'vcs.commit.id',\n value: { stringValue: event.commitHash },\n })\n }\n\n // Custom resource attributes from config\n if (config.resourceAttributes) {\n for (const [key, value] of Object.entries(config.resourceAttributes)) {\n attributes.push({\n key,\n value: toAttributeValue(value),\n })\n }\n }\n\n return attributes\n}\n\n/**\n * Build headers from OTEL env vars.\n * Kept inline as OTLP-specific (parses OTEL_EXPORTER_OTLP_HEADERS=key=val,key=val).\n */\nfunction getHeadersFromEnv(): Record<string, string> | undefined {\n const headersEnv = process.env.OTEL_EXPORTER_OTLP_HEADERS || process.env.NUXT_OTLP_HEADERS\n if (headersEnv) {\n const headers: Record<string, string> = {}\n const decoded = decodeURIComponent(headersEnv)\n for (const pair of decoded.split(',')) {\n const eqIndex = pair.indexOf('=')\n if (eqIndex > 0) {\n const key = pair.slice(0, eqIndex).trim()\n const value = pair.slice(eqIndex + 1).trim()\n if (key && value) {\n headers[key] = value\n }\n }\n }\n if (Object.keys(headers).length > 0) return headers\n }\n\n const auth = process.env.NUXT_OTLP_AUTH\n if (auth) {\n return { Authorization: auth }\n }\n\n return undefined\n}\n\n/**\n * Create a drain function for sending logs to an OTLP endpoint.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createOTLPDrain()\n * 2. runtimeConfig.evlog.otlp (NUXT_EVLOG_OTLP_*)\n * 3. runtimeConfig.otlp (NUXT_OTLP_*)\n * 4. Environment variables: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME\n *\n * @example\n * ```ts\n * // Zero config - reads from runtimeConfig or env vars\n * nitroApp.hooks.hook('evlog:drain', createOTLPDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createOTLPDrain({\n * endpoint: 'http://localhost:4318',\n * }))\n * ```\n */\nexport function createOTLPDrain(overrides?: Partial<OTLPConfig>) {\n return defineDrain<OTLPConfig>({\n name: 'otlp',\n resolve: async () => {\n const config = await resolveAdapterConfig<OTLPConfig>('otlp', OTLP_FIELDS, overrides)\n\n // OTLP-specific: resolve headers from env if not provided via config\n if (!config.headers) {\n config.headers = getHeadersFromEnv()\n }\n\n if (!config.endpoint) {\n console.error('[evlog/otlp] Missing endpoint. Set NUXT_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT env var, or pass to createOTLPDrain()')\n return null\n }\n return config as OTLPConfig\n },\n send: sendBatchToOTLP,\n })\n}\n\n/**\n * Send a single event to an OTLP endpoint.\n *\n * @example\n * ```ts\n * await sendToOTLP(event, {\n * endpoint: 'http://localhost:4318',\n * })\n * ```\n */\nexport async function sendToOTLP(event: WideEvent, config: OTLPConfig): Promise<void> {\n await sendBatchToOTLP([event], config)\n}\n\n/**\n * Send a batch of events to an OTLP endpoint.\n *\n * @example\n * ```ts\n * await sendBatchToOTLP(events, {\n * endpoint: 'http://localhost:4318',\n * })\n * ```\n */\nexport async function sendBatchToOTLP(events: WideEvent[], config: OTLPConfig): Promise<void> {\n if (events.length === 0) return\n\n const url = `${config.endpoint.replace(/\\/$/, '')}/v1/logs`\n\n // Group events by (service, environment) so each gets correct OTLP resource attributes\n const grouped = new Map<string, WideEvent[]>()\n for (const event of events) {\n const key = `${event.service}::${event.environment}`\n const group = grouped.get(key)\n if (group) {\n group.push(event)\n } else {\n grouped.set(key, [event])\n }\n }\n\n const payload: ExportLogsServiceRequest = {\n resourceLogs: Array.from(grouped.values()).map((groupEvents) => ({\n resource: { attributes: buildResourceAttributes(groupEvents[0]!, config) },\n scopeLogs: [\n {\n scope: { name: 'evlog', version: '1.0.0' },\n logRecords: groupEvents.map(toOTLPLogRecord),\n },\n ],\n })),\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...config.headers,\n }\n\n await httpPost({\n url,\n headers,\n body: JSON.stringify(payload),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'OTLP',\n })\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
1
+ import { F as DrainContext, it as WideEvent } from "../audit-CTIviX3P.mjs";
2
2
  //#region src/adapters/posthog.d.ts
3
3
  interface PostHogConfig {
4
4
  /** PostHog project API key */
@@ -1,4 +1,4 @@
1
- import { n as resolveAdapterConfig, t as httpPost } from "../_http-CHSsrWDJ.mjs";
1
+ import { n as resolveAdapterConfig, t as httpPost } from "../_http-BY1e9pwC.mjs";
2
2
  import { t as defineDrain } from "../_drain-CmCtsuF6.mjs";
3
3
  import { sendBatchToOTLP } from "./otlp.mjs";
4
4
  //#region src/adapters/posthog.ts
@@ -1 +1 @@
1
- {"version":3,"file":"posthog.mjs","names":[],"sources":["../../src/adapters/posthog.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\nimport { sendBatchToOTLP } from './otlp'\nimport type { OTLPConfig } from './otlp'\n\nexport interface PostHogConfig {\n /** PostHog project API key */\n apiKey: string\n /** PostHog host URL. Default: https://us.i.posthog.com */\n host?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\nexport interface PostHogEventsConfig extends PostHogConfig {\n /** PostHog event name. Default: evlog_wide_event */\n eventName?: string\n /** Override distinct_id (defaults to event.service) */\n distinctId?: string\n}\n\n/** PostHog event structure for the batch API */\nexport interface PostHogEvent {\n event: string\n distinct_id: string\n timestamp: string\n properties: Record<string, unknown>\n}\n\nconst POSTHOG_FIELDS: ConfigField<PostHogConfig>[] = [\n { key: 'apiKey', env: ['NUXT_POSTHOG_API_KEY', 'POSTHOG_API_KEY'] },\n { key: 'host', env: ['NUXT_POSTHOG_HOST', 'POSTHOG_HOST'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nconst POSTHOG_EVENTS_FIELDS: ConfigField<PostHogEventsConfig>[] = [\n ...POSTHOG_FIELDS,\n { key: 'eventName' },\n { key: 'distinctId' },\n]\n\nfunction resolveHost(config: PostHogConfig): string {\n return (config.host ?? 'https://us.i.posthog.com').replace(/\\/$/, '')\n}\n\nfunction toOTLPConfig(config: PostHogConfig): OTLPConfig {\n const host = resolveHost(config)\n return {\n endpoint: `${host}/i`,\n headers: { Authorization: `Bearer ${config.apiKey}` },\n timeout: config.timeout,\n retries: config.retries,\n }\n}\n\n/**\n * Convert a WideEvent to a PostHog custom event format.\n */\nexport function toPostHogEvent(event: WideEvent, config: PostHogEventsConfig): PostHogEvent {\n const { timestamp, level, service, ...rest } = event\n\n return {\n event: config.eventName ?? 'evlog_wide_event',\n distinct_id: config.distinctId ?? (typeof event.userId === 'string' ? event.userId : undefined) ?? service,\n timestamp,\n properties: {\n level,\n service,\n ...rest,\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// PostHog Logs (OTLP) — default\n// ---------------------------------------------------------------------------\n\n/**\n * Create a drain function for sending logs to PostHog Logs via OTLP.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createPostHogDrain()\n * 2. runtimeConfig.evlog.posthog\n * 3. runtimeConfig.posthog\n * 4. Environment variables: NUXT_POSTHOG_*, POSTHOG_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_POSTHOG_API_KEY env var\n * nitroApp.hooks.hook('evlog:drain', createPostHogDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createPostHogDrain({\n * apiKey: 'phc_...',\n * host: 'https://eu.i.posthog.com',\n * }))\n * ```\n */\nexport function createPostHogDrain(overrides?: Partial<PostHogConfig>) {\n return defineDrain<PostHogConfig>({\n name: 'posthog',\n resolve: async () => {\n const config = await resolveAdapterConfig<PostHogConfig>('posthog', POSTHOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/posthog] Missing apiKey. Set NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY env var or pass to createPostHogDrain()')\n return null\n }\n return config as PostHogConfig\n },\n send: sendBatchToPostHog,\n })\n}\n\n/**\n * Send a single event to PostHog Logs via OTLP.\n *\n * @example\n * ```ts\n * await sendToPostHog(event, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendToPostHog(event: WideEvent, config: PostHogConfig): Promise<void> {\n await sendBatchToPostHog([event], config)\n}\n\n/**\n * Send a batch of events to PostHog Logs via OTLP.\n *\n * @example\n * ```ts\n * await sendBatchToPostHog(events, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendBatchToPostHog(events: WideEvent[], config: PostHogConfig): Promise<void> {\n if (events.length === 0) return\n await sendBatchToOTLP(events, toOTLPConfig(config))\n}\n\n// ---------------------------------------------------------------------------\n// PostHog Events (custom events via /batch/)\n// ---------------------------------------------------------------------------\n\n/**\n * Create a drain function for sending logs to PostHog as custom events.\n *\n * Uses PostHog's `/batch/` API. Consider using `createPostHogDrain()` instead\n * which uses PostHog Logs (OTLP) and is significantly cheaper.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createPostHogEventsDrain()\n * 2. runtimeConfig.evlog.posthog\n * 3. runtimeConfig.posthog\n * 4. Environment variables: NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY, NUXT_POSTHOG_HOST/POSTHOG_HOST\n *\n * @example\n * ```ts\n * nitroApp.hooks.hook('evlog:drain', createPostHogEventsDrain({\n * eventName: 'server_request',\n * }))\n * ```\n */\nexport function createPostHogEventsDrain(overrides?: Partial<PostHogEventsConfig>) {\n return defineDrain<PostHogEventsConfig>({\n name: 'posthog-events',\n resolve: async () => {\n const config = await resolveAdapterConfig<PostHogEventsConfig>('posthog', POSTHOG_EVENTS_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/posthog-events] Missing apiKey. Set NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY env var or pass to createPostHogEventsDrain()')\n return null\n }\n return config as PostHogEventsConfig\n },\n send: sendBatchToPostHogEvents,\n })\n}\n\n/**\n * Send a single event to PostHog as a custom event.\n *\n * @example\n * ```ts\n * await sendToPostHogEvents(event, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendToPostHogEvents(event: WideEvent, config: PostHogEventsConfig): Promise<void> {\n await sendBatchToPostHogEvents([event], config)\n}\n\n/**\n * Send a batch of events to PostHog as custom events via the `/batch/` API.\n *\n * @example\n * ```ts\n * await sendBatchToPostHogEvents(events, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendBatchToPostHogEvents(events: WideEvent[], config: PostHogEventsConfig): Promise<void> {\n if (events.length === 0) return\n\n const url = `${resolveHost(config)}/batch/`\n const batch = events.map(event => toPostHogEvent(event, config))\n\n await httpPost({\n url,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ api_key: config.apiKey, batch }),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'PostHog',\n })\n}\n"],"mappings":";;;;AAkCA,MAAM,iBAA+C;CACnD;EAAE,KAAK;EAAU,KAAK,CAAC,wBAAwB,kBAAkB;EAAE;CACnE;EAAE,KAAK;EAAQ,KAAK,CAAC,qBAAqB,eAAe;EAAE;CAC3D,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,WAAW;CACnB;AAED,MAAM,wBAA4D;CAChE,GAAG;CACH,EAAE,KAAK,aAAa;CACpB,EAAE,KAAK,cAAc;CACtB;AAED,SAAS,YAAY,QAA+B;AAClD,SAAQ,OAAO,QAAQ,4BAA4B,QAAQ,OAAO,GAAG;;AAGvE,SAAS,aAAa,QAAmC;AAEvD,QAAO;EACL,UAAU,GAFC,YAAY,OAAO,CAEZ;EAClB,SAAS,EAAE,eAAe,UAAU,OAAO,UAAU;EACrD,SAAS,OAAO;EAChB,SAAS,OAAO;EACjB;;;;;AAMH,SAAgB,eAAe,OAAkB,QAA2C;CAC1F,MAAM,EAAE,WAAW,OAAO,SAAS,GAAG,SAAS;AAE/C,QAAO;EACL,OAAO,OAAO,aAAa;EAC3B,aAAa,OAAO,eAAe,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS,KAAA,MAAc;EACnG;EACA,YAAY;GACV;GACA;GACA,GAAG;GACJ;EACF;;;;;;;;;;;;;;;;;;;;;;;AA4BH,SAAgB,mBAAmB,WAAoC;AACrE,QAAO,YAA2B;EAChC,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBAAoC,WAAW,gBAAgB,UAAU;AAC9F,OAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,mHAAmH;AACjI,WAAO;;AAET,UAAO;;EAET,MAAM;EACP,CAAC;;;;;;;;;;;;AAaJ,eAAsB,cAAc,OAAkB,QAAsC;AAC1F,OAAM,mBAAmB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;AAa3C,eAAsB,mBAAmB,QAAqB,QAAsC;AAClG,KAAI,OAAO,WAAW,EAAG;AACzB,OAAM,gBAAgB,QAAQ,aAAa,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;AA0BrD,SAAgB,yBAAyB,WAA0C;AACjF,QAAO,YAAiC;EACtC,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBAA0C,WAAW,uBAAuB,UAAU;AAC3G,OAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,gIAAgI;AAC9I,WAAO;;AAET,UAAO;;EAET,MAAM;EACP,CAAC;;;;;;;;;;;;AAaJ,eAAsB,oBAAoB,OAAkB,QAA4C;AACtG,OAAM,yBAAyB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;AAajD,eAAsB,yBAAyB,QAAqB,QAA4C;AAC9G,KAAI,OAAO,WAAW,EAAG;CAEzB,MAAM,MAAM,GAAG,YAAY,OAAO,CAAC;CACnC,MAAM,QAAQ,OAAO,KAAI,UAAS,eAAe,OAAO,OAAO,CAAC;AAEhE,OAAM,SAAS;EACb;EACA,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU;GAAE,SAAS,OAAO;GAAQ;GAAO,CAAC;EACvD,SAAS,OAAO,WAAW;EAC3B,SAAS,OAAO;EAChB,OAAO;EACR,CAAC"}
1
+ {"version":3,"file":"posthog.mjs","names":[],"sources":["../../src/adapters/posthog.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\nimport { sendBatchToOTLP } from './otlp'\nimport type { OTLPConfig } from './otlp'\n\nexport interface PostHogConfig {\n /** PostHog project API key */\n apiKey: string\n /** PostHog host URL. Default: https://us.i.posthog.com */\n host?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\nexport interface PostHogEventsConfig extends PostHogConfig {\n /** PostHog event name. Default: evlog_wide_event */\n eventName?: string\n /** Override distinct_id (defaults to event.service) */\n distinctId?: string\n}\n\n/** PostHog event structure for the batch API */\nexport interface PostHogEvent {\n event: string\n distinct_id: string\n timestamp: string\n properties: Record<string, unknown>\n}\n\nconst POSTHOG_FIELDS: ConfigField<PostHogConfig>[] = [\n { key: 'apiKey', env: ['NUXT_POSTHOG_API_KEY', 'POSTHOG_API_KEY'] },\n { key: 'host', env: ['NUXT_POSTHOG_HOST', 'POSTHOG_HOST'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nconst POSTHOG_EVENTS_FIELDS: ConfigField<PostHogEventsConfig>[] = [\n ...POSTHOG_FIELDS,\n { key: 'eventName' },\n { key: 'distinctId' },\n]\n\nfunction resolveHost(config: PostHogConfig): string {\n return (config.host ?? 'https://us.i.posthog.com').replace(/\\/$/, '')\n}\n\nfunction toOTLPConfig(config: PostHogConfig): OTLPConfig {\n const host = resolveHost(config)\n return {\n endpoint: `${host}/i`,\n headers: { Authorization: `Bearer ${config.apiKey}` },\n timeout: config.timeout,\n retries: config.retries,\n }\n}\n\n/**\n * Convert a WideEvent to a PostHog custom event format.\n */\nexport function toPostHogEvent(event: WideEvent, config: PostHogEventsConfig): PostHogEvent {\n const { timestamp, level, service, ...rest } = event\n\n return {\n event: config.eventName ?? 'evlog_wide_event',\n distinct_id: config.distinctId ?? (typeof event.userId === 'string' ? event.userId : undefined) ?? service,\n timestamp,\n properties: {\n level,\n service,\n ...rest,\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// PostHog Logs (OTLP) — default\n// ---------------------------------------------------------------------------\n\n/**\n * Create a drain function for sending logs to PostHog Logs via OTLP.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createPostHogDrain()\n * 2. runtimeConfig.evlog.posthog\n * 3. runtimeConfig.posthog\n * 4. Environment variables: NUXT_POSTHOG_*, POSTHOG_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_POSTHOG_API_KEY env var\n * nitroApp.hooks.hook('evlog:drain', createPostHogDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createPostHogDrain({\n * apiKey: 'phc_...',\n * host: 'https://eu.i.posthog.com',\n * }))\n * ```\n */\nexport function createPostHogDrain(overrides?: Partial<PostHogConfig>) {\n return defineDrain<PostHogConfig>({\n name: 'posthog',\n resolve: async () => {\n const config = await resolveAdapterConfig<PostHogConfig>('posthog', POSTHOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/posthog] Missing apiKey. Set NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY env var or pass to createPostHogDrain()')\n return null\n }\n return config as PostHogConfig\n },\n send: sendBatchToPostHog,\n })\n}\n\n/**\n * Send a single event to PostHog Logs via OTLP.\n *\n * @example\n * ```ts\n * await sendToPostHog(event, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendToPostHog(event: WideEvent, config: PostHogConfig): Promise<void> {\n await sendBatchToPostHog([event], config)\n}\n\n/**\n * Send a batch of events to PostHog Logs via OTLP.\n *\n * @example\n * ```ts\n * await sendBatchToPostHog(events, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendBatchToPostHog(events: WideEvent[], config: PostHogConfig): Promise<void> {\n if (events.length === 0) return\n await sendBatchToOTLP(events, toOTLPConfig(config))\n}\n\n// ---------------------------------------------------------------------------\n// PostHog Events (custom events via /batch/)\n// ---------------------------------------------------------------------------\n\n/**\n * Create a drain function for sending logs to PostHog as custom events.\n *\n * Uses PostHog's `/batch/` API. Consider using `createPostHogDrain()` instead\n * which uses PostHog Logs (OTLP) and is significantly cheaper.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createPostHogEventsDrain()\n * 2. runtimeConfig.evlog.posthog\n * 3. runtimeConfig.posthog\n * 4. Environment variables: NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY, NUXT_POSTHOG_HOST/POSTHOG_HOST\n *\n * @example\n * ```ts\n * nitroApp.hooks.hook('evlog:drain', createPostHogEventsDrain({\n * eventName: 'server_request',\n * }))\n * ```\n */\nexport function createPostHogEventsDrain(overrides?: Partial<PostHogEventsConfig>) {\n return defineDrain<PostHogEventsConfig>({\n name: 'posthog-events',\n resolve: async () => {\n const config = await resolveAdapterConfig<PostHogEventsConfig>('posthog', POSTHOG_EVENTS_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/posthog-events] Missing apiKey. Set NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY env var or pass to createPostHogEventsDrain()')\n return null\n }\n return config as PostHogEventsConfig\n },\n send: sendBatchToPostHogEvents,\n })\n}\n\n/**\n * Send a single event to PostHog as a custom event.\n *\n * @example\n * ```ts\n * await sendToPostHogEvents(event, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendToPostHogEvents(event: WideEvent, config: PostHogEventsConfig): Promise<void> {\n await sendBatchToPostHogEvents([event], config)\n}\n\n/**\n * Send a batch of events to PostHog as custom events via the `/batch/` API.\n *\n * @example\n * ```ts\n * await sendBatchToPostHogEvents(events, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendBatchToPostHogEvents(events: WideEvent[], config: PostHogEventsConfig): Promise<void> {\n if (events.length === 0) return\n\n const url = `${resolveHost(config)}/batch/`\n const batch = events.map(event => toPostHogEvent(event, config))\n\n await httpPost({\n url,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ api_key: config.apiKey, batch }),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'PostHog',\n })\n}\n"],"mappings":";;;;AAkCA,MAAM,iBAA+C;CACnD;EAAE,KAAK;EAAU,KAAK,CAAC,wBAAwB,kBAAkB;EAAE;CACnE;EAAE,KAAK;EAAQ,KAAK,CAAC,qBAAqB,eAAe;EAAE;CAC3D,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,WAAW;CACnB;AAED,MAAM,wBAA4D;CAChE,GAAG;CACH,EAAE,KAAK,aAAa;CACpB,EAAE,KAAK,cAAc;CACtB;AAED,SAAS,YAAY,QAA+B;AAClD,SAAQ,OAAO,QAAQ,4BAA4B,QAAQ,OAAO,GAAG;;AAGvE,SAAS,aAAa,QAAmC;AAEvD,QAAO;EACL,UAAU,GAFC,YAAY,OAEN,CAAC;EAClB,SAAS,EAAE,eAAe,UAAU,OAAO,UAAU;EACrD,SAAS,OAAO;EAChB,SAAS,OAAO;EACjB;;;;;AAMH,SAAgB,eAAe,OAAkB,QAA2C;CAC1F,MAAM,EAAE,WAAW,OAAO,SAAS,GAAG,SAAS;AAE/C,QAAO;EACL,OAAO,OAAO,aAAa;EAC3B,aAAa,OAAO,eAAe,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS,KAAA,MAAc;EACnG;EACA,YAAY;GACV;GACA;GACA,GAAG;GACJ;EACF;;;;;;;;;;;;;;;;;;;;;;;AA4BH,SAAgB,mBAAmB,WAAoC;AACrE,QAAO,YAA2B;EAChC,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBAAoC,WAAW,gBAAgB,UAAU;AAC9F,OAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,mHAAmH;AACjI,WAAO;;AAET,UAAO;;EAET,MAAM;EACP,CAAC;;;;;;;;;;;;AAaJ,eAAsB,cAAc,OAAkB,QAAsC;AAC1F,OAAM,mBAAmB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;AAa3C,eAAsB,mBAAmB,QAAqB,QAAsC;AAClG,KAAI,OAAO,WAAW,EAAG;AACzB,OAAM,gBAAgB,QAAQ,aAAa,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;AA0BrD,SAAgB,yBAAyB,WAA0C;AACjF,QAAO,YAAiC;EACtC,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBAA0C,WAAW,uBAAuB,UAAU;AAC3G,OAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,gIAAgI;AAC9I,WAAO;;AAET,UAAO;;EAET,MAAM;EACP,CAAC;;;;;;;;;;;;AAaJ,eAAsB,oBAAoB,OAAkB,QAA4C;AACtG,OAAM,yBAAyB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;AAajD,eAAsB,yBAAyB,QAAqB,QAA4C;AAC9G,KAAI,OAAO,WAAW,EAAG;CAEzB,MAAM,MAAM,GAAG,YAAY,OAAO,CAAC;CACnC,MAAM,QAAQ,OAAO,KAAI,UAAS,eAAe,OAAO,OAAO,CAAC;AAEhE,OAAM,SAAS;EACb;EACA,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU;GAAE,SAAS,OAAO;GAAQ;GAAO,CAAC;EACvD,SAAS,OAAO,WAAW;EAC3B,SAAS,OAAO;EAChB,OAAO;EACR,CAAC"}
@@ -1,4 +1,4 @@
1
- import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
1
+ import { F as DrainContext, it as WideEvent } from "../audit-CTIviX3P.mjs";
2
2
  //#region src/adapters/sentry.d.ts
3
3
  interface SentryConfig {
4
4
  /** Sentry DSN */
@@ -1,4 +1,4 @@
1
- import { n as resolveAdapterConfig, t as httpPost } from "../_http-CHSsrWDJ.mjs";
1
+ import { n as resolveAdapterConfig, t as httpPost } from "../_http-BY1e9pwC.mjs";
2
2
  import { t as defineDrain } from "../_drain-CmCtsuF6.mjs";
3
3
  import { t as OTEL_SEVERITY_NUMBER } from "../_severity-CQijvfhU.mjs";
4
4
  //#region src/adapters/sentry.ts