@horizon-republic/nestjs-jetstream 2.9.1 → 2.11.0

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/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from2, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
  var __decorateClass = (decorators, target, key, kind) => {
20
30
  var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
@@ -29,6 +39,7 @@ var __decorateParam = (index, decorator) => (target, key) => decorator(target, k
29
39
  // src/index.ts
30
40
  var index_exports = {};
31
41
  __export(index_exports, {
42
+ ConsumeKind: () => ConsumeKind,
32
43
  DEFAULT_BROADCAST_CONSUMER_CONFIG: () => DEFAULT_BROADCAST_CONSUMER_CONFIG,
33
44
  DEFAULT_BROADCAST_STREAM_CONFIG: () => DEFAULT_BROADCAST_STREAM_CONFIG,
34
45
  DEFAULT_COMMAND_CONSUMER_CONFIG: () => DEFAULT_COMMAND_CONSUMER_CONFIG,
@@ -44,6 +55,7 @@ __export(index_exports, {
44
55
  DEFAULT_ORDERED_STREAM_CONFIG: () => DEFAULT_ORDERED_STREAM_CONFIG,
45
56
  DEFAULT_RPC_TIMEOUT: () => DEFAULT_RPC_TIMEOUT,
46
57
  DEFAULT_SHUTDOWN_TIMEOUT: () => DEFAULT_SHUTDOWN_TIMEOUT,
58
+ DEFAULT_TRACES: () => DEFAULT_TRACES,
47
59
  JETSTREAM_CODEC: () => JETSTREAM_CODEC,
48
60
  JETSTREAM_CONNECTION: () => JETSTREAM_CONNECTION,
49
61
  JETSTREAM_OPTIONS: () => JETSTREAM_OPTIONS,
@@ -55,15 +67,18 @@ __export(index_exports, {
55
67
  JetstreamRecord: () => JetstreamRecord,
56
68
  JetstreamRecordBuilder: () => JetstreamRecordBuilder,
57
69
  JetstreamStrategy: () => JetstreamStrategy,
70
+ JetstreamTrace: () => JetstreamTrace,
58
71
  JsonCodec: () => JsonCodec,
59
72
  MIN_METADATA_TTL: () => MIN_METADATA_TTL,
60
73
  MessageKind: () => MessageKind,
61
74
  MsgpackCodec: () => MsgpackCodec,
62
75
  NatsErrorCode: () => NatsErrorCode,
63
76
  PatternPrefix: () => PatternPrefix,
77
+ PublishKind: () => PublishKind,
64
78
  RESERVED_HEADERS: () => RESERVED_HEADERS,
65
79
  RpcContext: () => RpcContext,
66
80
  StreamKind: () => StreamKind,
81
+ TRACER_NAME: () => TRACER_NAME,
67
82
  TransportEvent: () => TransportEvent,
68
83
  buildBroadcastSubject: () => buildBroadcastSubject,
69
84
  buildSubject: () => buildSubject,
@@ -80,13 +95,14 @@ __export(index_exports, {
80
95
  module.exports = __toCommonJS(index_exports);
81
96
 
82
97
  // src/jetstream.module.ts
83
- var import_common14 = require("@nestjs/common");
98
+ var import_common21 = require("@nestjs/common");
84
99
 
85
100
  // src/client/jetstream.client.ts
86
- var import_common = require("@nestjs/common");
101
+ var import_common5 = require("@nestjs/common");
87
102
  var import_microservices = require("@nestjs/microservices");
88
- var import_transport_node = require("@nats-io/transport-node");
103
+ var import_api8 = require("@opentelemetry/api");
89
104
  var import_nuid = require("@nats-io/nuid");
105
+ var import_transport_node = require("@nats-io/transport-node");
90
106
 
91
107
  // src/interfaces/hooks.interface.ts
92
108
  var MessageKind = /* @__PURE__ */ ((MessageKind2) => {
@@ -104,6 +120,10 @@ var TransportEvent = /* @__PURE__ */ ((TransportEvent2) => {
104
120
  TransportEvent2["ShutdownStart"] = "shutdownStart";
105
121
  TransportEvent2["ShutdownComplete"] = "shutdownComplete";
106
122
  TransportEvent2["DeadLetter"] = "deadLetter";
123
+ TransportEvent2["ConsumerRecovered"] = "consumerRecovered";
124
+ TransportEvent2["HandlerCompleted"] = "handlerCompleted";
125
+ TransportEvent2["Published"] = "published";
126
+ TransportEvent2["RpcCompleted"] = "rpcCompleted";
107
127
  return TransportEvent2;
108
128
  })(TransportEvent || {});
109
129
 
@@ -276,6 +296,907 @@ var PatternPrefix = /* @__PURE__ */ ((PatternPrefix2) => {
276
296
  var isJetStreamRpcMode = (rpc) => rpc?.mode === "jetstream";
277
297
  var isCoreRpcMode = (rpc) => !rpc || rpc.mode === "core";
278
298
 
299
+ // src/otel/constants.ts
300
+ var TRACER_NAME = "@horizon-republic/nestjs-jetstream";
301
+
302
+ // src/otel/attribute-keys.ts
303
+ var ATTR_MESSAGING_SYSTEM = "messaging.system";
304
+ var ATTR_MESSAGING_DESTINATION_NAME = "messaging.destination.name";
305
+ var ATTR_MESSAGING_DESTINATION_TEMPLATE = "messaging.destination.template";
306
+ var ATTR_MESSAGING_CLIENT_ID = "messaging.client.id";
307
+ var ATTR_MESSAGING_OPERATION_NAME = "messaging.operation.name";
308
+ var ATTR_MESSAGING_OPERATION_TYPE = "messaging.operation.type";
309
+ var ATTR_MESSAGING_MESSAGE_BODY_SIZE = "messaging.message.body.size";
310
+ var ATTR_MESSAGING_MESSAGE_ID = "messaging.message.id";
311
+ var ATTR_MESSAGING_MESSAGE_CONVERSATION_ID = "messaging.message.conversation_id";
312
+ var ATTR_MESSAGING_CONSUMER_GROUP_NAME = "messaging.consumer.group.name";
313
+ var ATTR_MESSAGING_HEADER_PREFIX = "messaging.header.";
314
+ var ATTR_MESSAGING_NATS_STREAM_NAME = "messaging.nats.stream.name";
315
+ var ATTR_MESSAGING_NATS_STREAM_SEQUENCE = "messaging.nats.message.stream_sequence";
316
+ var ATTR_MESSAGING_NATS_CONSUMER_SEQUENCE = "messaging.nats.message.consumer_sequence";
317
+ var ATTR_MESSAGING_NATS_DELIVERY_COUNT = "messaging.nats.message.delivery_count";
318
+ var ATTR_MESSAGING_NATS_BODY = "messaging.nats.message.body";
319
+ var ATTR_MESSAGING_NATS_BODY_TRUNCATED = "messaging.nats.message.body.truncated";
320
+ var ATTR_SERVER_ADDRESS = "server.address";
321
+ var ATTR_SERVER_PORT = "server.port";
322
+ var ATTR_JETSTREAM_SERVICE_NAME = "jetstream.service.name";
323
+ var ATTR_JETSTREAM_KIND = "jetstream.kind";
324
+ var ATTR_JETSTREAM_RPC_REPLY_HAS_ERROR = "jetstream.rpc.reply.has_error";
325
+ var ATTR_JETSTREAM_RPC_REPLY_ERROR_CODE = "jetstream.rpc.reply.error.code";
326
+ var ATTR_JETSTREAM_PROVISIONING_ENTITY = "jetstream.provisioning.entity";
327
+ var ATTR_JETSTREAM_PROVISIONING_ACTION = "jetstream.provisioning.action";
328
+ var ATTR_JETSTREAM_PROVISIONING_NAME = "jetstream.provisioning.name";
329
+ var ATTR_JETSTREAM_SELF_HEALING_REASON = "jetstream.self_healing.reason";
330
+ var ATTR_JETSTREAM_MIGRATION_REASON = "jetstream.migration.reason";
331
+ var ATTR_JETSTREAM_DEAD_LETTER_REASON = "jetstream.dead_letter.reason";
332
+ var ATTR_JETSTREAM_SCHEDULE_TARGET = "jetstream.schedule.target";
333
+ var ATTR_NATS_CONNECTION_SERVER = "nats.connection.server";
334
+ var NATS_MSG_ID_HEADER = "Nats-Msg-Id";
335
+ var HOOK_PUBLISH = "publishHook";
336
+ var HOOK_CONSUME = "consumeHook";
337
+ var HOOK_RESPONSE = "responseHook";
338
+ var SPAN_NAME_PUBLISH = "publish";
339
+ var SPAN_NAME_PROCESS = "process";
340
+ var SPAN_NAME_SEND = "send";
341
+ var SPAN_NAME_DEAD_LETTER = "dead_letter";
342
+ var SPAN_NAME_NATS_CONNECTION = "nats.connection";
343
+ var SPAN_NAME_JETSTREAM_SHUTDOWN = "jetstream.shutdown";
344
+ var SPAN_NAME_JETSTREAM_SELF_HEALING = "jetstream.self_healing";
345
+ var SPAN_NAME_JETSTREAM_MIGRATION = "jetstream.migration";
346
+ var SPAN_NAME_JETSTREAM_PROVISIONING_PREFIX = "jetstream.provisioning.";
347
+ var EVENT_CONNECTION_DISCONNECTED = "connection.disconnected";
348
+ var EVENT_CONNECTION_RECONNECTED = "connection.reconnected";
349
+ var messagingHeaderAttr = (headerName) => `${ATTR_MESSAGING_HEADER_PREFIX}${headerName.toLowerCase()}`;
350
+
351
+ // src/otel/trace-kinds.ts
352
+ var JetstreamTrace = /* @__PURE__ */ ((JetstreamTrace2) => {
353
+ JetstreamTrace2["Publish"] = "publish";
354
+ JetstreamTrace2["Consume"] = "consume";
355
+ JetstreamTrace2["RpcClientSend"] = "rpc.client.send";
356
+ JetstreamTrace2["DeadLetter"] = "dead_letter";
357
+ JetstreamTrace2["ConnectionLifecycle"] = "connection.lifecycle";
358
+ JetstreamTrace2["SelfHealing"] = "self_healing";
359
+ JetstreamTrace2["Provisioning"] = "provisioning";
360
+ JetstreamTrace2["Migration"] = "migration";
361
+ JetstreamTrace2["Shutdown"] = "shutdown";
362
+ return JetstreamTrace2;
363
+ })(JetstreamTrace || {});
364
+ var DEFAULT_TRACES = [
365
+ "publish" /* Publish */,
366
+ "consume" /* Consume */,
367
+ "rpc.client.send" /* RpcClientSend */,
368
+ "dead_letter" /* DeadLetter */
369
+ ];
370
+
371
+ // src/otel/capture.ts
372
+ var NEGATION_PREFIX = "!";
373
+ var REGEX_SPECIAL_RE = /[.+?^${}()|[\]\\*]/gu;
374
+ var escapeForRegex = (input) => input.replace(REGEX_SPECIAL_RE, "\\$&");
375
+ var globToRegex = (glob) => {
376
+ const escaped = escapeForRegex(glob.toLowerCase()).replace(/\\\*/gu, ".*");
377
+ return new RegExp(`^${escaped}$`, "u");
378
+ };
379
+ var compileHeaderAllowlist = (allowlist) => {
380
+ if (allowlist === true) return () => true;
381
+ if (allowlist === false) return () => false;
382
+ if (allowlist.length === 0) return () => false;
383
+ const includes = [];
384
+ const excludes = [];
385
+ for (const pattern of allowlist) {
386
+ const isExclude = pattern.startsWith(NEGATION_PREFIX);
387
+ const body = isExclude ? pattern.slice(NEGATION_PREFIX.length) : pattern;
388
+ const regex = globToRegex(body);
389
+ if (isExclude) excludes.push(regex);
390
+ else includes.push(regex);
391
+ }
392
+ return (name) => {
393
+ const lower = name.toLowerCase();
394
+ if (!includes.some((re) => re.test(lower))) return false;
395
+ if (excludes.some((re) => re.test(lower))) return false;
396
+ return true;
397
+ };
398
+ };
399
+ var subjectMatcherCache = /* @__PURE__ */ new WeakMap();
400
+ var compileSubjectAllowlist = (allowlist) => {
401
+ if (!allowlist || allowlist.length === 0) return () => true;
402
+ const cached = subjectMatcherCache.get(allowlist);
403
+ if (cached) return cached;
404
+ const regexes = allowlist.map(globToRegex);
405
+ const matcher = (subject) => {
406
+ const lower = subject.toLowerCase();
407
+ return regexes.some((re) => re.test(lower));
408
+ };
409
+ subjectMatcherCache.set(allowlist, matcher);
410
+ return matcher;
411
+ };
412
+ var subjectMatchesAllowlist = (subject, allowlist) => compileSubjectAllowlist(allowlist)(subject);
413
+ var HEADER_DENYLIST = /* @__PURE__ */ new Set([
414
+ "traceparent",
415
+ "tracestate",
416
+ "baggage",
417
+ "sentry-trace",
418
+ "b3",
419
+ "x-b3-traceid",
420
+ "x-b3-spanid",
421
+ "x-b3-parentspanid",
422
+ "x-b3-sampled",
423
+ "x-b3-flags",
424
+ "uber-trace-id",
425
+ "x-correlation-id",
426
+ "x-reply-to",
427
+ "x-error",
428
+ "x-subject",
429
+ "x-caller-name"
430
+ ]);
431
+ var isNatsServerHeader = (lower) => lower.startsWith("nats-");
432
+ var captureMatchingHeaders = (headers2, matcher) => {
433
+ if (!headers2) return {};
434
+ const out = {};
435
+ for (const key of headers2.keys()) {
436
+ const lower = key.toLowerCase();
437
+ if (HEADER_DENYLIST.has(lower)) continue;
438
+ if (isNatsServerHeader(lower)) continue;
439
+ if (!matcher(key)) continue;
440
+ const value = headers2.get(key);
441
+ if (value === "") continue;
442
+ out[messagingHeaderAttr(lower)] = value;
443
+ }
444
+ return out;
445
+ };
446
+ var tryUtf8Decode = (bytes) => {
447
+ try {
448
+ return new TextDecoder("utf-8", { fatal: false }).decode(bytes);
449
+ } catch {
450
+ return Buffer.from(bytes).toString("base64");
451
+ }
452
+ };
453
+ var captureBodyAttribute = (subject, payload, capture) => {
454
+ if (capture === false) return {};
455
+ if (payload.byteLength === 0) return {};
456
+ if (!subjectMatchesAllowlist(subject, capture.subjectAllowlist)) return {};
457
+ const { maxBytes } = capture;
458
+ const truncated = payload.byteLength > maxBytes;
459
+ const slice = truncated ? payload.subarray(0, maxBytes) : payload;
460
+ const decoded = tryUtf8Decode(slice);
461
+ const out = { [ATTR_MESSAGING_NATS_BODY]: decoded };
462
+ if (truncated) out[ATTR_MESSAGING_NATS_BODY_TRUNCATED] = true;
463
+ return out;
464
+ };
465
+
466
+ // src/otel/config.ts
467
+ var PublishKind = /* @__PURE__ */ ((PublishKind2) => {
468
+ PublishKind2["Event"] = "event";
469
+ PublishKind2["RpcRequest"] = "rpc.request";
470
+ PublishKind2["Broadcast"] = "broadcast";
471
+ PublishKind2["Ordered"] = "ordered";
472
+ return PublishKind2;
473
+ })(PublishKind || {});
474
+ var ConsumeKind = /* @__PURE__ */ ((ConsumeKind2) => {
475
+ ConsumeKind2["Event"] = "event";
476
+ ConsumeKind2["Rpc"] = "rpc";
477
+ ConsumeKind2["Broadcast"] = "broadcast";
478
+ ConsumeKind2["Ordered"] = "ordered";
479
+ return ConsumeKind2;
480
+ })(ConsumeKind || {});
481
+ var DEFAULT_CAPTURE_HEADERS = ["x-request-id"];
482
+ var DEFAULT_CAPTURE_BODY_MAX_BYTES = 4096;
483
+ var NESTJS_BARE_ERROR_MESSAGE = "Internal server error";
484
+ var isNestjsBareErrorSentinel = (obj) => obj.status === "error" && obj.message === NESTJS_BARE_ERROR_MESSAGE;
485
+ var defaultErrorClassifier = (err) => {
486
+ if (err === null || typeof err !== "object") return "unexpected";
487
+ if (!(err instanceof Error)) {
488
+ if (isNestjsBareErrorSentinel(err)) return "unexpected";
489
+ return "expected";
490
+ }
491
+ let proto = err;
492
+ while (proto) {
493
+ const name = proto.constructor?.name;
494
+ if (name === "RpcException" || name === "HttpException") return "expected";
495
+ proto = Object.getPrototypeOf(proto);
496
+ }
497
+ return "unexpected";
498
+ };
499
+ var expandTracesOption = (option) => {
500
+ if (option === void 0 || option === "default") return new Set(DEFAULT_TRACES);
501
+ if (option === "all") return new Set(Object.values(JetstreamTrace));
502
+ if (option === "none") return /* @__PURE__ */ new Set();
503
+ return new Set(option);
504
+ };
505
+ var compileHeaderMatcher = (option) => compileHeaderAllowlist(option ?? DEFAULT_CAPTURE_HEADERS);
506
+ var resolveCaptureBody = (option) => {
507
+ if (option === void 0 || option === false) return false;
508
+ if (option === true) return { maxBytes: DEFAULT_CAPTURE_BODY_MAX_BYTES };
509
+ return {
510
+ maxBytes: option.maxBytes ?? DEFAULT_CAPTURE_BODY_MAX_BYTES,
511
+ subjectAllowlist: option.subjectAllowlist
512
+ };
513
+ };
514
+ var resolveOtelOptions = (options = {}) => {
515
+ return {
516
+ enabled: options.enabled ?? true,
517
+ traces: expandTracesOption(options.traces),
518
+ captureHeaders: compileHeaderMatcher(options.captureHeaders),
519
+ captureBody: resolveCaptureBody(options.captureBody),
520
+ publishHook: options.publishHook,
521
+ consumeHook: options.consumeHook,
522
+ responseHook: options.responseHook,
523
+ shouldTracePublish: options.shouldTracePublish,
524
+ shouldTraceConsume: options.shouldTraceConsume,
525
+ errorClassifier: options.errorClassifier ?? defaultErrorClassifier
526
+ };
527
+ };
528
+
529
+ // src/otel/internal-utils.ts
530
+ var import_common = require("@nestjs/common");
531
+ var logger = new import_common.Logger("Jetstream:Otel");
532
+ var safelyInvokeHook = (hookName, hook, ...args) => {
533
+ if (!hook) return;
534
+ const logHookFailure = (err) => {
535
+ const message = err instanceof Error ? err.message : String(err);
536
+ logger.debug(`OTel ${hookName} threw: ${message}`);
537
+ };
538
+ try {
539
+ const result = hook(...args);
540
+ if (result !== null && typeof result === "object" && "then" in result && typeof result.then === "function") {
541
+ result.then(void 0, logHookFailure);
542
+ }
543
+ } catch (err) {
544
+ logHookFailure(err);
545
+ }
546
+ };
547
+ var stripIpv6Brackets = (host) => host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
548
+ var parsePort = (portRaw) => {
549
+ if (!portRaw) return void 0;
550
+ const port = Number.parseInt(portRaw, 10);
551
+ return Number.isInteger(port) ? port : void 0;
552
+ };
553
+ var parseServerAddress = (servers) => {
554
+ const raw = servers[0];
555
+ if (!raw) return null;
556
+ if (raw.includes("://")) {
557
+ try {
558
+ const url = new URL(raw);
559
+ if (url.hostname.length === 0) return null;
560
+ const host2 = stripIpv6Brackets(url.hostname);
561
+ const port2 = parsePort(url.port || void 0);
562
+ return port2 === void 0 ? { host: host2 } : { host: host2, port: port2 };
563
+ } catch {
564
+ return null;
565
+ }
566
+ }
567
+ if (raw.startsWith("[")) {
568
+ const closeIdx = raw.indexOf("]");
569
+ if (closeIdx <= 0) return null;
570
+ const host2 = raw.slice(1, closeIdx);
571
+ const port2 = parsePort(raw.slice(closeIdx + 1).replace(/^:/u, ""));
572
+ return port2 === void 0 ? { host: host2 } : { host: host2, port: port2 };
573
+ }
574
+ const [host, portRaw] = raw.split(":");
575
+ if (!host) return null;
576
+ const port = parsePort(portRaw);
577
+ return port === void 0 ? { host } : { host, port };
578
+ };
579
+ var deriveOtelAttrs = (options) => ({
580
+ otel: resolveOtelOptions(options.otel),
581
+ serviceName: internalName(options.name),
582
+ serverEndpoint: parseServerAddress(options.servers)
583
+ });
584
+
585
+ // src/otel/propagator.ts
586
+ var import_api = require("@opentelemetry/api");
587
+ var injectContext = (ctx, carrier, setter) => {
588
+ import_api.propagation.inject(ctx, carrier, setter);
589
+ };
590
+ var extractContext = (ctx, carrier, getter) => import_api.propagation.extract(ctx, carrier, getter);
591
+
592
+ // src/otel/tracer.ts
593
+ var import_api2 = require("@opentelemetry/api");
594
+ var PACKAGE_VERSION = true ? "2.11.0" : "0.0.0";
595
+ var getTracer = () => import_api2.trace.getTracer(TRACER_NAME, PACKAGE_VERSION);
596
+
597
+ // src/otel/carrier.ts
598
+ var hdrsSetter = {
599
+ set: (headers2, key, value) => {
600
+ headers2.set(key, value);
601
+ }
602
+ };
603
+ var hdrsGetter = {
604
+ keys: (headers2) => headers2 ? headers2.keys() : [],
605
+ get: (headers2, key) => {
606
+ if (!headers2) return void 0;
607
+ const all = typeof headers2.values === "function" ? headers2.values(key) : void 0;
608
+ if (Array.isArray(all)) {
609
+ const nonEmpty = all.filter((value) => value !== "");
610
+ if (nonEmpty.length === 0) return void 0;
611
+ return nonEmpty.join(",");
612
+ }
613
+ const single = headers2.get(key);
614
+ return single === "" ? void 0 : single;
615
+ }
616
+ };
617
+
618
+ // src/otel/attributes.ts
619
+ var MESSAGING_SYSTEM = "nats";
620
+ var baseMessagingAttributes = (ctx) => {
621
+ const attrs = {
622
+ [ATTR_MESSAGING_SYSTEM]: MESSAGING_SYSTEM,
623
+ [ATTR_MESSAGING_DESTINATION_NAME]: ctx.subject,
624
+ [ATTR_MESSAGING_CLIENT_ID]: ctx.serviceName
625
+ };
626
+ if (ctx.serverAddress) attrs[ATTR_SERVER_ADDRESS] = ctx.serverAddress;
627
+ if (ctx.serverPort !== void 0) attrs[ATTR_SERVER_PORT] = ctx.serverPort;
628
+ if (ctx.pattern && ctx.pattern !== ctx.subject) {
629
+ attrs[ATTR_MESSAGING_DESTINATION_TEMPLATE] = ctx.pattern;
630
+ }
631
+ return attrs;
632
+ };
633
+ var jetstreamKindForPublish = (kind) => kind === "rpc.request" /* RpcRequest */ ? "rpc" /* Rpc */ : kind;
634
+ var buildPublishAttributes = (ctx) => {
635
+ const attrs = {
636
+ ...baseMessagingAttributes(ctx),
637
+ [ATTR_MESSAGING_OPERATION_NAME]: SPAN_NAME_PUBLISH,
638
+ [ATTR_MESSAGING_OPERATION_TYPE]: SPAN_NAME_SEND,
639
+ [ATTR_MESSAGING_MESSAGE_BODY_SIZE]: ctx.payloadBytes,
640
+ [ATTR_JETSTREAM_KIND]: jetstreamKindForPublish(ctx.kind)
641
+ };
642
+ if (ctx.messageId) attrs[ATTR_MESSAGING_MESSAGE_ID] = ctx.messageId;
643
+ if (ctx.correlationId) attrs[ATTR_MESSAGING_MESSAGE_CONVERSATION_ID] = ctx.correlationId;
644
+ return attrs;
645
+ };
646
+ var buildConsumeAttributes = (ctx) => {
647
+ const { msg, info, kind, payloadBytes } = ctx;
648
+ const attrs = {
649
+ ...baseMessagingAttributes(ctx),
650
+ [ATTR_MESSAGING_OPERATION_NAME]: SPAN_NAME_PROCESS,
651
+ [ATTR_MESSAGING_OPERATION_TYPE]: SPAN_NAME_PROCESS,
652
+ [ATTR_MESSAGING_MESSAGE_BODY_SIZE]: payloadBytes,
653
+ [ATTR_JETSTREAM_KIND]: kind
654
+ };
655
+ if (info) {
656
+ attrs[ATTR_MESSAGING_NATS_STREAM_NAME] = info.stream;
657
+ attrs[ATTR_MESSAGING_CONSUMER_GROUP_NAME] = info.consumer;
658
+ attrs[ATTR_MESSAGING_NATS_STREAM_SEQUENCE] = info.streamSequence;
659
+ attrs[ATTR_MESSAGING_NATS_CONSUMER_SEQUENCE] = info.deliverySequence;
660
+ attrs[ATTR_MESSAGING_NATS_DELIVERY_COUNT] = info.deliveryCount;
661
+ }
662
+ const messageId = msg.headers?.get(NATS_MSG_ID_HEADER);
663
+ if (messageId) attrs[ATTR_MESSAGING_MESSAGE_ID] = messageId;
664
+ return attrs;
665
+ };
666
+ var buildRpcClientAttributes = (ctx) => {
667
+ const attrs = {
668
+ ...baseMessagingAttributes(ctx),
669
+ [ATTR_MESSAGING_OPERATION_NAME]: SPAN_NAME_SEND,
670
+ [ATTR_MESSAGING_OPERATION_TYPE]: SPAN_NAME_SEND,
671
+ [ATTR_MESSAGING_MESSAGE_BODY_SIZE]: ctx.payloadBytes,
672
+ [ATTR_JETSTREAM_KIND]: "rpc" /* Rpc */
673
+ };
674
+ if (ctx.correlationId) attrs[ATTR_MESSAGING_MESSAGE_CONVERSATION_ID] = ctx.correlationId;
675
+ if (ctx.messageId) attrs[ATTR_MESSAGING_MESSAGE_ID] = ctx.messageId;
676
+ return attrs;
677
+ };
678
+ var buildDeadLetterAttributes = (ctx) => {
679
+ const attrs = {
680
+ ...baseMessagingAttributes(ctx),
681
+ [ATTR_MESSAGING_OPERATION_NAME]: SPAN_NAME_DEAD_LETTER,
682
+ [ATTR_MESSAGING_OPERATION_TYPE]: SPAN_NAME_PROCESS,
683
+ [ATTR_MESSAGING_NATS_DELIVERY_COUNT]: ctx.finalDeliveryCount
684
+ };
685
+ if (ctx.reason) attrs[ATTR_JETSTREAM_DEAD_LETTER_REASON] = ctx.reason;
686
+ return attrs;
687
+ };
688
+ var extractFromRpcException = (record) => {
689
+ const getError = record.getError;
690
+ if (typeof getError !== "function") return void 0;
691
+ return codeFromPayload(getError.call(record));
692
+ };
693
+ var extractFromHttpException = (record) => {
694
+ const getStatus = record.getStatus;
695
+ if (typeof getStatus !== "function") return void 0;
696
+ const status = getStatus.call(record);
697
+ return typeof status === "number" && Number.isFinite(status) ? `HTTP_${status}` : void 0;
698
+ };
699
+ var extractFromOwnCode = (record) => {
700
+ const code = record.code ?? record.errorCode;
701
+ return typeof code === "string" && isStableErrorCode(code) ? code : void 0;
702
+ };
703
+ var extractExpectedErrorCode = (err) => {
704
+ if (err === null || typeof err !== "object") return void 0;
705
+ const record = err;
706
+ if (hasAncestorNamed(err, "RpcException")) {
707
+ const fromPayload = extractFromRpcException(record);
708
+ if (fromPayload !== void 0) return fromPayload;
709
+ }
710
+ if (hasAncestorNamed(err, "HttpException")) {
711
+ const fromStatus = extractFromHttpException(record);
712
+ if (fromStatus !== void 0) return fromStatus;
713
+ }
714
+ return extractFromOwnCode(record);
715
+ };
716
+ var hasAncestorNamed = (err, name) => {
717
+ let proto = Object.getPrototypeOf(err);
718
+ while (proto) {
719
+ const ctorName = proto.constructor?.name;
720
+ if (ctorName === name) return true;
721
+ proto = Object.getPrototypeOf(proto);
722
+ }
723
+ return false;
724
+ };
725
+ var STABLE_ERROR_CODE_RE = /^[A-Z][A-Z0-9_]*$/u;
726
+ var isStableErrorCode = (value) => STABLE_ERROR_CODE_RE.test(value);
727
+ var codeFromPayload = (payload) => {
728
+ if (payload === null || payload === void 0) return void 0;
729
+ if (typeof payload === "string") return isStableErrorCode(payload) ? payload : void 0;
730
+ if (typeof payload === "object") {
731
+ const code = payload.code;
732
+ if (typeof code === "string" && isStableErrorCode(code)) return code;
733
+ }
734
+ return void 0;
735
+ };
736
+ var buildExpectedErrorAttributes = (err) => {
737
+ const attrs = {
738
+ [ATTR_JETSTREAM_RPC_REPLY_HAS_ERROR]: true
739
+ };
740
+ const code = extractExpectedErrorCode(err);
741
+ if (code !== void 0) attrs[ATTR_JETSTREAM_RPC_REPLY_ERROR_CODE] = code;
742
+ return attrs;
743
+ };
744
+ var applyExpectedErrorAttributes = (span, err) => {
745
+ span.setAttributes(buildExpectedErrorAttributes(err));
746
+ };
747
+
748
+ // src/otel/spans/publish.ts
749
+ var import_common2 = require("@nestjs/common");
750
+ var import_api3 = require("@opentelemetry/api");
751
+ var logger2 = new import_common2.Logger("Jetstream:Otel");
752
+ var shouldTracePublishSafe = (predicate, subject, record) => {
753
+ if (!predicate) return true;
754
+ try {
755
+ return predicate(subject, record);
756
+ } catch (err) {
757
+ const message = err instanceof Error ? err.message : String(err);
758
+ logger2.debug(`OTel shouldTracePublish threw: ${message}`);
759
+ return true;
760
+ }
761
+ };
762
+ var withPublishSpan = async (ctx, config, fn) => {
763
+ if (!config.enabled) return fn();
764
+ const shouldCreateSpan = config.traces.has("publish" /* Publish */) && shouldTracePublishSafe(config.shouldTracePublish, ctx.subject, ctx.record);
765
+ if (!shouldCreateSpan) {
766
+ injectContext(import_api3.context.active(), ctx.headers, hdrsSetter);
767
+ return fn();
768
+ }
769
+ const tracer = getTracer();
770
+ const span = tracer.startSpan(`${SPAN_NAME_PUBLISH} ${ctx.subject}`, {
771
+ kind: import_api3.SpanKind.PRODUCER,
772
+ attributes: {
773
+ ...buildPublishAttributes({
774
+ subject: ctx.subject,
775
+ pattern: ctx.pattern,
776
+ serviceName: ctx.serviceName,
777
+ serverAddress: ctx.endpoint?.host,
778
+ serverPort: ctx.endpoint?.port,
779
+ kind: ctx.kind,
780
+ payloadBytes: ctx.payloadBytes,
781
+ messageId: ctx.messageId,
782
+ correlationId: ctx.correlationId
783
+ }),
784
+ ...ctx.scheduleTarget ? { [ATTR_JETSTREAM_SCHEDULE_TARGET]: ctx.scheduleTarget } : {},
785
+ ...captureMatchingHeaders(ctx.headers, config.captureHeaders),
786
+ ...captureBodyAttribute(ctx.subject, ctx.payload, config.captureBody)
787
+ }
788
+ });
789
+ const ctxWithSpan = import_api3.trace.setSpan(import_api3.context.active(), span);
790
+ injectContext(ctxWithSpan, ctx.headers, hdrsSetter);
791
+ const start = Date.now();
792
+ const invokeResponseHook = (error) => {
793
+ import_api3.context.with(ctxWithSpan, () => {
794
+ safelyInvokeHook(HOOK_RESPONSE, config.responseHook, span, {
795
+ subject: ctx.subject,
796
+ durationMs: Date.now() - start,
797
+ error
798
+ });
799
+ });
800
+ };
801
+ try {
802
+ const result = await import_api3.context.with(ctxWithSpan, async () => {
803
+ safelyInvokeHook(HOOK_PUBLISH, config.publishHook, span, {
804
+ subject: ctx.subject,
805
+ record: ctx.record,
806
+ kind: ctx.kind
807
+ });
808
+ return fn();
809
+ });
810
+ span.setStatus({ code: import_api3.SpanStatusCode.OK });
811
+ invokeResponseHook();
812
+ return result;
813
+ } catch (err) {
814
+ const error = err instanceof Error ? err : new Error(String(err));
815
+ span.recordException(error);
816
+ span.setStatus({ code: import_api3.SpanStatusCode.ERROR, message: error.message });
817
+ invokeResponseHook(error);
818
+ throw err;
819
+ } finally {
820
+ span.end();
821
+ }
822
+ };
823
+
824
+ // src/otel/spans/consume.ts
825
+ var import_api4 = require("@opentelemetry/api");
826
+ var isPromiseLike = (value) => typeof value === "object" && value !== null && typeof value.then === "function";
827
+ var applyExpectedError = (span, err) => {
828
+ span.setStatus({ code: import_api4.SpanStatusCode.OK });
829
+ applyExpectedErrorAttributes(span, err);
830
+ };
831
+ var applyUnexpectedError = (span, err) => {
832
+ const error = err instanceof Error ? err : new Error(String(err));
833
+ span.recordException(error);
834
+ span.setStatus({ code: import_api4.SpanStatusCode.ERROR, message: error.message });
835
+ };
836
+ var withConsumeSpan = (ctx, config, fn, options = {}) => {
837
+ if (!config.enabled) return fn();
838
+ const parentCtx = extractContext(import_api4.ROOT_CONTEXT, ctx.msg.headers, hdrsGetter);
839
+ const shouldCreateSpan = config.traces.has("consume" /* Consume */) && (config.shouldTraceConsume?.(ctx.subject, ctx.msg) ?? true);
840
+ if (!shouldCreateSpan) {
841
+ return import_api4.context.with(parentCtx, fn);
842
+ }
843
+ const tracer = getTracer();
844
+ const span = tracer.startSpan(
845
+ `${SPAN_NAME_PROCESS} ${ctx.subject}`,
846
+ {
847
+ kind: import_api4.SpanKind.CONSUMER,
848
+ attributes: {
849
+ ...buildConsumeAttributes({
850
+ subject: ctx.subject,
851
+ pattern: ctx.pattern,
852
+ msg: ctx.msg,
853
+ info: ctx.info,
854
+ kind: ctx.kind,
855
+ payloadBytes: ctx.payloadBytes,
856
+ serviceName: ctx.serviceName,
857
+ serverAddress: ctx.endpoint?.host,
858
+ serverPort: ctx.endpoint?.port
859
+ }),
860
+ ...captureMatchingHeaders(ctx.msg.headers, config.captureHeaders),
861
+ ...captureBodyAttribute(ctx.subject, ctx.msg.data, config.captureBody)
862
+ }
863
+ },
864
+ parentCtx
865
+ );
866
+ const ctxWithSpan = import_api4.trace.setSpan(parentCtx, span);
867
+ const start = Date.now();
868
+ let finalized = false;
869
+ const { signal, timeoutLabel = "handler.timeout" } = options;
870
+ let detachAbort = null;
871
+ const invokeResponseHook = (durationMs, error) => {
872
+ import_api4.context.with(ctxWithSpan, () => {
873
+ safelyInvokeHook(HOOK_RESPONSE, config.responseHook, span, {
874
+ subject: ctx.subject,
875
+ durationMs,
876
+ error
877
+ });
878
+ });
879
+ };
880
+ const finishOk2 = () => {
881
+ if (finalized) return;
882
+ finalized = true;
883
+ detachAbort?.();
884
+ span.setStatus({ code: import_api4.SpanStatusCode.OK });
885
+ invokeResponseHook(Date.now() - start);
886
+ span.end();
887
+ };
888
+ const finishError2 = (err) => {
889
+ if (finalized) return;
890
+ finalized = true;
891
+ detachAbort?.();
892
+ let classification = "unexpected";
893
+ try {
894
+ classification = config.errorClassifier(err);
895
+ } catch {
896
+ }
897
+ if (classification === "expected") {
898
+ applyExpectedError(span, err);
899
+ } else {
900
+ applyUnexpectedError(span, err);
901
+ }
902
+ invokeResponseHook(Date.now() - start, err instanceof Error ? err : new Error(String(err)));
903
+ span.end();
904
+ };
905
+ const onAbort = () => {
906
+ if (finalized) return;
907
+ finalized = true;
908
+ const error = new Error(timeoutLabel);
909
+ span.addEvent(timeoutLabel);
910
+ span.recordException(error);
911
+ span.setStatus({ code: import_api4.SpanStatusCode.ERROR, message: timeoutLabel });
912
+ invokeResponseHook(Date.now() - start, error);
913
+ span.end();
914
+ };
915
+ if (signal) {
916
+ if (signal.aborted) {
917
+ onAbort();
918
+ } else {
919
+ signal.addEventListener("abort", onAbort, { once: true });
920
+ detachAbort = () => {
921
+ signal.removeEventListener("abort", onAbort);
922
+ };
923
+ }
924
+ }
925
+ let result;
926
+ try {
927
+ result = import_api4.context.with(ctxWithSpan, () => {
928
+ safelyInvokeHook(HOOK_CONSUME, config.consumeHook, span, {
929
+ subject: ctx.subject,
930
+ msg: ctx.msg,
931
+ handlerMetadata: ctx.handlerMetadata,
932
+ kind: ctx.kind
933
+ });
934
+ return fn();
935
+ });
936
+ } catch (err) {
937
+ finishError2(err);
938
+ throw err;
939
+ }
940
+ if (isPromiseLike(result)) {
941
+ return Promise.resolve(result).then(
942
+ (value) => {
943
+ finishOk2();
944
+ return value;
945
+ },
946
+ (err) => {
947
+ finishError2(err);
948
+ throw err;
949
+ }
950
+ );
951
+ }
952
+ finishOk2();
953
+ return result;
954
+ };
955
+
956
+ // src/otel/spans/rpc-client.ts
957
+ var import_common3 = require("@nestjs/common");
958
+ var import_api5 = require("@opentelemetry/api");
959
+ var logger3 = new import_common3.Logger("Jetstream:Otel");
960
+ var RPC_TIMEOUT_MESSAGE = "rpc.timeout";
961
+ var beginRpcClientSpan = (ctx, config) => {
962
+ if (!config.enabled) {
963
+ return {
964
+ activeContext: import_api5.context.active(),
965
+ finish: () => void 0
966
+ };
967
+ }
968
+ if (!config.traces.has("rpc.client.send" /* RpcClientSend */)) {
969
+ injectContext(import_api5.context.active(), ctx.headers, hdrsSetter);
970
+ return {
971
+ activeContext: import_api5.context.active(),
972
+ finish: () => void 0
973
+ };
974
+ }
975
+ const tracer = getTracer();
976
+ const span = tracer.startSpan(`${SPAN_NAME_SEND} ${ctx.subject}`, {
977
+ kind: import_api5.SpanKind.CLIENT,
978
+ attributes: {
979
+ ...buildRpcClientAttributes({
980
+ subject: ctx.subject,
981
+ pattern: ctx.pattern,
982
+ correlationId: ctx.correlationId,
983
+ payloadBytes: ctx.payloadBytes,
984
+ messageId: ctx.messageId,
985
+ serviceName: ctx.serviceName,
986
+ serverAddress: ctx.endpoint?.host,
987
+ serverPort: ctx.endpoint?.port
988
+ }),
989
+ ...captureMatchingHeaders(ctx.headers, config.captureHeaders),
990
+ ...captureBodyAttribute(ctx.subject, ctx.payload, config.captureBody)
991
+ }
992
+ });
993
+ const ctxWithSpan = import_api5.trace.setSpan(import_api5.context.active(), span);
994
+ injectContext(ctxWithSpan, ctx.headers, hdrsSetter);
995
+ const start = Date.now();
996
+ let finalized = false;
997
+ const finish = (outcome) => {
998
+ if (finalized) return;
999
+ finalized = true;
1000
+ let reply;
1001
+ let error;
1002
+ switch (outcome.kind) {
1003
+ case "ok" /* Ok */:
1004
+ reply = outcome.reply;
1005
+ span.setStatus({ code: import_api5.SpanStatusCode.OK });
1006
+ break;
1007
+ case "reply-error" /* ReplyError */:
1008
+ reply = outcome.replyPayload;
1009
+ applyExpectedErrorAttributes(span, outcome.replyPayload);
1010
+ span.setStatus({ code: import_api5.SpanStatusCode.OK });
1011
+ break;
1012
+ case "timeout" /* Timeout */:
1013
+ error = new Error(RPC_TIMEOUT_MESSAGE);
1014
+ span.addEvent(RPC_TIMEOUT_MESSAGE);
1015
+ span.setStatus({ code: import_api5.SpanStatusCode.ERROR, message: RPC_TIMEOUT_MESSAGE });
1016
+ break;
1017
+ case "error" /* Error */:
1018
+ error = outcome.error;
1019
+ span.recordException(outcome.error);
1020
+ span.setStatus({ code: import_api5.SpanStatusCode.ERROR, message: outcome.error.message });
1021
+ break;
1022
+ default: {
1023
+ const unknownOutcome = outcome;
1024
+ logger3.error(`Unhandled RPC outcome: ${String(unknownOutcome.kind)}`);
1025
+ span.setStatus({ code: import_api5.SpanStatusCode.ERROR, message: "unknown outcome" });
1026
+ }
1027
+ }
1028
+ import_api5.context.with(ctxWithSpan, () => {
1029
+ safelyInvokeHook(HOOK_RESPONSE, config.responseHook, span, {
1030
+ subject: ctx.subject,
1031
+ durationMs: Date.now() - start,
1032
+ reply,
1033
+ error
1034
+ });
1035
+ });
1036
+ span.end();
1037
+ };
1038
+ return { activeContext: ctxWithSpan, finish };
1039
+ };
1040
+
1041
+ // src/otel/spans/dead-letter.ts
1042
+ var import_api6 = require("@opentelemetry/api");
1043
+ var withDeadLetterSpan = async (ctx, config, fn) => {
1044
+ if (!config.enabled || !config.traces.has("dead_letter" /* DeadLetter */)) {
1045
+ return fn();
1046
+ }
1047
+ const parentCtx = extractContext(import_api6.ROOT_CONTEXT, ctx.msg.headers, hdrsGetter);
1048
+ const tracer = getTracer();
1049
+ const span = tracer.startSpan(
1050
+ `${SPAN_NAME_DEAD_LETTER} ${ctx.msg.subject}`,
1051
+ {
1052
+ kind: import_api6.SpanKind.INTERNAL,
1053
+ attributes: buildDeadLetterAttributes({
1054
+ subject: ctx.msg.subject,
1055
+ pattern: ctx.pattern,
1056
+ serviceName: ctx.serviceName,
1057
+ serverAddress: ctx.endpoint?.host,
1058
+ serverPort: ctx.endpoint?.port,
1059
+ finalDeliveryCount: ctx.finalDeliveryCount,
1060
+ reason: ctx.reason
1061
+ })
1062
+ },
1063
+ parentCtx
1064
+ );
1065
+ const ctxWithSpan = import_api6.trace.setSpan(parentCtx, span);
1066
+ const start = Date.now();
1067
+ const invokeResponseHook = (error) => {
1068
+ import_api6.context.with(ctxWithSpan, () => {
1069
+ safelyInvokeHook(HOOK_RESPONSE, config.responseHook, span, {
1070
+ subject: ctx.msg.subject,
1071
+ durationMs: Date.now() - start,
1072
+ error
1073
+ });
1074
+ });
1075
+ };
1076
+ try {
1077
+ const result = await import_api6.context.with(ctxWithSpan, fn);
1078
+ span.setStatus({ code: import_api6.SpanStatusCode.OK });
1079
+ invokeResponseHook();
1080
+ return result;
1081
+ } catch (err) {
1082
+ const error = err instanceof Error ? err : new Error(String(err));
1083
+ span.recordException(error);
1084
+ span.setStatus({ code: import_api6.SpanStatusCode.ERROR, message: error.message });
1085
+ invokeResponseHook(error);
1086
+ throw err;
1087
+ } finally {
1088
+ span.end();
1089
+ }
1090
+ };
1091
+
1092
+ // src/otel/spans/infrastructure.ts
1093
+ var import_common4 = require("@nestjs/common");
1094
+ var import_api7 = require("@opentelemetry/api");
1095
+ var logger4 = new import_common4.Logger("Jetstream:Otel");
1096
+ var startInfraSpan = (config, traceKind, name, ctx, extraAttributes = {}) => {
1097
+ if (!config.enabled || !config.traces.has(traceKind)) return null;
1098
+ const tracer = getTracer();
1099
+ const attributes = {
1100
+ [ATTR_JETSTREAM_SERVICE_NAME]: ctx.serviceName
1101
+ };
1102
+ if (ctx.endpoint?.host) attributes[ATTR_SERVER_ADDRESS] = ctx.endpoint.host;
1103
+ if (ctx.endpoint?.port !== void 0) attributes[ATTR_SERVER_PORT] = ctx.endpoint.port;
1104
+ for (const [key, value] of Object.entries(extraAttributes)) {
1105
+ if (value !== void 0) attributes[key] = value;
1106
+ }
1107
+ return tracer.startSpan(name, { kind: import_api7.SpanKind.INTERNAL, attributes });
1108
+ };
1109
+ var finishOk = (span) => {
1110
+ span.setStatus({ code: import_api7.SpanStatusCode.OK });
1111
+ span.end();
1112
+ };
1113
+ var finishError = (span, err) => {
1114
+ const error = err instanceof Error ? err : new Error(String(err));
1115
+ span.recordException(error);
1116
+ span.setStatus({ code: import_api7.SpanStatusCode.ERROR, message: error.message });
1117
+ span.end();
1118
+ };
1119
+ var wrapInfra = async (config, traceKind, name, ctx, attributes, op) => {
1120
+ const span = startInfraSpan(config, traceKind, name, ctx, attributes);
1121
+ if (!span) return op();
1122
+ const ctxWithSpan = import_api7.trace.setSpan(import_api7.context.active(), span);
1123
+ try {
1124
+ const result = await import_api7.context.with(ctxWithSpan, op);
1125
+ finishOk(span);
1126
+ return result;
1127
+ } catch (err) {
1128
+ finishError(span, err);
1129
+ throw err;
1130
+ }
1131
+ };
1132
+ var beginConnectionLifecycleSpan = (config, ctx) => {
1133
+ const span = startInfraSpan(
1134
+ config,
1135
+ "connection.lifecycle" /* ConnectionLifecycle */,
1136
+ SPAN_NAME_NATS_CONNECTION,
1137
+ ctx,
1138
+ { [ATTR_NATS_CONNECTION_SERVER]: ctx.server }
1139
+ );
1140
+ if (!span) {
1141
+ return {
1142
+ recordEvent: () => void 0,
1143
+ finish: () => void 0
1144
+ };
1145
+ }
1146
+ let finalized = false;
1147
+ return {
1148
+ recordEvent: (name, attributes) => {
1149
+ if (finalized) {
1150
+ logger4.debug(`recordEvent('${name}') called after connection span finished`);
1151
+ return;
1152
+ }
1153
+ span.addEvent(name, attributes);
1154
+ },
1155
+ finish: (err) => {
1156
+ if (finalized) return;
1157
+ finalized = true;
1158
+ if (err === void 0) finishOk(span);
1159
+ else finishError(span, err);
1160
+ }
1161
+ };
1162
+ };
1163
+ var withSelfHealingSpan = (config, ctx, op) => wrapInfra(
1164
+ config,
1165
+ "self_healing" /* SelfHealing */,
1166
+ SPAN_NAME_JETSTREAM_SELF_HEALING,
1167
+ ctx,
1168
+ {
1169
+ [ATTR_MESSAGING_NATS_STREAM_NAME]: ctx.stream,
1170
+ [ATTR_MESSAGING_CONSUMER_GROUP_NAME]: ctx.consumer,
1171
+ [ATTR_JETSTREAM_SELF_HEALING_REASON]: ctx.reason
1172
+ },
1173
+ op
1174
+ );
1175
+ var withProvisioningSpan = (config, ctx, op) => wrapInfra(
1176
+ config,
1177
+ "provisioning" /* Provisioning */,
1178
+ `${SPAN_NAME_JETSTREAM_PROVISIONING_PREFIX}${ctx.entity}`,
1179
+ ctx,
1180
+ {
1181
+ [ATTR_JETSTREAM_PROVISIONING_ENTITY]: ctx.entity,
1182
+ [ATTR_JETSTREAM_PROVISIONING_ACTION]: ctx.action,
1183
+ [ATTR_JETSTREAM_PROVISIONING_NAME]: ctx.name
1184
+ },
1185
+ op
1186
+ );
1187
+ var withMigrationSpan = (config, ctx, op) => wrapInfra(
1188
+ config,
1189
+ "migration" /* Migration */,
1190
+ SPAN_NAME_JETSTREAM_MIGRATION,
1191
+ ctx,
1192
+ {
1193
+ [ATTR_MESSAGING_NATS_STREAM_NAME]: ctx.stream,
1194
+ [ATTR_JETSTREAM_MIGRATION_REASON]: ctx.reason
1195
+ },
1196
+ op
1197
+ );
1198
+ var withShutdownSpan = (config, ctx, op) => wrapInfra(config, "shutdown" /* Shutdown */, SPAN_NAME_JETSTREAM_SHUTDOWN, ctx, {}, op);
1199
+
279
1200
  // src/client/jetstream.record.ts
280
1201
  var JetstreamRecord = class {
281
1202
  constructor(data, headers2, timeout, messageId, schedule, ttl) {
@@ -428,9 +1349,14 @@ var JetstreamRecordBuilder = class {
428
1349
  this.ttlDuration
429
1350
  );
430
1351
  }
431
- /** Validate that a header key is not reserved. */
1352
+ /**
1353
+ * Validate that a header key is not reserved. NATS treats header names
1354
+ * case-insensitively, so the check is against the lowercase form to keep
1355
+ * `'X-Correlation-ID'`, `'x-correlation-id'`, and any other casing in
1356
+ * lockstep. `RESERVED_HEADERS` is defined as an all-lowercase set.
1357
+ */
432
1358
  validateHeaderKey(key) {
433
- if (RESERVED_HEADERS.has(key)) {
1359
+ if (RESERVED_HEADERS.has(key.toLowerCase())) {
434
1360
  throw new Error(
435
1361
  `Header "${key}" is reserved by the JetStream transport and cannot be set manually. Reserved headers: ${[...RESERVED_HEADERS].join(", ")}`
436
1362
  );
@@ -451,6 +1377,22 @@ var nanosToGoDuration = (nanos) => {
451
1377
 
452
1378
  // src/client/jetstream.client.ts
453
1379
  var BROADCAST_SUBJECT_PREFIX = "broadcast.";
1380
+ var detectEventKind = (pattern) => {
1381
+ if (pattern.startsWith("broadcast:" /* Broadcast */)) return "broadcast" /* Broadcast */;
1382
+ if (pattern.startsWith("ordered:" /* Ordered */)) return "ordered" /* Ordered */;
1383
+ return "event" /* Event */;
1384
+ };
1385
+ var declaredEventPattern = (pattern) => {
1386
+ if (pattern.startsWith("broadcast:" /* Broadcast */))
1387
+ return pattern.slice("broadcast:" /* Broadcast */.length);
1388
+ if (pattern.startsWith("ordered:" /* Ordered */)) return pattern.slice("ordered:" /* Ordered */.length);
1389
+ return pattern;
1390
+ };
1391
+ var eventStreamKind = (kind) => {
1392
+ if (kind === "broadcast" /* Broadcast */) return "broadcast" /* Broadcast */;
1393
+ if (kind === "ordered" /* Ordered */) return "ordered" /* Ordered */;
1394
+ return "ev" /* Event */;
1395
+ };
454
1396
  var JetstreamClient = class extends import_microservices.ClientProxy {
455
1397
  constructor(rootOptions, targetServiceName, connection, codec, eventBus) {
456
1398
  super();
@@ -466,8 +1408,11 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
466
1408
  this.orderedSubjectPrefix = `${targetInternal}.${"ordered" /* Ordered */}.`;
467
1409
  this.isCoreMode = isCoreRpcMode(this.rootOptions.rpc);
468
1410
  this.defaultRpcTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? this.rootOptions.rpc?.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT : this.rootOptions.rpc?.timeout ?? DEFAULT_RPC_TIMEOUT;
1411
+ const derived = deriveOtelAttrs(this.rootOptions);
1412
+ this.otel = derived.otel;
1413
+ this.serverEndpoint = derived.serverEndpoint;
469
1414
  }
470
- logger = new import_common.Logger("Jetstream:Client");
1415
+ logger = new import_common5.Logger("Jetstream:Client");
471
1416
  /** Target service name this client sends messages to. */
472
1417
  targetName;
473
1418
  /** Pre-cached caller name derived from rootOptions.name, computed once in constructor. */
@@ -487,6 +1432,10 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
487
1432
  */
488
1433
  isCoreMode;
489
1434
  defaultRpcTimeout;
1435
+ /** Resolved OpenTelemetry configuration, computed once in the constructor. */
1436
+ otel;
1437
+ /** Server endpoint parts used for `server.address` / `server.port` span attributes. */
1438
+ serverEndpoint;
490
1439
  /** Shared inbox for JetStream-mode RPC responses. */
491
1440
  inbox = null;
492
1441
  inboxSubscription = null;
@@ -554,32 +1503,64 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
554
1503
  if (!this.readyForPublish) await this.connect();
555
1504
  const { data, hdrs, messageId, schedule, ttl } = this.extractRecordData(packet.data);
556
1505
  const eventSubject = this.buildEventSubject(packet.pattern);
1506
+ const publishSubject = schedule ? this.buildScheduleSubject(eventSubject) : eventSubject;
557
1507
  const msgHeaders = this.buildHeaders(hdrs, { subject: eventSubject });
558
- if (schedule) {
559
- const scheduleSubject = this.buildScheduleSubject(eventSubject);
560
- const ack = await this.connection.getJetStreamClient().publish(scheduleSubject, this.codec.encode(data), {
561
- headers: msgHeaders,
562
- msgID: messageId ?? import_nuid.nuid.next(),
563
- ttl,
564
- schedule: {
565
- specification: schedule.at,
566
- target: eventSubject
1508
+ const encoded = this.codec.encode(data);
1509
+ const effectiveMsgId = messageId ?? import_nuid.nuid.next();
1510
+ const record = packet.data instanceof JetstreamRecord ? packet.data : new JetstreamRecord(data, /* @__PURE__ */ new Map());
1511
+ const publishKind = detectEventKind(packet.pattern);
1512
+ const declaredPattern = declaredEventPattern(packet.pattern);
1513
+ const streamKind = eventStreamKind(publishKind);
1514
+ const startedAt = performance.now();
1515
+ try {
1516
+ await withPublishSpan(
1517
+ {
1518
+ subject: publishSubject,
1519
+ pattern: packet.pattern,
1520
+ record,
1521
+ kind: publishKind,
1522
+ payloadBytes: encoded.length,
1523
+ payload: encoded,
1524
+ messageId: effectiveMsgId,
1525
+ headers: msgHeaders,
1526
+ serviceName: this.callerName,
1527
+ endpoint: this.serverEndpoint,
1528
+ scheduleTarget: schedule ? eventSubject : void 0
1529
+ },
1530
+ this.otel,
1531
+ async () => {
1532
+ const warnIfDuplicate = (kindLabel, ack2) => {
1533
+ if (ack2.duplicate) {
1534
+ this.logger.warn(
1535
+ `Duplicate ${kindLabel} publish detected: ${publishSubject} (seq: ${ack2.seq})`
1536
+ );
1537
+ }
1538
+ };
1539
+ if (schedule) {
1540
+ const ack2 = await this.connection.getJetStreamClient().publish(publishSubject, encoded, {
1541
+ headers: msgHeaders,
1542
+ msgID: effectiveMsgId,
1543
+ ttl,
1544
+ schedule: {
1545
+ specification: schedule.at,
1546
+ target: eventSubject
1547
+ }
1548
+ });
1549
+ warnIfDuplicate("scheduled", ack2);
1550
+ return;
1551
+ }
1552
+ const ack = await this.connection.getJetStreamClient().publish(publishSubject, encoded, {
1553
+ headers: msgHeaders,
1554
+ msgID: effectiveMsgId,
1555
+ ttl
1556
+ });
1557
+ warnIfDuplicate("event", ack);
567
1558
  }
568
- });
569
- if (ack.duplicate) {
570
- this.logger.warn(
571
- `Duplicate scheduled publish detected: ${scheduleSubject} (seq: ${ack.seq})`
572
- );
573
- }
574
- } else {
575
- const ack = await this.connection.getJetStreamClient().publish(eventSubject, this.codec.encode(data), {
576
- headers: msgHeaders,
577
- msgID: messageId ?? import_nuid.nuid.next(),
578
- ttl
579
- });
580
- if (ack.duplicate) {
581
- this.logger.warn(`Duplicate event publish detected: ${eventSubject} (seq: ${ack.seq})`);
582
- }
1559
+ );
1560
+ this.reportPublished(declaredPattern, streamKind, startedAt, "success");
1561
+ } catch (err) {
1562
+ this.reportPublished(declaredPattern, streamKind, startedAt, "error");
1563
+ throw err;
583
1564
  }
584
1565
  return void 0;
585
1566
  }
@@ -608,14 +1589,17 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
608
1589
  };
609
1590
  let jetStreamCorrelationId = null;
610
1591
  if (this.isCoreMode) {
611
- this.publishCoreRpc(subject, data, hdrs, timeout, callback).catch(onUnhandled);
1592
+ this.publishCoreRpc(subject, data, hdrs, timeout, callback, packet.pattern).catch(
1593
+ onUnhandled
1594
+ );
612
1595
  } else {
613
1596
  jetStreamCorrelationId = import_nuid.nuid.next();
614
1597
  this.publishJetStreamRpc(subject, data, callback, {
615
1598
  headers: hdrs,
616
1599
  timeout,
617
1600
  correlationId: jetStreamCorrelationId,
618
- messageId
1601
+ messageId,
1602
+ declaredPattern: packet.pattern
619
1603
  }).catch(onUnhandled);
620
1604
  }
621
1605
  return () => {
@@ -630,38 +1614,118 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
630
1614
  };
631
1615
  }
632
1616
  /** Core mode: nc.request() with timeout. */
633
- async publishCoreRpc(subject, data, customHeaders, timeout, callback) {
1617
+ async publishCoreRpc(subject, data, customHeaders, timeout, callback, declaredPattern) {
1618
+ const effectiveTimeout = timeout ?? this.defaultRpcTimeout;
1619
+ const hdrs = this.buildHeaders(customHeaders, { subject });
1620
+ const encoded = this.codec.encode(data);
1621
+ const spanHandle = beginRpcClientSpan(
1622
+ {
1623
+ subject,
1624
+ payloadBytes: encoded.length,
1625
+ payload: encoded,
1626
+ headers: hdrs,
1627
+ serviceName: this.callerName,
1628
+ endpoint: this.serverEndpoint
1629
+ },
1630
+ this.otel
1631
+ );
1632
+ const startedAt = performance.now();
634
1633
  try {
635
1634
  const nc = this.readyForPublish ? this.connection.unwrap : await this.connect();
636
- const effectiveTimeout = timeout ?? this.defaultRpcTimeout;
637
- const hdrs = this.buildHeaders(customHeaders, { subject });
638
- const response = await nc.request(subject, this.codec.encode(data), {
639
- timeout: effectiveTimeout,
640
- headers: hdrs
641
- });
1635
+ const response = await import_api8.context.with(
1636
+ spanHandle.activeContext,
1637
+ () => nc.request(subject, encoded, {
1638
+ timeout: effectiveTimeout,
1639
+ headers: hdrs
1640
+ })
1641
+ );
642
1642
  const decoded = this.codec.decode(response.data);
643
1643
  if (response.headers?.get("x-error" /* Error */)) {
1644
+ spanHandle.finish({ kind: "reply-error" /* ReplyError */, replyPayload: decoded });
1645
+ this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "success");
1646
+ this.reportRpcCompleted(declaredPattern, startedAt, "error");
644
1647
  callback({ err: decoded, response: null, isDisposed: true });
645
1648
  } else {
1649
+ spanHandle.finish({ kind: "ok" /* Ok */, reply: decoded });
1650
+ this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "success");
1651
+ this.reportRpcCompleted(declaredPattern, startedAt, "success");
646
1652
  callback({ err: null, response: decoded, isDisposed: true });
647
1653
  }
648
1654
  } catch (err) {
649
1655
  const error = err instanceof Error ? err : new Error("Unknown error");
650
- this.logger.error(`Core RPC error (${subject}):`, err);
651
- this.eventBus.emit("error" /* Error */, error, "client-rpc");
1656
+ if (error instanceof import_transport_node.TimeoutError) {
1657
+ spanHandle.finish({ kind: "timeout" /* Timeout */ });
1658
+ this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, "");
1659
+ this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "success");
1660
+ this.reportRpcCompleted(declaredPattern, startedAt, "timeout");
1661
+ } else {
1662
+ spanHandle.finish({ kind: "error" /* Error */, error });
1663
+ this.eventBus.emit("error" /* Error */, error, "client-rpc");
1664
+ this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "error");
1665
+ this.reportRpcCompleted(declaredPattern, startedAt, "error");
1666
+ }
652
1667
  callback({ err: error, response: null, isDisposed: true });
653
1668
  }
654
1669
  }
655
1670
  /** JetStream mode: publish to stream + wait for inbox response. */
656
1671
  async publishJetStreamRpc(subject, data, callback, options) {
657
- const { headers: customHeaders, correlationId, messageId } = options;
1672
+ const { headers: customHeaders, correlationId, messageId, declaredPattern } = options;
658
1673
  const effectiveTimeout = options.timeout ?? this.defaultRpcTimeout;
659
- this.pendingMessages.set(correlationId, callback);
1674
+ const hdrs = this.buildHeaders(customHeaders, {
1675
+ subject,
1676
+ correlationId,
1677
+ replyTo: this.inbox ?? ""
1678
+ });
1679
+ const encoded = this.codec.encode(data);
1680
+ const spanHandle = beginRpcClientSpan(
1681
+ {
1682
+ subject,
1683
+ correlationId,
1684
+ payloadBytes: encoded.length,
1685
+ payload: encoded,
1686
+ messageId,
1687
+ headers: hdrs,
1688
+ serviceName: this.callerName,
1689
+ endpoint: this.serverEndpoint
1690
+ },
1691
+ this.otel
1692
+ );
1693
+ const startedAt = performance.now();
1694
+ this.pendingMessages.set(correlationId, (packet) => {
1695
+ if (packet.err) {
1696
+ if (packet.err instanceof Error) {
1697
+ spanHandle.finish({ kind: "error" /* Error */, error: packet.err });
1698
+ } else {
1699
+ spanHandle.finish({ kind: "reply-error" /* ReplyError */, replyPayload: packet.err });
1700
+ }
1701
+ this.reportRpcCompleted(declaredPattern, startedAt, "error");
1702
+ } else {
1703
+ spanHandle.finish({ kind: "ok" /* Ok */, reply: packet.response });
1704
+ this.reportRpcCompleted(declaredPattern, startedAt, "success");
1705
+ }
1706
+ callback(packet);
1707
+ });
1708
+ const timeoutId = setTimeout(() => {
1709
+ if (!this.pendingMessages.has(correlationId)) return;
1710
+ this.pendingTimeouts.delete(correlationId);
1711
+ this.pendingMessages.delete(correlationId);
1712
+ spanHandle.finish({ kind: "timeout" /* Timeout */ });
1713
+ this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
1714
+ this.reportRpcCompleted(declaredPattern, startedAt, "timeout");
1715
+ callback({ err: new Error(RPC_TIMEOUT_MESSAGE), response: null, isDisposed: true });
1716
+ }, effectiveTimeout);
1717
+ this.pendingTimeouts.set(correlationId, timeoutId);
660
1718
  try {
661
1719
  if (!this.readyForPublish) await this.connect();
662
1720
  if (!this.pendingMessages.has(correlationId)) return;
663
1721
  if (!this.inbox) {
1722
+ clearTimeout(timeoutId);
1723
+ this.pendingTimeouts.delete(correlationId);
664
1724
  this.pendingMessages.delete(correlationId);
1725
+ const inboxError = new Error("Inbox not initialized");
1726
+ spanHandle.finish({ kind: "error" /* Error */, error: inboxError });
1727
+ this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "error");
1728
+ this.reportRpcCompleted(declaredPattern, startedAt, "error");
665
1729
  callback({
666
1730
  err: new Error("Inbox not initialized \u2014 JetStream RPC mode requires a connected inbox"),
667
1731
  response: null,
@@ -669,24 +1733,14 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
669
1733
  });
670
1734
  return;
671
1735
  }
672
- const timeoutId = setTimeout(() => {
673
- if (!this.pendingMessages.has(correlationId)) return;
674
- this.pendingTimeouts.delete(correlationId);
675
- this.pendingMessages.delete(correlationId);
676
- this.logger.error(`JetStream RPC timeout (${effectiveTimeout}ms): ${subject}`);
677
- this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
678
- callback({ err: new Error("RPC timeout"), response: null, isDisposed: true });
679
- }, effectiveTimeout);
680
- this.pendingTimeouts.set(correlationId, timeoutId);
681
- const hdrs = this.buildHeaders(customHeaders, {
682
- subject,
683
- correlationId,
684
- replyTo: this.inbox
685
- });
686
- await this.connection.getJetStreamClient().publish(subject, this.codec.encode(data), {
687
- headers: hdrs,
688
- msgID: messageId ?? import_nuid.nuid.next()
689
- });
1736
+ await import_api8.context.with(
1737
+ spanHandle.activeContext,
1738
+ () => this.connection.getJetStreamClient().publish(subject, encoded, {
1739
+ headers: hdrs,
1740
+ msgID: messageId ?? import_nuid.nuid.next()
1741
+ })
1742
+ );
1743
+ this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "success");
690
1744
  } catch (err) {
691
1745
  const existingTimeout = this.pendingTimeouts.get(correlationId);
692
1746
  if (existingTimeout) {
@@ -696,10 +1750,34 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
696
1750
  if (!this.pendingMessages.has(correlationId)) return;
697
1751
  this.pendingMessages.delete(correlationId);
698
1752
  const error = err instanceof Error ? err : new Error("Unknown error");
699
- this.logger.error(`JetStream RPC publish error (${subject}):`, err);
1753
+ spanHandle.finish({ kind: "error" /* Error */, error });
1754
+ this.eventBus.emit("error" /* Error */, error, `jetstream-rpc-publish:${subject}`);
1755
+ this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "error");
1756
+ this.reportRpcCompleted(declaredPattern, startedAt, "error");
700
1757
  callback({ err: error, response: null, isDisposed: true });
701
1758
  }
702
1759
  }
1760
+ // hasHook is per-emit so late subscribers (JetstreamMetricsService during
1761
+ // OnApplicationBootstrap) still receive events.
1762
+ reportPublished(declaredPattern, kind, startedAt, status) {
1763
+ if (!this.eventBus.hasHook("published" /* Published */)) return;
1764
+ this.eventBus.emit(
1765
+ "published" /* Published */,
1766
+ declaredPattern,
1767
+ kind,
1768
+ performance.now() - startedAt,
1769
+ status
1770
+ );
1771
+ }
1772
+ reportRpcCompleted(declaredPattern, startedAt, status) {
1773
+ if (!this.eventBus.hasHook("rpcCompleted" /* RpcCompleted */)) return;
1774
+ this.eventBus.emit(
1775
+ "rpcCompleted" /* RpcCompleted */,
1776
+ declaredPattern,
1777
+ performance.now() - startedAt,
1778
+ status
1779
+ );
1780
+ }
703
1781
  /** Fail-fast all pending JetStream RPC callbacks on connection loss. */
704
1782
  handleDisconnect() {
705
1783
  this.rejectPendingRpcs(new Error("Connection lost"));
@@ -879,9 +1957,9 @@ var MsgpackCodec = class {
879
1957
  };
880
1958
 
881
1959
  // src/connection/connection.provider.ts
882
- var import_common2 = require("@nestjs/common");
1960
+ var import_common6 = require("@nestjs/common");
883
1961
  var import_transport_node2 = require("@nats-io/transport-node");
884
- var import_jetstream7 = require("@nats-io/jetstream");
1962
+ var import_jetstream8 = require("@nats-io/jetstream");
885
1963
  var import_rxjs = require("rxjs");
886
1964
  var DEFAULT_OPTIONS = {
887
1965
  maxReconnectAttempts: -1,
@@ -891,6 +1969,10 @@ var ConnectionProvider = class {
891
1969
  constructor(options, eventBus) {
892
1970
  this.options = options;
893
1971
  this.eventBus = eventBus;
1972
+ const derived = deriveOtelAttrs(options);
1973
+ this.otel = derived.otel;
1974
+ this.otelServiceName = derived.serviceName;
1975
+ this.otelEndpoint = derived.serverEndpoint;
894
1976
  this.nc$ = (0, import_rxjs.defer)(() => this.getConnection()).pipe(
895
1977
  (0, import_rxjs.shareReplay)({ bufferSize: 1, refCount: false })
896
1978
  );
@@ -903,12 +1985,16 @@ var ConnectionProvider = class {
903
1985
  nc$;
904
1986
  /** Live stream of connection status events (no replay). */
905
1987
  status$;
906
- logger = new import_common2.Logger("Jetstream:Connection");
1988
+ logger = new import_common6.Logger("Jetstream:Connection");
907
1989
  connection = null;
908
1990
  connectionPromise = null;
909
1991
  jsClient = null;
910
1992
  jsmInstance = null;
911
1993
  jsmPromise = null;
1994
+ otel;
1995
+ otelServiceName;
1996
+ otelEndpoint;
1997
+ lifecycleSpan = null;
912
1998
  /**
913
1999
  * Establish NATS connection. Idempotent — returns cached connection on subsequent calls.
914
2000
  *
@@ -951,7 +2037,7 @@ var ConnectionProvider = class {
951
2037
  if (!this.connection || this.connection.isClosed()) {
952
2038
  throw new Error("Not connected \u2014 call getConnection() before getJetStreamClient()");
953
2039
  }
954
- this.jsClient ??= (0, import_jetstream7.jetstream)(this.connection);
2040
+ this.jsClient ??= (0, import_jetstream8.jetstream)(this.connection);
955
2041
  return this.jsClient;
956
2042
  }
957
2043
  /** Direct access to the raw NATS connection, or `null` if not yet connected. */
@@ -970,218 +2056,942 @@ var ConnectionProvider = class {
970
2056
  } catch {
971
2057
  }
972
2058
  }
973
- if (!this.connection || this.connection.isClosed()) return;
2059
+ if (!this.connection || this.connection.isClosed()) return;
2060
+ try {
2061
+ await withShutdownSpan(
2062
+ this.otel,
2063
+ { serviceName: this.otelServiceName, endpoint: this.otelEndpoint },
2064
+ async () => {
2065
+ try {
2066
+ await this.connection?.drain();
2067
+ await this.connection?.closed();
2068
+ } catch {
2069
+ try {
2070
+ await this.connection?.close();
2071
+ } catch {
2072
+ }
2073
+ }
2074
+ }
2075
+ );
2076
+ } finally {
2077
+ this.lifecycleSpan?.finish();
2078
+ this.lifecycleSpan = null;
2079
+ this.connection = null;
2080
+ this.connectionPromise = null;
2081
+ this.jsClient = null;
2082
+ this.jsmInstance = null;
2083
+ this.jsmPromise = null;
2084
+ }
2085
+ }
2086
+ async initJetStreamManager() {
2087
+ try {
2088
+ const nc = await this.getConnection();
2089
+ this.jsmInstance = await (0, import_jetstream8.jetstreamManager)(nc);
2090
+ this.logger.log("JetStream manager initialized");
2091
+ return this.jsmInstance;
2092
+ } finally {
2093
+ this.jsmPromise = null;
2094
+ }
2095
+ }
2096
+ /** Internal: establish the physical connection with reconnect monitoring. */
2097
+ async establish() {
2098
+ try {
2099
+ const nc = await (0, import_transport_node2.connect)({
2100
+ ...DEFAULT_OPTIONS,
2101
+ // Default the NATS connection name to the OTel-derived service name so
2102
+ // `nats server info` lines up with span attributes, but let user-supplied
2103
+ // `connectionOptions.name` win when set.
2104
+ name: this.otelServiceName,
2105
+ ...this.options.connectionOptions,
2106
+ servers: this.options.servers
2107
+ });
2108
+ this.connection = nc;
2109
+ this.logger.log(`NATS connection established: ${nc.getServer()}`);
2110
+ this.eventBus.emit("connect" /* Connect */, nc.getServer());
2111
+ this.lifecycleSpan?.finish();
2112
+ this.lifecycleSpan = beginConnectionLifecycleSpan(this.otel, {
2113
+ serviceName: this.otelServiceName,
2114
+ endpoint: this.otelEndpoint,
2115
+ server: nc.getServer()
2116
+ });
2117
+ this.monitorStatus(nc);
2118
+ return nc;
2119
+ } catch (err) {
2120
+ if (err instanceof Error && err.message.includes("REFUSED")) {
2121
+ throw new Error(`NATS connection refused: ${this.options.servers.join(", ")}`);
2122
+ }
2123
+ throw err;
2124
+ }
2125
+ }
2126
+ /** Handle a single `nc.status()` event, emitting hooks and span events. */
2127
+ handleStatusEvent(status, nc) {
2128
+ switch (status.type) {
2129
+ case "disconnect":
2130
+ this.eventBus.emit("disconnect" /* Disconnect */);
2131
+ this.lifecycleSpan?.recordEvent(EVENT_CONNECTION_DISCONNECTED);
2132
+ break;
2133
+ case "reconnect":
2134
+ this.jsClient = null;
2135
+ this.jsmInstance = null;
2136
+ this.jsmPromise = null;
2137
+ this.eventBus.emit("reconnect" /* Reconnect */, nc.getServer());
2138
+ this.lifecycleSpan?.recordEvent(EVENT_CONNECTION_RECONNECTED, {
2139
+ [ATTR_NATS_CONNECTION_SERVER]: nc.getServer()
2140
+ });
2141
+ break;
2142
+ case "error":
2143
+ this.eventBus.emit(
2144
+ "error" /* Error */,
2145
+ status.error,
2146
+ "connection"
2147
+ );
2148
+ break;
2149
+ case "update":
2150
+ case "ldm":
2151
+ case "reconnecting":
2152
+ case "ping":
2153
+ case "staleConnection":
2154
+ case "forceReconnect":
2155
+ case "slowConsumer":
2156
+ case "close":
2157
+ break;
2158
+ default: {
2159
+ const _exhaustive = status;
2160
+ const unknown = _exhaustive.type ?? "unknown";
2161
+ this.logger.warn(`Unhandled NATS status event: ${unknown}`);
2162
+ }
2163
+ }
2164
+ }
2165
+ /** Subscribe to connection status events and emit hooks. */
2166
+ monitorStatus(nc) {
2167
+ void (async () => {
2168
+ try {
2169
+ for await (const status of nc.status()) {
2170
+ this.handleStatusEvent(status, nc);
2171
+ }
2172
+ } finally {
2173
+ this.lifecycleSpan?.finish();
2174
+ this.lifecycleSpan = null;
2175
+ }
2176
+ })().catch((err) => {
2177
+ this.logger.error("Status monitor error", err);
2178
+ });
2179
+ }
2180
+ };
2181
+
2182
+ // src/hooks/event-bus.ts
2183
+ var EventBus = class {
2184
+ hooks;
2185
+ logger;
2186
+ subscribers = /* @__PURE__ */ new Map();
2187
+ constructor(logger5, hooks) {
2188
+ this.logger = logger5;
2189
+ this.hooks = hooks ?? {};
2190
+ }
2191
+ /**
2192
+ * Subscribe to a transport event. Used by built-in observers (e.g. metrics).
2193
+ * Multiple subscribers per event are supported; each is called independently.
2194
+ */
2195
+ subscribe(event, handler) {
2196
+ const list = this.subscribers.get(event) ?? [];
2197
+ list.push(handler);
2198
+ this.subscribers.set(event, list);
2199
+ }
2200
+ /**
2201
+ * Emit a lifecycle event. Dispatches to all internal subscribers and the
2202
+ * registered user hook (if any).
2203
+ */
2204
+ emit(event, ...args) {
2205
+ this.dispatch(event, args);
2206
+ }
2207
+ /**
2208
+ * Hot-path optimized emit for MessageRouted events.
2209
+ * Avoids rest/spread overhead of the generic `emit()`.
2210
+ */
2211
+ emitMessageRouted(subject, kind) {
2212
+ this.dispatch("messageRouted" /* MessageRouted */, [subject, kind]);
2213
+ }
2214
+ /**
2215
+ * Check whether any listener (user hook or internal subscriber) is registered
2216
+ * for the given event. Used by routing hot path to elide the emit call when
2217
+ * no one is listening.
2218
+ */
2219
+ hasHook(event) {
2220
+ return this.hooks[event] !== void 0 || (this.subscribers.get(event)?.length ?? 0) > 0;
2221
+ }
2222
+ dispatch(event, args) {
2223
+ const subs = this.subscribers.get(event);
2224
+ if (subs?.length) {
2225
+ for (const sub of [...subs]) {
2226
+ this.callHook(event, sub, ...args);
2227
+ }
2228
+ }
2229
+ const hook = this.hooks[event];
2230
+ if (hook) {
2231
+ this.callHook(event, hook, ...args);
2232
+ }
2233
+ }
2234
+ callHook(event, hook, ...args) {
2235
+ try {
2236
+ const result = hook(...args);
2237
+ if (result && typeof result.catch === "function") {
2238
+ result.catch((err) => {
2239
+ this.logger.error(
2240
+ `Async hook "${event}" rejected: ${err instanceof Error ? err.message : err}`
2241
+ );
2242
+ });
2243
+ }
2244
+ } catch (err) {
2245
+ this.logger.error(
2246
+ `Hook "${event}" threw an error: ${err instanceof Error ? err.message : err}`
2247
+ );
2248
+ }
2249
+ }
2250
+ };
2251
+
2252
+ // src/health/jetstream.health-indicator.ts
2253
+ var import_common7 = require("@nestjs/common");
2254
+ var JetstreamHealthIndicator = class {
2255
+ constructor(connection) {
2256
+ this.connection = connection;
2257
+ }
2258
+ logger = new import_common7.Logger("Jetstream:Health");
2259
+ /**
2260
+ * Plain health status check.
2261
+ *
2262
+ * Returns the current connection status without throwing.
2263
+ * Use this for custom health endpoints or monitoring integrations.
2264
+ *
2265
+ * @returns Connection status with server URL and RTT latency.
2266
+ */
2267
+ async check() {
2268
+ const nc = this.connection.unwrap;
2269
+ if (!nc || nc.isClosed()) {
2270
+ return { connected: false, server: null, latency: null };
2271
+ }
2272
+ try {
2273
+ const start = performance.now();
2274
+ await nc.rtt();
2275
+ const latency = Math.round(performance.now() - start);
2276
+ return { connected: true, server: nc.getServer(), latency };
2277
+ } catch (err) {
2278
+ this.logger.warn(`Health check failed: ${err instanceof Error ? err.message : err}`);
2279
+ return { connected: false, server: nc.getServer(), latency: null };
2280
+ }
2281
+ }
2282
+ /**
2283
+ * Terminus-compatible health check.
2284
+ *
2285
+ * Returns `{ [key]: { status: 'up', ... } }` on success.
2286
+ * Throws an error with `{ [key]: { status: 'down', ... } }` on failure.
2287
+ *
2288
+ * The thrown error sets `isHealthCheckError: true` and `causes` — the
2289
+ * duck-type contract that Terminus `HealthCheckExecutor` uses to distinguish
2290
+ * health failures from unexpected exceptions. Works with both Terminus v10
2291
+ * (`instanceof HealthCheckError`) and v11+ (`error?.isHealthCheckError`).
2292
+ *
2293
+ * @param key - Health indicator key (default: `'jetstream'`).
2294
+ * @returns Object with status, server, and latency under the given key.
2295
+ * @throws Error with `isHealthCheckError`, `causes`, and `{ [key]: { status: 'down' } }`.
2296
+ */
2297
+ async isHealthy(key = "jetstream") {
2298
+ const status = await this.check();
2299
+ const details = {
2300
+ status: status.connected ? "up" : "down",
2301
+ server: status.server,
2302
+ latency: status.latency
2303
+ };
2304
+ if (!status.connected) {
2305
+ const causes = { [key]: details };
2306
+ throw Object.assign(new Error("Jetstream health check failed"), {
2307
+ causes,
2308
+ isHealthCheckError: true
2309
+ });
2310
+ }
2311
+ return { [key]: details };
2312
+ }
2313
+ };
2314
+ JetstreamHealthIndicator = __decorateClass([
2315
+ (0, import_common7.Injectable)()
2316
+ ], JetstreamHealthIndicator);
2317
+
2318
+ // src/metrics/metrics.module.ts
2319
+ var import_common11 = require("@nestjs/common");
2320
+
2321
+ // src/server/routing/pattern-registry.ts
2322
+ var import_common8 = require("@nestjs/common");
2323
+ var HANDLER_LABELS = {
2324
+ ["broadcast" /* Broadcast */]: "broadcast" /* Broadcast */,
2325
+ ["ordered" /* Ordered */]: "ordered" /* Ordered */,
2326
+ ["ev" /* Event */]: "event" /* Event */,
2327
+ ["cmd" /* Command */]: "rpc" /* Rpc */
2328
+ };
2329
+ var PatternRegistry = class {
2330
+ constructor(options) {
2331
+ this.options = options;
2332
+ }
2333
+ logger = new import_common8.Logger("Jetstream:PatternRegistry");
2334
+ registry = /* @__PURE__ */ new Map();
2335
+ // Cached after registerHandlers() — the registry is immutable from that point
2336
+ cachedPatterns = null;
2337
+ _hasEvents = false;
2338
+ _hasCommands = false;
2339
+ _hasBroadcasts = false;
2340
+ _hasOrdered = false;
2341
+ _hasMetadata = false;
2342
+ /**
2343
+ * Register all handlers from the NestJS strategy.
2344
+ *
2345
+ * @param handlers Map of pattern -> MessageHandler from `Server.getHandlers()`.
2346
+ */
2347
+ registerHandlers(handlers) {
2348
+ const serviceName = this.options.name;
2349
+ for (const [pattern, handler] of handlers) {
2350
+ const extras = handler.extras;
2351
+ const isEvent = handler.isEventHandler ?? false;
2352
+ const isBroadcast = !!extras?.broadcast;
2353
+ const isOrdered = !!extras?.ordered;
2354
+ const meta = extras?.meta;
2355
+ if (isBroadcast && isOrdered) {
2356
+ throw new Error(
2357
+ `Handler "${pattern}" cannot be both broadcast and ordered. Use one or the other.`
2358
+ );
2359
+ }
2360
+ let kind;
2361
+ if (isBroadcast) kind = "broadcast" /* Broadcast */;
2362
+ else if (isOrdered) kind = "ordered" /* Ordered */;
2363
+ else if (isEvent) kind = "ev" /* Event */;
2364
+ else kind = "cmd" /* Command */;
2365
+ const fullSubject = kind === "broadcast" /* Broadcast */ ? buildBroadcastSubject(pattern) : buildSubject(serviceName, kind, pattern);
2366
+ this.registry.set(fullSubject, {
2367
+ handler,
2368
+ pattern,
2369
+ isEvent: isEvent && !isOrdered,
2370
+ isBroadcast,
2371
+ isOrdered,
2372
+ meta
2373
+ });
2374
+ this.logger.debug(`Registered ${HANDLER_LABELS[kind]}: ${pattern} -> ${fullSubject}`);
2375
+ }
2376
+ this.cachedPatterns = this.buildPatternsByKind();
2377
+ this._hasEvents = this.cachedPatterns.events.length > 0;
2378
+ this._hasCommands = this.cachedPatterns.commands.length > 0;
2379
+ this._hasBroadcasts = this.cachedPatterns.broadcasts.length > 0;
2380
+ this._hasOrdered = this.cachedPatterns.ordered.length > 0;
2381
+ this._hasMetadata = [...this.registry.values()].some((entry) => entry.meta !== void 0);
2382
+ this.logSummary();
2383
+ }
2384
+ /** Find handler for a full NATS subject. */
2385
+ getHandler(subject) {
2386
+ return this.registry.get(subject)?.handler ?? null;
2387
+ }
2388
+ /**
2389
+ * Resolve the declared pattern and {@link StreamKind} for a full NATS subject.
2390
+ *
2391
+ * Returns `null` when the subject is not registered. The declared pattern is
2392
+ * the value the user passed to `@EventPattern`/`@MessagePattern` — stable and
2393
+ * bounded, suitable for use as a Prometheus label without cardinality risk.
2394
+ */
2395
+ resolveDeclared(subject) {
2396
+ const entry = this.registry.get(subject);
2397
+ if (!entry) return null;
2398
+ return { pattern: entry.pattern, kind: this.resolveStreamKind(entry) };
2399
+ }
2400
+ /** Get all registered broadcast patterns (for consumer filter_subject setup). */
2401
+ getBroadcastPatterns() {
2402
+ return this.getPatternsByKind().broadcasts.map((p) => buildBroadcastSubject(p));
2403
+ }
2404
+ hasBroadcastHandlers() {
2405
+ return this._hasBroadcasts;
2406
+ }
2407
+ hasRpcHandlers() {
2408
+ return this._hasCommands;
2409
+ }
2410
+ hasEventHandlers() {
2411
+ return this._hasEvents;
2412
+ }
2413
+ hasOrderedHandlers() {
2414
+ return this._hasOrdered;
2415
+ }
2416
+ /** Get fully-qualified NATS subjects for ordered handlers. */
2417
+ getOrderedSubjects() {
2418
+ return this.getPatternsByKind().ordered.map(
2419
+ (p) => buildSubject(this.options.name, "ordered" /* Ordered */, p)
2420
+ );
2421
+ }
2422
+ /** Check if any registered handler has metadata. */
2423
+ hasMetadata() {
2424
+ return this._hasMetadata;
2425
+ }
2426
+ /**
2427
+ * Get handler metadata entries for KV publishing.
2428
+ *
2429
+ * Returns a map of KV key -> metadata object for all handlers that have `meta`.
2430
+ * Key format: `{serviceName}.{kind}.{pattern}`.
2431
+ */
2432
+ getMetadataEntries() {
2433
+ const entries = /* @__PURE__ */ new Map();
2434
+ for (const entry of this.registry.values()) {
2435
+ if (!entry.meta) continue;
2436
+ const kind = this.resolveStreamKind(entry);
2437
+ const key = metadataKey(this.options.name, kind, entry.pattern);
2438
+ entries.set(key, entry.meta);
2439
+ }
2440
+ return entries;
2441
+ }
2442
+ /** Get patterns grouped by kind (cached after registration). */
2443
+ getPatternsByKind() {
2444
+ const patterns = this.cachedPatterns ?? this.buildPatternsByKind();
2445
+ return {
2446
+ events: [...patterns.events],
2447
+ commands: [...patterns.commands],
2448
+ broadcasts: [...patterns.broadcasts],
2449
+ ordered: [...patterns.ordered]
2450
+ };
2451
+ }
2452
+ /** Normalize a full NATS subject back to the user-facing pattern. */
2453
+ normalizeSubject(subject) {
2454
+ const name = internalName(this.options.name);
2455
+ const prefixes = [
2456
+ `${name}.${"cmd" /* Command */}.`,
2457
+ `${name}.${"ev" /* Event */}.`,
2458
+ `${name}.${"ordered" /* Ordered */}.`,
2459
+ `${"broadcast" /* Broadcast */}.`
2460
+ ];
2461
+ for (const prefix of prefixes) {
2462
+ if (subject.startsWith(prefix)) {
2463
+ return subject.slice(prefix.length);
2464
+ }
2465
+ }
2466
+ return subject;
2467
+ }
2468
+ buildPatternsByKind() {
2469
+ const events = [];
2470
+ const commands = [];
2471
+ const broadcasts = [];
2472
+ const ordered = [];
2473
+ for (const entry of this.registry.values()) {
2474
+ if (entry.isBroadcast) broadcasts.push(entry.pattern);
2475
+ else if (entry.isOrdered) ordered.push(entry.pattern);
2476
+ else if (entry.isEvent) events.push(entry.pattern);
2477
+ else commands.push(entry.pattern);
2478
+ }
2479
+ return { events, commands, broadcasts, ordered };
2480
+ }
2481
+ resolveStreamKind(entry) {
2482
+ if (entry.isBroadcast) return "broadcast" /* Broadcast */;
2483
+ if (entry.isOrdered) return "ordered" /* Ordered */;
2484
+ if (entry.isEvent) return "ev" /* Event */;
2485
+ return "cmd" /* Command */;
2486
+ }
2487
+ logSummary() {
2488
+ const { events, commands, broadcasts, ordered } = this.getPatternsByKind();
2489
+ const parts = [
2490
+ `${commands.length} RPC`,
2491
+ `${events.length} events`,
2492
+ `${broadcasts.length} broadcasts`
2493
+ ];
2494
+ if (ordered.length > 0) {
2495
+ parts.push(`${ordered.length} ordered`);
2496
+ }
2497
+ this.logger.log(`Registered handlers: ${parts.join(", ")}`);
2498
+ }
2499
+ };
2500
+
2501
+ // src/metrics/metrics.constants.ts
2502
+ var JETSTREAM_METRICS_CONFIG = /* @__PURE__ */ Symbol("JETSTREAM_METRICS_CONFIG");
2503
+ var JETSTREAM_METRICS_REGISTRY = /* @__PURE__ */ Symbol("JETSTREAM_METRICS_REGISTRY");
2504
+ var JETSTREAM_METRICS_PROM_CLIENT = /* @__PURE__ */ Symbol("JETSTREAM_METRICS_PROM_CLIENT");
2505
+ var DEFAULT_METRICS_PREFIX = "jetstream_";
2506
+ var DEFAULT_POLL_INTERVAL_MS = 15e3;
2507
+ var DEFAULT_HISTOGRAM_BUCKETS = {
2508
+ handlerDuration: [1e-3, 5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
2509
+ publishDuration: [1e-3, 5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
2510
+ rpcDuration: [1e-3, 5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
2511
+ };
2512
+ var ERROR_CONTEXT_PREFIXES = [
2513
+ ["connection", "connection"],
2514
+ ["codec", "codec"],
2515
+ ["client-rpc", "publish"],
2516
+ ["jetstream-rpc-publish", "publish"],
2517
+ ["publish", "publish"],
2518
+ ["message-provider", "consume"],
2519
+ ["consume", "consume"],
2520
+ ["core-rpc-handler", "handler"],
2521
+ ["rpc-handler", "handler"],
2522
+ // EventRouter formats contexts as `${StreamKind.*}-handler:...` — the enum
2523
+ // uses short forms (`ev`, `ordered`, `broadcast`) so both surface in the wild.
2524
+ ["ev-handler", "handler"],
2525
+ ["event-handler", "handler"],
2526
+ ["broadcast-handler", "handler"],
2527
+ ["ordered-handler", "handler"],
2528
+ ["handler", "handler"],
2529
+ ["shutdown", "shutdown"]
2530
+ ];
2531
+ var UNMATCHED_SUBJECT_LABEL = "<unmatched>";
2532
+ var STREAM_KIND_LABEL = {
2533
+ ["ev" /* Event */]: "event",
2534
+ ["cmd" /* Command */]: "command",
2535
+ ["broadcast" /* Broadcast */]: "broadcast",
2536
+ ["ordered" /* Ordered */]: "ordered"
2537
+ };
2538
+
2539
+ // src/metrics/metrics.service.ts
2540
+ var import_common10 = require("@nestjs/common");
2541
+
2542
+ // src/metrics/error-context-mapper.ts
2543
+ var mapErrorContext = (context7) => {
2544
+ if (!context7) return "other";
2545
+ for (const [prefix, mapped] of ERROR_CONTEXT_PREFIXES) {
2546
+ if (context7 === prefix || context7.startsWith(`${prefix}:`)) {
2547
+ return mapped;
2548
+ }
2549
+ }
2550
+ return "other";
2551
+ };
2552
+
2553
+ // src/metrics/metrics.factory.ts
2554
+ var createMetrics = (opts) => {
2555
+ const { register, promClient } = opts;
2556
+ const prefix = opts.prefix ?? DEFAULT_METRICS_PREFIX;
2557
+ const buckets = {
2558
+ handlerDuration: opts.buckets?.handlerDuration ?? DEFAULT_HISTOGRAM_BUCKETS.handlerDuration,
2559
+ publishDuration: opts.buckets?.publishDuration ?? DEFAULT_HISTOGRAM_BUCKETS.publishDuration,
2560
+ rpcDuration: opts.buckets?.rpcDuration ?? DEFAULT_HISTOGRAM_BUCKETS.rpcDuration
2561
+ };
2562
+ if (opts.defaultLabels && Object.keys(opts.defaultLabels).length > 0) {
2563
+ register.setDefaultLabels(opts.defaultLabels);
2564
+ }
2565
+ const counter = (name, help, labelNames) => new promClient.Counter({ name: `${prefix}${name}`, help, labelNames, registers: [register] });
2566
+ const histogram = (name, help, labelNames, bucketArr) => new promClient.Histogram({
2567
+ name: `${prefix}${name}`,
2568
+ help,
2569
+ labelNames,
2570
+ buckets: bucketArr,
2571
+ registers: [register]
2572
+ });
2573
+ const gauge = (name, help, labelNames) => new promClient.Gauge({ name: `${prefix}${name}`, help, labelNames, registers: [register] });
2574
+ return {
2575
+ messagesReceivedTotal: counter(
2576
+ "messages_received_total",
2577
+ "Total messages routed to a handler.",
2578
+ ["stream", "subject", "kind"]
2579
+ ),
2580
+ messagesProcessedTotal: counter(
2581
+ "messages_processed_total",
2582
+ "Total messages whose handler completed.",
2583
+ ["stream", "subject", "kind", "status"]
2584
+ ),
2585
+ messagesUnhandledTotal: counter(
2586
+ "messages_unhandled_total",
2587
+ "Messages received but not matching any registered handler.",
2588
+ ["subject"]
2589
+ ),
2590
+ messagesDeadLetterTotal: counter(
2591
+ "messages_dead_letter_total",
2592
+ "Messages routed to dead-letter after exhausting redelivery attempts.",
2593
+ ["stream", "subject"]
2594
+ ),
2595
+ publishTotal: counter(
2596
+ "publish_total",
2597
+ "Total publish/send operations performed by the client.",
2598
+ ["subject", "kind", "status"]
2599
+ ),
2600
+ rpcTimeoutTotal: counter("rpc_timeout_total", "RPC calls that exceeded the timeout deadline.", [
2601
+ "subject"
2602
+ ]),
2603
+ consumerRecoveredTotal: counter(
2604
+ "consumer_recovered_total",
2605
+ "Self-healing recoveries after consume-loop failures.",
2606
+ ["kind"]
2607
+ ),
2608
+ errorsTotal: counter("errors_total", "Transport-level errors emitted on the EventBus.", [
2609
+ "context"
2610
+ ]),
2611
+ handlerDurationSeconds: histogram(
2612
+ "handler_duration_seconds",
2613
+ "Wall-clock duration of handler execution.",
2614
+ ["stream", "subject", "kind", "status"],
2615
+ buckets.handlerDuration
2616
+ ),
2617
+ publishDurationSeconds: histogram(
2618
+ "publish_duration_seconds",
2619
+ "Wall-clock duration of client publish/send operations.",
2620
+ ["subject", "kind", "status"],
2621
+ buckets.publishDuration
2622
+ ),
2623
+ rpcDurationSeconds: histogram(
2624
+ "rpc_duration_seconds",
2625
+ "Wall-clock duration of RPC round-trips from client perspective.",
2626
+ ["subject", "status"],
2627
+ buckets.rpcDuration
2628
+ ),
2629
+ consumerNumPending: gauge(
2630
+ "consumer_num_pending",
2631
+ "Messages not yet delivered to this consumer.",
2632
+ ["stream", "consumer", "kind"]
2633
+ ),
2634
+ consumerNumAckPending: gauge(
2635
+ "consumer_num_ack_pending",
2636
+ "Messages delivered but not yet acked.",
2637
+ ["stream", "consumer", "kind"]
2638
+ ),
2639
+ consumerNumRedelivered: gauge(
2640
+ "consumer_num_redelivered",
2641
+ "Messages currently in redelivery state.",
2642
+ ["stream", "consumer", "kind"]
2643
+ ),
2644
+ consumerNumWaiting: gauge(
2645
+ "consumer_num_waiting",
2646
+ "Pull-request waiting count for this consumer.",
2647
+ ["stream", "consumer", "kind"]
2648
+ ),
2649
+ streamMessages: gauge("stream_messages", "Total messages stored in this stream.", ["stream"]),
2650
+ streamBytes: gauge("stream_bytes", "Total bytes stored in this stream.", ["stream"]),
2651
+ connectionUp: gauge("connection_up", "NATS connection state (1 connected, 0 disconnected).", [
2652
+ "server"
2653
+ ]),
2654
+ metricsPollErrorsTotal: counter(
2655
+ "metrics_poll_errors_total",
2656
+ "Errors encountered while polling JetStreamManager for gauge data.",
2657
+ ["target"]
2658
+ )
2659
+ };
2660
+ };
2661
+
2662
+ // src/metrics/poll-runner.ts
2663
+ var import_common9 = require("@nestjs/common");
2664
+ var PollRunner = class {
2665
+ constructor(opts) {
2666
+ this.opts = opts;
2667
+ }
2668
+ logger = new import_common9.Logger("Jetstream:Metrics:Poll");
2669
+ timer = null;
2670
+ inFlight = null;
2671
+ start() {
2672
+ if (this.timer !== null) return;
2673
+ if (this.opts.intervalMs <= 0) return;
2674
+ if (this.opts.targets.length === 0) return;
2675
+ this.timer = setInterval(() => {
2676
+ if (this.inFlight !== null) {
2677
+ this.logger.warn("Skipping poll tick \u2014 previous cycle still in flight");
2678
+ return;
2679
+ }
2680
+ this.inFlight = this.tick().finally(() => {
2681
+ this.inFlight = null;
2682
+ });
2683
+ }, this.opts.intervalMs);
2684
+ }
2685
+ async stop() {
2686
+ if (this.timer !== null) {
2687
+ clearInterval(this.timer);
2688
+ this.timer = null;
2689
+ }
2690
+ if (this.inFlight !== null) await this.inFlight;
2691
+ }
2692
+ /** @internal Visible for tests. Runs one poll cycle. */
2693
+ async tick() {
2694
+ let jsm;
974
2695
  try {
975
- await this.connection.drain();
976
- await this.connection.closed();
2696
+ jsm = await this.opts.jsmFactory();
977
2697
  } catch {
2698
+ this.recordPollError("jsm.connect");
2699
+ return;
2700
+ }
2701
+ await Promise.all([this.pollConsumers(jsm), this.pollStreams(jsm)]);
2702
+ }
2703
+ async pollConsumers(jsm) {
2704
+ for (const target of this.opts.targets) {
978
2705
  try {
979
- await this.connection.close();
2706
+ const info = await jsm.consumers.info(target.stream, target.consumer);
2707
+ const labels = {
2708
+ stream: target.stream,
2709
+ consumer: target.consumer,
2710
+ kind: STREAM_KIND_LABEL[target.kind]
2711
+ };
2712
+ this.opts.metrics.consumerNumPending.labels(labels).set(info.num_pending);
2713
+ this.opts.metrics.consumerNumAckPending.labels(labels).set(info.num_ack_pending);
2714
+ this.opts.metrics.consumerNumRedelivered.labels(labels).set(info.num_redelivered);
2715
+ this.opts.metrics.consumerNumWaiting.labels(labels).set(info.num_waiting);
980
2716
  } catch {
2717
+ this.recordPollError("consumer.info");
981
2718
  }
982
- } finally {
983
- this.connection = null;
984
- this.connectionPromise = null;
985
- this.jsClient = null;
986
- this.jsmInstance = null;
987
- this.jsmPromise = null;
988
- }
989
- }
990
- async initJetStreamManager() {
991
- try {
992
- const nc = await this.getConnection();
993
- this.jsmInstance = await (0, import_jetstream7.jetstreamManager)(nc);
994
- this.logger.log("JetStream manager initialized");
995
- return this.jsmInstance;
996
- } finally {
997
- this.jsmPromise = null;
998
2719
  }
999
2720
  }
1000
- /** Internal: establish the physical connection with reconnect monitoring. */
1001
- async establish() {
1002
- const name = internalName(this.options.name);
1003
- try {
1004
- const nc = await (0, import_transport_node2.connect)({
1005
- ...DEFAULT_OPTIONS,
1006
- ...this.options.connectionOptions,
1007
- servers: this.options.servers,
1008
- name
1009
- });
1010
- this.connection = nc;
1011
- this.logger.log(`NATS connection established: ${nc.getServer()}`);
1012
- this.eventBus.emit("connect" /* Connect */, nc.getServer());
1013
- this.monitorStatus(nc);
1014
- return nc;
1015
- } catch (err) {
1016
- if (err instanceof Error && err.message.includes("REFUSED")) {
1017
- throw new Error(`NATS connection refused: ${this.options.servers.join(", ")}`);
2721
+ async pollStreams(jsm) {
2722
+ const uniqueStreams = new Set(this.opts.targets.map((t) => t.stream));
2723
+ for (const stream of uniqueStreams) {
2724
+ try {
2725
+ const info = await jsm.streams.info(stream);
2726
+ this.opts.metrics.streamMessages.labels({ stream }).set(info.state.messages);
2727
+ this.opts.metrics.streamBytes.labels({ stream }).set(info.state.bytes);
2728
+ } catch {
2729
+ this.recordPollError("stream.info");
1018
2730
  }
1019
- throw err;
1020
2731
  }
1021
2732
  }
1022
- /** Subscribe to connection status events and emit hooks. */
1023
- monitorStatus(nc) {
1024
- void (async () => {
1025
- for await (const status of nc.status()) {
1026
- switch (status.type) {
1027
- case "disconnect":
1028
- this.eventBus.emit("disconnect" /* Disconnect */);
1029
- break;
1030
- case "reconnect":
1031
- this.jsClient = null;
1032
- this.jsmInstance = null;
1033
- this.jsmPromise = null;
1034
- this.eventBus.emit("reconnect" /* Reconnect */, nc.getServer());
1035
- break;
1036
- case "error":
1037
- this.eventBus.emit(
1038
- "error" /* Error */,
1039
- status.error,
1040
- "connection"
1041
- );
1042
- break;
1043
- case "update":
1044
- case "ldm":
1045
- case "reconnecting":
1046
- case "ping":
1047
- case "staleConnection":
1048
- case "forceReconnect":
1049
- case "slowConsumer":
1050
- case "close":
1051
- break;
1052
- }
1053
- }
1054
- })().catch((err) => {
1055
- this.logger.error("Status monitor error", err);
1056
- });
2733
+ recordPollError(target) {
2734
+ this.opts.metrics.metricsPollErrorsTotal.labels({ target }).inc();
1057
2735
  }
1058
2736
  };
1059
2737
 
1060
- // src/hooks/event-bus.ts
1061
- var EventBus = class {
1062
- hooks;
1063
- logger;
1064
- constructor(logger, hooks) {
1065
- this.logger = logger;
1066
- this.hooks = hooks ?? {};
1067
- }
1068
- /**
1069
- * Emit a lifecycle event. Dispatches to custom hook if registered, otherwise no-op.
1070
- *
1071
- * @param event - The {@link TransportEvent} to emit.
1072
- * @param args - Arguments matching the hook signature for this event.
1073
- */
1074
- emit(event, ...args) {
1075
- const hook = this.hooks[event];
1076
- if (!hook) return;
1077
- this.callHook(event, hook, ...args);
2738
+ // src/metrics/metrics.service.ts
2739
+ var JetstreamMetricsService = class {
2740
+ constructor(eventBus, config, promClient, options, patternRegistry, connection = null) {
2741
+ this.eventBus = eventBus;
2742
+ this.config = config;
2743
+ this.promClient = promClient;
2744
+ this.options = options;
2745
+ this.patternRegistry = patternRegistry;
2746
+ this.connection = connection;
1078
2747
  }
1079
- /**
1080
- * Hot-path optimized emit for MessageRouted events.
1081
- * Avoids rest/spread overhead of the generic `emit()`.
1082
- */
1083
- emitMessageRouted(subject, kind) {
1084
- const hook = this.hooks["messageRouted" /* MessageRouted */];
1085
- if (!hook) return;
1086
- this.callHook(
1087
- "messageRouted" /* MessageRouted */,
1088
- hook,
1089
- subject,
1090
- kind
2748
+ logger = new import_common10.Logger("Jetstream:Metrics");
2749
+ metrics = null;
2750
+ pollRunner = null;
2751
+ activeServers = /* @__PURE__ */ new Set();
2752
+ async onApplicationBootstrap() {
2753
+ if (this.metrics !== null) return;
2754
+ if (!this.config.register) {
2755
+ throw new Error(
2756
+ "JetstreamMetricsService requires a prom-client Registry \u2014 none was resolved by JetstreamMetricsModule."
2757
+ );
2758
+ }
2759
+ this.metrics = createMetrics({
2760
+ register: this.config.register,
2761
+ promClient: this.promClient,
2762
+ prefix: this.config.prefix,
2763
+ defaultLabels: this.config.defaultLabels,
2764
+ buckets: this.config.buckets
2765
+ });
2766
+ this.subscribeToEvents();
2767
+ this.syncInitialConnectionState();
2768
+ this.startPolling();
2769
+ this.logger.log(
2770
+ `Metrics enabled (prefix=${this.config.prefix ?? DEFAULT_METRICS_PREFIX}, poll=${this.getEffectivePollInterval()}ms)`
1091
2771
  );
1092
2772
  }
2773
+ async onModuleDestroy() {
2774
+ await this.pollRunner?.stop();
2775
+ this.pollRunner = null;
2776
+ }
2777
+ /** @internal Visible for tests. `0` disables polling. */
2778
+ getEffectivePollInterval() {
2779
+ return this.config.pollInterval ?? DEFAULT_POLL_INTERVAL_MS;
2780
+ }
1093
2781
  /**
1094
- * Check whether a hook is registered for the given event.
1095
- *
1096
- * Used by the routing hot path to elide the emit call entirely when the
1097
- * transport owner did not register a listener.
2782
+ * NATS connects during early bootstrap, before this service subscribes to
2783
+ * the EventBus — the initial `Connect` emission misses us. Mirror the
2784
+ * current state here so `connection_up` reflects reality the moment metrics
2785
+ * come online; later disconnects/reconnects update it normally.
1098
2786
  */
1099
- hasHook(event) {
1100
- return this.hooks[event] !== void 0;
1101
- }
1102
- callHook(event, hook, ...args) {
1103
- try {
1104
- const result = hook(...args);
1105
- if (result && typeof result.catch === "function") {
1106
- result.catch((err) => {
1107
- this.logger.error(
1108
- `Async hook "${event}" rejected: ${err instanceof Error ? err.message : err}`
1109
- );
1110
- });
1111
- }
1112
- } catch (err) {
1113
- this.logger.error(
1114
- `Hook "${event}" threw an error: ${err instanceof Error ? err.message : err}`
1115
- );
2787
+ syncInitialConnectionState() {
2788
+ const nc = this.connection?.unwrap;
2789
+ if (!nc) return;
2790
+ const server = nc.getServer();
2791
+ this.activeServers.add(server);
2792
+ this.metrics?.connectionUp.labels({ server }).set(1);
2793
+ }
2794
+ /** Skips polling for publisher-only deployments and when no kinds are active. */
2795
+ startPolling() {
2796
+ const interval = this.getEffectivePollInterval();
2797
+ const connection = this.connection;
2798
+ if (interval <= 0 || !this.patternRegistry || !connection || !this.metrics) return;
2799
+ const targets = this.buildPollTargets();
2800
+ if (targets.length === 0) return;
2801
+ this.pollRunner = new PollRunner({
2802
+ intervalMs: interval,
2803
+ jsmFactory: async () => connection.getJetStreamManager(),
2804
+ metrics: this.metrics,
2805
+ targets
2806
+ });
2807
+ this.pollRunner.start();
2808
+ }
2809
+ buildPollTargets() {
2810
+ const registry = this.patternRegistry;
2811
+ if (!registry) return [];
2812
+ const targets = [];
2813
+ if (registry.hasEventHandlers()) {
2814
+ targets.push({
2815
+ kind: "ev" /* Event */,
2816
+ stream: streamName(this.options.name, "ev" /* Event */),
2817
+ consumer: consumerName(this.options.name, "ev" /* Event */)
2818
+ });
2819
+ }
2820
+ if (registry.hasRpcHandlers() && isJetStreamRpcMode(this.options.rpc)) {
2821
+ targets.push({
2822
+ kind: "cmd" /* Command */,
2823
+ stream: streamName(this.options.name, "cmd" /* Command */),
2824
+ consumer: consumerName(this.options.name, "cmd" /* Command */)
2825
+ });
2826
+ }
2827
+ if (registry.hasBroadcastHandlers()) {
2828
+ targets.push({
2829
+ kind: "broadcast" /* Broadcast */,
2830
+ stream: streamName(this.options.name, "broadcast" /* Broadcast */),
2831
+ consumer: consumerName(this.options.name, "broadcast" /* Broadcast */)
2832
+ });
2833
+ }
2834
+ return targets;
2835
+ }
2836
+ subscribeToEvents() {
2837
+ this.eventBus.subscribe("connect" /* Connect */, this.onConnect);
2838
+ this.eventBus.subscribe("disconnect" /* Disconnect */, this.onDisconnect);
2839
+ this.eventBus.subscribe("reconnect" /* Reconnect */, this.onReconnect);
2840
+ this.eventBus.subscribe("error" /* Error */, this.onError);
2841
+ this.eventBus.subscribe("rpcTimeout" /* RpcTimeout */, this.onRpcTimeout);
2842
+ this.eventBus.subscribe("messageRouted" /* MessageRouted */, this.onMessageRouted);
2843
+ this.eventBus.subscribe("deadLetter" /* DeadLetter */, this.onDeadLetter);
2844
+ this.eventBus.subscribe("consumerRecovered" /* ConsumerRecovered */, this.onConsumerRecovered);
2845
+ this.eventBus.subscribe("handlerCompleted" /* HandlerCompleted */, this.onHandlerCompleted);
2846
+ this.eventBus.subscribe("published" /* Published */, this.onPublished);
2847
+ this.eventBus.subscribe("rpcCompleted" /* RpcCompleted */, this.onRpcCompleted);
2848
+ }
2849
+ onConnect = (server) => {
2850
+ this.activeServers.add(server);
2851
+ this.metrics?.connectionUp.labels({ server }).set(1);
2852
+ };
2853
+ onReconnect = (server) => {
2854
+ this.activeServers.add(server);
2855
+ this.metrics?.connectionUp.labels({ server }).set(1);
2856
+ };
2857
+ onDisconnect = () => {
2858
+ for (const server of this.activeServers) {
2859
+ this.metrics?.connectionUp.labels({ server }).set(0);
1116
2860
  }
2861
+ };
2862
+ onError = (_err, context7) => {
2863
+ this.metrics?.errorsTotal.labels({ context: mapErrorContext(context7) }).inc();
2864
+ };
2865
+ onRpcTimeout = (subject, _correlationId) => {
2866
+ const declared = this.resolveDeclared(subject);
2867
+ const subjectLabel = declared?.pattern ?? UNMATCHED_SUBJECT_LABEL;
2868
+ this.metrics?.rpcTimeoutTotal.labels({ subject: subjectLabel }).inc();
2869
+ };
2870
+ // `_kind` collapses broadcast/ordered into MessageKind.Event — we use
2871
+ // declared.kind from PatternRegistry for the precise label instead.
2872
+ onMessageRouted = (subject, _kind) => {
2873
+ if (!this.metrics) return;
2874
+ const declared = this.resolveDeclared(subject);
2875
+ if (!declared) {
2876
+ this.metrics.messagesUnhandledTotal.labels({ subject: UNMATCHED_SUBJECT_LABEL }).inc();
2877
+ return;
2878
+ }
2879
+ this.metrics.messagesReceivedTotal.labels({
2880
+ stream: streamName(this.options.name, declared.kind),
2881
+ subject: declared.pattern,
2882
+ kind: STREAM_KIND_LABEL[declared.kind]
2883
+ }).inc();
2884
+ };
2885
+ onDeadLetter = (info) => {
2886
+ const declared = this.resolveDeclared(info.subject);
2887
+ const subjectLabel = declared?.pattern ?? UNMATCHED_SUBJECT_LABEL;
2888
+ this.metrics?.messagesDeadLetterTotal.labels({ stream: info.stream, subject: subjectLabel }).inc();
2889
+ };
2890
+ onConsumerRecovered = (label, _attempts) => {
2891
+ const kindLabel = STREAM_KIND_LABEL[label] ?? String(label);
2892
+ this.metrics?.consumerRecoveredTotal.labels({ kind: kindLabel }).inc();
2893
+ };
2894
+ onHandlerCompleted = (pattern, kind, durationMs, status) => {
2895
+ if (!this.metrics) return;
2896
+ const stream = streamName(this.options.name, kind);
2897
+ const kindLabel = STREAM_KIND_LABEL[kind];
2898
+ const labels = { stream, subject: pattern, kind: kindLabel, status };
2899
+ this.metrics.messagesProcessedTotal.labels(labels).inc();
2900
+ this.metrics.handlerDurationSeconds.labels(labels).observe(durationMs / 1e3);
2901
+ };
2902
+ onPublished = (pattern, kind, durationMs, status) => {
2903
+ if (!this.metrics) return;
2904
+ const labels = { subject: pattern, kind: STREAM_KIND_LABEL[kind], status };
2905
+ this.metrics.publishTotal.labels(labels).inc();
2906
+ this.metrics.publishDurationSeconds.labels(labels).observe(durationMs / 1e3);
2907
+ };
2908
+ onRpcCompleted = (pattern, durationMs, status) => {
2909
+ this.metrics?.rpcDurationSeconds.labels({ subject: pattern, status }).observe(durationMs / 1e3);
2910
+ };
2911
+ resolveDeclared(subject) {
2912
+ return this.patternRegistry?.resolveDeclared(subject) ?? null;
1117
2913
  }
1118
2914
  };
2915
+ JetstreamMetricsService = __decorateClass([
2916
+ (0, import_common10.Injectable)(),
2917
+ __decorateParam(1, (0, import_common10.Inject)(JETSTREAM_METRICS_CONFIG)),
2918
+ __decorateParam(2, (0, import_common10.Inject)(JETSTREAM_METRICS_PROM_CLIENT)),
2919
+ __decorateParam(3, (0, import_common10.Inject)(JETSTREAM_OPTIONS)),
2920
+ __decorateParam(4, (0, import_common10.Optional)()),
2921
+ __decorateParam(5, (0, import_common10.Optional)()),
2922
+ __decorateParam(5, (0, import_common10.Inject)(JETSTREAM_CONNECTION))
2923
+ ], JetstreamMetricsService);
1119
2924
 
1120
- // src/health/jetstream.health-indicator.ts
1121
- var import_common3 = require("@nestjs/common");
1122
- var JetstreamHealthIndicator = class {
1123
- constructor(connection) {
1124
- this.connection = connection;
2925
+ // src/metrics/metrics.module.ts
2926
+ var PROM_CLIENT_INSTALL_MESSAGE = "prom-client is required when JetstreamModule.forRoot({ metrics: ... }) is enabled. Install it with: pnpm add prom-client";
2927
+ var resolvePromClient = async () => {
2928
+ try {
2929
+ return await import("prom-client");
2930
+ } catch {
2931
+ throw new Error(PROM_CLIENT_INSTALL_MESSAGE);
1125
2932
  }
1126
- logger = new import_common3.Logger("Jetstream:Health");
1127
- /**
1128
- * Plain health status check.
1129
- *
1130
- * Returns the current connection status without throwing.
1131
- * Use this for custom health endpoints or monitoring integrations.
1132
- *
1133
- * @returns Connection status with server URL and RTT latency.
1134
- */
1135
- async check() {
1136
- const nc = this.connection.unwrap;
1137
- if (!nc || nc.isClosed()) {
1138
- return { connected: false, server: null, latency: null };
1139
- }
1140
- try {
1141
- const start = performance.now();
1142
- await nc.rtt();
1143
- const latency = Math.round(performance.now() - start);
1144
- return { connected: true, server: nc.getServer(), latency };
1145
- } catch (err) {
1146
- this.logger.warn(`Health check failed: ${err instanceof Error ? err.message : err}`);
1147
- return { connected: false, server: nc.getServer(), latency: null };
2933
+ };
2934
+ var normalizeMetricsConfig = (option, promClient) => {
2935
+ const user = option && option !== true ? option : {};
2936
+ return {
2937
+ register: user.register ?? promClient.register,
2938
+ prefix: user.prefix ?? DEFAULT_METRICS_PREFIX,
2939
+ defaultLabels: user.defaultLabels,
2940
+ pollInterval: user.pollInterval ?? DEFAULT_POLL_INTERVAL_MS,
2941
+ buckets: user.buckets
2942
+ };
2943
+ };
2944
+ var JetstreamMetricsModule = class {
2945
+ static forFeature(metricsOption) {
2946
+ if (!metricsOption) {
2947
+ return { module: JetstreamMetricsModule, providers: [], exports: [] };
1148
2948
  }
1149
- }
1150
- /**
1151
- * Terminus-compatible health check.
1152
- *
1153
- * Returns `{ [key]: { status: 'up', ... } }` on success.
1154
- * Throws an error with `{ [key]: { status: 'down', ... } }` on failure.
1155
- *
1156
- * The thrown error sets `isHealthCheckError: true` and `causes` — the
1157
- * duck-type contract that Terminus `HealthCheckExecutor` uses to distinguish
1158
- * health failures from unexpected exceptions. Works with both Terminus v10
1159
- * (`instanceof HealthCheckError`) and v11+ (`error?.isHealthCheckError`).
1160
- *
1161
- * @param key - Health indicator key (default: `'jetstream'`).
1162
- * @returns Object with status, server, and latency under the given key.
1163
- * @throws Error with `isHealthCheckError`, `causes`, and `{ [key]: { status: 'down' } }`.
1164
- */
1165
- async isHealthy(key = "jetstream") {
1166
- const status = await this.check();
1167
- const details = {
1168
- status: status.connected ? "up" : "down",
1169
- server: status.server,
1170
- latency: status.latency
2949
+ const promClientProvider = {
2950
+ provide: JETSTREAM_METRICS_PROM_CLIENT,
2951
+ useFactory: async () => {
2952
+ const mod = await resolvePromClient();
2953
+ return { Counter: mod.Counter, Histogram: mod.Histogram, Gauge: mod.Gauge };
2954
+ }
2955
+ };
2956
+ const configProvider = {
2957
+ provide: JETSTREAM_METRICS_CONFIG,
2958
+ useFactory: async () => {
2959
+ const mod = await resolvePromClient();
2960
+ return normalizeMetricsConfig(metricsOption, mod);
2961
+ }
2962
+ };
2963
+ const registryProvider = {
2964
+ provide: JETSTREAM_METRICS_REGISTRY,
2965
+ inject: [JETSTREAM_METRICS_CONFIG],
2966
+ useFactory: (cfg) => cfg.register
2967
+ };
2968
+ const serviceProvider = {
2969
+ provide: JetstreamMetricsService,
2970
+ inject: [
2971
+ JETSTREAM_EVENT_BUS,
2972
+ JETSTREAM_METRICS_CONFIG,
2973
+ JETSTREAM_METRICS_PROM_CLIENT,
2974
+ JETSTREAM_OPTIONS,
2975
+ { token: PatternRegistry, optional: true },
2976
+ { token: JETSTREAM_CONNECTION, optional: true }
2977
+ ],
2978
+ useFactory: (eventBus, cfg, runtime, opts, patternRegistry, connection) => new JetstreamMetricsService(eventBus, cfg, runtime, opts, patternRegistry, connection)
2979
+ };
2980
+ return {
2981
+ module: JetstreamMetricsModule,
2982
+ providers: [promClientProvider, configProvider, registryProvider, serviceProvider],
2983
+ exports: [
2984
+ JetstreamMetricsService,
2985
+ JETSTREAM_METRICS_CONFIG,
2986
+ JETSTREAM_METRICS_REGISTRY,
2987
+ JETSTREAM_METRICS_PROM_CLIENT
2988
+ ]
1171
2989
  };
1172
- if (!status.connected) {
1173
- const causes = { [key]: details };
1174
- throw Object.assign(new Error("Jetstream health check failed"), {
1175
- causes,
1176
- isHealthCheckError: true
1177
- });
1178
- }
1179
- return { [key]: details };
1180
2990
  }
1181
2991
  };
1182
- JetstreamHealthIndicator = __decorateClass([
1183
- (0, import_common3.Injectable)()
1184
- ], JetstreamHealthIndicator);
2992
+ JetstreamMetricsModule = __decorateClass([
2993
+ (0, import_common11.Module)({})
2994
+ ], JetstreamMetricsModule);
1185
2995
 
1186
2996
  // src/server/strategy.ts
1187
2997
  var import_microservices2 = require("@nestjs/microservices");
@@ -1257,6 +3067,26 @@ var JetstreamStrategy = class extends import_microservices2.Server {
1257
3067
  this.messageProvider.destroy();
1258
3068
  this.started = false;
1259
3069
  }
3070
+ /**
3071
+ * Override NestJS `Server.addHandler` to fail-fast on duplicate pattern registration.
3072
+ *
3073
+ * The base class silently overwrites duplicate RPC handlers (last wins) and appends
3074
+ * duplicate event handlers to a linked list. Both behaviors are hazardous in a
3075
+ * JetStream context: silent overwrite drops a handler the user wrote, and double
3076
+ * event dispatch double-acks/double-processes the same JetStream message.
3077
+ *
3078
+ * We treat any pattern collision as a fatal misconfiguration so it surfaces at
3079
+ * bootstrap instead of in production traffic.
3080
+ */
3081
+ addHandler(pattern, callback, isEventHandler = false, extras = {}) {
3082
+ const normalizedPattern = this.normalizePattern(pattern);
3083
+ if (this.messageHandlers.has(normalizedPattern)) {
3084
+ throw new Error(
3085
+ `Duplicate handler registered for pattern "${normalizedPattern}". Each @EventPattern() / @MessagePattern() value must be unique within a microservice \u2014 find and remove the second declaration.`
3086
+ );
3087
+ }
3088
+ super.addHandler(pattern, callback, isEventHandler, extras);
3089
+ }
1260
3090
  /**
1261
3091
  * Register event listener (required by Server base class).
1262
3092
  *
@@ -1329,7 +3159,7 @@ var JetstreamStrategy = class extends import_microservices2.Server {
1329
3159
  };
1330
3160
 
1331
3161
  // src/server/core-rpc.server.ts
1332
- var import_common4 = require("@nestjs/common");
3162
+ var import_common12 = require("@nestjs/common");
1333
3163
  var import_transport_node3 = require("@nats-io/transport-node");
1334
3164
 
1335
3165
  // src/context/rpc.context.ts
@@ -1339,9 +3169,6 @@ var RpcContext = class extends import_microservices3.BaseRpcContext {
1339
3169
  _retryDelay;
1340
3170
  _shouldTerminate = false;
1341
3171
  _terminateReason;
1342
- // ---------------------------------------------------------------------------
1343
- // Message accessors
1344
- // ---------------------------------------------------------------------------
1345
3172
  /**
1346
3173
  * Get the underlying NATS message.
1347
3174
  *
@@ -1376,9 +3203,6 @@ var RpcContext = class extends import_microservices3.BaseRpcContext {
1376
3203
  isJetStream() {
1377
3204
  return "ack" in this.args[0];
1378
3205
  }
1379
- // ---------------------------------------------------------------------------
1380
- // JetStream metadata (return undefined for Core NATS messages)
1381
- // ---------------------------------------------------------------------------
1382
3206
  /** How many times this message has been delivered. */
1383
3207
  getDeliveryCount() {
1384
3208
  return this.asJetStream()?.info.deliveryCount;
@@ -1400,9 +3224,6 @@ var RpcContext = class extends import_microservices3.BaseRpcContext {
1400
3224
  getCallerName() {
1401
3225
  return this.getHeader("x-caller-name" /* CallerName */);
1402
3226
  }
1403
- // ---------------------------------------------------------------------------
1404
- // Handler-controlled settlement
1405
- // ---------------------------------------------------------------------------
1406
3227
  /**
1407
3228
  * Signal the transport to retry (nak) this message instead of acknowledging it.
1408
3229
  *
@@ -1447,9 +3268,6 @@ var RpcContext = class extends import_microservices3.BaseRpcContext {
1447
3268
  throw new Error(`${method}() is only available for JetStream messages`);
1448
3269
  }
1449
3270
  }
1450
- // ---------------------------------------------------------------------------
1451
- // Transport-facing state (read by EventRouter)
1452
- // ---------------------------------------------------------------------------
1453
3271
  /** @internal */
1454
3272
  get shouldRetry() {
1455
3273
  return this._shouldRetry;
@@ -1571,7 +3389,7 @@ var unwrapResult = (result) => {
1571
3389
  }
1572
3390
  return result;
1573
3391
  };
1574
- var isPromiseLike = (value) => value !== null && typeof value === "object" && typeof value.then === "function";
3392
+ var isPromiseLike2 = (value) => value !== null && typeof value === "object" && typeof value.then === "function";
1575
3393
  var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
1576
3394
  let done = false;
1577
3395
  let subscription = null;
@@ -1601,20 +3419,25 @@ var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
1601
3419
  // src/server/core-rpc.server.ts
1602
3420
  var CoreRpcServer = class {
1603
3421
  constructor(options, connection, patternRegistry, codec, eventBus) {
1604
- this.options = options;
1605
3422
  this.connection = connection;
1606
3423
  this.patternRegistry = patternRegistry;
1607
3424
  this.codec = codec;
1608
3425
  this.eventBus = eventBus;
3426
+ const derived = deriveOtelAttrs(options);
3427
+ this.otel = derived.otel;
3428
+ this.serviceName = derived.serviceName;
3429
+ this.serverEndpoint = derived.serverEndpoint;
1609
3430
  }
1610
- logger = new import_common4.Logger("Jetstream:CoreRpc");
3431
+ logger = new import_common12.Logger("Jetstream:CoreRpc");
1611
3432
  subscription = null;
3433
+ otel;
3434
+ serviceName;
3435
+ serverEndpoint;
1612
3436
  /** Start listening for RPC requests on the command subject. */
1613
3437
  async start() {
1614
3438
  const nc = await this.connection.getConnection();
1615
- const serviceName = internalName(this.options.name);
1616
- const subject = `${serviceName}.cmd.>`;
1617
- const queue = `${serviceName}_cmd_queue`;
3439
+ const subject = `${this.serviceName}.cmd.>`;
3440
+ const queue = `${this.serviceName}_cmd_queue`;
1618
3441
  this.subscription = nc.subscribe(subject, {
1619
3442
  queue,
1620
3443
  callback: (err, msg) => {
@@ -1658,15 +3481,51 @@ var CoreRpcServer = class {
1658
3481
  return;
1659
3482
  }
1660
3483
  const ctx = new RpcContext([msg]);
3484
+ const startedAt = performance.now();
1661
3485
  try {
1662
- const raw = unwrapResult(handler(data, ctx));
1663
- const result = isPromiseLike(raw) ? await raw : raw;
1664
- msg.respond(this.codec.encode(result));
3486
+ const raw = await withConsumeSpan(
3487
+ {
3488
+ subject: msg.subject,
3489
+ msg,
3490
+ kind: "rpc" /* Rpc */,
3491
+ payloadBytes: msg.data.length,
3492
+ handlerMetadata: { pattern: msg.subject },
3493
+ serviceName: this.serviceName,
3494
+ endpoint: this.serverEndpoint
3495
+ },
3496
+ this.otel,
3497
+ () => {
3498
+ const out = unwrapResult(handler(data, ctx));
3499
+ return isPromiseLike2(out) ? out : out;
3500
+ }
3501
+ );
3502
+ msg.respond(this.codec.encode(raw));
3503
+ this.reportHandlerCompleted(msg, startedAt, "success");
1665
3504
  } catch (err) {
1666
- this.logger.error(`Handler error for Core RPC ${msg.subject}:`, err);
3505
+ this.eventBus.emit(
3506
+ "error" /* Error */,
3507
+ err instanceof Error ? err : new Error(String(err)),
3508
+ `core-rpc-handler:${msg.subject}`
3509
+ );
1667
3510
  this.respondWithError(msg, err);
3511
+ this.reportHandlerCompleted(msg, startedAt, "error");
1668
3512
  }
1669
3513
  }
3514
+ // See EventRouter.reportHandlerCompleted for the rationale on declared
3515
+ // pattern + per-emit hasHook check.
3516
+ reportHandlerCompleted(msg, startedAt, status) {
3517
+ if (!this.eventBus.hasHook("handlerCompleted" /* HandlerCompleted */)) return;
3518
+ const declared = this.patternRegistry.resolveDeclared(msg.subject);
3519
+ const pattern = declared?.pattern ?? msg.subject;
3520
+ const kind = declared?.kind ?? "cmd" /* Command */;
3521
+ this.eventBus.emit(
3522
+ "handlerCompleted" /* HandlerCompleted */,
3523
+ pattern,
3524
+ kind,
3525
+ performance.now() - startedAt,
3526
+ status
3527
+ );
3528
+ }
1670
3529
  /** Send an error response back to the caller with x-error header. */
1671
3530
  respondWithError(msg, error) {
1672
3531
  try {
@@ -1680,8 +3539,8 @@ var CoreRpcServer = class {
1680
3539
  };
1681
3540
 
1682
3541
  // src/server/infrastructure/stream.provider.ts
1683
- var import_common6 = require("@nestjs/common");
1684
- var import_jetstream14 = require("@nats-io/jetstream");
3542
+ var import_common14 = require("@nestjs/common");
3543
+ var import_jetstream17 = require("@nats-io/jetstream");
1685
3544
 
1686
3545
  // src/server/infrastructure/nats-error-codes.ts
1687
3546
  var NatsErrorCode = /* @__PURE__ */ ((NatsErrorCode2) => {
@@ -1747,8 +3606,8 @@ var isEqual = (a, b) => {
1747
3606
  };
1748
3607
 
1749
3608
  // src/server/infrastructure/stream-migration.ts
1750
- var import_common5 = require("@nestjs/common");
1751
- var import_jetstream13 = require("@nats-io/jetstream");
3609
+ var import_common13 = require("@nestjs/common");
3610
+ var import_jetstream16 = require("@nats-io/jetstream");
1752
3611
  var MIGRATION_BACKUP_SUFFIX = "__migration_backup";
1753
3612
  var DEFAULT_SOURCING_TIMEOUT_MS = 3e4;
1754
3613
  var SOURCING_POLL_INTERVAL_MS = 100;
@@ -1756,7 +3615,7 @@ var StreamMigration = class {
1756
3615
  constructor(sourcingTimeoutMs = DEFAULT_SOURCING_TIMEOUT_MS) {
1757
3616
  this.sourcingTimeoutMs = sourcingTimeoutMs;
1758
3617
  }
1759
- logger = new import_common5.Logger("Jetstream:Stream");
3618
+ logger = new import_common13.Logger("Jetstream:Stream");
1760
3619
  async migrate(jsm, streamName2, newConfig) {
1761
3620
  const backupName = `${streamName2}${MIGRATION_BACKUP_SUFFIX}`;
1762
3621
  const startTime = Date.now();
@@ -1825,7 +3684,7 @@ var StreamMigration = class {
1825
3684
  this.logger.warn(`Found orphaned migration backup stream: ${backupName}, cleaning up`);
1826
3685
  await jsm.streams.delete(backupName);
1827
3686
  } catch (err) {
1828
- if (err instanceof import_jetstream13.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
3687
+ if (err instanceof import_jetstream16.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
1829
3688
  return;
1830
3689
  }
1831
3690
  throw err;
@@ -1838,9 +3697,16 @@ var StreamProvider = class {
1838
3697
  constructor(options, connection) {
1839
3698
  this.options = options;
1840
3699
  this.connection = connection;
3700
+ const derived = deriveOtelAttrs(options);
3701
+ this.otel = derived.otel;
3702
+ this.otelServiceName = derived.serviceName;
3703
+ this.otelEndpoint = derived.serverEndpoint;
1841
3704
  }
1842
- logger = new import_common6.Logger("Jetstream:Stream");
3705
+ logger = new import_common14.Logger("Jetstream:Stream");
1843
3706
  migration = new StreamMigration();
3707
+ otel;
3708
+ otelServiceName;
3709
+ otelEndpoint;
1844
3710
  /**
1845
3711
  * Ensure all required streams exist with correct configuration.
1846
3712
  *
@@ -1886,32 +3752,56 @@ var StreamProvider = class {
1886
3752
  /** Ensure a single stream exists, creating or updating as needed. */
1887
3753
  async ensureStream(jsm, kind) {
1888
3754
  const config = this.buildConfig(kind);
1889
- this.logger.log(`Ensuring stream: ${config.name}`);
1890
- try {
1891
- const currentInfo = await jsm.streams.info(config.name);
1892
- return await this.handleExistingStream(jsm, currentInfo, config);
1893
- } catch (err) {
1894
- if (err instanceof import_jetstream14.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
1895
- this.logger.log(`Creating stream: ${config.name}`);
1896
- return await jsm.streams.add(config);
3755
+ return withProvisioningSpan(
3756
+ this.otel,
3757
+ {
3758
+ serviceName: this.otelServiceName,
3759
+ endpoint: this.otelEndpoint,
3760
+ entity: "stream",
3761
+ name: config.name,
3762
+ action: "ensure"
3763
+ },
3764
+ async () => {
3765
+ this.logger.log(`Ensuring stream: ${config.name}`);
3766
+ try {
3767
+ const currentInfo = await jsm.streams.info(config.name);
3768
+ return await this.handleExistingStream(jsm, currentInfo, config);
3769
+ } catch (err) {
3770
+ if (err instanceof import_jetstream17.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
3771
+ this.logger.log(`Creating stream: ${config.name}`);
3772
+ return await jsm.streams.add(config);
3773
+ }
3774
+ throw err;
3775
+ }
1897
3776
  }
1898
- throw err;
1899
- }
3777
+ );
1900
3778
  }
1901
3779
  /** Ensure a dead-letter queue stream exists, creating or updating as needed. */
1902
3780
  async ensureDlqStream(jsm) {
1903
3781
  const config = this.buildDlqConfig();
1904
- this.logger.log(`Ensuring DLQ stream: ${config.name}`);
1905
- try {
1906
- const currentInfo = await jsm.streams.info(config.name);
1907
- return await this.handleExistingStream(jsm, currentInfo, config);
1908
- } catch (err) {
1909
- if (err instanceof import_jetstream14.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
1910
- this.logger.log(`Creating DLQ stream: ${config.name}`);
1911
- return await jsm.streams.add(config);
3782
+ return withProvisioningSpan(
3783
+ this.otel,
3784
+ {
3785
+ serviceName: this.otelServiceName,
3786
+ endpoint: this.otelEndpoint,
3787
+ entity: "stream",
3788
+ name: config.name,
3789
+ action: "ensure"
3790
+ },
3791
+ async () => {
3792
+ this.logger.log(`Ensuring DLQ stream: ${config.name}`);
3793
+ try {
3794
+ const currentInfo = await jsm.streams.info(config.name);
3795
+ return await this.handleExistingStream(jsm, currentInfo, config);
3796
+ } catch (err) {
3797
+ if (err instanceof import_jetstream17.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
3798
+ this.logger.log(`Creating DLQ stream: ${config.name}`);
3799
+ return await jsm.streams.add(config);
3800
+ }
3801
+ throw err;
3802
+ }
1912
3803
  }
1913
- throw err;
1914
- }
3804
+ );
1915
3805
  }
1916
3806
  async handleExistingStream(jsm, currentInfo, config) {
1917
3807
  const diff = compareStreamConfig(currentInfo.config, config);
@@ -1940,7 +3830,18 @@ var StreamProvider = class {
1940
3830
  }
1941
3831
  return currentInfo;
1942
3832
  }
1943
- await this.migration.migrate(jsm, config.name, config);
3833
+ await withMigrationSpan(
3834
+ this.otel,
3835
+ {
3836
+ serviceName: this.otelServiceName,
3837
+ endpoint: this.otelEndpoint,
3838
+ stream: config.name,
3839
+ reason: diff.changes.filter((c) => c.mutability === "immutable").map((c) => c.property).join(", ")
3840
+ },
3841
+ async () => {
3842
+ await this.migration.migrate(jsm, config.name, config);
3843
+ }
3844
+ );
1944
3845
  return await jsm.streams.info(config.name);
1945
3846
  }
1946
3847
  buildMutableOnlyConfig(config, currentConfig, diff) {
@@ -2056,16 +3957,23 @@ var StreamProvider = class {
2056
3957
  };
2057
3958
 
2058
3959
  // src/server/infrastructure/consumer.provider.ts
2059
- var import_common7 = require("@nestjs/common");
2060
- var import_jetstream16 = require("@nats-io/jetstream");
3960
+ var import_common15 = require("@nestjs/common");
3961
+ var import_jetstream19 = require("@nats-io/jetstream");
2061
3962
  var ConsumerProvider = class {
2062
3963
  constructor(options, connection, streamProvider, patternRegistry) {
2063
3964
  this.options = options;
2064
3965
  this.connection = connection;
2065
3966
  this.streamProvider = streamProvider;
2066
3967
  this.patternRegistry = patternRegistry;
2067
- }
2068
- logger = new import_common7.Logger("Jetstream:Consumer");
3968
+ const derived = deriveOtelAttrs(options);
3969
+ this.otel = derived.otel;
3970
+ this.otelServiceName = derived.serviceName;
3971
+ this.otelEndpoint = derived.serverEndpoint;
3972
+ }
3973
+ logger = new import_common15.Logger("Jetstream:Consumer");
3974
+ otel;
3975
+ otelServiceName;
3976
+ otelEndpoint;
2069
3977
  /**
2070
3978
  * Ensure consumers exist for the specified kinds.
2071
3979
  *
@@ -2095,17 +4003,29 @@ var ConsumerProvider = class {
2095
4003
  const stream = this.streamProvider.getStreamName(kind);
2096
4004
  const config = this.buildConfig(kind);
2097
4005
  const name = config.durable_name;
2098
- this.logger.log(`Ensuring consumer: ${name} on stream: ${stream}`);
2099
- try {
2100
- await jsm.consumers.info(stream, name);
2101
- this.logger.debug(`Consumer exists, updating: ${name}`);
2102
- return await jsm.consumers.update(stream, name, config);
2103
- } catch (err) {
2104
- if (!(err instanceof import_jetstream16.JetStreamApiError) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
2105
- throw err;
4006
+ return withProvisioningSpan(
4007
+ this.otel,
4008
+ {
4009
+ serviceName: this.otelServiceName,
4010
+ endpoint: this.otelEndpoint,
4011
+ entity: "consumer",
4012
+ name,
4013
+ action: "ensure"
4014
+ },
4015
+ async () => {
4016
+ this.logger.log(`Ensuring consumer: ${name} on stream: ${stream}`);
4017
+ try {
4018
+ await jsm.consumers.info(stream, name);
4019
+ this.logger.debug(`Consumer exists, updating: ${name}`);
4020
+ return await jsm.consumers.update(stream, name, config);
4021
+ } catch (err) {
4022
+ if (!(err instanceof import_jetstream19.JetStreamApiError) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
4023
+ throw err;
4024
+ }
4025
+ return await this.createConsumer(jsm, stream, name, config);
4026
+ }
2106
4027
  }
2107
- return await this.createConsumer(jsm, stream, name, config);
2108
- }
4028
+ );
2109
4029
  }
2110
4030
  /**
2111
4031
  * Recover a consumer that disappeared during runtime.
@@ -2124,16 +4044,28 @@ var ConsumerProvider = class {
2124
4044
  const stream = this.streamProvider.getStreamName(kind);
2125
4045
  const config = this.buildConfig(kind);
2126
4046
  const name = config.durable_name;
2127
- this.logger.log(`Recovering consumer: ${name} on stream: ${stream}`);
2128
- await this.assertNoMigrationInProgress(jsm, stream);
2129
- try {
2130
- return await jsm.consumers.info(stream, name);
2131
- } catch (err) {
2132
- if (!(err instanceof import_jetstream16.JetStreamApiError) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
2133
- throw err;
4047
+ return withProvisioningSpan(
4048
+ this.otel,
4049
+ {
4050
+ serviceName: this.otelServiceName,
4051
+ endpoint: this.otelEndpoint,
4052
+ entity: "consumer",
4053
+ name,
4054
+ action: "recover"
4055
+ },
4056
+ async () => {
4057
+ this.logger.log(`Recovering consumer: ${name} on stream: ${stream}`);
4058
+ await this.assertNoMigrationInProgress(jsm, stream);
4059
+ try {
4060
+ return await jsm.consumers.info(stream, name);
4061
+ } catch (err) {
4062
+ if (!(err instanceof import_jetstream19.JetStreamApiError) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
4063
+ throw err;
4064
+ }
4065
+ return await this.createConsumer(jsm, stream, name, config);
4066
+ }
2134
4067
  }
2135
- return await this.createConsumer(jsm, stream, name, config);
2136
- }
4068
+ );
2137
4069
  }
2138
4070
  /**
2139
4071
  * Throw if a migration backup stream exists for this stream.
@@ -2148,7 +4080,7 @@ var ConsumerProvider = class {
2148
4080
  `Stream ${stream} is being migrated (backup ${backupName} exists). Waiting for migration to complete before recovering consumer.`
2149
4081
  );
2150
4082
  } catch (err) {
2151
- if (err instanceof import_jetstream16.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
4083
+ if (err instanceof import_jetstream19.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
2152
4084
  return;
2153
4085
  }
2154
4086
  throw err;
@@ -2162,7 +4094,7 @@ var ConsumerProvider = class {
2162
4094
  try {
2163
4095
  return await jsm.consumers.add(stream, config);
2164
4096
  } catch (addErr) {
2165
- if (addErr instanceof import_jetstream16.JetStreamApiError && addErr.apiError().err_code === 10148 /* ConsumerAlreadyExists */) {
4097
+ if (addErr instanceof import_jetstream19.JetStreamApiError && addErr.apiError().err_code === 10148 /* ConsumerAlreadyExists */) {
2166
4098
  this.logger.debug(`Consumer ${name} created by another pod, using existing`);
2167
4099
  return await jsm.consumers.info(stream, name);
2168
4100
  }
@@ -2249,8 +4181,8 @@ var ConsumerProvider = class {
2249
4181
  };
2250
4182
 
2251
4183
  // src/server/infrastructure/message.provider.ts
2252
- var import_common8 = require("@nestjs/common");
2253
- var import_jetstream18 = require("@nats-io/jetstream");
4184
+ var import_common16 = require("@nestjs/common");
4185
+ var import_jetstream21 = require("@nats-io/jetstream");
2254
4186
  var import_rxjs3 = require("rxjs");
2255
4187
  var MessageProvider = class {
2256
4188
  constructor(connection, eventBus, consumeOptionsMap = /* @__PURE__ */ new Map(), consumerRecoveryFn) {
@@ -2259,7 +4191,7 @@ var MessageProvider = class {
2259
4191
  this.consumeOptionsMap = consumeOptionsMap;
2260
4192
  this.consumerRecoveryFn = consumerRecoveryFn;
2261
4193
  }
2262
- logger = new import_common8.Logger("Jetstream:Message");
4194
+ logger = new import_common16.Logger("Jetstream:Message");
2263
4195
  activeIterators = /* @__PURE__ */ new Set();
2264
4196
  orderedReadyResolve = null;
2265
4197
  orderedReadyReject = null;
@@ -2311,7 +4243,7 @@ var MessageProvider = class {
2311
4243
  */
2312
4244
  async startOrdered(streamName2, filterSubjects, orderedConfig) {
2313
4245
  const consumerOpts = { filter_subjects: filterSubjects };
2314
- if (orderedConfig?.deliverPolicy !== void 0 && orderedConfig.deliverPolicy !== import_jetstream18.DeliverPolicy.All) {
4246
+ if (orderedConfig?.deliverPolicy !== void 0 && orderedConfig.deliverPolicy !== import_jetstream21.DeliverPolicy.All) {
2315
4247
  consumerOpts.deliver_policy = orderedConfig.deliverPolicy;
2316
4248
  }
2317
4249
  if (orderedConfig?.optStartSeq !== void 0) {
@@ -2357,10 +4289,13 @@ var MessageProvider = class {
2357
4289
  /** Create a self-healing consumer flow for a specific kind. */
2358
4290
  createFlow(kind, info) {
2359
4291
  const target$ = this.getTargetSubject(kind);
2360
- return this.createSelfHealingFlow(() => this.consumeOnce(kind, info, target$), info.name);
4292
+ return this.createSelfHealingFlow(
4293
+ (onConnected) => this.consumeOnce(kind, info, target$, onConnected),
4294
+ info.name
4295
+ );
2361
4296
  }
2362
4297
  /** Single iteration: get consumer -> pull messages -> emit to subject. */
2363
- async consumeOnce(kind, info, target$) {
4298
+ async consumeOnce(kind, info, target$, onConnected) {
2364
4299
  const js = this.connection.getJetStreamClient();
2365
4300
  let consumer;
2366
4301
  let consumerName2 = info.name;
@@ -2388,6 +4323,7 @@ var MessageProvider = class {
2388
4323
  });
2389
4324
  this.activeIterators.add(messages);
2390
4325
  this.monitorConsumerHealth(messages, consumerName2);
4326
+ onConnected();
2391
4327
  try {
2392
4328
  await messages.closed();
2393
4329
  } finally {
@@ -2442,7 +4378,7 @@ var MessageProvider = class {
2442
4378
  /** Create a self-healing ordered consumer flow. */
2443
4379
  createOrderedFlow(streamName2, consumerOpts) {
2444
4380
  return this.createSelfHealingFlow(
2445
- () => this.consumeOrderedOnce(streamName2, consumerOpts),
4381
+ (onConnected) => this.consumeOrderedOnce(streamName2, consumerOpts, onConnected),
2446
4382
  "ordered" /* Ordered */,
2447
4383
  (err) => {
2448
4384
  if (this.orderedReadyReject) {
@@ -2456,10 +4392,15 @@ var MessageProvider = class {
2456
4392
  /** Shared self-healing flow: defer -> retry with exponential backoff on error/completion. */
2457
4393
  createSelfHealingFlow(source, label, onFirstError) {
2458
4394
  let consecutiveFailures = 0;
2459
- return (0, import_rxjs3.defer)(source).pipe(
2460
- (0, import_rxjs3.tap)(() => {
2461
- consecutiveFailures = 0;
2462
- }),
4395
+ const onConnected = () => {
4396
+ if (consecutiveFailures > 0) {
4397
+ const attempts = consecutiveFailures;
4398
+ this.logger.log(`Consumer ${label} recovered after ${attempts} failed attempt(s)`);
4399
+ this.eventBus.emit("consumerRecovered" /* ConsumerRecovered */, label, attempts);
4400
+ }
4401
+ consecutiveFailures = 0;
4402
+ };
4403
+ return (0, import_rxjs3.defer)(() => source(onConnected)).pipe(
2463
4404
  (0, import_rxjs3.catchError)((err) => {
2464
4405
  consecutiveFailures++;
2465
4406
  this.logger.error(`Consumer ${label} error, will restart:`, err);
@@ -2482,7 +4423,7 @@ var MessageProvider = class {
2482
4423
  );
2483
4424
  }
2484
4425
  /** Single iteration: create ordered consumer -> push messages into the subject. */
2485
- async consumeOrderedOnce(streamName2, consumerOpts) {
4426
+ async consumeOrderedOnce(streamName2, consumerOpts, onConnected) {
2486
4427
  const js = this.connection.getJetStreamClient();
2487
4428
  const consumer = await js.consumers.get(streamName2, consumerOpts);
2488
4429
  const orderedMessages$ = this.orderedMessages$;
@@ -2497,6 +4438,7 @@ var MessageProvider = class {
2497
4438
  this.orderedReadyReject = null;
2498
4439
  }
2499
4440
  this.activeIterators.add(messages);
4441
+ onConnected();
2500
4442
  try {
2501
4443
  await messages.closed();
2502
4444
  } finally {
@@ -2506,7 +4448,7 @@ var MessageProvider = class {
2506
4448
  };
2507
4449
 
2508
4450
  // src/server/infrastructure/metadata.provider.ts
2509
- var import_common9 = require("@nestjs/common");
4451
+ var import_common17 = require("@nestjs/common");
2510
4452
  var import_kv = require("@nats-io/kv");
2511
4453
  var MetadataProvider = class {
2512
4454
  constructor(options, connection) {
@@ -2515,7 +4457,7 @@ var MetadataProvider = class {
2515
4457
  this.replicas = options.metadata?.replicas ?? DEFAULT_METADATA_REPLICAS;
2516
4458
  this.ttl = Math.max(options.metadata?.ttl ?? DEFAULT_METADATA_TTL, MIN_METADATA_TTL);
2517
4459
  }
2518
- logger = new import_common9.Logger("Jetstream:Metadata");
4460
+ logger = new import_common17.Logger("Jetstream:Metadata");
2519
4461
  bucketName;
2520
4462
  replicas;
2521
4463
  ttl;
@@ -2607,177 +4549,14 @@ var MetadataProvider = class {
2607
4549
  }
2608
4550
  };
2609
4551
 
2610
- // src/server/routing/pattern-registry.ts
2611
- var import_common10 = require("@nestjs/common");
2612
- var HANDLER_LABELS = {
2613
- ["broadcast" /* Broadcast */]: "broadcast" /* Broadcast */,
2614
- ["ordered" /* Ordered */]: "ordered" /* Ordered */,
2615
- ["ev" /* Event */]: "event" /* Event */,
2616
- ["cmd" /* Command */]: "rpc" /* Rpc */
2617
- };
2618
- var PatternRegistry = class {
2619
- constructor(options) {
2620
- this.options = options;
2621
- }
2622
- logger = new import_common10.Logger("Jetstream:PatternRegistry");
2623
- registry = /* @__PURE__ */ new Map();
2624
- // Cached after registerHandlers() — the registry is immutable from that point
2625
- cachedPatterns = null;
2626
- _hasEvents = false;
2627
- _hasCommands = false;
2628
- _hasBroadcasts = false;
2629
- _hasOrdered = false;
2630
- _hasMetadata = false;
2631
- /**
2632
- * Register all handlers from the NestJS strategy.
2633
- *
2634
- * @param handlers Map of pattern -> MessageHandler from `Server.getHandlers()`.
2635
- */
2636
- registerHandlers(handlers) {
2637
- const serviceName = this.options.name;
2638
- for (const [pattern, handler] of handlers) {
2639
- const extras = handler.extras;
2640
- const isEvent = handler.isEventHandler ?? false;
2641
- const isBroadcast = !!extras?.broadcast;
2642
- const isOrdered = !!extras?.ordered;
2643
- const meta = extras?.meta;
2644
- if (isBroadcast && isOrdered) {
2645
- throw new Error(
2646
- `Handler "${pattern}" cannot be both broadcast and ordered. Use one or the other.`
2647
- );
2648
- }
2649
- let kind;
2650
- if (isBroadcast) kind = "broadcast" /* Broadcast */;
2651
- else if (isOrdered) kind = "ordered" /* Ordered */;
2652
- else if (isEvent) kind = "ev" /* Event */;
2653
- else kind = "cmd" /* Command */;
2654
- const fullSubject = kind === "broadcast" /* Broadcast */ ? buildBroadcastSubject(pattern) : buildSubject(serviceName, kind, pattern);
2655
- this.registry.set(fullSubject, {
2656
- handler,
2657
- pattern,
2658
- isEvent: isEvent && !isOrdered,
2659
- isBroadcast,
2660
- isOrdered,
2661
- meta
2662
- });
2663
- this.logger.debug(`Registered ${HANDLER_LABELS[kind]}: ${pattern} -> ${fullSubject}`);
2664
- }
2665
- this.cachedPatterns = this.buildPatternsByKind();
2666
- this._hasEvents = this.cachedPatterns.events.length > 0;
2667
- this._hasCommands = this.cachedPatterns.commands.length > 0;
2668
- this._hasBroadcasts = this.cachedPatterns.broadcasts.length > 0;
2669
- this._hasOrdered = this.cachedPatterns.ordered.length > 0;
2670
- this._hasMetadata = [...this.registry.values()].some((entry) => entry.meta !== void 0);
2671
- this.logSummary();
2672
- }
2673
- /** Find handler for a full NATS subject. */
2674
- getHandler(subject) {
2675
- return this.registry.get(subject)?.handler ?? null;
2676
- }
2677
- /** Get all registered broadcast patterns (for consumer filter_subject setup). */
2678
- getBroadcastPatterns() {
2679
- return this.getPatternsByKind().broadcasts.map((p) => buildBroadcastSubject(p));
2680
- }
2681
- hasBroadcastHandlers() {
2682
- return this._hasBroadcasts;
2683
- }
2684
- hasRpcHandlers() {
2685
- return this._hasCommands;
2686
- }
2687
- hasEventHandlers() {
2688
- return this._hasEvents;
2689
- }
2690
- hasOrderedHandlers() {
2691
- return this._hasOrdered;
2692
- }
2693
- /** Get fully-qualified NATS subjects for ordered handlers. */
2694
- getOrderedSubjects() {
2695
- return this.getPatternsByKind().ordered.map(
2696
- (p) => buildSubject(this.options.name, "ordered" /* Ordered */, p)
2697
- );
2698
- }
2699
- /** Check if any registered handler has metadata. */
2700
- hasMetadata() {
2701
- return this._hasMetadata;
2702
- }
2703
- /**
2704
- * Get handler metadata entries for KV publishing.
2705
- *
2706
- * Returns a map of KV key -> metadata object for all handlers that have `meta`.
2707
- * Key format: `{serviceName}.{kind}.{pattern}`.
2708
- */
2709
- getMetadataEntries() {
2710
- const entries = /* @__PURE__ */ new Map();
2711
- for (const entry of this.registry.values()) {
2712
- if (!entry.meta) continue;
2713
- const kind = this.resolveStreamKind(entry);
2714
- const key = metadataKey(this.options.name, kind, entry.pattern);
2715
- entries.set(key, entry.meta);
2716
- }
2717
- return entries;
2718
- }
2719
- /** Get patterns grouped by kind (cached after registration). */
2720
- getPatternsByKind() {
2721
- const patterns = this.cachedPatterns ?? this.buildPatternsByKind();
2722
- return {
2723
- events: [...patterns.events],
2724
- commands: [...patterns.commands],
2725
- broadcasts: [...patterns.broadcasts],
2726
- ordered: [...patterns.ordered]
2727
- };
2728
- }
2729
- /** Normalize a full NATS subject back to the user-facing pattern. */
2730
- normalizeSubject(subject) {
2731
- const name = internalName(this.options.name);
2732
- const prefixes = [
2733
- `${name}.${"cmd" /* Command */}.`,
2734
- `${name}.${"ev" /* Event */}.`,
2735
- `${name}.${"ordered" /* Ordered */}.`,
2736
- `${"broadcast" /* Broadcast */}.`
2737
- ];
2738
- for (const prefix of prefixes) {
2739
- if (subject.startsWith(prefix)) {
2740
- return subject.slice(prefix.length);
2741
- }
2742
- }
2743
- return subject;
2744
- }
2745
- buildPatternsByKind() {
2746
- const events = [];
2747
- const commands = [];
2748
- const broadcasts = [];
2749
- const ordered = [];
2750
- for (const entry of this.registry.values()) {
2751
- if (entry.isBroadcast) broadcasts.push(entry.pattern);
2752
- else if (entry.isOrdered) ordered.push(entry.pattern);
2753
- else if (entry.isEvent) events.push(entry.pattern);
2754
- else commands.push(entry.pattern);
2755
- }
2756
- return { events, commands, broadcasts, ordered };
2757
- }
2758
- resolveStreamKind(entry) {
2759
- if (entry.isBroadcast) return "broadcast" /* Broadcast */;
2760
- if (entry.isOrdered) return "ordered" /* Ordered */;
2761
- if (entry.isEvent) return "ev" /* Event */;
2762
- return "cmd" /* Command */;
2763
- }
2764
- logSummary() {
2765
- const { events, commands, broadcasts, ordered } = this.getPatternsByKind();
2766
- const parts = [
2767
- `${commands.length} RPC`,
2768
- `${events.length} events`,
2769
- `${broadcasts.length} broadcasts`
2770
- ];
2771
- if (ordered.length > 0) {
2772
- parts.push(`${ordered.length} ordered`);
2773
- }
2774
- this.logger.log(`Registered handlers: ${parts.join(", ")}`);
2775
- }
2776
- };
2777
-
2778
4552
  // src/server/routing/event.router.ts
2779
- var import_common11 = require("@nestjs/common");
4553
+ var import_common18 = require("@nestjs/common");
2780
4554
  var import_transport_node4 = require("@nats-io/transport-node");
4555
+ var eventConsumeKindFor = (kind) => {
4556
+ if (kind === "broadcast" /* Broadcast */) return "broadcast" /* Broadcast */;
4557
+ if (kind === "ordered" /* Ordered */) return "ordered" /* Ordered */;
4558
+ return "event" /* Event */;
4559
+ };
2781
4560
  var EventRouter = class {
2782
4561
  constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig, processingConfig, ackWaitMap, connection, options) {
2783
4562
  this.messageProvider = messageProvider;
@@ -2789,9 +4568,22 @@ var EventRouter = class {
2789
4568
  this.ackWaitMap = ackWaitMap;
2790
4569
  this.connection = connection;
2791
4570
  this.options = options;
4571
+ if (options) {
4572
+ const derived = deriveOtelAttrs(options);
4573
+ this.otel = derived.otel;
4574
+ this.serviceName = derived.serviceName;
4575
+ this.serverEndpoint = derived.serverEndpoint;
4576
+ } else {
4577
+ this.otel = resolveOtelOptions({ enabled: false });
4578
+ this.serviceName = "";
4579
+ this.serverEndpoint = null;
4580
+ }
2792
4581
  }
2793
- logger = new import_common11.Logger("Jetstream:EventRouter");
4582
+ logger = new import_common18.Logger("Jetstream:EventRouter");
2794
4583
  subscriptions = [];
4584
+ otel;
4585
+ serviceName;
4586
+ serverEndpoint;
2795
4587
  /**
2796
4588
  * Update the max_deliver thresholds from actual NATS consumer configs.
2797
4589
  * Called after consumers are ensured so the DLQ map reflects reality.
@@ -2821,13 +4613,24 @@ var EventRouter = class {
2821
4613
  const patternRegistry = this.patternRegistry;
2822
4614
  const codec = this.codec;
2823
4615
  const eventBus = this.eventBus;
2824
- const logger = this.logger;
4616
+ const logger5 = this.logger;
2825
4617
  const deadLetterConfig = this.deadLetterConfig;
4618
+ const otel = this.otel;
4619
+ const serviceName = this.serviceName;
4620
+ const serverEndpoint = this.serverEndpoint;
4621
+ const spanKind = eventConsumeKindFor(kind);
2826
4622
  const ackExtensionInterval = isOrdered ? null : resolveAckExtensionInterval(this.getAckExtensionConfig(kind), this.ackWaitMap?.get(kind));
2827
4623
  const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
2828
4624
  const concurrency = this.getConcurrency(kind);
2829
4625
  const hasDlqCheck = deadLetterConfig !== void 0;
2830
- const emitRouted = eventBus.hasHook("messageRouted" /* MessageRouted */);
4626
+ const reportHandlerCompleted = (msg, startedAt, status) => {
4627
+ if (!eventBus.hasHook("handlerCompleted" /* HandlerCompleted */)) return;
4628
+ const declared = patternRegistry.resolveDeclared(msg.subject);
4629
+ const pattern = declared?.pattern ?? msg.subject;
4630
+ const declaredKind = declared?.kind ?? kind;
4631
+ const durationMs = performance.now() - startedAt;
4632
+ eventBus.emit("handlerCompleted" /* HandlerCompleted */, pattern, declaredKind, durationMs, status);
4633
+ };
2831
4634
  const isDeadLetter = (msg) => {
2832
4635
  if (!hasDlqCheck) return false;
2833
4636
  const maxDeliver = deadLetterConfig.maxDeliverByStream?.get(msg.info.stream);
@@ -2853,7 +4656,7 @@ var EventRouter = class {
2853
4656
  const handler = patternRegistry.getHandler(subject);
2854
4657
  if (!handler) {
2855
4658
  msg.term(`No handler for event: ${subject}`);
2856
- logger.error(`No handler for subject: ${subject}`);
4659
+ logger5.error(`No handler for subject: ${subject}`);
2857
4660
  return null;
2858
4661
  }
2859
4662
  let data;
@@ -2861,47 +4664,79 @@ var EventRouter = class {
2861
4664
  data = codec.decode(msg.data);
2862
4665
  } catch (err) {
2863
4666
  msg.term("Decode error");
2864
- logger.error(`Decode error for ${subject}:`, err);
4667
+ logger5.error(`Decode error for ${subject}:`, err);
2865
4668
  return null;
2866
4669
  }
2867
- if (emitRouted) eventBus.emitMessageRouted(subject, "event" /* Event */);
4670
+ eventBus.emitMessageRouted(subject, "event" /* Event */);
2868
4671
  return { handler, data };
2869
4672
  } catch (err) {
2870
- logger.error(`Unexpected error in ${kind} event router`, err);
4673
+ logger5.error(`Unexpected error in ${kind} event router`, err);
2871
4674
  try {
2872
4675
  msg.term("Unexpected router error");
2873
4676
  } catch (termErr) {
2874
- logger.error(`Failed to terminate message ${subject}:`, termErr);
4677
+ logger5.error(`Failed to terminate message ${subject}:`, termErr);
2875
4678
  }
2876
4679
  return null;
2877
4680
  }
2878
4681
  };
4682
+ const statusForContext = (ctx) => {
4683
+ if (ctx.shouldTerminate) return "terminated";
4684
+ if (ctx.shouldRetry) return "retried";
4685
+ return "success";
4686
+ };
2879
4687
  const handleSafe = (msg) => {
2880
4688
  const resolved = resolveEvent(msg);
2881
4689
  if (resolved === null) return void 0;
2882
4690
  const { handler, data } = resolved;
2883
4691
  const ctx = new RpcContext([msg]);
2884
4692
  const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
4693
+ const startedAt = performance.now();
2885
4694
  let pending;
2886
4695
  try {
2887
- pending = unwrapResult(handler(data, ctx));
4696
+ pending = withConsumeSpan(
4697
+ {
4698
+ subject: msg.subject,
4699
+ msg,
4700
+ info: msg.info,
4701
+ kind: spanKind,
4702
+ payloadBytes: msg.data.length,
4703
+ handlerMetadata: { pattern: msg.subject },
4704
+ serviceName,
4705
+ endpoint: serverEndpoint
4706
+ },
4707
+ otel,
4708
+ () => unwrapResult(handler(data, ctx))
4709
+ );
2888
4710
  } catch (err) {
2889
- logger.error(`Event handler error (${msg.subject}) in ${kind} router:`, err);
2890
- if (stopAckExtension !== null) stopAckExtension();
2891
- return settleFailure(msg, data, err);
4711
+ eventBus.emit(
4712
+ "error" /* Error */,
4713
+ err instanceof Error ? err : new Error(String(err)),
4714
+ `${kind}-handler:${msg.subject}`
4715
+ );
4716
+ reportHandlerCompleted(msg, startedAt, "error");
4717
+ return settleFailure(msg, data, err).finally(() => {
4718
+ if (stopAckExtension !== null) stopAckExtension();
4719
+ });
2892
4720
  }
2893
- if (!isPromiseLike(pending)) {
4721
+ if (!isPromiseLike2(pending)) {
2894
4722
  settleSuccess(msg, ctx);
4723
+ reportHandlerCompleted(msg, startedAt, statusForContext(ctx));
2895
4724
  if (stopAckExtension !== null) stopAckExtension();
2896
4725
  return void 0;
2897
4726
  }
2898
4727
  return pending.then(
2899
4728
  () => {
2900
4729
  settleSuccess(msg, ctx);
4730
+ reportHandlerCompleted(msg, startedAt, statusForContext(ctx));
2901
4731
  if (stopAckExtension !== null) stopAckExtension();
2902
4732
  },
2903
4733
  async (err) => {
2904
- logger.error(`Event handler error (${msg.subject}) in ${kind} router:`, err);
4734
+ eventBus.emit(
4735
+ "error" /* Error */,
4736
+ err instanceof Error ? err : new Error(String(err)),
4737
+ `${kind}-handler:${msg.subject}`
4738
+ );
4739
+ reportHandlerCompleted(msg, startedAt, "error");
2905
4740
  try {
2906
4741
  await settleFailure(msg, data, err);
2907
4742
  } finally {
@@ -2917,42 +4752,65 @@ var EventRouter = class {
2917
4752
  try {
2918
4753
  handler = patternRegistry.getHandler(subject);
2919
4754
  if (!handler) {
2920
- logger.error(`No handler for subject: ${subject}`);
4755
+ logger5.error(`No handler for subject: ${subject}`);
2921
4756
  return void 0;
2922
4757
  }
2923
4758
  try {
2924
4759
  data = codec.decode(msg.data);
2925
4760
  } catch (err) {
2926
- logger.error(`Decode error for ${subject}:`, err);
4761
+ logger5.error(`Decode error for ${subject}:`, err);
2927
4762
  return void 0;
2928
4763
  }
2929
- if (emitRouted) eventBus.emitMessageRouted(subject, "event" /* Event */);
4764
+ eventBus.emitMessageRouted(subject, "event" /* Event */);
2930
4765
  } catch (err) {
2931
- logger.error(`Ordered handler error (${subject}):`, err);
4766
+ logger5.error(`Ordered handler error (${subject}):`, err);
2932
4767
  return void 0;
2933
4768
  }
2934
4769
  const ctx = new RpcContext([msg]);
2935
4770
  const warnIfSettlementAttempted = () => {
2936
4771
  if (ctx.shouldRetry || ctx.shouldTerminate) {
2937
- logger.warn(
4772
+ logger5.warn(
2938
4773
  `retry()/terminate() ignored for ordered message ${subject} \u2014 ordered consumers auto-acknowledge`
2939
4774
  );
2940
4775
  }
2941
4776
  };
4777
+ const startedAt = performance.now();
2942
4778
  let pending;
2943
4779
  try {
2944
- pending = unwrapResult(handler(data, ctx));
4780
+ pending = withConsumeSpan(
4781
+ {
4782
+ subject: msg.subject,
4783
+ msg,
4784
+ info: msg.info,
4785
+ kind: spanKind,
4786
+ payloadBytes: msg.data.length,
4787
+ handlerMetadata: { pattern: msg.subject },
4788
+ serviceName,
4789
+ endpoint: serverEndpoint
4790
+ },
4791
+ otel,
4792
+ () => unwrapResult(handler(data, ctx))
4793
+ );
2945
4794
  } catch (err) {
2946
- logger.error(`Ordered handler error (${subject}):`, err);
4795
+ logger5.error(`Ordered handler error (${subject}):`, err);
4796
+ reportHandlerCompleted(msg, startedAt, "error");
2947
4797
  return void 0;
2948
4798
  }
2949
- if (!isPromiseLike(pending)) {
4799
+ if (!isPromiseLike2(pending)) {
2950
4800
  warnIfSettlementAttempted();
4801
+ reportHandlerCompleted(msg, startedAt, "success");
2951
4802
  return void 0;
2952
4803
  }
2953
- return pending.then(warnIfSettlementAttempted, (err) => {
2954
- logger.error(`Ordered handler error (${subject}):`, err);
2955
- });
4804
+ return pending.then(
4805
+ () => {
4806
+ warnIfSettlementAttempted();
4807
+ reportHandlerCompleted(msg, startedAt, "success");
4808
+ },
4809
+ (err) => {
4810
+ logger5.error(`Ordered handler error (${subject}):`, err);
4811
+ reportHandlerCompleted(msg, startedAt, "error");
4812
+ }
4813
+ );
2956
4814
  };
2957
4815
  const route = isOrdered ? handleOrderedSafe : handleSafe;
2958
4816
  const maxActive = isOrdered ? 1 : concurrency ?? Number.POSITIVE_INFINITY;
@@ -2984,7 +4842,7 @@ var EventRouter = class {
2984
4842
  backlog.push(msg);
2985
4843
  if (!backlogWarned && backlog.length >= backlogWarnThreshold) {
2986
4844
  backlogWarned = true;
2987
- logger.warn(
4845
+ logger5.warn(
2988
4846
  `${kind} backlog reached ${backlog.length} messages \u2014 consumer may be falling behind`
2989
4847
  );
2990
4848
  }
@@ -3000,7 +4858,7 @@ var EventRouter = class {
3000
4858
  }
3001
4859
  },
3002
4860
  error: (err) => {
3003
- logger.error(`Stream error in ${kind} router`, err);
4861
+ logger5.error(`Stream error in ${kind} router`, err);
3004
4862
  }
3005
4863
  });
3006
4864
  this.subscriptions.push(subscription);
@@ -3015,14 +4873,11 @@ var EventRouter = class {
3015
4873
  if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.ackExtension;
3016
4874
  return void 0;
3017
4875
  }
3018
- /** Handle a dead letter: invoke callback, then term or nak based on result. */
3019
4876
  /**
3020
- * Fallback execution for a dead letter when DLQ is disabled, or when
3021
- * publishing to the DLQ stream fails (due to network or NATS errors).
3022
- *
3023
- * Triggers the user-provided `onDeadLetter` hook for logging/alerting.
3024
- * On success, terminates the message. On error, leaves it unacknowledged (nak)
3025
- * so NATS can retry the delivery on the next cycle.
4877
+ * Last-resort path for a dead letter: invoke `onDeadLetter`, then `term` on
4878
+ * success or `nak` on hook failure so NATS retries on the next delivery
4879
+ * cycle. Used when DLQ stream isn't configured, or when publishing to it
4880
+ * failed and we still have to surface the message somewhere observable.
3026
4881
  */
3027
4882
  async fallbackToOnDeadLetterCallback(info, msg) {
3028
4883
  if (!this.deadLetterConfig) {
@@ -3114,20 +4969,37 @@ var EventRouter = class {
3114
4969
  streamSequence: msg.info.streamSequence,
3115
4970
  timestamp: new Date(msg.info.timestampNanos / 1e6).toISOString()
3116
4971
  };
3117
- this.eventBus.emit("deadLetter" /* DeadLetter */, info);
3118
- if (!this.options?.dlq) {
3119
- await this.fallbackToOnDeadLetterCallback(info, msg);
3120
- } else {
3121
- await this.publishToDlq(msg, info, error);
3122
- }
4972
+ await withDeadLetterSpan(
4973
+ {
4974
+ msg,
4975
+ // Pattern resolution mirrors event-routing: when a registered
4976
+ // pattern matches, surface it on the DLQ span so APM can filter
4977
+ // dead letters by handler without parsing the subject. Falls back
4978
+ // to the subject itself when no glob handler is in play.
4979
+ pattern: this.patternRegistry.getHandler(msg.subject) ? msg.subject : void 0,
4980
+ finalDeliveryCount: msg.info.deliveryCount,
4981
+ reason: error instanceof Error ? error.message : String(error),
4982
+ serviceName: this.serviceName,
4983
+ endpoint: this.serverEndpoint
4984
+ },
4985
+ this.otel,
4986
+ async () => {
4987
+ this.eventBus.emit("deadLetter" /* DeadLetter */, info);
4988
+ if (!this.options?.dlq) {
4989
+ await this.fallbackToOnDeadLetterCallback(info, msg);
4990
+ } else {
4991
+ await this.publishToDlq(msg, info, error);
4992
+ }
4993
+ }
4994
+ );
3123
4995
  }
3124
4996
  };
3125
4997
 
3126
4998
  // src/server/routing/rpc.router.ts
3127
- var import_common12 = require("@nestjs/common");
4999
+ var import_common19 = require("@nestjs/common");
3128
5000
  var import_transport_node5 = require("@nats-io/transport-node");
3129
5001
  var RpcRouter = class {
3130
- constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap) {
5002
+ constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap, options) {
3131
5003
  this.messageProvider = messageProvider;
3132
5004
  this.patternRegistry = patternRegistry;
3133
5005
  this.connection = connection;
@@ -3137,13 +5009,26 @@ var RpcRouter = class {
3137
5009
  this.ackWaitMap = ackWaitMap;
3138
5010
  this.timeout = rpcOptions?.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT;
3139
5011
  this.concurrency = rpcOptions?.concurrency;
5012
+ if (options) {
5013
+ const derived = deriveOtelAttrs(options);
5014
+ this.otel = derived.otel;
5015
+ this.serviceName = derived.serviceName;
5016
+ this.serverEndpoint = derived.serverEndpoint;
5017
+ } else {
5018
+ this.otel = resolveOtelOptions({ enabled: false });
5019
+ this.serviceName = "";
5020
+ this.serverEndpoint = null;
5021
+ }
3140
5022
  }
3141
- logger = new import_common12.Logger("Jetstream:RpcRouter");
5023
+ logger = new import_common19.Logger("Jetstream:RpcRouter");
3142
5024
  timeout;
3143
5025
  concurrency;
3144
5026
  resolvedAckExtensionInterval;
3145
5027
  subscription = null;
3146
5028
  cachedNc = null;
5029
+ otel;
5030
+ serviceName;
5031
+ serverEndpoint;
3147
5032
  /** Lazily resolve the ack extension interval (needs ackWaitMap populated at runtime). */
3148
5033
  get ackExtensionInterval() {
3149
5034
  if (this.resolvedAckExtensionInterval !== void 0) return this.resolvedAckExtensionInterval;
@@ -3160,21 +5045,37 @@ var RpcRouter = class {
3160
5045
  const patternRegistry = this.patternRegistry;
3161
5046
  const codec = this.codec;
3162
5047
  const eventBus = this.eventBus;
3163
- const logger = this.logger;
5048
+ const logger5 = this.logger;
3164
5049
  const timeout = this.timeout;
3165
5050
  const ackExtensionInterval = this.ackExtensionInterval;
3166
5051
  const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
3167
5052
  const maxActive = this.concurrency ?? Number.POSITIVE_INFINITY;
5053
+ const otel = this.otel;
5054
+ const serviceName = this.serviceName;
5055
+ const serverEndpoint = this.serverEndpoint;
3168
5056
  const emitRpcTimeout = (subject, correlationId) => {
3169
5057
  eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
3170
5058
  };
5059
+ const reportHandlerCompleted = (msg, startedAt, status) => {
5060
+ if (!eventBus.hasHook("handlerCompleted" /* HandlerCompleted */)) return;
5061
+ const declared = patternRegistry.resolveDeclared(msg.subject);
5062
+ const pattern = declared?.pattern ?? msg.subject;
5063
+ const declaredKind = declared?.kind ?? "cmd" /* Command */;
5064
+ eventBus.emit(
5065
+ "handlerCompleted" /* HandlerCompleted */,
5066
+ pattern,
5067
+ declaredKind,
5068
+ performance.now() - startedAt,
5069
+ status
5070
+ );
5071
+ };
3171
5072
  const publishReply = (replyTo, correlationId, payload) => {
3172
5073
  try {
3173
5074
  const hdrs = (0, import_transport_node5.headers)();
3174
5075
  hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
3175
5076
  nc.publish(replyTo, codec.encode(payload), { headers: hdrs });
3176
5077
  } catch (publishErr) {
3177
- logger.error(`Failed to publish RPC response`, publishErr);
5078
+ logger5.error(`Failed to publish RPC response`, publishErr);
3178
5079
  }
3179
5080
  };
3180
5081
  const publishErrorReply = (replyTo, correlationId, subject, err) => {
@@ -3184,7 +5085,7 @@ var RpcRouter = class {
3184
5085
  hdrs.set("x-error" /* Error */, "true");
3185
5086
  nc.publish(replyTo, codec.encode(serializeError(err)), { headers: hdrs });
3186
5087
  } catch (encodeErr) {
3187
- logger.error(`Failed to encode RPC error for ${subject}`, encodeErr);
5088
+ logger5.error(`Failed to encode RPC error for ${subject}`, encodeErr);
3188
5089
  }
3189
5090
  };
3190
5091
  const resolveCommand = (msg) => {
@@ -3193,7 +5094,7 @@ var RpcRouter = class {
3193
5094
  const handler = patternRegistry.getHandler(subject);
3194
5095
  if (!handler) {
3195
5096
  msg.term(`No handler for RPC: ${subject}`);
3196
- logger.error(`No handler for RPC subject: ${subject}`);
5097
+ logger5.error(`No handler for RPC subject: ${subject}`);
3197
5098
  return null;
3198
5099
  }
3199
5100
  const msgHeaders = msg.headers;
@@ -3201,7 +5102,7 @@ var RpcRouter = class {
3201
5102
  const correlationId = msgHeaders?.get("x-correlation-id" /* CorrelationId */);
3202
5103
  if (!replyTo || !correlationId) {
3203
5104
  msg.term("Missing required headers (reply-to or correlation-id)");
3204
- logger.error(`Missing headers for RPC: ${subject}`);
5105
+ logger5.error(`Missing headers for RPC: ${subject}`);
3205
5106
  return null;
3206
5107
  }
3207
5108
  let data;
@@ -3209,17 +5110,17 @@ var RpcRouter = class {
3209
5110
  data = codec.decode(msg.data);
3210
5111
  } catch (err) {
3211
5112
  msg.term("Decode error");
3212
- logger.error(`Decode error for RPC ${subject}:`, err);
5113
+ logger5.error(`Decode error for RPC ${subject}:`, err);
3213
5114
  return null;
3214
5115
  }
3215
5116
  eventBus.emitMessageRouted(subject, "rpc" /* Rpc */);
3216
5117
  return { handler, data, replyTo, correlationId };
3217
5118
  } catch (err) {
3218
- logger.error("Unexpected error in RPC router", err);
5119
+ logger5.error("Unexpected error in RPC router", err);
3219
5120
  try {
3220
5121
  msg.term("Unexpected router error");
3221
5122
  } catch (termErr) {
3222
- logger.error(`Failed to terminate RPC message ${subject}:`, termErr);
5123
+ logger5.error(`Failed to terminate RPC message ${subject}:`, termErr);
3223
5124
  }
3224
5125
  return null;
3225
5126
  }
@@ -3231,20 +5132,45 @@ var RpcRouter = class {
3231
5132
  const subject = msg.subject;
3232
5133
  const ctx = new RpcContext([msg]);
3233
5134
  const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
5135
+ const startedAt = performance.now();
5136
+ const reportHandlerError = (err) => {
5137
+ eventBus.emit(
5138
+ "error" /* Error */,
5139
+ err instanceof Error ? err : new Error(String(err)),
5140
+ `rpc-handler:${subject}`
5141
+ );
5142
+ publishErrorReply(replyTo, correlationId, subject, err);
5143
+ msg.term(`Handler error: ${subject}`);
5144
+ };
5145
+ const abortController = new AbortController();
3234
5146
  let pending;
3235
5147
  try {
3236
- pending = unwrapResult(handler(data, ctx));
5148
+ pending = withConsumeSpan(
5149
+ {
5150
+ subject,
5151
+ msg,
5152
+ info: msg.info,
5153
+ kind: "rpc" /* Rpc */,
5154
+ payloadBytes: msg.data.length,
5155
+ handlerMetadata: { pattern: subject },
5156
+ serviceName,
5157
+ endpoint: serverEndpoint
5158
+ },
5159
+ otel,
5160
+ () => unwrapResult(handler(data, ctx)),
5161
+ { signal: abortController.signal, timeoutLabel: "rpc.handler.timeout" }
5162
+ );
3237
5163
  } catch (err) {
3238
5164
  if (stopAckExtension !== null) stopAckExtension();
3239
- logger.error(`RPC handler error (${subject}):`, err);
3240
- publishErrorReply(replyTo, correlationId, subject, err);
3241
- msg.term(`Handler error: ${subject}`);
5165
+ reportHandlerError(err);
5166
+ reportHandlerCompleted(msg, startedAt, "error");
3242
5167
  return void 0;
3243
5168
  }
3244
- if (!isPromiseLike(pending)) {
5169
+ if (!isPromiseLike2(pending)) {
3245
5170
  if (stopAckExtension !== null) stopAckExtension();
3246
5171
  msg.ack();
3247
5172
  publishReply(replyTo, correlationId, pending);
5173
+ reportHandlerCompleted(msg, startedAt, "success");
3248
5174
  return void 0;
3249
5175
  }
3250
5176
  let settled = false;
@@ -3252,9 +5178,10 @@ var RpcRouter = class {
3252
5178
  if (settled) return;
3253
5179
  settled = true;
3254
5180
  if (stopAckExtension !== null) stopAckExtension();
3255
- logger.error(`RPC timeout (${timeout}ms): ${subject}`);
5181
+ abortController.abort();
3256
5182
  emitRpcTimeout(subject, correlationId);
3257
5183
  msg.term("Handler timeout");
5184
+ reportHandlerCompleted(msg, startedAt, "terminated");
3258
5185
  }, timeout);
3259
5186
  return pending.then(
3260
5187
  (result) => {
@@ -3264,15 +5191,15 @@ var RpcRouter = class {
3264
5191
  if (stopAckExtension !== null) stopAckExtension();
3265
5192
  msg.ack();
3266
5193
  publishReply(replyTo, correlationId, result);
5194
+ reportHandlerCompleted(msg, startedAt, "success");
3267
5195
  },
3268
5196
  (err) => {
3269
5197
  if (settled) return;
3270
5198
  settled = true;
3271
5199
  clearTimeout(timeoutId);
3272
5200
  if (stopAckExtension !== null) stopAckExtension();
3273
- logger.error(`RPC handler error (${subject}):`, err);
3274
- publishErrorReply(replyTo, correlationId, subject, err);
3275
- msg.term(`Handler error: ${subject}`);
5201
+ reportHandlerError(err);
5202
+ reportHandlerCompleted(msg, startedAt, "error");
3276
5203
  }
3277
5204
  );
3278
5205
  };
@@ -3304,7 +5231,7 @@ var RpcRouter = class {
3304
5231
  backlog.push(msg);
3305
5232
  if (!backlogWarned && backlog.length >= backlogWarnThreshold) {
3306
5233
  backlogWarned = true;
3307
- logger.warn(
5234
+ logger5.warn(
3308
5235
  `RPC backlog reached ${backlog.length} messages \u2014 consumer may be falling behind`
3309
5236
  );
3310
5237
  }
@@ -3320,7 +5247,7 @@ var RpcRouter = class {
3320
5247
  }
3321
5248
  },
3322
5249
  error: (err) => {
3323
- logger.error("Stream error in RPC router", err);
5250
+ logger5.error("Stream error in RPC router", err);
3324
5251
  }
3325
5252
  });
3326
5253
  }
@@ -3332,14 +5259,14 @@ var RpcRouter = class {
3332
5259
  };
3333
5260
 
3334
5261
  // src/shutdown/shutdown.manager.ts
3335
- var import_common13 = require("@nestjs/common");
5262
+ var import_common20 = require("@nestjs/common");
3336
5263
  var ShutdownManager = class {
3337
5264
  constructor(connection, eventBus, timeout) {
3338
5265
  this.connection = connection;
3339
5266
  this.eventBus = eventBus;
3340
5267
  this.timeout = timeout;
3341
5268
  }
3342
- logger = new import_common13.Logger("Jetstream:Shutdown");
5269
+ logger = new import_common20.Logger("Jetstream:Shutdown");
3343
5270
  shutdownPromise;
3344
5271
  /**
3345
5272
  * Execute the full shutdown sequence.
@@ -3379,9 +5306,6 @@ var JetstreamModule = class {
3379
5306
  this.shutdownManager = shutdownManager;
3380
5307
  this.strategy = strategy;
3381
5308
  }
3382
- // -------------------------------------------------------------------
3383
- // forRoot — global module registration
3384
- // -------------------------------------------------------------------
3385
5309
  /**
3386
5310
  * Register the JetStream transport globally.
3387
5311
  *
@@ -3396,21 +5320,20 @@ var JetstreamModule = class {
3396
5320
  return {
3397
5321
  module: JetstreamModule,
3398
5322
  global: true,
5323
+ imports: options.metrics ? [JetstreamMetricsModule.forFeature(options.metrics)] : [],
3399
5324
  providers,
3400
5325
  exports: [
3401
5326
  JETSTREAM_CONNECTION,
3402
5327
  JETSTREAM_CODEC,
3403
5328
  JETSTREAM_EVENT_BUS,
3404
5329
  JETSTREAM_OPTIONS,
5330
+ PatternRegistry,
3405
5331
  ShutdownManager,
3406
5332
  JetstreamStrategy,
3407
5333
  JetstreamHealthIndicator
3408
5334
  ]
3409
5335
  };
3410
5336
  }
3411
- // -------------------------------------------------------------------
3412
- // forRootAsync — async global module registration
3413
- // -------------------------------------------------------------------
3414
5337
  /**
3415
5338
  * Register the JetStream transport globally with async configuration.
3416
5339
  *
@@ -3423,25 +5346,24 @@ var JetstreamModule = class {
3423
5346
  static forRootAsync(asyncOptions) {
3424
5347
  const asyncProviders = this.createAsyncOptionsProvider(asyncOptions);
3425
5348
  const coreProviders = this.createCoreDependentProviders();
5349
+ const metricsImports = asyncOptions.metrics ? [JetstreamMetricsModule.forFeature(asyncOptions.metrics)] : [];
3426
5350
  return {
3427
5351
  module: JetstreamModule,
3428
5352
  global: true,
3429
- imports: asyncOptions.imports ?? [],
5353
+ imports: [...asyncOptions.imports ?? [], ...metricsImports],
3430
5354
  providers: [...asyncProviders, ...coreProviders],
3431
5355
  exports: [
3432
5356
  JETSTREAM_CONNECTION,
3433
5357
  JETSTREAM_CODEC,
3434
5358
  JETSTREAM_EVENT_BUS,
3435
5359
  JETSTREAM_OPTIONS,
5360
+ PatternRegistry,
3436
5361
  ShutdownManager,
3437
5362
  JetstreamStrategy,
3438
5363
  JetstreamHealthIndicator
3439
5364
  ]
3440
5365
  };
3441
5366
  }
3442
- // -------------------------------------------------------------------
3443
- // forFeature — per-module client registration
3444
- // -------------------------------------------------------------------
3445
5367
  /**
3446
5368
  * Register a lightweight client proxy for a target service.
3447
5369
  *
@@ -3467,9 +5389,6 @@ var JetstreamModule = class {
3467
5389
  exports: [clientToken]
3468
5390
  };
3469
5391
  }
3470
- // -------------------------------------------------------------------
3471
- // Provider factories
3472
- // -------------------------------------------------------------------
3473
5392
  static createCoreProviders(options) {
3474
5393
  return [
3475
5394
  {
@@ -3487,8 +5406,8 @@ var JetstreamModule = class {
3487
5406
  provide: JETSTREAM_EVENT_BUS,
3488
5407
  inject: [JETSTREAM_OPTIONS],
3489
5408
  useFactory: (options) => {
3490
- const logger = new import_common14.Logger("Jetstream:Module");
3491
- return new EventBus(logger, options.hooks);
5409
+ const logger5 = new import_common21.Logger("Jetstream:Module");
5410
+ return new EventBus(logger5, options.hooks);
3492
5411
  }
3493
5412
  },
3494
5413
  // Codec — global encode/decode
@@ -3527,10 +5446,8 @@ var JetstreamModule = class {
3527
5446
  );
3528
5447
  }
3529
5448
  },
3530
- // ---------------------------------------------------------------
3531
5449
  // Consumer infrastructure — only created when consumer !== false.
3532
5450
  // Providers return null when consumer is disabled (publisher-only mode).
3533
- // ---------------------------------------------------------------
3534
5451
  // PatternRegistry — subject-to-handler mapping
3535
5452
  {
3536
5453
  provide: PatternRegistry,
@@ -3566,8 +5483,14 @@ var JetstreamModule = class {
3566
5483
  // MessageProvider — pull-based message consumption
3567
5484
  {
3568
5485
  provide: MessageProvider,
3569
- inject: [JETSTREAM_OPTIONS, JETSTREAM_CONNECTION, JETSTREAM_EVENT_BUS, ConsumerProvider],
3570
- useFactory: (options, connection, eventBus, consumerProvider) => {
5486
+ inject: [
5487
+ JETSTREAM_OPTIONS,
5488
+ JETSTREAM_CONNECTION,
5489
+ JETSTREAM_EVENT_BUS,
5490
+ ConsumerProvider,
5491
+ StreamProvider
5492
+ ],
5493
+ useFactory: (options, connection, eventBus, consumerProvider, streamProvider) => {
3571
5494
  if (options.consumer === false) return null;
3572
5495
  const consumeOptionsMap = /* @__PURE__ */ new Map();
3573
5496
  if (options.events?.consume)
@@ -3577,10 +5500,22 @@ var JetstreamModule = class {
3577
5500
  if (options.rpc?.mode === "jetstream" && options.rpc.consume) {
3578
5501
  consumeOptionsMap.set("cmd" /* Command */, options.rpc.consume);
3579
5502
  }
3580
- const consumerRecoveryFn = consumerProvider ? async (kind) => {
3581
- const jsm = await connection.getJetStreamManager();
3582
- return consumerProvider.recoverConsumer(jsm, kind);
3583
- } : void 0;
5503
+ const derived = deriveOtelAttrs(options);
5504
+ const { otel, serverEndpoint: otelEndpoint, serviceName: otelServiceName } = derived;
5505
+ const consumerRecoveryFn = consumerProvider && streamProvider ? async (kind) => withSelfHealingSpan(
5506
+ otel,
5507
+ {
5508
+ serviceName: otelServiceName,
5509
+ endpoint: otelEndpoint,
5510
+ consumer: consumerProvider.getConsumerName(kind),
5511
+ stream: streamProvider.getStreamName(kind),
5512
+ reason: "consumer not found"
5513
+ },
5514
+ async () => {
5515
+ const jsm = await connection.getJetStreamManager();
5516
+ return consumerProvider.recoverConsumer(jsm, kind);
5517
+ }
5518
+ ) : void 0;
3584
5519
  return new MessageProvider(connection, eventBus, consumeOptionsMap, consumerRecoveryFn);
3585
5520
  }
3586
5521
  },
@@ -3651,7 +5586,8 @@ var JetstreamModule = class {
3651
5586
  codec,
3652
5587
  eventBus,
3653
5588
  rpcOptions,
3654
- ackWaitMap
5589
+ ackWaitMap,
5590
+ options
3655
5591
  );
3656
5592
  }
3657
5593
  },
@@ -3754,9 +5690,6 @@ var JetstreamModule = class {
3754
5690
  }
3755
5691
  ];
3756
5692
  }
3757
- // -------------------------------------------------------------------
3758
- // Lifecycle hooks
3759
- // -------------------------------------------------------------------
3760
5693
  /**
3761
5694
  * Gracefully shut down the transport on application termination.
3762
5695
  */
@@ -3767,15 +5700,16 @@ var JetstreamModule = class {
3767
5700
  }
3768
5701
  };
3769
5702
  JetstreamModule = __decorateClass([
3770
- (0, import_common14.Global)(),
3771
- (0, import_common14.Module)({}),
3772
- __decorateParam(0, (0, import_common14.Optional)()),
3773
- __decorateParam(0, (0, import_common14.Inject)(ShutdownManager)),
3774
- __decorateParam(1, (0, import_common14.Optional)()),
3775
- __decorateParam(1, (0, import_common14.Inject)(JetstreamStrategy))
5703
+ (0, import_common21.Global)(),
5704
+ (0, import_common21.Module)({}),
5705
+ __decorateParam(0, (0, import_common21.Optional)()),
5706
+ __decorateParam(0, (0, import_common21.Inject)(ShutdownManager)),
5707
+ __decorateParam(1, (0, import_common21.Optional)()),
5708
+ __decorateParam(1, (0, import_common21.Inject)(JetstreamStrategy))
3776
5709
  ], JetstreamModule);
3777
5710
  // Annotate the CommonJS export names for ESM import in node:
3778
5711
  0 && (module.exports = {
5712
+ ConsumeKind,
3779
5713
  DEFAULT_BROADCAST_CONSUMER_CONFIG,
3780
5714
  DEFAULT_BROADCAST_STREAM_CONFIG,
3781
5715
  DEFAULT_COMMAND_CONSUMER_CONFIG,
@@ -3791,6 +5725,7 @@ JetstreamModule = __decorateClass([
3791
5725
  DEFAULT_ORDERED_STREAM_CONFIG,
3792
5726
  DEFAULT_RPC_TIMEOUT,
3793
5727
  DEFAULT_SHUTDOWN_TIMEOUT,
5728
+ DEFAULT_TRACES,
3794
5729
  JETSTREAM_CODEC,
3795
5730
  JETSTREAM_CONNECTION,
3796
5731
  JETSTREAM_OPTIONS,
@@ -3802,15 +5737,18 @@ JetstreamModule = __decorateClass([
3802
5737
  JetstreamRecord,
3803
5738
  JetstreamRecordBuilder,
3804
5739
  JetstreamStrategy,
5740
+ JetstreamTrace,
3805
5741
  JsonCodec,
3806
5742
  MIN_METADATA_TTL,
3807
5743
  MessageKind,
3808
5744
  MsgpackCodec,
3809
5745
  NatsErrorCode,
3810
5746
  PatternPrefix,
5747
+ PublishKind,
3811
5748
  RESERVED_HEADERS,
3812
5749
  RpcContext,
3813
5750
  StreamKind,
5751
+ TRACER_NAME,
3814
5752
  TransportEvent,
3815
5753
  buildBroadcastSubject,
3816
5754
  buildSubject,