evlog 2.12.0 → 2.14.0

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 (115) hide show
  1. package/README.md +111 -0
  2. package/dist/adapters/axiom.d.mts +1 -1
  3. package/dist/adapters/better-stack.d.mts +1 -1
  4. package/dist/adapters/datadog.d.mts +1 -1
  5. package/dist/adapters/fs.d.mts +1 -1
  6. package/dist/adapters/hyperdx.d.mts +1 -1
  7. package/dist/adapters/otlp.d.mts +1 -1
  8. package/dist/adapters/posthog.d.mts +1 -1
  9. package/dist/adapters/sentry.d.mts +1 -1
  10. package/dist/ai/index.d.mts +106 -5
  11. package/dist/ai/index.d.mts.map +1 -1
  12. package/dist/ai/index.mjs +28 -5
  13. package/dist/ai/index.mjs.map +1 -1
  14. package/dist/audit-d9esRZOK.mjs +1440 -0
  15. package/dist/audit-d9esRZOK.mjs.map +1 -0
  16. package/dist/audit-mUutdf6A.d.mts +1130 -0
  17. package/dist/audit-mUutdf6A.d.mts.map +1 -0
  18. package/dist/better-auth/index.d.mts +220 -0
  19. package/dist/better-auth/index.d.mts.map +1 -0
  20. package/dist/better-auth/index.mjs +205 -0
  21. package/dist/better-auth/index.mjs.map +1 -0
  22. package/dist/browser.d.mts +1 -1
  23. package/dist/elysia/index.d.mts +2 -2
  24. package/dist/elysia/index.d.mts.map +1 -1
  25. package/dist/elysia/index.mjs +16 -4
  26. package/dist/elysia/index.mjs.map +1 -1
  27. package/dist/enrichers.d.mts +1 -1
  28. package/dist/{error-WRz4_F3W.d.mts → error-D1FZI2Kd.d.mts} +2 -2
  29. package/dist/{error-WRz4_F3W.d.mts.map → error-D1FZI2Kd.d.mts.map} +1 -1
  30. package/dist/error.d.mts +1 -1
  31. package/dist/{errors-J2kt7mZh.d.mts → errors-NIXCyk6I.d.mts} +2 -2
  32. package/dist/{errors-J2kt7mZh.d.mts.map → errors-NIXCyk6I.d.mts.map} +1 -1
  33. package/dist/express/index.d.mts +2 -2
  34. package/dist/express/index.d.mts.map +1 -1
  35. package/dist/express/index.mjs +8 -4
  36. package/dist/express/index.mjs.map +1 -1
  37. package/dist/fastify/index.d.mts +2 -2
  38. package/dist/fastify/index.d.mts.map +1 -1
  39. package/dist/fastify/index.mjs +8 -4
  40. package/dist/fastify/index.mjs.map +1 -1
  41. package/dist/fork-CTJXnpl8.mjs +72 -0
  42. package/dist/fork-CTJXnpl8.mjs.map +1 -0
  43. package/dist/headers-D74M0wsg.mjs +30 -0
  44. package/dist/headers-D74M0wsg.mjs.map +1 -0
  45. package/dist/hono/index.d.mts +2 -2
  46. package/dist/hono/index.mjs +2 -1
  47. package/dist/hono/index.mjs.map +1 -1
  48. package/dist/http.d.mts +1 -1
  49. package/dist/index.d.mts +7 -7
  50. package/dist/index.mjs +2 -2
  51. package/dist/{logger-Bm0k3Hf3.d.mts → logger-b3epPH0N.d.mts} +8 -4
  52. package/dist/logger-b3epPH0N.d.mts.map +1 -0
  53. package/dist/logger.d.mts +1 -1
  54. package/dist/logger.mjs +1 -1
  55. package/dist/{headers-ht4yS2mx.mjs → middleware-BWOJ7JI0.mjs} +9 -30
  56. package/dist/middleware-BWOJ7JI0.mjs.map +1 -0
  57. package/dist/{middleware-D_igVy93.d.mts → middleware-BYf26Lfu.d.mts} +14 -3
  58. package/dist/{middleware-D_igVy93.d.mts.map → middleware-BYf26Lfu.d.mts.map} +1 -1
  59. package/dist/nestjs/index.d.mts +2 -2
  60. package/dist/nestjs/index.d.mts.map +1 -1
  61. package/dist/nestjs/index.mjs +8 -4
  62. package/dist/nestjs/index.mjs.map +1 -1
  63. package/dist/next/client.d.mts +1 -1
  64. package/dist/next/index.d.mts +4 -4
  65. package/dist/next/index.d.mts.map +1 -1
  66. package/dist/next/index.mjs +15 -1
  67. package/dist/next/index.mjs.map +1 -1
  68. package/dist/next/instrumentation.d.mts +1 -1
  69. package/dist/next/instrumentation.mjs +1 -1
  70. package/dist/nitro/module.d.mts +2 -2
  71. package/dist/nitro/plugin.mjs +1 -1
  72. package/dist/nitro/v3/index.d.mts +2 -2
  73. package/dist/nitro/v3/module.d.mts +1 -1
  74. package/dist/nitro/v3/plugin.mjs +1 -1
  75. package/dist/nitro/v3/useLogger.d.mts +1 -1
  76. package/dist/{nitro-BeRXZcBd.d.mts → nitro-DenB86W6.d.mts} +2 -2
  77. package/dist/{nitro-BeRXZcBd.d.mts.map → nitro-DenB86W6.d.mts.map} +1 -1
  78. package/dist/nuxt/module.d.mts +1 -1
  79. package/dist/nuxt/module.mjs +1 -1
  80. package/dist/{parseError-DhXS_vzM.d.mts → parseError-BR9pocvY.d.mts} +2 -2
  81. package/dist/parseError-BR9pocvY.d.mts.map +1 -0
  82. package/dist/react-router/index.d.mts +2 -2
  83. package/dist/react-router/index.d.mts.map +1 -1
  84. package/dist/react-router/index.mjs +8 -4
  85. package/dist/react-router/index.mjs.map +1 -1
  86. package/dist/runtime/client/log.d.mts +1 -1
  87. package/dist/runtime/server/routes/_evlog/ingest.post.mjs +1 -1
  88. package/dist/runtime/server/useLogger.d.mts +1 -1
  89. package/dist/runtime/utils/parseError.d.mts +2 -2
  90. package/dist/{storage-DpLJYMoc.mjs → storage-CFGTn37X.mjs} +1 -1
  91. package/dist/{storage-DpLJYMoc.mjs.map → storage-CFGTn37X.mjs.map} +1 -1
  92. package/dist/sveltekit/index.d.mts +2 -2
  93. package/dist/sveltekit/index.d.mts.map +1 -1
  94. package/dist/sveltekit/index.mjs +8 -4
  95. package/dist/sveltekit/index.mjs.map +1 -1
  96. package/dist/toolkit.d.mts +41 -4
  97. package/dist/toolkit.d.mts.map +1 -1
  98. package/dist/toolkit.mjs +5 -3
  99. package/dist/types.d.mts +2 -2
  100. package/dist/{useLogger-Dcj1Nrsa.d.mts → useLogger-C56tDPwf.d.mts} +2 -2
  101. package/dist/{useLogger-Dcj1Nrsa.d.mts.map → useLogger-C56tDPwf.d.mts.map} +1 -1
  102. package/dist/{utils-Bnc95-VC.d.mts → utils-DzGCLRFe.d.mts} +2 -2
  103. package/dist/{utils-Bnc95-VC.d.mts.map → utils-DzGCLRFe.d.mts.map} +1 -1
  104. package/dist/utils.d.mts +1 -1
  105. package/dist/vite/index.d.mts +1 -1
  106. package/dist/workers.d.mts +1 -1
  107. package/dist/workers.mjs +1 -1
  108. package/package.json +16 -3
  109. package/dist/headers-ht4yS2mx.mjs.map +0 -1
  110. package/dist/logger-Bm0k3Hf3.d.mts.map +0 -1
  111. package/dist/logger-DY0X5oQd.mjs +0 -704
  112. package/dist/logger-DY0X5oQd.mjs.map +0 -1
  113. package/dist/parseError-DhXS_vzM.d.mts.map +0 -1
  114. package/dist/types-D5OwxZCw.d.mts +0 -587
  115. package/dist/types-D5OwxZCw.d.mts.map +0 -1
@@ -0,0 +1,1130 @@
1
+ //#region src/types.d.ts
2
+ declare module 'nitropack/types' {
3
+ interface NitroRuntimeHooks {
4
+ /**
5
+ * Tail sampling hook - called before emitting a log.
6
+ * Set `ctx.shouldKeep = true` to force-keep the log regardless of head sampling.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * nitroApp.hooks.hook('evlog:emit:keep', (ctx) => {
11
+ * if (ctx.context.user?.premium) {
12
+ * ctx.shouldKeep = true
13
+ * }
14
+ * })
15
+ * ```
16
+ */
17
+ 'evlog:emit:keep': (ctx: TailSamplingContext) => void | Promise<void>;
18
+ /**
19
+ * Enrichment hook - called after emit, before drain.
20
+ * Use this to enrich the event with derived context (e.g. geo, user agent).
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * nitroApp.hooks.hook('evlog:enrich', (ctx) => {
25
+ * ctx.event.deploymentId = process.env.DEPLOYMENT_ID
26
+ * })
27
+ * ```
28
+ */
29
+ 'evlog:enrich': (ctx: EnrichContext) => void | Promise<void>;
30
+ /**
31
+ * Drain hook - called after emitting a log (fire-and-forget).
32
+ * Use this to send logs to external services like Axiom, Loki, or custom endpoints.
33
+ * Errors are logged but never block the request.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * nitroApp.hooks.hook('evlog:drain', async (ctx) => {
38
+ * await fetch('https://api.axiom.co/v1/datasets/logs/ingest', {
39
+ * method: 'POST',
40
+ * headers: { Authorization: `Bearer ${process.env.AXIOM_TOKEN}` },
41
+ * body: JSON.stringify([ctx.event])
42
+ * })
43
+ * })
44
+ * ```
45
+ */
46
+ 'evlog:drain': (ctx: DrainContext) => void | Promise<void>;
47
+ }
48
+ }
49
+ declare module 'nitro/types' {
50
+ interface NitroRuntimeHooks {
51
+ 'evlog:emit:keep': (ctx: TailSamplingContext) => void | Promise<void>;
52
+ 'evlog:enrich': (ctx: EnrichContext) => void | Promise<void>;
53
+ 'evlog:drain': (ctx: DrainContext) => void | Promise<void>;
54
+ }
55
+ }
56
+ /**
57
+ * Transport configuration for sending client logs to the server
58
+ */
59
+ interface TransportConfig {
60
+ /**
61
+ * Enable sending logs to the server API
62
+ * @default false
63
+ */
64
+ enabled?: boolean;
65
+ /**
66
+ * API endpoint for log ingestion
67
+ * @default '/api/_evlog/ingest'
68
+ */
69
+ endpoint?: string;
70
+ /**
71
+ * Fetch credentials mode
72
+ * @default 'same-origin'
73
+ */
74
+ credentials?: RequestCredentials;
75
+ }
76
+ /**
77
+ * Payload sent from client to server for log ingestion
78
+ */
79
+ interface IngestPayload {
80
+ timestamp: string;
81
+ level: 'info' | 'error' | 'warn' | 'debug';
82
+ [key: string]: unknown;
83
+ }
84
+ /**
85
+ * Auto-redaction configuration for PII protection.
86
+ * Scrubs sensitive data from wide events before console output and draining.
87
+ *
88
+ * Built-in patterns are included by default. Opt out with `builtins: false`
89
+ * or select specific ones with `builtins: ['email', 'creditCard']`.
90
+ */
91
+ interface RedactConfig {
92
+ /** Dot-notation paths to redact (e.g., 'user.email', 'headers.x-forwarded-for') */
93
+ paths?: string[];
94
+ /** Additional regex patterns to match and replace string values anywhere in the event */
95
+ patterns?: RegExp[];
96
+ /**
97
+ * Control built-in PII patterns.
98
+ * - `undefined` / omitted → all built-ins enabled (default)
99
+ * - `false` → no built-ins, only custom `paths`/`patterns`
100
+ * - `['email', 'creditCard', ...]` → only the listed built-ins
101
+ *
102
+ * Available: `'creditCard'`, `'email'`, `'ipv4'`, `'phone'`, `'jwt'`, `'bearer'`, `'iban'`
103
+ */
104
+ builtins?: false | Array<'creditCard' | 'email' | 'ipv4' | 'phone' | 'jwt' | 'bearer' | 'iban'>;
105
+ /**
106
+ * Replacement string used for path-based and custom pattern redaction.
107
+ * Built-in patterns use smart partial masking instead (e.g. `****1111` for credit cards).
108
+ * @default '[REDACTED]'
109
+ */
110
+ replacement?: string;
111
+ /** @internal Resolved masker functions from built-in patterns. Not user-facing. */
112
+ _maskers?: Array<[RegExp, (match: string) => string]>;
113
+ }
114
+ /**
115
+ * Sampling rates per log level (0-100 percentage)
116
+ */
117
+ interface SamplingRates {
118
+ /** Percentage of info logs to keep (0-100). Default: 100 */
119
+ info?: number;
120
+ /** Percentage of warn logs to keep (0-100). Default: 100 */
121
+ warn?: number;
122
+ /** Percentage of debug logs to keep (0-100). Default: 100 */
123
+ debug?: number;
124
+ /** Percentage of error logs to keep (0-100). Default: 100 */
125
+ error?: number;
126
+ }
127
+ /**
128
+ * Tail sampling condition for forcing log retention based on request outcome.
129
+ * All conditions use >= comparison (e.g., status: 400 means status >= 400).
130
+ */
131
+ interface TailSamplingCondition {
132
+ /** Keep if HTTP status >= this value (e.g., 400 for all errors) */
133
+ status?: number;
134
+ /** Keep if request duration >= this value in milliseconds */
135
+ duration?: number;
136
+ /** Keep if path matches this glob pattern (e.g., '/api/critical/**') */
137
+ path?: string;
138
+ }
139
+ /**
140
+ * Context passed to tail sampling evaluation and hooks.
141
+ * Contains request outcome information for sampling decisions.
142
+ */
143
+ interface TailSamplingContext {
144
+ /** HTTP response status code */
145
+ status?: number;
146
+ /** Request duration in milliseconds (raw number) */
147
+ duration?: number;
148
+ /** Request path */
149
+ path?: string;
150
+ /** HTTP method */
151
+ method?: string;
152
+ /** Full accumulated context from the request logger */
153
+ context: Record<string, unknown>;
154
+ /**
155
+ * Set to true in evlog:emit:keep hook to force keep this log.
156
+ * Multiple hooks can set this - if any sets it to true, the log is kept.
157
+ */
158
+ shouldKeep?: boolean;
159
+ }
160
+ /**
161
+ * Context passed to the evlog:enrich hook.
162
+ * Called after emit, before drain.
163
+ */
164
+ interface EnrichContext {
165
+ /** The emitted wide event (mutable). */
166
+ event: WideEvent;
167
+ /** Request metadata (if available) */
168
+ request?: {
169
+ method?: string;
170
+ path: string;
171
+ requestId?: string;
172
+ };
173
+ /** Safe HTTP request headers (sensitive headers filtered out) */
174
+ headers?: Record<string, string>;
175
+ /** Optional response metadata */
176
+ response?: {
177
+ status?: number;
178
+ headers?: Record<string, string>;
179
+ };
180
+ }
181
+ /**
182
+ * Context passed to the evlog:drain hook.
183
+ * Contains the complete wide event and request metadata for external transport.
184
+ */
185
+ interface DrainContext {
186
+ /** The complete wide event to drain */
187
+ event: WideEvent;
188
+ /** Request metadata (if available) */
189
+ request?: {
190
+ method?: string;
191
+ path?: string;
192
+ requestId?: string;
193
+ };
194
+ /** HTTP headers from the original request (useful for correlation with external services) */
195
+ headers?: Record<string, string>;
196
+ }
197
+ /**
198
+ * Sampling configuration for filtering logs
199
+ */
200
+ interface SamplingConfig {
201
+ /**
202
+ * Sampling rates per log level (head sampling).
203
+ * Values are percentages from 0 to 100.
204
+ * Default: 100 for all levels (log everything).
205
+ * Error defaults to 100 even if not specified.
206
+ *
207
+ * @example
208
+ * ```ts
209
+ * sampling: {
210
+ * rates: {
211
+ * info: 10, // Keep 10% of info logs
212
+ * warn: 50, // Keep 50% of warning logs
213
+ * debug: 5, // Keep 5% of debug logs
214
+ * error: 100, // Always keep errors (default)
215
+ * }
216
+ * }
217
+ * ```
218
+ */
219
+ rates?: SamplingRates;
220
+ /**
221
+ * Tail sampling conditions for forcing log retention (OR logic).
222
+ * If ANY condition matches, the log is kept regardless of head sampling.
223
+ * Use the `evlog:emit:keep` Nitro hook for custom conditions.
224
+ *
225
+ * @example
226
+ * ```ts
227
+ * sampling: {
228
+ * rates: { info: 10 }, // Head sampling: keep 10% of info logs
229
+ * keep: [
230
+ * { status: 400 }, // Always keep if status >= 400
231
+ * { duration: 1000 }, // Always keep if duration >= 1000ms
232
+ * { path: '/api/critical/**' }, // Always keep critical paths
233
+ * ]
234
+ * }
235
+ * ```
236
+ */
237
+ keep?: TailSamplingCondition[];
238
+ }
239
+ /**
240
+ * Route-based service configuration
241
+ */
242
+ interface RouteConfig {
243
+ /** Service name to use for routes matching this pattern */
244
+ service: string;
245
+ }
246
+ /**
247
+ * Environment context automatically included in every log event
248
+ */
249
+ interface EnvironmentContext {
250
+ /** Service name (auto-detected from package.json or configurable) */
251
+ service: string;
252
+ /** Environment: 'development', 'production', 'test', etc. */
253
+ environment: 'development' | 'production' | 'test' | string;
254
+ /** Application version (auto-detected from package.json) */
255
+ version?: string;
256
+ /** Git commit hash (auto-detected from CI/CD env vars) */
257
+ commitHash?: string;
258
+ /** Deployment region (auto-detected from cloud provider env vars) */
259
+ region?: string;
260
+ }
261
+ /**
262
+ * Logger configuration options
263
+ */
264
+ interface LoggerConfig {
265
+ /**
266
+ * Enable or disable all logging globally.
267
+ * When false, all emits, tagged logs, and request logger operations become no-ops.
268
+ * @default true
269
+ */
270
+ enabled?: boolean;
271
+ /** Environment context overrides */
272
+ env?: Partial<EnvironmentContext>;
273
+ /** Enable pretty printing (auto-detected: true in dev, false in prod) */
274
+ pretty?: boolean;
275
+ /** Sampling configuration for filtering logs */
276
+ sampling?: SamplingConfig;
277
+ /**
278
+ * Minimum severity for the global `log` API (tagged and object form).
279
+ * Does not apply to `createLogger().emit()` / request wide events (use `sampling` for volume).
280
+ * Order: debug < info < warn < error.
281
+ * @default 'debug' (all levels)
282
+ */
283
+ minLevel?: LogLevel;
284
+ /**
285
+ * When pretty is disabled, emit JSON strings (default) or raw objects.
286
+ * Set to false for environments like Cloudflare Workers that expect objects.
287
+ * @default true
288
+ */
289
+ stringify?: boolean;
290
+ /**
291
+ * Suppress built-in console output.
292
+ * When true, events are still built, sampled, and passed to drains,
293
+ * but nothing is written to console. Use when drains own the output
294
+ * channel (e.g., stdout-based platforms like GCP Cloud Run, AWS Lambda).
295
+ * @default false
296
+ */
297
+ silent?: boolean;
298
+ /**
299
+ * Auto-redaction configuration for PII protection.
300
+ * Scrubs sensitive data from wide events before console output and before any drain.
301
+ *
302
+ * - `true` → enable with all built-in patterns (email, credit card, IPv4, phone, JWT, Bearer, IBAN)
303
+ * - `{ paths, patterns, builtins }` → fine-grained control
304
+ * - `false` → explicitly disable redaction
305
+ *
306
+ * @default true in production, false in development
307
+ *
308
+ * @example
309
+ * ```ts
310
+ * // Disable in production
311
+ * initLogger({ redact: false })
312
+ *
313
+ * // With custom paths on top of built-ins
314
+ * initLogger({
315
+ * redact: {
316
+ * paths: ['user.password', 'headers.authorization'],
317
+ * },
318
+ * })
319
+ *
320
+ * // Only specific built-ins + custom patterns
321
+ * initLogger({
322
+ * redact: {
323
+ * builtins: ['email', 'creditCard'],
324
+ * patterns: [/SECRET_\w+/g],
325
+ * },
326
+ * })
327
+ * ```
328
+ */
329
+ redact?: boolean | RedactConfig;
330
+ /**
331
+ * Drain callback called with every emitted event (fire-and-forget).
332
+ * Use this to send logs to external services outside of Nitro.
333
+ * Compatible with drain adapters (`createAxiomDrain()`) and pipeline-wrapped drains.
334
+ *
335
+ * @example
336
+ * ```ts
337
+ * import { initLogger, log } from 'evlog'
338
+ * import { createAxiomDrain } from 'evlog/axiom'
339
+ *
340
+ * initLogger({
341
+ * drain: createAxiomDrain({ dataset: 'logs', token: '...' }),
342
+ * })
343
+ *
344
+ * log.info({ action: 'user_login' }) // automatically drained
345
+ * ```
346
+ *
347
+ * @example
348
+ * ```ts
349
+ * // With pipeline for batching and retry
350
+ * import { createDrainPipeline } from 'evlog/pipeline'
351
+ *
352
+ * const pipeline = createDrainPipeline({ batch: { size: 25 } })
353
+ * const drain = pipeline(createAxiomDrain({ dataset: 'logs', token: '...' }))
354
+ *
355
+ * initLogger({ drain })
356
+ *
357
+ * // Flush on shutdown
358
+ * process.on('beforeExit', () => drain.flush())
359
+ * ```
360
+ */
361
+ drain?: (ctx: DrainContext) => void | Promise<void>;
362
+ /** @internal Suppress the "silent without drain" warning (used by hook-based frameworks like Nitro/Nuxt) */
363
+ _suppressDrainWarning?: boolean;
364
+ }
365
+ /**
366
+ * Audit actor — who initiated the action.
367
+ *
368
+ * `type` covers the most common actor families. `id` is required and should be
369
+ * a stable identifier (user id, service name, API key id, agent id). `model`,
370
+ * `tools`, `reason`, and `promptId` are filled when `type === 'agent'` and
371
+ * mirror the AI SDK fields already used by `evlog/ai`.
372
+ */
373
+ interface AuditActor {
374
+ type: 'user' | 'system' | 'api' | 'agent';
375
+ id: string;
376
+ displayName?: string;
377
+ email?: string;
378
+ model?: string;
379
+ tools?: string[];
380
+ reason?: string;
381
+ promptId?: string;
382
+ }
383
+ /**
384
+ * Audit target — the resource the action was performed on.
385
+ *
386
+ * `type` is a free-form string (e.g. `'invoice'`, `'user'`, `'subscription'`)
387
+ * narrowed by {@link defineAuditAction}. Additional fields are allowed for
388
+ * resource-specific metadata (e.g. `tenantId`, `path`, `previousOwnerId`).
389
+ */
390
+ interface AuditTarget {
391
+ type: string;
392
+ id: string;
393
+ [key: string]: unknown;
394
+ }
395
+ /**
396
+ * Reserved audit fields on the wide event.
397
+ *
398
+ * Set via `log.audit({ ... })`, `log.set({ audit: { ... } })`, or the
399
+ * standalone `audit({ ... })` helper. Downstream filters on `audit IS NOT NULL`.
400
+ *
401
+ * - `outcome` — `'success' | 'failure' | 'denied'`. `'denied'` records an
402
+ * AuthZ-denied action (often forgotten but exactly what auditors want).
403
+ * - `changes.before/after` — the diff for mutating actions. Use
404
+ * {@link auditDiff} to produce a redact-aware compact JSON Patch.
405
+ * - `causationId` / `correlationId` — chain related actions (admin action →
406
+ * system reactions). Set by callers, propagated by `auditEnricher` when
407
+ * available on the request.
408
+ * - `signature` / `prevHash` — populated by the {@link signed} drain wrapper.
409
+ * Never set by application code.
410
+ * - `idempotencyKey` — derived deterministically by `log.audit()` so retries
411
+ * across drains are safe.
412
+ * - `context` — request/runtime context auto-populated by {@link auditEnricher}
413
+ * (`requestId`, `traceId`, `ip`, `userAgent`, `tenantId`).
414
+ */
415
+ interface AuditFields {
416
+ /** Action name. Convention: `'<resource>.<verb>'`, e.g. `'invoice.refund'`. */
417
+ action: string;
418
+ actor: AuditActor;
419
+ target?: AuditTarget;
420
+ outcome: 'success' | 'failure' | 'denied';
421
+ /** Human-readable explanation, especially required for `outcome: 'denied'`. */
422
+ reason?: string;
423
+ /** Before/after snapshots for mutating actions. */
424
+ changes?: {
425
+ before?: unknown;
426
+ after?: unknown;
427
+ };
428
+ /** ID of the action that caused this one. */
429
+ causationId?: string;
430
+ /** ID shared by every action in the same logical operation. */
431
+ correlationId?: string;
432
+ /** Schema version of the audit envelope. Defaults to `1` when omitted by the caller. */
433
+ version?: number;
434
+ /** Set by `log.audit()` as a stable hash for safe retries across drains. */
435
+ idempotencyKey?: string;
436
+ /** Request/runtime context — populated by `auditEnricher`. */
437
+ context?: {
438
+ requestId?: string;
439
+ traceId?: string;
440
+ ip?: string;
441
+ userAgent?: string;
442
+ tenantId?: string;
443
+ [key: string]: unknown;
444
+ };
445
+ /** HMAC signature of the event when wrapped with `signed({ strategy: 'hmac' })`. */
446
+ signature?: string;
447
+ /** Previous event hash when wrapped with `signed({ strategy: 'hash-chain' })`. */
448
+ prevHash?: string;
449
+ /** Hash of the current event when wrapped with `signed({ strategy: 'hash-chain' })`. */
450
+ hash?: string;
451
+ }
452
+ /**
453
+ * Base structure for all wide events.
454
+ *
455
+ * Augment via `declare module 'evlog'` to add app-specific top-level fields.
456
+ * `audit` is reserved for {@link AuditFields}.
457
+ */
458
+ interface BaseWideEvent {
459
+ timestamp: string;
460
+ level: 'info' | 'error' | 'warn' | 'debug';
461
+ service: string;
462
+ environment: string;
463
+ version?: string;
464
+ commitHash?: string;
465
+ region?: string;
466
+ audit?: AuditFields;
467
+ }
468
+ /**
469
+ * Wide event with arbitrary additional fields
470
+ */
471
+ type WideEvent = BaseWideEvent & Record<string, unknown>;
472
+ /**
473
+ * Recursively makes all properties optional.
474
+ * Arrays are kept as-is (not deeply partial).
475
+ */
476
+ type DeepPartial<T> = T extends Array<unknown> ? T : T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;
477
+ /**
478
+ * Fields set internally by the evlog plugin (status, service, etc.).
479
+ * These are always accepted by `set()` regardless of the user-defined field type.
480
+ */
481
+ interface InternalFields {
482
+ status?: number;
483
+ service?: string;
484
+ requestLogs?: RequestLogEntry[];
485
+ /** Label for a forked background wide event (child operation name). */
486
+ operation?: string;
487
+ /** Parent request's `requestId` when this event was produced by `log.fork()`. */
488
+ _parentRequestId?: string;
489
+ }
490
+ /**
491
+ * Request-scoped log entry captured during a request lifecycle.
492
+ */
493
+ interface RequestLogEntry {
494
+ level: 'info' | 'warn';
495
+ message: string;
496
+ timestamp: string;
497
+ }
498
+ /**
499
+ * Resolved context type for logger methods.
500
+ * User fields are deeply partial (matching deep merge behavior) with internal
501
+ * field keys omitted to avoid intersection conflicts, then internal fields
502
+ * are added back with their canonical types.
503
+ */
504
+ type FieldContext<T extends object = Record<string, unknown>> = DeepPartial<Omit<T, keyof InternalFields>> & InternalFields;
505
+ /**
506
+ * Request-scoped logger for building wide events
507
+ *
508
+ * After {@link RequestLogger.emit} runs (including when head sampling drops the event),
509
+ * the logger is **sealed**: further `set`, `error`, `info`, and `warn` calls log a
510
+ * console warning and do not mutate the wide event. A second `emit` is ignored with
511
+ * a warning. Use {@link RequestLogger.fork} on supported integrations for intentional
512
+ * background work that needs its own wide event.
513
+ *
514
+ * `fork` is only present on request loggers from integrations that attach it (not on
515
+ * standalone `createLogger()` instances).
516
+ *
517
+ * @example
518
+ * ```ts
519
+ * const logger = useLogger(event)
520
+ * logger.set({ user: { id: '123' } })
521
+ * logger.set({ cart: { items: 3 } })
522
+ * // emit() is called automatically by the plugin
523
+ * ```
524
+ *
525
+ * @example
526
+ * ```ts
527
+ * // With typed fields for compile-time safety
528
+ * interface MyFields {
529
+ * user: { id: string; plan: string }
530
+ * action: string
531
+ * }
532
+ * const logger = useLogger<MyFields>(event)
533
+ * logger.set({ user: { id: '123', plan: 'pro' } }) // OK
534
+ * logger.set({ user: { id: '123' } }) // OK (deep partial)
535
+ * logger.set({ action: 'checkout' }) // OK
536
+ * logger.set({ status: 200 }) // OK (internal field)
537
+ * logger.set({ account: '...' }) // TS error
538
+ * ```
539
+ */
540
+ interface RequestLogger<T extends object = Record<string, unknown>> {
541
+ /**
542
+ * Add context to the wide event. Plain objects are merged recursively.
543
+ * When both the existing and incoming values for a key are arrays, elements are
544
+ * concatenated (existing order preserved, new elements appended). Otherwise the
545
+ * new value replaces the old one (including when only one side is an array).
546
+ *
547
+ * No-ops with a console warning after the wide event has been emitted.
548
+ */
549
+ set: (context: FieldContext<T>) => void;
550
+ /**
551
+ * Log an error and capture its details.
552
+ *
553
+ * No-ops with a console warning after the wide event has been emitted.
554
+ */
555
+ error: (error: Error | string, context?: FieldContext<T>) => void;
556
+ /**
557
+ * Capture an informational message inside the request wide event.
558
+ *
559
+ * No-ops with a console warning after the wide event has been emitted.
560
+ */
561
+ info: (message: string, context?: FieldContext<T>) => void;
562
+ /**
563
+ * Capture a warning message inside the request wide event.
564
+ *
565
+ * No-ops with a console warning after the wide event has been emitted.
566
+ */
567
+ warn: (message: string, context?: FieldContext<T>) => void;
568
+ /**
569
+ * Emit the final wide event with all accumulated context.
570
+ * Returns the emitted WideEvent, or null if the log was sampled out.
571
+ *
572
+ * Seals the logger: after this returns (including when the return value is `null`
573
+ * due to sampling), further mutations are ignored with warnings.
574
+ */
575
+ emit: (overrides?: FieldContext<T> & {
576
+ _forceKeep?: boolean;
577
+ }) => WideEvent | null;
578
+ /**
579
+ * Get the current accumulated context
580
+ */
581
+ getContext: () => FieldContext<T> & Record<string, unknown>;
582
+ /**
583
+ * Run async (or sync) work in a **child** request logger scope so `useLogger()`
584
+ * resolves to the child logger while `fn` runs. The child emits its own wide event
585
+ * when `fn` settles, with `operation` and `_parentRequestId` set for correlation.
586
+ * Only available on integrations that attach this method (Express, Fastify, NestJS,
587
+ * SvelteKit, React Router, Next.js `withEvlog`, Elysia — see docs).
588
+ *
589
+ * @param label - Value stored as `operation` on the child wide event.
590
+ * @param fn - Function to run; may return a Promise. Errors are captured on the
591
+ * child logger and emitted.
592
+ *
593
+ * @example
594
+ * ```ts
595
+ * log.fork('process_order', async () => {
596
+ * const log = useLogger()
597
+ * log.set({ step: 'charged' })
598
+ * })
599
+ * ```
600
+ */
601
+ fork?: (label: string, fn: () => void | Promise<void>) => void;
602
+ /**
603
+ * Record an audit event on this wide event. Strictly equivalent to
604
+ * `log.set({ audit: { ... } })` plus tail-sample force-keep.
605
+ *
606
+ * Use `log.audit.deny(reason, fields)` for AuthZ-denied actions — most teams
607
+ * forget to log denials but they are exactly what auditors and security teams
608
+ * ask for.
609
+ *
610
+ * Available on every logger returned by `createLogger()` / `createRequestLogger()`
611
+ * and on framework loggers exposed via `useLogger()` / `c.get('log')` etc.
612
+ *
613
+ * @example
614
+ * ```ts
615
+ * log.audit({
616
+ * action: 'invoice.refund',
617
+ * actor: { type: 'user', id: user.id, email: user.email },
618
+ * target: { type: 'invoice', id: 'inv_889' },
619
+ * outcome: 'success',
620
+ * reason: 'Customer requested refund',
621
+ * })
622
+ * ```
623
+ */
624
+ audit?: AuditLoggerMethod;
625
+ }
626
+ /** @internal Forward-declaration to avoid a circular import with `audit.ts`. */
627
+ interface AuditLoggerMethod {
628
+ (input: AuditInput): void;
629
+ deny: (reason: string, input: Omit<AuditInput, 'outcome' | 'reason'>) => void;
630
+ }
631
+ /**
632
+ * Log level type
633
+ */
634
+ type LogLevel = 'info' | 'error' | 'warn' | 'debug';
635
+ /**
636
+ * Simple logging API - as easy as console.log
637
+ *
638
+ * @example
639
+ * ```ts
640
+ * log.info('auth', 'User logged in')
641
+ * log.error({ action: 'payment', error: 'failed' })
642
+ * ```
643
+ */
644
+ interface Log {
645
+ /**
646
+ * Log an info message or wide event
647
+ * @example log.info('auth', 'User logged in')
648
+ * @example log.info({ action: 'login', userId: '123' })
649
+ */
650
+ info(tag: string, message: string): void;
651
+ info(event: Record<string, unknown>): void;
652
+ /**
653
+ * Log an error message or wide event
654
+ * @example log.error('payment', 'Payment failed')
655
+ * @example log.error({ action: 'payment', error: 'declined' })
656
+ */
657
+ error(tag: string, message: string): void;
658
+ error(event: Record<string, unknown>): void;
659
+ /**
660
+ * Log a warning message or wide event
661
+ * @example log.warn('api', 'Rate limit approaching')
662
+ * @example log.warn({ action: 'api', remaining: 10 })
663
+ */
664
+ warn(tag: string, message: string): void;
665
+ warn(event: Record<string, unknown>): void;
666
+ /**
667
+ * Log a debug message or wide event
668
+ * @example log.debug('cache', 'Cache miss')
669
+ * @example log.debug({ action: 'cache', key: 'user_123' })
670
+ */
671
+ debug(tag: string, message: string): void;
672
+ debug(event: Record<string, unknown>): void;
673
+ }
674
+ /**
675
+ * Error options for creating structured errors
676
+ */
677
+ interface ErrorOptions {
678
+ /** What actually happened */
679
+ message: string;
680
+ /** HTTP status code (default: 500) */
681
+ status?: number;
682
+ /** Why this error occurred */
683
+ why?: string;
684
+ /** How to fix this issue */
685
+ fix?: string;
686
+ /** Link to documentation or more information */
687
+ link?: string;
688
+ /** The original error that caused this */
689
+ cause?: Error;
690
+ /**
691
+ * Backend-only diagnostic context (auditing, support, debugging).
692
+ * Never included in HTTP responses or `EvlogError#toJSON`; included in wide events when the error is passed to `log.error()`.
693
+ */
694
+ internal?: Record<string, unknown>;
695
+ }
696
+ /**
697
+ * Options for creating a request logger
698
+ */
699
+ interface RequestLoggerOptions {
700
+ method?: string;
701
+ path?: string;
702
+ requestId?: string;
703
+ }
704
+ /**
705
+ * H3 event context with evlog logger attached
706
+ */
707
+ interface H3EventContext {
708
+ log?: RequestLogger;
709
+ requestId?: string;
710
+ status?: number;
711
+ /** Internal: start time for duration calculation in tail sampling */
712
+ _evlogStartTime?: number;
713
+ /** Internal: flag to prevent double emission on errors */
714
+ _evlogEmitted?: boolean;
715
+ /** Internal: whether the route matched shouldLog filtering (emit-time guard) */
716
+ _evlogShouldEmit?: boolean;
717
+ [key: string]: unknown;
718
+ }
719
+ /**
720
+ * Server event type for Nitro/h3 handlers
721
+ */
722
+ interface ServerEvent {
723
+ method: string;
724
+ path: string;
725
+ context: H3EventContext & {
726
+ /** Cloudflare Workers context (available when deployed to CF Workers) */cloudflare?: {
727
+ context: {
728
+ waitUntil: (promise: Promise<unknown>) => void;
729
+ };
730
+ }; /** Vercel Edge context (available when deployed to Vercel Edge) */
731
+ waitUntil?: (promise: Promise<unknown>) => void;
732
+ };
733
+ node?: {
734
+ res?: {
735
+ statusCode?: number;
736
+ };
737
+ };
738
+ response?: Response;
739
+ }
740
+ /**
741
+ * Parsed evlog error with all fields at the top level
742
+ */
743
+ interface ParsedError {
744
+ message: string;
745
+ status: number;
746
+ why?: string;
747
+ fix?: string;
748
+ link?: string;
749
+ raw: unknown;
750
+ }
751
+ //#endregion
752
+ //#region src/audit.d.ts
753
+ /**
754
+ * Current version of the audit envelope. Bumped when `AuditFields` evolves
755
+ * in a backward-incompatible way so downstream pipelines can branch on it.
756
+ */
757
+ declare const AUDIT_SCHEMA_VERSION = 1;
758
+ /**
759
+ * Input accepted by `log.audit()`, `audit()`, and `withAudit()`.
760
+ *
761
+ * `outcome` defaults to `'success'`. Internal fields populated by the audit
762
+ * pipeline (`idempotencyKey`, `context`, `signature`, `prevHash`, `hash`) are
763
+ * stripped — pass them through `log.set({ audit })` if you really need to.
764
+ */
765
+ interface AuditInput {
766
+ action: string;
767
+ actor: AuditActor;
768
+ target?: AuditTarget;
769
+ outcome?: AuditFields['outcome'];
770
+ reason?: string;
771
+ changes?: AuditFields['changes'];
772
+ causationId?: string;
773
+ correlationId?: string;
774
+ version?: number;
775
+ }
776
+ /**
777
+ * Build a normalised {@link AuditFields} from caller input. Defaults:
778
+ * - `outcome` → `'success'`
779
+ * - `version` → {@link AUDIT_SCHEMA_VERSION}
780
+ *
781
+ * `idempotencyKey` is filled at emit time with the event timestamp so retries
782
+ * stay deterministic.
783
+ */
784
+ declare function buildAuditFields(input: AuditInput): AuditFields;
785
+ /**
786
+ * Add audit semantics to an existing {@link RequestLogger}.
787
+ *
788
+ * Mutates the logger in place by adding an `audit` method (with a `.deny()`
789
+ * sub-method). Strictly equivalent to calling `log.set({ audit: ... })` plus
790
+ * `_forceKeep` on emit. Idempotent: calling twice on the same logger only
791
+ * attaches the methods once.
792
+ *
793
+ * @example
794
+ * ```ts
795
+ * const log = withAuditMethods(createLogger())
796
+ * log.audit({
797
+ * action: 'invoice.refund',
798
+ * actor: { type: 'user', id: user.id },
799
+ * target: { type: 'invoice', id: 'inv_889' },
800
+ * })
801
+ * ```
802
+ */
803
+ declare function withAuditMethods<T extends object = Record<string, unknown>>(logger: RequestLogger<T>): AuditableLogger<T>;
804
+ /**
805
+ * Logger augmented with `.audit()` / `.audit.deny()` helpers.
806
+ */
807
+ type AuditableLogger<T extends object = Record<string, unknown>> = RequestLogger<T> & {
808
+ audit: AuditMethod<T>;
809
+ };
810
+ /** Method shape attached to {@link AuditableLogger}. */
811
+ interface AuditMethod<T extends object = Record<string, unknown>> {
812
+ (input: AuditInput): void;
813
+ /**
814
+ * Record an AuthZ-denied action. Forces `outcome: 'denied'` and requires
815
+ * a human-readable `reason`. Most teams forget to log denials — they are
816
+ * exactly what auditors and security teams ask for.
817
+ */
818
+ deny: (reason: string, input: Omit<AuditInput, 'outcome' | 'reason'>) => void;
819
+ }
820
+ /**
821
+ * Standalone audit emitter for non-request contexts (jobs, scripts, CLIs).
822
+ *
823
+ * Creates a one-shot logger, sets the audit fields, and emits immediately.
824
+ * The event is force-kept past tail sampling. Returns the emitted wide event,
825
+ * or `null` if logging is globally disabled.
826
+ *
827
+ * @example
828
+ * ```ts
829
+ * import { audit } from 'evlog'
830
+ *
831
+ * audit({
832
+ * action: 'cron.cleanup',
833
+ * actor: { type: 'system', id: 'cron' },
834
+ * target: { type: 'job', id: 'cleanup-stale-sessions' },
835
+ * outcome: 'success',
836
+ * })
837
+ * ```
838
+ */
839
+ declare function audit(input: AuditInput): WideEvent | null;
840
+ /**
841
+ * Wrap a function so its outcome (success / failure / denied) is automatically
842
+ * audited.
843
+ *
844
+ * Behaviour:
845
+ * - If `fn` resolves, an audit event with `outcome: 'success'` is emitted.
846
+ * - If `fn` throws an `EvlogError` (or any error) with `status === 403`, the
847
+ * audit event is recorded as `'denied'` with the error message as `reason`.
848
+ * - Any other thrown error produces `outcome: 'failure'` and re-throws.
849
+ *
850
+ * Use {@link AuditDeniedError} to signal denial without an HTTP status.
851
+ *
852
+ * @example
853
+ * ```ts
854
+ * const refundInvoice = withAudit(
855
+ * { action: 'invoice.refund', target: (input) => ({ type: 'invoice', id: input.id }) },
856
+ * async (input: { id: string }, ctx: { actor: AuditActor }) => {
857
+ * await db.invoices.refund(input.id)
858
+ * }
859
+ * )
860
+ *
861
+ * await refundInvoice({ id: 'inv_889' }, { actor: { type: 'user', id: user.id } })
862
+ * ```
863
+ */
864
+ declare function withAudit<TInput, TOutput>(options: WithAuditOptions<TInput>, fn: (input: TInput, ctx: WithAuditContext) => Promise<TOutput> | TOutput): (input: TInput, ctx: WithAuditContext) => Promise<TOutput>;
865
+ /**
866
+ * Throw inside a {@link withAudit} body to mark the action as `outcome: 'denied'`
867
+ * regardless of the underlying HTTP status. The constructor message becomes the
868
+ * audit `reason`.
869
+ */
870
+ declare class AuditDeniedError extends Error {
871
+ constructor(reason: string);
872
+ }
873
+ /** Options for {@link withAudit}. `target` may be derived from the input. */
874
+ interface WithAuditOptions<TInput> {
875
+ action: string;
876
+ target?: AuditTarget | ((input: TInput) => AuditTarget | undefined);
877
+ }
878
+ /**
879
+ * Runtime context required by a {@link withAudit}-wrapped function.
880
+ * The actor is always required; correlation IDs are optional.
881
+ */
882
+ interface WithAuditContext {
883
+ actor: AuditActor;
884
+ causationId?: string;
885
+ correlationId?: string;
886
+ }
887
+ /**
888
+ * Compute a compact, redact-aware diff between two objects for the
889
+ * `changes` field. Output is a JSON Patch-style array (RFC 6902 subset:
890
+ * `add`, `remove`, `replace`) — small enough to ship over the wire.
891
+ *
892
+ * Object keys whose name matches one of the `redactPaths` (dot-notation, e.g.
893
+ * `'user.password'`, `'card.cvv'`) are replaced with `'[REDACTED]'` so PII
894
+ * never leaks through the diff.
895
+ *
896
+ * @example
897
+ * ```ts
898
+ * log.audit({
899
+ * action: 'user.update',
900
+ * actor: { type: 'user', id: user.id },
901
+ * target: { type: 'user', id: 'usr_42' },
902
+ * changes: auditDiff(before, after, { redactPaths: ['password'] }),
903
+ * })
904
+ * ```
905
+ */
906
+ declare function auditDiff(before: unknown, after: unknown, options?: AuditDiffOptions): {
907
+ before?: unknown;
908
+ after?: unknown;
909
+ patch: AuditPatchOp[];
910
+ };
911
+ /** Single JSON Patch operation produced by {@link auditDiff}. */
912
+ interface AuditPatchOp {
913
+ op: 'add' | 'remove' | 'replace';
914
+ path: string;
915
+ value?: unknown;
916
+ }
917
+ /** Options for {@link auditDiff}. */
918
+ interface AuditDiffOptions {
919
+ /** Object keys (dot-notation) whose values should be replaced with `[REDACTED]`. */
920
+ redactPaths?: string[];
921
+ /** Custom replacement string. @default '[REDACTED]' */
922
+ replacement?: string;
923
+ /** Include the full redacted `before` snapshot alongside the patch. */
924
+ includeBefore?: boolean;
925
+ /** Include the full redacted `after` snapshot alongside the patch. */
926
+ includeAfter?: boolean;
927
+ }
928
+ /**
929
+ * Define a typed audit action with an optional fixed target type.
930
+ *
931
+ * Returns a curried helper that fills in the action name (and target shape
932
+ * if provided) so call sites stay terse and the action set is discoverable
933
+ * in one place.
934
+ *
935
+ * @example
936
+ * ```ts
937
+ * const refund = defineAuditAction('invoice.refund', { target: 'invoice' })
938
+ *
939
+ * log.audit(refund({
940
+ * actor: { type: 'user', id: user.id },
941
+ * target: { id: 'inv_889' }, // type inferred as 'invoice'
942
+ * outcome: 'success',
943
+ * }))
944
+ * ```
945
+ */
946
+ declare function defineAuditAction<TTargetType extends string | undefined = undefined>(action: string, options?: {
947
+ target?: TTargetType;
948
+ }): DefinedAuditAction<TTargetType>;
949
+ /**
950
+ * Return type of {@link defineAuditAction}. Accepts a partial input (no
951
+ * `action`, target type pre-filled when provided).
952
+ */
953
+ type DefinedAuditAction<TTargetType extends string | undefined> = (input: TTargetType extends string ? Omit<AuditInput, 'action' | 'target'> & {
954
+ target?: Omit<AuditTarget, 'type'> & {
955
+ type?: TTargetType;
956
+ };
957
+ } : Omit<AuditInput, 'action'>) => AuditInput;
958
+ /**
959
+ * Test helper that captures every audit event emitted while it is active.
960
+ *
961
+ * Returns `{ events, restore, expect }`:
962
+ * - `events` — live array of captured `AuditFields`, populated as audits fire.
963
+ * - `restore()` — uninstall the collector. Call from `afterEach()`.
964
+ * - `expect.toIncludeAuditOf(matcher)` — assertion helper used inside `expect`
965
+ * blocks, returns `true` if at least one captured event matches.
966
+ *
967
+ * Only captures audits going through `log.audit()` and the standalone
968
+ * `audit()` function. Events emitted via raw `log.set({ audit })` skip the
969
+ * collector by design — wrap them with `log.audit()` to make them visible to
970
+ * tests.
971
+ *
972
+ * @example
973
+ * ```ts
974
+ * const captured = mockAudit()
975
+ * await refundInvoice('inv_889')
976
+ * expect(captured.events).toHaveLength(1)
977
+ * expect(captured.toIncludeAuditOf({ action: 'invoice.refund' })).toBe(true)
978
+ * captured.restore()
979
+ * ```
980
+ */
981
+ declare function mockAudit(): MockAudit;
982
+ /** Result of {@link mockAudit}. */
983
+ interface MockAudit {
984
+ events: AuditFields[];
985
+ restore: () => void;
986
+ toIncludeAuditOf: (matcher: AuditMatcher) => boolean;
987
+ }
988
+ /** Partial structural matcher for {@link MockAudit.toIncludeAuditOf}. */
989
+ interface AuditMatcher {
990
+ action?: string | RegExp;
991
+ outcome?: AuditFields['outcome'];
992
+ actor?: Partial<AuditActor>;
993
+ target?: Partial<AuditTarget>;
994
+ }
995
+ /** Shape of the optional better-auth bridge for the audit enricher. */
996
+ interface AuditEnricherBetterAuthBridge {
997
+ /** Read the current authenticated session for this request, if any. */
998
+ getSession: (ctx: EnrichContext) => Promise<AuditActor | null | undefined> | AuditActor | null | undefined;
999
+ }
1000
+ /** Options for {@link auditEnricher}. */
1001
+ interface AuditEnricherOptions {
1002
+ /**
1003
+ * Resolve the tenant id for the current request. The result is stored at
1004
+ * `event.audit.context.tenantId`. Multi-tenant SaaS gets isolation by default.
1005
+ */
1006
+ tenantId?: (ctx: EnrichContext) => string | undefined;
1007
+ /**
1008
+ * Bridge to populate `event.audit.actor` from the authenticated session.
1009
+ * Only used when the application has not already filled `actor`.
1010
+ */
1011
+ bridge?: AuditEnricherBetterAuthBridge;
1012
+ /** When true, overwrite existing context fields. @default false */
1013
+ overwrite?: boolean;
1014
+ }
1015
+ /**
1016
+ * Enrich audit-bearing wide events with request, runtime, and tenant context.
1017
+ *
1018
+ * Runs only when `event.audit` is present — every other event passes through
1019
+ * untouched. Populates:
1020
+ * - `event.audit.context.requestId` from `ctx.request.requestId`
1021
+ * - `event.audit.context.traceId` from `event.traceId`
1022
+ * - `event.audit.context.ip` from `x-forwarded-for` / `x-real-ip`
1023
+ * - `event.audit.context.userAgent` from `user-agent`
1024
+ * - `event.audit.context.tenantId` from `options.tenantId(ctx)`
1025
+ *
1026
+ * Optionally fills `event.audit.actor` from the better-auth bridge when the
1027
+ * caller did not provide one. Anything else (custom actor strategies,
1028
+ * extra context) belongs in a custom enricher — replace this one entirely.
1029
+ */
1030
+ declare function auditEnricher(options?: AuditEnricherOptions): (ctx: EnrichContext) => void | Promise<void>;
1031
+ /** Options accepted by {@link auditOnly}. */
1032
+ interface AuditOnlyOptions {
1033
+ /**
1034
+ * When true, the wrapper awaits the wrapped drain so the event is flushed
1035
+ * before the request resolves. Use for crash-safe audit storage.
1036
+ * @default false
1037
+ */
1038
+ await?: boolean;
1039
+ }
1040
+ /** Drain function signature accepted by all wrappers. Matches `LoggerConfig['drain']`. */
1041
+ type DrainFn = (ctx: DrainContext) => void | Promise<void>;
1042
+ /**
1043
+ * Wrap any drain so it only receives events that carry an `audit` field.
1044
+ *
1045
+ * Use to route audit events to dedicated storage (separate Axiom dataset,
1046
+ * append-only Postgres table, FS journal) without affecting your main drain.
1047
+ *
1048
+ * Per-sink failure isolation comes from `initLogger({ drain: [...] })`: each
1049
+ * drain in the array is invoked independently, so a crashed Axiom call never
1050
+ * blocks the FS audit drain.
1051
+ *
1052
+ * @example
1053
+ * ```ts
1054
+ * import { initLogger, auditOnly } from 'evlog'
1055
+ * import { createAxiomDrain } from 'evlog/axiom'
1056
+ * import { createFsDrain } from 'evlog/fs'
1057
+ *
1058
+ * initLogger({
1059
+ * drain: [
1060
+ * createAxiomDrain({ dataset: 'logs' }),
1061
+ * auditOnly(createFsDrain({ dir: '.audit' }), { await: true }),
1062
+ * ],
1063
+ * })
1064
+ * ```
1065
+ */
1066
+ declare function auditOnly(drain: DrainFn, options?: AuditOnlyOptions): DrainFn;
1067
+ /** Pluggable persistence for the hash-chain state. */
1068
+ interface SignedChainState {
1069
+ /** Load the previous hash from durable storage, or `null` on first run. */
1070
+ load: () => Promise<string | null> | string | null;
1071
+ /** Persist the latest hash so the chain survives process restarts. */
1072
+ save: (hash: string) => Promise<void> | void;
1073
+ }
1074
+ /** Options for {@link signed}. Pick a strategy at construction time. */
1075
+ type SignedOptions = {
1076
+ strategy: 'hmac';
1077
+ secret: string;
1078
+ algorithm?: 'sha256' | 'sha512';
1079
+ } | {
1080
+ strategy: 'hash-chain';
1081
+ state?: SignedChainState;
1082
+ algorithm?: 'sha256' | 'sha512';
1083
+ };
1084
+ /**
1085
+ * Wrap a drain so every event passing through gains tamper-evident integrity.
1086
+ *
1087
+ * - `'hmac'` — adds `event.audit.signature` (HMAC of the canonical event).
1088
+ * - `'hash-chain'` — adds `event.audit.prevHash` and `event.audit.hash` so the
1089
+ * sequence of events forms a verifiable chain. State persists in memory
1090
+ * by default; pass a `state: { load, save }` for cross-process / durable
1091
+ * chains (Redis, file, Postgres).
1092
+ *
1093
+ * The signature is computed before the event is forwarded to the wrapped
1094
+ * drain — combine with {@link auditOnly} when you only want integrity for
1095
+ * audit events.
1096
+ *
1097
+ * @example
1098
+ * ```ts
1099
+ * import { initLogger, auditOnly, signed } from 'evlog'
1100
+ * import { createFsDrain } from 'evlog/fs'
1101
+ *
1102
+ * initLogger({
1103
+ * drain: auditOnly(
1104
+ * signed(createFsDrain({ dir: '.audit' }), { strategy: 'hash-chain' }),
1105
+ * { await: true },
1106
+ * ),
1107
+ * })
1108
+ * ```
1109
+ */
1110
+ declare function signed(drain: DrainFn, options: SignedOptions): DrainFn;
1111
+ /**
1112
+ * Strict redact preset for audit events.
1113
+ *
1114
+ * Combine with the user's existing redact configuration via spread:
1115
+ * `initLogger({ redact: { paths: [...auditRedactPreset.paths!, ...mine] } })`.
1116
+ *
1117
+ * Hardens PII handling:
1118
+ * - Drops `Authorization` and `Cookie` headers anywhere they appear.
1119
+ * - Drops common credential field names (`password`, `passwordHash`, `token`,
1120
+ * `apiKey`, `secret`, `accessToken`, `refreshToken`, `cardNumber`, `cvv`,
1121
+ * `ssn`).
1122
+ *
1123
+ * Built-in pattern maskers (email, credit card, …) keep their default
1124
+ * behaviour — partial masking, not full redaction — so audit trails retain
1125
+ * enough signal to be useful.
1126
+ */
1127
+ declare const auditRedactPreset: RedactConfig;
1128
+ //#endregion
1129
+ export { SamplingRates as $, AuditFields as A, H3EventContext as B, buildAuditFields as C, withAudit as D, signed as E, DrainContext as F, LoggerConfig as G, InternalFields as H, EnrichContext as I, RequestLogEntry as J, ParsedError as K, EnvironmentContext as L, AuditTarget as M, BaseWideEvent as N, withAuditMethods as O, DeepPartial as P, SamplingConfig as Q, ErrorOptions as R, auditRedactPreset as S, mockAudit as T, Log as U, IngestPayload as V, LogLevel as W, RequestLoggerOptions as X, RequestLogger as Y, RouteConfig as Z, WithAuditOptions as _, AuditInput as a, auditEnricher as b, AuditOnlyOptions as c, DefinedAuditAction as d, ServerEvent as et, DrainFn as f, WithAuditContext as g, SignedOptions as h, AuditEnricherOptions as i, WideEvent as it, AuditLoggerMethod as j, AuditActor as k, AuditPatchOp as l, SignedChainState as m, AuditDeniedError as n, TailSamplingContext as nt, AuditMatcher as o, MockAudit as p, RedactConfig as q, AuditDiffOptions as r, TransportConfig as rt, AuditMethod as s, AUDIT_SCHEMA_VERSION as t, TailSamplingCondition as tt, AuditableLogger as u, audit as v, defineAuditAction as w, auditOnly as x, auditDiff as y, FieldContext as z };
1130
+ //# sourceMappingURL=audit-mUutdf6A.d.mts.map