logixia 1.13.3 → 2.0.4

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 (229) hide show
  1. package/README.md +117 -44
  2. package/dist/async-context-B05ArHgM.mjs +134 -0
  3. package/dist/async-context-B05ArHgM.mjs.map +1 -0
  4. package/dist/async-context-DTQgi8f7.js +150 -0
  5. package/dist/async-context-DTQgi8f7.js.map +1 -0
  6. package/dist/{azure-monitor.transport-CHnjVRF-.mjs → azure-monitor.transport-BPbgjATu.mjs} +27 -15
  7. package/dist/azure-monitor.transport-BPbgjATu.mjs.map +1 -0
  8. package/dist/{azure-monitor.transport-BDFO4F-i.d.ts → azure-monitor.transport-C7kYyZUW.d.mts} +3 -6
  9. package/dist/azure-monitor.transport-C7kYyZUW.d.mts.map +1 -0
  10. package/dist/{azure-monitor.transport-CrvfHd9C.js → azure-monitor.transport-CwbOLZHB.js} +32 -20
  11. package/dist/azure-monitor.transport-CwbOLZHB.js.map +1 -0
  12. package/dist/{azure-monitor.transport-D5wQEH04.d.mts → azure-monitor.transport-DV03Z5P4.d.ts} +3 -6
  13. package/dist/azure-monitor.transport-DV03Z5P4.d.ts.map +1 -0
  14. package/dist/azure.d.mts +1 -2
  15. package/dist/azure.d.ts +1 -2
  16. package/dist/azure.js +3 -6
  17. package/dist/azure.mjs +1 -6
  18. package/dist/{batch-buffer-PGvSgbmd.mjs → batch-buffer-0fgLmIPn.mjs} +21 -7
  19. package/dist/batch-buffer-0fgLmIPn.mjs.map +1 -0
  20. package/dist/{batch-buffer-BY-mRWIK.js → batch-buffer-DT4gdXmf.js} +53 -40
  21. package/dist/batch-buffer-DT4gdXmf.js.map +1 -0
  22. package/dist/browser.d.mts +52 -23
  23. package/dist/browser.d.mts.map +1 -1
  24. package/dist/browser.d.ts +52 -23
  25. package/dist/browser.d.ts.map +1 -1
  26. package/dist/browser.js +460 -62
  27. package/dist/browser.js.map +1 -1
  28. package/dist/browser.mjs +461 -65
  29. package/dist/browser.mjs.map +1 -1
  30. package/dist/{nestjs-extras-ImbuDhs1.mjs → builder-D70z6kjj.mjs} +279 -965
  31. package/dist/builder-D70z6kjj.mjs.map +1 -0
  32. package/dist/{nestjs-extras-DnJ6vrIC.js → builder-M7NoaugM.js} +421 -1177
  33. package/dist/builder-M7NoaugM.js.map +1 -0
  34. package/dist/cli/index.js +86 -67
  35. package/dist/cloudwatch.d.mts +1 -2
  36. package/dist/cloudwatch.d.ts +1 -2
  37. package/dist/cloudwatch.js +3 -7
  38. package/dist/cloudwatch.mjs +1 -7
  39. package/dist/{cloudwatch.transport-BO1ahxw-.js → cloudwatch.transport-B2wKQFF7.js} +61 -36
  40. package/dist/cloudwatch.transport-B2wKQFF7.js.map +1 -0
  41. package/dist/{cloudwatch.transport-DZ6ies7g.mjs → cloudwatch.transport-BTvYfLs9.mjs} +54 -28
  42. package/dist/cloudwatch.transport-BTvYfLs9.mjs.map +1 -0
  43. package/dist/{cloudwatch.transport-B00st6Xv.d.ts → cloudwatch.transport-Bf7L4I59.d.ts} +2 -6
  44. package/dist/cloudwatch.transport-Bf7L4I59.d.ts.map +1 -0
  45. package/dist/{cloudwatch.transport-Blc7Gtxt.d.mts → cloudwatch.transport-Rj4yTpRn.d.mts} +2 -6
  46. package/dist/cloudwatch.transport-Rj4yTpRn.d.mts.map +1 -0
  47. package/dist/{coerce.utils-Cu6IUTgX.mjs → coerce.utils-B_rEudCn.mjs} +3 -4
  48. package/dist/{coerce.utils-Cu6IUTgX.mjs.map → coerce.utils-B_rEudCn.mjs.map} +1 -1
  49. package/dist/{coerce.utils-B-LilukN.js → coerce.utils-C1C5sIX0.js} +8 -10
  50. package/dist/{coerce.utils-B-LilukN.js.map → coerce.utils-C1C5sIX0.js.map} +1 -1
  51. package/dist/correlation.d.mts +13 -16
  52. package/dist/correlation.d.mts.map +1 -1
  53. package/dist/correlation.d.ts +13 -16
  54. package/dist/correlation.d.ts.map +1 -1
  55. package/dist/correlation.js +58 -9
  56. package/dist/correlation.js.map +1 -1
  57. package/dist/correlation.mjs +56 -6
  58. package/dist/correlation.mjs.map +1 -1
  59. package/dist/{entry-fields-BJkuPnIg.mjs → entry-fields-CGuvT2R0.mjs} +5 -6
  60. package/dist/entry-fields-CGuvT2R0.mjs.map +1 -0
  61. package/dist/{entry-fields-BvOLFlJ4.js → entry-fields-RLd1jCoR.js} +10 -12
  62. package/dist/entry-fields-RLd1jCoR.js.map +1 -0
  63. package/dist/gcp.d.mts +1 -2
  64. package/dist/gcp.d.ts +1 -2
  65. package/dist/gcp.js +3 -7
  66. package/dist/gcp.mjs +1 -7
  67. package/dist/{gcp.transport-m4fVw0b1.d.ts → gcp.transport-C5D72b1g.d.mts} +3 -6
  68. package/dist/gcp.transport-C5D72b1g.d.mts.map +1 -0
  69. package/dist/{gcp.transport-zzNyIoih.d.mts → gcp.transport-CFQfdAOK.d.ts} +3 -6
  70. package/dist/gcp.transport-CFQfdAOK.d.ts.map +1 -0
  71. package/dist/{gcp.transport-DBX5TPIy.mjs → gcp.transport-CGKopAhQ.mjs} +55 -16
  72. package/dist/gcp.transport-CGKopAhQ.mjs.map +1 -0
  73. package/dist/{gcp.transport-CHV8MGgz.js → gcp.transport-KZe9hCAR.js} +60 -22
  74. package/dist/gcp.transport-KZe9hCAR.js.map +1 -0
  75. package/dist/{index-nZEeCr3X.d.ts → index-DwozbmB4.d.ts} +3 -8
  76. package/dist/index-DwozbmB4.d.ts.map +1 -0
  77. package/dist/{index-BPwEisy4.d.mts → index-xqaJtAZn.d.mts} +3 -8
  78. package/dist/index-xqaJtAZn.d.mts.map +1 -0
  79. package/dist/index.d.mts +141 -203
  80. package/dist/index.d.mts.map +1 -1
  81. package/dist/index.d.ts +140 -202
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +196 -381
  84. package/dist/index.js.map +1 -1
  85. package/dist/index.mjs +145 -294
  86. package/dist/index.mjs.map +1 -1
  87. package/dist/internal-log-b0Vp4qrB.js +183 -0
  88. package/dist/internal-log-b0Vp4qrB.js.map +1 -0
  89. package/dist/internal-log-uCSd1PMc.mjs +142 -0
  90. package/dist/internal-log-uCSd1PMc.mjs.map +1 -0
  91. package/dist/logitron-logger-D2B75Oc7.d.ts +365 -0
  92. package/dist/logitron-logger-D2B75Oc7.d.ts.map +1 -0
  93. package/dist/logitron-logger-oEd6wNhf.d.mts +365 -0
  94. package/dist/logitron-logger-oEd6wNhf.d.mts.map +1 -0
  95. package/dist/middleware.d.mts +10 -12
  96. package/dist/middleware.d.mts.map +1 -1
  97. package/dist/middleware.d.ts +10 -12
  98. package/dist/middleware.d.ts.map +1 -1
  99. package/dist/middleware.js +16 -47
  100. package/dist/middleware.js.map +1 -1
  101. package/dist/middleware.mjs +13 -45
  102. package/dist/middleware.mjs.map +1 -1
  103. package/dist/nest.d.mts +467 -10
  104. package/dist/nest.d.mts.map +1 -1
  105. package/dist/nest.d.ts +467 -10
  106. package/dist/nest.d.ts.map +1 -1
  107. package/dist/nest.js +889 -43
  108. package/dist/nest.js.map +1 -0
  109. package/dist/nest.mjs +850 -9
  110. package/dist/nest.mjs.map +1 -0
  111. package/dist/{trace.utils-CvXgaiQT.mjs → request-trace-C_fvIDv4.mjs} +44 -9
  112. package/dist/request-trace-C_fvIDv4.mjs.map +1 -0
  113. package/dist/{trace.utils-DpBOLDtq.js → request-trace-wTJdKp6-.js} +121 -77
  114. package/dist/request-trace-wTJdKp6-.js.map +1 -0
  115. package/dist/rolldown-runtime-D3PmRqjj.mjs +4 -0
  116. package/dist/{safe-stringify-DJ66tW5I.mjs → safe-stringify-BMRl3EX3.mjs} +3 -4
  117. package/dist/safe-stringify-BMRl3EX3.mjs.map +1 -0
  118. package/dist/{safe-stringify-DWo3aZp_.js → safe-stringify-DLz1Kz3o.js} +23 -25
  119. package/dist/safe-stringify-DLz1Kz3o.js.map +1 -0
  120. package/dist/{search-CHUZPl0h.d.ts → search-BH33XhuU.d.mts} +2 -22
  121. package/dist/search-BH33XhuU.d.mts.map +1 -0
  122. package/dist/{search-DjXSn6v5.mjs → search-CWMVYC-R.mjs} +34 -41
  123. package/dist/search-CWMVYC-R.mjs.map +1 -0
  124. package/dist/{search-BfBzDIT9.d.mts → search-CazwyX5Z.d.ts} +2 -22
  125. package/dist/search-CazwyX5Z.d.ts.map +1 -0
  126. package/dist/{search-B0MH1SJJ.js → search-DUfWuzFP.js} +64 -71
  127. package/dist/search-DUfWuzFP.js.map +1 -0
  128. package/dist/search.d.mts +2 -4
  129. package/dist/search.d.ts +2 -4
  130. package/dist/search.js +3 -4
  131. package/dist/search.mjs +1 -4
  132. package/dist/secret-config-BegMLkYl.mjs +209 -0
  133. package/dist/secret-config-BegMLkYl.mjs.map +1 -0
  134. package/dist/secret-config-CBZBs0p1.js +271 -0
  135. package/dist/secret-config-CBZBs0p1.js.map +1 -0
  136. package/dist/slack.transport-Ce96JBDk.mjs +824 -0
  137. package/dist/slack.transport-Ce96JBDk.mjs.map +1 -0
  138. package/dist/slack.transport-Dlx3xvb7.js +847 -0
  139. package/dist/slack.transport-Dlx3xvb7.js.map +1 -0
  140. package/dist/slack.transport-DrirgDNy.d.ts +159 -0
  141. package/dist/slack.transport-DrirgDNy.d.ts.map +1 -0
  142. package/dist/slack.transport-DxHOXlLu.d.mts +159 -0
  143. package/dist/slack.transport-DxHOXlLu.d.mts.map +1 -0
  144. package/dist/testing.d.mts +3 -8
  145. package/dist/testing.d.mts.map +1 -1
  146. package/dist/testing.d.ts +3 -8
  147. package/dist/testing.d.ts.map +1 -1
  148. package/dist/testing.js +3 -4
  149. package/dist/testing.js.map +1 -1
  150. package/dist/testing.mjs +2 -3
  151. package/dist/testing.mjs.map +1 -1
  152. package/dist/{transport.manager-Crau3Rle.js → transport.manager-DkcEOlqV.js} +331 -234
  153. package/dist/transport.manager-DkcEOlqV.js.map +1 -0
  154. package/dist/{transport.manager-CpdVHEGP.mjs → transport.manager-eZ_EeC00.mjs} +274 -178
  155. package/dist/transport.manager-eZ_EeC00.mjs.map +1 -0
  156. package/dist/{transport.types-CtNr3D7S.d.mts → transport.types-BJVL1QQl.d.mts} +5 -4
  157. package/dist/transport.types-BJVL1QQl.d.mts.map +1 -0
  158. package/dist/{transport.types-DqVLZTyf.d.ts → transport.types-BJVL1QQl.d.ts} +5 -4
  159. package/dist/transport.types-BJVL1QQl.d.ts.map +1 -0
  160. package/dist/transports.d.mts +32 -41
  161. package/dist/transports.d.mts.map +1 -1
  162. package/dist/transports.d.ts +32 -41
  163. package/dist/transports.d.ts.map +1 -1
  164. package/dist/transports.js +12 -12
  165. package/dist/transports.mjs +7 -12
  166. package/dist/worker-entry.js +337 -87
  167. package/dist/worker.d.mts +1 -2
  168. package/dist/worker.d.ts +1 -2
  169. package/dist/worker.js +3 -4
  170. package/dist/worker.mjs +1 -4
  171. package/dist/{worker.transport-Bj-i0P9V.d.ts → worker.transport-BuPwBi3d.d.ts} +2 -6
  172. package/dist/worker.transport-BuPwBi3d.d.ts.map +1 -0
  173. package/dist/{worker.transport-JP8YHBuU.js → worker.transport-C6GNl_r2.js} +44 -14
  174. package/dist/worker.transport-C6GNl_r2.js.map +1 -0
  175. package/dist/{worker.transport-otwhJ_Et.mjs → worker.transport-DLVA4y2e.mjs} +39 -9
  176. package/dist/worker.transport-DLVA4y2e.mjs.map +1 -0
  177. package/dist/{worker.transport-Bgv9heDI.d.mts → worker.transport-hJay1wxA.d.mts} +2 -6
  178. package/dist/worker.transport-hJay1wxA.d.mts.map +1 -0
  179. package/package.json +36 -19
  180. package/dist/async-context-BzJrbfQs.mjs +0 -72
  181. package/dist/async-context-BzJrbfQs.mjs.map +0 -1
  182. package/dist/async-context-DyQQYq3I.js +0 -92
  183. package/dist/async-context-DyQQYq3I.js.map +0 -1
  184. package/dist/azure-monitor.transport-BDFO4F-i.d.ts.map +0 -1
  185. package/dist/azure-monitor.transport-CHnjVRF-.mjs.map +0 -1
  186. package/dist/azure-monitor.transport-CrvfHd9C.js.map +0 -1
  187. package/dist/azure-monitor.transport-D5wQEH04.d.mts.map +0 -1
  188. package/dist/batch-buffer-BY-mRWIK.js.map +0 -1
  189. package/dist/batch-buffer-PGvSgbmd.mjs.map +0 -1
  190. package/dist/chunk-DLSkYcPk.mjs +0 -7
  191. package/dist/chunk-nOFOJqeH.js +0 -30
  192. package/dist/cloudwatch.transport-B00st6Xv.d.ts.map +0 -1
  193. package/dist/cloudwatch.transport-BO1ahxw-.js.map +0 -1
  194. package/dist/cloudwatch.transport-Blc7Gtxt.d.mts.map +0 -1
  195. package/dist/cloudwatch.transport-DZ6ies7g.mjs.map +0 -1
  196. package/dist/entry-fields-BJkuPnIg.mjs.map +0 -1
  197. package/dist/entry-fields-BvOLFlJ4.js.map +0 -1
  198. package/dist/gcp.transport-CHV8MGgz.js.map +0 -1
  199. package/dist/gcp.transport-DBX5TPIy.mjs.map +0 -1
  200. package/dist/gcp.transport-m4fVw0b1.d.ts.map +0 -1
  201. package/dist/gcp.transport-zzNyIoih.d.mts.map +0 -1
  202. package/dist/index-BPwEisy4.d.mts.map +0 -1
  203. package/dist/index-nZEeCr3X.d.ts.map +0 -1
  204. package/dist/internal-log-BUJYMZbE.js +0 -70
  205. package/dist/internal-log-BUJYMZbE.js.map +0 -1
  206. package/dist/internal-log-Cm8SOWSV.mjs +0 -52
  207. package/dist/internal-log-Cm8SOWSV.mjs.map +0 -1
  208. package/dist/nestjs-extras-DnJ6vrIC.js.map +0 -1
  209. package/dist/nestjs-extras-ImbuDhs1.mjs.map +0 -1
  210. package/dist/nestjs-extras-OkC796py.d.mts +0 -725
  211. package/dist/nestjs-extras-OkC796py.d.mts.map +0 -1
  212. package/dist/nestjs-extras-mKrHE9S6.d.ts +0 -725
  213. package/dist/nestjs-extras-mKrHE9S6.d.ts.map +0 -1
  214. package/dist/safe-stringify-DJ66tW5I.mjs.map +0 -1
  215. package/dist/safe-stringify-DWo3aZp_.js.map +0 -1
  216. package/dist/search-B0MH1SJJ.js.map +0 -1
  217. package/dist/search-BfBzDIT9.d.mts.map +0 -1
  218. package/dist/search-CHUZPl0h.d.ts.map +0 -1
  219. package/dist/search-DjXSn6v5.mjs.map +0 -1
  220. package/dist/trace.utils-CvXgaiQT.mjs.map +0 -1
  221. package/dist/trace.utils-DpBOLDtq.js.map +0 -1
  222. package/dist/transport.manager-CpdVHEGP.mjs.map +0 -1
  223. package/dist/transport.manager-Crau3Rle.js.map +0 -1
  224. package/dist/transport.types-CtNr3D7S.d.mts.map +0 -1
  225. package/dist/transport.types-DqVLZTyf.d.ts.map +0 -1
  226. package/dist/worker.transport-Bgv9heDI.d.mts.map +0 -1
  227. package/dist/worker.transport-Bj-i0P9V.d.ts.map +0 -1
  228. package/dist/worker.transport-JP8YHBuU.js.map +0 -1
  229. package/dist/worker.transport-otwhJ_Et.mjs.map +0 -1
package/README.md CHANGED
@@ -673,7 +673,7 @@ const logger = createLogger({
673
673
  analytics: {
674
674
  datadog: {
675
675
  apiKey: process.env.DD_API_KEY!,
676
- site: 'datadoghq.com', // or 'datadoghq.eu', 'us3.datadoghq.com'
676
+ site: 'datadoghq.com', // or 'datadoghq.eu', 'us3.datadoghq.com', 'us5.datadoghq.com', 'ap1.datadoghq.com', 'ddog-gov.com'
677
677
  service: 'api',
678
678
  env: 'production',
679
679
  enableLogs: true,
@@ -758,6 +758,31 @@ const logger = createLogger({
758
758
  await logger.info('Order placed', { orderId: 'ord_789' });
759
759
  ```
760
760
 
761
+ ### Slack and Discord webhook transports
762
+
763
+ Send alerts to a chat channel. Both default to `level: 'error'`, batch entries that arrive within `minIntervalMs` (default 1000) into one message, and cap a message at 10 entries (the rest are dropped and counted in the message).
764
+
765
+ ```typescript
766
+ import { createLogger } from 'logixia';
767
+ import { DiscordTransport, SlackTransport } from 'logixia';
768
+
769
+ const logger = createLogger({
770
+ appName: 'api',
771
+ transports: {
772
+ custom: [
773
+ new SlackTransport({ webhookUrl: process.env.SLACK_WEBHOOK_URL!, level: 'error' }),
774
+ new DiscordTransport({ webhookUrl: process.env.DISCORD_WEBHOOK_URL!, level: 'warn' }),
775
+ ],
776
+ },
777
+ });
778
+ ```
779
+
780
+ - The webhook URL is a secret. It must be `https:` on `hooks.slack.com` (Slack) or `discord.com` / `discordapp.com` (Discord) unless you pass `allowCustomUrl: true`. It is never logged and never appears in error messages (only the host does).
781
+ - Logged text is escaped: Slack `&`, `<`, `>` (so `<!channel>` and `<@U123>` in log data cannot ping anyone); Discord markdown, plus `allowed_mentions: { parse: [] }` on every request.
782
+ - Platform limits are enforced by truncation (Slack header 150 and data 2,500 chars; Discord title 256, description 4,096, 25 fields, 6,000 per message).
783
+ - HTTP 429 waits for `Retry-After` (Discord: `retry_after`), then retries up to `maxRetries` (default 1). Other failures keep the batch queued with backoff; `flush()` rejects with a host-only error. The queue is bounded by `maxBufferSize` (default 1000, oldest dropped).
784
+ - Also available: `username`, `iconEmoji` (Slack), `avatarUrl` (Discord), `filter`, `formatter`, `flush()` and `close()` (flushes pending entries).
785
+
761
786
  ### Custom transport
762
787
 
763
788
  Implement `ITransport` to send logs anywhere — Slack, PagerDuty, S3, an internal queue:
@@ -957,23 +982,33 @@ const incomingTraceId = extractTraceId(req, {
957
982
  ### Express / Fastify middleware
958
983
 
959
984
  ```typescript
960
- import { traceMiddleware } from 'logixia';
985
+ import { createExpressContextMiddleware, createFastifyContextHook } from 'logixia';
961
986
 
962
- // Zero-config — reads from traceparent / x-trace-id / x-request-id / x-correlation-id
963
- // and generates a UUID v4 if none is present. Sets X-Trace-Id on the response.
964
- app.use(traceMiddleware());
987
+ // Express: trace ID = first present header of traceIdHeader (default
988
+ // DEFAULT_TRACE_HEADERS: traceparent, x-trace-id, x-request-id, x-correlation-id,
989
+ // trace-id; traceparent is parsed to its 32-hex trace id), else request.id, else a
990
+ // generated UUID. The ID is available via getCurrentTraceId() and is the logger's
991
+ // top-level traceId for everything inside the request. No response header is set
992
+ // unless you ask for one with responseHeader.
993
+ app.use(createExpressContextMiddleware({ responseHeader: 'X-Trace-Id' }));
965
994
 
966
- // With custom config:
967
- app.use(
968
- traceMiddleware({
969
- enabled: true,
970
- generator: () => `req_${crypto.randomUUID()}`,
971
- extractor: {
972
- header: ['x-trace-id', 'traceparent'],
973
- query: ['traceId'],
974
- },
975
- })
976
- );
995
+ // Fastify: same resolution; fastify's default counter ids ('req-1', ...) are never
996
+ // used as trace IDs. Also shared with createFastifyPlugin() from 'logixia/middleware'.
997
+ // traceIdHeader accepts a single name or a list; responseHeader works as above.
998
+ fastify.addHook('onRequest', createFastifyContextHook());
999
+
1000
+ // Prefer full control? Wrap the request yourself:
1001
+ app.use((req, res, next) => {
1002
+ const traceId =
1003
+ extractTraceId(req, { header: DEFAULT_TRACE_HEADERS, query: ['traceId'] }) ?? generateTraceId();
1004
+ res.setHeader('X-Trace-Id', traceId);
1005
+ runWithTraceId(traceId, () => next());
1006
+ });
1007
+
1008
+ // Request/response logging (separate from tracing) lives in 'logixia/middleware':
1009
+ // app.use(createExpressMiddleware(logger));
1010
+ // await fastify.register(createFastifyPlugin(logger));
1011
+ // NestJS: import { TraceMiddleware } from 'logixia/nest';
977
1012
 
978
1013
  // Service layer — no parameters needed, trace ID propagates automatically
979
1014
  class OrderService {
@@ -992,6 +1027,25 @@ class OrderService {
992
1027
 
993
1028
  The default headers checked for an incoming trace ID (in priority order) are: `traceparent`, `x-trace-id`, `x-request-id`, `x-correlation-id`, `trace-id`.
994
1029
 
1030
+ ### Hono
1031
+
1032
+ On Node, Hono needs nothing beyond `runWithTraceId()` — wrap `await next()` and every log line in the request carries the trace ID:
1033
+
1034
+ ```typescript
1035
+ app.use(async (c, next) => {
1036
+ const traceId =
1037
+ extractTraceId({ headers: c.req.header() }, { header: DEFAULT_TRACE_HEADERS }) ??
1038
+ generateTraceId();
1039
+ await runWithTraceId(traceId, next);
1040
+ });
1041
+ ```
1042
+
1043
+ See [`examples/hono-example.ts`](./examples/hono-example.ts) (`npm run dev:hono`) for the full version.
1044
+
1045
+ ### Next.js (App Router)
1046
+
1047
+ In a route handler on the Node.js runtime (`export const runtime = 'nodejs'`) the regular `logixia` entry works: wrap the handler body in `runWithTraceId(traceId, ...)`. See [`examples/nextjs/`](./examples/nextjs/) for a copy-paste route handler.
1048
+
995
1049
  ### NestJS trace middleware
996
1050
 
997
1051
  The `TraceMiddleware` class integrates directly with NestJS's middleware system. `LogixiaLoggerModule.forRoot()` applies it automatically across all routes — no manual wiring needed:
@@ -1001,7 +1055,7 @@ The `TraceMiddleware` class integrates directly with NestJS's middleware system.
1001
1055
  // For manual use in a custom module:
1002
1056
 
1003
1057
  import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
1004
- import { TraceMiddleware } from 'logixia';
1058
+ import { TraceMiddleware } from 'logixia/nest';
1005
1059
 
1006
1060
  @Module({})
1007
1061
  export class AppModule implements NestModule {
@@ -1016,7 +1070,7 @@ export class AppModule implements NestModule {
1016
1070
  Propagates trace IDs through Kafka message handlers. Reads `traceId` / `trace_id` / `x-trace-id` from the message body or headers and runs the handler inside that trace context:
1017
1071
 
1018
1072
  ```typescript
1019
- import { KafkaTraceInterceptor } from 'logixia';
1073
+ import { KafkaTraceInterceptor } from 'logixia/nest';
1020
1074
  import { UseInterceptors, Controller } from '@nestjs/common';
1021
1075
  import { MessagePattern } from '@nestjs/microservices';
1022
1076
 
@@ -1038,7 +1092,7 @@ export class OrdersConsumer {
1038
1092
  Propagates trace IDs through WebSocket event handlers. Reads `traceId` from the message body, event payload, or handshake query:
1039
1093
 
1040
1094
  ```typescript
1041
- import { WebSocketTraceInterceptor } from 'logixia';
1095
+ import { WebSocketTraceInterceptor } from 'logixia/nest';
1042
1096
  import { UseInterceptors, WebSocketGateway, SubscribeMessage } from '@nestjs/websockets';
1043
1097
 
1044
1098
  @UseInterceptors(new WebSocketTraceInterceptor())
@@ -1060,6 +1114,11 @@ export class EventsGateway {
1060
1114
 
1061
1115
  ## NestJS integration
1062
1116
 
1117
+ > **Upgrading from 1.x:** since 2.0.0, everything NestJS-specific lives in `logixia/nest` only.
1118
+ > Change `import { LogixiaLoggerModule, LogixiaLoggerService, LogixiaExceptionFilter, InjectLogger, LogMethod } from 'logixia'`
1119
+ > to `... from 'logixia/nest'`. The root `logixia` entry no longer loads `@nestjs/common`, so plain Node, Express and Fastify apps
1120
+ > work without NestJS installed.
1121
+
1063
1122
  Drop-in module with zero boilerplate. Registers `TraceMiddleware` for all routes, provides `LogixiaLoggerService`, `KafkaTraceInterceptor`, and `WebSocketTraceInterceptor` via the global DI container.
1064
1123
 
1065
1124
  > **Full working example** — see [`examples/nestjs-app/`](./examples/nestjs-app/) for a complete NestJS app with Docker Compose (Postgres, MongoDB, Kafka, Kafdrop) that exercises every feature: `LogixiaLoggerModule`, `TraceMiddleware`, `LogixiaExceptionFilter`, `HttpLoggingInterceptor`, `WebSocketTraceInterceptor`, `KafkaTraceInterceptor`, `@LogMethod`, `child()` loggers, `timeAsync`, real Kafka producer + consumer.
@@ -1074,7 +1133,7 @@ Drop-in module with zero boilerplate. Registers `TraceMiddleware` for all routes
1074
1133
  ```typescript
1075
1134
  // app.module.ts
1076
1135
  import { Module } from '@nestjs/common';
1077
- import { LogixiaLoggerModule } from 'logixia';
1136
+ import { LogixiaLoggerModule } from 'logixia/nest';
1078
1137
 
1079
1138
  @Module({
1080
1139
  imports: [
@@ -1120,7 +1179,7 @@ LogixiaLoggerModule.forRootAsync({
1120
1179
  ```typescript
1121
1180
  // orders.service.ts
1122
1181
  import { Injectable } from '@nestjs/common';
1123
- import { LogixiaLoggerService } from 'logixia';
1182
+ import { LogixiaLoggerService } from 'logixia/nest';
1124
1183
 
1125
1184
  @Injectable()
1126
1185
  export class OrdersService {
@@ -1138,7 +1197,7 @@ export class OrdersService {
1138
1197
  ```typescript
1139
1198
  // orders.module.ts
1140
1199
  import { Module } from '@nestjs/common';
1141
- import { LogixiaLoggerModule } from 'logixia';
1200
+ import { LogixiaLoggerModule } from 'logixia/nest';
1142
1201
  import { OrdersService } from './orders.service';
1143
1202
 
1144
1203
  @Module({
@@ -1151,7 +1210,7 @@ export class OrdersModule {}
1151
1210
  ```typescript
1152
1211
  // orders.service.ts — inject the feature-scoped token
1153
1212
  import { Inject, Injectable } from '@nestjs/common';
1154
- import { LOGIXIA_LOGGER_PREFIX, LogixiaLoggerService } from 'logixia';
1213
+ import { LOGIXIA_LOGGER_PREFIX, LogixiaLoggerService } from 'logixia/nest';
1155
1214
 
1156
1215
  @Injectable()
1157
1216
  export class OrdersService {
@@ -1186,12 +1245,12 @@ await logger.mysql('Slow query detected', { query, ms: 1240 });
1186
1245
  await logger.payment('Charge captured', { txnId, amount: 99.99 });
1187
1246
  ```
1188
1247
 
1189
- **TraceId** — with `traceId: true`, every log line carries a correlation ID automatically. Use `LogixiaContext.run()` to scope a trace to a block (the `TraceMiddleware` does this per-request automatically):
1248
+ **TraceId** — with `traceId: true`, every log line carries a correlation ID automatically. Use `runWithTraceId()` to scope a trace to a block (the `TraceMiddleware` does this per-request automatically):
1190
1249
 
1191
1250
  ```typescript
1192
- import { LogixiaContext } from 'logixia';
1251
+ import { runWithTraceId } from 'logixia';
1193
1252
 
1194
- await LogixiaContext.run({ traceId: req.headers['x-request-id'] }, async () => {
1253
+ await runWithTraceId(String(req.headers['x-request-id']), async () => {
1195
1254
  await logger.info('Request received'); // traceId: "abc-123" in every line
1196
1255
  await logger.kafka('Event published'); // same traceId
1197
1256
  });
@@ -1206,7 +1265,7 @@ Automatically logs method entry, exit, duration, and errors — no manual `try/c
1206
1265
 
1207
1266
  ```typescript
1208
1267
  import { Injectable } from '@nestjs/common';
1209
- import { LogixiaLoggerService, LogMethod } from 'logixia';
1268
+ import { LogixiaLoggerService, LogMethod } from 'logixia/nest';
1210
1269
 
1211
1270
  @Injectable()
1212
1271
  export class PaymentService {
@@ -1243,7 +1302,7 @@ A global NestJS exception filter that automatically logs unhandled exceptions
1243
1302
  ```typescript
1244
1303
  // main.ts
1245
1304
  import { NestFactory } from '@nestjs/core';
1246
- import { LogixiaExceptionFilter } from 'logixia';
1305
+ import { LogixiaExceptionFilter } from 'logixia/nest';
1247
1306
  import { AppModule } from './app.module';
1248
1307
 
1249
1308
  async function bootstrap() {
@@ -1258,7 +1317,7 @@ With `LogixiaLoggerModule` set up, inject the service directly so it logs to you
1258
1317
 
1259
1318
  ```typescript
1260
1319
  import { APP_FILTER } from '@nestjs/core';
1261
- import { LogixiaExceptionFilter, LogixiaLoggerService } from 'logixia';
1320
+ import { LogixiaExceptionFilter, LogixiaLoggerService } from 'logixia/nest';
1262
1321
 
1263
1322
  // In AppModule providers:
1264
1323
  {
@@ -1357,6 +1416,8 @@ const app = Fastify();
1357
1416
  app.addHook('onRequest', correlationFastifyHook());
1358
1417
  ```
1359
1418
 
1419
+ > **Full working example** — see [`examples/fastify-example.ts`](./examples/fastify-example.ts) (`npm run dev:fastify`) for request logging, trace IDs and redaction on Fastify.
1420
+
1360
1421
  ### Outbound fetch / axios
1361
1422
 
1362
1423
  Every outbound HTTP call made inside a correlated context automatically carries the `X-Correlation-ID` header.
@@ -1480,6 +1541,10 @@ const logger = new BrowserLogger({
1480
1541
  });
1481
1542
  ```
1482
1543
 
1544
+ Delivery failures (network errors and non-OK HTTP statuses) are counted in `failedFlushes` / `lastError` and passed to the optional `onError` callback. A throttled (at most one per minute) console report containing only the error name, HTTP status and endpoint host is printed everywhere except real browser pages, where it is silent by default; set `silent: true` or `silent: false` to override. Entries written to the transport from inside `onError` or while a report is printed are dropped (counted in `dropped`) to avoid feedback loops.
1545
+
1546
+ **Cloudflare Workers** — see [`examples/cloudflare-worker/`](./examples/cloudflare-worker/) for a Worker that logs JSON, tags each request with `child({ requestId })` and flushes a `BrowserRemoteTransport` with `ctx.waitUntil()`. Pass `flushIntervalMs: 0` to the transport: Workers don't allow timers at global scope.
1547
+
1483
1548
  The following utilities from the main package are also re-exported from `logixia/browser` (safe for non-Node runtimes):
1484
1549
 
1485
1550
  ```typescript
@@ -1686,24 +1751,27 @@ interface TransportLogEntry {
1686
1751
  Query your in-memory log history without shipping to Elasticsearch, Datadog, or any external service. Useful in development and lightweight production setups:
1687
1752
 
1688
1753
  ```typescript
1689
- import { SearchManager } from 'logixia';
1754
+ import { SearchManager } from 'logixia/search';
1690
1755
 
1691
- const search = new SearchManager({ maxEntries: 10_000 });
1756
+ const search = new SearchManager({ maxIndexSize: 10_000 });
1692
1757
 
1693
1758
  // Index a batch of entries (from a file, database query, or any source)
1694
- await search.index(logEntries);
1695
-
1696
- // Search by text query, level, and time range
1697
- const results = await search.search({
1698
- query: 'payment failed',
1699
- level: 'error',
1700
- from: new Date('2025-01-01'),
1701
- to: new Date(),
1702
- limit: 50,
1703
- });
1704
- // → sorted by relevance, full metadata included
1759
+ await search.indexLogs(logEntries); // or: await search.indexLog(entry)
1760
+
1761
+ // Search by text, with filters and options
1762
+ const results = await search.search(
1763
+ 'payment failed',
1764
+ { levels: ['error'], timeRange: { start: new Date('2025-01-01'), end: new Date() } },
1765
+ { limit: 50 }
1766
+ );
1767
+ // → sorted by relevance, each result is { log, score, ... }
1768
+
1769
+ const nl = await search.naturalLanguageSearch('payment errors'); // plain-English query
1770
+ const trace = await search.correlateByTraceId('req-001'); // every log of one request
1705
1771
  ```
1706
1772
 
1773
+ The same code runs as [`examples/search-readme.ts`](examples/search-readme.ts).
1774
+
1707
1775
  ---
1708
1776
 
1709
1777
  ## OpenTelemetry
@@ -1757,6 +1825,8 @@ const logger = createLogger({
1757
1825
  // trace↔log correlation. Buffers drain on close() — no loss on shutdown.
1758
1826
  ```
1759
1827
 
1828
+ Import it from `logixia` or `logixia/transports`. Ready-made configs (URL and auth headers) for Grafana Loki / Grafana Cloud, Axiom, Better Stack and a local OpenTelemetry Collector are in [docs/otlp-recipes.md](docs/otlp-recipes.md).
1829
+
1760
1830
  ---
1761
1831
 
1762
1832
  ## Wide events / canonical log lines
@@ -2256,11 +2326,14 @@ import {
2256
2326
  registerForShutdown, // (logger) => void
2257
2327
  deregisterFromShutdown, // (logger) => void
2258
2328
  resetShutdownHandlers, // () => void — useful in tests
2259
- // NestJS extras
2329
+ } from 'logixia';
2330
+
2331
+ // NestJS extras (requires @nestjs/common):
2332
+ import {
2260
2333
  InjectLogger, // @InjectLogger() parameter decorator
2261
2334
  LogMethod, // @LogMethod() method decorator
2262
2335
  LogixiaExceptionFilter, // global exception filter
2263
- } from 'logixia';
2336
+ } from 'logixia/nest';
2264
2337
 
2265
2338
  // Cloud transports (subpath imports):
2266
2339
  import { CloudWatchTransport } from 'logixia/cloudwatch';
@@ -0,0 +1,134 @@
1
+ import { n as resolveTraceHeaders, o as TraceContext, t as resolveRequestTraceId } from "./request-trace-C_fvIDv4.mjs";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ // src/context/async-context.ts
4
+ /**
5
+ * LogixiaContext — Zero-boilerplate AsyncLocalStorage context propagation.
6
+ *
7
+ * Lets every `logger.info()` call inside a context automatically pick up stored
8
+ * fields (requestId, userId, tenantId, …) without passing them through every function.
9
+ *
10
+ * Works across `Promise.all`, `setTimeout`, and event emitters — anything that
11
+ * inherits the async execution context.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { LogixiaContext } from 'logixia';
16
+ *
17
+ * // Wrap a request handler:
18
+ * app.use((req, res, next) => {
19
+ * LogixiaContext.run({ requestId: req.id, userId: req.user?.id }, next);
20
+ * });
21
+ *
22
+ * // Anywhere deeper in the call tree:
23
+ * async function processOrder(orderId: string) {
24
+ * const ctx = LogixiaContext.get(); // { requestId, userId, orderId }
25
+ * logger.info('Processing order', { orderId, ...ctx });
26
+ * }
27
+ * ```
28
+ */
29
+ const _storage = new AsyncLocalStorage();
30
+ const LogixiaContext = {
31
+ /**
32
+ * Run `callback` inside an async context that carries `store` fields.
33
+ * All loggers queried within `callback` (and its async descendants) will
34
+ * automatically see these fields.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * LogixiaContext.run({ requestId: 'abc', userId: '42' }, async () => {
39
+ * await processOrder(); // sees requestId + userId in every log
40
+ * });
41
+ * ```
42
+ */
43
+ run(store, callback) {
44
+ const parent = _storage.getStore() ?? {};
45
+ const scoped = () => _storage.run({
46
+ ...parent,
47
+ ...store
48
+ }, callback);
49
+ const traceId = store.traceId;
50
+ if (typeof traceId !== "string" || traceId === "") return scoped();
51
+ const trace = TraceContext.instance;
52
+ return trace.storage.run({
53
+ ...trace.storage.getStore(),
54
+ [trace.contextKey]: traceId
55
+ }, scoped);
56
+ },
57
+ /**
58
+ * Return the context fields active in the current async scope.
59
+ * Returns `undefined` when called outside any `LogixiaContext.run()`.
60
+ */
61
+ get() {
62
+ return _storage.getStore();
63
+ },
64
+ /**
65
+ * Merge `fields` into the **existing** context for the current scope.
66
+ * If called outside a `run()` context this is a no-op (logs a dev warning).
67
+ */
68
+ set(fields) {
69
+ const store = _storage.getStore();
70
+ if (!store) return;
71
+ for (const key of Object.keys(fields)) {
72
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
73
+ store[key] = fields[key];
74
+ }
75
+ },
76
+ /**
77
+ * Retrieve the underlying `AsyncLocalStorage` instance.
78
+ * Useful for advanced use-cases like custom middleware.
79
+ */
80
+ getStorage() {
81
+ return _storage;
82
+ }
83
+ };
84
+ function buildScope(request, options, headers) {
85
+ return {
86
+ ...options.enrich ? options.enrich(request) : {},
87
+ traceId: resolveRequestTraceId(request, headers)
88
+ };
89
+ }
90
+ /**
91
+ * Create an Express/Connect-compatible middleware that wraps each request in
92
+ * a `LogixiaContext.run()` scope populated with common request fields.
93
+ *
94
+ * Trace ID: the first present header of `traceIdHeader` (default
95
+ * `DEFAULT_TRACE_HEADERS`, `traceparent` parsed), else `request.id`, else a
96
+ * generated UUID. It is available via `getCurrentTraceId()` and is the logger's
97
+ * top-level `traceId` inside the request. No response header is set unless
98
+ * `responseHeader` is given.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * import { createExpressContextMiddleware } from 'logixia';
103
+ * app.use(createExpressContextMiddleware({ responseHeader: 'x-trace-id' }));
104
+ * ```
105
+ */
106
+ function createExpressContextMiddleware(options = {}) {
107
+ const headers = resolveTraceHeaders(options.traceIdHeader);
108
+ return function logixiaContextMiddleware(req, res, next) {
109
+ var _res$setHeader;
110
+ const scope = buildScope(req, options, headers);
111
+ if (options.responseHeader) (_res$setHeader = res.setHeader) === null || _res$setHeader === void 0 || _res$setHeader.call(res, options.responseHeader, scope.traceId);
112
+ LogixiaContext.run(scope, next);
113
+ };
114
+ }
115
+ /**
116
+ * Create a Fastify lifecycle hook that wraps each request in a context scope.
117
+ *
118
+ * Register with `fastify.addHook('onRequest', createFastifyContextHook())`.
119
+ * Resolves the trace ID exactly like {@link createExpressContextMiddleware} and
120
+ * shares it with `createFastifyPlugin()` from `logixia/middleware`. Fastify's
121
+ * default counter `request.id` (`req-1`, ...) is never used as a trace ID.
122
+ */
123
+ function createFastifyContextHook(options = {}) {
124
+ const headers = resolveTraceHeaders(options.traceIdHeader);
125
+ return function logixiaFastifyHook(request, reply, done) {
126
+ var _reply$header;
127
+ const scope = buildScope(request, options, headers);
128
+ if (options.responseHeader) (_reply$header = reply.header) === null || _reply$header === void 0 || _reply$header.call(reply, options.responseHeader, scope.traceId);
129
+ LogixiaContext.run(scope, done);
130
+ };
131
+ }
132
+ export { createExpressContextMiddleware as n, createFastifyContextHook as r, LogixiaContext as t };
133
+
134
+ //# sourceMappingURL=async-context-B05ArHgM.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-context-B05ArHgM.mjs","names":[],"sources":["../src/context/async-context.ts"],"sourcesContent":["/**\n * LogixiaContext — Zero-boilerplate AsyncLocalStorage context propagation.\n *\n * Lets every `logger.info()` call inside a context automatically pick up stored\n * fields (requestId, userId, tenantId, …) without passing them through every function.\n *\n * Works across `Promise.all`, `setTimeout`, and event emitters — anything that\n * inherits the async execution context.\n *\n * @example\n * ```ts\n * import { LogixiaContext } from 'logixia';\n *\n * // Wrap a request handler:\n * app.use((req, res, next) => {\n * LogixiaContext.run({ requestId: req.id, userId: req.user?.id }, next);\n * });\n *\n * // Anywhere deeper in the call tree:\n * async function processOrder(orderId: string) {\n * const ctx = LogixiaContext.get(); // { requestId, userId, orderId }\n * logger.info('Processing order', { orderId, ...ctx });\n * }\n * ```\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\nimport {\n type ContextRequestLike,\n resolveRequestTraceId,\n resolveTraceHeaders,\n type TraceHeaderOption,\n} from '../utils/request-trace';\nimport { TraceContext } from '../utils/trace.utils';\n\nexport interface LogContext {\n traceId?: string;\n spanId?: string;\n userId?: string;\n tenantId?: string;\n sessionId?: string;\n [key: string]: unknown;\n}\n\n// Module-level singleton so all loggers share the same ALS store.\nconst _storage = new AsyncLocalStorage<LogContext>();\n\nexport const LogixiaContext = {\n /**\n * Run `callback` inside an async context that carries `store` fields.\n * All loggers queried within `callback` (and its async descendants) will\n * automatically see these fields.\n *\n * @example\n * ```ts\n * LogixiaContext.run({ requestId: 'abc', userId: '42' }, async () => {\n * await processOrder(); // sees requestId + userId in every log\n * });\n * ```\n */\n run<T>(store: LogContext, callback: () => T): T {\n const parent = _storage.getStore() ?? {};\n const scoped = (): T => _storage.run({ ...parent, ...store }, callback);\n const traceId = store.traceId;\n if (typeof traceId !== 'string' || traceId === '') return scoped();\n // An explicit traceId also becomes the TraceContext ID, so getCurrentTraceId(),\n // the logger, outbound propagation and the OTel bridge all see the same value.\n const trace = TraceContext.instance;\n return trace.storage.run({ ...trace.storage.getStore(), [trace.contextKey]: traceId }, scoped);\n },\n\n /**\n * Return the context fields active in the current async scope.\n * Returns `undefined` when called outside any `LogixiaContext.run()`.\n */\n get(): LogContext | undefined {\n return _storage.getStore();\n },\n\n /**\n * Merge `fields` into the **existing** context for the current scope.\n * If called outside a `run()` context this is a no-op (logs a dev warning).\n */\n set(fields: Partial<LogContext>): void {\n const store = _storage.getStore();\n if (!store) {\n // Silently ignore — avoids crashing in code that may call setContext\n // before a request context is established (e.g. background jobs).\n return;\n }\n for (const key of Object.keys(fields)) {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;\n store[key] = fields[key];\n }\n },\n\n /**\n * Retrieve the underlying `AsyncLocalStorage` instance.\n * Useful for advanced use-cases like custom middleware.\n */\n getStorage(): AsyncLocalStorage<LogContext> {\n return _storage;\n },\n};\n\nexport type { ContextRequestLike };\n\ninterface ContextHelperOptions<Req extends ContextRequestLike> {\n /**\n * Extract additional fields from the request. A `traceId` returned here is ignored:\n * the resolved request trace ID always wins.\n */\n enrich?: (request: Req & Record<string, unknown>) => Partial<LogContext>;\n /**\n * Header name, or list of names tried in order, to read the trace ID from.\n * Default: `DEFAULT_TRACE_HEADERS` (traceparent is parsed to its 32-hex trace id).\n */\n traceIdHeader?: TraceHeaderOption;\n /**\n * Echo the resolved trace ID on the response under this header name.\n * Default: `false` (no response header is set).\n */\n responseHeader?: string | false;\n}\n\nfunction buildScope<Req extends ContextRequestLike>(\n request: Req,\n options: ContextHelperOptions<Req>,\n headers: readonly string[]\n): LogContext {\n const extra = options.enrich ? options.enrich(request as Req & Record<string, unknown>) : {};\n return { ...extra, traceId: resolveRequestTraceId(request, headers) };\n}\n\n/**\n * Create an Express/Connect-compatible middleware that wraps each request in\n * a `LogixiaContext.run()` scope populated with common request fields.\n *\n * Trace ID: the first present header of `traceIdHeader` (default\n * `DEFAULT_TRACE_HEADERS`, `traceparent` parsed), else `request.id`, else a\n * generated UUID. It is available via `getCurrentTraceId()` and is the logger's\n * top-level `traceId` inside the request. No response header is set unless\n * `responseHeader` is given.\n *\n * @example\n * ```ts\n * import { createExpressContextMiddleware } from 'logixia';\n * app.use(createExpressContextMiddleware({ responseHeader: 'x-trace-id' }));\n * ```\n */\nexport function createExpressContextMiddleware<Req extends ContextRequestLike = ContextRequestLike>(\n options: ContextHelperOptions<Req> = {}\n) {\n const headers = resolveTraceHeaders(options.traceIdHeader);\n\n return function logixiaContextMiddleware(\n req: Req,\n res: { setHeader?: (name: string, value: string) => unknown },\n next: () => void\n ): void {\n const scope = buildScope(req, options, headers);\n if (options.responseHeader) res.setHeader?.(options.responseHeader, scope.traceId as string);\n LogixiaContext.run(scope, next);\n };\n}\n\n/**\n * Create a Fastify lifecycle hook that wraps each request in a context scope.\n *\n * Register with `fastify.addHook('onRequest', createFastifyContextHook())`.\n * Resolves the trace ID exactly like {@link createExpressContextMiddleware} and\n * shares it with `createFastifyPlugin()` from `logixia/middleware`. Fastify's\n * default counter `request.id` (`req-1`, ...) is never used as a trace ID.\n */\nexport function createFastifyContextHook<Req extends ContextRequestLike = ContextRequestLike>(\n options: ContextHelperOptions<Req> = {}\n) {\n const headers = resolveTraceHeaders(options.traceIdHeader);\n\n return function logixiaFastifyHook(\n request: Req,\n reply: { header?: (name: string, value: string) => unknown },\n done: () => void\n ): void {\n const scope = buildScope(request, options, headers);\n if (options.responseHeader) reply.header?.(options.responseHeader, scope.traceId as string);\n LogixiaContext.run(scope, done);\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,MAAM,WAAW,IAAI,kBAA8B;AAEnD,MAAa,iBAAiB;;;;;;;;;;;;;CAa5B,IAAO,OAAmB,UAAsB;EAC9C,MAAM,SAAS,SAAS,SAAS,KAAK,CAAC;EACvC,MAAM,eAAkB,SAAS,IAAI;GAAE,GAAG;GAAQ,GAAG;EAAM,GAAG,QAAQ;EACtE,MAAM,UAAU,MAAM;EACtB,IAAI,OAAO,YAAY,YAAY,YAAY,IAAI,OAAO,OAAO;EAGjE,MAAM,QAAQ,aAAa;EAC3B,OAAO,MAAM,QAAQ,IAAI;GAAE,GAAG,MAAM,QAAQ,SAAS;IAAI,MAAM,aAAa;EAAQ,GAAG,MAAM;CAC/F;;;;;CAMA,MAA8B;EAC5B,OAAO,SAAS,SAAS;CAC3B;;;;;CAMA,IAAI,QAAmC;EACrC,MAAM,QAAQ,SAAS,SAAS;EAChC,IAAI,CAAC,OAGH;EAEF,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,GAAG;GACrC,IAAI,QAAQ,eAAe,QAAQ,iBAAiB,QAAQ,aAAa;GACzE,MAAM,OAAO,OAAO;EACtB;CACF;;;;;CAMA,aAA4C;EAC1C,OAAO;CACT;AACF;AAsBA,SAAS,WACP,SACA,SACA,SACY;CAEZ,OAAO;EAAE,GADK,QAAQ,SAAS,QAAQ,OAAO,OAAwC,IAAI,CAAC;EACxE,SAAS,sBAAsB,SAAS,OAAO;CAAE;AACtE;;;;;;;;;;;;;;;;;AAkBA,SAAgB,+BACd,UAAqC,CAAC,GACtC;CACA,MAAM,UAAU,oBAAoB,QAAQ,aAAa;CAEzD,OAAO,SAAS,yBACd,KACA,KACA,MACM;;EACN,MAAM,QAAQ,WAAW,KAAK,SAAS,OAAO;EAC9C,IAAI,QAAQ,gBAAgB,CAAA,iBAAA,IAAI,eAAA,QAAA,mBAAA,KAAA,KAAA,eAAA,KAAA,KAAY,QAAQ,gBAAgB,MAAM,OAAiB;EAC3F,eAAe,IAAI,OAAO,IAAI;CAChC;AACF;;;;;;;;;AAUA,SAAgB,yBACd,UAAqC,CAAC,GACtC;CACA,MAAM,UAAU,oBAAoB,QAAQ,aAAa;CAEzD,OAAO,SAAS,mBACd,SACA,OACA,MACM;;EACN,MAAM,QAAQ,WAAW,SAAS,SAAS,OAAO;EAClD,IAAI,QAAQ,gBAAgB,CAAA,gBAAA,MAAM,YAAA,QAAA,kBAAA,KAAA,KAAA,cAAA,KAAA,OAAS,QAAQ,gBAAgB,MAAM,OAAiB;EAC1F,eAAe,IAAI,OAAO,IAAI;CAChC;AACF"}
@@ -0,0 +1,150 @@
1
+ const require_request_trace = require("./request-trace-wTJdKp6-.js");
2
+ // src/context/async-context.ts
3
+ /**
4
+ * LogixiaContext — Zero-boilerplate AsyncLocalStorage context propagation.
5
+ *
6
+ * Lets every `logger.info()` call inside a context automatically pick up stored
7
+ * fields (requestId, userId, tenantId, …) without passing them through every function.
8
+ *
9
+ * Works across `Promise.all`, `setTimeout`, and event emitters — anything that
10
+ * inherits the async execution context.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { LogixiaContext } from 'logixia';
15
+ *
16
+ * // Wrap a request handler:
17
+ * app.use((req, res, next) => {
18
+ * LogixiaContext.run({ requestId: req.id, userId: req.user?.id }, next);
19
+ * });
20
+ *
21
+ * // Anywhere deeper in the call tree:
22
+ * async function processOrder(orderId: string) {
23
+ * const ctx = LogixiaContext.get(); // { requestId, userId, orderId }
24
+ * logger.info('Processing order', { orderId, ...ctx });
25
+ * }
26
+ * ```
27
+ */
28
+ const _storage = new (require("node:async_hooks")).AsyncLocalStorage();
29
+ const LogixiaContext = {
30
+ /**
31
+ * Run `callback` inside an async context that carries `store` fields.
32
+ * All loggers queried within `callback` (and its async descendants) will
33
+ * automatically see these fields.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * LogixiaContext.run({ requestId: 'abc', userId: '42' }, async () => {
38
+ * await processOrder(); // sees requestId + userId in every log
39
+ * });
40
+ * ```
41
+ */
42
+ run(store, callback) {
43
+ const parent = _storage.getStore() ?? {};
44
+ const scoped = () => _storage.run({
45
+ ...parent,
46
+ ...store
47
+ }, callback);
48
+ const traceId = store.traceId;
49
+ if (typeof traceId !== "string" || traceId === "") return scoped();
50
+ const trace = require_request_trace.TraceContext.instance;
51
+ return trace.storage.run({
52
+ ...trace.storage.getStore(),
53
+ [trace.contextKey]: traceId
54
+ }, scoped);
55
+ },
56
+ /**
57
+ * Return the context fields active in the current async scope.
58
+ * Returns `undefined` when called outside any `LogixiaContext.run()`.
59
+ */
60
+ get() {
61
+ return _storage.getStore();
62
+ },
63
+ /**
64
+ * Merge `fields` into the **existing** context for the current scope.
65
+ * If called outside a `run()` context this is a no-op (logs a dev warning).
66
+ */
67
+ set(fields) {
68
+ const store = _storage.getStore();
69
+ if (!store) return;
70
+ for (const key of Object.keys(fields)) {
71
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
72
+ store[key] = fields[key];
73
+ }
74
+ },
75
+ /**
76
+ * Retrieve the underlying `AsyncLocalStorage` instance.
77
+ * Useful for advanced use-cases like custom middleware.
78
+ */
79
+ getStorage() {
80
+ return _storage;
81
+ }
82
+ };
83
+ function buildScope(request, options, headers) {
84
+ return {
85
+ ...options.enrich ? options.enrich(request) : {},
86
+ traceId: require_request_trace.resolveRequestTraceId(request, headers)
87
+ };
88
+ }
89
+ /**
90
+ * Create an Express/Connect-compatible middleware that wraps each request in
91
+ * a `LogixiaContext.run()` scope populated with common request fields.
92
+ *
93
+ * Trace ID: the first present header of `traceIdHeader` (default
94
+ * `DEFAULT_TRACE_HEADERS`, `traceparent` parsed), else `request.id`, else a
95
+ * generated UUID. It is available via `getCurrentTraceId()` and is the logger's
96
+ * top-level `traceId` inside the request. No response header is set unless
97
+ * `responseHeader` is given.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * import { createExpressContextMiddleware } from 'logixia';
102
+ * app.use(createExpressContextMiddleware({ responseHeader: 'x-trace-id' }));
103
+ * ```
104
+ */
105
+ function createExpressContextMiddleware(options = {}) {
106
+ const headers = require_request_trace.resolveTraceHeaders(options.traceIdHeader);
107
+ return function logixiaContextMiddleware(req, res, next) {
108
+ var _res$setHeader;
109
+ const scope = buildScope(req, options, headers);
110
+ if (options.responseHeader) (_res$setHeader = res.setHeader) === null || _res$setHeader === void 0 || _res$setHeader.call(res, options.responseHeader, scope.traceId);
111
+ LogixiaContext.run(scope, next);
112
+ };
113
+ }
114
+ /**
115
+ * Create a Fastify lifecycle hook that wraps each request in a context scope.
116
+ *
117
+ * Register with `fastify.addHook('onRequest', createFastifyContextHook())`.
118
+ * Resolves the trace ID exactly like {@link createExpressContextMiddleware} and
119
+ * shares it with `createFastifyPlugin()` from `logixia/middleware`. Fastify's
120
+ * default counter `request.id` (`req-1`, ...) is never used as a trace ID.
121
+ */
122
+ function createFastifyContextHook(options = {}) {
123
+ const headers = require_request_trace.resolveTraceHeaders(options.traceIdHeader);
124
+ return function logixiaFastifyHook(request, reply, done) {
125
+ var _reply$header;
126
+ const scope = buildScope(request, options, headers);
127
+ if (options.responseHeader) (_reply$header = reply.header) === null || _reply$header === void 0 || _reply$header.call(reply, options.responseHeader, scope.traceId);
128
+ LogixiaContext.run(scope, done);
129
+ };
130
+ }
131
+ Object.defineProperty(exports, "LogixiaContext", {
132
+ enumerable: true,
133
+ get: function() {
134
+ return LogixiaContext;
135
+ }
136
+ });
137
+ Object.defineProperty(exports, "createExpressContextMiddleware", {
138
+ enumerable: true,
139
+ get: function() {
140
+ return createExpressContextMiddleware;
141
+ }
142
+ });
143
+ Object.defineProperty(exports, "createFastifyContextHook", {
144
+ enumerable: true,
145
+ get: function() {
146
+ return createFastifyContextHook;
147
+ }
148
+ });
149
+
150
+ //# sourceMappingURL=async-context-DTQgi8f7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-context-DTQgi8f7.js","names":["AsyncLocalStorage","TraceContext","resolveRequestTraceId","resolveTraceHeaders"],"sources":["../src/context/async-context.ts"],"sourcesContent":["/**\n * LogixiaContext — Zero-boilerplate AsyncLocalStorage context propagation.\n *\n * Lets every `logger.info()` call inside a context automatically pick up stored\n * fields (requestId, userId, tenantId, …) without passing them through every function.\n *\n * Works across `Promise.all`, `setTimeout`, and event emitters — anything that\n * inherits the async execution context.\n *\n * @example\n * ```ts\n * import { LogixiaContext } from 'logixia';\n *\n * // Wrap a request handler:\n * app.use((req, res, next) => {\n * LogixiaContext.run({ requestId: req.id, userId: req.user?.id }, next);\n * });\n *\n * // Anywhere deeper in the call tree:\n * async function processOrder(orderId: string) {\n * const ctx = LogixiaContext.get(); // { requestId, userId, orderId }\n * logger.info('Processing order', { orderId, ...ctx });\n * }\n * ```\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\nimport {\n type ContextRequestLike,\n resolveRequestTraceId,\n resolveTraceHeaders,\n type TraceHeaderOption,\n} from '../utils/request-trace';\nimport { TraceContext } from '../utils/trace.utils';\n\nexport interface LogContext {\n traceId?: string;\n spanId?: string;\n userId?: string;\n tenantId?: string;\n sessionId?: string;\n [key: string]: unknown;\n}\n\n// Module-level singleton so all loggers share the same ALS store.\nconst _storage = new AsyncLocalStorage<LogContext>();\n\nexport const LogixiaContext = {\n /**\n * Run `callback` inside an async context that carries `store` fields.\n * All loggers queried within `callback` (and its async descendants) will\n * automatically see these fields.\n *\n * @example\n * ```ts\n * LogixiaContext.run({ requestId: 'abc', userId: '42' }, async () => {\n * await processOrder(); // sees requestId + userId in every log\n * });\n * ```\n */\n run<T>(store: LogContext, callback: () => T): T {\n const parent = _storage.getStore() ?? {};\n const scoped = (): T => _storage.run({ ...parent, ...store }, callback);\n const traceId = store.traceId;\n if (typeof traceId !== 'string' || traceId === '') return scoped();\n // An explicit traceId also becomes the TraceContext ID, so getCurrentTraceId(),\n // the logger, outbound propagation and the OTel bridge all see the same value.\n const trace = TraceContext.instance;\n return trace.storage.run({ ...trace.storage.getStore(), [trace.contextKey]: traceId }, scoped);\n },\n\n /**\n * Return the context fields active in the current async scope.\n * Returns `undefined` when called outside any `LogixiaContext.run()`.\n */\n get(): LogContext | undefined {\n return _storage.getStore();\n },\n\n /**\n * Merge `fields` into the **existing** context for the current scope.\n * If called outside a `run()` context this is a no-op (logs a dev warning).\n */\n set(fields: Partial<LogContext>): void {\n const store = _storage.getStore();\n if (!store) {\n // Silently ignore — avoids crashing in code that may call setContext\n // before a request context is established (e.g. background jobs).\n return;\n }\n for (const key of Object.keys(fields)) {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;\n store[key] = fields[key];\n }\n },\n\n /**\n * Retrieve the underlying `AsyncLocalStorage` instance.\n * Useful for advanced use-cases like custom middleware.\n */\n getStorage(): AsyncLocalStorage<LogContext> {\n return _storage;\n },\n};\n\nexport type { ContextRequestLike };\n\ninterface ContextHelperOptions<Req extends ContextRequestLike> {\n /**\n * Extract additional fields from the request. A `traceId` returned here is ignored:\n * the resolved request trace ID always wins.\n */\n enrich?: (request: Req & Record<string, unknown>) => Partial<LogContext>;\n /**\n * Header name, or list of names tried in order, to read the trace ID from.\n * Default: `DEFAULT_TRACE_HEADERS` (traceparent is parsed to its 32-hex trace id).\n */\n traceIdHeader?: TraceHeaderOption;\n /**\n * Echo the resolved trace ID on the response under this header name.\n * Default: `false` (no response header is set).\n */\n responseHeader?: string | false;\n}\n\nfunction buildScope<Req extends ContextRequestLike>(\n request: Req,\n options: ContextHelperOptions<Req>,\n headers: readonly string[]\n): LogContext {\n const extra = options.enrich ? options.enrich(request as Req & Record<string, unknown>) : {};\n return { ...extra, traceId: resolveRequestTraceId(request, headers) };\n}\n\n/**\n * Create an Express/Connect-compatible middleware that wraps each request in\n * a `LogixiaContext.run()` scope populated with common request fields.\n *\n * Trace ID: the first present header of `traceIdHeader` (default\n * `DEFAULT_TRACE_HEADERS`, `traceparent` parsed), else `request.id`, else a\n * generated UUID. It is available via `getCurrentTraceId()` and is the logger's\n * top-level `traceId` inside the request. No response header is set unless\n * `responseHeader` is given.\n *\n * @example\n * ```ts\n * import { createExpressContextMiddleware } from 'logixia';\n * app.use(createExpressContextMiddleware({ responseHeader: 'x-trace-id' }));\n * ```\n */\nexport function createExpressContextMiddleware<Req extends ContextRequestLike = ContextRequestLike>(\n options: ContextHelperOptions<Req> = {}\n) {\n const headers = resolveTraceHeaders(options.traceIdHeader);\n\n return function logixiaContextMiddleware(\n req: Req,\n res: { setHeader?: (name: string, value: string) => unknown },\n next: () => void\n ): void {\n const scope = buildScope(req, options, headers);\n if (options.responseHeader) res.setHeader?.(options.responseHeader, scope.traceId as string);\n LogixiaContext.run(scope, next);\n };\n}\n\n/**\n * Create a Fastify lifecycle hook that wraps each request in a context scope.\n *\n * Register with `fastify.addHook('onRequest', createFastifyContextHook())`.\n * Resolves the trace ID exactly like {@link createExpressContextMiddleware} and\n * shares it with `createFastifyPlugin()` from `logixia/middleware`. Fastify's\n * default counter `request.id` (`req-1`, ...) is never used as a trace ID.\n */\nexport function createFastifyContextHook<Req extends ContextRequestLike = ContextRequestLike>(\n options: ContextHelperOptions<Req> = {}\n) {\n const headers = resolveTraceHeaders(options.traceIdHeader);\n\n return function logixiaFastifyHook(\n request: Req,\n reply: { header?: (name: string, value: string) => unknown },\n done: () => void\n ): void {\n const scope = buildScope(request, options, headers);\n if (options.responseHeader) reply.header?.(options.responseHeader, scope.traceId as string);\n LogixiaContext.run(scope, done);\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,MAAM,WAAW,K,0BAAIA,EAAAA,CAAAA,kBAA8B;AAEnD,MAAa,iBAAiB;;;;;;;;;;;;;CAa5B,IAAO,OAAmB,UAAsB;EAC9C,MAAM,SAAS,SAAS,SAAS,KAAK,CAAC;EACvC,MAAM,eAAkB,SAAS,IAAI;GAAE,GAAG;GAAQ,GAAG;EAAM,GAAG,QAAQ;EACtE,MAAM,UAAU,MAAM;EACtB,IAAI,OAAO,YAAY,YAAY,YAAY,IAAI,OAAO,OAAO;EAGjE,MAAM,QAAQC,sBAAAA,aAAa;EAC3B,OAAO,MAAM,QAAQ,IAAI;GAAE,GAAG,MAAM,QAAQ,SAAS;IAAI,MAAM,aAAa;EAAQ,GAAG,MAAM;CAC/F;;;;;CAMA,MAA8B;EAC5B,OAAO,SAAS,SAAS;CAC3B;;;;;CAMA,IAAI,QAAmC;EACrC,MAAM,QAAQ,SAAS,SAAS;EAChC,IAAI,CAAC,OAGH;EAEF,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,GAAG;GACrC,IAAI,QAAQ,eAAe,QAAQ,iBAAiB,QAAQ,aAAa;GACzE,MAAM,OAAO,OAAO;EACtB;CACF;;;;;CAMA,aAA4C;EAC1C,OAAO;CACT;AACF;AAsBA,SAAS,WACP,SACA,SACA,SACY;CAEZ,OAAO;EAAE,GADK,QAAQ,SAAS,QAAQ,OAAO,OAAwC,IAAI,CAAC;EACxE,SAASC,sBAAAA,sBAAsB,SAAS,OAAO;CAAE;AACtE;;;;;;;;;;;;;;;;;AAkBA,SAAgB,+BACd,UAAqC,CAAC,GACtC;CACA,MAAM,UAAUC,sBAAAA,oBAAoB,QAAQ,aAAa;CAEzD,OAAO,SAAS,yBACd,KACA,KACA,MACM;;EACN,MAAM,QAAQ,WAAW,KAAK,SAAS,OAAO;EAC9C,IAAI,QAAQ,gBAAgB,CAAA,iBAAA,IAAI,eAAA,QAAA,mBAAA,KAAA,KAAA,eAAA,KAAA,KAAY,QAAQ,gBAAgB,MAAM,OAAiB;EAC3F,eAAe,IAAI,OAAO,IAAI;CAChC;AACF;;;;;;;;;AAUA,SAAgB,yBACd,UAAqC,CAAC,GACtC;CACA,MAAM,UAAUA,sBAAAA,oBAAoB,QAAQ,aAAa;CAEzD,OAAO,SAAS,mBACd,SACA,OACA,MACM;;EACN,MAAM,QAAQ,WAAW,SAAS,SAAS,OAAO;EAClD,IAAI,QAAQ,gBAAgB,CAAA,gBAAA,MAAM,YAAA,QAAA,kBAAA,KAAA,KAAA,cAAA,KAAA,OAAS,QAAQ,gBAAgB,MAAM,OAAiB;EAC1F,eAAe,IAAI,OAAO,IAAI;CAChC;AACF"}