logixia 1.12.4 → 1.12.6

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 (166) hide show
  1. package/README.md +80 -27
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/async-context-CSHWoHMB.js +88 -0
  4. package/dist/async-context-CSHWoHMB.js.map +1 -0
  5. package/dist/async-context-CwBNMm2E.mjs +68 -0
  6. package/dist/async-context-CwBNMm2E.mjs.map +1 -0
  7. package/dist/azure-monitor.transport-CG7ClkYj.d.ts +62 -0
  8. package/dist/azure-monitor.transport-CG7ClkYj.d.ts.map +1 -0
  9. package/dist/azure-monitor.transport-CHnjVRF-.mjs +152 -0
  10. package/dist/azure-monitor.transport-CHnjVRF-.mjs.map +1 -0
  11. package/dist/azure-monitor.transport-Cm74kXZS.d.mts +62 -0
  12. package/dist/azure-monitor.transport-Cm74kXZS.d.mts.map +1 -0
  13. package/dist/azure-monitor.transport-CrvfHd9C.js +157 -0
  14. package/dist/azure-monitor.transport-CrvfHd9C.js.map +1 -0
  15. package/dist/azure.d.mts +3 -0
  16. package/dist/azure.d.ts +3 -0
  17. package/dist/azure.js +6 -0
  18. package/dist/azure.mjs +6 -0
  19. package/dist/batch-buffer-BY-mRWIK.js +170 -0
  20. package/dist/batch-buffer-BY-mRWIK.js.map +1 -0
  21. package/dist/batch-buffer-PGvSgbmd.mjs +132 -0
  22. package/dist/batch-buffer-PGvSgbmd.mjs.map +1 -0
  23. package/dist/browser.d.mts +427 -0
  24. package/dist/browser.d.mts.map +1 -0
  25. package/dist/browser.d.ts +427 -0
  26. package/dist/browser.d.ts.map +1 -0
  27. package/dist/browser.js +590 -0
  28. package/dist/browser.js.map +1 -0
  29. package/dist/browser.mjs +586 -0
  30. package/dist/browser.mjs.map +1 -0
  31. package/dist/chunk-DLSkYcPk.mjs +7 -0
  32. package/dist/chunk-nOFOJqeH.js +30 -0
  33. package/dist/cloudwatch.d.mts +3 -0
  34. package/dist/cloudwatch.d.ts +3 -0
  35. package/dist/cloudwatch.js +6 -0
  36. package/dist/cloudwatch.mjs +6 -0
  37. package/dist/cloudwatch.transport-CGHY4buE.d.mts +81 -0
  38. package/dist/cloudwatch.transport-CGHY4buE.d.mts.map +1 -0
  39. package/dist/cloudwatch.transport-DK_8cKd3.d.ts +81 -0
  40. package/dist/cloudwatch.transport-DK_8cKd3.d.ts.map +1 -0
  41. package/dist/cloudwatch.transport-pNBNWJLt.js +370 -0
  42. package/dist/cloudwatch.transport-pNBNWJLt.js.map +1 -0
  43. package/dist/cloudwatch.transport-yBwm4OeO.mjs +360 -0
  44. package/dist/cloudwatch.transport-yBwm4OeO.mjs.map +1 -0
  45. package/dist/coerce.utils-BN980Y9V.js +44 -0
  46. package/dist/coerce.utils-BN980Y9V.js.map +1 -0
  47. package/dist/coerce.utils-Dxv4_ikC.mjs +38 -0
  48. package/dist/coerce.utils-Dxv4_ikC.mjs.map +1 -0
  49. package/dist/correlation.d.mts +238 -0
  50. package/dist/correlation.d.mts.map +1 -0
  51. package/dist/correlation.d.ts +238 -0
  52. package/dist/correlation.d.ts.map +1 -0
  53. package/dist/correlation.js +272 -0
  54. package/dist/correlation.js.map +1 -0
  55. package/dist/correlation.mjs +261 -0
  56. package/dist/correlation.mjs.map +1 -0
  57. package/dist/gcp.d.mts +3 -0
  58. package/dist/gcp.d.ts +3 -0
  59. package/dist/gcp.js +6 -0
  60. package/dist/gcp.mjs +6 -0
  61. package/dist/gcp.transport-BJuNRFe_.mjs +192 -0
  62. package/dist/gcp.transport-BJuNRFe_.mjs.map +1 -0
  63. package/dist/gcp.transport-BOhkVKlI.d.ts +71 -0
  64. package/dist/gcp.transport-BOhkVKlI.d.ts.map +1 -0
  65. package/dist/gcp.transport-Bi6CUqZR.d.mts +71 -0
  66. package/dist/gcp.transport-Bi6CUqZR.d.mts.map +1 -0
  67. package/dist/gcp.transport-SHdtc1n0.js +198 -0
  68. package/dist/gcp.transport-SHdtc1n0.js.map +1 -0
  69. package/dist/{index-CwFww7VC.d.ts → index-BPKwwERo.d.mts} +18 -174
  70. package/dist/index-BPKwwERo.d.mts.map +1 -0
  71. package/dist/{index-DnyeaoZU.d.mts → index-C1X-TE5I.d.ts} +18 -174
  72. package/dist/index-C1X-TE5I.d.ts.map +1 -0
  73. package/dist/index.d.mts +41 -11
  74. package/dist/index.d.mts.map +1 -1
  75. package/dist/index.d.ts +40 -10
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +87 -147
  78. package/dist/index.js.map +1 -1
  79. package/dist/index.mjs +62 -132
  80. package/dist/index.mjs.map +1 -1
  81. package/dist/internal-log-BUJYMZbE.js +70 -0
  82. package/dist/internal-log-BUJYMZbE.js.map +1 -0
  83. package/dist/internal-log-Cm8SOWSV.mjs +52 -0
  84. package/dist/internal-log-Cm8SOWSV.mjs.map +1 -0
  85. package/dist/{logitron-logger.module-BIGTm2V6.mjs → logitron-logger.module-Bs3XYZgF.mjs} +266 -166
  86. package/dist/logitron-logger.module-Bs3XYZgF.mjs.map +1 -0
  87. package/dist/{logitron-logger.module-BTxDpK6u.js → logitron-logger.module-BsDZR5BH.js} +297 -203
  88. package/dist/logitron-logger.module-BsDZR5BH.js.map +1 -0
  89. package/dist/{logitron-logger.module-BmT-ewin.d.ts → logitron-logger.module-CxT1F6Cm.d.ts} +23 -10
  90. package/dist/logitron-logger.module-CxT1F6Cm.d.ts.map +1 -0
  91. package/dist/{logitron-logger.module-BCq3sF27.d.mts → logitron-logger.module-zDKVd_o0.d.mts} +23 -10
  92. package/dist/logitron-logger.module-zDKVd_o0.d.mts.map +1 -0
  93. package/dist/middleware.d.mts +5 -2
  94. package/dist/middleware.d.mts.map +1 -1
  95. package/dist/middleware.d.ts +5 -2
  96. package/dist/middleware.d.ts.map +1 -1
  97. package/dist/middleware.js +13 -4
  98. package/dist/middleware.js.map +1 -1
  99. package/dist/middleware.mjs +14 -4
  100. package/dist/middleware.mjs.map +1 -1
  101. package/dist/nest.d.mts +3 -2
  102. package/dist/nest.d.mts.map +1 -1
  103. package/dist/nest.d.ts +3 -2
  104. package/dist/nest.d.ts.map +1 -1
  105. package/dist/nest.js +6 -2
  106. package/dist/nest.mjs +6 -2
  107. package/dist/safe-stringify-DJ66tW5I.mjs +118 -0
  108. package/dist/safe-stringify-DJ66tW5I.mjs.map +1 -0
  109. package/dist/safe-stringify-DWo3aZp_.js +142 -0
  110. package/dist/safe-stringify-DWo3aZp_.js.map +1 -0
  111. package/dist/search-CFQRISUv.mjs +1596 -0
  112. package/dist/search-CFQRISUv.mjs.map +1 -0
  113. package/dist/search-CQOo-Iq6.d.mts +816 -0
  114. package/dist/search-CQOo-Iq6.d.mts.map +1 -0
  115. package/dist/search-DujhR94O.d.ts +816 -0
  116. package/dist/search-DujhR94O.d.ts.map +1 -0
  117. package/dist/search-njTI2hdo.js +1631 -0
  118. package/dist/search-njTI2hdo.js.map +1 -0
  119. package/dist/search.d.mts +4 -1
  120. package/dist/search.d.ts +4 -1
  121. package/dist/search.js +9 -1
  122. package/dist/search.mjs +3 -2
  123. package/dist/testing.d.mts +2 -1
  124. package/dist/testing.d.mts.map +1 -1
  125. package/dist/testing.d.ts +2 -1
  126. package/dist/testing.d.ts.map +1 -1
  127. package/dist/{transport.manager-ciG4r5uz.js → transport.manager-BivyUTtU.js} +497 -437
  128. package/dist/transport.manager-BivyUTtU.js.map +1 -0
  129. package/dist/{transport.manager-DETpXuaY.mjs → transport.manager-D6KL3t9V.mjs} +473 -364
  130. package/dist/transport.manager-D6KL3t9V.mjs.map +1 -0
  131. package/dist/transport.types-B5vcyv2k.d.mts +187 -0
  132. package/dist/transport.types-B5vcyv2k.d.mts.map +1 -0
  133. package/dist/transport.types-BuY84TwO.d.ts +187 -0
  134. package/dist/transport.types-BuY84TwO.d.ts.map +1 -0
  135. package/dist/transports.d.mts +90 -29
  136. package/dist/transports.d.mts.map +1 -1
  137. package/dist/transports.d.ts +90 -29
  138. package/dist/transports.d.ts.map +1 -1
  139. package/dist/transports.js +14 -2
  140. package/dist/transports.mjs +10 -2
  141. package/dist/worker-entry.js +1241 -0
  142. package/dist/worker.d.mts +3 -0
  143. package/dist/worker.d.ts +3 -0
  144. package/dist/worker.js +4 -0
  145. package/dist/worker.mjs +4 -0
  146. package/dist/worker.transport-BP8Q28Z2.d.mts +76 -0
  147. package/dist/worker.transport-BP8Q28Z2.d.mts.map +1 -0
  148. package/dist/worker.transport-BhMyvqQ2.mjs +167 -0
  149. package/dist/worker.transport-BhMyvqQ2.mjs.map +1 -0
  150. package/dist/worker.transport-CfH6z37Z.js +168 -0
  151. package/dist/worker.transport-CfH6z37Z.js.map +1 -0
  152. package/dist/worker.transport-mKXgNK0L.d.ts +76 -0
  153. package/dist/worker.transport-mKXgNK0L.d.ts.map +1 -0
  154. package/package.json +23 -18
  155. package/dist/index-CwFww7VC.d.ts.map +0 -1
  156. package/dist/index-DnyeaoZU.d.mts.map +0 -1
  157. package/dist/logitron-logger.module-BCq3sF27.d.mts.map +0 -1
  158. package/dist/logitron-logger.module-BIGTm2V6.mjs.map +0 -1
  159. package/dist/logitron-logger.module-BTxDpK6u.js.map +0 -1
  160. package/dist/logitron-logger.module-BmT-ewin.d.ts.map +0 -1
  161. package/dist/search-Cg_OasF-.d.ts +0 -1
  162. package/dist/search-DN676Dnz.d.mts +0 -1
  163. package/dist/search-DOvSI-mb.mjs +0 -1
  164. package/dist/search-DoZF3RZj.js +0 -0
  165. package/dist/transport.manager-DETpXuaY.mjs.map +0 -1
  166. package/dist/transport.manager-ciG4r5uz.js.map +0 -1
@@ -0,0 +1,272 @@
1
+ const require_chunk = require('./chunk-nOFOJqeH.js');
2
+ const require_async_context = require('./async-context-CSHWoHMB.js');
3
+ let node_crypto = require("node:crypto");
4
+ node_crypto = require_chunk.__toESM(node_crypto);
5
+
6
+ //#region src/correlation.ts
7
+ /**
8
+ * Generate a new correlation ID using `crypto.randomUUID()` (available in
9
+ * Node.js 14.17+, all modern browsers, and Edge runtimes).
10
+ *
11
+ * When the Web Crypto global is unavailable (e.g. Node 18 without
12
+ * `--experimental-global-webcrypto`), fall back to `node:crypto`'s
13
+ * `randomUUID`, which returns a proper UUID v4 — matching the shape this
14
+ * function's JSDoc and README promise. `logixia/correlation` is Node-only
15
+ * (it builds on `AsyncLocalStorage`), so importing `node:crypto` is safe here.
16
+ */
17
+ function generateCorrelationId() {
18
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
19
+ return (0, node_crypto.randomUUID)();
20
+ }
21
+ const MAX_INCOMING_ID_LENGTH = 128;
22
+ const VALID_INCOMING_ID = /^[A-Za-z0-9._:-]+$/;
23
+ /**
24
+ * Return an incoming correlation ID only if it is short and made of safe
25
+ * characters. Anything else (over-long, empty, or containing newlines or other
26
+ * control characters) is dropped so the caller generates a fresh ID instead of
27
+ * writing client-supplied text into every log line.
28
+ */
29
+ function validIncomingId(value) {
30
+ if (value === void 0 || value.length > MAX_INCOMING_ID_LENGTH) return void 0;
31
+ return VALID_INCOMING_ID.test(value) ? value : void 0;
32
+ }
33
+ /**
34
+ * Return the correlation ID from the current `LogixiaContext`, or `undefined`
35
+ * when called outside any context scope.
36
+ */
37
+ function getCurrentCorrelationId() {
38
+ const ctx = require_async_context.LogixiaContext.get();
39
+ return ctx === null || ctx === void 0 ? void 0 : ctx.correlationId;
40
+ }
41
+ /**
42
+ * Run `callback` inside a correlation context scope.
43
+ *
44
+ * If a correlation ID is not provided, a new UUID is generated.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * await withCorrelationId('cid-abc', async () => {
49
+ * logger.info('Processing message'); // → { correlationId: 'cid-abc', ... }
50
+ * await callDownstream(); // correlationFetch() picks up cid-abc
51
+ * });
52
+ * ```
53
+ */
54
+ function withCorrelationId(correlationId, callback) {
55
+ const id = correlationId ?? generateCorrelationId();
56
+ return require_async_context.LogixiaContext.run({ correlationId: id }, callback);
57
+ }
58
+ /**
59
+ * Express-compatible middleware that:
60
+ * 1. Reads `X-Correlation-ID` from the incoming request (or generates a new one)
61
+ * 2. Wraps the request in a `LogixiaContext` so all loggers in the handler
62
+ * automatically include `correlationId`
63
+ * 3. Writes the correlation ID back to the response header (opt-out via options)
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * import { correlationMiddleware } from 'logixia/correlation';
68
+ * app.use(correlationMiddleware());
69
+ * ```
70
+ */
71
+ function correlationMiddleware(options = {}) {
72
+ const { header = "x-correlation-id", originServiceHeader = "x-origin-service", generate = generateCorrelationId, setResponseHeader = true } = options;
73
+ return function logixiaCorrelationMiddleware(req, res, next) {
74
+ const headers = req["headers"] ?? {};
75
+ const correlationId = validIncomingId(headers[header.toLowerCase()]) ?? generate();
76
+ const originService = headers[originServiceHeader.toLowerCase()];
77
+ if (setResponseHeader) {
78
+ const setHeader = res["setHeader"];
79
+ if (typeof setHeader === "function") setHeader.call(res, header, correlationId);
80
+ }
81
+ const ctx = {
82
+ correlationId,
83
+ ...originService !== void 0 ? { originService } : {}
84
+ };
85
+ require_async_context.LogixiaContext.run(ctx, next);
86
+ };
87
+ }
88
+ /**
89
+ * Fastify `onRequest` lifecycle hook — same functionality as `correlationMiddleware`
90
+ * but adapted to Fastify's hook API.
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * import { correlationFastifyHook } from 'logixia/correlation';
95
+ * fastify.addHook('onRequest', correlationFastifyHook());
96
+ * ```
97
+ */
98
+ function correlationFastifyHook(options = {}) {
99
+ const { header = "x-correlation-id", originServiceHeader = "x-origin-service", generate = generateCorrelationId, setResponseHeader = true } = options;
100
+ return function logixiaCorrelationFastifyHook(request, reply, done) {
101
+ const headers = request["headers"] ?? {};
102
+ const correlationId = validIncomingId(headers[header.toLowerCase()]) ?? generate();
103
+ const originService = headers[originServiceHeader.toLowerCase()];
104
+ if (setResponseHeader) {
105
+ const replyHeader = reply["header"];
106
+ if (typeof replyHeader === "function") replyHeader.call(reply, header, correlationId);
107
+ }
108
+ const ctx = {
109
+ correlationId,
110
+ ...originService !== void 0 ? { originService } : {}
111
+ };
112
+ require_async_context.LogixiaContext.run(ctx, done);
113
+ };
114
+ }
115
+ /**
116
+ * A drop-in replacement for `fetch` that automatically injects the current
117
+ * `correlationId` from `LogixiaContext` as an `X-Correlation-ID` request header.
118
+ *
119
+ * Falls back to the global `fetch` when no correlation context is active.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * import { correlationFetch } from 'logixia/correlation';
124
+ *
125
+ * // Inside a request handler with correlationMiddleware active:
126
+ * const response = await correlationFetch('https://payments.internal/v1/charge', {
127
+ * method: 'POST',
128
+ * body: JSON.stringify({ amount: 100 }),
129
+ * });
130
+ * ```
131
+ */
132
+ async function correlationFetch(input, init = {}, options = {}) {
133
+ const header = options.header ?? "x-correlation-id";
134
+ const correlationId = getCurrentCorrelationId();
135
+ const headers = new Headers(input instanceof Request ? input.headers : void 0);
136
+ for (const [name, value] of new Headers(init.headers)) headers.set(name, value);
137
+ if (correlationId && !headers.has(header)) headers.set(header, correlationId);
138
+ return globalThis.fetch(input, {
139
+ ...init,
140
+ headers
141
+ });
142
+ }
143
+ /**
144
+ * Returns an axios request interceptor that automatically injects the current
145
+ * correlation ID into outgoing axios requests.
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * import axios from 'axios';
150
+ * import { createCorrelationAxiosInterceptor } from 'logixia/correlation';
151
+ *
152
+ * axios.interceptors.request.use(createCorrelationAxiosInterceptor());
153
+ * ```
154
+ */
155
+ function createCorrelationAxiosInterceptor(options = {}) {
156
+ const header = options.header ?? "x-correlation-id";
157
+ return function correlationAxiosInterceptor(config) {
158
+ const correlationId = getCurrentCorrelationId();
159
+ if (!correlationId) return config;
160
+ const headers = config["headers"] ?? {};
161
+ if (!headers[header]) headers[header] = correlationId;
162
+ return {
163
+ ...config,
164
+ headers
165
+ };
166
+ };
167
+ }
168
+ /**
169
+ * Create a child logger enriched with all correlation-relevant identifiers
170
+ * extracted from the incoming HTTP request.
171
+ *
172
+ * The child logger's every call automatically includes:
173
+ * `correlationId`, `requestId`, `method`, `url`, `userAgent`, `ip`
174
+ *
175
+ * @example
176
+ * ```ts
177
+ * import { childFromRequest } from 'logixia/correlation';
178
+ * import { logger } from 'logixia';
179
+ *
180
+ * app.use((req, res, next) => {
181
+ * req.logger = childFromRequest(req, logger);
182
+ * next();
183
+ * });
184
+ *
185
+ * app.get('/orders', (req, res) => {
186
+ * req.logger.info('Listing orders'); // → correlationId, requestId, method, url ...
187
+ * });
188
+ * ```
189
+ */
190
+ function childFromRequest(req, logger, options = {}) {
191
+ var _headers$xForwarded, _req$socket;
192
+ const correlationHeader = options.correlationHeader ?? "x-correlation-id";
193
+ const headers = req["headers"] ?? {};
194
+ const correlationId = validIncomingId(headers[correlationHeader.toLowerCase()]) ?? getCurrentCorrelationId() ?? generateCorrelationId();
195
+ const method = req["method"];
196
+ const url = req["url"] ?? req["originalUrl"];
197
+ const userAgent = headers["user-agent"];
198
+ const ip = ((_headers$xForwarded = headers["x-forwarded-for"]) === null || _headers$xForwarded === void 0 || (_headers$xForwarded = _headers$xForwarded.split(",")[0]) === null || _headers$xForwarded === void 0 ? void 0 : _headers$xForwarded.trim()) ?? ((_req$socket = req["socket"]) === null || _req$socket === void 0 ? void 0 : _req$socket["remoteAddress"]);
199
+ return logger.child({
200
+ correlationId,
201
+ ...method !== void 0 ? { method } : {},
202
+ ...url !== void 0 ? { url } : {},
203
+ ...userAgent !== void 0 ? { userAgent } : {},
204
+ ...ip !== void 0 ? { ip } : {}
205
+ });
206
+ }
207
+ /**
208
+ * Extract a correlation context from a Kafka / RabbitMQ / SQS message object.
209
+ *
210
+ * Looks for `correlationId`, `correlation_id`, or `x-correlation-id` in
211
+ * `message.headers` (Kafka) or `message.MessageAttributes` (SQS).
212
+ *
213
+ * @example Kafka consumer
214
+ * ```ts
215
+ * import { extractMessageCorrelationId, withCorrelationId } from 'logixia/correlation';
216
+ *
217
+ * consumer.run({
218
+ * eachMessage: async ({ message }) => {
219
+ * const correlationId = extractMessageCorrelationId(message);
220
+ * await withCorrelationId(correlationId, () => processMessage(message));
221
+ * },
222
+ * });
223
+ * ```
224
+ */
225
+ function extractMessageCorrelationId(message) {
226
+ const kafkaHeaders = message["headers"];
227
+ if (kafkaHeaders) {
228
+ const raw = kafkaHeaders["x-correlation-id"] ?? kafkaHeaders["correlationId"] ?? kafkaHeaders["correlation_id"];
229
+ if (raw !== void 0) return Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
230
+ }
231
+ const sqsAttrs = message["MessageAttributes"];
232
+ if (sqsAttrs) {
233
+ var _sqsAttrs$xCorrelati, _sqsAttrs$correlation, _sqsAttrs$correlation2;
234
+ return ((_sqsAttrs$xCorrelati = sqsAttrs["x-correlation-id"]) === null || _sqsAttrs$xCorrelati === void 0 ? void 0 : _sqsAttrs$xCorrelati.StringValue) ?? ((_sqsAttrs$correlation = sqsAttrs["correlationId"]) === null || _sqsAttrs$correlation === void 0 ? void 0 : _sqsAttrs$correlation.StringValue) ?? ((_sqsAttrs$correlation2 = sqsAttrs["correlation_id"]) === null || _sqsAttrs$correlation2 === void 0 ? void 0 : _sqsAttrs$correlation2.StringValue);
235
+ }
236
+ return message["correlationId"] ?? message["correlation_id"];
237
+ }
238
+ /**
239
+ * Build Kafka message headers containing the current correlation context.
240
+ *
241
+ * @example
242
+ * ```ts
243
+ * import { buildKafkaCorrelationHeaders } from 'logixia/correlation';
244
+ *
245
+ * await producer.send({
246
+ * topic: 'orders',
247
+ * messages: [{ value: JSON.stringify(order), headers: buildKafkaCorrelationHeaders() }],
248
+ * });
249
+ * ```
250
+ */
251
+ function buildKafkaCorrelationHeaders() {
252
+ const ctx = require_async_context.LogixiaContext.get();
253
+ const headers = {};
254
+ const correlationId = ctx === null || ctx === void 0 ? void 0 : ctx.correlationId;
255
+ if (correlationId) headers["x-correlation-id"] = correlationId;
256
+ const traceId = ctx === null || ctx === void 0 ? void 0 : ctx.traceId;
257
+ if (traceId) headers["x-trace-id"] = traceId;
258
+ return headers;
259
+ }
260
+
261
+ //#endregion
262
+ exports.buildKafkaCorrelationHeaders = buildKafkaCorrelationHeaders;
263
+ exports.childFromRequest = childFromRequest;
264
+ exports.correlationFastifyHook = correlationFastifyHook;
265
+ exports.correlationFetch = correlationFetch;
266
+ exports.correlationMiddleware = correlationMiddleware;
267
+ exports.createCorrelationAxiosInterceptor = createCorrelationAxiosInterceptor;
268
+ exports.extractMessageCorrelationId = extractMessageCorrelationId;
269
+ exports.generateCorrelationId = generateCorrelationId;
270
+ exports.getCurrentCorrelationId = getCurrentCorrelationId;
271
+ exports.withCorrelationId = withCorrelationId;
272
+ //# sourceMappingURL=correlation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation.js","names":["LogixiaContext","ctx: CorrelationContext","headers: Record<string, string>"],"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';\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 `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\nconst MAX_INCOMING_ID_LENGTH = 128;\nconst VALID_INCOMING_ID = /^[A-Za-z0-9._:-]+$/;\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: string | undefined): string | undefined {\n if (value === undefined || value.length > MAX_INCOMING_ID_LENGTH) return undefined;\n return VALID_INCOMING_ID.test(value) ? value : undefined;\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 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 correlationId = validIncomingId(headers[header.toLowerCase()]) ?? generate();\n const originService = 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 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 correlationId = validIncomingId(headers[header.toLowerCase()]) ?? generate();\n const originService = 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\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`, `requestId`, `method`, `url`, `userAgent`, `ip`\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(ctx: Record<string, unknown>): TLogger }>(\n req: Record<string, unknown>,\n logger: TLogger,\n options: { correlationHeader?: string } = {}\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 userAgent = headers['user-agent'];\n const ip =\n (headers['x-forwarded-for'] as string | undefined)?.split(',')[0]?.trim() ??\n ((req['socket'] as Record<string, unknown> | undefined)?.['remoteAddress'] as\n string | undefined);\n\n return logger.child({\n correlationId,\n ...(method !== undefined ? { method } : {}),\n ...(url !== undefined ? { url } : {}),\n ...(userAgent !== undefined ? { userAgent } : {}),\n ...(ip !== undefined ? { ip } : {}),\n });\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 Buffer.isBuffer(raw) ? raw.toString('utf8') : 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 (\n sqsAttrs['x-correlation-id']?.StringValue ??\n sqsAttrs['correlationId']?.StringValue ??\n sqsAttrs['correlation_id']?.StringValue\n );\n }\n\n // AMQP / generic envelope\n return (\n (message['correlationId'] as string | undefined) ??\n (message['correlation_id'] as string | undefined)\n );\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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"}
@@ -0,0 +1,261 @@
1
+ import { t as LogixiaContext } from "./async-context-CwBNMm2E.mjs";
2
+ import { randomUUID } from "node:crypto";
3
+
4
+ //#region src/correlation.ts
5
+ /**
6
+ * Generate a new correlation ID using `crypto.randomUUID()` (available in
7
+ * Node.js 14.17+, all modern browsers, and Edge runtimes).
8
+ *
9
+ * When the Web Crypto global is unavailable (e.g. Node 18 without
10
+ * `--experimental-global-webcrypto`), fall back to `node:crypto`'s
11
+ * `randomUUID`, which returns a proper UUID v4 — matching the shape this
12
+ * function's JSDoc and README promise. `logixia/correlation` is Node-only
13
+ * (it builds on `AsyncLocalStorage`), so importing `node:crypto` is safe here.
14
+ */
15
+ function generateCorrelationId() {
16
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
17
+ return randomUUID();
18
+ }
19
+ const MAX_INCOMING_ID_LENGTH = 128;
20
+ const VALID_INCOMING_ID = /^[A-Za-z0-9._:-]+$/;
21
+ /**
22
+ * Return an incoming correlation ID only if it is short and made of safe
23
+ * characters. Anything else (over-long, empty, or containing newlines or other
24
+ * control characters) is dropped so the caller generates a fresh ID instead of
25
+ * writing client-supplied text into every log line.
26
+ */
27
+ function validIncomingId(value) {
28
+ if (value === void 0 || value.length > MAX_INCOMING_ID_LENGTH) return void 0;
29
+ return VALID_INCOMING_ID.test(value) ? value : void 0;
30
+ }
31
+ /**
32
+ * Return the correlation ID from the current `LogixiaContext`, or `undefined`
33
+ * when called outside any context scope.
34
+ */
35
+ function getCurrentCorrelationId() {
36
+ const ctx = LogixiaContext.get();
37
+ return ctx === null || ctx === void 0 ? void 0 : ctx.correlationId;
38
+ }
39
+ /**
40
+ * Run `callback` inside a correlation context scope.
41
+ *
42
+ * If a correlation ID is not provided, a new UUID is generated.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * await withCorrelationId('cid-abc', async () => {
47
+ * logger.info('Processing message'); // → { correlationId: 'cid-abc', ... }
48
+ * await callDownstream(); // correlationFetch() picks up cid-abc
49
+ * });
50
+ * ```
51
+ */
52
+ function withCorrelationId(correlationId, callback) {
53
+ const id = correlationId ?? generateCorrelationId();
54
+ return LogixiaContext.run({ correlationId: id }, callback);
55
+ }
56
+ /**
57
+ * Express-compatible middleware that:
58
+ * 1. Reads `X-Correlation-ID` from the incoming request (or generates a new one)
59
+ * 2. Wraps the request in a `LogixiaContext` so all loggers in the handler
60
+ * automatically include `correlationId`
61
+ * 3. Writes the correlation ID back to the response header (opt-out via options)
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * import { correlationMiddleware } from 'logixia/correlation';
66
+ * app.use(correlationMiddleware());
67
+ * ```
68
+ */
69
+ function correlationMiddleware(options = {}) {
70
+ const { header = "x-correlation-id", originServiceHeader = "x-origin-service", generate = generateCorrelationId, setResponseHeader = true } = options;
71
+ return function logixiaCorrelationMiddleware(req, res, next) {
72
+ const headers = req["headers"] ?? {};
73
+ const correlationId = validIncomingId(headers[header.toLowerCase()]) ?? generate();
74
+ const originService = headers[originServiceHeader.toLowerCase()];
75
+ if (setResponseHeader) {
76
+ const setHeader = res["setHeader"];
77
+ if (typeof setHeader === "function") setHeader.call(res, header, correlationId);
78
+ }
79
+ const ctx = {
80
+ correlationId,
81
+ ...originService !== void 0 ? { originService } : {}
82
+ };
83
+ LogixiaContext.run(ctx, next);
84
+ };
85
+ }
86
+ /**
87
+ * Fastify `onRequest` lifecycle hook — same functionality as `correlationMiddleware`
88
+ * but adapted to Fastify's hook API.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * import { correlationFastifyHook } from 'logixia/correlation';
93
+ * fastify.addHook('onRequest', correlationFastifyHook());
94
+ * ```
95
+ */
96
+ function correlationFastifyHook(options = {}) {
97
+ const { header = "x-correlation-id", originServiceHeader = "x-origin-service", generate = generateCorrelationId, setResponseHeader = true } = options;
98
+ return function logixiaCorrelationFastifyHook(request, reply, done) {
99
+ const headers = request["headers"] ?? {};
100
+ const correlationId = validIncomingId(headers[header.toLowerCase()]) ?? generate();
101
+ const originService = headers[originServiceHeader.toLowerCase()];
102
+ if (setResponseHeader) {
103
+ const replyHeader = reply["header"];
104
+ if (typeof replyHeader === "function") replyHeader.call(reply, header, correlationId);
105
+ }
106
+ const ctx = {
107
+ correlationId,
108
+ ...originService !== void 0 ? { originService } : {}
109
+ };
110
+ LogixiaContext.run(ctx, done);
111
+ };
112
+ }
113
+ /**
114
+ * A drop-in replacement for `fetch` that automatically injects the current
115
+ * `correlationId` from `LogixiaContext` as an `X-Correlation-ID` request header.
116
+ *
117
+ * Falls back to the global `fetch` when no correlation context is active.
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * import { correlationFetch } from 'logixia/correlation';
122
+ *
123
+ * // Inside a request handler with correlationMiddleware active:
124
+ * const response = await correlationFetch('https://payments.internal/v1/charge', {
125
+ * method: 'POST',
126
+ * body: JSON.stringify({ amount: 100 }),
127
+ * });
128
+ * ```
129
+ */
130
+ async function correlationFetch(input, init = {}, options = {}) {
131
+ const header = options.header ?? "x-correlation-id";
132
+ const correlationId = getCurrentCorrelationId();
133
+ const headers = new Headers(input instanceof Request ? input.headers : void 0);
134
+ for (const [name, value] of new Headers(init.headers)) headers.set(name, value);
135
+ if (correlationId && !headers.has(header)) headers.set(header, correlationId);
136
+ return globalThis.fetch(input, {
137
+ ...init,
138
+ headers
139
+ });
140
+ }
141
+ /**
142
+ * Returns an axios request interceptor that automatically injects the current
143
+ * correlation ID into outgoing axios requests.
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * import axios from 'axios';
148
+ * import { createCorrelationAxiosInterceptor } from 'logixia/correlation';
149
+ *
150
+ * axios.interceptors.request.use(createCorrelationAxiosInterceptor());
151
+ * ```
152
+ */
153
+ function createCorrelationAxiosInterceptor(options = {}) {
154
+ const header = options.header ?? "x-correlation-id";
155
+ return function correlationAxiosInterceptor(config) {
156
+ const correlationId = getCurrentCorrelationId();
157
+ if (!correlationId) return config;
158
+ const headers = config["headers"] ?? {};
159
+ if (!headers[header]) headers[header] = correlationId;
160
+ return {
161
+ ...config,
162
+ headers
163
+ };
164
+ };
165
+ }
166
+ /**
167
+ * Create a child logger enriched with all correlation-relevant identifiers
168
+ * extracted from the incoming HTTP request.
169
+ *
170
+ * The child logger's every call automatically includes:
171
+ * `correlationId`, `requestId`, `method`, `url`, `userAgent`, `ip`
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * import { childFromRequest } from 'logixia/correlation';
176
+ * import { logger } from 'logixia';
177
+ *
178
+ * app.use((req, res, next) => {
179
+ * req.logger = childFromRequest(req, logger);
180
+ * next();
181
+ * });
182
+ *
183
+ * app.get('/orders', (req, res) => {
184
+ * req.logger.info('Listing orders'); // → correlationId, requestId, method, url ...
185
+ * });
186
+ * ```
187
+ */
188
+ function childFromRequest(req, logger, options = {}) {
189
+ var _headers$xForwarded, _req$socket;
190
+ const correlationHeader = options.correlationHeader ?? "x-correlation-id";
191
+ const headers = req["headers"] ?? {};
192
+ const correlationId = validIncomingId(headers[correlationHeader.toLowerCase()]) ?? getCurrentCorrelationId() ?? generateCorrelationId();
193
+ const method = req["method"];
194
+ const url = req["url"] ?? req["originalUrl"];
195
+ const userAgent = headers["user-agent"];
196
+ const ip = ((_headers$xForwarded = headers["x-forwarded-for"]) === null || _headers$xForwarded === void 0 || (_headers$xForwarded = _headers$xForwarded.split(",")[0]) === null || _headers$xForwarded === void 0 ? void 0 : _headers$xForwarded.trim()) ?? ((_req$socket = req["socket"]) === null || _req$socket === void 0 ? void 0 : _req$socket["remoteAddress"]);
197
+ return logger.child({
198
+ correlationId,
199
+ ...method !== void 0 ? { method } : {},
200
+ ...url !== void 0 ? { url } : {},
201
+ ...userAgent !== void 0 ? { userAgent } : {},
202
+ ...ip !== void 0 ? { ip } : {}
203
+ });
204
+ }
205
+ /**
206
+ * Extract a correlation context from a Kafka / RabbitMQ / SQS message object.
207
+ *
208
+ * Looks for `correlationId`, `correlation_id`, or `x-correlation-id` in
209
+ * `message.headers` (Kafka) or `message.MessageAttributes` (SQS).
210
+ *
211
+ * @example Kafka consumer
212
+ * ```ts
213
+ * import { extractMessageCorrelationId, withCorrelationId } from 'logixia/correlation';
214
+ *
215
+ * consumer.run({
216
+ * eachMessage: async ({ message }) => {
217
+ * const correlationId = extractMessageCorrelationId(message);
218
+ * await withCorrelationId(correlationId, () => processMessage(message));
219
+ * },
220
+ * });
221
+ * ```
222
+ */
223
+ function extractMessageCorrelationId(message) {
224
+ const kafkaHeaders = message["headers"];
225
+ if (kafkaHeaders) {
226
+ const raw = kafkaHeaders["x-correlation-id"] ?? kafkaHeaders["correlationId"] ?? kafkaHeaders["correlation_id"];
227
+ if (raw !== void 0) return Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
228
+ }
229
+ const sqsAttrs = message["MessageAttributes"];
230
+ if (sqsAttrs) {
231
+ var _sqsAttrs$xCorrelati, _sqsAttrs$correlation, _sqsAttrs$correlation2;
232
+ return ((_sqsAttrs$xCorrelati = sqsAttrs["x-correlation-id"]) === null || _sqsAttrs$xCorrelati === void 0 ? void 0 : _sqsAttrs$xCorrelati.StringValue) ?? ((_sqsAttrs$correlation = sqsAttrs["correlationId"]) === null || _sqsAttrs$correlation === void 0 ? void 0 : _sqsAttrs$correlation.StringValue) ?? ((_sqsAttrs$correlation2 = sqsAttrs["correlation_id"]) === null || _sqsAttrs$correlation2 === void 0 ? void 0 : _sqsAttrs$correlation2.StringValue);
233
+ }
234
+ return message["correlationId"] ?? message["correlation_id"];
235
+ }
236
+ /**
237
+ * Build Kafka message headers containing the current correlation context.
238
+ *
239
+ * @example
240
+ * ```ts
241
+ * import { buildKafkaCorrelationHeaders } from 'logixia/correlation';
242
+ *
243
+ * await producer.send({
244
+ * topic: 'orders',
245
+ * messages: [{ value: JSON.stringify(order), headers: buildKafkaCorrelationHeaders() }],
246
+ * });
247
+ * ```
248
+ */
249
+ function buildKafkaCorrelationHeaders() {
250
+ const ctx = LogixiaContext.get();
251
+ const headers = {};
252
+ const correlationId = ctx === null || ctx === void 0 ? void 0 : ctx.correlationId;
253
+ if (correlationId) headers["x-correlation-id"] = correlationId;
254
+ const traceId = ctx === null || ctx === void 0 ? void 0 : ctx.traceId;
255
+ if (traceId) headers["x-trace-id"] = traceId;
256
+ return headers;
257
+ }
258
+
259
+ //#endregion
260
+ export { buildKafkaCorrelationHeaders, childFromRequest, correlationFastifyHook, correlationFetch, correlationMiddleware, createCorrelationAxiosInterceptor, extractMessageCorrelationId, generateCorrelationId, getCurrentCorrelationId, withCorrelationId };
261
+ //# sourceMappingURL=correlation.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation.mjs","names":["ctx: CorrelationContext","headers: Record<string, string>"],"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';\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 `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\nconst MAX_INCOMING_ID_LENGTH = 128;\nconst VALID_INCOMING_ID = /^[A-Za-z0-9._:-]+$/;\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: string | undefined): string | undefined {\n if (value === undefined || value.length > MAX_INCOMING_ID_LENGTH) return undefined;\n return VALID_INCOMING_ID.test(value) ? value : undefined;\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 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 correlationId = validIncomingId(headers[header.toLowerCase()]) ?? generate();\n const originService = 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 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 correlationId = validIncomingId(headers[header.toLowerCase()]) ?? generate();\n const originService = 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\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`, `requestId`, `method`, `url`, `userAgent`, `ip`\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(ctx: Record<string, unknown>): TLogger }>(\n req: Record<string, unknown>,\n logger: TLogger,\n options: { correlationHeader?: string } = {}\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 userAgent = headers['user-agent'];\n const ip =\n (headers['x-forwarded-for'] as string | undefined)?.split(',')[0]?.trim() ??\n ((req['socket'] as Record<string, unknown> | undefined)?.['remoteAddress'] as\n string | undefined);\n\n return logger.child({\n correlationId,\n ...(method !== undefined ? { method } : {}),\n ...(url !== undefined ? { url } : {}),\n ...(userAgent !== undefined ? { userAgent } : {}),\n ...(ip !== undefined ? { ip } : {}),\n });\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 Buffer.isBuffer(raw) ? raw.toString('utf8') : 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 (\n sqsAttrs['x-correlation-id']?.StringValue ??\n sqsAttrs['correlationId']?.StringValue ??\n sqsAttrs['correlation_id']?.StringValue\n );\n }\n\n // AMQP / generic envelope\n return (\n (message['correlationId'] as string | undefined) ??\n (message['correlation_id'] as string | undefined)\n );\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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"}
package/dist/gcp.d.mts ADDED
@@ -0,0 +1,3 @@
1
+ import "./transport.types-B5vcyv2k.mjs";
2
+ import { n as GCPTransport, r as GCPTransportConfig, t as GCPMonitoredResource } from "./gcp.transport-Bi6CUqZR.mjs";
3
+ export { GCPMonitoredResource, GCPTransport, GCPTransportConfig };
package/dist/gcp.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import "./transport.types-BuY84TwO.js";
2
+ import { n as GCPTransport, r as GCPTransportConfig, t as GCPMonitoredResource } from "./gcp.transport-BOhkVKlI.js";
3
+ export { GCPMonitoredResource, GCPTransport, GCPTransportConfig };
package/dist/gcp.js ADDED
@@ -0,0 +1,6 @@
1
+ require('./internal-log-BUJYMZbE.js');
2
+ require('./safe-stringify-DWo3aZp_.js');
3
+ require('./batch-buffer-BY-mRWIK.js');
4
+ const require_gcp_transport = require('./gcp.transport-SHdtc1n0.js');
5
+
6
+ exports.GCPTransport = require_gcp_transport.GCPTransport;
package/dist/gcp.mjs ADDED
@@ -0,0 +1,6 @@
1
+ import "./internal-log-Cm8SOWSV.mjs";
2
+ import "./safe-stringify-DJ66tW5I.mjs";
3
+ import "./batch-buffer-PGvSgbmd.mjs";
4
+ import { t as GCPTransport } from "./gcp.transport-BJuNRFe_.mjs";
5
+
6
+ export { GCPTransport };