logixia 1.13.0 → 1.13.3
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.
- package/README.md +9 -0
- package/dist/{async-context-CwBNMm2E.mjs → async-context-BzJrbfQs.mjs} +8 -4
- package/dist/async-context-BzJrbfQs.mjs.map +1 -0
- package/dist/{async-context-CSHWoHMB.js → async-context-DyQQYq3I.js} +8 -4
- package/dist/async-context-DyQQYq3I.js.map +1 -0
- package/dist/{azure-monitor.transport-CG7ClkYj.d.ts → azure-monitor.transport-BDFO4F-i.d.ts} +2 -2
- package/dist/{azure-monitor.transport-CG7ClkYj.d.ts.map → azure-monitor.transport-BDFO4F-i.d.ts.map} +1 -1
- package/dist/{azure-monitor.transport-Cm74kXZS.d.mts → azure-monitor.transport-D5wQEH04.d.mts} +2 -2
- package/dist/{azure-monitor.transport-Cm74kXZS.d.mts.map → azure-monitor.transport-D5wQEH04.d.mts.map} +1 -1
- package/dist/azure.d.mts +2 -2
- package/dist/azure.d.ts +2 -2
- package/dist/cli/index.js +98 -52
- package/dist/cloudwatch.d.mts +2 -2
- package/dist/cloudwatch.d.ts +2 -2
- package/dist/cloudwatch.js +2 -1
- package/dist/cloudwatch.mjs +2 -1
- package/dist/{cloudwatch.transport-CGHY4buE.d.mts → cloudwatch.transport-B00st6Xv.d.ts} +9 -2
- package/dist/cloudwatch.transport-B00st6Xv.d.ts.map +1 -0
- package/dist/{cloudwatch.transport-pNBNWJLt.js → cloudwatch.transport-BO1ahxw-.js} +26 -19
- package/dist/cloudwatch.transport-BO1ahxw-.js.map +1 -0
- package/dist/{cloudwatch.transport-DK_8cKd3.d.ts → cloudwatch.transport-Blc7Gtxt.d.mts} +9 -2
- package/dist/cloudwatch.transport-Blc7Gtxt.d.mts.map +1 -0
- package/dist/{cloudwatch.transport-yBwm4OeO.mjs → cloudwatch.transport-DZ6ies7g.mjs} +26 -19
- package/dist/cloudwatch.transport-DZ6ies7g.mjs.map +1 -0
- package/dist/{coerce.utils-BN980Y9V.js → coerce.utils-B-LilukN.js} +1 -1
- package/dist/{coerce.utils-BN980Y9V.js.map → coerce.utils-B-LilukN.js.map} +1 -1
- package/dist/{coerce.utils-Dxv4_ikC.mjs → coerce.utils-Cu6IUTgX.mjs} +1 -1
- package/dist/{coerce.utils-Dxv4_ikC.mjs.map → coerce.utils-Cu6IUTgX.mjs.map} +1 -1
- package/dist/correlation.d.mts +24 -6
- package/dist/correlation.d.mts.map +1 -1
- package/dist/correlation.d.ts +24 -6
- package/dist/correlation.d.ts.map +1 -1
- package/dist/correlation.js +41 -16
- package/dist/correlation.js.map +1 -1
- package/dist/correlation.mjs +41 -16
- package/dist/correlation.mjs.map +1 -1
- package/dist/entry-fields-BJkuPnIg.mjs +30 -0
- package/dist/entry-fields-BJkuPnIg.mjs.map +1 -0
- package/dist/entry-fields-BvOLFlJ4.js +36 -0
- package/dist/entry-fields-BvOLFlJ4.js.map +1 -0
- package/dist/gcp.d.mts +2 -2
- package/dist/gcp.d.ts +2 -2
- package/dist/gcp.js +2 -1
- package/dist/gcp.mjs +2 -1
- package/dist/{gcp.transport-SHdtc1n0.js → gcp.transport-CHV8MGgz.js} +5 -5
- package/dist/gcp.transport-CHV8MGgz.js.map +1 -0
- package/dist/{gcp.transport-BJuNRFe_.mjs → gcp.transport-DBX5TPIy.mjs} +5 -5
- package/dist/gcp.transport-DBX5TPIy.mjs.map +1 -0
- package/dist/{gcp.transport-BOhkVKlI.d.ts → gcp.transport-m4fVw0b1.d.ts} +2 -2
- package/dist/gcp.transport-m4fVw0b1.d.ts.map +1 -0
- package/dist/{gcp.transport-Bi6CUqZR.d.mts → gcp.transport-zzNyIoih.d.mts} +2 -2
- package/dist/gcp.transport-zzNyIoih.d.mts.map +1 -0
- package/dist/{index-BPKwwERo.d.mts → index-BPwEisy4.d.mts} +2 -2
- package/dist/{index-BPKwwERo.d.mts.map → index-BPwEisy4.d.mts.map} +1 -1
- package/dist/{index-C1X-TE5I.d.ts → index-nZEeCr3X.d.ts} +2 -2
- package/dist/{index-C1X-TE5I.d.ts.map → index-nZEeCr3X.d.ts.map} +1 -1
- package/dist/index.d.mts +63 -98
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +63 -98
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +167 -443
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +98 -377
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.d.mts +11 -4
- package/dist/middleware.d.mts.map +1 -1
- package/dist/middleware.d.ts +11 -4
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +148 -24
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +148 -24
- package/dist/middleware.mjs.map +1 -1
- package/dist/nest.d.mts +4 -4
- package/dist/nest.d.ts +4 -4
- package/dist/nest.js +20 -11
- package/dist/nest.mjs +7 -5
- package/dist/{logitron-logger.module-BsDZR5BH.js → nestjs-extras-DnJ6vrIC.js} +562 -444
- package/dist/nestjs-extras-DnJ6vrIC.js.map +1 -0
- package/dist/{logitron-logger.module-Bs3XYZgF.mjs → nestjs-extras-ImbuDhs1.mjs} +505 -349
- package/dist/nestjs-extras-ImbuDhs1.mjs.map +1 -0
- package/dist/{logitron-logger.module-CbXy2gfW.d.ts → nestjs-extras-OkC796py.d.mts} +111 -4
- package/dist/nestjs-extras-OkC796py.d.mts.map +1 -0
- package/dist/{logitron-logger.module-BdG1Jnpk.d.mts → nestjs-extras-mKrHE9S6.d.ts} +111 -4
- package/dist/nestjs-extras-mKrHE9S6.d.ts.map +1 -0
- package/dist/{search-njTI2hdo.js → search-B0MH1SJJ.js} +23 -10
- package/dist/search-B0MH1SJJ.js.map +1 -0
- package/dist/{search-DujhR94O.d.ts → search-BfBzDIT9.d.mts} +2 -3
- package/dist/search-BfBzDIT9.d.mts.map +1 -0
- package/dist/{search-CQOo-Iq6.d.mts → search-CHUZPl0h.d.ts} +2 -3
- package/dist/search-CHUZPl0h.d.ts.map +1 -0
- package/dist/{search-CFQRISUv.mjs → search-DjXSn6v5.mjs} +23 -10
- package/dist/search-DjXSn6v5.mjs.map +1 -0
- package/dist/search.d.mts +3 -3
- package/dist/search.d.ts +3 -3
- package/dist/search.js +2 -2
- package/dist/search.mjs +2 -2
- package/dist/testing.d.mts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/trace.utils-CvXgaiQT.mjs +278 -0
- package/dist/trace.utils-CvXgaiQT.mjs.map +1 -0
- package/dist/trace.utils-DpBOLDtq.js +358 -0
- package/dist/trace.utils-DpBOLDtq.js.map +1 -0
- package/dist/{transport.manager-D6KL3t9V.mjs → transport.manager-CpdVHEGP.mjs} +365 -86
- package/dist/transport.manager-CpdVHEGP.mjs.map +1 -0
- package/dist/{transport.manager-BivyUTtU.js → transport.manager-Crau3Rle.js} +370 -85
- package/dist/transport.manager-Crau3Rle.js.map +1 -0
- package/dist/{transport.types-B5vcyv2k.d.mts → transport.types-CtNr3D7S.d.mts} +19 -2
- package/dist/transport.types-CtNr3D7S.d.mts.map +1 -0
- package/dist/{transport.types-BuY84TwO.d.ts → transport.types-DqVLZTyf.d.ts} +19 -2
- package/dist/transport.types-DqVLZTyf.d.ts.map +1 -0
- package/dist/transports.d.mts +38 -13
- package/dist/transports.d.mts.map +1 -1
- package/dist/transports.d.ts +38 -13
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +6 -5
- package/dist/transports.mjs +6 -5
- package/dist/worker-entry.js +307 -55
- package/dist/worker.d.mts +2 -2
- package/dist/worker.d.ts +2 -2
- package/dist/worker.js +1 -1
- package/dist/worker.mjs +1 -1
- package/dist/{worker.transport-BP8Q28Z2.d.mts → worker.transport-Bgv9heDI.d.mts} +2 -2
- package/dist/{worker.transport-BP8Q28Z2.d.mts.map → worker.transport-Bgv9heDI.d.mts.map} +1 -1
- package/dist/{worker.transport-mKXgNK0L.d.ts → worker.transport-Bj-i0P9V.d.ts} +2 -2
- package/dist/{worker.transport-mKXgNK0L.d.ts.map → worker.transport-Bj-i0P9V.d.ts.map} +1 -1
- package/dist/{worker.transport-CfH6z37Z.js → worker.transport-JP8YHBuU.js} +1 -1
- package/dist/{worker.transport-CfH6z37Z.js.map → worker.transport-JP8YHBuU.js.map} +1 -1
- package/dist/{worker.transport-BhMyvqQ2.mjs → worker.transport-otwhJ_Et.mjs} +1 -1
- package/dist/{worker.transport-BhMyvqQ2.mjs.map → worker.transport-otwhJ_Et.mjs.map} +1 -1
- package/package.json +10 -6
- package/dist/.tsbuildinfo +0 -1
- package/dist/async-context-CSHWoHMB.js.map +0 -1
- package/dist/async-context-CwBNMm2E.mjs.map +0 -1
- package/dist/cloudwatch.transport-CGHY4buE.d.mts.map +0 -1
- package/dist/cloudwatch.transport-DK_8cKd3.d.ts.map +0 -1
- package/dist/cloudwatch.transport-pNBNWJLt.js.map +0 -1
- package/dist/cloudwatch.transport-yBwm4OeO.mjs.map +0 -1
- package/dist/gcp.transport-BJuNRFe_.mjs.map +0 -1
- package/dist/gcp.transport-BOhkVKlI.d.ts.map +0 -1
- package/dist/gcp.transport-Bi6CUqZR.d.mts.map +0 -1
- package/dist/gcp.transport-SHdtc1n0.js.map +0 -1
- package/dist/logitron-logger.module-BdG1Jnpk.d.mts.map +0 -1
- package/dist/logitron-logger.module-Bs3XYZgF.mjs.map +0 -1
- package/dist/logitron-logger.module-BsDZR5BH.js.map +0 -1
- package/dist/logitron-logger.module-CbXy2gfW.d.ts.map +0 -1
- package/dist/search-CFQRISUv.mjs.map +0 -1
- package/dist/search-CQOo-Iq6.d.mts.map +0 -1
- package/dist/search-DujhR94O.d.ts.map +0 -1
- package/dist/search-njTI2hdo.js.map +0 -1
- package/dist/transport.manager-BivyUTtU.js.map +0 -1
- package/dist/transport.manager-D6KL3t9V.mjs.map +0 -1
- package/dist/transport.types-B5vcyv2k.d.mts.map +0 -1
- package/dist/transport.types-BuY84TwO.d.ts.map +0 -1
- package/scripts/postinstall.js +0 -61
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace.utils-CvXgaiQT.mjs","names":["resolvedConfig: TraceIdConfig","traceId: string | undefined"],"sources":["../src/utils/trace.utils.ts"],"sourcesContent":["/**\n * Trace ID utilities for Logitron\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { randomUUID } from 'node:crypto';\n\nimport type { TraceIdConfig, TraceIdExtractorConfig } from '../types';\n\n/** Default key used to store the trace ID in AsyncLocalStorage. */\nexport const TRACE_CONTEXT_KEY = 'traceId' as const;\n\n// ── TraceContext class ────────────────────────────────────────────────────────\n\n/**\n * Singleton that owns the AsyncLocalStorage and the active contextKey.\n *\n * Why a class instead of bare module-level variables?\n * - The contextKey is user-configurable; encapsulating it here means there is\n * exactly one place that reads and writes it.\n * - All helpers (getCurrentTraceId, runWithTraceId, etc.) delegate to this\n * instance, so the key is always in sync without hidden global state.\n * - Easier to test: you can reset the singleton between test cases.\n *\n * Usage (advanced):\n * import { TraceContext } from 'logixia';\n * TraceContext.instance.contextKey // → 'traceId' (or custom)\n * TraceContext.instance.getCurrentTraceId() // same as standalone fn\n */\nexport class TraceContext {\n private static _instance: TraceContext | null = null;\n\n readonly storage = new AsyncLocalStorage<Record<string, unknown>>();\n private _contextKey: string = TRACE_CONTEXT_KEY;\n\n private constructor() {}\n\n /** The process-wide singleton. */\n static get instance(): TraceContext {\n if (!TraceContext._instance) {\n TraceContext._instance = new TraceContext();\n }\n return TraceContext._instance;\n }\n\n /** @internal Reset the singleton (useful in tests). */\n static _reset(): void {\n TraceContext._instance = null;\n }\n\n // ── Key management ──────────────────────────────────────────────────────────\n\n /** The AsyncLocalStorage key that holds the trace ID value. */\n get contextKey(): string {\n return this._contextKey;\n }\n\n /** @internal Called by TraceMiddleware when it boots to register the user's key. */\n setContextKey(key: string): void {\n this._contextKey = key;\n }\n\n // ── Core operations ─────────────────────────────────────────────────────────\n\n /** UUID v4 generator (default). */\n generate(): string {\n return randomUUID();\n }\n\n /** Read the trace ID from the current async context. */\n getCurrentTraceId(): string | undefined {\n return this.storage.getStore()?.[this._contextKey] as string | undefined;\n }\n\n /**\n * Mutate the CURRENT async context in-place.\n *\n * ⚠️ DEPRECATED — unsafe for concurrent requests.\n *\n * Uses `AsyncLocalStorage.enterWith()`, which mutates the current async\n * execution context and every Promise chain spawned from it. In a server\n * processing overlapping requests this can cause a trace ID set for one\n * request to bleed into *other* in-flight requests that share the same\n * async parent (e.g. a module-level setup function or a cached handler).\n *\n * Use {@link run} instead — it scopes the context to a callback so there\n * is no risk of cross-request leakage.\n *\n * @deprecated Use `TraceContext.instance.run(traceId, fn)` — do not call\n * `setTraceId` from request-handling code.\n */\n setTraceId(traceId: string, data?: Record<string, unknown>): void {\n const current = this.storage.getStore() ?? {};\n this.storage.enterWith({ ...current, [this._contextKey]: traceId, ...data });\n }\n\n /** Run `fn` inside a new async context that carries `traceId`. */\n run<T>(traceId: string, fn: () => T, data?: Record<string, unknown>): T {\n return this.storage.run({ [this._contextKey]: traceId, ...data }, fn);\n }\n}\n\n// ── Module-level aliases (backwards-compatible public API) ────────────────────\n\n/**\n * The shared AsyncLocalStorage instance.\n * @deprecated Prefer `TraceContext.instance.storage` for new code.\n */\nexport const traceStorage = TraceContext.instance.storage;\n\n/** Returns the key currently used to store the trace ID in AsyncLocalStorage. */\nexport function getTraceContextKey(): string {\n return TraceContext.instance.contextKey;\n}\n\n/** @internal Called by TraceMiddleware / traceMiddleware() when they boot. */\nexport function _setActiveContextKey(key: string): void {\n TraceContext.instance.setContextKey(key);\n}\n\n/** Generate a UUID v4 trace ID. */\nexport function generateTraceId(): string {\n return TraceContext.instance.generate();\n}\n\n/**\n * Get the current trace ID from async context.\n * Returns `undefined` when called outside a traced request.\n */\nexport function getCurrentTraceId(): string | undefined {\n return TraceContext.instance.getCurrentTraceId();\n}\n\n/**\n * Set trace ID in the CURRENT async context without starting a new one.\n *\n * ⚠️ DEPRECATED — unsafe for concurrent requests.\n *\n * Uses `AsyncLocalStorage.enterWith()`, which mutates the current async\n * execution context and every Promise chain spawned from it. Under load this\n * can cause a trace ID from one request to bleed into others sharing the same\n * async parent.\n *\n * Use {@link runWithTraceId} instead. Migration patterns:\n *\n * **Express / Fastify middleware — before:**\n * ```ts\n * app.use((req, _res, next) => {\n * setTraceId(req.headers['x-trace-id'] ?? generateTraceId());\n * next();\n * });\n * ```\n * **after:**\n * ```ts\n * app.use((req, _res, next) => {\n * runWithTraceId(req.headers['x-trace-id'] ?? generateTraceId(), () => next());\n * });\n * ```\n *\n * **Kafka / queue consumer — before:**\n * ```ts\n * async function handle(msg) {\n * setTraceId(msg.headers['x-trace-id']);\n * await processMessage(msg);\n * }\n * ```\n * **after:**\n * ```ts\n * async function handle(msg) {\n * await runWithTraceId(msg.headers['x-trace-id'], () => processMessage(msg));\n * }\n * ```\n *\n * **Background job — before:**\n * ```ts\n * setTraceId(generateTraceId(), { jobId: job.id });\n * await job.run();\n * ```\n * **after:**\n * ```ts\n * await runWithTraceId(generateTraceId(), () => job.run(), { jobId: job.id });\n * ```\n *\n * @deprecated Use `runWithTraceId(traceId, fn)` — do not call `setTraceId`\n * from request-handling code.\n */\nexport function setTraceId(traceId: string, data?: Record<string, unknown>): void {\n TraceContext.instance.setTraceId(traceId, data);\n}\n\n/** Run `fn` inside a new async context carrying `traceId`. */\nexport function runWithTraceId<T>(traceId: string, fn: () => T, data?: Record<string, unknown>): T {\n return TraceContext.instance.run(traceId, fn, data);\n}\n\n// ── Request extraction ────────────────────────────────────────────────────────\n\n/** Shape that extractTraceId accepts (Express-compatible) */\ninterface RequestLike {\n headers?: Record<string, string | string[] | undefined>;\n query?: Record<string, string | string[] | undefined>;\n body?: Record<string, string | undefined>;\n params?: Record<string, string | undefined>;\n}\n\n/** Maximum accepted length for an incoming trace / correlation / request ID. */\nexport const MAX_INCOMING_ID_LENGTH = 128;\n\nconst SAFE_INCOMING_ID = /^[A-Za-z0-9._:-]{1,128}$/;\n\n/**\n * Validate an externally supplied identifier (trace ID, correlation ID, origin\n * service name, message-queue ID). Accepts only 1-128 characters from\n * `[A-Za-z0-9._:-]`; surrounding whitespace is trimmed first. Arrays use their\n * first element. Returns the cleaned value, or `undefined` when it is unusable\n * so callers can generate a fresh ID instead of passing client text through.\n */\nexport function sanitizeIncomingId(value: unknown): string | undefined {\n const first = Array.isArray(value) ? value[0] : value;\n if (typeof first !== 'string' || first.length > MAX_INCOMING_ID_LENGTH * 2) return undefined;\n const trimmed = first.trim();\n return SAFE_INCOMING_ID.test(trimmed) ? trimmed : undefined;\n}\n\n/** W3C trace-context: `version-traceId(32hex)-spanId(16hex)-flags(2hex)`. */\nconst W3C_TRACEPARENT_RE = /^[\\da-f]{2}-([\\da-f]{32})-[\\da-f]{16}-[\\da-f]{2}$/i;\n\n/**\n * Extract the 32-hex trace-id segment from a W3C `traceparent` header value.\n * Returns the parsed trace ID, or the original value unchanged when it is not a\n * well-formed traceparent (so non-W3C propagation still works).\n */\nfunction parseTraceparent(value: string): string {\n const m = W3C_TRACEPARENT_RE.exec(value);\n return m ? m[1]! : value;\n}\n\n/** Extract trace ID from request using configuration (header → query → body → params). */\nexport function extractTraceId(\n request: unknown,\n config: TraceIdExtractorConfig\n): string | undefined {\n const req = request as RequestLike;\n\n if (config.header) {\n const headers = Array.isArray(config.header) ? config.header : [config.header];\n for (const header of headers) {\n const lower = header.toLowerCase();\n const value = sanitizeIncomingId(req.headers?.[lower]);\n if (value) return lower === 'traceparent' ? parseTraceparent(value) : value;\n }\n }\n\n if (config.query) {\n const queries = Array.isArray(config.query) ? config.query : [config.query];\n for (const query of queries) {\n const value = sanitizeIncomingId(req.query?.[query]);\n if (value) return value;\n }\n }\n\n if (config.body) {\n const bodyFields = Array.isArray(config.body) ? config.body : [config.body];\n for (const field of bodyFields) {\n const value = sanitizeIncomingId(req.body?.[field]);\n if (value) return value;\n }\n }\n\n if (config.params) {\n const paramFields = Array.isArray(config.params) ? config.params : [config.params];\n for (const param of paramFields) {\n const value = sanitizeIncomingId(req.params?.[param]);\n if (value) return value;\n }\n }\n\n return undefined;\n}\n\n/**\n * Default headers checked for incoming trace ID propagation, in priority order:\n * traceparent (W3C/OTel) → x-trace-id → x-request-id → x-correlation-id → trace-id\n */\nexport const DEFAULT_TRACE_HEADERS = [\n 'traceparent',\n 'x-trace-id',\n 'x-request-id',\n 'x-correlation-id',\n 'trace-id',\n];\n\n/**\n * Create trace ID middleware for Express/NestJS\n */\nexport function createTraceMiddleware(config: TraceIdConfig) {\n const defaultExtractor = {\n header: DEFAULT_TRACE_HEADERS,\n query: ['traceId', 'trace_id'],\n };\n const resolvedConfig: TraceIdConfig = {\n ...config,\n extractor: config?.extractor ? { ...defaultExtractor, ...config.extractor } : defaultExtractor,\n };\n\n _setActiveContextKey(resolvedConfig.contextKey ?? TRACE_CONTEXT_KEY);\n\n return (req: unknown, res: unknown, next: () => void) => {\n let traceId: string | undefined;\n\n if (resolvedConfig.extractor) {\n traceId = extractTraceId(req, resolvedConfig.extractor);\n }\n\n if (!traceId) {\n traceId = resolvedConfig.generator ? resolvedConfig.generator() : generateTraceId();\n }\n\n (req as Record<string, unknown>).traceId = traceId;\n // Echo the trace ID back on the response, tolerating different response APIs:\n // Express/Node use res.setHeader(), Fastify replies use reply.header(). Guard\n // both and never let a missing/!function method (or already-sent headers)\n // crash the request — the trace ID is still propagated via async context.\n const resObj = res as {\n setHeader?: (k: string, v: string) => void;\n header?: (k: string, v: string) => void;\n headersSent?: boolean;\n };\n if (!resObj.headersSent) {\n try {\n if (typeof resObj.setHeader === 'function') {\n resObj.setHeader('X-Trace-Id', traceId);\n } else if (typeof resObj.header === 'function') {\n resObj.header('X-Trace-Id', traceId);\n }\n } catch {\n /* response not in a header-settable state — context propagation still works */\n }\n }\n\n runWithTraceId(traceId, () => next());\n };\n}\n"],"mappings":";;;;;AAUA,MAAa,oBAAoB;;;;;;;;;;;;;;;;AAmBjC,IAAa,eAAb,MAAa,aAAa;8BACwB;CAKhD,AAAQ,cAAc;iBAHH,IAAI,mBAA4C;qBACrC;;;CAK9B,WAAW,WAAyB;AAClC,MAAI,CAAC,aAAa,UAChB,cAAa,YAAY,IAAI,cAAc;AAE7C,SAAO,aAAa;;;CAItB,OAAO,SAAe;AACpB,eAAa,YAAY;;;CAM3B,IAAI,aAAqB;AACvB,SAAO,KAAK;;;CAId,cAAc,KAAmB;AAC/B,OAAK,cAAc;;;CAMrB,WAAmB;AACjB,SAAO,YAAY;;;CAIrB,oBAAwC;;AACtC,kCAAO,KAAK,QAAQ,UAAU,gFAAG,KAAK;;;;;;;;;;;;;;;;;;;CAoBxC,WAAW,SAAiB,MAAsC;EAChE,MAAM,UAAU,KAAK,QAAQ,UAAU,IAAI,EAAE;AAC7C,OAAK,QAAQ,UAAU;GAAE,GAAG;IAAU,KAAK,cAAc;GAAS,GAAG;GAAM,CAAC;;;CAI9E,IAAO,SAAiB,IAAa,MAAmC;AACtE,SAAO,KAAK,QAAQ,IAAI;IAAG,KAAK,cAAc;GAAS,GAAG;GAAM,EAAE,GAAG;;;;;;;AAUzE,MAAa,eAAe,aAAa,SAAS;;AAGlD,SAAgB,qBAA6B;AAC3C,QAAO,aAAa,SAAS;;;AAI/B,SAAgB,qBAAqB,KAAmB;AACtD,cAAa,SAAS,cAAc,IAAI;;;AAI1C,SAAgB,kBAA0B;AACxC,QAAO,aAAa,SAAS,UAAU;;;;;;AAOzC,SAAgB,oBAAwC;AACtD,QAAO,aAAa,SAAS,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDlD,SAAgB,WAAW,SAAiB,MAAsC;AAChF,cAAa,SAAS,WAAW,SAAS,KAAK;;;AAIjD,SAAgB,eAAkB,SAAiB,IAAa,MAAmC;AACjG,QAAO,aAAa,SAAS,IAAI,SAAS,IAAI,KAAK;;;AAcrD,MAAa,yBAAyB;AAEtC,MAAM,mBAAmB;;;;;;;;AASzB,SAAgB,mBAAmB,OAAoC;CACrE,MAAM,QAAQ,MAAM,QAAQ,MAAM,GAAG,MAAM,KAAK;AAChD,KAAI,OAAO,UAAU,YAAY,MAAM,SAAS,yBAAyB,EAAG,QAAO;CACnF,MAAM,UAAU,MAAM,MAAM;AAC5B,QAAO,iBAAiB,KAAK,QAAQ,GAAG,UAAU;;;AAIpD,MAAM,qBAAqB;;;;;;AAO3B,SAAS,iBAAiB,OAAuB;CAC/C,MAAM,IAAI,mBAAmB,KAAK,MAAM;AACxC,QAAO,IAAI,EAAE,KAAM;;;AAIrB,SAAgB,eACd,SACA,QACoB;CACpB,MAAM,MAAM;AAEZ,KAAI,OAAO,QAAQ;EACjB,MAAM,UAAU,MAAM,QAAQ,OAAO,OAAO,GAAG,OAAO,SAAS,CAAC,OAAO,OAAO;AAC9E,OAAK,MAAM,UAAU,SAAS;;GAC5B,MAAM,QAAQ,OAAO,aAAa;GAClC,MAAM,QAAQ,mCAAmB,IAAI,qEAAU,OAAO;AACtD,OAAI,MAAO,QAAO,UAAU,gBAAgB,iBAAiB,MAAM,GAAG;;;AAI1E,KAAI,OAAO,OAAO;EAChB,MAAM,UAAU,MAAM,QAAQ,OAAO,MAAM,GAAG,OAAO,QAAQ,CAAC,OAAO,MAAM;AAC3E,OAAK,MAAM,SAAS,SAAS;;GAC3B,MAAM,QAAQ,iCAAmB,IAAI,+DAAQ,OAAO;AACpD,OAAI,MAAO,QAAO;;;AAItB,KAAI,OAAO,MAAM;EACf,MAAM,aAAa,MAAM,QAAQ,OAAO,KAAK,GAAG,OAAO,OAAO,CAAC,OAAO,KAAK;AAC3E,OAAK,MAAM,SAAS,YAAY;;GAC9B,MAAM,QAAQ,gCAAmB,IAAI,4DAAO,OAAO;AACnD,OAAI,MAAO,QAAO;;;AAItB,KAAI,OAAO,QAAQ;EACjB,MAAM,cAAc,MAAM,QAAQ,OAAO,OAAO,GAAG,OAAO,SAAS,CAAC,OAAO,OAAO;AAClF,OAAK,MAAM,SAAS,aAAa;;GAC/B,MAAM,QAAQ,kCAAmB,IAAI,kEAAS,OAAO;AACrD,OAAI,MAAO,QAAO;;;;;;;;AAWxB,MAAa,wBAAwB;CACnC;CACA;CACA;CACA;CACA;CACD;;;;AAKD,SAAgB,sBAAsB,QAAuB;CAC3D,MAAM,mBAAmB;EACvB,QAAQ;EACR,OAAO,CAAC,WAAW,WAAW;EAC/B;CACD,MAAMA,iBAAgC;EACpC,GAAG;EACH,4DAAW,OAAQ,aAAY;GAAE,GAAG;GAAkB,GAAG,OAAO;GAAW,GAAG;EAC/E;AAED,sBAAqB,eAAe,cAAc,kBAAkB;AAEpE,SAAQ,KAAc,KAAc,SAAqB;EACvD,IAAIC;AAEJ,MAAI,eAAe,UACjB,WAAU,eAAe,KAAK,eAAe,UAAU;AAGzD,MAAI,CAAC,QACH,WAAU,eAAe,YAAY,eAAe,WAAW,GAAG,iBAAiB;AAGrF,EAAC,IAAgC,UAAU;EAK3C,MAAM,SAAS;AAKf,MAAI,CAAC,OAAO,YACV,KAAI;AACF,OAAI,OAAO,OAAO,cAAc,WAC9B,QAAO,UAAU,cAAc,QAAQ;YAC9B,OAAO,OAAO,WAAW,WAClC,QAAO,OAAO,cAAc,QAAQ;UAEhC;AAKV,iBAAe,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-nOFOJqeH.js');
|
|
2
|
+
let node_crypto = require("node:crypto");
|
|
3
|
+
node_crypto = require_chunk.__toESM(node_crypto);
|
|
4
|
+
let node_async_hooks = require("node:async_hooks");
|
|
5
|
+
node_async_hooks = require_chunk.__toESM(node_async_hooks);
|
|
6
|
+
|
|
7
|
+
//#region src/utils/trace.utils.ts
|
|
8
|
+
/** Default key used to store the trace ID in AsyncLocalStorage. */
|
|
9
|
+
const TRACE_CONTEXT_KEY = "traceId";
|
|
10
|
+
/**
|
|
11
|
+
* Singleton that owns the AsyncLocalStorage and the active contextKey.
|
|
12
|
+
*
|
|
13
|
+
* Why a class instead of bare module-level variables?
|
|
14
|
+
* - The contextKey is user-configurable; encapsulating it here means there is
|
|
15
|
+
* exactly one place that reads and writes it.
|
|
16
|
+
* - All helpers (getCurrentTraceId, runWithTraceId, etc.) delegate to this
|
|
17
|
+
* instance, so the key is always in sync without hidden global state.
|
|
18
|
+
* - Easier to test: you can reset the singleton between test cases.
|
|
19
|
+
*
|
|
20
|
+
* Usage (advanced):
|
|
21
|
+
* import { TraceContext } from 'logixia';
|
|
22
|
+
* TraceContext.instance.contextKey // → 'traceId' (or custom)
|
|
23
|
+
* TraceContext.instance.getCurrentTraceId() // same as standalone fn
|
|
24
|
+
*/
|
|
25
|
+
var TraceContext = class TraceContext {
|
|
26
|
+
static #_ = this._instance = null;
|
|
27
|
+
constructor() {
|
|
28
|
+
this.storage = new node_async_hooks.AsyncLocalStorage();
|
|
29
|
+
this._contextKey = TRACE_CONTEXT_KEY;
|
|
30
|
+
}
|
|
31
|
+
/** The process-wide singleton. */
|
|
32
|
+
static get instance() {
|
|
33
|
+
if (!TraceContext._instance) TraceContext._instance = new TraceContext();
|
|
34
|
+
return TraceContext._instance;
|
|
35
|
+
}
|
|
36
|
+
/** @internal Reset the singleton (useful in tests). */
|
|
37
|
+
static _reset() {
|
|
38
|
+
TraceContext._instance = null;
|
|
39
|
+
}
|
|
40
|
+
/** The AsyncLocalStorage key that holds the trace ID value. */
|
|
41
|
+
get contextKey() {
|
|
42
|
+
return this._contextKey;
|
|
43
|
+
}
|
|
44
|
+
/** @internal Called by TraceMiddleware when it boots to register the user's key. */
|
|
45
|
+
setContextKey(key) {
|
|
46
|
+
this._contextKey = key;
|
|
47
|
+
}
|
|
48
|
+
/** UUID v4 generator (default). */
|
|
49
|
+
generate() {
|
|
50
|
+
return (0, node_crypto.randomUUID)();
|
|
51
|
+
}
|
|
52
|
+
/** Read the trace ID from the current async context. */
|
|
53
|
+
getCurrentTraceId() {
|
|
54
|
+
var _this$storage$getStor;
|
|
55
|
+
return (_this$storage$getStor = this.storage.getStore()) === null || _this$storage$getStor === void 0 ? void 0 : _this$storage$getStor[this._contextKey];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Mutate the CURRENT async context in-place.
|
|
59
|
+
*
|
|
60
|
+
* ⚠️ DEPRECATED — unsafe for concurrent requests.
|
|
61
|
+
*
|
|
62
|
+
* Uses `AsyncLocalStorage.enterWith()`, which mutates the current async
|
|
63
|
+
* execution context and every Promise chain spawned from it. In a server
|
|
64
|
+
* processing overlapping requests this can cause a trace ID set for one
|
|
65
|
+
* request to bleed into *other* in-flight requests that share the same
|
|
66
|
+
* async parent (e.g. a module-level setup function or a cached handler).
|
|
67
|
+
*
|
|
68
|
+
* Use {@link run} instead — it scopes the context to a callback so there
|
|
69
|
+
* is no risk of cross-request leakage.
|
|
70
|
+
*
|
|
71
|
+
* @deprecated Use `TraceContext.instance.run(traceId, fn)` — do not call
|
|
72
|
+
* `setTraceId` from request-handling code.
|
|
73
|
+
*/
|
|
74
|
+
setTraceId(traceId, data) {
|
|
75
|
+
const current = this.storage.getStore() ?? {};
|
|
76
|
+
this.storage.enterWith({
|
|
77
|
+
...current,
|
|
78
|
+
[this._contextKey]: traceId,
|
|
79
|
+
...data
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/** Run `fn` inside a new async context that carries `traceId`. */
|
|
83
|
+
run(traceId, fn, data) {
|
|
84
|
+
return this.storage.run({
|
|
85
|
+
[this._contextKey]: traceId,
|
|
86
|
+
...data
|
|
87
|
+
}, fn);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* The shared AsyncLocalStorage instance.
|
|
92
|
+
* @deprecated Prefer `TraceContext.instance.storage` for new code.
|
|
93
|
+
*/
|
|
94
|
+
const traceStorage = TraceContext.instance.storage;
|
|
95
|
+
/** Returns the key currently used to store the trace ID in AsyncLocalStorage. */
|
|
96
|
+
function getTraceContextKey() {
|
|
97
|
+
return TraceContext.instance.contextKey;
|
|
98
|
+
}
|
|
99
|
+
/** @internal Called by TraceMiddleware / traceMiddleware() when they boot. */
|
|
100
|
+
function _setActiveContextKey(key) {
|
|
101
|
+
TraceContext.instance.setContextKey(key);
|
|
102
|
+
}
|
|
103
|
+
/** Generate a UUID v4 trace ID. */
|
|
104
|
+
function generateTraceId() {
|
|
105
|
+
return TraceContext.instance.generate();
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get the current trace ID from async context.
|
|
109
|
+
* Returns `undefined` when called outside a traced request.
|
|
110
|
+
*/
|
|
111
|
+
function getCurrentTraceId() {
|
|
112
|
+
return TraceContext.instance.getCurrentTraceId();
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Set trace ID in the CURRENT async context without starting a new one.
|
|
116
|
+
*
|
|
117
|
+
* ⚠️ DEPRECATED — unsafe for concurrent requests.
|
|
118
|
+
*
|
|
119
|
+
* Uses `AsyncLocalStorage.enterWith()`, which mutates the current async
|
|
120
|
+
* execution context and every Promise chain spawned from it. Under load this
|
|
121
|
+
* can cause a trace ID from one request to bleed into others sharing the same
|
|
122
|
+
* async parent.
|
|
123
|
+
*
|
|
124
|
+
* Use {@link runWithTraceId} instead. Migration patterns:
|
|
125
|
+
*
|
|
126
|
+
* **Express / Fastify middleware — before:**
|
|
127
|
+
* ```ts
|
|
128
|
+
* app.use((req, _res, next) => {
|
|
129
|
+
* setTraceId(req.headers['x-trace-id'] ?? generateTraceId());
|
|
130
|
+
* next();
|
|
131
|
+
* });
|
|
132
|
+
* ```
|
|
133
|
+
* **after:**
|
|
134
|
+
* ```ts
|
|
135
|
+
* app.use((req, _res, next) => {
|
|
136
|
+
* runWithTraceId(req.headers['x-trace-id'] ?? generateTraceId(), () => next());
|
|
137
|
+
* });
|
|
138
|
+
* ```
|
|
139
|
+
*
|
|
140
|
+
* **Kafka / queue consumer — before:**
|
|
141
|
+
* ```ts
|
|
142
|
+
* async function handle(msg) {
|
|
143
|
+
* setTraceId(msg.headers['x-trace-id']);
|
|
144
|
+
* await processMessage(msg);
|
|
145
|
+
* }
|
|
146
|
+
* ```
|
|
147
|
+
* **after:**
|
|
148
|
+
* ```ts
|
|
149
|
+
* async function handle(msg) {
|
|
150
|
+
* await runWithTraceId(msg.headers['x-trace-id'], () => processMessage(msg));
|
|
151
|
+
* }
|
|
152
|
+
* ```
|
|
153
|
+
*
|
|
154
|
+
* **Background job — before:**
|
|
155
|
+
* ```ts
|
|
156
|
+
* setTraceId(generateTraceId(), { jobId: job.id });
|
|
157
|
+
* await job.run();
|
|
158
|
+
* ```
|
|
159
|
+
* **after:**
|
|
160
|
+
* ```ts
|
|
161
|
+
* await runWithTraceId(generateTraceId(), () => job.run(), { jobId: job.id });
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* @deprecated Use `runWithTraceId(traceId, fn)` — do not call `setTraceId`
|
|
165
|
+
* from request-handling code.
|
|
166
|
+
*/
|
|
167
|
+
function setTraceId(traceId, data) {
|
|
168
|
+
TraceContext.instance.setTraceId(traceId, data);
|
|
169
|
+
}
|
|
170
|
+
/** Run `fn` inside a new async context carrying `traceId`. */
|
|
171
|
+
function runWithTraceId(traceId, fn, data) {
|
|
172
|
+
return TraceContext.instance.run(traceId, fn, data);
|
|
173
|
+
}
|
|
174
|
+
/** Maximum accepted length for an incoming trace / correlation / request ID. */
|
|
175
|
+
const MAX_INCOMING_ID_LENGTH = 128;
|
|
176
|
+
const SAFE_INCOMING_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
177
|
+
/**
|
|
178
|
+
* Validate an externally supplied identifier (trace ID, correlation ID, origin
|
|
179
|
+
* service name, message-queue ID). Accepts only 1-128 characters from
|
|
180
|
+
* `[A-Za-z0-9._:-]`; surrounding whitespace is trimmed first. Arrays use their
|
|
181
|
+
* first element. Returns the cleaned value, or `undefined` when it is unusable
|
|
182
|
+
* so callers can generate a fresh ID instead of passing client text through.
|
|
183
|
+
*/
|
|
184
|
+
function sanitizeIncomingId(value) {
|
|
185
|
+
const first = Array.isArray(value) ? value[0] : value;
|
|
186
|
+
if (typeof first !== "string" || first.length > MAX_INCOMING_ID_LENGTH * 2) return void 0;
|
|
187
|
+
const trimmed = first.trim();
|
|
188
|
+
return SAFE_INCOMING_ID.test(trimmed) ? trimmed : void 0;
|
|
189
|
+
}
|
|
190
|
+
/** W3C trace-context: `version-traceId(32hex)-spanId(16hex)-flags(2hex)`. */
|
|
191
|
+
const W3C_TRACEPARENT_RE = /^[\da-f]{2}-([\da-f]{32})-[\da-f]{16}-[\da-f]{2}$/i;
|
|
192
|
+
/**
|
|
193
|
+
* Extract the 32-hex trace-id segment from a W3C `traceparent` header value.
|
|
194
|
+
* Returns the parsed trace ID, or the original value unchanged when it is not a
|
|
195
|
+
* well-formed traceparent (so non-W3C propagation still works).
|
|
196
|
+
*/
|
|
197
|
+
function parseTraceparent(value) {
|
|
198
|
+
const m = W3C_TRACEPARENT_RE.exec(value);
|
|
199
|
+
return m ? m[1] : value;
|
|
200
|
+
}
|
|
201
|
+
/** Extract trace ID from request using configuration (header → query → body → params). */
|
|
202
|
+
function extractTraceId(request, config) {
|
|
203
|
+
const req = request;
|
|
204
|
+
if (config.header) {
|
|
205
|
+
const headers = Array.isArray(config.header) ? config.header : [config.header];
|
|
206
|
+
for (const header of headers) {
|
|
207
|
+
var _req$headers;
|
|
208
|
+
const lower = header.toLowerCase();
|
|
209
|
+
const value = sanitizeIncomingId((_req$headers = req.headers) === null || _req$headers === void 0 ? void 0 : _req$headers[lower]);
|
|
210
|
+
if (value) return lower === "traceparent" ? parseTraceparent(value) : value;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (config.query) {
|
|
214
|
+
const queries = Array.isArray(config.query) ? config.query : [config.query];
|
|
215
|
+
for (const query of queries) {
|
|
216
|
+
var _req$query;
|
|
217
|
+
const value = sanitizeIncomingId((_req$query = req.query) === null || _req$query === void 0 ? void 0 : _req$query[query]);
|
|
218
|
+
if (value) return value;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (config.body) {
|
|
222
|
+
const bodyFields = Array.isArray(config.body) ? config.body : [config.body];
|
|
223
|
+
for (const field of bodyFields) {
|
|
224
|
+
var _req$body;
|
|
225
|
+
const value = sanitizeIncomingId((_req$body = req.body) === null || _req$body === void 0 ? void 0 : _req$body[field]);
|
|
226
|
+
if (value) return value;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (config.params) {
|
|
230
|
+
const paramFields = Array.isArray(config.params) ? config.params : [config.params];
|
|
231
|
+
for (const param of paramFields) {
|
|
232
|
+
var _req$params;
|
|
233
|
+
const value = sanitizeIncomingId((_req$params = req.params) === null || _req$params === void 0 ? void 0 : _req$params[param]);
|
|
234
|
+
if (value) return value;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Default headers checked for incoming trace ID propagation, in priority order:
|
|
240
|
+
* traceparent (W3C/OTel) → x-trace-id → x-request-id → x-correlation-id → trace-id
|
|
241
|
+
*/
|
|
242
|
+
const DEFAULT_TRACE_HEADERS = [
|
|
243
|
+
"traceparent",
|
|
244
|
+
"x-trace-id",
|
|
245
|
+
"x-request-id",
|
|
246
|
+
"x-correlation-id",
|
|
247
|
+
"trace-id"
|
|
248
|
+
];
|
|
249
|
+
/**
|
|
250
|
+
* Create trace ID middleware for Express/NestJS
|
|
251
|
+
*/
|
|
252
|
+
function createTraceMiddleware(config) {
|
|
253
|
+
const defaultExtractor = {
|
|
254
|
+
header: DEFAULT_TRACE_HEADERS,
|
|
255
|
+
query: ["traceId", "trace_id"]
|
|
256
|
+
};
|
|
257
|
+
const resolvedConfig = {
|
|
258
|
+
...config,
|
|
259
|
+
extractor: (config === null || config === void 0 ? void 0 : config.extractor) ? {
|
|
260
|
+
...defaultExtractor,
|
|
261
|
+
...config.extractor
|
|
262
|
+
} : defaultExtractor
|
|
263
|
+
};
|
|
264
|
+
_setActiveContextKey(resolvedConfig.contextKey ?? TRACE_CONTEXT_KEY);
|
|
265
|
+
return (req, res, next) => {
|
|
266
|
+
let traceId;
|
|
267
|
+
if (resolvedConfig.extractor) traceId = extractTraceId(req, resolvedConfig.extractor);
|
|
268
|
+
if (!traceId) traceId = resolvedConfig.generator ? resolvedConfig.generator() : generateTraceId();
|
|
269
|
+
req.traceId = traceId;
|
|
270
|
+
const resObj = res;
|
|
271
|
+
if (!resObj.headersSent) try {
|
|
272
|
+
if (typeof resObj.setHeader === "function") resObj.setHeader("X-Trace-Id", traceId);
|
|
273
|
+
else if (typeof resObj.header === "function") resObj.header("X-Trace-Id", traceId);
|
|
274
|
+
} catch {}
|
|
275
|
+
runWithTraceId(traceId, () => next());
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
//#endregion
|
|
280
|
+
Object.defineProperty(exports, 'DEFAULT_TRACE_HEADERS', {
|
|
281
|
+
enumerable: true,
|
|
282
|
+
get: function () {
|
|
283
|
+
return DEFAULT_TRACE_HEADERS;
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
Object.defineProperty(exports, 'MAX_INCOMING_ID_LENGTH', {
|
|
287
|
+
enumerable: true,
|
|
288
|
+
get: function () {
|
|
289
|
+
return MAX_INCOMING_ID_LENGTH;
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
Object.defineProperty(exports, 'TRACE_CONTEXT_KEY', {
|
|
293
|
+
enumerable: true,
|
|
294
|
+
get: function () {
|
|
295
|
+
return TRACE_CONTEXT_KEY;
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
Object.defineProperty(exports, 'TraceContext', {
|
|
299
|
+
enumerable: true,
|
|
300
|
+
get: function () {
|
|
301
|
+
return TraceContext;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
Object.defineProperty(exports, '_setActiveContextKey', {
|
|
305
|
+
enumerable: true,
|
|
306
|
+
get: function () {
|
|
307
|
+
return _setActiveContextKey;
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
Object.defineProperty(exports, 'createTraceMiddleware', {
|
|
311
|
+
enumerable: true,
|
|
312
|
+
get: function () {
|
|
313
|
+
return createTraceMiddleware;
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
Object.defineProperty(exports, 'extractTraceId', {
|
|
317
|
+
enumerable: true,
|
|
318
|
+
get: function () {
|
|
319
|
+
return extractTraceId;
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
Object.defineProperty(exports, 'getCurrentTraceId', {
|
|
323
|
+
enumerable: true,
|
|
324
|
+
get: function () {
|
|
325
|
+
return getCurrentTraceId;
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
Object.defineProperty(exports, 'getTraceContextKey', {
|
|
329
|
+
enumerable: true,
|
|
330
|
+
get: function () {
|
|
331
|
+
return getTraceContextKey;
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
Object.defineProperty(exports, 'runWithTraceId', {
|
|
335
|
+
enumerable: true,
|
|
336
|
+
get: function () {
|
|
337
|
+
return runWithTraceId;
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
Object.defineProperty(exports, 'sanitizeIncomingId', {
|
|
341
|
+
enumerable: true,
|
|
342
|
+
get: function () {
|
|
343
|
+
return sanitizeIncomingId;
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
Object.defineProperty(exports, 'setTraceId', {
|
|
347
|
+
enumerable: true,
|
|
348
|
+
get: function () {
|
|
349
|
+
return setTraceId;
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
Object.defineProperty(exports, 'traceStorage', {
|
|
353
|
+
enumerable: true,
|
|
354
|
+
get: function () {
|
|
355
|
+
return traceStorage;
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
//# sourceMappingURL=trace.utils-DpBOLDtq.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace.utils-DpBOLDtq.js","names":["AsyncLocalStorage","resolvedConfig: TraceIdConfig","traceId: string | undefined"],"sources":["../src/utils/trace.utils.ts"],"sourcesContent":["/**\n * Trace ID utilities for Logitron\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { randomUUID } from 'node:crypto';\n\nimport type { TraceIdConfig, TraceIdExtractorConfig } from '../types';\n\n/** Default key used to store the trace ID in AsyncLocalStorage. */\nexport const TRACE_CONTEXT_KEY = 'traceId' as const;\n\n// ── TraceContext class ────────────────────────────────────────────────────────\n\n/**\n * Singleton that owns the AsyncLocalStorage and the active contextKey.\n *\n * Why a class instead of bare module-level variables?\n * - The contextKey is user-configurable; encapsulating it here means there is\n * exactly one place that reads and writes it.\n * - All helpers (getCurrentTraceId, runWithTraceId, etc.) delegate to this\n * instance, so the key is always in sync without hidden global state.\n * - Easier to test: you can reset the singleton between test cases.\n *\n * Usage (advanced):\n * import { TraceContext } from 'logixia';\n * TraceContext.instance.contextKey // → 'traceId' (or custom)\n * TraceContext.instance.getCurrentTraceId() // same as standalone fn\n */\nexport class TraceContext {\n private static _instance: TraceContext | null = null;\n\n readonly storage = new AsyncLocalStorage<Record<string, unknown>>();\n private _contextKey: string = TRACE_CONTEXT_KEY;\n\n private constructor() {}\n\n /** The process-wide singleton. */\n static get instance(): TraceContext {\n if (!TraceContext._instance) {\n TraceContext._instance = new TraceContext();\n }\n return TraceContext._instance;\n }\n\n /** @internal Reset the singleton (useful in tests). */\n static _reset(): void {\n TraceContext._instance = null;\n }\n\n // ── Key management ──────────────────────────────────────────────────────────\n\n /** The AsyncLocalStorage key that holds the trace ID value. */\n get contextKey(): string {\n return this._contextKey;\n }\n\n /** @internal Called by TraceMiddleware when it boots to register the user's key. */\n setContextKey(key: string): void {\n this._contextKey = key;\n }\n\n // ── Core operations ─────────────────────────────────────────────────────────\n\n /** UUID v4 generator (default). */\n generate(): string {\n return randomUUID();\n }\n\n /** Read the trace ID from the current async context. */\n getCurrentTraceId(): string | undefined {\n return this.storage.getStore()?.[this._contextKey] as string | undefined;\n }\n\n /**\n * Mutate the CURRENT async context in-place.\n *\n * ⚠️ DEPRECATED — unsafe for concurrent requests.\n *\n * Uses `AsyncLocalStorage.enterWith()`, which mutates the current async\n * execution context and every Promise chain spawned from it. In a server\n * processing overlapping requests this can cause a trace ID set for one\n * request to bleed into *other* in-flight requests that share the same\n * async parent (e.g. a module-level setup function or a cached handler).\n *\n * Use {@link run} instead — it scopes the context to a callback so there\n * is no risk of cross-request leakage.\n *\n * @deprecated Use `TraceContext.instance.run(traceId, fn)` — do not call\n * `setTraceId` from request-handling code.\n */\n setTraceId(traceId: string, data?: Record<string, unknown>): void {\n const current = this.storage.getStore() ?? {};\n this.storage.enterWith({ ...current, [this._contextKey]: traceId, ...data });\n }\n\n /** Run `fn` inside a new async context that carries `traceId`. */\n run<T>(traceId: string, fn: () => T, data?: Record<string, unknown>): T {\n return this.storage.run({ [this._contextKey]: traceId, ...data }, fn);\n }\n}\n\n// ── Module-level aliases (backwards-compatible public API) ────────────────────\n\n/**\n * The shared AsyncLocalStorage instance.\n * @deprecated Prefer `TraceContext.instance.storage` for new code.\n */\nexport const traceStorage = TraceContext.instance.storage;\n\n/** Returns the key currently used to store the trace ID in AsyncLocalStorage. */\nexport function getTraceContextKey(): string {\n return TraceContext.instance.contextKey;\n}\n\n/** @internal Called by TraceMiddleware / traceMiddleware() when they boot. */\nexport function _setActiveContextKey(key: string): void {\n TraceContext.instance.setContextKey(key);\n}\n\n/** Generate a UUID v4 trace ID. */\nexport function generateTraceId(): string {\n return TraceContext.instance.generate();\n}\n\n/**\n * Get the current trace ID from async context.\n * Returns `undefined` when called outside a traced request.\n */\nexport function getCurrentTraceId(): string | undefined {\n return TraceContext.instance.getCurrentTraceId();\n}\n\n/**\n * Set trace ID in the CURRENT async context without starting a new one.\n *\n * ⚠️ DEPRECATED — unsafe for concurrent requests.\n *\n * Uses `AsyncLocalStorage.enterWith()`, which mutates the current async\n * execution context and every Promise chain spawned from it. Under load this\n * can cause a trace ID from one request to bleed into others sharing the same\n * async parent.\n *\n * Use {@link runWithTraceId} instead. Migration patterns:\n *\n * **Express / Fastify middleware — before:**\n * ```ts\n * app.use((req, _res, next) => {\n * setTraceId(req.headers['x-trace-id'] ?? generateTraceId());\n * next();\n * });\n * ```\n * **after:**\n * ```ts\n * app.use((req, _res, next) => {\n * runWithTraceId(req.headers['x-trace-id'] ?? generateTraceId(), () => next());\n * });\n * ```\n *\n * **Kafka / queue consumer — before:**\n * ```ts\n * async function handle(msg) {\n * setTraceId(msg.headers['x-trace-id']);\n * await processMessage(msg);\n * }\n * ```\n * **after:**\n * ```ts\n * async function handle(msg) {\n * await runWithTraceId(msg.headers['x-trace-id'], () => processMessage(msg));\n * }\n * ```\n *\n * **Background job — before:**\n * ```ts\n * setTraceId(generateTraceId(), { jobId: job.id });\n * await job.run();\n * ```\n * **after:**\n * ```ts\n * await runWithTraceId(generateTraceId(), () => job.run(), { jobId: job.id });\n * ```\n *\n * @deprecated Use `runWithTraceId(traceId, fn)` — do not call `setTraceId`\n * from request-handling code.\n */\nexport function setTraceId(traceId: string, data?: Record<string, unknown>): void {\n TraceContext.instance.setTraceId(traceId, data);\n}\n\n/** Run `fn` inside a new async context carrying `traceId`. */\nexport function runWithTraceId<T>(traceId: string, fn: () => T, data?: Record<string, unknown>): T {\n return TraceContext.instance.run(traceId, fn, data);\n}\n\n// ── Request extraction ────────────────────────────────────────────────────────\n\n/** Shape that extractTraceId accepts (Express-compatible) */\ninterface RequestLike {\n headers?: Record<string, string | string[] | undefined>;\n query?: Record<string, string | string[] | undefined>;\n body?: Record<string, string | undefined>;\n params?: Record<string, string | undefined>;\n}\n\n/** Maximum accepted length for an incoming trace / correlation / request ID. */\nexport const MAX_INCOMING_ID_LENGTH = 128;\n\nconst SAFE_INCOMING_ID = /^[A-Za-z0-9._:-]{1,128}$/;\n\n/**\n * Validate an externally supplied identifier (trace ID, correlation ID, origin\n * service name, message-queue ID). Accepts only 1-128 characters from\n * `[A-Za-z0-9._:-]`; surrounding whitespace is trimmed first. Arrays use their\n * first element. Returns the cleaned value, or `undefined` when it is unusable\n * so callers can generate a fresh ID instead of passing client text through.\n */\nexport function sanitizeIncomingId(value: unknown): string | undefined {\n const first = Array.isArray(value) ? value[0] : value;\n if (typeof first !== 'string' || first.length > MAX_INCOMING_ID_LENGTH * 2) return undefined;\n const trimmed = first.trim();\n return SAFE_INCOMING_ID.test(trimmed) ? trimmed : undefined;\n}\n\n/** W3C trace-context: `version-traceId(32hex)-spanId(16hex)-flags(2hex)`. */\nconst W3C_TRACEPARENT_RE = /^[\\da-f]{2}-([\\da-f]{32})-[\\da-f]{16}-[\\da-f]{2}$/i;\n\n/**\n * Extract the 32-hex trace-id segment from a W3C `traceparent` header value.\n * Returns the parsed trace ID, or the original value unchanged when it is not a\n * well-formed traceparent (so non-W3C propagation still works).\n */\nfunction parseTraceparent(value: string): string {\n const m = W3C_TRACEPARENT_RE.exec(value);\n return m ? m[1]! : value;\n}\n\n/** Extract trace ID from request using configuration (header → query → body → params). */\nexport function extractTraceId(\n request: unknown,\n config: TraceIdExtractorConfig\n): string | undefined {\n const req = request as RequestLike;\n\n if (config.header) {\n const headers = Array.isArray(config.header) ? config.header : [config.header];\n for (const header of headers) {\n const lower = header.toLowerCase();\n const value = sanitizeIncomingId(req.headers?.[lower]);\n if (value) return lower === 'traceparent' ? parseTraceparent(value) : value;\n }\n }\n\n if (config.query) {\n const queries = Array.isArray(config.query) ? config.query : [config.query];\n for (const query of queries) {\n const value = sanitizeIncomingId(req.query?.[query]);\n if (value) return value;\n }\n }\n\n if (config.body) {\n const bodyFields = Array.isArray(config.body) ? config.body : [config.body];\n for (const field of bodyFields) {\n const value = sanitizeIncomingId(req.body?.[field]);\n if (value) return value;\n }\n }\n\n if (config.params) {\n const paramFields = Array.isArray(config.params) ? config.params : [config.params];\n for (const param of paramFields) {\n const value = sanitizeIncomingId(req.params?.[param]);\n if (value) return value;\n }\n }\n\n return undefined;\n}\n\n/**\n * Default headers checked for incoming trace ID propagation, in priority order:\n * traceparent (W3C/OTel) → x-trace-id → x-request-id → x-correlation-id → trace-id\n */\nexport const DEFAULT_TRACE_HEADERS = [\n 'traceparent',\n 'x-trace-id',\n 'x-request-id',\n 'x-correlation-id',\n 'trace-id',\n];\n\n/**\n * Create trace ID middleware for Express/NestJS\n */\nexport function createTraceMiddleware(config: TraceIdConfig) {\n const defaultExtractor = {\n header: DEFAULT_TRACE_HEADERS,\n query: ['traceId', 'trace_id'],\n };\n const resolvedConfig: TraceIdConfig = {\n ...config,\n extractor: config?.extractor ? { ...defaultExtractor, ...config.extractor } : defaultExtractor,\n };\n\n _setActiveContextKey(resolvedConfig.contextKey ?? TRACE_CONTEXT_KEY);\n\n return (req: unknown, res: unknown, next: () => void) => {\n let traceId: string | undefined;\n\n if (resolvedConfig.extractor) {\n traceId = extractTraceId(req, resolvedConfig.extractor);\n }\n\n if (!traceId) {\n traceId = resolvedConfig.generator ? resolvedConfig.generator() : generateTraceId();\n }\n\n (req as Record<string, unknown>).traceId = traceId;\n // Echo the trace ID back on the response, tolerating different response APIs:\n // Express/Node use res.setHeader(), Fastify replies use reply.header(). Guard\n // both and never let a missing/!function method (or already-sent headers)\n // crash the request — the trace ID is still propagated via async context.\n const resObj = res as {\n setHeader?: (k: string, v: string) => void;\n header?: (k: string, v: string) => void;\n headersSent?: boolean;\n };\n if (!resObj.headersSent) {\n try {\n if (typeof resObj.setHeader === 'function') {\n resObj.setHeader('X-Trace-Id', traceId);\n } else if (typeof resObj.header === 'function') {\n resObj.header('X-Trace-Id', traceId);\n }\n } catch {\n /* response not in a header-settable state — context propagation still works */\n }\n }\n\n runWithTraceId(traceId, () => next());\n };\n}\n"],"mappings":"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"}
|