logixia 2.0.4 → 2.0.7

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 (218) hide show
  1. package/README.md +172 -2614
  2. package/dist/{async-context-B05ArHgM.mjs → async-context-COuvmwsS.mjs} +4 -6
  3. package/dist/async-context-COuvmwsS.mjs.map +1 -0
  4. package/dist/{async-context-DTQgi8f7.js → async-context-Dv0bmPC-.js} +4 -6
  5. package/dist/async-context-Dv0bmPC-.js.map +1 -0
  6. package/dist/{azure-monitor.transport-DV03Z5P4.d.ts → azure-monitor.transport-Bvf7Xegt.d.ts} +2 -2
  7. package/dist/{azure-monitor.transport-DV03Z5P4.d.ts.map → azure-monitor.transport-Bvf7Xegt.d.ts.map} +1 -1
  8. package/dist/{azure-monitor.transport-C7kYyZUW.d.mts → azure-monitor.transport-CbSHOnNX.d.mts} +2 -2
  9. package/dist/{azure-monitor.transport-C7kYyZUW.d.mts.map → azure-monitor.transport-CbSHOnNX.d.mts.map} +1 -1
  10. package/dist/{azure-monitor.transport-BPbgjATu.mjs → azure-monitor.transport-Coz5Gesu.mjs} +5 -5
  11. package/dist/azure-monitor.transport-Coz5Gesu.mjs.map +1 -0
  12. package/dist/{azure-monitor.transport-CwbOLZHB.js → azure-monitor.transport-D6ryi8rA.js} +5 -5
  13. package/dist/azure-monitor.transport-D6ryi8rA.js.map +1 -0
  14. package/dist/azure.d.mts +1 -1
  15. package/dist/azure.d.ts +1 -1
  16. package/dist/azure.js +1 -1
  17. package/dist/azure.mjs +1 -1
  18. package/dist/{batch-buffer-DT4gdXmf.js → batch-buffer-CFzKnuNZ.js} +3 -3
  19. package/dist/{batch-buffer-DT4gdXmf.js.map → batch-buffer-CFzKnuNZ.js.map} +1 -1
  20. package/dist/{batch-buffer-0fgLmIPn.mjs → batch-buffer-CQUWDMYq.mjs} +3 -3
  21. package/dist/{batch-buffer-0fgLmIPn.mjs.map → batch-buffer-CQUWDMYq.mjs.map} +1 -1
  22. package/dist/browser.d.mts +5 -5
  23. package/dist/browser.d.mts.map +1 -1
  24. package/dist/browser.d.ts +5 -5
  25. package/dist/browser.d.ts.map +1 -1
  26. package/dist/browser.js +72 -13
  27. package/dist/browser.js.map +1 -1
  28. package/dist/browser.mjs +72 -13
  29. package/dist/browser.mjs.map +1 -1
  30. package/dist/{builder-D70z6kjj.mjs → builder-C_TgxEOW.mjs} +106 -153
  31. package/dist/builder-C_TgxEOW.mjs.map +1 -0
  32. package/dist/{builder-M7NoaugM.js → builder-CzmM_4Ub.js} +110 -180
  33. package/dist/builder-CzmM_4Ub.js.map +1 -0
  34. package/dist/cli/index.js +157 -66
  35. package/dist/cloudwatch.d.mts +1 -1
  36. package/dist/cloudwatch.d.ts +1 -1
  37. package/dist/cloudwatch.js +1 -1
  38. package/dist/cloudwatch.mjs +1 -1
  39. package/dist/{cloudwatch.transport-Bf7L4I59.d.ts → cloudwatch.transport-CQyT6U91.d.ts} +2 -2
  40. package/dist/{cloudwatch.transport-Bf7L4I59.d.ts.map → cloudwatch.transport-CQyT6U91.d.ts.map} +1 -1
  41. package/dist/{cloudwatch.transport-Rj4yTpRn.d.mts → cloudwatch.transport-CjVrI6S4.d.mts} +2 -2
  42. package/dist/{cloudwatch.transport-Rj4yTpRn.d.mts.map → cloudwatch.transport-CjVrI6S4.d.mts.map} +1 -1
  43. package/dist/{cloudwatch.transport-BTvYfLs9.mjs → cloudwatch.transport-Cr1aeTGj.mjs} +6 -6
  44. package/dist/cloudwatch.transport-Cr1aeTGj.mjs.map +1 -0
  45. package/dist/{cloudwatch.transport-B2wKQFF7.js → cloudwatch.transport-D4G5xYfG.js} +6 -6
  46. package/dist/cloudwatch.transport-D4G5xYfG.js.map +1 -0
  47. package/dist/correlation.d.mts +10 -55
  48. package/dist/correlation.d.mts.map +1 -1
  49. package/dist/correlation.d.ts +10 -55
  50. package/dist/correlation.d.ts.map +1 -1
  51. package/dist/correlation.js +15 -13
  52. package/dist/correlation.js.map +1 -1
  53. package/dist/correlation.mjs +15 -13
  54. package/dist/correlation.mjs.map +1 -1
  55. package/dist/{entry-fields-RLd1jCoR.js → entry-fields-CVwoQplO.js} +8 -3
  56. package/dist/entry-fields-CVwoQplO.js.map +1 -0
  57. package/dist/{entry-fields-CGuvT2R0.mjs → entry-fields-DNTKQxoU.mjs} +8 -3
  58. package/dist/entry-fields-DNTKQxoU.mjs.map +1 -0
  59. package/dist/gcp.d.mts +1 -1
  60. package/dist/gcp.d.ts +1 -1
  61. package/dist/gcp.js +1 -1
  62. package/dist/gcp.mjs +1 -1
  63. package/dist/{gcp.transport-CFQfdAOK.d.ts → gcp.transport-BCIvdFV-.d.ts} +4 -4
  64. package/dist/{gcp.transport-CFQfdAOK.d.ts.map → gcp.transport-BCIvdFV-.d.ts.map} +1 -1
  65. package/dist/{gcp.transport-KZe9hCAR.js → gcp.transport-CVuRpchl.js} +20 -16
  66. package/dist/gcp.transport-CVuRpchl.js.map +1 -0
  67. package/dist/{gcp.transport-C5D72b1g.d.mts → gcp.transport-D9SdLPwP.d.mts} +4 -4
  68. package/dist/{gcp.transport-C5D72b1g.d.mts.map → gcp.transport-D9SdLPwP.d.mts.map} +1 -1
  69. package/dist/{gcp.transport-CGKopAhQ.mjs → gcp.transport-LLK2bw2-.mjs} +20 -16
  70. package/dist/gcp.transport-LLK2bw2-.mjs.map +1 -0
  71. package/dist/http-shapes-BnBkJcid.d.mts +32 -0
  72. package/dist/http-shapes-BnBkJcid.d.mts.map +1 -0
  73. package/dist/http-shapes-BnBkJcid.d.ts +32 -0
  74. package/dist/http-shapes-BnBkJcid.d.ts.map +1 -0
  75. package/dist/{index-xqaJtAZn.d.mts → index-BAkPHp5D.d.mts} +150 -8
  76. package/dist/index-BAkPHp5D.d.mts.map +1 -0
  77. package/dist/{index-DwozbmB4.d.ts → index-e7LowmAu.d.ts} +150 -8
  78. package/dist/index-e7LowmAu.d.ts.map +1 -0
  79. package/dist/index.d.mts +21 -65
  80. package/dist/index.d.mts.map +1 -1
  81. package/dist/index.d.ts +21 -65
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +26 -26
  84. package/dist/index.js.map +1 -1
  85. package/dist/index.mjs +21 -21
  86. package/dist/index.mjs.map +1 -1
  87. package/dist/{internal-log-uCSd1PMc.mjs → internal-log-Cy5nF1ZL.mjs} +2 -81
  88. package/dist/internal-log-Cy5nF1ZL.mjs.map +1 -0
  89. package/dist/{internal-log-b0Vp4qrB.js → internal-log-DwqTjhpb.js} +1 -104
  90. package/dist/internal-log-DwqTjhpb.js.map +1 -0
  91. package/dist/{logitron-logger-D2B75Oc7.d.ts → logitron-logger-Qh3y9-WH.d.ts} +13 -100
  92. package/dist/logitron-logger-Qh3y9-WH.d.ts.map +1 -0
  93. package/dist/{logitron-logger-oEd6wNhf.d.mts → logitron-logger-sYuk8fOb.d.mts} +13 -100
  94. package/dist/logitron-logger-sYuk8fOb.d.mts.map +1 -0
  95. package/dist/middleware.d.mts +4 -17
  96. package/dist/middleware.d.mts.map +1 -1
  97. package/dist/middleware.d.ts +4 -17
  98. package/dist/middleware.d.ts.map +1 -1
  99. package/dist/middleware.js +9 -39
  100. package/dist/middleware.js.map +1 -1
  101. package/dist/middleware.mjs +7 -37
  102. package/dist/middleware.mjs.map +1 -1
  103. package/dist/nest.d.mts +24 -8
  104. package/dist/nest.d.mts.map +1 -1
  105. package/dist/nest.d.ts +24 -8
  106. package/dist/nest.d.ts.map +1 -1
  107. package/dist/nest.js +140 -27
  108. package/dist/nest.js.map +1 -1
  109. package/dist/nest.mjs +140 -27
  110. package/dist/nest.mjs.map +1 -1
  111. package/dist/{transport.manager-DkcEOlqV.js → otel-ChnVYRLA.js} +214 -23
  112. package/dist/otel-ChnVYRLA.js.map +1 -0
  113. package/dist/{transport.manager-eZ_EeC00.mjs → otel-DOuwGPCd.mjs} +173 -24
  114. package/dist/otel-DOuwGPCd.mjs.map +1 -0
  115. package/dist/{request-trace-C_fvIDv4.mjs → request-trace-BgNykH9M.mjs} +1 -5
  116. package/dist/{request-trace-C_fvIDv4.mjs.map → request-trace-BgNykH9M.mjs.map} +1 -1
  117. package/dist/{request-trace-wTJdKp6-.js → request-trace-ByujsUZk.js} +1 -5
  118. package/dist/{request-trace-wTJdKp6-.js.map → request-trace-ByujsUZk.js.map} +1 -1
  119. package/dist/{safe-stringify-BMRl3EX3.mjs → safe-stringify--7gbe95n.mjs} +37 -12
  120. package/dist/safe-stringify--7gbe95n.mjs.map +1 -0
  121. package/dist/{safe-stringify-DLz1Kz3o.js → safe-stringify-CN7icp8S.js} +37 -12
  122. package/dist/safe-stringify-CN7icp8S.js.map +1 -0
  123. package/dist/{search-DUfWuzFP.js → search-CRjhHxe9.js} +269 -25
  124. package/dist/search-CRjhHxe9.js.map +1 -0
  125. package/dist/{search-CazwyX5Z.d.ts → search-D-8HJADE.d.mts} +5 -2
  126. package/dist/search-D-8HJADE.d.mts.map +1 -0
  127. package/dist/{search-CWMVYC-R.mjs → search-DAXKE-P9.mjs} +269 -25
  128. package/dist/search-DAXKE-P9.mjs.map +1 -0
  129. package/dist/{search-BH33XhuU.d.mts → search-DPwTKPve.d.ts} +5 -2
  130. package/dist/search-DPwTKPve.d.ts.map +1 -0
  131. package/dist/search.d.mts +1 -1
  132. package/dist/search.d.ts +1 -1
  133. package/dist/search.js +1 -1
  134. package/dist/search.mjs +1 -1
  135. package/dist/{secret-config-CBZBs0p1.js → secret-config-BvFl2dC8.js} +7 -7
  136. package/dist/secret-config-BvFl2dC8.js.map +1 -0
  137. package/dist/{secret-config-BegMLkYl.mjs → secret-config-ByGq7kJd.mjs} +5 -5
  138. package/dist/secret-config-ByGq7kJd.mjs.map +1 -0
  139. package/dist/sensitive-names-CHMowujD.js +276 -0
  140. package/dist/sensitive-names-CHMowujD.js.map +1 -0
  141. package/dist/sensitive-names-o__1PUUR.mjs +217 -0
  142. package/dist/sensitive-names-o__1PUUR.mjs.map +1 -0
  143. package/dist/{slack.transport-DxHOXlLu.d.mts → slack.transport-BDyENuR0.d.mts} +2 -2
  144. package/dist/{slack.transport-DxHOXlLu.d.mts.map → slack.transport-BDyENuR0.d.mts.map} +1 -1
  145. package/dist/{slack.transport-DrirgDNy.d.ts → slack.transport-C6E0l4kw.d.ts} +2 -2
  146. package/dist/{slack.transport-DrirgDNy.d.ts.map → slack.transport-C6E0l4kw.d.ts.map} +1 -1
  147. package/dist/{slack.transport-Ce96JBDk.mjs → slack.transport-CSj1j0_X.mjs} +66 -15
  148. package/dist/slack.transport-CSj1j0_X.mjs.map +1 -0
  149. package/dist/{slack.transport-Dlx3xvb7.js → slack.transport-zLQ9MGxS.js} +67 -16
  150. package/dist/slack.transport-zLQ9MGxS.js.map +1 -0
  151. package/dist/testing.d.mts +11 -4
  152. package/dist/testing.d.mts.map +1 -1
  153. package/dist/testing.d.ts +11 -4
  154. package/dist/testing.d.ts.map +1 -1
  155. package/dist/testing.js +38 -3
  156. package/dist/testing.js.map +1 -1
  157. package/dist/testing.mjs +38 -3
  158. package/dist/testing.mjs.map +1 -1
  159. package/dist/{transport.types-BJVL1QQl.d.ts → transport.types-CzJq4IrF.d.mts} +14 -3
  160. package/dist/transport.types-CzJq4IrF.d.mts.map +1 -0
  161. package/dist/{transport.types-BJVL1QQl.d.mts → transport.types-CzJq4IrF.d.ts} +14 -3
  162. package/dist/transport.types-CzJq4IrF.d.ts.map +1 -0
  163. package/dist/transports.d.mts +25 -13
  164. package/dist/transports.d.mts.map +1 -1
  165. package/dist/transports.d.ts +25 -13
  166. package/dist/transports.d.ts.map +1 -1
  167. package/dist/transports.js +15 -15
  168. package/dist/transports.mjs +6 -6
  169. package/dist/worker-entry.js +130 -25
  170. package/dist/worker.d.mts +1 -1
  171. package/dist/worker.d.ts +1 -1
  172. package/dist/worker.js +1 -1
  173. package/dist/worker.mjs +1 -1
  174. package/dist/{worker.transport-BuPwBi3d.d.ts → worker.transport-B9mCQSM1.d.ts} +7 -2
  175. package/dist/worker.transport-B9mCQSM1.d.ts.map +1 -0
  176. package/dist/{worker.transport-hJay1wxA.d.mts → worker.transport-Bl8lFRck.d.mts} +7 -2
  177. package/dist/worker.transport-Bl8lFRck.d.mts.map +1 -0
  178. package/dist/{worker.transport-DLVA4y2e.mjs → worker.transport-D78xhNhB.mjs} +13 -10
  179. package/dist/worker.transport-D78xhNhB.mjs.map +1 -0
  180. package/dist/{worker.transport-C6GNl_r2.js → worker.transport-DQDv1nAJ.js} +13 -10
  181. package/dist/worker.transport-DQDv1nAJ.js.map +1 -0
  182. package/package.json +33 -65
  183. package/dist/async-context-B05ArHgM.mjs.map +0 -1
  184. package/dist/async-context-DTQgi8f7.js.map +0 -1
  185. package/dist/azure-monitor.transport-BPbgjATu.mjs.map +0 -1
  186. package/dist/azure-monitor.transport-CwbOLZHB.js.map +0 -1
  187. package/dist/builder-D70z6kjj.mjs.map +0 -1
  188. package/dist/builder-M7NoaugM.js.map +0 -1
  189. package/dist/cloudwatch.transport-B2wKQFF7.js.map +0 -1
  190. package/dist/cloudwatch.transport-BTvYfLs9.mjs.map +0 -1
  191. package/dist/entry-fields-CGuvT2R0.mjs.map +0 -1
  192. package/dist/entry-fields-RLd1jCoR.js.map +0 -1
  193. package/dist/gcp.transport-CGKopAhQ.mjs.map +0 -1
  194. package/dist/gcp.transport-KZe9hCAR.js.map +0 -1
  195. package/dist/index-DwozbmB4.d.ts.map +0 -1
  196. package/dist/index-xqaJtAZn.d.mts.map +0 -1
  197. package/dist/internal-log-b0Vp4qrB.js.map +0 -1
  198. package/dist/internal-log-uCSd1PMc.mjs.map +0 -1
  199. package/dist/logitron-logger-D2B75Oc7.d.ts.map +0 -1
  200. package/dist/logitron-logger-oEd6wNhf.d.mts.map +0 -1
  201. package/dist/safe-stringify-BMRl3EX3.mjs.map +0 -1
  202. package/dist/safe-stringify-DLz1Kz3o.js.map +0 -1
  203. package/dist/search-BH33XhuU.d.mts.map +0 -1
  204. package/dist/search-CWMVYC-R.mjs.map +0 -1
  205. package/dist/search-CazwyX5Z.d.ts.map +0 -1
  206. package/dist/search-DUfWuzFP.js.map +0 -1
  207. package/dist/secret-config-BegMLkYl.mjs.map +0 -1
  208. package/dist/secret-config-CBZBs0p1.js.map +0 -1
  209. package/dist/slack.transport-Ce96JBDk.mjs.map +0 -1
  210. package/dist/slack.transport-Dlx3xvb7.js.map +0 -1
  211. package/dist/transport.manager-DkcEOlqV.js.map +0 -1
  212. package/dist/transport.manager-eZ_EeC00.mjs.map +0 -1
  213. package/dist/transport.types-BJVL1QQl.d.mts.map +0 -1
  214. package/dist/transport.types-BJVL1QQl.d.ts.map +0 -1
  215. package/dist/worker.transport-BuPwBi3d.d.ts.map +0 -1
  216. package/dist/worker.transport-C6GNl_r2.js.map +0 -1
  217. package/dist/worker.transport-DLVA4y2e.mjs.map +0 -1
  218. package/dist/worker.transport-hJay1wxA.d.mts.map +0 -1
@@ -1,55 +1,4 @@
1
- /**
2
- * logixia — Microservices Correlation ID Propagation
3
- *
4
- * Provides zero-boilerplate correlation ID generation and propagation for
5
- * distributed systems. A single correlation ID travels through every service
6
- * in a request fan-out, making it trivial to join logs across services.
7
- *
8
- * Features:
9
- * - UUID v4 correlation ID generation (via `crypto.randomUUID()`)
10
- * - Express / Fastify / NestJS incoming-request middleware
11
- * - `correlationFetch()` — wraps global `fetch`, auto-injects `X-Correlation-ID`
12
- * - `createCorrelationAxiosInterceptor()` — axios request interceptor
13
- * - `childFromRequest(req, logger)` — child logger with all request identifiers
14
- * - Kafka / SQS / RabbitMQ message context helpers
15
- * - Integration with `LogixiaContext` (AsyncLocalStorage) so every `logger.info()`
16
- * call automatically includes the correlationId without per-call passing.
17
- *
18
- * @example Express middleware
19
- * ```ts
20
- * import { correlationMiddleware } from 'logixia/correlation';
21
- * import { logger } from 'logixia';
22
- *
23
- * app.use(correlationMiddleware());
24
- *
25
- * app.get('/orders', (req, res) => {
26
- * // correlationId is automatically in every log inside this request
27
- * logger.info('Listing orders');
28
- * res.json({ ok: true });
29
- * });
30
- * ```
31
- *
32
- * @example Outbound fetch with correlation
33
- * ```ts
34
- * import { correlationFetch } from 'logixia/correlation';
35
- *
36
- * // Reads correlationId from current LogixiaContext and injects it as header
37
- * const data = await correlationFetch('https://payments.internal/charge', {
38
- * method: 'POST',
39
- * body: JSON.stringify({ amount: 100 }),
40
- * });
41
- * ```
42
- *
43
- * @example Manual correlation context
44
- * ```ts
45
- * import { withCorrelationId } from 'logixia/correlation';
46
- *
47
- * await withCorrelationId('trace-abc-123', async () => {
48
- * // All logs and outbound fetch calls in here carry 'trace-abc-123'
49
- * await processOrder();
50
- * });
51
- * ```
52
- */
1
+ import { n as HttpResponseShape, t as HttpRequestShape } from "./http-shapes-BnBkJcid.mjs";
53
2
  /** Fields injected into the log context by the correlation middleware. */
54
3
  export interface CorrelationContext {
55
4
  correlationId: string;
@@ -132,7 +81,7 @@ export declare function withCorrelationId<T>(correlationId: string | undefined,
132
81
  * app.use(correlationMiddleware());
133
82
  * ```
134
83
  */
135
- export declare function correlationMiddleware(options?: CorrelationMiddlewareOptions): (req: Record<string, unknown>, res: Record<string, unknown>, next: () => void) => void;
84
+ export declare function correlationMiddleware(options?: CorrelationMiddlewareOptions): (req: HttpRequestShape, res: HttpResponseShape, next: () => void) => void;
136
85
  /**
137
86
  * Fastify `onRequest` lifecycle hook — same functionality as `correlationMiddleware`
138
87
  * but adapted to Fastify's hook API.
@@ -143,7 +92,7 @@ export declare function correlationMiddleware(options?: CorrelationMiddlewareOpt
143
92
  * fastify.addHook('onRequest', correlationFastifyHook());
144
93
  * ```
145
94
  */
146
- export declare function correlationFastifyHook(options?: CorrelationMiddlewareOptions): (request: Record<string, unknown>, reply: Record<string, unknown>, done: () => void) => void;
95
+ export declare function correlationFastifyHook(options?: CorrelationMiddlewareOptions): (request: HttpRequestShape, reply: HttpResponseShape, done: () => void) => void;
147
96
  /**
148
97
  * A drop-in replacement for `fetch` that automatically injects the current
149
98
  * `correlationId` from `LogixiaContext` as an `X-Correlation-ID` request header.
@@ -196,6 +145,12 @@ export interface ChildFromRequestOptions {
196
145
  * logger gets `child(fields)`.
197
146
  */
198
147
  childContext?: string;
148
+ /**
149
+ * Extra query parameter names redacted in the bound `url` field, on top of the
150
+ * defaults (`token`, `code`, `password`, `api_key`, …). A bare logger exposes no
151
+ * `redact.queryParams`, so pass the same list you gave `LoggerConfig.redact`.
152
+ */
153
+ redactQueryParams?: readonly string[];
199
154
  }
200
155
  /**
201
156
  * Create a child logger enriched with all correlation-relevant identifiers
@@ -223,7 +178,7 @@ export interface ChildFromRequestOptions {
223
178
  */
224
179
  export declare function childFromRequest<TLogger extends {
225
180
  child(...args: never[]): TLogger;
226
- }>(req: Record<string, unknown>, logger: TLogger, options?: ChildFromRequestOptions): TLogger;
181
+ }>(req: HttpRequestShape, logger: TLogger, options?: ChildFromRequestOptions): TLogger;
227
182
  /**
228
183
  * Extract a correlation context from a Kafka / RabbitMQ / SQS message object.
229
184
  *
@@ -1 +1 @@
1
- {"version":3,"file":"correlation.d.mts","names":[],"sources":["../src/correlation.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6DiB;EACf;EACA;;EAEA;;GAEC;;iBAGc;;;;;EAKf;;;;;EAKA;;;;;EAKA;;;;;;;EAOA;;;;;;EAMA;;;;;;;;;;;;wBAec;;;;;wBAwBA;;;;;;;;;;;;;;wBAkBA,kBAAkB,GAAG,mCAAmC,gBAAgB,IAAI;;;;;;;;;;;;;;wBAoB5E,sBAAsB,UAAS,gCAU3C,KAAK,yBACL,KAAK,yBACL;;;;;;;;;;;wBAqCY,uBAAuB,UAAS,gCAU5C,SAAS,yBACT,OAAO,yBACP;;;;;;;;;;;;;;;;;;wBA4CkB,iBACpB,gBAAgB,MAAM,SACtB,OAAM,aACN;EAAW;IACV,QAAQ;;;;;;;;;;;;;wBAwCK,kCACd;EAAW;KACT,QAAQ,4BAA4B;iBAuBvB;;EAEf;;;;;;;EAOA;;;;;;;EAOA;;;;;;;;;;;;;;;;;;;;;;;;;;wBA2Bc,iBAAiB;EAAkB,SAAS,gBAAgB;GAC1E,KAAK,yBACL,QAAQ,SACR,UAAS,0BACR;;;;;;;;;;;;;;;;;;;wBA+Ea,4BAA4B,SAAS;;;;;;;;;;;;;;wBAyCrC,gCAAgC"}
1
+ {"version":3,"file":"correlation.d.mts","names":[],"sources":["../src/correlation.ts"],"mappings":";;iBA+DiB;EACf;EACA;;EAEA;;GAEC;;iBAGc;;;;;EAKf;;;;;EAKA;;;;;EAKA;;;;;;;EAOA;;;;;;EAMA;;;;;;;;;;;;wBAec;;;;;wBAwBA;;;;;;;;;;;;;;wBAkBA,kBAAkB,GAAG,mCAAmC,gBAAgB,IAAI;;;;;;;;;;;;;;wBAoB5E,sBAAsB,UAAS,gCAU3C,KAAK,kBACL,KAAK,mBACL;;;;;;;;;;;wBAkCY,uBAAuB,UAAS,gCAU5C,SAAS,kBACT,OAAO,mBACP;;;;;;;;;;;;;;;;;;wBAyCkB,iBACpB,gBAAgB,MAAM,SACtB,OAAM,aACN;EAAW;IACV,QAAQ;;;;;;;;;;;;;wBAwCK,kCACd;EAAW;KACT,QAAQ,4BAA4B;iBAuBvB;;EAEf;;;;;;;EAOA;;;;;;;EAOA;;;;;;EAMA;;;;;;;;;;;;;;;;;;;;;;;;;;wBA2Bc,iBAAiB;EAAkB,SAAS,gBAAgB;GAC1E,KAAK,kBACL,QAAQ,SACR,UAAS,0BACR;;;;;;;;;;;;;;;;;;;wBAsFa,4BAA4B,SAAS;;;;;;;;;;;;;;wBAyCrC,gCAAgC"}
@@ -1,55 +1,4 @@
1
- /**
2
- * logixia — Microservices Correlation ID Propagation
3
- *
4
- * Provides zero-boilerplate correlation ID generation and propagation for
5
- * distributed systems. A single correlation ID travels through every service
6
- * in a request fan-out, making it trivial to join logs across services.
7
- *
8
- * Features:
9
- * - UUID v4 correlation ID generation (via `crypto.randomUUID()`)
10
- * - Express / Fastify / NestJS incoming-request middleware
11
- * - `correlationFetch()` — wraps global `fetch`, auto-injects `X-Correlation-ID`
12
- * - `createCorrelationAxiosInterceptor()` — axios request interceptor
13
- * - `childFromRequest(req, logger)` — child logger with all request identifiers
14
- * - Kafka / SQS / RabbitMQ message context helpers
15
- * - Integration with `LogixiaContext` (AsyncLocalStorage) so every `logger.info()`
16
- * call automatically includes the correlationId without per-call passing.
17
- *
18
- * @example Express middleware
19
- * ```ts
20
- * import { correlationMiddleware } from 'logixia/correlation';
21
- * import { logger } from 'logixia';
22
- *
23
- * app.use(correlationMiddleware());
24
- *
25
- * app.get('/orders', (req, res) => {
26
- * // correlationId is automatically in every log inside this request
27
- * logger.info('Listing orders');
28
- * res.json({ ok: true });
29
- * });
30
- * ```
31
- *
32
- * @example Outbound fetch with correlation
33
- * ```ts
34
- * import { correlationFetch } from 'logixia/correlation';
35
- *
36
- * // Reads correlationId from current LogixiaContext and injects it as header
37
- * const data = await correlationFetch('https://payments.internal/charge', {
38
- * method: 'POST',
39
- * body: JSON.stringify({ amount: 100 }),
40
- * });
41
- * ```
42
- *
43
- * @example Manual correlation context
44
- * ```ts
45
- * import { withCorrelationId } from 'logixia/correlation';
46
- *
47
- * await withCorrelationId('trace-abc-123', async () => {
48
- * // All logs and outbound fetch calls in here carry 'trace-abc-123'
49
- * await processOrder();
50
- * });
51
- * ```
52
- */
1
+ import { n as HttpResponseShape, t as HttpRequestShape } from "./http-shapes-BnBkJcid.js";
53
2
  /** Fields injected into the log context by the correlation middleware. */
54
3
  export interface CorrelationContext {
55
4
  correlationId: string;
@@ -132,7 +81,7 @@ export declare function withCorrelationId<T>(correlationId: string | undefined,
132
81
  * app.use(correlationMiddleware());
133
82
  * ```
134
83
  */
135
- export declare function correlationMiddleware(options?: CorrelationMiddlewareOptions): (req: Record<string, unknown>, res: Record<string, unknown>, next: () => void) => void;
84
+ export declare function correlationMiddleware(options?: CorrelationMiddlewareOptions): (req: HttpRequestShape, res: HttpResponseShape, next: () => void) => void;
136
85
  /**
137
86
  * Fastify `onRequest` lifecycle hook — same functionality as `correlationMiddleware`
138
87
  * but adapted to Fastify's hook API.
@@ -143,7 +92,7 @@ export declare function correlationMiddleware(options?: CorrelationMiddlewareOpt
143
92
  * fastify.addHook('onRequest', correlationFastifyHook());
144
93
  * ```
145
94
  */
146
- export declare function correlationFastifyHook(options?: CorrelationMiddlewareOptions): (request: Record<string, unknown>, reply: Record<string, unknown>, done: () => void) => void;
95
+ export declare function correlationFastifyHook(options?: CorrelationMiddlewareOptions): (request: HttpRequestShape, reply: HttpResponseShape, done: () => void) => void;
147
96
  /**
148
97
  * A drop-in replacement for `fetch` that automatically injects the current
149
98
  * `correlationId` from `LogixiaContext` as an `X-Correlation-ID` request header.
@@ -196,6 +145,12 @@ export interface ChildFromRequestOptions {
196
145
  * logger gets `child(fields)`.
197
146
  */
198
147
  childContext?: string;
148
+ /**
149
+ * Extra query parameter names redacted in the bound `url` field, on top of the
150
+ * defaults (`token`, `code`, `password`, `api_key`, …). A bare logger exposes no
151
+ * `redact.queryParams`, so pass the same list you gave `LoggerConfig.redact`.
152
+ */
153
+ redactQueryParams?: readonly string[];
199
154
  }
200
155
  /**
201
156
  * Create a child logger enriched with all correlation-relevant identifiers
@@ -223,7 +178,7 @@ export interface ChildFromRequestOptions {
223
178
  */
224
179
  export declare function childFromRequest<TLogger extends {
225
180
  child(...args: never[]): TLogger;
226
- }>(req: Record<string, unknown>, logger: TLogger, options?: ChildFromRequestOptions): TLogger;
181
+ }>(req: HttpRequestShape, logger: TLogger, options?: ChildFromRequestOptions): TLogger;
227
182
  /**
228
183
  * Extract a correlation context from a Kafka / RabbitMQ / SQS message object.
229
184
  *
@@ -1 +1 @@
1
- {"version":3,"file":"correlation.d.ts","names":[],"sources":["../src/correlation.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6DiB;EACf;EACA;;EAEA;;GAEC;;iBAGc;;;;;EAKf;;;;;EAKA;;;;;EAKA;;;;;;;EAOA;;;;;;EAMA;;;;;;;;;;;;wBAec;;;;;wBAwBA;;;;;;;;;;;;;;wBAkBA,kBAAkB,GAAG,mCAAmC,gBAAgB,IAAI;;;;;;;;;;;;;;wBAoB5E,sBAAsB,UAAS,gCAU3C,KAAK,yBACL,KAAK,yBACL;;;;;;;;;;;wBAqCY,uBAAuB,UAAS,gCAU5C,SAAS,yBACT,OAAO,yBACP;;;;;;;;;;;;;;;;;;wBA4CkB,iBACpB,gBAAgB,MAAM,SACtB,OAAM,aACN;EAAW;IACV,QAAQ;;;;;;;;;;;;;wBAwCK,kCACd;EAAW;KACT,QAAQ,4BAA4B;iBAuBvB;;EAEf;;;;;;;EAOA;;;;;;;EAOA;;;;;;;;;;;;;;;;;;;;;;;;;;wBA2Bc,iBAAiB;EAAkB,SAAS,gBAAgB;GAC1E,KAAK,yBACL,QAAQ,SACR,UAAS,0BACR;;;;;;;;;;;;;;;;;;;wBA+Ea,4BAA4B,SAAS;;;;;;;;;;;;;;wBAyCrC,gCAAgC"}
1
+ {"version":3,"file":"correlation.d.ts","names":[],"sources":["../src/correlation.ts"],"mappings":";;iBA+DiB;EACf;EACA;;EAEA;;GAEC;;iBAGc;;;;;EAKf;;;;;EAKA;;;;;EAKA;;;;;;;EAOA;;;;;;EAMA;;;;;;;;;;;;wBAec;;;;;wBAwBA;;;;;;;;;;;;;;wBAkBA,kBAAkB,GAAG,mCAAmC,gBAAgB,IAAI;;;;;;;;;;;;;;wBAoB5E,sBAAsB,UAAS,gCAU3C,KAAK,kBACL,KAAK,mBACL;;;;;;;;;;;wBAkCY,uBAAuB,UAAS,gCAU5C,SAAS,kBACT,OAAO,mBACP;;;;;;;;;;;;;;;;;;wBAyCkB,iBACpB,gBAAgB,MAAM,SACtB,OAAM,aACN;EAAW;IACV,QAAQ;;;;;;;;;;;;;wBAwCK,kCACd;EAAW;KACT,QAAQ,4BAA4B;iBAuBvB;;EAEf;;;;;;;EAOA;;;;;;;EAOA;;;;;;EAMA;;;;;;;;;;;;;;;;;;;;;;;;;;wBA2Bc,iBAAiB;EAAkB,SAAS,gBAAgB;GAC1E,KAAK,kBACL,QAAQ,SACR,UAAS,0BACR;;;;;;;;;;;;;;;;;;;wBAsFa,4BAA4B,SAAS;;;;;;;;;;;;;;wBAyCrC,gCAAgC"}
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_request_trace = require("./request-trace-wTJdKp6-.js");
3
- const require_async_context = require("./async-context-DTQgi8f7.js");
2
+ const require_sensitive_names = require("./sensitive-names-CHMowujD.js");
3
+ const require_request_trace = require("./request-trace-ByujsUZk.js");
4
+ const require_async_context = require("./async-context-Dv0bmPC-.js");
4
5
  let node_crypto = require("node:crypto");
5
6
  // src/correlation.ts
6
7
  /**
@@ -120,12 +121,12 @@ function withCorrelationId(correlationId, callback) {
120
121
  function correlationMiddleware(options = {}) {
121
122
  const { header = "x-correlation-id", originServiceHeader = "x-origin-service", generate = generateCorrelationId, trustIncoming = true, setResponseHeader = true } = options;
122
123
  return function logixiaCorrelationMiddleware(req, res, next) {
123
- const headers = req["headers"] ?? {};
124
+ const headers = req.headers ?? {};
124
125
  const correlationId = (trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : void 0) ?? generate();
125
126
  const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);
126
127
  if (setResponseHeader) {
127
- const setHeader = res["setHeader"];
128
- if (typeof setHeader === "function") setHeader.call(res, header, correlationId);
128
+ var _res$setHeader;
129
+ (_res$setHeader = res.setHeader) === null || _res$setHeader === void 0 || _res$setHeader.call(res, header, correlationId);
129
130
  }
130
131
  const ctx = {
131
132
  correlationId,
@@ -147,12 +148,12 @@ function correlationMiddleware(options = {}) {
147
148
  function correlationFastifyHook(options = {}) {
148
149
  const { header = "x-correlation-id", originServiceHeader = "x-origin-service", generate = generateCorrelationId, trustIncoming = true, setResponseHeader = true } = options;
149
150
  return function logixiaCorrelationFastifyHook(request, reply, done) {
150
- const headers = request["headers"] ?? {};
151
+ const headers = request.headers ?? {};
151
152
  const correlationId = (trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : void 0) ?? generate();
152
153
  const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);
153
154
  if (setResponseHeader) {
154
- const replyHeader = reply["header"];
155
- if (typeof replyHeader === "function") replyHeader.call(reply, header, correlationId);
155
+ var _reply$header;
156
+ (_reply$header = reply.header) === null || _reply$header === void 0 || _reply$header.call(reply, header, correlationId);
156
157
  }
157
158
  const ctx = {
158
159
  correlationId,
@@ -242,10 +243,11 @@ const MAX_IP_LENGTH = 64;
242
243
  */
243
244
  function childFromRequest(req, logger, options = {}) {
244
245
  const correlationHeader = options.correlationHeader ?? "x-correlation-id";
245
- const headers = req["headers"] ?? {};
246
+ const headers = req.headers ?? {};
246
247
  const correlationId = validIncomingId(headers[correlationHeader.toLowerCase()]) ?? getCurrentCorrelationId() ?? generateCorrelationId();
247
- const method = req["method"];
248
- const url = req["url"] ?? req["originalUrl"];
248
+ const method = req.method;
249
+ const rawUrl = req.originalUrl ?? req.url;
250
+ const url = rawUrl === void 0 ? void 0 : require_sensitive_names.redactUrlQuery(rawUrl, require_sensitive_names.buildNameSet(require_sensitive_names.DEFAULT_REDACT_QUERY_PARAMS, options.redactQueryParams));
249
251
  const rawUserAgent = headers["user-agent"];
250
252
  const userAgent = typeof rawUserAgent === "string" ? rawUserAgent.slice(0, MAX_USER_AGENT_LENGTH) : void 0;
251
253
  const ip = resolveClientIp(req, headers, options.trustProxy === true);
@@ -274,9 +276,9 @@ function resolveClientIp(req, headers, trustProxy) {
274
276
  const first = typeof forwarded === "string" ? (_forwarded$split$ = forwarded.split(",")[0]) === null || _forwarded$split$ === void 0 ? void 0 : _forwarded$split$.trim() : void 0;
275
277
  if (first) return first.slice(0, MAX_IP_LENGTH);
276
278
  }
277
- const fromReq = req["ip"];
279
+ const fromReq = req.ip;
278
280
  if (typeof fromReq === "string" && fromReq) return fromReq.slice(0, MAX_IP_LENGTH);
279
- const remote = (_req$socket = req["socket"]) === null || _req$socket === void 0 ? void 0 : _req$socket["remoteAddress"];
281
+ const remote = (_req$socket = req.socket) === null || _req$socket === void 0 ? void 0 : _req$socket.remoteAddress;
280
282
  return typeof remote === "string" ? remote.slice(0, MAX_IP_LENGTH) : void 0;
281
283
  }
282
284
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"correlation.js","names":["randomUUID","sanitizeIncomingId","LogixiaContext"],"sources":["../src/correlation.ts"],"sourcesContent":["/**\n * logixia — Microservices Correlation ID Propagation\n *\n * Provides zero-boilerplate correlation ID generation and propagation for\n * distributed systems. A single correlation ID travels through every service\n * in a request fan-out, making it trivial to join logs across services.\n *\n * Features:\n * - UUID v4 correlation ID generation (via `crypto.randomUUID()`)\n * - Express / Fastify / NestJS incoming-request middleware\n * - `correlationFetch()` — wraps global `fetch`, auto-injects `X-Correlation-ID`\n * - `createCorrelationAxiosInterceptor()` — axios request interceptor\n * - `childFromRequest(req, logger)` — child logger with all request identifiers\n * - Kafka / SQS / RabbitMQ message context helpers\n * - Integration with `LogixiaContext` (AsyncLocalStorage) so every `logger.info()`\n * call automatically includes the correlationId without per-call passing.\n *\n * @example Express middleware\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use(correlationMiddleware());\n *\n * app.get('/orders', (req, res) => {\n * // correlationId is automatically in every log inside this request\n * logger.info('Listing orders');\n * res.json({ ok: true });\n * });\n * ```\n *\n * @example Outbound fetch with correlation\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Reads correlationId from current LogixiaContext and injects it as header\n * const data = await correlationFetch('https://payments.internal/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n *\n * @example Manual correlation context\n * ```ts\n * import { withCorrelationId } from 'logixia/correlation';\n *\n * await withCorrelationId('trace-abc-123', async () => {\n * // All logs and outbound fetch calls in here carry 'trace-abc-123'\n * await processOrder();\n * });\n * ```\n */\n\nimport { randomUUID } from 'node:crypto';\n\nimport { LogixiaContext } from './context/async-context';\nimport { sanitizeIncomingId } from './utils/trace.utils';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/** Fields injected into the log context by the correlation middleware. */\nexport interface CorrelationContext {\n correlationId: string;\n traceId?: string;\n /** Service that originated the request (from `X-Origin-Service` header). */\n originService?: string;\n /** Index signature so CorrelationContext is assignable to LogContext. */\n [key: string]: unknown;\n}\n\nexport interface CorrelationMiddlewareOptions {\n /**\n * HTTP header to read/write the correlation ID from/to.\n * @default 'x-correlation-id'\n */\n header?: string;\n /**\n * HTTP header for the originating service name.\n * @default 'x-origin-service'\n */\n originServiceHeader?: string;\n /**\n * Function to generate a new correlation ID when none is present in the request.\n * @default () => crypto.randomUUID()\n */\n generate?: () => string;\n /**\n * When `false`, the incoming correlation header is ignored and a new ID is\n * always generated. Use this at the edge when client-supplied IDs should not\n * be trusted.\n * @default true\n */\n trustIncoming?: boolean;\n /**\n * When `true`, the response will include the correlation ID header so\n * clients can log it client-side.\n * @default true\n */\n setResponseHeader?: boolean;\n}\n\n// ── ID generation ─────────────────────────────────────────────────────────────\n\n/**\n * Generate a new correlation ID using `crypto.randomUUID()` (available in\n * Node.js 14.17+, all modern browsers, and Edge runtimes).\n *\n * When the Web Crypto global is unavailable (e.g. Node 18 without\n * `--experimental-global-webcrypto`), fall back to `node:crypto`'s\n * `randomUUID`, which returns a proper UUID v4 — matching the shape this\n * function's JSDoc and README promise. `logixia/correlation` is Node-only\n * (it builds on `AsyncLocalStorage`), so importing `node:crypto` is safe here.\n */\nexport function generateCorrelationId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return randomUUID();\n}\n\n/**\n * Return an incoming correlation ID only if it is short and made of safe\n * characters. Anything else (over-long, empty, or containing newlines or other\n * control characters) is dropped so the caller generates a fresh ID instead of\n * writing client-supplied text into every log line.\n */\nfunction validIncomingId(value: unknown): string | undefined {\n if (typeof value === 'string' && value.trim() !== value) return undefined;\n return sanitizeIncomingId(value);\n}\n\n// ── Context helpers ───────────────────────────────────────────────────────────\n\n/**\n * Return the correlation ID from the current `LogixiaContext`, or `undefined`\n * when called outside any context scope.\n */\nexport function getCurrentCorrelationId(): string | undefined {\n const ctx = LogixiaContext.get();\n return ctx?.correlationId as string | undefined;\n}\n\n/**\n * Run `callback` inside a correlation context scope.\n *\n * If a correlation ID is not provided, a new UUID is generated.\n *\n * @example\n * ```ts\n * await withCorrelationId('cid-abc', async () => {\n * logger.info('Processing message'); // → { correlationId: 'cid-abc', ... }\n * await callDownstream(); // correlationFetch() picks up cid-abc\n * });\n * ```\n */\nexport function withCorrelationId<T>(correlationId: string | undefined, callback: () => T): T {\n const id = correlationId ?? generateCorrelationId();\n return LogixiaContext.run({ correlationId: id }, callback);\n}\n\n// ── Express / Connect middleware ──────────────────────────────────────────────\n\n/**\n * Express-compatible middleware that:\n * 1. Reads `X-Correlation-ID` from the incoming request (or generates a new one)\n * 2. Wraps the request in a `LogixiaContext` so all loggers in the handler\n * automatically include `correlationId`\n * 3. Writes the correlation ID back to the response header (opt-out via options)\n *\n * @example\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * app.use(correlationMiddleware());\n * ```\n */\nexport function correlationMiddleware(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n trustIncoming = true,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationMiddleware(\n req: Record<string, unknown>,\n res: Record<string, unknown>,\n next: () => void\n ): void {\n const headers = (req['headers'] ?? {}) as Record<string, string | undefined>;\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const incomingId = trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : undefined;\n const correlationId = incomingId ?? generate();\n const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);\n\n if (setResponseHeader) {\n const setHeader = res['setHeader'] as ((name: string, value: string) => void) | undefined;\n if (typeof setHeader === 'function') {\n setHeader.call(res, header, correlationId);\n }\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, next);\n };\n}\n\n// ── Fastify hook ──────────────────────────────────────────────────────────────\n\n/**\n * Fastify `onRequest` lifecycle hook — same functionality as `correlationMiddleware`\n * but adapted to Fastify's hook API.\n *\n * @example\n * ```ts\n * import { correlationFastifyHook } from 'logixia/correlation';\n * fastify.addHook('onRequest', correlationFastifyHook());\n * ```\n */\nexport function correlationFastifyHook(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n trustIncoming = true,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationFastifyHook(\n request: Record<string, unknown>,\n reply: Record<string, unknown>,\n done: () => void\n ): void {\n const headers = (request['headers'] ?? {}) as Record<string, string | undefined>;\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const incomingId = trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : undefined;\n const correlationId = incomingId ?? generate();\n const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);\n\n if (setResponseHeader) {\n const replyHeader = reply['header'] as ((name: string, value: string) => void) | undefined;\n if (typeof replyHeader === 'function') {\n replyHeader.call(reply, header, correlationId);\n }\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, done);\n };\n}\n\n// ── Outbound fetch wrapper ────────────────────────────────────────────────────\n\n/**\n * A drop-in replacement for `fetch` that automatically injects the current\n * `correlationId` from `LogixiaContext` as an `X-Correlation-ID` request header.\n *\n * Falls back to the global `fetch` when no correlation context is active.\n *\n * @example\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Inside a request handler with correlationMiddleware active:\n * const response = await correlationFetch('https://payments.internal/v1/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n */\nexport async function correlationFetch(\n input: string | URL | Request,\n init: RequestInit = {},\n options: { header?: string } = {}\n): Promise<Response> {\n const header = options.header ?? 'x-correlation-id';\n const correlationId = getCurrentCorrelationId();\n\n // `init.headers` can be a plain object, a `Headers` instance, or an array of\n // `[name, value]` tuples. Spreading it into a plain object loses everything\n // but the object form: a `Headers` instance has no own enumerable properties,\n // and an array spread yields numeric indices. `Headers` accepts all three, is\n // case-insensitive, and is the shape `fetch` expects back.\n //\n // A `Request` carries headers of its own, and `fetch(request, { headers })`\n // replaces them rather than merging them, so they are seeded first and the\n // caller's `init.headers` is applied on top. Without this, accepting a\n // `Request` here would drop its headers, which is the same defect one input\n // shape over.\n const headers = new Headers(input instanceof Request ? input.headers : undefined);\n for (const [name, value] of new Headers(init.headers)) {\n headers.set(name, value);\n }\n if (correlationId && !headers.has(header)) {\n headers.set(header, correlationId);\n }\n\n return globalThis.fetch(input, { ...init, headers });\n}\n\n// ── Axios interceptor factory ─────────────────────────────────────────────────\n\n/**\n * Returns an axios request interceptor that automatically injects the current\n * correlation ID into outgoing axios requests.\n *\n * @example\n * ```ts\n * import axios from 'axios';\n * import { createCorrelationAxiosInterceptor } from 'logixia/correlation';\n *\n * axios.interceptors.request.use(createCorrelationAxiosInterceptor());\n * ```\n */\nexport function createCorrelationAxiosInterceptor(\n options: { header?: string } = {}\n): (config: Record<string, unknown>) => Record<string, unknown> {\n const header = options.header ?? 'x-correlation-id';\n\n return function correlationAxiosInterceptor(\n config: Record<string, unknown>\n ): Record<string, unknown> {\n const correlationId = getCurrentCorrelationId();\n if (!correlationId) return config;\n\n const headers = (config['headers'] ?? {}) as Record<string, unknown>;\n if (!headers[header]) {\n headers[header] = correlationId;\n }\n\n return { ...config, headers };\n };\n}\n\n// ── Child logger from request ─────────────────────────────────────────────────\n\nconst MAX_USER_AGENT_LENGTH = 512;\nconst MAX_IP_LENGTH = 64;\n\nexport interface ChildFromRequestOptions {\n /** Header to read the correlation ID from. @default 'x-correlation-id' */\n correlationHeader?: string;\n /**\n * Take the client IP from the first `X-Forwarded-For` entry. Only enable this\n * when the app sits behind a proxy that overwrites the header; otherwise any\n * client can choose the logged IP.\n * @default false\n */\n trustProxy?: boolean;\n /**\n * Context name passed as the first argument to `logger.child(context, data)`.\n * When omitted, logixia loggers (detected by their `getContext` /\n * `getAvailableTransports` methods) get `child('request', fields)` and any other\n * logger gets `child(fields)`.\n */\n childContext?: string;\n}\n\n/**\n * Create a child logger enriched with all correlation-relevant identifiers\n * extracted from the incoming HTTP request.\n *\n * The child logger's every call automatically includes:\n * `correlationId`, `method`, `url`, `userAgent`, `ip`\n *\n * The IP comes from `req.ip` / the socket address unless `trustProxy` is set.\n *\n * @example\n * ```ts\n * import { childFromRequest } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use((req, res, next) => {\n * req.logger = childFromRequest(req, logger);\n * next();\n * });\n *\n * app.get('/orders', (req, res) => {\n * req.logger.info('Listing orders'); // → correlationId, requestId, method, url ...\n * });\n * ```\n */\nexport function childFromRequest<TLogger extends { child(...args: never[]): TLogger }>(\n req: Record<string, unknown>,\n logger: TLogger,\n options: ChildFromRequestOptions = {}\n): TLogger {\n const correlationHeader = options.correlationHeader ?? 'x-correlation-id';\n\n const headers = (req['headers'] ?? {}) as Record<string, string | undefined>;\n // Node lower-cases incoming header names, so look up with a lower-cased key.\n const correlationId =\n validIncomingId(headers[correlationHeader.toLowerCase()]) ??\n getCurrentCorrelationId() ??\n generateCorrelationId();\n\n const method = req['method'] as string | undefined;\n const url = (req['url'] as string | undefined) ?? (req['originalUrl'] as string | undefined);\n const rawUserAgent = headers['user-agent'];\n const userAgent =\n typeof rawUserAgent === 'string' ? rawUserAgent.slice(0, MAX_USER_AGENT_LENGTH) : undefined;\n const ip = resolveClientIp(req, headers, options.trustProxy === true);\n\n const fields = {\n correlationId,\n ...(method !== undefined ? { method } : {}),\n ...(url !== undefined ? { url } : {}),\n ...(userAgent !== undefined ? { userAgent } : {}),\n ...(ip !== undefined ? { ip } : {}),\n };\n\n const child = logger.child as (...args: unknown[]) => TLogger;\n if (options.childContext !== undefined) {\n return child.call(logger, options.childContext, fields);\n }\n // logixia's ILogger.child(context: string, data?) takes a string namespace;\n // pino/winston-style loggers take a bindings object.\n if (usesStringContext(logger)) {\n return child.call(logger, 'request', fields);\n }\n return child.call(logger, fields);\n}\n\n/** ILogger implementations (LogixiaLogger, LogixiaLoggerService, mock logger) expose these methods. */\nfunction usesStringContext(logger: object): boolean {\n const l = logger as Record<string, unknown>;\n return typeof l['getContext'] === 'function' || typeof l['getAvailableTransports'] === 'function';\n}\n\nfunction resolveClientIp(\n req: Record<string, unknown>,\n headers: Record<string, string | undefined>,\n trustProxy: boolean\n): string | undefined {\n if (trustProxy) {\n const forwarded = headers['x-forwarded-for'];\n const first = typeof forwarded === 'string' ? forwarded.split(',')[0]?.trim() : undefined;\n if (first) return first.slice(0, MAX_IP_LENGTH);\n }\n const fromReq = req['ip'];\n if (typeof fromReq === 'string' && fromReq) return fromReq.slice(0, MAX_IP_LENGTH);\n const remote = (req['socket'] as Record<string, unknown> | undefined)?.['remoteAddress'];\n return typeof remote === 'string' ? remote.slice(0, MAX_IP_LENGTH) : undefined;\n}\n\n// ── Message queue helpers ─────────────────────────────────────────────────────\n\n/**\n * Extract a correlation context from a Kafka / RabbitMQ / SQS message object.\n *\n * Looks for `correlationId`, `correlation_id`, or `x-correlation-id` in\n * `message.headers` (Kafka) or `message.MessageAttributes` (SQS).\n *\n * @example Kafka consumer\n * ```ts\n * import { extractMessageCorrelationId, withCorrelationId } from 'logixia/correlation';\n *\n * consumer.run({\n * eachMessage: async ({ message }) => {\n * const correlationId = extractMessageCorrelationId(message);\n * await withCorrelationId(correlationId, () => processMessage(message));\n * },\n * });\n * ```\n */\nexport function extractMessageCorrelationId(message: Record<string, unknown>): string | undefined {\n // Kafka: message.headers is Record<string, Buffer | string>\n const kafkaHeaders = message['headers'] as Record<string, Buffer | string> | undefined;\n if (kafkaHeaders) {\n const raw =\n kafkaHeaders['x-correlation-id'] ??\n kafkaHeaders['correlationId'] ??\n kafkaHeaders['correlation_id'];\n if (raw !== undefined) {\n return validIncomingId(Buffer.isBuffer(raw) ? raw.toString('utf8', 0, 512) : String(raw));\n }\n }\n\n // SQS: message.MessageAttributes\n const sqsAttrs = message['MessageAttributes'] as\n Record<string, { StringValue?: string }> | undefined;\n if (sqsAttrs) {\n return validIncomingId(\n sqsAttrs['x-correlation-id']?.StringValue ??\n sqsAttrs['correlationId']?.StringValue ??\n sqsAttrs['correlation_id']?.StringValue\n );\n }\n\n // AMQP / generic envelope\n return validIncomingId(message['correlationId'] ?? message['correlation_id']);\n}\n\n/**\n * Build Kafka message headers containing the current correlation context.\n *\n * @example\n * ```ts\n * import { buildKafkaCorrelationHeaders } from 'logixia/correlation';\n *\n * await producer.send({\n * topic: 'orders',\n * messages: [{ value: JSON.stringify(order), headers: buildKafkaCorrelationHeaders() }],\n * });\n * ```\n */\nexport function buildKafkaCorrelationHeaders(): Record<string, string> {\n const ctx = LogixiaContext.get();\n const headers: Record<string, string> = {};\n\n const correlationId = ctx?.correlationId as string | undefined;\n if (correlationId) headers['x-correlation-id'] = correlationId;\n\n const traceId = ctx?.traceId as string | undefined;\n if (traceId) headers['x-trace-id'] = traceId;\n\n return headers;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"correlation.js","names":["randomUUID","sanitizeIncomingId","LogixiaContext","redactUrlQuery","buildNameSet","DEFAULT_REDACT_QUERY_PARAMS"],"sources":["../src/correlation.ts"],"sourcesContent":["/**\n * logixia — Microservices Correlation ID Propagation\n *\n * Provides zero-boilerplate correlation ID generation and propagation for\n * distributed systems. A single correlation ID travels through every service\n * in a request fan-out, making it trivial to join logs across services.\n *\n * Features:\n * - UUID v4 correlation ID generation (via `crypto.randomUUID()`)\n * - Express / Fastify / NestJS incoming-request middleware\n * - `correlationFetch()` — wraps global `fetch`, auto-injects `X-Correlation-ID`\n * - `createCorrelationAxiosInterceptor()` — axios request interceptor\n * - `childFromRequest(req, logger)` — child logger with all request identifiers\n * - Kafka / SQS / RabbitMQ message context helpers\n * - Integration with `LogixiaContext` (AsyncLocalStorage) so every `logger.info()`\n * call automatically includes the correlationId without per-call passing.\n *\n * @example Express middleware\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use(correlationMiddleware());\n *\n * app.get('/orders', (req, res) => {\n * // correlationId is automatically in every log inside this request\n * logger.info('Listing orders');\n * res.json({ ok: true });\n * });\n * ```\n *\n * @example Outbound fetch with correlation\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Reads correlationId from current LogixiaContext and injects it as header\n * const data = await correlationFetch('https://payments.internal/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n *\n * @example Manual correlation context\n * ```ts\n * import { withCorrelationId } from 'logixia/correlation';\n *\n * await withCorrelationId('trace-abc-123', async () => {\n * // All logs and outbound fetch calls in here carry 'trace-abc-123'\n * await processOrder();\n * });\n * ```\n */\n\nimport { randomUUID } from 'node:crypto';\n\nimport { LogixiaContext } from './context/async-context';\nimport type { HttpRequestShape, HttpResponseShape } from './types/http-shapes';\nimport { buildNameSet, DEFAULT_REDACT_QUERY_PARAMS, redactUrlQuery } from './utils/sensitive-names';\nimport { sanitizeIncomingId } from './utils/trace.utils';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/** Fields injected into the log context by the correlation middleware. */\nexport interface CorrelationContext {\n correlationId: string;\n traceId?: string;\n /** Service that originated the request (from `X-Origin-Service` header). */\n originService?: string;\n /** Index signature so CorrelationContext is assignable to LogContext. */\n [key: string]: unknown;\n}\n\nexport interface CorrelationMiddlewareOptions {\n /**\n * HTTP header to read/write the correlation ID from/to.\n * @default 'x-correlation-id'\n */\n header?: string;\n /**\n * HTTP header for the originating service name.\n * @default 'x-origin-service'\n */\n originServiceHeader?: string;\n /**\n * Function to generate a new correlation ID when none is present in the request.\n * @default () => crypto.randomUUID()\n */\n generate?: () => string;\n /**\n * When `false`, the incoming correlation header is ignored and a new ID is\n * always generated. Use this at the edge when client-supplied IDs should not\n * be trusted.\n * @default true\n */\n trustIncoming?: boolean;\n /**\n * When `true`, the response will include the correlation ID header so\n * clients can log it client-side.\n * @default true\n */\n setResponseHeader?: boolean;\n}\n\n// ── ID generation ─────────────────────────────────────────────────────────────\n\n/**\n * Generate a new correlation ID using `crypto.randomUUID()` (available in\n * Node.js 14.17+, all modern browsers, and Edge runtimes).\n *\n * When the Web Crypto global is unavailable (e.g. Node 18 without\n * `--experimental-global-webcrypto`), fall back to `node:crypto`'s\n * `randomUUID`, which returns a proper UUID v4 — matching the shape this\n * function's JSDoc and README promise. `logixia/correlation` is Node-only\n * (it builds on `AsyncLocalStorage`), so importing `node:crypto` is safe here.\n */\nexport function generateCorrelationId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return randomUUID();\n}\n\n/**\n * Return an incoming correlation ID only if it is short and made of safe\n * characters. Anything else (over-long, empty, or containing newlines or other\n * control characters) is dropped so the caller generates a fresh ID instead of\n * writing client-supplied text into every log line.\n */\nfunction validIncomingId(value: unknown): string | undefined {\n if (typeof value === 'string' && value.trim() !== value) return undefined;\n return sanitizeIncomingId(value);\n}\n\n// ── Context helpers ───────────────────────────────────────────────────────────\n\n/**\n * Return the correlation ID from the current `LogixiaContext`, or `undefined`\n * when called outside any context scope.\n */\nexport function getCurrentCorrelationId(): string | undefined {\n const ctx = LogixiaContext.get();\n return ctx?.correlationId as string | undefined;\n}\n\n/**\n * Run `callback` inside a correlation context scope.\n *\n * If a correlation ID is not provided, a new UUID is generated.\n *\n * @example\n * ```ts\n * await withCorrelationId('cid-abc', async () => {\n * logger.info('Processing message'); // → { correlationId: 'cid-abc', ... }\n * await callDownstream(); // correlationFetch() picks up cid-abc\n * });\n * ```\n */\nexport function withCorrelationId<T>(correlationId: string | undefined, callback: () => T): T {\n const id = correlationId ?? generateCorrelationId();\n return LogixiaContext.run({ correlationId: id }, callback);\n}\n\n// ── Express / Connect middleware ──────────────────────────────────────────────\n\n/**\n * Express-compatible middleware that:\n * 1. Reads `X-Correlation-ID` from the incoming request (or generates a new one)\n * 2. Wraps the request in a `LogixiaContext` so all loggers in the handler\n * automatically include `correlationId`\n * 3. Writes the correlation ID back to the response header (opt-out via options)\n *\n * @example\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * app.use(correlationMiddleware());\n * ```\n */\nexport function correlationMiddleware(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n trustIncoming = true,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationMiddleware(\n req: HttpRequestShape,\n res: HttpResponseShape,\n next: () => void\n ): void {\n const headers = req.headers ?? {};\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const incomingId = trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : undefined;\n const correlationId = incomingId ?? generate();\n const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);\n\n if (setResponseHeader) {\n res.setHeader?.(header, correlationId);\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, next);\n };\n}\n\n// ── Fastify hook ──────────────────────────────────────────────────────────────\n\n/**\n * Fastify `onRequest` lifecycle hook — same functionality as `correlationMiddleware`\n * but adapted to Fastify's hook API.\n *\n * @example\n * ```ts\n * import { correlationFastifyHook } from 'logixia/correlation';\n * fastify.addHook('onRequest', correlationFastifyHook());\n * ```\n */\nexport function correlationFastifyHook(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n trustIncoming = true,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationFastifyHook(\n request: HttpRequestShape,\n reply: HttpResponseShape,\n done: () => void\n ): void {\n const headers = request.headers ?? {};\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const incomingId = trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : undefined;\n const correlationId = incomingId ?? generate();\n const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);\n\n if (setResponseHeader) {\n reply.header?.(header, correlationId);\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, done);\n };\n}\n\n// ── Outbound fetch wrapper ────────────────────────────────────────────────────\n\n/**\n * A drop-in replacement for `fetch` that automatically injects the current\n * `correlationId` from `LogixiaContext` as an `X-Correlation-ID` request header.\n *\n * Falls back to the global `fetch` when no correlation context is active.\n *\n * @example\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Inside a request handler with correlationMiddleware active:\n * const response = await correlationFetch('https://payments.internal/v1/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n */\nexport async function correlationFetch(\n input: string | URL | Request,\n init: RequestInit = {},\n options: { header?: string } = {}\n): Promise<Response> {\n const header = options.header ?? 'x-correlation-id';\n const correlationId = getCurrentCorrelationId();\n\n // `init.headers` can be a plain object, a `Headers` instance, or an array of\n // `[name, value]` tuples. Spreading it into a plain object loses everything\n // but the object form: a `Headers` instance has no own enumerable properties,\n // and an array spread yields numeric indices. `Headers` accepts all three, is\n // case-insensitive, and is the shape `fetch` expects back.\n //\n // A `Request` carries headers of its own, and `fetch(request, { headers })`\n // replaces them rather than merging them, so they are seeded first and the\n // caller's `init.headers` is applied on top. Without this, accepting a\n // `Request` here would drop its headers, which is the same defect one input\n // shape over.\n const headers = new Headers(input instanceof Request ? input.headers : undefined);\n for (const [name, value] of new Headers(init.headers)) {\n headers.set(name, value);\n }\n if (correlationId && !headers.has(header)) {\n headers.set(header, correlationId);\n }\n\n return globalThis.fetch(input, { ...init, headers });\n}\n\n// ── Axios interceptor factory ─────────────────────────────────────────────────\n\n/**\n * Returns an axios request interceptor that automatically injects the current\n * correlation ID into outgoing axios requests.\n *\n * @example\n * ```ts\n * import axios from 'axios';\n * import { createCorrelationAxiosInterceptor } from 'logixia/correlation';\n *\n * axios.interceptors.request.use(createCorrelationAxiosInterceptor());\n * ```\n */\nexport function createCorrelationAxiosInterceptor(\n options: { header?: string } = {}\n): (config: Record<string, unknown>) => Record<string, unknown> {\n const header = options.header ?? 'x-correlation-id';\n\n return function correlationAxiosInterceptor(\n config: Record<string, unknown>\n ): Record<string, unknown> {\n const correlationId = getCurrentCorrelationId();\n if (!correlationId) return config;\n\n const headers = (config['headers'] ?? {}) as Record<string, unknown>;\n if (!headers[header]) {\n headers[header] = correlationId;\n }\n\n return { ...config, headers };\n };\n}\n\n// ── Child logger from request ─────────────────────────────────────────────────\n\nconst MAX_USER_AGENT_LENGTH = 512;\nconst MAX_IP_LENGTH = 64;\n\nexport interface ChildFromRequestOptions {\n /** Header to read the correlation ID from. @default 'x-correlation-id' */\n correlationHeader?: string;\n /**\n * Take the client IP from the first `X-Forwarded-For` entry. Only enable this\n * when the app sits behind a proxy that overwrites the header; otherwise any\n * client can choose the logged IP.\n * @default false\n */\n trustProxy?: boolean;\n /**\n * Context name passed as the first argument to `logger.child(context, data)`.\n * When omitted, logixia loggers (detected by their `getContext` /\n * `getAvailableTransports` methods) get `child('request', fields)` and any other\n * logger gets `child(fields)`.\n */\n childContext?: string;\n /**\n * Extra query parameter names redacted in the bound `url` field, on top of the\n * defaults (`token`, `code`, `password`, `api_key`, …). A bare logger exposes no\n * `redact.queryParams`, so pass the same list you gave `LoggerConfig.redact`.\n */\n redactQueryParams?: readonly string[];\n}\n\n/**\n * Create a child logger enriched with all correlation-relevant identifiers\n * extracted from the incoming HTTP request.\n *\n * The child logger's every call automatically includes:\n * `correlationId`, `method`, `url`, `userAgent`, `ip`\n *\n * The IP comes from `req.ip` / the socket address unless `trustProxy` is set.\n *\n * @example\n * ```ts\n * import { childFromRequest } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use((req, res, next) => {\n * req.logger = childFromRequest(req, logger);\n * next();\n * });\n *\n * app.get('/orders', (req, res) => {\n * req.logger.info('Listing orders'); // → correlationId, requestId, method, url ...\n * });\n * ```\n */\nexport function childFromRequest<TLogger extends { child(...args: never[]): TLogger }>(\n req: HttpRequestShape,\n logger: TLogger,\n options: ChildFromRequestOptions = {}\n): TLogger {\n const correlationHeader = options.correlationHeader ?? 'x-correlation-id';\n\n const headers = req.headers ?? {};\n // Node lower-cases incoming header names, so look up with a lower-cased key.\n const correlationId =\n validIncomingId(headers[correlationHeader.toLowerCase()]) ??\n getCurrentCorrelationId() ??\n generateCorrelationId();\n\n const method = req.method;\n const rawUrl = req.originalUrl ?? req.url;\n const url =\n rawUrl === undefined\n ? undefined\n : redactUrlQuery(\n rawUrl,\n buildNameSet(DEFAULT_REDACT_QUERY_PARAMS, options.redactQueryParams)\n );\n const rawUserAgent = headers['user-agent'];\n const userAgent =\n typeof rawUserAgent === 'string' ? rawUserAgent.slice(0, MAX_USER_AGENT_LENGTH) : undefined;\n const ip = resolveClientIp(req, headers, options.trustProxy === true);\n\n const fields = {\n correlationId,\n ...(method !== undefined ? { method } : {}),\n ...(url !== undefined ? { url } : {}),\n ...(userAgent !== undefined ? { userAgent } : {}),\n ...(ip !== undefined ? { ip } : {}),\n };\n\n const child = logger.child as (...args: unknown[]) => TLogger;\n if (options.childContext !== undefined) {\n return child.call(logger, options.childContext, fields);\n }\n // logixia's ILogger.child(context: string, data?) takes a string namespace;\n // pino/winston-style loggers take a bindings object.\n if (usesStringContext(logger)) {\n return child.call(logger, 'request', fields);\n }\n return child.call(logger, fields);\n}\n\n/** ILogger implementations (LogixiaLogger, LogixiaLoggerService, mock logger) expose these methods. */\nfunction usesStringContext(logger: object): boolean {\n const l = logger as Record<string, unknown>;\n return typeof l['getContext'] === 'function' || typeof l['getAvailableTransports'] === 'function';\n}\n\nfunction resolveClientIp(\n req: HttpRequestShape,\n headers: Record<string, string | string[] | undefined>,\n trustProxy: boolean\n): string | undefined {\n if (trustProxy) {\n const forwarded = headers['x-forwarded-for'];\n const first = typeof forwarded === 'string' ? forwarded.split(',')[0]?.trim() : undefined;\n if (first) return first.slice(0, MAX_IP_LENGTH);\n }\n const fromReq = req.ip;\n if (typeof fromReq === 'string' && fromReq) return fromReq.slice(0, MAX_IP_LENGTH);\n const remote = req.socket?.remoteAddress;\n return typeof remote === 'string' ? remote.slice(0, MAX_IP_LENGTH) : undefined;\n}\n\n// ── Message queue helpers ─────────────────────────────────────────────────────\n\n/**\n * Extract a correlation context from a Kafka / RabbitMQ / SQS message object.\n *\n * Looks for `correlationId`, `correlation_id`, or `x-correlation-id` in\n * `message.headers` (Kafka) or `message.MessageAttributes` (SQS).\n *\n * @example Kafka consumer\n * ```ts\n * import { extractMessageCorrelationId, withCorrelationId } from 'logixia/correlation';\n *\n * consumer.run({\n * eachMessage: async ({ message }) => {\n * const correlationId = extractMessageCorrelationId(message);\n * await withCorrelationId(correlationId, () => processMessage(message));\n * },\n * });\n * ```\n */\nexport function extractMessageCorrelationId(message: Record<string, unknown>): string | undefined {\n // Kafka: message.headers is Record<string, Buffer | string>\n const kafkaHeaders = message['headers'] as Record<string, Buffer | string> | undefined;\n if (kafkaHeaders) {\n const raw =\n kafkaHeaders['x-correlation-id'] ??\n kafkaHeaders['correlationId'] ??\n kafkaHeaders['correlation_id'];\n if (raw !== undefined) {\n return validIncomingId(Buffer.isBuffer(raw) ? raw.toString('utf8', 0, 512) : String(raw));\n }\n }\n\n // SQS: message.MessageAttributes\n const sqsAttrs = message['MessageAttributes'] as\n Record<string, { StringValue?: string }> | undefined;\n if (sqsAttrs) {\n return validIncomingId(\n sqsAttrs['x-correlation-id']?.StringValue ??\n sqsAttrs['correlationId']?.StringValue ??\n sqsAttrs['correlation_id']?.StringValue\n );\n }\n\n // AMQP / generic envelope\n return validIncomingId(message['correlationId'] ?? message['correlation_id']);\n}\n\n/**\n * Build Kafka message headers containing the current correlation context.\n *\n * @example\n * ```ts\n * import { buildKafkaCorrelationHeaders } from 'logixia/correlation';\n *\n * await producer.send({\n * topic: 'orders',\n * messages: [{ value: JSON.stringify(order), headers: buildKafkaCorrelationHeaders() }],\n * });\n * ```\n */\nexport function buildKafkaCorrelationHeaders(): Record<string, string> {\n const ctx = LogixiaContext.get();\n const headers: Record<string, string> = {};\n\n const correlationId = ctx?.correlationId as string | undefined;\n if (correlationId) headers['x-correlation-id'] = correlationId;\n\n const traceId = ctx?.traceId as string | undefined;\n if (traceId) headers['x-trace-id'] = traceId;\n\n return headers;\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
- import { p as sanitizeIncomingId } from "./request-trace-C_fvIDv4.mjs";
2
- import { t as LogixiaContext } from "./async-context-B05ArHgM.mjs";
1
+ import { a as buildNameSet, n as DEFAULT_REDACT_QUERY_PARAMS, o as redactUrlQuery } from "./sensitive-names-o__1PUUR.mjs";
2
+ import { p as sanitizeIncomingId } from "./request-trace-BgNykH9M.mjs";
3
+ import { t as LogixiaContext } from "./async-context-COuvmwsS.mjs";
3
4
  import { randomUUID } from "node:crypto";
4
5
  // src/correlation.ts
5
6
  /**
@@ -119,12 +120,12 @@ function withCorrelationId(correlationId, callback) {
119
120
  function correlationMiddleware(options = {}) {
120
121
  const { header = "x-correlation-id", originServiceHeader = "x-origin-service", generate = generateCorrelationId, trustIncoming = true, setResponseHeader = true } = options;
121
122
  return function logixiaCorrelationMiddleware(req, res, next) {
122
- const headers = req["headers"] ?? {};
123
+ const headers = req.headers ?? {};
123
124
  const correlationId = (trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : void 0) ?? generate();
124
125
  const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);
125
126
  if (setResponseHeader) {
126
- const setHeader = res["setHeader"];
127
- if (typeof setHeader === "function") setHeader.call(res, header, correlationId);
127
+ var _res$setHeader;
128
+ (_res$setHeader = res.setHeader) === null || _res$setHeader === void 0 || _res$setHeader.call(res, header, correlationId);
128
129
  }
129
130
  const ctx = {
130
131
  correlationId,
@@ -146,12 +147,12 @@ function correlationMiddleware(options = {}) {
146
147
  function correlationFastifyHook(options = {}) {
147
148
  const { header = "x-correlation-id", originServiceHeader = "x-origin-service", generate = generateCorrelationId, trustIncoming = true, setResponseHeader = true } = options;
148
149
  return function logixiaCorrelationFastifyHook(request, reply, done) {
149
- const headers = request["headers"] ?? {};
150
+ const headers = request.headers ?? {};
150
151
  const correlationId = (trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : void 0) ?? generate();
151
152
  const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);
152
153
  if (setResponseHeader) {
153
- const replyHeader = reply["header"];
154
- if (typeof replyHeader === "function") replyHeader.call(reply, header, correlationId);
154
+ var _reply$header;
155
+ (_reply$header = reply.header) === null || _reply$header === void 0 || _reply$header.call(reply, header, correlationId);
155
156
  }
156
157
  const ctx = {
157
158
  correlationId,
@@ -241,10 +242,11 @@ const MAX_IP_LENGTH = 64;
241
242
  */
242
243
  function childFromRequest(req, logger, options = {}) {
243
244
  const correlationHeader = options.correlationHeader ?? "x-correlation-id";
244
- const headers = req["headers"] ?? {};
245
+ const headers = req.headers ?? {};
245
246
  const correlationId = validIncomingId(headers[correlationHeader.toLowerCase()]) ?? getCurrentCorrelationId() ?? generateCorrelationId();
246
- const method = req["method"];
247
- const url = req["url"] ?? req["originalUrl"];
247
+ const method = req.method;
248
+ const rawUrl = req.originalUrl ?? req.url;
249
+ const url = rawUrl === void 0 ? void 0 : redactUrlQuery(rawUrl, buildNameSet(DEFAULT_REDACT_QUERY_PARAMS, options.redactQueryParams));
248
250
  const rawUserAgent = headers["user-agent"];
249
251
  const userAgent = typeof rawUserAgent === "string" ? rawUserAgent.slice(0, MAX_USER_AGENT_LENGTH) : void 0;
250
252
  const ip = resolveClientIp(req, headers, options.trustProxy === true);
@@ -273,9 +275,9 @@ function resolveClientIp(req, headers, trustProxy) {
273
275
  const first = typeof forwarded === "string" ? (_forwarded$split$ = forwarded.split(",")[0]) === null || _forwarded$split$ === void 0 ? void 0 : _forwarded$split$.trim() : void 0;
274
276
  if (first) return first.slice(0, MAX_IP_LENGTH);
275
277
  }
276
- const fromReq = req["ip"];
278
+ const fromReq = req.ip;
277
279
  if (typeof fromReq === "string" && fromReq) return fromReq.slice(0, MAX_IP_LENGTH);
278
- const remote = (_req$socket = req["socket"]) === null || _req$socket === void 0 ? void 0 : _req$socket["remoteAddress"];
280
+ const remote = (_req$socket = req.socket) === null || _req$socket === void 0 ? void 0 : _req$socket.remoteAddress;
279
281
  return typeof remote === "string" ? remote.slice(0, MAX_IP_LENGTH) : void 0;
280
282
  }
281
283
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"correlation.mjs","names":[],"sources":["../src/correlation.ts"],"sourcesContent":["/**\n * logixia — Microservices Correlation ID Propagation\n *\n * Provides zero-boilerplate correlation ID generation and propagation for\n * distributed systems. A single correlation ID travels through every service\n * in a request fan-out, making it trivial to join logs across services.\n *\n * Features:\n * - UUID v4 correlation ID generation (via `crypto.randomUUID()`)\n * - Express / Fastify / NestJS incoming-request middleware\n * - `correlationFetch()` — wraps global `fetch`, auto-injects `X-Correlation-ID`\n * - `createCorrelationAxiosInterceptor()` — axios request interceptor\n * - `childFromRequest(req, logger)` — child logger with all request identifiers\n * - Kafka / SQS / RabbitMQ message context helpers\n * - Integration with `LogixiaContext` (AsyncLocalStorage) so every `logger.info()`\n * call automatically includes the correlationId without per-call passing.\n *\n * @example Express middleware\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use(correlationMiddleware());\n *\n * app.get('/orders', (req, res) => {\n * // correlationId is automatically in every log inside this request\n * logger.info('Listing orders');\n * res.json({ ok: true });\n * });\n * ```\n *\n * @example Outbound fetch with correlation\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Reads correlationId from current LogixiaContext and injects it as header\n * const data = await correlationFetch('https://payments.internal/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n *\n * @example Manual correlation context\n * ```ts\n * import { withCorrelationId } from 'logixia/correlation';\n *\n * await withCorrelationId('trace-abc-123', async () => {\n * // All logs and outbound fetch calls in here carry 'trace-abc-123'\n * await processOrder();\n * });\n * ```\n */\n\nimport { randomUUID } from 'node:crypto';\n\nimport { LogixiaContext } from './context/async-context';\nimport { sanitizeIncomingId } from './utils/trace.utils';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/** Fields injected into the log context by the correlation middleware. */\nexport interface CorrelationContext {\n correlationId: string;\n traceId?: string;\n /** Service that originated the request (from `X-Origin-Service` header). */\n originService?: string;\n /** Index signature so CorrelationContext is assignable to LogContext. */\n [key: string]: unknown;\n}\n\nexport interface CorrelationMiddlewareOptions {\n /**\n * HTTP header to read/write the correlation ID from/to.\n * @default 'x-correlation-id'\n */\n header?: string;\n /**\n * HTTP header for the originating service name.\n * @default 'x-origin-service'\n */\n originServiceHeader?: string;\n /**\n * Function to generate a new correlation ID when none is present in the request.\n * @default () => crypto.randomUUID()\n */\n generate?: () => string;\n /**\n * When `false`, the incoming correlation header is ignored and a new ID is\n * always generated. Use this at the edge when client-supplied IDs should not\n * be trusted.\n * @default true\n */\n trustIncoming?: boolean;\n /**\n * When `true`, the response will include the correlation ID header so\n * clients can log it client-side.\n * @default true\n */\n setResponseHeader?: boolean;\n}\n\n// ── ID generation ─────────────────────────────────────────────────────────────\n\n/**\n * Generate a new correlation ID using `crypto.randomUUID()` (available in\n * Node.js 14.17+, all modern browsers, and Edge runtimes).\n *\n * When the Web Crypto global is unavailable (e.g. Node 18 without\n * `--experimental-global-webcrypto`), fall back to `node:crypto`'s\n * `randomUUID`, which returns a proper UUID v4 — matching the shape this\n * function's JSDoc and README promise. `logixia/correlation` is Node-only\n * (it builds on `AsyncLocalStorage`), so importing `node:crypto` is safe here.\n */\nexport function generateCorrelationId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return randomUUID();\n}\n\n/**\n * Return an incoming correlation ID only if it is short and made of safe\n * characters. Anything else (over-long, empty, or containing newlines or other\n * control characters) is dropped so the caller generates a fresh ID instead of\n * writing client-supplied text into every log line.\n */\nfunction validIncomingId(value: unknown): string | undefined {\n if (typeof value === 'string' && value.trim() !== value) return undefined;\n return sanitizeIncomingId(value);\n}\n\n// ── Context helpers ───────────────────────────────────────────────────────────\n\n/**\n * Return the correlation ID from the current `LogixiaContext`, or `undefined`\n * when called outside any context scope.\n */\nexport function getCurrentCorrelationId(): string | undefined {\n const ctx = LogixiaContext.get();\n return ctx?.correlationId as string | undefined;\n}\n\n/**\n * Run `callback` inside a correlation context scope.\n *\n * If a correlation ID is not provided, a new UUID is generated.\n *\n * @example\n * ```ts\n * await withCorrelationId('cid-abc', async () => {\n * logger.info('Processing message'); // → { correlationId: 'cid-abc', ... }\n * await callDownstream(); // correlationFetch() picks up cid-abc\n * });\n * ```\n */\nexport function withCorrelationId<T>(correlationId: string | undefined, callback: () => T): T {\n const id = correlationId ?? generateCorrelationId();\n return LogixiaContext.run({ correlationId: id }, callback);\n}\n\n// ── Express / Connect middleware ──────────────────────────────────────────────\n\n/**\n * Express-compatible middleware that:\n * 1. Reads `X-Correlation-ID` from the incoming request (or generates a new one)\n * 2. Wraps the request in a `LogixiaContext` so all loggers in the handler\n * automatically include `correlationId`\n * 3. Writes the correlation ID back to the response header (opt-out via options)\n *\n * @example\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * app.use(correlationMiddleware());\n * ```\n */\nexport function correlationMiddleware(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n trustIncoming = true,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationMiddleware(\n req: Record<string, unknown>,\n res: Record<string, unknown>,\n next: () => void\n ): void {\n const headers = (req['headers'] ?? {}) as Record<string, string | undefined>;\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const incomingId = trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : undefined;\n const correlationId = incomingId ?? generate();\n const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);\n\n if (setResponseHeader) {\n const setHeader = res['setHeader'] as ((name: string, value: string) => void) | undefined;\n if (typeof setHeader === 'function') {\n setHeader.call(res, header, correlationId);\n }\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, next);\n };\n}\n\n// ── Fastify hook ──────────────────────────────────────────────────────────────\n\n/**\n * Fastify `onRequest` lifecycle hook — same functionality as `correlationMiddleware`\n * but adapted to Fastify's hook API.\n *\n * @example\n * ```ts\n * import { correlationFastifyHook } from 'logixia/correlation';\n * fastify.addHook('onRequest', correlationFastifyHook());\n * ```\n */\nexport function correlationFastifyHook(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n trustIncoming = true,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationFastifyHook(\n request: Record<string, unknown>,\n reply: Record<string, unknown>,\n done: () => void\n ): void {\n const headers = (request['headers'] ?? {}) as Record<string, string | undefined>;\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const incomingId = trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : undefined;\n const correlationId = incomingId ?? generate();\n const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);\n\n if (setResponseHeader) {\n const replyHeader = reply['header'] as ((name: string, value: string) => void) | undefined;\n if (typeof replyHeader === 'function') {\n replyHeader.call(reply, header, correlationId);\n }\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, done);\n };\n}\n\n// ── Outbound fetch wrapper ────────────────────────────────────────────────────\n\n/**\n * A drop-in replacement for `fetch` that automatically injects the current\n * `correlationId` from `LogixiaContext` as an `X-Correlation-ID` request header.\n *\n * Falls back to the global `fetch` when no correlation context is active.\n *\n * @example\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Inside a request handler with correlationMiddleware active:\n * const response = await correlationFetch('https://payments.internal/v1/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n */\nexport async function correlationFetch(\n input: string | URL | Request,\n init: RequestInit = {},\n options: { header?: string } = {}\n): Promise<Response> {\n const header = options.header ?? 'x-correlation-id';\n const correlationId = getCurrentCorrelationId();\n\n // `init.headers` can be a plain object, a `Headers` instance, or an array of\n // `[name, value]` tuples. Spreading it into a plain object loses everything\n // but the object form: a `Headers` instance has no own enumerable properties,\n // and an array spread yields numeric indices. `Headers` accepts all three, is\n // case-insensitive, and is the shape `fetch` expects back.\n //\n // A `Request` carries headers of its own, and `fetch(request, { headers })`\n // replaces them rather than merging them, so they are seeded first and the\n // caller's `init.headers` is applied on top. Without this, accepting a\n // `Request` here would drop its headers, which is the same defect one input\n // shape over.\n const headers = new Headers(input instanceof Request ? input.headers : undefined);\n for (const [name, value] of new Headers(init.headers)) {\n headers.set(name, value);\n }\n if (correlationId && !headers.has(header)) {\n headers.set(header, correlationId);\n }\n\n return globalThis.fetch(input, { ...init, headers });\n}\n\n// ── Axios interceptor factory ─────────────────────────────────────────────────\n\n/**\n * Returns an axios request interceptor that automatically injects the current\n * correlation ID into outgoing axios requests.\n *\n * @example\n * ```ts\n * import axios from 'axios';\n * import { createCorrelationAxiosInterceptor } from 'logixia/correlation';\n *\n * axios.interceptors.request.use(createCorrelationAxiosInterceptor());\n * ```\n */\nexport function createCorrelationAxiosInterceptor(\n options: { header?: string } = {}\n): (config: Record<string, unknown>) => Record<string, unknown> {\n const header = options.header ?? 'x-correlation-id';\n\n return function correlationAxiosInterceptor(\n config: Record<string, unknown>\n ): Record<string, unknown> {\n const correlationId = getCurrentCorrelationId();\n if (!correlationId) return config;\n\n const headers = (config['headers'] ?? {}) as Record<string, unknown>;\n if (!headers[header]) {\n headers[header] = correlationId;\n }\n\n return { ...config, headers };\n };\n}\n\n// ── Child logger from request ─────────────────────────────────────────────────\n\nconst MAX_USER_AGENT_LENGTH = 512;\nconst MAX_IP_LENGTH = 64;\n\nexport interface ChildFromRequestOptions {\n /** Header to read the correlation ID from. @default 'x-correlation-id' */\n correlationHeader?: string;\n /**\n * Take the client IP from the first `X-Forwarded-For` entry. Only enable this\n * when the app sits behind a proxy that overwrites the header; otherwise any\n * client can choose the logged IP.\n * @default false\n */\n trustProxy?: boolean;\n /**\n * Context name passed as the first argument to `logger.child(context, data)`.\n * When omitted, logixia loggers (detected by their `getContext` /\n * `getAvailableTransports` methods) get `child('request', fields)` and any other\n * logger gets `child(fields)`.\n */\n childContext?: string;\n}\n\n/**\n * Create a child logger enriched with all correlation-relevant identifiers\n * extracted from the incoming HTTP request.\n *\n * The child logger's every call automatically includes:\n * `correlationId`, `method`, `url`, `userAgent`, `ip`\n *\n * The IP comes from `req.ip` / the socket address unless `trustProxy` is set.\n *\n * @example\n * ```ts\n * import { childFromRequest } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use((req, res, next) => {\n * req.logger = childFromRequest(req, logger);\n * next();\n * });\n *\n * app.get('/orders', (req, res) => {\n * req.logger.info('Listing orders'); // → correlationId, requestId, method, url ...\n * });\n * ```\n */\nexport function childFromRequest<TLogger extends { child(...args: never[]): TLogger }>(\n req: Record<string, unknown>,\n logger: TLogger,\n options: ChildFromRequestOptions = {}\n): TLogger {\n const correlationHeader = options.correlationHeader ?? 'x-correlation-id';\n\n const headers = (req['headers'] ?? {}) as Record<string, string | undefined>;\n // Node lower-cases incoming header names, so look up with a lower-cased key.\n const correlationId =\n validIncomingId(headers[correlationHeader.toLowerCase()]) ??\n getCurrentCorrelationId() ??\n generateCorrelationId();\n\n const method = req['method'] as string | undefined;\n const url = (req['url'] as string | undefined) ?? (req['originalUrl'] as string | undefined);\n const rawUserAgent = headers['user-agent'];\n const userAgent =\n typeof rawUserAgent === 'string' ? rawUserAgent.slice(0, MAX_USER_AGENT_LENGTH) : undefined;\n const ip = resolveClientIp(req, headers, options.trustProxy === true);\n\n const fields = {\n correlationId,\n ...(method !== undefined ? { method } : {}),\n ...(url !== undefined ? { url } : {}),\n ...(userAgent !== undefined ? { userAgent } : {}),\n ...(ip !== undefined ? { ip } : {}),\n };\n\n const child = logger.child as (...args: unknown[]) => TLogger;\n if (options.childContext !== undefined) {\n return child.call(logger, options.childContext, fields);\n }\n // logixia's ILogger.child(context: string, data?) takes a string namespace;\n // pino/winston-style loggers take a bindings object.\n if (usesStringContext(logger)) {\n return child.call(logger, 'request', fields);\n }\n return child.call(logger, fields);\n}\n\n/** ILogger implementations (LogixiaLogger, LogixiaLoggerService, mock logger) expose these methods. */\nfunction usesStringContext(logger: object): boolean {\n const l = logger as Record<string, unknown>;\n return typeof l['getContext'] === 'function' || typeof l['getAvailableTransports'] === 'function';\n}\n\nfunction resolveClientIp(\n req: Record<string, unknown>,\n headers: Record<string, string | undefined>,\n trustProxy: boolean\n): string | undefined {\n if (trustProxy) {\n const forwarded = headers['x-forwarded-for'];\n const first = typeof forwarded === 'string' ? forwarded.split(',')[0]?.trim() : undefined;\n if (first) return first.slice(0, MAX_IP_LENGTH);\n }\n const fromReq = req['ip'];\n if (typeof fromReq === 'string' && fromReq) return fromReq.slice(0, MAX_IP_LENGTH);\n const remote = (req['socket'] as Record<string, unknown> | undefined)?.['remoteAddress'];\n return typeof remote === 'string' ? remote.slice(0, MAX_IP_LENGTH) : undefined;\n}\n\n// ── Message queue helpers ─────────────────────────────────────────────────────\n\n/**\n * Extract a correlation context from a Kafka / RabbitMQ / SQS message object.\n *\n * Looks for `correlationId`, `correlation_id`, or `x-correlation-id` in\n * `message.headers` (Kafka) or `message.MessageAttributes` (SQS).\n *\n * @example Kafka consumer\n * ```ts\n * import { extractMessageCorrelationId, withCorrelationId } from 'logixia/correlation';\n *\n * consumer.run({\n * eachMessage: async ({ message }) => {\n * const correlationId = extractMessageCorrelationId(message);\n * await withCorrelationId(correlationId, () => processMessage(message));\n * },\n * });\n * ```\n */\nexport function extractMessageCorrelationId(message: Record<string, unknown>): string | undefined {\n // Kafka: message.headers is Record<string, Buffer | string>\n const kafkaHeaders = message['headers'] as Record<string, Buffer | string> | undefined;\n if (kafkaHeaders) {\n const raw =\n kafkaHeaders['x-correlation-id'] ??\n kafkaHeaders['correlationId'] ??\n kafkaHeaders['correlation_id'];\n if (raw !== undefined) {\n return validIncomingId(Buffer.isBuffer(raw) ? raw.toString('utf8', 0, 512) : String(raw));\n }\n }\n\n // SQS: message.MessageAttributes\n const sqsAttrs = message['MessageAttributes'] as\n Record<string, { StringValue?: string }> | undefined;\n if (sqsAttrs) {\n return validIncomingId(\n sqsAttrs['x-correlation-id']?.StringValue ??\n sqsAttrs['correlationId']?.StringValue ??\n sqsAttrs['correlation_id']?.StringValue\n );\n }\n\n // AMQP / generic envelope\n return validIncomingId(message['correlationId'] ?? message['correlation_id']);\n}\n\n/**\n * Build Kafka message headers containing the current correlation context.\n *\n * @example\n * ```ts\n * import { buildKafkaCorrelationHeaders } from 'logixia/correlation';\n *\n * await producer.send({\n * topic: 'orders',\n * messages: [{ value: JSON.stringify(order), headers: buildKafkaCorrelationHeaders() }],\n * });\n * ```\n */\nexport function buildKafkaCorrelationHeaders(): Record<string, string> {\n const ctx = LogixiaContext.get();\n const headers: Record<string, string> = {};\n\n const correlationId = ctx?.correlationId as string | undefined;\n if (correlationId) headers['x-correlation-id'] = correlationId;\n\n const traceId = ctx?.traceId as string | undefined;\n if (traceId) headers['x-trace-id'] = traceId;\n\n return headers;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"correlation.mjs","names":[],"sources":["../src/correlation.ts"],"sourcesContent":["/**\n * logixia — Microservices Correlation ID Propagation\n *\n * Provides zero-boilerplate correlation ID generation and propagation for\n * distributed systems. A single correlation ID travels through every service\n * in a request fan-out, making it trivial to join logs across services.\n *\n * Features:\n * - UUID v4 correlation ID generation (via `crypto.randomUUID()`)\n * - Express / Fastify / NestJS incoming-request middleware\n * - `correlationFetch()` — wraps global `fetch`, auto-injects `X-Correlation-ID`\n * - `createCorrelationAxiosInterceptor()` — axios request interceptor\n * - `childFromRequest(req, logger)` — child logger with all request identifiers\n * - Kafka / SQS / RabbitMQ message context helpers\n * - Integration with `LogixiaContext` (AsyncLocalStorage) so every `logger.info()`\n * call automatically includes the correlationId without per-call passing.\n *\n * @example Express middleware\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use(correlationMiddleware());\n *\n * app.get('/orders', (req, res) => {\n * // correlationId is automatically in every log inside this request\n * logger.info('Listing orders');\n * res.json({ ok: true });\n * });\n * ```\n *\n * @example Outbound fetch with correlation\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Reads correlationId from current LogixiaContext and injects it as header\n * const data = await correlationFetch('https://payments.internal/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n *\n * @example Manual correlation context\n * ```ts\n * import { withCorrelationId } from 'logixia/correlation';\n *\n * await withCorrelationId('trace-abc-123', async () => {\n * // All logs and outbound fetch calls in here carry 'trace-abc-123'\n * await processOrder();\n * });\n * ```\n */\n\nimport { randomUUID } from 'node:crypto';\n\nimport { LogixiaContext } from './context/async-context';\nimport type { HttpRequestShape, HttpResponseShape } from './types/http-shapes';\nimport { buildNameSet, DEFAULT_REDACT_QUERY_PARAMS, redactUrlQuery } from './utils/sensitive-names';\nimport { sanitizeIncomingId } from './utils/trace.utils';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/** Fields injected into the log context by the correlation middleware. */\nexport interface CorrelationContext {\n correlationId: string;\n traceId?: string;\n /** Service that originated the request (from `X-Origin-Service` header). */\n originService?: string;\n /** Index signature so CorrelationContext is assignable to LogContext. */\n [key: string]: unknown;\n}\n\nexport interface CorrelationMiddlewareOptions {\n /**\n * HTTP header to read/write the correlation ID from/to.\n * @default 'x-correlation-id'\n */\n header?: string;\n /**\n * HTTP header for the originating service name.\n * @default 'x-origin-service'\n */\n originServiceHeader?: string;\n /**\n * Function to generate a new correlation ID when none is present in the request.\n * @default () => crypto.randomUUID()\n */\n generate?: () => string;\n /**\n * When `false`, the incoming correlation header is ignored and a new ID is\n * always generated. Use this at the edge when client-supplied IDs should not\n * be trusted.\n * @default true\n */\n trustIncoming?: boolean;\n /**\n * When `true`, the response will include the correlation ID header so\n * clients can log it client-side.\n * @default true\n */\n setResponseHeader?: boolean;\n}\n\n// ── ID generation ─────────────────────────────────────────────────────────────\n\n/**\n * Generate a new correlation ID using `crypto.randomUUID()` (available in\n * Node.js 14.17+, all modern browsers, and Edge runtimes).\n *\n * When the Web Crypto global is unavailable (e.g. Node 18 without\n * `--experimental-global-webcrypto`), fall back to `node:crypto`'s\n * `randomUUID`, which returns a proper UUID v4 — matching the shape this\n * function's JSDoc and README promise. `logixia/correlation` is Node-only\n * (it builds on `AsyncLocalStorage`), so importing `node:crypto` is safe here.\n */\nexport function generateCorrelationId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return randomUUID();\n}\n\n/**\n * Return an incoming correlation ID only if it is short and made of safe\n * characters. Anything else (over-long, empty, or containing newlines or other\n * control characters) is dropped so the caller generates a fresh ID instead of\n * writing client-supplied text into every log line.\n */\nfunction validIncomingId(value: unknown): string | undefined {\n if (typeof value === 'string' && value.trim() !== value) return undefined;\n return sanitizeIncomingId(value);\n}\n\n// ── Context helpers ───────────────────────────────────────────────────────────\n\n/**\n * Return the correlation ID from the current `LogixiaContext`, or `undefined`\n * when called outside any context scope.\n */\nexport function getCurrentCorrelationId(): string | undefined {\n const ctx = LogixiaContext.get();\n return ctx?.correlationId as string | undefined;\n}\n\n/**\n * Run `callback` inside a correlation context scope.\n *\n * If a correlation ID is not provided, a new UUID is generated.\n *\n * @example\n * ```ts\n * await withCorrelationId('cid-abc', async () => {\n * logger.info('Processing message'); // → { correlationId: 'cid-abc', ... }\n * await callDownstream(); // correlationFetch() picks up cid-abc\n * });\n * ```\n */\nexport function withCorrelationId<T>(correlationId: string | undefined, callback: () => T): T {\n const id = correlationId ?? generateCorrelationId();\n return LogixiaContext.run({ correlationId: id }, callback);\n}\n\n// ── Express / Connect middleware ──────────────────────────────────────────────\n\n/**\n * Express-compatible middleware that:\n * 1. Reads `X-Correlation-ID` from the incoming request (or generates a new one)\n * 2. Wraps the request in a `LogixiaContext` so all loggers in the handler\n * automatically include `correlationId`\n * 3. Writes the correlation ID back to the response header (opt-out via options)\n *\n * @example\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * app.use(correlationMiddleware());\n * ```\n */\nexport function correlationMiddleware(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n trustIncoming = true,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationMiddleware(\n req: HttpRequestShape,\n res: HttpResponseShape,\n next: () => void\n ): void {\n const headers = req.headers ?? {};\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const incomingId = trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : undefined;\n const correlationId = incomingId ?? generate();\n const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);\n\n if (setResponseHeader) {\n res.setHeader?.(header, correlationId);\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, next);\n };\n}\n\n// ── Fastify hook ──────────────────────────────────────────────────────────────\n\n/**\n * Fastify `onRequest` lifecycle hook — same functionality as `correlationMiddleware`\n * but adapted to Fastify's hook API.\n *\n * @example\n * ```ts\n * import { correlationFastifyHook } from 'logixia/correlation';\n * fastify.addHook('onRequest', correlationFastifyHook());\n * ```\n */\nexport function correlationFastifyHook(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n trustIncoming = true,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationFastifyHook(\n request: HttpRequestShape,\n reply: HttpResponseShape,\n done: () => void\n ): void {\n const headers = request.headers ?? {};\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const incomingId = trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : undefined;\n const correlationId = incomingId ?? generate();\n const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);\n\n if (setResponseHeader) {\n reply.header?.(header, correlationId);\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, done);\n };\n}\n\n// ── Outbound fetch wrapper ────────────────────────────────────────────────────\n\n/**\n * A drop-in replacement for `fetch` that automatically injects the current\n * `correlationId` from `LogixiaContext` as an `X-Correlation-ID` request header.\n *\n * Falls back to the global `fetch` when no correlation context is active.\n *\n * @example\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Inside a request handler with correlationMiddleware active:\n * const response = await correlationFetch('https://payments.internal/v1/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n */\nexport async function correlationFetch(\n input: string | URL | Request,\n init: RequestInit = {},\n options: { header?: string } = {}\n): Promise<Response> {\n const header = options.header ?? 'x-correlation-id';\n const correlationId = getCurrentCorrelationId();\n\n // `init.headers` can be a plain object, a `Headers` instance, or an array of\n // `[name, value]` tuples. Spreading it into a plain object loses everything\n // but the object form: a `Headers` instance has no own enumerable properties,\n // and an array spread yields numeric indices. `Headers` accepts all three, is\n // case-insensitive, and is the shape `fetch` expects back.\n //\n // A `Request` carries headers of its own, and `fetch(request, { headers })`\n // replaces them rather than merging them, so they are seeded first and the\n // caller's `init.headers` is applied on top. Without this, accepting a\n // `Request` here would drop its headers, which is the same defect one input\n // shape over.\n const headers = new Headers(input instanceof Request ? input.headers : undefined);\n for (const [name, value] of new Headers(init.headers)) {\n headers.set(name, value);\n }\n if (correlationId && !headers.has(header)) {\n headers.set(header, correlationId);\n }\n\n return globalThis.fetch(input, { ...init, headers });\n}\n\n// ── Axios interceptor factory ─────────────────────────────────────────────────\n\n/**\n * Returns an axios request interceptor that automatically injects the current\n * correlation ID into outgoing axios requests.\n *\n * @example\n * ```ts\n * import axios from 'axios';\n * import { createCorrelationAxiosInterceptor } from 'logixia/correlation';\n *\n * axios.interceptors.request.use(createCorrelationAxiosInterceptor());\n * ```\n */\nexport function createCorrelationAxiosInterceptor(\n options: { header?: string } = {}\n): (config: Record<string, unknown>) => Record<string, unknown> {\n const header = options.header ?? 'x-correlation-id';\n\n return function correlationAxiosInterceptor(\n config: Record<string, unknown>\n ): Record<string, unknown> {\n const correlationId = getCurrentCorrelationId();\n if (!correlationId) return config;\n\n const headers = (config['headers'] ?? {}) as Record<string, unknown>;\n if (!headers[header]) {\n headers[header] = correlationId;\n }\n\n return { ...config, headers };\n };\n}\n\n// ── Child logger from request ─────────────────────────────────────────────────\n\nconst MAX_USER_AGENT_LENGTH = 512;\nconst MAX_IP_LENGTH = 64;\n\nexport interface ChildFromRequestOptions {\n /** Header to read the correlation ID from. @default 'x-correlation-id' */\n correlationHeader?: string;\n /**\n * Take the client IP from the first `X-Forwarded-For` entry. Only enable this\n * when the app sits behind a proxy that overwrites the header; otherwise any\n * client can choose the logged IP.\n * @default false\n */\n trustProxy?: boolean;\n /**\n * Context name passed as the first argument to `logger.child(context, data)`.\n * When omitted, logixia loggers (detected by their `getContext` /\n * `getAvailableTransports` methods) get `child('request', fields)` and any other\n * logger gets `child(fields)`.\n */\n childContext?: string;\n /**\n * Extra query parameter names redacted in the bound `url` field, on top of the\n * defaults (`token`, `code`, `password`, `api_key`, …). A bare logger exposes no\n * `redact.queryParams`, so pass the same list you gave `LoggerConfig.redact`.\n */\n redactQueryParams?: readonly string[];\n}\n\n/**\n * Create a child logger enriched with all correlation-relevant identifiers\n * extracted from the incoming HTTP request.\n *\n * The child logger's every call automatically includes:\n * `correlationId`, `method`, `url`, `userAgent`, `ip`\n *\n * The IP comes from `req.ip` / the socket address unless `trustProxy` is set.\n *\n * @example\n * ```ts\n * import { childFromRequest } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use((req, res, next) => {\n * req.logger = childFromRequest(req, logger);\n * next();\n * });\n *\n * app.get('/orders', (req, res) => {\n * req.logger.info('Listing orders'); // → correlationId, requestId, method, url ...\n * });\n * ```\n */\nexport function childFromRequest<TLogger extends { child(...args: never[]): TLogger }>(\n req: HttpRequestShape,\n logger: TLogger,\n options: ChildFromRequestOptions = {}\n): TLogger {\n const correlationHeader = options.correlationHeader ?? 'x-correlation-id';\n\n const headers = req.headers ?? {};\n // Node lower-cases incoming header names, so look up with a lower-cased key.\n const correlationId =\n validIncomingId(headers[correlationHeader.toLowerCase()]) ??\n getCurrentCorrelationId() ??\n generateCorrelationId();\n\n const method = req.method;\n const rawUrl = req.originalUrl ?? req.url;\n const url =\n rawUrl === undefined\n ? undefined\n : redactUrlQuery(\n rawUrl,\n buildNameSet(DEFAULT_REDACT_QUERY_PARAMS, options.redactQueryParams)\n );\n const rawUserAgent = headers['user-agent'];\n const userAgent =\n typeof rawUserAgent === 'string' ? rawUserAgent.slice(0, MAX_USER_AGENT_LENGTH) : undefined;\n const ip = resolveClientIp(req, headers, options.trustProxy === true);\n\n const fields = {\n correlationId,\n ...(method !== undefined ? { method } : {}),\n ...(url !== undefined ? { url } : {}),\n ...(userAgent !== undefined ? { userAgent } : {}),\n ...(ip !== undefined ? { ip } : {}),\n };\n\n const child = logger.child as (...args: unknown[]) => TLogger;\n if (options.childContext !== undefined) {\n return child.call(logger, options.childContext, fields);\n }\n // logixia's ILogger.child(context: string, data?) takes a string namespace;\n // pino/winston-style loggers take a bindings object.\n if (usesStringContext(logger)) {\n return child.call(logger, 'request', fields);\n }\n return child.call(logger, fields);\n}\n\n/** ILogger implementations (LogixiaLogger, LogixiaLoggerService, mock logger) expose these methods. */\nfunction usesStringContext(logger: object): boolean {\n const l = logger as Record<string, unknown>;\n return typeof l['getContext'] === 'function' || typeof l['getAvailableTransports'] === 'function';\n}\n\nfunction resolveClientIp(\n req: HttpRequestShape,\n headers: Record<string, string | string[] | undefined>,\n trustProxy: boolean\n): string | undefined {\n if (trustProxy) {\n const forwarded = headers['x-forwarded-for'];\n const first = typeof forwarded === 'string' ? forwarded.split(',')[0]?.trim() : undefined;\n if (first) return first.slice(0, MAX_IP_LENGTH);\n }\n const fromReq = req.ip;\n if (typeof fromReq === 'string' && fromReq) return fromReq.slice(0, MAX_IP_LENGTH);\n const remote = req.socket?.remoteAddress;\n return typeof remote === 'string' ? remote.slice(0, MAX_IP_LENGTH) : undefined;\n}\n\n// ── Message queue helpers ─────────────────────────────────────────────────────\n\n/**\n * Extract a correlation context from a Kafka / RabbitMQ / SQS message object.\n *\n * Looks for `correlationId`, `correlation_id`, or `x-correlation-id` in\n * `message.headers` (Kafka) or `message.MessageAttributes` (SQS).\n *\n * @example Kafka consumer\n * ```ts\n * import { extractMessageCorrelationId, withCorrelationId } from 'logixia/correlation';\n *\n * consumer.run({\n * eachMessage: async ({ message }) => {\n * const correlationId = extractMessageCorrelationId(message);\n * await withCorrelationId(correlationId, () => processMessage(message));\n * },\n * });\n * ```\n */\nexport function extractMessageCorrelationId(message: Record<string, unknown>): string | undefined {\n // Kafka: message.headers is Record<string, Buffer | string>\n const kafkaHeaders = message['headers'] as Record<string, Buffer | string> | undefined;\n if (kafkaHeaders) {\n const raw =\n kafkaHeaders['x-correlation-id'] ??\n kafkaHeaders['correlationId'] ??\n kafkaHeaders['correlation_id'];\n if (raw !== undefined) {\n return validIncomingId(Buffer.isBuffer(raw) ? raw.toString('utf8', 0, 512) : String(raw));\n }\n }\n\n // SQS: message.MessageAttributes\n const sqsAttrs = message['MessageAttributes'] as\n Record<string, { StringValue?: string }> | undefined;\n if (sqsAttrs) {\n return validIncomingId(\n sqsAttrs['x-correlation-id']?.StringValue ??\n sqsAttrs['correlationId']?.StringValue ??\n sqsAttrs['correlation_id']?.StringValue\n );\n }\n\n // AMQP / generic envelope\n return validIncomingId(message['correlationId'] ?? message['correlation_id']);\n}\n\n/**\n * Build Kafka message headers containing the current correlation context.\n *\n * @example\n * ```ts\n * import { buildKafkaCorrelationHeaders } from 'logixia/correlation';\n *\n * await producer.send({\n * topic: 'orders',\n * messages: [{ value: JSON.stringify(order), headers: buildKafkaCorrelationHeaders() }],\n * });\n * ```\n */\nexport function buildKafkaCorrelationHeaders(): Record<string, string> {\n const ctx = LogixiaContext.get();\n const headers: Record<string, string> = {};\n\n const correlationId = ctx?.correlationId as string | undefined;\n if (correlationId) headers['x-correlation-id'] = correlationId;\n\n const traceId = ctx?.traceId as string | undefined;\n if (traceId) headers['x-trace-id'] = traceId;\n\n return headers;\n}\n"],"mappings":"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"}