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.
- package/README.md +117 -44
- package/dist/async-context-B05ArHgM.mjs +134 -0
- package/dist/async-context-B05ArHgM.mjs.map +1 -0
- package/dist/async-context-DTQgi8f7.js +150 -0
- package/dist/async-context-DTQgi8f7.js.map +1 -0
- package/dist/{azure-monitor.transport-CHnjVRF-.mjs → azure-monitor.transport-BPbgjATu.mjs} +27 -15
- package/dist/azure-monitor.transport-BPbgjATu.mjs.map +1 -0
- package/dist/{azure-monitor.transport-BDFO4F-i.d.ts → azure-monitor.transport-C7kYyZUW.d.mts} +3 -6
- package/dist/azure-monitor.transport-C7kYyZUW.d.mts.map +1 -0
- package/dist/{azure-monitor.transport-CrvfHd9C.js → azure-monitor.transport-CwbOLZHB.js} +32 -20
- package/dist/azure-monitor.transport-CwbOLZHB.js.map +1 -0
- package/dist/{azure-monitor.transport-D5wQEH04.d.mts → azure-monitor.transport-DV03Z5P4.d.ts} +3 -6
- package/dist/azure-monitor.transport-DV03Z5P4.d.ts.map +1 -0
- package/dist/azure.d.mts +1 -2
- package/dist/azure.d.ts +1 -2
- package/dist/azure.js +3 -6
- package/dist/azure.mjs +1 -6
- package/dist/{batch-buffer-PGvSgbmd.mjs → batch-buffer-0fgLmIPn.mjs} +21 -7
- package/dist/batch-buffer-0fgLmIPn.mjs.map +1 -0
- package/dist/{batch-buffer-BY-mRWIK.js → batch-buffer-DT4gdXmf.js} +53 -40
- package/dist/batch-buffer-DT4gdXmf.js.map +1 -0
- package/dist/browser.d.mts +52 -23
- package/dist/browser.d.mts.map +1 -1
- package/dist/browser.d.ts +52 -23
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +460 -62
- package/dist/browser.js.map +1 -1
- package/dist/browser.mjs +461 -65
- package/dist/browser.mjs.map +1 -1
- package/dist/{nestjs-extras-ImbuDhs1.mjs → builder-D70z6kjj.mjs} +279 -965
- package/dist/builder-D70z6kjj.mjs.map +1 -0
- package/dist/{nestjs-extras-DnJ6vrIC.js → builder-M7NoaugM.js} +421 -1177
- package/dist/builder-M7NoaugM.js.map +1 -0
- package/dist/cli/index.js +86 -67
- package/dist/cloudwatch.d.mts +1 -2
- package/dist/cloudwatch.d.ts +1 -2
- package/dist/cloudwatch.js +3 -7
- package/dist/cloudwatch.mjs +1 -7
- package/dist/{cloudwatch.transport-BO1ahxw-.js → cloudwatch.transport-B2wKQFF7.js} +61 -36
- package/dist/cloudwatch.transport-B2wKQFF7.js.map +1 -0
- package/dist/{cloudwatch.transport-DZ6ies7g.mjs → cloudwatch.transport-BTvYfLs9.mjs} +54 -28
- package/dist/cloudwatch.transport-BTvYfLs9.mjs.map +1 -0
- package/dist/{cloudwatch.transport-B00st6Xv.d.ts → cloudwatch.transport-Bf7L4I59.d.ts} +2 -6
- package/dist/cloudwatch.transport-Bf7L4I59.d.ts.map +1 -0
- package/dist/{cloudwatch.transport-Blc7Gtxt.d.mts → cloudwatch.transport-Rj4yTpRn.d.mts} +2 -6
- package/dist/cloudwatch.transport-Rj4yTpRn.d.mts.map +1 -0
- package/dist/{coerce.utils-Cu6IUTgX.mjs → coerce.utils-B_rEudCn.mjs} +3 -4
- package/dist/{coerce.utils-Cu6IUTgX.mjs.map → coerce.utils-B_rEudCn.mjs.map} +1 -1
- package/dist/{coerce.utils-B-LilukN.js → coerce.utils-C1C5sIX0.js} +8 -10
- package/dist/{coerce.utils-B-LilukN.js.map → coerce.utils-C1C5sIX0.js.map} +1 -1
- package/dist/correlation.d.mts +13 -16
- package/dist/correlation.d.mts.map +1 -1
- package/dist/correlation.d.ts +13 -16
- package/dist/correlation.d.ts.map +1 -1
- package/dist/correlation.js +58 -9
- package/dist/correlation.js.map +1 -1
- package/dist/correlation.mjs +56 -6
- package/dist/correlation.mjs.map +1 -1
- package/dist/{entry-fields-BJkuPnIg.mjs → entry-fields-CGuvT2R0.mjs} +5 -6
- package/dist/entry-fields-CGuvT2R0.mjs.map +1 -0
- package/dist/{entry-fields-BvOLFlJ4.js → entry-fields-RLd1jCoR.js} +10 -12
- package/dist/entry-fields-RLd1jCoR.js.map +1 -0
- package/dist/gcp.d.mts +1 -2
- package/dist/gcp.d.ts +1 -2
- package/dist/gcp.js +3 -7
- package/dist/gcp.mjs +1 -7
- package/dist/{gcp.transport-m4fVw0b1.d.ts → gcp.transport-C5D72b1g.d.mts} +3 -6
- package/dist/gcp.transport-C5D72b1g.d.mts.map +1 -0
- package/dist/{gcp.transport-zzNyIoih.d.mts → gcp.transport-CFQfdAOK.d.ts} +3 -6
- package/dist/gcp.transport-CFQfdAOK.d.ts.map +1 -0
- package/dist/{gcp.transport-DBX5TPIy.mjs → gcp.transport-CGKopAhQ.mjs} +55 -16
- package/dist/gcp.transport-CGKopAhQ.mjs.map +1 -0
- package/dist/{gcp.transport-CHV8MGgz.js → gcp.transport-KZe9hCAR.js} +60 -22
- package/dist/gcp.transport-KZe9hCAR.js.map +1 -0
- package/dist/{index-nZEeCr3X.d.ts → index-DwozbmB4.d.ts} +3 -8
- package/dist/index-DwozbmB4.d.ts.map +1 -0
- package/dist/{index-BPwEisy4.d.mts → index-xqaJtAZn.d.mts} +3 -8
- package/dist/index-xqaJtAZn.d.mts.map +1 -0
- package/dist/index.d.mts +141 -203
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +140 -202
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +196 -381
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +145 -294
- package/dist/index.mjs.map +1 -1
- package/dist/internal-log-b0Vp4qrB.js +183 -0
- package/dist/internal-log-b0Vp4qrB.js.map +1 -0
- package/dist/internal-log-uCSd1PMc.mjs +142 -0
- package/dist/internal-log-uCSd1PMc.mjs.map +1 -0
- package/dist/logitron-logger-D2B75Oc7.d.ts +365 -0
- package/dist/logitron-logger-D2B75Oc7.d.ts.map +1 -0
- package/dist/logitron-logger-oEd6wNhf.d.mts +365 -0
- package/dist/logitron-logger-oEd6wNhf.d.mts.map +1 -0
- package/dist/middleware.d.mts +10 -12
- package/dist/middleware.d.mts.map +1 -1
- package/dist/middleware.d.ts +10 -12
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +16 -47
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +13 -45
- package/dist/middleware.mjs.map +1 -1
- package/dist/nest.d.mts +467 -10
- package/dist/nest.d.mts.map +1 -1
- package/dist/nest.d.ts +467 -10
- package/dist/nest.d.ts.map +1 -1
- package/dist/nest.js +889 -43
- package/dist/nest.js.map +1 -0
- package/dist/nest.mjs +850 -9
- package/dist/nest.mjs.map +1 -0
- package/dist/{trace.utils-CvXgaiQT.mjs → request-trace-C_fvIDv4.mjs} +44 -9
- package/dist/request-trace-C_fvIDv4.mjs.map +1 -0
- package/dist/{trace.utils-DpBOLDtq.js → request-trace-wTJdKp6-.js} +121 -77
- package/dist/request-trace-wTJdKp6-.js.map +1 -0
- package/dist/rolldown-runtime-D3PmRqjj.mjs +4 -0
- package/dist/{safe-stringify-DJ66tW5I.mjs → safe-stringify-BMRl3EX3.mjs} +3 -4
- package/dist/safe-stringify-BMRl3EX3.mjs.map +1 -0
- package/dist/{safe-stringify-DWo3aZp_.js → safe-stringify-DLz1Kz3o.js} +23 -25
- package/dist/safe-stringify-DLz1Kz3o.js.map +1 -0
- package/dist/{search-CHUZPl0h.d.ts → search-BH33XhuU.d.mts} +2 -22
- package/dist/search-BH33XhuU.d.mts.map +1 -0
- package/dist/{search-DjXSn6v5.mjs → search-CWMVYC-R.mjs} +34 -41
- package/dist/search-CWMVYC-R.mjs.map +1 -0
- package/dist/{search-BfBzDIT9.d.mts → search-CazwyX5Z.d.ts} +2 -22
- package/dist/search-CazwyX5Z.d.ts.map +1 -0
- package/dist/{search-B0MH1SJJ.js → search-DUfWuzFP.js} +64 -71
- package/dist/search-DUfWuzFP.js.map +1 -0
- package/dist/search.d.mts +2 -4
- package/dist/search.d.ts +2 -4
- package/dist/search.js +3 -4
- package/dist/search.mjs +1 -4
- package/dist/secret-config-BegMLkYl.mjs +209 -0
- package/dist/secret-config-BegMLkYl.mjs.map +1 -0
- package/dist/secret-config-CBZBs0p1.js +271 -0
- package/dist/secret-config-CBZBs0p1.js.map +1 -0
- package/dist/slack.transport-Ce96JBDk.mjs +824 -0
- package/dist/slack.transport-Ce96JBDk.mjs.map +1 -0
- package/dist/slack.transport-Dlx3xvb7.js +847 -0
- package/dist/slack.transport-Dlx3xvb7.js.map +1 -0
- package/dist/slack.transport-DrirgDNy.d.ts +159 -0
- package/dist/slack.transport-DrirgDNy.d.ts.map +1 -0
- package/dist/slack.transport-DxHOXlLu.d.mts +159 -0
- package/dist/slack.transport-DxHOXlLu.d.mts.map +1 -0
- package/dist/testing.d.mts +3 -8
- package/dist/testing.d.mts.map +1 -1
- package/dist/testing.d.ts +3 -8
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +3 -4
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +2 -3
- package/dist/testing.mjs.map +1 -1
- package/dist/{transport.manager-Crau3Rle.js → transport.manager-DkcEOlqV.js} +331 -234
- package/dist/transport.manager-DkcEOlqV.js.map +1 -0
- package/dist/{transport.manager-CpdVHEGP.mjs → transport.manager-eZ_EeC00.mjs} +274 -178
- package/dist/transport.manager-eZ_EeC00.mjs.map +1 -0
- package/dist/{transport.types-CtNr3D7S.d.mts → transport.types-BJVL1QQl.d.mts} +5 -4
- package/dist/transport.types-BJVL1QQl.d.mts.map +1 -0
- package/dist/{transport.types-DqVLZTyf.d.ts → transport.types-BJVL1QQl.d.ts} +5 -4
- package/dist/transport.types-BJVL1QQl.d.ts.map +1 -0
- package/dist/transports.d.mts +32 -41
- package/dist/transports.d.mts.map +1 -1
- package/dist/transports.d.ts +32 -41
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +12 -12
- package/dist/transports.mjs +7 -12
- package/dist/worker-entry.js +337 -87
- package/dist/worker.d.mts +1 -2
- package/dist/worker.d.ts +1 -2
- package/dist/worker.js +3 -4
- package/dist/worker.mjs +1 -4
- package/dist/{worker.transport-Bj-i0P9V.d.ts → worker.transport-BuPwBi3d.d.ts} +2 -6
- package/dist/worker.transport-BuPwBi3d.d.ts.map +1 -0
- package/dist/{worker.transport-JP8YHBuU.js → worker.transport-C6GNl_r2.js} +44 -14
- package/dist/worker.transport-C6GNl_r2.js.map +1 -0
- package/dist/{worker.transport-otwhJ_Et.mjs → worker.transport-DLVA4y2e.mjs} +39 -9
- package/dist/worker.transport-DLVA4y2e.mjs.map +1 -0
- package/dist/{worker.transport-Bgv9heDI.d.mts → worker.transport-hJay1wxA.d.mts} +2 -6
- package/dist/worker.transport-hJay1wxA.d.mts.map +1 -0
- package/package.json +36 -19
- package/dist/async-context-BzJrbfQs.mjs +0 -72
- package/dist/async-context-BzJrbfQs.mjs.map +0 -1
- package/dist/async-context-DyQQYq3I.js +0 -92
- package/dist/async-context-DyQQYq3I.js.map +0 -1
- package/dist/azure-monitor.transport-BDFO4F-i.d.ts.map +0 -1
- package/dist/azure-monitor.transport-CHnjVRF-.mjs.map +0 -1
- package/dist/azure-monitor.transport-CrvfHd9C.js.map +0 -1
- package/dist/azure-monitor.transport-D5wQEH04.d.mts.map +0 -1
- package/dist/batch-buffer-BY-mRWIK.js.map +0 -1
- package/dist/batch-buffer-PGvSgbmd.mjs.map +0 -1
- package/dist/chunk-DLSkYcPk.mjs +0 -7
- package/dist/chunk-nOFOJqeH.js +0 -30
- package/dist/cloudwatch.transport-B00st6Xv.d.ts.map +0 -1
- package/dist/cloudwatch.transport-BO1ahxw-.js.map +0 -1
- package/dist/cloudwatch.transport-Blc7Gtxt.d.mts.map +0 -1
- package/dist/cloudwatch.transport-DZ6ies7g.mjs.map +0 -1
- package/dist/entry-fields-BJkuPnIg.mjs.map +0 -1
- package/dist/entry-fields-BvOLFlJ4.js.map +0 -1
- package/dist/gcp.transport-CHV8MGgz.js.map +0 -1
- package/dist/gcp.transport-DBX5TPIy.mjs.map +0 -1
- package/dist/gcp.transport-m4fVw0b1.d.ts.map +0 -1
- package/dist/gcp.transport-zzNyIoih.d.mts.map +0 -1
- package/dist/index-BPwEisy4.d.mts.map +0 -1
- package/dist/index-nZEeCr3X.d.ts.map +0 -1
- package/dist/internal-log-BUJYMZbE.js +0 -70
- package/dist/internal-log-BUJYMZbE.js.map +0 -1
- package/dist/internal-log-Cm8SOWSV.mjs +0 -52
- package/dist/internal-log-Cm8SOWSV.mjs.map +0 -1
- package/dist/nestjs-extras-DnJ6vrIC.js.map +0 -1
- package/dist/nestjs-extras-ImbuDhs1.mjs.map +0 -1
- package/dist/nestjs-extras-OkC796py.d.mts +0 -725
- package/dist/nestjs-extras-OkC796py.d.mts.map +0 -1
- package/dist/nestjs-extras-mKrHE9S6.d.ts +0 -725
- package/dist/nestjs-extras-mKrHE9S6.d.ts.map +0 -1
- package/dist/safe-stringify-DJ66tW5I.mjs.map +0 -1
- package/dist/safe-stringify-DWo3aZp_.js.map +0 -1
- package/dist/search-B0MH1SJJ.js.map +0 -1
- package/dist/search-BfBzDIT9.d.mts.map +0 -1
- package/dist/search-CHUZPl0h.d.ts.map +0 -1
- package/dist/search-DjXSn6v5.mjs.map +0 -1
- package/dist/trace.utils-CvXgaiQT.mjs.map +0 -1
- package/dist/trace.utils-DpBOLDtq.js.map +0 -1
- package/dist/transport.manager-CpdVHEGP.mjs.map +0 -1
- package/dist/transport.manager-Crau3Rle.js.map +0 -1
- package/dist/transport.types-CtNr3D7S.d.mts.map +0 -1
- package/dist/transport.types-DqVLZTyf.d.ts.map +0 -1
- package/dist/worker.transport-Bgv9heDI.d.mts.map +0 -1
- package/dist/worker.transport-Bj-i0P9V.d.ts.map +0 -1
- package/dist/worker.transport-JP8YHBuU.js.map +0 -1
- 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 {
|
|
985
|
+
import { createExpressContextMiddleware, createFastifyContextHook } from 'logixia';
|
|
961
986
|
|
|
962
|
-
//
|
|
963
|
-
//
|
|
964
|
-
|
|
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
|
-
//
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
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 `
|
|
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 {
|
|
1251
|
+
import { runWithTraceId } from 'logixia';
|
|
1193
1252
|
|
|
1194
|
-
await
|
|
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({
|
|
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.
|
|
1695
|
-
|
|
1696
|
-
// Search by text
|
|
1697
|
-
const results = await search.search(
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
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
|
-
|
|
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"}
|