import { AsyncLocalStorage } from 'node:async_hooks'; import { randomBytes, randomUUID } from 'node:crypto'; /** * Request-scoped trace context (P9 observability). A module-level AsyncLocalStorage * so singleton services can read the current request's trace id via `currentTraceId()` * WITHOUT threading it through every method or constructor. The middleware runs each * request inside `runWithTrace(...)`; async projectors run outside a request and * simply see `undefined` (their events already carry the trace from the emitting call). */ export interface TraceStore { traceId: string; correlationId?: string; } const als = new AsyncLocalStorage(); export function runWithTrace(traceId: string, fn: () => T): T { return als.run({ traceId }, fn); } export function currentTraceId(): string | undefined { return als.getStore()?.traceId; } /** A fresh trace id (used when no inbound trace context is present). */ export function newTraceId(): string { return randomUUID(); } /** W3C traceparent for a trace id: 00-<32 hex>-<16 hex span>-01. */ export function toTraceparent(traceId: string): string { const trace = traceId.replace(/-/g, '').padEnd(32, '0').slice(0, 32); const span = randomBytes(8).toString('hex'); return `00-${trace}-${span}-01`; } /** Extract a trace id from an inbound traceparent header (the 2nd field), else undefined. */ export function traceIdFromTraceparent(traceparent?: string): string | undefined { if (!traceparent) return undefined; const parts = traceparent.split('-'); return parts.length >= 3 && /^[0-9a-f]{32}$/i.test(parts[1]) ? parts[1] : undefined; }