@horizon-republic/nestjs-jetstream 2.9.0 → 2.10.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.js CHANGED
@@ -14,19 +14,21 @@ var __decorateParam = (index, decorator) => (target, key) => decorator(target, k
14
14
  import {
15
15
  Global,
16
16
  Inject,
17
- Logger as Logger14,
17
+ Logger as Logger18,
18
18
  Module,
19
19
  Optional
20
20
  } from "@nestjs/common";
21
21
 
22
22
  // src/client/jetstream.client.ts
23
- import { Logger } from "@nestjs/common";
23
+ import { Logger as Logger5 } from "@nestjs/common";
24
24
  import { ClientProxy } from "@nestjs/microservices";
25
+ import { context as context6 } from "@opentelemetry/api";
26
+ import { nuid } from "@nats-io/nuid";
25
27
  import {
26
28
  createInbox,
27
- headers as natsHeaders
29
+ headers as natsHeaders,
30
+ TimeoutError
28
31
  } from "@nats-io/transport-node";
29
- import { nuid } from "@nats-io/nuid";
30
32
 
31
33
  // src/interfaces/hooks.interface.ts
32
34
  var MessageKind = /* @__PURE__ */ ((MessageKind2) => {
@@ -224,6 +226,920 @@ var PatternPrefix = /* @__PURE__ */ ((PatternPrefix2) => {
224
226
  var isJetStreamRpcMode = (rpc) => rpc?.mode === "jetstream";
225
227
  var isCoreRpcMode = (rpc) => !rpc || rpc.mode === "core";
226
228
 
229
+ // src/otel/constants.ts
230
+ var TRACER_NAME = "@horizon-republic/nestjs-jetstream";
231
+
232
+ // src/otel/attribute-keys.ts
233
+ var ATTR_MESSAGING_SYSTEM = "messaging.system";
234
+ var ATTR_MESSAGING_DESTINATION_NAME = "messaging.destination.name";
235
+ var ATTR_MESSAGING_DESTINATION_TEMPLATE = "messaging.destination.template";
236
+ var ATTR_MESSAGING_CLIENT_ID = "messaging.client.id";
237
+ var ATTR_MESSAGING_OPERATION_NAME = "messaging.operation.name";
238
+ var ATTR_MESSAGING_OPERATION_TYPE = "messaging.operation.type";
239
+ var ATTR_MESSAGING_MESSAGE_BODY_SIZE = "messaging.message.body.size";
240
+ var ATTR_MESSAGING_MESSAGE_ID = "messaging.message.id";
241
+ var ATTR_MESSAGING_MESSAGE_CONVERSATION_ID = "messaging.message.conversation_id";
242
+ var ATTR_MESSAGING_CONSUMER_GROUP_NAME = "messaging.consumer.group.name";
243
+ var ATTR_MESSAGING_HEADER_PREFIX = "messaging.header.";
244
+ var ATTR_MESSAGING_NATS_STREAM_NAME = "messaging.nats.stream.name";
245
+ var ATTR_MESSAGING_NATS_STREAM_SEQUENCE = "messaging.nats.message.stream_sequence";
246
+ var ATTR_MESSAGING_NATS_CONSUMER_SEQUENCE = "messaging.nats.message.consumer_sequence";
247
+ var ATTR_MESSAGING_NATS_DELIVERY_COUNT = "messaging.nats.message.delivery_count";
248
+ var ATTR_MESSAGING_NATS_BODY = "messaging.nats.message.body";
249
+ var ATTR_MESSAGING_NATS_BODY_TRUNCATED = "messaging.nats.message.body.truncated";
250
+ var ATTR_SERVER_ADDRESS = "server.address";
251
+ var ATTR_SERVER_PORT = "server.port";
252
+ var ATTR_JETSTREAM_SERVICE_NAME = "jetstream.service.name";
253
+ var ATTR_JETSTREAM_KIND = "jetstream.kind";
254
+ var ATTR_JETSTREAM_RPC_REPLY_HAS_ERROR = "jetstream.rpc.reply.has_error";
255
+ var ATTR_JETSTREAM_RPC_REPLY_ERROR_CODE = "jetstream.rpc.reply.error.code";
256
+ var ATTR_JETSTREAM_PROVISIONING_ENTITY = "jetstream.provisioning.entity";
257
+ var ATTR_JETSTREAM_PROVISIONING_ACTION = "jetstream.provisioning.action";
258
+ var ATTR_JETSTREAM_PROVISIONING_NAME = "jetstream.provisioning.name";
259
+ var ATTR_JETSTREAM_SELF_HEALING_REASON = "jetstream.self_healing.reason";
260
+ var ATTR_JETSTREAM_MIGRATION_REASON = "jetstream.migration.reason";
261
+ var ATTR_JETSTREAM_DEAD_LETTER_REASON = "jetstream.dead_letter.reason";
262
+ var ATTR_JETSTREAM_SCHEDULE_TARGET = "jetstream.schedule.target";
263
+ var ATTR_NATS_CONNECTION_SERVER = "nats.connection.server";
264
+ var NATS_MSG_ID_HEADER = "Nats-Msg-Id";
265
+ var HOOK_PUBLISH = "publishHook";
266
+ var HOOK_CONSUME = "consumeHook";
267
+ var HOOK_RESPONSE = "responseHook";
268
+ var SPAN_NAME_PUBLISH = "publish";
269
+ var SPAN_NAME_PROCESS = "process";
270
+ var SPAN_NAME_SEND = "send";
271
+ var SPAN_NAME_DEAD_LETTER = "dead_letter";
272
+ var SPAN_NAME_NATS_CONNECTION = "nats.connection";
273
+ var SPAN_NAME_JETSTREAM_SHUTDOWN = "jetstream.shutdown";
274
+ var SPAN_NAME_JETSTREAM_SELF_HEALING = "jetstream.self_healing";
275
+ var SPAN_NAME_JETSTREAM_MIGRATION = "jetstream.migration";
276
+ var SPAN_NAME_JETSTREAM_PROVISIONING_PREFIX = "jetstream.provisioning.";
277
+ var EVENT_CONNECTION_DISCONNECTED = "connection.disconnected";
278
+ var EVENT_CONNECTION_RECONNECTED = "connection.reconnected";
279
+ var messagingHeaderAttr = (headerName) => `${ATTR_MESSAGING_HEADER_PREFIX}${headerName.toLowerCase()}`;
280
+
281
+ // src/otel/trace-kinds.ts
282
+ var JetstreamTrace = /* @__PURE__ */ ((JetstreamTrace2) => {
283
+ JetstreamTrace2["Publish"] = "publish";
284
+ JetstreamTrace2["Consume"] = "consume";
285
+ JetstreamTrace2["RpcClientSend"] = "rpc.client.send";
286
+ JetstreamTrace2["DeadLetter"] = "dead_letter";
287
+ JetstreamTrace2["ConnectionLifecycle"] = "connection.lifecycle";
288
+ JetstreamTrace2["SelfHealing"] = "self_healing";
289
+ JetstreamTrace2["Provisioning"] = "provisioning";
290
+ JetstreamTrace2["Migration"] = "migration";
291
+ JetstreamTrace2["Shutdown"] = "shutdown";
292
+ return JetstreamTrace2;
293
+ })(JetstreamTrace || {});
294
+ var DEFAULT_TRACES = [
295
+ "publish" /* Publish */,
296
+ "consume" /* Consume */,
297
+ "rpc.client.send" /* RpcClientSend */,
298
+ "dead_letter" /* DeadLetter */
299
+ ];
300
+
301
+ // src/otel/capture.ts
302
+ var NEGATION_PREFIX = "!";
303
+ var REGEX_SPECIAL_RE = /[.+?^${}()|[\]\\*]/gu;
304
+ var escapeForRegex = (input) => input.replace(REGEX_SPECIAL_RE, "\\$&");
305
+ var globToRegex = (glob) => {
306
+ const escaped = escapeForRegex(glob.toLowerCase()).replace(/\\\*/gu, ".*");
307
+ return new RegExp(`^${escaped}$`, "u");
308
+ };
309
+ var compileHeaderAllowlist = (allowlist) => {
310
+ if (allowlist === true) return () => true;
311
+ if (allowlist === false) return () => false;
312
+ if (allowlist.length === 0) return () => false;
313
+ const includes = [];
314
+ const excludes = [];
315
+ for (const pattern of allowlist) {
316
+ const isExclude = pattern.startsWith(NEGATION_PREFIX);
317
+ const body = isExclude ? pattern.slice(NEGATION_PREFIX.length) : pattern;
318
+ const regex = globToRegex(body);
319
+ if (isExclude) excludes.push(regex);
320
+ else includes.push(regex);
321
+ }
322
+ return (name) => {
323
+ const lower = name.toLowerCase();
324
+ if (!includes.some((re) => re.test(lower))) return false;
325
+ if (excludes.some((re) => re.test(lower))) return false;
326
+ return true;
327
+ };
328
+ };
329
+ var subjectMatcherCache = /* @__PURE__ */ new WeakMap();
330
+ var compileSubjectAllowlist = (allowlist) => {
331
+ if (!allowlist || allowlist.length === 0) return () => true;
332
+ const cached = subjectMatcherCache.get(allowlist);
333
+ if (cached) return cached;
334
+ const regexes = allowlist.map(globToRegex);
335
+ const matcher = (subject) => {
336
+ const lower = subject.toLowerCase();
337
+ return regexes.some((re) => re.test(lower));
338
+ };
339
+ subjectMatcherCache.set(allowlist, matcher);
340
+ return matcher;
341
+ };
342
+ var subjectMatchesAllowlist = (subject, allowlist) => compileSubjectAllowlist(allowlist)(subject);
343
+ var HEADER_DENYLIST = /* @__PURE__ */ new Set([
344
+ "traceparent",
345
+ "tracestate",
346
+ "baggage",
347
+ "sentry-trace",
348
+ "b3",
349
+ "x-b3-traceid",
350
+ "x-b3-spanid",
351
+ "x-b3-parentspanid",
352
+ "x-b3-sampled",
353
+ "x-b3-flags",
354
+ "uber-trace-id",
355
+ "x-correlation-id",
356
+ "x-reply-to",
357
+ "x-error",
358
+ "x-subject",
359
+ "x-caller-name"
360
+ ]);
361
+ var isNatsServerHeader = (lower) => lower.startsWith("nats-");
362
+ var captureMatchingHeaders = (headers2, matcher) => {
363
+ if (!headers2) return {};
364
+ const out = {};
365
+ for (const key of headers2.keys()) {
366
+ const lower = key.toLowerCase();
367
+ if (HEADER_DENYLIST.has(lower)) continue;
368
+ if (isNatsServerHeader(lower)) continue;
369
+ if (!matcher(key)) continue;
370
+ const value = headers2.get(key);
371
+ if (value === "") continue;
372
+ out[messagingHeaderAttr(lower)] = value;
373
+ }
374
+ return out;
375
+ };
376
+ var tryUtf8Decode = (bytes) => {
377
+ try {
378
+ return new TextDecoder("utf-8", { fatal: false }).decode(bytes);
379
+ } catch {
380
+ return Buffer.from(bytes).toString("base64");
381
+ }
382
+ };
383
+ var captureBodyAttribute = (subject, payload, capture) => {
384
+ if (capture === false) return {};
385
+ if (payload.byteLength === 0) return {};
386
+ if (!subjectMatchesAllowlist(subject, capture.subjectAllowlist)) return {};
387
+ const { maxBytes } = capture;
388
+ const truncated = payload.byteLength > maxBytes;
389
+ const slice = truncated ? payload.subarray(0, maxBytes) : payload;
390
+ const decoded = tryUtf8Decode(slice);
391
+ const out = { [ATTR_MESSAGING_NATS_BODY]: decoded };
392
+ if (truncated) out[ATTR_MESSAGING_NATS_BODY_TRUNCATED] = true;
393
+ return out;
394
+ };
395
+
396
+ // src/otel/config.ts
397
+ var PublishKind = /* @__PURE__ */ ((PublishKind2) => {
398
+ PublishKind2["Event"] = "event";
399
+ PublishKind2["RpcRequest"] = "rpc.request";
400
+ PublishKind2["Broadcast"] = "broadcast";
401
+ PublishKind2["Ordered"] = "ordered";
402
+ return PublishKind2;
403
+ })(PublishKind || {});
404
+ var ConsumeKind = /* @__PURE__ */ ((ConsumeKind2) => {
405
+ ConsumeKind2["Event"] = "event";
406
+ ConsumeKind2["Rpc"] = "rpc";
407
+ ConsumeKind2["Broadcast"] = "broadcast";
408
+ ConsumeKind2["Ordered"] = "ordered";
409
+ return ConsumeKind2;
410
+ })(ConsumeKind || {});
411
+ var DEFAULT_CAPTURE_HEADERS = ["x-request-id"];
412
+ var DEFAULT_CAPTURE_BODY_MAX_BYTES = 4096;
413
+ var NESTJS_BARE_ERROR_MESSAGE = "Internal server error";
414
+ var isNestjsBareErrorSentinel = (obj) => obj.status === "error" && obj.message === NESTJS_BARE_ERROR_MESSAGE;
415
+ var defaultErrorClassifier = (err) => {
416
+ if (err === null || typeof err !== "object") return "unexpected";
417
+ if (!(err instanceof Error)) {
418
+ if (isNestjsBareErrorSentinel(err)) return "unexpected";
419
+ return "expected";
420
+ }
421
+ let proto = err;
422
+ while (proto) {
423
+ const name = proto.constructor?.name;
424
+ if (name === "RpcException" || name === "HttpException") return "expected";
425
+ proto = Object.getPrototypeOf(proto);
426
+ }
427
+ return "unexpected";
428
+ };
429
+ var expandTracesOption = (option) => {
430
+ if (option === void 0 || option === "default") return new Set(DEFAULT_TRACES);
431
+ if (option === "all") return new Set(Object.values(JetstreamTrace));
432
+ if (option === "none") return /* @__PURE__ */ new Set();
433
+ return new Set(option);
434
+ };
435
+ var compileHeaderMatcher = (option) => compileHeaderAllowlist(option ?? DEFAULT_CAPTURE_HEADERS);
436
+ var resolveCaptureBody = (option) => {
437
+ if (option === void 0 || option === false) return false;
438
+ if (option === true) return { maxBytes: DEFAULT_CAPTURE_BODY_MAX_BYTES };
439
+ return {
440
+ maxBytes: option.maxBytes ?? DEFAULT_CAPTURE_BODY_MAX_BYTES,
441
+ subjectAllowlist: option.subjectAllowlist
442
+ };
443
+ };
444
+ var resolveOtelOptions = (options = {}) => {
445
+ return {
446
+ enabled: options.enabled ?? true,
447
+ traces: expandTracesOption(options.traces),
448
+ captureHeaders: compileHeaderMatcher(options.captureHeaders),
449
+ captureBody: resolveCaptureBody(options.captureBody),
450
+ publishHook: options.publishHook,
451
+ consumeHook: options.consumeHook,
452
+ responseHook: options.responseHook,
453
+ shouldTracePublish: options.shouldTracePublish,
454
+ shouldTraceConsume: options.shouldTraceConsume,
455
+ errorClassifier: options.errorClassifier ?? defaultErrorClassifier
456
+ };
457
+ };
458
+
459
+ // src/otel/internal-utils.ts
460
+ import { Logger } from "@nestjs/common";
461
+ var logger = new Logger("Jetstream:Otel");
462
+ var safelyInvokeHook = (hookName, hook, ...args) => {
463
+ if (!hook) return;
464
+ const logHookFailure = (err) => {
465
+ const message = err instanceof Error ? err.message : String(err);
466
+ logger.debug(`OTel ${hookName} threw: ${message}`);
467
+ };
468
+ try {
469
+ const result = hook(...args);
470
+ if (result !== null && typeof result === "object" && "then" in result && typeof result.then === "function") {
471
+ result.then(void 0, logHookFailure);
472
+ }
473
+ } catch (err) {
474
+ logHookFailure(err);
475
+ }
476
+ };
477
+ var stripIpv6Brackets = (host) => host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
478
+ var parsePort = (portRaw) => {
479
+ if (!portRaw) return void 0;
480
+ const port = Number.parseInt(portRaw, 10);
481
+ return Number.isInteger(port) ? port : void 0;
482
+ };
483
+ var parseServerAddress = (servers) => {
484
+ const raw = servers[0];
485
+ if (!raw) return null;
486
+ if (raw.includes("://")) {
487
+ try {
488
+ const url = new URL(raw);
489
+ if (url.hostname.length === 0) return null;
490
+ const host2 = stripIpv6Brackets(url.hostname);
491
+ const port2 = parsePort(url.port || void 0);
492
+ return port2 === void 0 ? { host: host2 } : { host: host2, port: port2 };
493
+ } catch {
494
+ return null;
495
+ }
496
+ }
497
+ if (raw.startsWith("[")) {
498
+ const closeIdx = raw.indexOf("]");
499
+ if (closeIdx <= 0) return null;
500
+ const host2 = raw.slice(1, closeIdx);
501
+ const port2 = parsePort(raw.slice(closeIdx + 1).replace(/^:/u, ""));
502
+ return port2 === void 0 ? { host: host2 } : { host: host2, port: port2 };
503
+ }
504
+ const [host, portRaw] = raw.split(":");
505
+ if (!host) return null;
506
+ const port = parsePort(portRaw);
507
+ return port === void 0 ? { host } : { host, port };
508
+ };
509
+ var deriveOtelAttrs = (options) => ({
510
+ otel: resolveOtelOptions(options.otel),
511
+ serviceName: internalName(options.name),
512
+ serverEndpoint: parseServerAddress(options.servers)
513
+ });
514
+
515
+ // src/otel/propagator.ts
516
+ import {
517
+ propagation
518
+ } from "@opentelemetry/api";
519
+ var injectContext = (ctx, carrier, setter) => {
520
+ propagation.inject(ctx, carrier, setter);
521
+ };
522
+ var extractContext = (ctx, carrier, getter) => propagation.extract(ctx, carrier, getter);
523
+
524
+ // src/otel/tracer.ts
525
+ import { trace } from "@opentelemetry/api";
526
+ var PACKAGE_VERSION = true ? "2.10.0" : "0.0.0";
527
+ var getTracer = () => trace.getTracer(TRACER_NAME, PACKAGE_VERSION);
528
+
529
+ // src/otel/carrier.ts
530
+ var hdrsSetter = {
531
+ set: (headers2, key, value) => {
532
+ headers2.set(key, value);
533
+ }
534
+ };
535
+ var hdrsGetter = {
536
+ keys: (headers2) => headers2 ? headers2.keys() : [],
537
+ get: (headers2, key) => {
538
+ if (!headers2) return void 0;
539
+ const all = typeof headers2.values === "function" ? headers2.values(key) : void 0;
540
+ if (Array.isArray(all)) {
541
+ const nonEmpty = all.filter((value) => value !== "");
542
+ if (nonEmpty.length === 0) return void 0;
543
+ return nonEmpty.join(",");
544
+ }
545
+ const single = headers2.get(key);
546
+ return single === "" ? void 0 : single;
547
+ }
548
+ };
549
+
550
+ // src/otel/attributes.ts
551
+ var MESSAGING_SYSTEM = "nats";
552
+ var baseMessagingAttributes = (ctx) => {
553
+ const attrs = {
554
+ [ATTR_MESSAGING_SYSTEM]: MESSAGING_SYSTEM,
555
+ [ATTR_MESSAGING_DESTINATION_NAME]: ctx.subject,
556
+ [ATTR_MESSAGING_CLIENT_ID]: ctx.serviceName
557
+ };
558
+ if (ctx.serverAddress) attrs[ATTR_SERVER_ADDRESS] = ctx.serverAddress;
559
+ if (ctx.serverPort !== void 0) attrs[ATTR_SERVER_PORT] = ctx.serverPort;
560
+ if (ctx.pattern && ctx.pattern !== ctx.subject) {
561
+ attrs[ATTR_MESSAGING_DESTINATION_TEMPLATE] = ctx.pattern;
562
+ }
563
+ return attrs;
564
+ };
565
+ var jetstreamKindForPublish = (kind) => kind === "rpc.request" /* RpcRequest */ ? "rpc" /* Rpc */ : kind;
566
+ var buildPublishAttributes = (ctx) => {
567
+ const attrs = {
568
+ ...baseMessagingAttributes(ctx),
569
+ [ATTR_MESSAGING_OPERATION_NAME]: SPAN_NAME_PUBLISH,
570
+ [ATTR_MESSAGING_OPERATION_TYPE]: SPAN_NAME_SEND,
571
+ [ATTR_MESSAGING_MESSAGE_BODY_SIZE]: ctx.payloadBytes,
572
+ [ATTR_JETSTREAM_KIND]: jetstreamKindForPublish(ctx.kind)
573
+ };
574
+ if (ctx.messageId) attrs[ATTR_MESSAGING_MESSAGE_ID] = ctx.messageId;
575
+ if (ctx.correlationId) attrs[ATTR_MESSAGING_MESSAGE_CONVERSATION_ID] = ctx.correlationId;
576
+ return attrs;
577
+ };
578
+ var buildConsumeAttributes = (ctx) => {
579
+ const { msg, info, kind, payloadBytes } = ctx;
580
+ const attrs = {
581
+ ...baseMessagingAttributes(ctx),
582
+ [ATTR_MESSAGING_OPERATION_NAME]: SPAN_NAME_PROCESS,
583
+ [ATTR_MESSAGING_OPERATION_TYPE]: SPAN_NAME_PROCESS,
584
+ [ATTR_MESSAGING_MESSAGE_BODY_SIZE]: payloadBytes,
585
+ [ATTR_JETSTREAM_KIND]: kind
586
+ };
587
+ if (info) {
588
+ attrs[ATTR_MESSAGING_NATS_STREAM_NAME] = info.stream;
589
+ attrs[ATTR_MESSAGING_CONSUMER_GROUP_NAME] = info.consumer;
590
+ attrs[ATTR_MESSAGING_NATS_STREAM_SEQUENCE] = info.streamSequence;
591
+ attrs[ATTR_MESSAGING_NATS_CONSUMER_SEQUENCE] = info.deliverySequence;
592
+ attrs[ATTR_MESSAGING_NATS_DELIVERY_COUNT] = info.deliveryCount;
593
+ }
594
+ const messageId = msg.headers?.get(NATS_MSG_ID_HEADER);
595
+ if (messageId) attrs[ATTR_MESSAGING_MESSAGE_ID] = messageId;
596
+ return attrs;
597
+ };
598
+ var buildRpcClientAttributes = (ctx) => {
599
+ const attrs = {
600
+ ...baseMessagingAttributes(ctx),
601
+ [ATTR_MESSAGING_OPERATION_NAME]: SPAN_NAME_SEND,
602
+ [ATTR_MESSAGING_OPERATION_TYPE]: SPAN_NAME_SEND,
603
+ [ATTR_MESSAGING_MESSAGE_BODY_SIZE]: ctx.payloadBytes,
604
+ [ATTR_JETSTREAM_KIND]: "rpc" /* Rpc */
605
+ };
606
+ if (ctx.correlationId) attrs[ATTR_MESSAGING_MESSAGE_CONVERSATION_ID] = ctx.correlationId;
607
+ if (ctx.messageId) attrs[ATTR_MESSAGING_MESSAGE_ID] = ctx.messageId;
608
+ return attrs;
609
+ };
610
+ var buildDeadLetterAttributes = (ctx) => {
611
+ const attrs = {
612
+ ...baseMessagingAttributes(ctx),
613
+ [ATTR_MESSAGING_OPERATION_NAME]: SPAN_NAME_DEAD_LETTER,
614
+ [ATTR_MESSAGING_OPERATION_TYPE]: SPAN_NAME_PROCESS,
615
+ [ATTR_MESSAGING_NATS_DELIVERY_COUNT]: ctx.finalDeliveryCount
616
+ };
617
+ if (ctx.reason) attrs[ATTR_JETSTREAM_DEAD_LETTER_REASON] = ctx.reason;
618
+ return attrs;
619
+ };
620
+ var extractFromRpcException = (record) => {
621
+ const getError = record.getError;
622
+ if (typeof getError !== "function") return void 0;
623
+ return codeFromPayload(getError.call(record));
624
+ };
625
+ var extractFromHttpException = (record) => {
626
+ const getStatus = record.getStatus;
627
+ if (typeof getStatus !== "function") return void 0;
628
+ const status = getStatus.call(record);
629
+ return typeof status === "number" && Number.isFinite(status) ? `HTTP_${status}` : void 0;
630
+ };
631
+ var extractFromOwnCode = (record) => {
632
+ const code = record.code ?? record.errorCode;
633
+ return typeof code === "string" && isStableErrorCode(code) ? code : void 0;
634
+ };
635
+ var extractExpectedErrorCode = (err) => {
636
+ if (err === null || typeof err !== "object") return void 0;
637
+ const record = err;
638
+ if (hasAncestorNamed(err, "RpcException")) {
639
+ const fromPayload = extractFromRpcException(record);
640
+ if (fromPayload !== void 0) return fromPayload;
641
+ }
642
+ if (hasAncestorNamed(err, "HttpException")) {
643
+ const fromStatus = extractFromHttpException(record);
644
+ if (fromStatus !== void 0) return fromStatus;
645
+ }
646
+ return extractFromOwnCode(record);
647
+ };
648
+ var hasAncestorNamed = (err, name) => {
649
+ let proto = Object.getPrototypeOf(err);
650
+ while (proto) {
651
+ const ctorName = proto.constructor?.name;
652
+ if (ctorName === name) return true;
653
+ proto = Object.getPrototypeOf(proto);
654
+ }
655
+ return false;
656
+ };
657
+ var STABLE_ERROR_CODE_RE = /^[A-Z][A-Z0-9_]*$/u;
658
+ var isStableErrorCode = (value) => STABLE_ERROR_CODE_RE.test(value);
659
+ var codeFromPayload = (payload) => {
660
+ if (payload === null || payload === void 0) return void 0;
661
+ if (typeof payload === "string") return isStableErrorCode(payload) ? payload : void 0;
662
+ if (typeof payload === "object") {
663
+ const code = payload.code;
664
+ if (typeof code === "string" && isStableErrorCode(code)) return code;
665
+ }
666
+ return void 0;
667
+ };
668
+ var buildExpectedErrorAttributes = (err) => {
669
+ const attrs = {
670
+ [ATTR_JETSTREAM_RPC_REPLY_HAS_ERROR]: true
671
+ };
672
+ const code = extractExpectedErrorCode(err);
673
+ if (code !== void 0) attrs[ATTR_JETSTREAM_RPC_REPLY_ERROR_CODE] = code;
674
+ return attrs;
675
+ };
676
+ var applyExpectedErrorAttributes = (span, err) => {
677
+ span.setAttributes(buildExpectedErrorAttributes(err));
678
+ };
679
+
680
+ // src/otel/spans/publish.ts
681
+ import { Logger as Logger2 } from "@nestjs/common";
682
+ import { SpanKind, SpanStatusCode, context, trace as trace2 } from "@opentelemetry/api";
683
+ var logger2 = new Logger2("Jetstream:Otel");
684
+ var shouldTracePublishSafe = (predicate, subject, record) => {
685
+ if (!predicate) return true;
686
+ try {
687
+ return predicate(subject, record);
688
+ } catch (err) {
689
+ const message = err instanceof Error ? err.message : String(err);
690
+ logger2.debug(`OTel shouldTracePublish threw: ${message}`);
691
+ return true;
692
+ }
693
+ };
694
+ var withPublishSpan = async (ctx, config, fn) => {
695
+ if (!config.enabled) return fn();
696
+ const shouldCreateSpan = config.traces.has("publish" /* Publish */) && shouldTracePublishSafe(config.shouldTracePublish, ctx.subject, ctx.record);
697
+ if (!shouldCreateSpan) {
698
+ injectContext(context.active(), ctx.headers, hdrsSetter);
699
+ return fn();
700
+ }
701
+ const tracer = getTracer();
702
+ const span = tracer.startSpan(`${SPAN_NAME_PUBLISH} ${ctx.subject}`, {
703
+ kind: SpanKind.PRODUCER,
704
+ attributes: {
705
+ ...buildPublishAttributes({
706
+ subject: ctx.subject,
707
+ pattern: ctx.pattern,
708
+ serviceName: ctx.serviceName,
709
+ serverAddress: ctx.endpoint?.host,
710
+ serverPort: ctx.endpoint?.port,
711
+ kind: ctx.kind,
712
+ payloadBytes: ctx.payloadBytes,
713
+ messageId: ctx.messageId,
714
+ correlationId: ctx.correlationId
715
+ }),
716
+ ...ctx.scheduleTarget ? { [ATTR_JETSTREAM_SCHEDULE_TARGET]: ctx.scheduleTarget } : {},
717
+ ...captureMatchingHeaders(ctx.headers, config.captureHeaders),
718
+ ...captureBodyAttribute(ctx.subject, ctx.payload, config.captureBody)
719
+ }
720
+ });
721
+ const ctxWithSpan = trace2.setSpan(context.active(), span);
722
+ injectContext(ctxWithSpan, ctx.headers, hdrsSetter);
723
+ const start = Date.now();
724
+ const invokeResponseHook = (error) => {
725
+ context.with(ctxWithSpan, () => {
726
+ safelyInvokeHook(HOOK_RESPONSE, config.responseHook, span, {
727
+ subject: ctx.subject,
728
+ durationMs: Date.now() - start,
729
+ error
730
+ });
731
+ });
732
+ };
733
+ try {
734
+ const result = await context.with(ctxWithSpan, async () => {
735
+ safelyInvokeHook(HOOK_PUBLISH, config.publishHook, span, {
736
+ subject: ctx.subject,
737
+ record: ctx.record,
738
+ kind: ctx.kind
739
+ });
740
+ return fn();
741
+ });
742
+ span.setStatus({ code: SpanStatusCode.OK });
743
+ invokeResponseHook();
744
+ return result;
745
+ } catch (err) {
746
+ const error = err instanceof Error ? err : new Error(String(err));
747
+ span.recordException(error);
748
+ span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
749
+ invokeResponseHook(error);
750
+ throw err;
751
+ } finally {
752
+ span.end();
753
+ }
754
+ };
755
+
756
+ // src/otel/spans/consume.ts
757
+ import {
758
+ ROOT_CONTEXT,
759
+ SpanKind as SpanKind2,
760
+ SpanStatusCode as SpanStatusCode2,
761
+ context as context2,
762
+ trace as trace3
763
+ } from "@opentelemetry/api";
764
+ var isPromiseLike = (value) => typeof value === "object" && value !== null && typeof value.then === "function";
765
+ var applyExpectedError = (span, err) => {
766
+ span.setStatus({ code: SpanStatusCode2.OK });
767
+ applyExpectedErrorAttributes(span, err);
768
+ };
769
+ var applyUnexpectedError = (span, err) => {
770
+ const error = err instanceof Error ? err : new Error(String(err));
771
+ span.recordException(error);
772
+ span.setStatus({ code: SpanStatusCode2.ERROR, message: error.message });
773
+ };
774
+ var withConsumeSpan = (ctx, config, fn, options = {}) => {
775
+ if (!config.enabled) return fn();
776
+ const parentCtx = extractContext(ROOT_CONTEXT, ctx.msg.headers, hdrsGetter);
777
+ const shouldCreateSpan = config.traces.has("consume" /* Consume */) && (config.shouldTraceConsume?.(ctx.subject, ctx.msg) ?? true);
778
+ if (!shouldCreateSpan) {
779
+ return context2.with(parentCtx, fn);
780
+ }
781
+ const tracer = getTracer();
782
+ const span = tracer.startSpan(
783
+ `${SPAN_NAME_PROCESS} ${ctx.subject}`,
784
+ {
785
+ kind: SpanKind2.CONSUMER,
786
+ attributes: {
787
+ ...buildConsumeAttributes({
788
+ subject: ctx.subject,
789
+ pattern: ctx.pattern,
790
+ msg: ctx.msg,
791
+ info: ctx.info,
792
+ kind: ctx.kind,
793
+ payloadBytes: ctx.payloadBytes,
794
+ serviceName: ctx.serviceName,
795
+ serverAddress: ctx.endpoint?.host,
796
+ serverPort: ctx.endpoint?.port
797
+ }),
798
+ ...captureMatchingHeaders(ctx.msg.headers, config.captureHeaders),
799
+ ...captureBodyAttribute(ctx.subject, ctx.msg.data, config.captureBody)
800
+ }
801
+ },
802
+ parentCtx
803
+ );
804
+ const ctxWithSpan = trace3.setSpan(parentCtx, span);
805
+ const start = Date.now();
806
+ let finalized = false;
807
+ const { signal, timeoutLabel = "handler.timeout" } = options;
808
+ let detachAbort = null;
809
+ const invokeResponseHook = (durationMs, error) => {
810
+ context2.with(ctxWithSpan, () => {
811
+ safelyInvokeHook(HOOK_RESPONSE, config.responseHook, span, {
812
+ subject: ctx.subject,
813
+ durationMs,
814
+ error
815
+ });
816
+ });
817
+ };
818
+ const finishOk2 = () => {
819
+ if (finalized) return;
820
+ finalized = true;
821
+ detachAbort?.();
822
+ span.setStatus({ code: SpanStatusCode2.OK });
823
+ invokeResponseHook(Date.now() - start);
824
+ span.end();
825
+ };
826
+ const finishError2 = (err) => {
827
+ if (finalized) return;
828
+ finalized = true;
829
+ detachAbort?.();
830
+ let classification = "unexpected";
831
+ try {
832
+ classification = config.errorClassifier(err);
833
+ } catch {
834
+ }
835
+ if (classification === "expected") {
836
+ applyExpectedError(span, err);
837
+ } else {
838
+ applyUnexpectedError(span, err);
839
+ }
840
+ invokeResponseHook(Date.now() - start, err instanceof Error ? err : new Error(String(err)));
841
+ span.end();
842
+ };
843
+ const onAbort = () => {
844
+ if (finalized) return;
845
+ finalized = true;
846
+ const error = new Error(timeoutLabel);
847
+ span.addEvent(timeoutLabel);
848
+ span.recordException(error);
849
+ span.setStatus({ code: SpanStatusCode2.ERROR, message: timeoutLabel });
850
+ invokeResponseHook(Date.now() - start, error);
851
+ span.end();
852
+ };
853
+ if (signal) {
854
+ if (signal.aborted) {
855
+ onAbort();
856
+ } else {
857
+ signal.addEventListener("abort", onAbort, { once: true });
858
+ detachAbort = () => {
859
+ signal.removeEventListener("abort", onAbort);
860
+ };
861
+ }
862
+ }
863
+ let result;
864
+ try {
865
+ result = context2.with(ctxWithSpan, () => {
866
+ safelyInvokeHook(HOOK_CONSUME, config.consumeHook, span, {
867
+ subject: ctx.subject,
868
+ msg: ctx.msg,
869
+ handlerMetadata: ctx.handlerMetadata,
870
+ kind: ctx.kind
871
+ });
872
+ return fn();
873
+ });
874
+ } catch (err) {
875
+ finishError2(err);
876
+ throw err;
877
+ }
878
+ if (isPromiseLike(result)) {
879
+ return Promise.resolve(result).then(
880
+ (value) => {
881
+ finishOk2();
882
+ return value;
883
+ },
884
+ (err) => {
885
+ finishError2(err);
886
+ throw err;
887
+ }
888
+ );
889
+ }
890
+ finishOk2();
891
+ return result;
892
+ };
893
+
894
+ // src/otel/spans/rpc-client.ts
895
+ import { Logger as Logger3 } from "@nestjs/common";
896
+ import { SpanKind as SpanKind3, SpanStatusCode as SpanStatusCode3, context as context3, trace as trace4 } from "@opentelemetry/api";
897
+ var logger3 = new Logger3("Jetstream:Otel");
898
+ var RPC_TIMEOUT_MESSAGE = "rpc.timeout";
899
+ var beginRpcClientSpan = (ctx, config) => {
900
+ if (!config.enabled) {
901
+ return {
902
+ activeContext: context3.active(),
903
+ finish: () => void 0
904
+ };
905
+ }
906
+ if (!config.traces.has("rpc.client.send" /* RpcClientSend */)) {
907
+ injectContext(context3.active(), ctx.headers, hdrsSetter);
908
+ return {
909
+ activeContext: context3.active(),
910
+ finish: () => void 0
911
+ };
912
+ }
913
+ const tracer = getTracer();
914
+ const span = tracer.startSpan(`${SPAN_NAME_SEND} ${ctx.subject}`, {
915
+ kind: SpanKind3.CLIENT,
916
+ attributes: {
917
+ ...buildRpcClientAttributes({
918
+ subject: ctx.subject,
919
+ pattern: ctx.pattern,
920
+ correlationId: ctx.correlationId,
921
+ payloadBytes: ctx.payloadBytes,
922
+ messageId: ctx.messageId,
923
+ serviceName: ctx.serviceName,
924
+ serverAddress: ctx.endpoint?.host,
925
+ serverPort: ctx.endpoint?.port
926
+ }),
927
+ ...captureMatchingHeaders(ctx.headers, config.captureHeaders),
928
+ ...captureBodyAttribute(ctx.subject, ctx.payload, config.captureBody)
929
+ }
930
+ });
931
+ const ctxWithSpan = trace4.setSpan(context3.active(), span);
932
+ injectContext(ctxWithSpan, ctx.headers, hdrsSetter);
933
+ const start = Date.now();
934
+ let finalized = false;
935
+ const finish = (outcome) => {
936
+ if (finalized) return;
937
+ finalized = true;
938
+ let reply;
939
+ let error;
940
+ switch (outcome.kind) {
941
+ case "ok" /* Ok */:
942
+ reply = outcome.reply;
943
+ span.setStatus({ code: SpanStatusCode3.OK });
944
+ break;
945
+ case "reply-error" /* ReplyError */:
946
+ reply = outcome.replyPayload;
947
+ applyExpectedErrorAttributes(span, outcome.replyPayload);
948
+ span.setStatus({ code: SpanStatusCode3.OK });
949
+ break;
950
+ case "timeout" /* Timeout */:
951
+ error = new Error(RPC_TIMEOUT_MESSAGE);
952
+ span.addEvent(RPC_TIMEOUT_MESSAGE);
953
+ span.setStatus({ code: SpanStatusCode3.ERROR, message: RPC_TIMEOUT_MESSAGE });
954
+ break;
955
+ case "error" /* Error */:
956
+ error = outcome.error;
957
+ span.recordException(outcome.error);
958
+ span.setStatus({ code: SpanStatusCode3.ERROR, message: outcome.error.message });
959
+ break;
960
+ default: {
961
+ const unknownOutcome = outcome;
962
+ logger3.error(`Unhandled RPC outcome: ${String(unknownOutcome.kind)}`);
963
+ span.setStatus({ code: SpanStatusCode3.ERROR, message: "unknown outcome" });
964
+ }
965
+ }
966
+ context3.with(ctxWithSpan, () => {
967
+ safelyInvokeHook(HOOK_RESPONSE, config.responseHook, span, {
968
+ subject: ctx.subject,
969
+ durationMs: Date.now() - start,
970
+ reply,
971
+ error
972
+ });
973
+ });
974
+ span.end();
975
+ };
976
+ return { activeContext: ctxWithSpan, finish };
977
+ };
978
+
979
+ // src/otel/spans/dead-letter.ts
980
+ import { ROOT_CONTEXT as ROOT_CONTEXT2, SpanKind as SpanKind4, SpanStatusCode as SpanStatusCode4, context as context4, trace as trace5 } from "@opentelemetry/api";
981
+ var withDeadLetterSpan = async (ctx, config, fn) => {
982
+ if (!config.enabled || !config.traces.has("dead_letter" /* DeadLetter */)) {
983
+ return fn();
984
+ }
985
+ const parentCtx = extractContext(ROOT_CONTEXT2, ctx.msg.headers, hdrsGetter);
986
+ const tracer = getTracer();
987
+ const span = tracer.startSpan(
988
+ `${SPAN_NAME_DEAD_LETTER} ${ctx.msg.subject}`,
989
+ {
990
+ kind: SpanKind4.INTERNAL,
991
+ attributes: buildDeadLetterAttributes({
992
+ subject: ctx.msg.subject,
993
+ pattern: ctx.pattern,
994
+ serviceName: ctx.serviceName,
995
+ serverAddress: ctx.endpoint?.host,
996
+ serverPort: ctx.endpoint?.port,
997
+ finalDeliveryCount: ctx.finalDeliveryCount,
998
+ reason: ctx.reason
999
+ })
1000
+ },
1001
+ parentCtx
1002
+ );
1003
+ const ctxWithSpan = trace5.setSpan(parentCtx, span);
1004
+ const start = Date.now();
1005
+ const invokeResponseHook = (error) => {
1006
+ context4.with(ctxWithSpan, () => {
1007
+ safelyInvokeHook(HOOK_RESPONSE, config.responseHook, span, {
1008
+ subject: ctx.msg.subject,
1009
+ durationMs: Date.now() - start,
1010
+ error
1011
+ });
1012
+ });
1013
+ };
1014
+ try {
1015
+ const result = await context4.with(ctxWithSpan, fn);
1016
+ span.setStatus({ code: SpanStatusCode4.OK });
1017
+ invokeResponseHook();
1018
+ return result;
1019
+ } catch (err) {
1020
+ const error = err instanceof Error ? err : new Error(String(err));
1021
+ span.recordException(error);
1022
+ span.setStatus({ code: SpanStatusCode4.ERROR, message: error.message });
1023
+ invokeResponseHook(error);
1024
+ throw err;
1025
+ } finally {
1026
+ span.end();
1027
+ }
1028
+ };
1029
+
1030
+ // src/otel/spans/infrastructure.ts
1031
+ import { Logger as Logger4 } from "@nestjs/common";
1032
+ import {
1033
+ SpanKind as SpanKind5,
1034
+ SpanStatusCode as SpanStatusCode5,
1035
+ context as context5,
1036
+ trace as trace6
1037
+ } from "@opentelemetry/api";
1038
+ var logger4 = new Logger4("Jetstream:Otel");
1039
+ var startInfraSpan = (config, traceKind, name, ctx, extraAttributes = {}) => {
1040
+ if (!config.enabled || !config.traces.has(traceKind)) return null;
1041
+ const tracer = getTracer();
1042
+ const attributes = {
1043
+ [ATTR_JETSTREAM_SERVICE_NAME]: ctx.serviceName
1044
+ };
1045
+ if (ctx.endpoint?.host) attributes[ATTR_SERVER_ADDRESS] = ctx.endpoint.host;
1046
+ if (ctx.endpoint?.port !== void 0) attributes[ATTR_SERVER_PORT] = ctx.endpoint.port;
1047
+ for (const [key, value] of Object.entries(extraAttributes)) {
1048
+ if (value !== void 0) attributes[key] = value;
1049
+ }
1050
+ return tracer.startSpan(name, { kind: SpanKind5.INTERNAL, attributes });
1051
+ };
1052
+ var finishOk = (span) => {
1053
+ span.setStatus({ code: SpanStatusCode5.OK });
1054
+ span.end();
1055
+ };
1056
+ var finishError = (span, err) => {
1057
+ const error = err instanceof Error ? err : new Error(String(err));
1058
+ span.recordException(error);
1059
+ span.setStatus({ code: SpanStatusCode5.ERROR, message: error.message });
1060
+ span.end();
1061
+ };
1062
+ var wrapInfra = async (config, traceKind, name, ctx, attributes, op) => {
1063
+ const span = startInfraSpan(config, traceKind, name, ctx, attributes);
1064
+ if (!span) return op();
1065
+ const ctxWithSpan = trace6.setSpan(context5.active(), span);
1066
+ try {
1067
+ const result = await context5.with(ctxWithSpan, op);
1068
+ finishOk(span);
1069
+ return result;
1070
+ } catch (err) {
1071
+ finishError(span, err);
1072
+ throw err;
1073
+ }
1074
+ };
1075
+ var beginConnectionLifecycleSpan = (config, ctx) => {
1076
+ const span = startInfraSpan(
1077
+ config,
1078
+ "connection.lifecycle" /* ConnectionLifecycle */,
1079
+ SPAN_NAME_NATS_CONNECTION,
1080
+ ctx,
1081
+ { [ATTR_NATS_CONNECTION_SERVER]: ctx.server }
1082
+ );
1083
+ if (!span) {
1084
+ return {
1085
+ recordEvent: () => void 0,
1086
+ finish: () => void 0
1087
+ };
1088
+ }
1089
+ let finalized = false;
1090
+ return {
1091
+ recordEvent: (name, attributes) => {
1092
+ if (finalized) {
1093
+ logger4.debug(`recordEvent('${name}') called after connection span finished`);
1094
+ return;
1095
+ }
1096
+ span.addEvent(name, attributes);
1097
+ },
1098
+ finish: (err) => {
1099
+ if (finalized) return;
1100
+ finalized = true;
1101
+ if (err === void 0) finishOk(span);
1102
+ else finishError(span, err);
1103
+ }
1104
+ };
1105
+ };
1106
+ var withSelfHealingSpan = (config, ctx, op) => wrapInfra(
1107
+ config,
1108
+ "self_healing" /* SelfHealing */,
1109
+ SPAN_NAME_JETSTREAM_SELF_HEALING,
1110
+ ctx,
1111
+ {
1112
+ [ATTR_MESSAGING_NATS_STREAM_NAME]: ctx.stream,
1113
+ [ATTR_MESSAGING_CONSUMER_GROUP_NAME]: ctx.consumer,
1114
+ [ATTR_JETSTREAM_SELF_HEALING_REASON]: ctx.reason
1115
+ },
1116
+ op
1117
+ );
1118
+ var withProvisioningSpan = (config, ctx, op) => wrapInfra(
1119
+ config,
1120
+ "provisioning" /* Provisioning */,
1121
+ `${SPAN_NAME_JETSTREAM_PROVISIONING_PREFIX}${ctx.entity}`,
1122
+ ctx,
1123
+ {
1124
+ [ATTR_JETSTREAM_PROVISIONING_ENTITY]: ctx.entity,
1125
+ [ATTR_JETSTREAM_PROVISIONING_ACTION]: ctx.action,
1126
+ [ATTR_JETSTREAM_PROVISIONING_NAME]: ctx.name
1127
+ },
1128
+ op
1129
+ );
1130
+ var withMigrationSpan = (config, ctx, op) => wrapInfra(
1131
+ config,
1132
+ "migration" /* Migration */,
1133
+ SPAN_NAME_JETSTREAM_MIGRATION,
1134
+ ctx,
1135
+ {
1136
+ [ATTR_MESSAGING_NATS_STREAM_NAME]: ctx.stream,
1137
+ [ATTR_JETSTREAM_MIGRATION_REASON]: ctx.reason
1138
+ },
1139
+ op
1140
+ );
1141
+ var withShutdownSpan = (config, ctx, op) => wrapInfra(config, "shutdown" /* Shutdown */, SPAN_NAME_JETSTREAM_SHUTDOWN, ctx, {}, op);
1142
+
227
1143
  // src/client/jetstream.record.ts
228
1144
  var JetstreamRecord = class {
229
1145
  constructor(data, headers2, timeout, messageId, schedule, ttl) {
@@ -376,9 +1292,14 @@ var JetstreamRecordBuilder = class {
376
1292
  this.ttlDuration
377
1293
  );
378
1294
  }
379
- /** Validate that a header key is not reserved. */
1295
+ /**
1296
+ * Validate that a header key is not reserved. NATS treats header names
1297
+ * case-insensitively, so the check is against the lowercase form to keep
1298
+ * `'X-Correlation-ID'`, `'x-correlation-id'`, and any other casing in
1299
+ * lockstep. `RESERVED_HEADERS` is defined as an all-lowercase set.
1300
+ */
380
1301
  validateHeaderKey(key) {
381
- if (RESERVED_HEADERS.has(key)) {
1302
+ if (RESERVED_HEADERS.has(key.toLowerCase())) {
382
1303
  throw new Error(
383
1304
  `Header "${key}" is reserved by the JetStream transport and cannot be set manually. Reserved headers: ${[...RESERVED_HEADERS].join(", ")}`
384
1305
  );
@@ -398,6 +1319,12 @@ var nanosToGoDuration = (nanos) => {
398
1319
  };
399
1320
 
400
1321
  // src/client/jetstream.client.ts
1322
+ var BROADCAST_SUBJECT_PREFIX = "broadcast.";
1323
+ var detectEventKind = (pattern) => {
1324
+ if (pattern.startsWith("broadcast:" /* Broadcast */)) return "broadcast" /* Broadcast */;
1325
+ if (pattern.startsWith("ordered:" /* Ordered */)) return "ordered" /* Ordered */;
1326
+ return "event" /* Event */;
1327
+ };
401
1328
  var JetstreamClient = class extends ClientProxy {
402
1329
  constructor(rootOptions, targetServiceName, connection, codec, eventBus) {
403
1330
  super();
@@ -407,12 +1334,40 @@ var JetstreamClient = class extends ClientProxy {
407
1334
  this.eventBus = eventBus;
408
1335
  this.targetName = targetServiceName;
409
1336
  this.callerName = internalName(this.rootOptions.name);
410
- }
411
- logger = new Logger("Jetstream:Client");
1337
+ const targetInternal = internalName(targetServiceName);
1338
+ this.eventSubjectPrefix = `${targetInternal}.${"ev" /* Event */}.`;
1339
+ this.commandSubjectPrefix = `${targetInternal}.${"cmd" /* Command */}.`;
1340
+ this.orderedSubjectPrefix = `${targetInternal}.${"ordered" /* Ordered */}.`;
1341
+ this.isCoreMode = isCoreRpcMode(this.rootOptions.rpc);
1342
+ this.defaultRpcTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? this.rootOptions.rpc?.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT : this.rootOptions.rpc?.timeout ?? DEFAULT_RPC_TIMEOUT;
1343
+ const derived = deriveOtelAttrs(this.rootOptions);
1344
+ this.otel = derived.otel;
1345
+ this.serverEndpoint = derived.serverEndpoint;
1346
+ }
1347
+ logger = new Logger5("Jetstream:Client");
412
1348
  /** Target service name this client sends messages to. */
413
1349
  targetName;
414
1350
  /** Pre-cached caller name derived from rootOptions.name, computed once in constructor. */
415
1351
  callerName;
1352
+ /**
1353
+ * Subject prefixes of the form `{serviceName}__microservice.{kind}.` — one
1354
+ * per stream kind this client may publish to. Built once in the constructor
1355
+ * so producing a full subject is a single string concat with the user pattern.
1356
+ */
1357
+ eventSubjectPrefix;
1358
+ commandSubjectPrefix;
1359
+ orderedSubjectPrefix;
1360
+ /**
1361
+ * RPC configuration snapshots. The values are derived from rootOptions at
1362
+ * construction time so the publish hot path never has to re-run
1363
+ * isCoreRpcMode / getRpcTimeout on every call.
1364
+ */
1365
+ isCoreMode;
1366
+ defaultRpcTimeout;
1367
+ /** Resolved OpenTelemetry configuration, computed once in the constructor. */
1368
+ otel;
1369
+ /** Server endpoint parts used for `server.address` / `server.port` span attributes. */
1370
+ serverEndpoint;
416
1371
  /** Shared inbox for JetStream-mode RPC responses. */
417
1372
  inbox = null;
418
1373
  inboxSubscription = null;
@@ -422,6 +1377,12 @@ var JetstreamClient = class extends ClientProxy {
422
1377
  pendingTimeouts = /* @__PURE__ */ new Map();
423
1378
  /** Subscription to connection status events for disconnect handling. */
424
1379
  statusSubscription = null;
1380
+ /**
1381
+ * Cached readiness flag. Once `connect()` has wired the inbox and status
1382
+ * subscription, subsequent publishes skip the `await connect()` microtask
1383
+ * and reach for the underlying connection synchronously instead.
1384
+ */
1385
+ readyForPublish = false;
425
1386
  /**
426
1387
  * Establish connection. Called automatically by NestJS on first use.
427
1388
  *
@@ -432,7 +1393,7 @@ var JetstreamClient = class extends ClientProxy {
432
1393
  */
433
1394
  async connect() {
434
1395
  const nc = await this.connection.getConnection();
435
- if (isJetStreamRpcMode(this.rootOptions.rpc) && !this.inboxSubscription) {
1396
+ if (!this.isCoreMode && !this.inboxSubscription) {
436
1397
  this.setupInbox(nc);
437
1398
  }
438
1399
  this.statusSubscription ??= this.connection.status$.subscribe((status) => {
@@ -440,12 +1401,14 @@ var JetstreamClient = class extends ClientProxy {
440
1401
  this.handleDisconnect();
441
1402
  }
442
1403
  });
1404
+ this.readyForPublish = true;
443
1405
  return nc;
444
1406
  }
445
1407
  /** Clean up resources: reject pending RPCs, unsubscribe from status events. */
446
1408
  async close() {
447
1409
  this.statusSubscription?.unsubscribe();
448
1410
  this.statusSubscription = null;
1411
+ this.readyForPublish = false;
449
1412
  this.rejectPendingRpcs(new Error("Client closed"));
450
1413
  }
451
1414
  /**
@@ -469,36 +1432,58 @@ var JetstreamClient = class extends ClientProxy {
469
1432
  * set to the original event subject.
470
1433
  */
471
1434
  async dispatchEvent(packet) {
472
- await this.connect();
1435
+ if (!this.readyForPublish) await this.connect();
473
1436
  const { data, hdrs, messageId, schedule, ttl } = this.extractRecordData(packet.data);
474
1437
  const eventSubject = this.buildEventSubject(packet.pattern);
1438
+ const publishSubject = schedule ? this.buildScheduleSubject(eventSubject) : eventSubject;
475
1439
  const msgHeaders = this.buildHeaders(hdrs, { subject: eventSubject });
476
- if (schedule) {
477
- const scheduleSubject = this.buildScheduleSubject(eventSubject);
478
- const ack = await this.connection.getJetStreamClient().publish(scheduleSubject, this.codec.encode(data), {
1440
+ const encoded = this.codec.encode(data);
1441
+ const effectiveMsgId = messageId ?? nuid.next();
1442
+ const record = packet.data instanceof JetstreamRecord ? packet.data : new JetstreamRecord(data, /* @__PURE__ */ new Map());
1443
+ await withPublishSpan(
1444
+ {
1445
+ subject: publishSubject,
1446
+ pattern: packet.pattern,
1447
+ record,
1448
+ kind: detectEventKind(packet.pattern),
1449
+ payloadBytes: encoded.length,
1450
+ payload: encoded,
1451
+ messageId: effectiveMsgId,
479
1452
  headers: msgHeaders,
480
- msgID: messageId ?? nuid.next(),
481
- ttl,
482
- schedule: {
483
- specification: schedule.at,
484
- target: eventSubject
1453
+ serviceName: this.callerName,
1454
+ endpoint: this.serverEndpoint,
1455
+ scheduleTarget: schedule ? eventSubject : void 0
1456
+ },
1457
+ this.otel,
1458
+ async () => {
1459
+ const warnIfDuplicate = (kindLabel, ack2) => {
1460
+ if (ack2.duplicate) {
1461
+ this.logger.warn(
1462
+ `Duplicate ${kindLabel} publish detected: ${publishSubject} (seq: ${ack2.seq})`
1463
+ );
1464
+ }
1465
+ };
1466
+ if (schedule) {
1467
+ const ack2 = await this.connection.getJetStreamClient().publish(publishSubject, encoded, {
1468
+ headers: msgHeaders,
1469
+ msgID: effectiveMsgId,
1470
+ ttl,
1471
+ schedule: {
1472
+ specification: schedule.at,
1473
+ target: eventSubject
1474
+ }
1475
+ });
1476
+ warnIfDuplicate("scheduled", ack2);
1477
+ return;
485
1478
  }
486
- });
487
- if (ack.duplicate) {
488
- this.logger.warn(
489
- `Duplicate scheduled publish detected: ${scheduleSubject} (seq: ${ack.seq})`
490
- );
491
- }
492
- } else {
493
- const ack = await this.connection.getJetStreamClient().publish(eventSubject, this.codec.encode(data), {
494
- headers: msgHeaders,
495
- msgID: messageId ?? nuid.next(),
496
- ttl
497
- });
498
- if (ack.duplicate) {
499
- this.logger.warn(`Duplicate event publish detected: ${eventSubject} (seq: ${ack.seq})`);
1479
+ const ack = await this.connection.getJetStreamClient().publish(publishSubject, encoded, {
1480
+ headers: msgHeaders,
1481
+ msgID: effectiveMsgId,
1482
+ ttl
1483
+ });
1484
+ warnIfDuplicate("event", ack);
500
1485
  }
501
- }
1486
+ );
502
1487
  return void 0;
503
1488
  }
504
1489
  /**
@@ -508,7 +1493,7 @@ var JetstreamClient = class extends ClientProxy {
508
1493
  * JetStream mode: publishes to stream + waits for inbox response.
509
1494
  */
510
1495
  publish(packet, callback) {
511
- const subject = buildSubject(this.targetName, "cmd" /* Command */, packet.pattern);
1496
+ const subject = this.commandSubjectPrefix + packet.pattern;
512
1497
  const { data, hdrs, timeout, messageId, schedule, ttl } = this.extractRecordData(packet.data);
513
1498
  if (schedule) {
514
1499
  this.logger.warn(
@@ -525,7 +1510,7 @@ var JetstreamClient = class extends ClientProxy {
525
1510
  callback({ err: new Error("Internal transport error"), response: null, isDisposed: true });
526
1511
  };
527
1512
  let jetStreamCorrelationId = null;
528
- if (isCoreRpcMode(this.rootOptions.rpc)) {
1513
+ if (this.isCoreMode) {
529
1514
  this.publishCoreRpc(subject, data, hdrs, timeout, callback).catch(onUnhandled);
530
1515
  } else {
531
1516
  jetStreamCorrelationId = nuid.next();
@@ -549,37 +1534,102 @@ var JetstreamClient = class extends ClientProxy {
549
1534
  }
550
1535
  /** Core mode: nc.request() with timeout. */
551
1536
  async publishCoreRpc(subject, data, customHeaders, timeout, callback) {
1537
+ const effectiveTimeout = timeout ?? this.defaultRpcTimeout;
1538
+ const hdrs = this.buildHeaders(customHeaders, { subject });
1539
+ const encoded = this.codec.encode(data);
1540
+ const spanHandle = beginRpcClientSpan(
1541
+ {
1542
+ subject,
1543
+ payloadBytes: encoded.length,
1544
+ payload: encoded,
1545
+ headers: hdrs,
1546
+ serviceName: this.callerName,
1547
+ endpoint: this.serverEndpoint
1548
+ },
1549
+ this.otel
1550
+ );
552
1551
  try {
553
- const nc = await this.connect();
554
- const effectiveTimeout = timeout ?? this.getRpcTimeout();
555
- const hdrs = this.buildHeaders(customHeaders, { subject });
556
- const response = await nc.request(subject, this.codec.encode(data), {
557
- timeout: effectiveTimeout,
558
- headers: hdrs
559
- });
1552
+ const nc = this.readyForPublish ? this.connection.unwrap : await this.connect();
1553
+ const response = await context6.with(
1554
+ spanHandle.activeContext,
1555
+ () => nc.request(subject, encoded, {
1556
+ timeout: effectiveTimeout,
1557
+ headers: hdrs
1558
+ })
1559
+ );
560
1560
  const decoded = this.codec.decode(response.data);
561
1561
  if (response.headers?.get("x-error" /* Error */)) {
1562
+ spanHandle.finish({ kind: "reply-error" /* ReplyError */, replyPayload: decoded });
562
1563
  callback({ err: decoded, response: null, isDisposed: true });
563
1564
  } else {
1565
+ spanHandle.finish({ kind: "ok" /* Ok */, reply: decoded });
564
1566
  callback({ err: null, response: decoded, isDisposed: true });
565
1567
  }
566
1568
  } catch (err) {
567
1569
  const error = err instanceof Error ? err : new Error("Unknown error");
568
- this.logger.error(`Core RPC error (${subject}):`, err);
569
- this.eventBus.emit("error" /* Error */, error, "client-rpc");
1570
+ if (error instanceof TimeoutError) {
1571
+ spanHandle.finish({ kind: "timeout" /* Timeout */ });
1572
+ this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, "");
1573
+ } else {
1574
+ spanHandle.finish({ kind: "error" /* Error */, error });
1575
+ this.eventBus.emit("error" /* Error */, error, "client-rpc");
1576
+ }
570
1577
  callback({ err: error, response: null, isDisposed: true });
571
1578
  }
572
1579
  }
573
1580
  /** JetStream mode: publish to stream + wait for inbox response. */
574
1581
  async publishJetStreamRpc(subject, data, callback, options) {
575
1582
  const { headers: customHeaders, correlationId, messageId } = options;
576
- const effectiveTimeout = options.timeout ?? this.getRpcTimeout();
577
- this.pendingMessages.set(correlationId, callback);
1583
+ const effectiveTimeout = options.timeout ?? this.defaultRpcTimeout;
1584
+ const hdrs = this.buildHeaders(customHeaders, {
1585
+ subject,
1586
+ correlationId,
1587
+ replyTo: this.inbox ?? ""
1588
+ });
1589
+ const encoded = this.codec.encode(data);
1590
+ const spanHandle = beginRpcClientSpan(
1591
+ {
1592
+ subject,
1593
+ correlationId,
1594
+ payloadBytes: encoded.length,
1595
+ payload: encoded,
1596
+ messageId,
1597
+ headers: hdrs,
1598
+ serviceName: this.callerName,
1599
+ endpoint: this.serverEndpoint
1600
+ },
1601
+ this.otel
1602
+ );
1603
+ this.pendingMessages.set(correlationId, (packet) => {
1604
+ if (packet.err) {
1605
+ if (packet.err instanceof Error) {
1606
+ spanHandle.finish({ kind: "error" /* Error */, error: packet.err });
1607
+ } else {
1608
+ spanHandle.finish({ kind: "reply-error" /* ReplyError */, replyPayload: packet.err });
1609
+ }
1610
+ } else {
1611
+ spanHandle.finish({ kind: "ok" /* Ok */, reply: packet.response });
1612
+ }
1613
+ callback(packet);
1614
+ });
1615
+ const timeoutId = setTimeout(() => {
1616
+ if (!this.pendingMessages.has(correlationId)) return;
1617
+ this.pendingTimeouts.delete(correlationId);
1618
+ this.pendingMessages.delete(correlationId);
1619
+ spanHandle.finish({ kind: "timeout" /* Timeout */ });
1620
+ this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
1621
+ callback({ err: new Error(RPC_TIMEOUT_MESSAGE), response: null, isDisposed: true });
1622
+ }, effectiveTimeout);
1623
+ this.pendingTimeouts.set(correlationId, timeoutId);
578
1624
  try {
579
- await this.connect();
1625
+ if (!this.readyForPublish) await this.connect();
580
1626
  if (!this.pendingMessages.has(correlationId)) return;
581
1627
  if (!this.inbox) {
1628
+ clearTimeout(timeoutId);
1629
+ this.pendingTimeouts.delete(correlationId);
582
1630
  this.pendingMessages.delete(correlationId);
1631
+ const inboxError = new Error("Inbox not initialized");
1632
+ spanHandle.finish({ kind: "error" /* Error */, error: inboxError });
583
1633
  callback({
584
1634
  err: new Error("Inbox not initialized \u2014 JetStream RPC mode requires a connected inbox"),
585
1635
  response: null,
@@ -587,24 +1637,13 @@ var JetstreamClient = class extends ClientProxy {
587
1637
  });
588
1638
  return;
589
1639
  }
590
- const timeoutId = setTimeout(() => {
591
- if (!this.pendingMessages.has(correlationId)) return;
592
- this.pendingTimeouts.delete(correlationId);
593
- this.pendingMessages.delete(correlationId);
594
- this.logger.error(`JetStream RPC timeout (${effectiveTimeout}ms): ${subject}`);
595
- this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
596
- callback({ err: new Error("RPC timeout"), response: null, isDisposed: true });
597
- }, effectiveTimeout);
598
- this.pendingTimeouts.set(correlationId, timeoutId);
599
- const hdrs = this.buildHeaders(customHeaders, {
600
- subject,
601
- correlationId,
602
- replyTo: this.inbox
603
- });
604
- await this.connection.getJetStreamClient().publish(subject, this.codec.encode(data), {
605
- headers: hdrs,
606
- msgID: messageId ?? nuid.next()
607
- });
1640
+ await context6.with(
1641
+ spanHandle.activeContext,
1642
+ () => this.connection.getJetStreamClient().publish(subject, encoded, {
1643
+ headers: hdrs,
1644
+ msgID: messageId ?? nuid.next()
1645
+ })
1646
+ );
608
1647
  } catch (err) {
609
1648
  const existingTimeout = this.pendingTimeouts.get(correlationId);
610
1649
  if (existingTimeout) {
@@ -614,7 +1653,8 @@ var JetstreamClient = class extends ClientProxy {
614
1653
  if (!this.pendingMessages.has(correlationId)) return;
615
1654
  this.pendingMessages.delete(correlationId);
616
1655
  const error = err instanceof Error ? err : new Error("Unknown error");
617
- this.logger.error(`JetStream RPC publish error (${subject}):`, err);
1656
+ spanHandle.finish({ kind: "error" /* Error */, error });
1657
+ this.eventBus.emit("error" /* Error */, error, `jetstream-rpc-publish:${subject}`);
618
1658
  callback({ err: error, response: null, isDisposed: true });
619
1659
  }
620
1660
  }
@@ -622,6 +1662,7 @@ var JetstreamClient = class extends ClientProxy {
622
1662
  handleDisconnect() {
623
1663
  this.rejectPendingRpcs(new Error("Connection lost"));
624
1664
  this.inbox = null;
1665
+ this.readyForPublish = false;
625
1666
  }
626
1667
  /** Reject all pending RPC callbacks, clear timeouts, and tear down inbox. */
627
1668
  rejectPendingRpcs(error) {
@@ -685,19 +1726,22 @@ var JetstreamClient = class extends ClientProxy {
685
1726
  this.pendingMessages.delete(correlationId);
686
1727
  }
687
1728
  }
688
- /** Build event subject — workqueue, broadcast, or ordered. */
1729
+ /**
1730
+ * Resolve a user pattern to a fully-qualified NATS subject, dispatching
1731
+ * between the event, broadcast, and ordered prefixes.
1732
+ *
1733
+ * The leading-char check short-circuits the `startsWith` comparisons for
1734
+ * patterns that cannot possibly carry a broadcast/ordered marker, which is
1735
+ * the overwhelmingly common case.
1736
+ */
689
1737
  buildEventSubject(pattern) {
690
- if (pattern.startsWith("broadcast:" /* Broadcast */)) {
691
- return buildBroadcastSubject(pattern.slice("broadcast:" /* Broadcast */.length));
692
- }
693
- if (pattern.startsWith("ordered:" /* Ordered */)) {
694
- return buildSubject(
695
- this.targetName,
696
- "ordered" /* Ordered */,
697
- pattern.slice("ordered:" /* Ordered */.length)
698
- );
1738
+ if (pattern.charCodeAt(0) === 98 && pattern.startsWith("broadcast:" /* Broadcast */)) {
1739
+ return BROADCAST_SUBJECT_PREFIX + pattern.slice("broadcast:" /* Broadcast */.length);
1740
+ }
1741
+ if (pattern.charCodeAt(0) === 111 && pattern.startsWith("ordered:" /* Ordered */)) {
1742
+ return this.orderedSubjectPrefix + pattern.slice("ordered:" /* Ordered */.length);
699
1743
  }
700
- return buildSubject(this.targetName, "ev" /* Event */, pattern);
1744
+ return this.eventSubjectPrefix + pattern;
701
1745
  }
702
1746
  /** Build NATS headers merging custom headers with transport headers. */
703
1747
  buildHeaders(customHeaders, transport) {
@@ -722,7 +1766,7 @@ var JetstreamClient = class extends ClientProxy {
722
1766
  if (rawData instanceof JetstreamRecord) {
723
1767
  return {
724
1768
  data: rawData.data,
725
- hdrs: rawData.headers.size > 0 ? new Map(rawData.headers) : null,
1769
+ hdrs: rawData.headers.size > 0 ? rawData.headers : null,
726
1770
  timeout: rawData.timeout,
727
1771
  messageId: rawData.messageId,
728
1772
  schedule: rawData.schedule,
@@ -765,11 +1809,6 @@ var JetstreamClient = class extends ClientProxy {
765
1809
  const pattern = withoutPrefix.slice(dotIndex + 1);
766
1810
  return `${targetPrefix}_sch.${pattern}`;
767
1811
  }
768
- getRpcTimeout() {
769
- if (!this.rootOptions.rpc) return DEFAULT_RPC_TIMEOUT;
770
- const defaultTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? DEFAULT_JETSTREAM_RPC_TIMEOUT : DEFAULT_RPC_TIMEOUT;
771
- return this.rootOptions.rpc.timeout ?? defaultTimeout;
772
- }
773
1812
  };
774
1813
 
775
1814
  // src/codec/json.codec.ts
@@ -784,8 +1823,21 @@ var JsonCodec = class {
784
1823
  }
785
1824
  };
786
1825
 
1826
+ // src/codec/msgpack.codec.ts
1827
+ var MsgpackCodec = class {
1828
+ constructor(packr) {
1829
+ this.packr = packr;
1830
+ }
1831
+ encode(data) {
1832
+ return this.packr.pack(data);
1833
+ }
1834
+ decode(data) {
1835
+ return this.packr.unpack(data);
1836
+ }
1837
+ };
1838
+
787
1839
  // src/connection/connection.provider.ts
788
- import { Logger as Logger2 } from "@nestjs/common";
1840
+ import { Logger as Logger6 } from "@nestjs/common";
789
1841
  import {
790
1842
  connect
791
1843
  } from "@nats-io/transport-node";
@@ -802,6 +1854,10 @@ var ConnectionProvider = class {
802
1854
  constructor(options, eventBus) {
803
1855
  this.options = options;
804
1856
  this.eventBus = eventBus;
1857
+ const derived = deriveOtelAttrs(options);
1858
+ this.otel = derived.otel;
1859
+ this.otelServiceName = derived.serviceName;
1860
+ this.otelEndpoint = derived.serverEndpoint;
805
1861
  this.nc$ = defer(() => this.getConnection()).pipe(
806
1862
  shareReplay({ bufferSize: 1, refCount: false })
807
1863
  );
@@ -814,12 +1870,16 @@ var ConnectionProvider = class {
814
1870
  nc$;
815
1871
  /** Live stream of connection status events (no replay). */
816
1872
  status$;
817
- logger = new Logger2("Jetstream:Connection");
1873
+ logger = new Logger6("Jetstream:Connection");
818
1874
  connection = null;
819
1875
  connectionPromise = null;
820
1876
  jsClient = null;
821
1877
  jsmInstance = null;
822
1878
  jsmPromise = null;
1879
+ otel;
1880
+ otelServiceName;
1881
+ otelEndpoint;
1882
+ lifecycleSpan = null;
823
1883
  /**
824
1884
  * Establish NATS connection. Idempotent — returns cached connection on subsequent calls.
825
1885
  *
@@ -883,14 +1943,24 @@ var ConnectionProvider = class {
883
1943
  }
884
1944
  if (!this.connection || this.connection.isClosed()) return;
885
1945
  try {
886
- await this.connection.drain();
887
- await this.connection.closed();
888
- } catch {
889
- try {
890
- await this.connection.close();
891
- } catch {
892
- }
1946
+ await withShutdownSpan(
1947
+ this.otel,
1948
+ { serviceName: this.otelServiceName, endpoint: this.otelEndpoint },
1949
+ async () => {
1950
+ try {
1951
+ await this.connection?.drain();
1952
+ await this.connection?.closed();
1953
+ } catch {
1954
+ try {
1955
+ await this.connection?.close();
1956
+ } catch {
1957
+ }
1958
+ }
1959
+ }
1960
+ );
893
1961
  } finally {
1962
+ this.lifecycleSpan?.finish();
1963
+ this.lifecycleSpan = null;
894
1964
  this.connection = null;
895
1965
  this.connectionPromise = null;
896
1966
  this.jsClient = null;
@@ -910,17 +1980,25 @@ var ConnectionProvider = class {
910
1980
  }
911
1981
  /** Internal: establish the physical connection with reconnect monitoring. */
912
1982
  async establish() {
913
- const name = internalName(this.options.name);
914
1983
  try {
915
1984
  const nc = await connect({
916
1985
  ...DEFAULT_OPTIONS,
1986
+ // Default the NATS connection name to the OTel-derived service name so
1987
+ // `nats server info` lines up with span attributes, but let user-supplied
1988
+ // `connectionOptions.name` win when set.
1989
+ name: this.otelServiceName,
917
1990
  ...this.options.connectionOptions,
918
- servers: this.options.servers,
919
- name
1991
+ servers: this.options.servers
920
1992
  });
921
1993
  this.connection = nc;
922
1994
  this.logger.log(`NATS connection established: ${nc.getServer()}`);
923
1995
  this.eventBus.emit("connect" /* Connect */, nc.getServer());
1996
+ this.lifecycleSpan?.finish();
1997
+ this.lifecycleSpan = beginConnectionLifecycleSpan(this.otel, {
1998
+ serviceName: this.otelServiceName,
1999
+ endpoint: this.otelEndpoint,
2000
+ server: nc.getServer()
2001
+ });
924
2002
  this.monitorStatus(nc);
925
2003
  return nc;
926
2004
  } catch (err) {
@@ -930,37 +2008,55 @@ var ConnectionProvider = class {
930
2008
  throw err;
931
2009
  }
932
2010
  }
2011
+ /** Handle a single `nc.status()` event, emitting hooks and span events. */
2012
+ handleStatusEvent(status, nc) {
2013
+ switch (status.type) {
2014
+ case "disconnect":
2015
+ this.eventBus.emit("disconnect" /* Disconnect */);
2016
+ this.lifecycleSpan?.recordEvent(EVENT_CONNECTION_DISCONNECTED);
2017
+ break;
2018
+ case "reconnect":
2019
+ this.jsClient = null;
2020
+ this.jsmInstance = null;
2021
+ this.jsmPromise = null;
2022
+ this.eventBus.emit("reconnect" /* Reconnect */, nc.getServer());
2023
+ this.lifecycleSpan?.recordEvent(EVENT_CONNECTION_RECONNECTED, {
2024
+ [ATTR_NATS_CONNECTION_SERVER]: nc.getServer()
2025
+ });
2026
+ break;
2027
+ case "error":
2028
+ this.eventBus.emit(
2029
+ "error" /* Error */,
2030
+ status.error,
2031
+ "connection"
2032
+ );
2033
+ break;
2034
+ case "update":
2035
+ case "ldm":
2036
+ case "reconnecting":
2037
+ case "ping":
2038
+ case "staleConnection":
2039
+ case "forceReconnect":
2040
+ case "slowConsumer":
2041
+ case "close":
2042
+ break;
2043
+ default: {
2044
+ const _exhaustive = status;
2045
+ const unknown = _exhaustive.type ?? "unknown";
2046
+ this.logger.warn(`Unhandled NATS status event: ${unknown}`);
2047
+ }
2048
+ }
2049
+ }
933
2050
  /** Subscribe to connection status events and emit hooks. */
934
2051
  monitorStatus(nc) {
935
2052
  void (async () => {
936
- for await (const status of nc.status()) {
937
- switch (status.type) {
938
- case "disconnect":
939
- this.eventBus.emit("disconnect" /* Disconnect */);
940
- break;
941
- case "reconnect":
942
- this.jsClient = null;
943
- this.jsmInstance = null;
944
- this.jsmPromise = null;
945
- this.eventBus.emit("reconnect" /* Reconnect */, nc.getServer());
946
- break;
947
- case "error":
948
- this.eventBus.emit(
949
- "error" /* Error */,
950
- status.error,
951
- "connection"
952
- );
953
- break;
954
- case "update":
955
- case "ldm":
956
- case "reconnecting":
957
- case "ping":
958
- case "staleConnection":
959
- case "forceReconnect":
960
- case "slowConsumer":
961
- case "close":
962
- break;
2053
+ try {
2054
+ for await (const status of nc.status()) {
2055
+ this.handleStatusEvent(status, nc);
963
2056
  }
2057
+ } finally {
2058
+ this.lifecycleSpan?.finish();
2059
+ this.lifecycleSpan = null;
964
2060
  }
965
2061
  })().catch((err) => {
966
2062
  this.logger.error("Status monitor error", err);
@@ -972,8 +2068,8 @@ var ConnectionProvider = class {
972
2068
  var EventBus = class {
973
2069
  hooks;
974
2070
  logger;
975
- constructor(logger, hooks) {
976
- this.logger = logger;
2071
+ constructor(logger5, hooks) {
2072
+ this.logger = logger5;
977
2073
  this.hooks = hooks ?? {};
978
2074
  }
979
2075
  /**
@@ -1001,6 +2097,15 @@ var EventBus = class {
1001
2097
  kind
1002
2098
  );
1003
2099
  }
2100
+ /**
2101
+ * Check whether a hook is registered for the given event.
2102
+ *
2103
+ * Used by the routing hot path to elide the emit call entirely when the
2104
+ * transport owner did not register a listener.
2105
+ */
2106
+ hasHook(event) {
2107
+ return this.hooks[event] !== void 0;
2108
+ }
1004
2109
  callHook(event, hook, ...args) {
1005
2110
  try {
1006
2111
  const result = hook(...args);
@@ -1020,12 +2125,12 @@ var EventBus = class {
1020
2125
  };
1021
2126
 
1022
2127
  // src/health/jetstream.health-indicator.ts
1023
- import { Injectable, Logger as Logger3 } from "@nestjs/common";
2128
+ import { Injectable, Logger as Logger7 } from "@nestjs/common";
1024
2129
  var JetstreamHealthIndicator = class {
1025
2130
  constructor(connection) {
1026
2131
  this.connection = connection;
1027
2132
  }
1028
- logger = new Logger3("Jetstream:Health");
2133
+ logger = new Logger7("Jetstream:Health");
1029
2134
  /**
1030
2135
  * Plain health status check.
1031
2136
  *
@@ -1231,7 +2336,7 @@ var JetstreamStrategy = class extends Server {
1231
2336
  };
1232
2337
 
1233
2338
  // src/server/core-rpc.server.ts
1234
- import { Logger as Logger4 } from "@nestjs/common";
2339
+ import { Logger as Logger8 } from "@nestjs/common";
1235
2340
  import { headers as natsHeaders2 } from "@nats-io/transport-node";
1236
2341
 
1237
2342
  // src/context/rpc.context.ts
@@ -1241,9 +2346,6 @@ var RpcContext = class extends BaseRpcContext {
1241
2346
  _retryDelay;
1242
2347
  _shouldTerminate = false;
1243
2348
  _terminateReason;
1244
- // ---------------------------------------------------------------------------
1245
- // Message accessors
1246
- // ---------------------------------------------------------------------------
1247
2349
  /**
1248
2350
  * Get the underlying NATS message.
1249
2351
  *
@@ -1278,9 +2380,6 @@ var RpcContext = class extends BaseRpcContext {
1278
2380
  isJetStream() {
1279
2381
  return "ack" in this.args[0];
1280
2382
  }
1281
- // ---------------------------------------------------------------------------
1282
- // JetStream metadata (return undefined for Core NATS messages)
1283
- // ---------------------------------------------------------------------------
1284
2383
  /** How many times this message has been delivered. */
1285
2384
  getDeliveryCount() {
1286
2385
  return this.asJetStream()?.info.deliveryCount;
@@ -1302,9 +2401,6 @@ var RpcContext = class extends BaseRpcContext {
1302
2401
  getCallerName() {
1303
2402
  return this.getHeader("x-caller-name" /* CallerName */);
1304
2403
  }
1305
- // ---------------------------------------------------------------------------
1306
- // Handler-controlled settlement
1307
- // ---------------------------------------------------------------------------
1308
2404
  /**
1309
2405
  * Signal the transport to retry (nak) this message instead of acknowledging it.
1310
2406
  *
@@ -1349,9 +2445,6 @@ var RpcContext = class extends BaseRpcContext {
1349
2445
  throw new Error(`${method}() is only available for JetStream messages`);
1350
2446
  }
1351
2447
  }
1352
- // ---------------------------------------------------------------------------
1353
- // Transport-facing state (read by EventRouter)
1354
- // ---------------------------------------------------------------------------
1355
2448
  /** @internal */
1356
2449
  get shouldRetry() {
1357
2450
  return this._shouldRetry;
@@ -1383,16 +2476,71 @@ var resolveAckExtensionInterval = (config, ackWaitNanos) => {
1383
2476
  const interval = Math.floor(ackWaitNanos / 1e6 / 2);
1384
2477
  return Math.max(interval, MIN_ACK_EXTENSION_INTERVAL);
1385
2478
  };
2479
+ var AckExtensionPool = class {
2480
+ entries = /* @__PURE__ */ new Set();
2481
+ handle = null;
2482
+ handleFireAt = 0;
2483
+ schedule(msg, interval) {
2484
+ const entry = {
2485
+ msg,
2486
+ interval,
2487
+ nextFireAt: Date.now() + interval,
2488
+ active: true
2489
+ };
2490
+ this.entries.add(entry);
2491
+ this.ensureWake(entry.nextFireAt);
2492
+ return entry;
2493
+ }
2494
+ cancel(entry) {
2495
+ if (!entry.active) return;
2496
+ entry.active = false;
2497
+ this.entries.delete(entry);
2498
+ if (this.entries.size === 0 && this.handle !== null) {
2499
+ clearTimeout(this.handle);
2500
+ this.handle = null;
2501
+ this.handleFireAt = 0;
2502
+ }
2503
+ }
2504
+ /**
2505
+ * Ensure the shared timer will fire no later than `dueAt`. If an earlier
2506
+ * wake is already scheduled, leave it; otherwise replace it with a tighter one.
2507
+ */
2508
+ ensureWake(dueAt) {
2509
+ if (this.handle !== null && this.handleFireAt <= dueAt) return;
2510
+ if (this.handle !== null) clearTimeout(this.handle);
2511
+ const delay = Math.max(0, dueAt - Date.now());
2512
+ const handle = setTimeout(() => {
2513
+ this.tick();
2514
+ }, delay);
2515
+ if (typeof handle.unref === "function") handle.unref();
2516
+ this.handle = handle;
2517
+ this.handleFireAt = dueAt;
2518
+ }
2519
+ tick() {
2520
+ this.handle = null;
2521
+ this.handleFireAt = 0;
2522
+ const now = Date.now();
2523
+ let earliest = Infinity;
2524
+ for (const entry of this.entries) {
2525
+ if (!entry.active) continue;
2526
+ if (entry.nextFireAt <= now) {
2527
+ try {
2528
+ entry.msg.working();
2529
+ } catch {
2530
+ }
2531
+ entry.nextFireAt = now + entry.interval;
2532
+ }
2533
+ if (entry.nextFireAt < earliest) earliest = entry.nextFireAt;
2534
+ }
2535
+ if (earliest !== Infinity) this.ensureWake(earliest);
2536
+ }
2537
+ };
2538
+ var pool = new AckExtensionPool();
1386
2539
  var startAckExtensionTimer = (msg, interval) => {
1387
2540
  if (interval === null || interval <= 0) return null;
1388
- const timer2 = setInterval(() => {
1389
- try {
1390
- msg.working();
1391
- } catch {
1392
- }
1393
- }, interval);
2541
+ const entry = pool.schedule(msg, interval);
1394
2542
  return () => {
1395
- clearInterval(timer2);
2543
+ pool.cancel(entry);
1396
2544
  };
1397
2545
  };
1398
2546
 
@@ -1406,11 +2554,8 @@ var serializeError = (err) => {
1406
2554
 
1407
2555
  // src/utils/unwrap-result.ts
1408
2556
  import { isObservable } from "rxjs";
1409
- var RESOLVED_VOID = Promise.resolve(void 0);
1410
- var RESOLVED_NULL = Promise.resolve(null);
1411
2557
  var unwrapResult = (result) => {
1412
- if (result === void 0) return RESOLVED_VOID;
1413
- if (result === null) return RESOLVED_NULL;
2558
+ if (result === void 0 || result === null) return result;
1414
2559
  if (isObservable(result)) {
1415
2560
  return subscribeToFirst(result);
1416
2561
  }
@@ -1419,8 +2564,9 @@ var unwrapResult = (result) => {
1419
2564
  (resolved) => isObservable(resolved) ? subscribeToFirst(resolved) : resolved
1420
2565
  );
1421
2566
  }
1422
- return Promise.resolve(result);
2567
+ return result;
1423
2568
  };
2569
+ var isPromiseLike2 = (value) => value !== null && typeof value === "object" && typeof value.then === "function";
1424
2570
  var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
1425
2571
  let done = false;
1426
2572
  let subscription = null;
@@ -1450,20 +2596,25 @@ var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
1450
2596
  // src/server/core-rpc.server.ts
1451
2597
  var CoreRpcServer = class {
1452
2598
  constructor(options, connection, patternRegistry, codec, eventBus) {
1453
- this.options = options;
1454
2599
  this.connection = connection;
1455
2600
  this.patternRegistry = patternRegistry;
1456
2601
  this.codec = codec;
1457
2602
  this.eventBus = eventBus;
2603
+ const derived = deriveOtelAttrs(options);
2604
+ this.otel = derived.otel;
2605
+ this.serviceName = derived.serviceName;
2606
+ this.serverEndpoint = derived.serverEndpoint;
1458
2607
  }
1459
- logger = new Logger4("Jetstream:CoreRpc");
2608
+ logger = new Logger8("Jetstream:CoreRpc");
1460
2609
  subscription = null;
2610
+ otel;
2611
+ serviceName;
2612
+ serverEndpoint;
1461
2613
  /** Start listening for RPC requests on the command subject. */
1462
2614
  async start() {
1463
2615
  const nc = await this.connection.getConnection();
1464
- const serviceName = internalName(this.options.name);
1465
- const subject = `${serviceName}.cmd.>`;
1466
- const queue = `${serviceName}_cmd_queue`;
2616
+ const subject = `${this.serviceName}.cmd.>`;
2617
+ const queue = `${this.serviceName}_cmd_queue`;
1467
2618
  this.subscription = nc.subscribe(subject, {
1468
2619
  queue,
1469
2620
  callback: (err, msg) => {
@@ -1508,10 +2659,29 @@ var CoreRpcServer = class {
1508
2659
  }
1509
2660
  const ctx = new RpcContext([msg]);
1510
2661
  try {
1511
- const result = await unwrapResult(handler(data, ctx));
1512
- msg.respond(this.codec.encode(result));
2662
+ const raw = await withConsumeSpan(
2663
+ {
2664
+ subject: msg.subject,
2665
+ msg,
2666
+ kind: "rpc" /* Rpc */,
2667
+ payloadBytes: msg.data.length,
2668
+ handlerMetadata: { pattern: msg.subject },
2669
+ serviceName: this.serviceName,
2670
+ endpoint: this.serverEndpoint
2671
+ },
2672
+ this.otel,
2673
+ () => {
2674
+ const out = unwrapResult(handler(data, ctx));
2675
+ return isPromiseLike2(out) ? out : out;
2676
+ }
2677
+ );
2678
+ msg.respond(this.codec.encode(raw));
1513
2679
  } catch (err) {
1514
- this.logger.error(`Handler error for Core RPC ${msg.subject}:`, err);
2680
+ this.eventBus.emit(
2681
+ "error" /* Error */,
2682
+ err instanceof Error ? err : new Error(String(err)),
2683
+ `core-rpc-handler:${msg.subject}`
2684
+ );
1515
2685
  this.respondWithError(msg, err);
1516
2686
  }
1517
2687
  }
@@ -1528,9 +2698,17 @@ var CoreRpcServer = class {
1528
2698
  };
1529
2699
 
1530
2700
  // src/server/infrastructure/stream.provider.ts
1531
- import { Logger as Logger6 } from "@nestjs/common";
2701
+ import { Logger as Logger10 } from "@nestjs/common";
1532
2702
  import { JetStreamApiError as JetStreamApiError2 } from "@nats-io/jetstream";
1533
2703
 
2704
+ // src/server/infrastructure/nats-error-codes.ts
2705
+ var NatsErrorCode = /* @__PURE__ */ ((NatsErrorCode2) => {
2706
+ NatsErrorCode2[NatsErrorCode2["ConsumerNotFound"] = 10014] = "ConsumerNotFound";
2707
+ NatsErrorCode2[NatsErrorCode2["ConsumerAlreadyExists"] = 10148] = "ConsumerAlreadyExists";
2708
+ NatsErrorCode2[NatsErrorCode2["StreamNotFound"] = 10059] = "StreamNotFound";
2709
+ return NatsErrorCode2;
2710
+ })(NatsErrorCode || {});
2711
+
1534
2712
  // src/server/infrastructure/stream-config-diff.ts
1535
2713
  var TRANSPORT_CONTROLLED_PROPERTIES = /* @__PURE__ */ new Set([
1536
2714
  "retention"
@@ -1587,7 +2765,7 @@ var isEqual = (a, b) => {
1587
2765
  };
1588
2766
 
1589
2767
  // src/server/infrastructure/stream-migration.ts
1590
- import { Logger as Logger5 } from "@nestjs/common";
2768
+ import { Logger as Logger9 } from "@nestjs/common";
1591
2769
  import { JetStreamApiError } from "@nats-io/jetstream";
1592
2770
  var MIGRATION_BACKUP_SUFFIX = "__migration_backup";
1593
2771
  var DEFAULT_SOURCING_TIMEOUT_MS = 3e4;
@@ -1596,7 +2774,7 @@ var StreamMigration = class {
1596
2774
  constructor(sourcingTimeoutMs = DEFAULT_SOURCING_TIMEOUT_MS) {
1597
2775
  this.sourcingTimeoutMs = sourcingTimeoutMs;
1598
2776
  }
1599
- logger = new Logger5("Jetstream:Stream");
2777
+ logger = new Logger9("Jetstream:Stream");
1600
2778
  async migrate(jsm, streamName2, newConfig) {
1601
2779
  const backupName = `${streamName2}${MIGRATION_BACKUP_SUFFIX}`;
1602
2780
  const startTime = Date.now();
@@ -1678,9 +2856,16 @@ var StreamProvider = class {
1678
2856
  constructor(options, connection) {
1679
2857
  this.options = options;
1680
2858
  this.connection = connection;
2859
+ const derived = deriveOtelAttrs(options);
2860
+ this.otel = derived.otel;
2861
+ this.otelServiceName = derived.serviceName;
2862
+ this.otelEndpoint = derived.serverEndpoint;
1681
2863
  }
1682
- logger = new Logger6("Jetstream:Stream");
2864
+ logger = new Logger10("Jetstream:Stream");
1683
2865
  migration = new StreamMigration();
2866
+ otel;
2867
+ otelServiceName;
2868
+ otelEndpoint;
1684
2869
  /**
1685
2870
  * Ensure all required streams exist with correct configuration.
1686
2871
  *
@@ -1726,32 +2911,56 @@ var StreamProvider = class {
1726
2911
  /** Ensure a single stream exists, creating or updating as needed. */
1727
2912
  async ensureStream(jsm, kind) {
1728
2913
  const config = this.buildConfig(kind);
1729
- this.logger.log(`Ensuring stream: ${config.name}`);
1730
- try {
1731
- const currentInfo = await jsm.streams.info(config.name);
1732
- return await this.handleExistingStream(jsm, currentInfo, config);
1733
- } catch (err) {
1734
- if (err instanceof JetStreamApiError2 && err.apiError().err_code === 10059 /* StreamNotFound */) {
1735
- this.logger.log(`Creating stream: ${config.name}`);
1736
- return await jsm.streams.add(config);
2914
+ return withProvisioningSpan(
2915
+ this.otel,
2916
+ {
2917
+ serviceName: this.otelServiceName,
2918
+ endpoint: this.otelEndpoint,
2919
+ entity: "stream",
2920
+ name: config.name,
2921
+ action: "ensure"
2922
+ },
2923
+ async () => {
2924
+ this.logger.log(`Ensuring stream: ${config.name}`);
2925
+ try {
2926
+ const currentInfo = await jsm.streams.info(config.name);
2927
+ return await this.handleExistingStream(jsm, currentInfo, config);
2928
+ } catch (err) {
2929
+ if (err instanceof JetStreamApiError2 && err.apiError().err_code === 10059 /* StreamNotFound */) {
2930
+ this.logger.log(`Creating stream: ${config.name}`);
2931
+ return await jsm.streams.add(config);
2932
+ }
2933
+ throw err;
2934
+ }
1737
2935
  }
1738
- throw err;
1739
- }
2936
+ );
1740
2937
  }
1741
2938
  /** Ensure a dead-letter queue stream exists, creating or updating as needed. */
1742
2939
  async ensureDlqStream(jsm) {
1743
2940
  const config = this.buildDlqConfig();
1744
- this.logger.log(`Ensuring DLQ stream: ${config.name}`);
1745
- try {
1746
- const currentInfo = await jsm.streams.info(config.name);
1747
- return await this.handleExistingStream(jsm, currentInfo, config);
1748
- } catch (err) {
1749
- if (err instanceof JetStreamApiError2 && err.apiError().err_code === 10059 /* StreamNotFound */) {
1750
- this.logger.log(`Creating DLQ stream: ${config.name}`);
1751
- return await jsm.streams.add(config);
2941
+ return withProvisioningSpan(
2942
+ this.otel,
2943
+ {
2944
+ serviceName: this.otelServiceName,
2945
+ endpoint: this.otelEndpoint,
2946
+ entity: "stream",
2947
+ name: config.name,
2948
+ action: "ensure"
2949
+ },
2950
+ async () => {
2951
+ this.logger.log(`Ensuring DLQ stream: ${config.name}`);
2952
+ try {
2953
+ const currentInfo = await jsm.streams.info(config.name);
2954
+ return await this.handleExistingStream(jsm, currentInfo, config);
2955
+ } catch (err) {
2956
+ if (err instanceof JetStreamApiError2 && err.apiError().err_code === 10059 /* StreamNotFound */) {
2957
+ this.logger.log(`Creating DLQ stream: ${config.name}`);
2958
+ return await jsm.streams.add(config);
2959
+ }
2960
+ throw err;
2961
+ }
1752
2962
  }
1753
- throw err;
1754
- }
2963
+ );
1755
2964
  }
1756
2965
  async handleExistingStream(jsm, currentInfo, config) {
1757
2966
  const diff = compareStreamConfig(currentInfo.config, config);
@@ -1780,7 +2989,18 @@ var StreamProvider = class {
1780
2989
  }
1781
2990
  return currentInfo;
1782
2991
  }
1783
- await this.migration.migrate(jsm, config.name, config);
2992
+ await withMigrationSpan(
2993
+ this.otel,
2994
+ {
2995
+ serviceName: this.otelServiceName,
2996
+ endpoint: this.otelEndpoint,
2997
+ stream: config.name,
2998
+ reason: diff.changes.filter((c) => c.mutability === "immutable").map((c) => c.property).join(", ")
2999
+ },
3000
+ async () => {
3001
+ await this.migration.migrate(jsm, config.name, config);
3002
+ }
3003
+ );
1784
3004
  return await jsm.streams.info(config.name);
1785
3005
  }
1786
3006
  buildMutableOnlyConfig(config, currentConfig, diff) {
@@ -1896,7 +3116,7 @@ var StreamProvider = class {
1896
3116
  };
1897
3117
 
1898
3118
  // src/server/infrastructure/consumer.provider.ts
1899
- import { Logger as Logger7 } from "@nestjs/common";
3119
+ import { Logger as Logger11 } from "@nestjs/common";
1900
3120
  import { JetStreamApiError as JetStreamApiError3 } from "@nats-io/jetstream";
1901
3121
  var ConsumerProvider = class {
1902
3122
  constructor(options, connection, streamProvider, patternRegistry) {
@@ -1904,8 +3124,15 @@ var ConsumerProvider = class {
1904
3124
  this.connection = connection;
1905
3125
  this.streamProvider = streamProvider;
1906
3126
  this.patternRegistry = patternRegistry;
1907
- }
1908
- logger = new Logger7("Jetstream:Consumer");
3127
+ const derived = deriveOtelAttrs(options);
3128
+ this.otel = derived.otel;
3129
+ this.otelServiceName = derived.serviceName;
3130
+ this.otelEndpoint = derived.serverEndpoint;
3131
+ }
3132
+ logger = new Logger11("Jetstream:Consumer");
3133
+ otel;
3134
+ otelServiceName;
3135
+ otelEndpoint;
1909
3136
  /**
1910
3137
  * Ensure consumers exist for the specified kinds.
1911
3138
  *
@@ -1935,17 +3162,29 @@ var ConsumerProvider = class {
1935
3162
  const stream = this.streamProvider.getStreamName(kind);
1936
3163
  const config = this.buildConfig(kind);
1937
3164
  const name = config.durable_name;
1938
- this.logger.log(`Ensuring consumer: ${name} on stream: ${stream}`);
1939
- try {
1940
- await jsm.consumers.info(stream, name);
1941
- this.logger.debug(`Consumer exists, updating: ${name}`);
1942
- return await jsm.consumers.update(stream, name, config);
1943
- } catch (err) {
1944
- if (!(err instanceof JetStreamApiError3) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
1945
- throw err;
3165
+ return withProvisioningSpan(
3166
+ this.otel,
3167
+ {
3168
+ serviceName: this.otelServiceName,
3169
+ endpoint: this.otelEndpoint,
3170
+ entity: "consumer",
3171
+ name,
3172
+ action: "ensure"
3173
+ },
3174
+ async () => {
3175
+ this.logger.log(`Ensuring consumer: ${name} on stream: ${stream}`);
3176
+ try {
3177
+ await jsm.consumers.info(stream, name);
3178
+ this.logger.debug(`Consumer exists, updating: ${name}`);
3179
+ return await jsm.consumers.update(stream, name, config);
3180
+ } catch (err) {
3181
+ if (!(err instanceof JetStreamApiError3) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
3182
+ throw err;
3183
+ }
3184
+ return await this.createConsumer(jsm, stream, name, config);
3185
+ }
1946
3186
  }
1947
- return await this.createConsumer(jsm, stream, name, config);
1948
- }
3187
+ );
1949
3188
  }
1950
3189
  /**
1951
3190
  * Recover a consumer that disappeared during runtime.
@@ -1964,16 +3203,28 @@ var ConsumerProvider = class {
1964
3203
  const stream = this.streamProvider.getStreamName(kind);
1965
3204
  const config = this.buildConfig(kind);
1966
3205
  const name = config.durable_name;
1967
- this.logger.log(`Recovering consumer: ${name} on stream: ${stream}`);
1968
- await this.assertNoMigrationInProgress(jsm, stream);
1969
- try {
1970
- return await jsm.consumers.info(stream, name);
1971
- } catch (err) {
1972
- if (!(err instanceof JetStreamApiError3) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
1973
- throw err;
3206
+ return withProvisioningSpan(
3207
+ this.otel,
3208
+ {
3209
+ serviceName: this.otelServiceName,
3210
+ endpoint: this.otelEndpoint,
3211
+ entity: "consumer",
3212
+ name,
3213
+ action: "recover"
3214
+ },
3215
+ async () => {
3216
+ this.logger.log(`Recovering consumer: ${name} on stream: ${stream}`);
3217
+ await this.assertNoMigrationInProgress(jsm, stream);
3218
+ try {
3219
+ return await jsm.consumers.info(stream, name);
3220
+ } catch (err) {
3221
+ if (!(err instanceof JetStreamApiError3) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
3222
+ throw err;
3223
+ }
3224
+ return await this.createConsumer(jsm, stream, name, config);
3225
+ }
1974
3226
  }
1975
- return await this.createConsumer(jsm, stream, name, config);
1976
- }
3227
+ );
1977
3228
  }
1978
3229
  /**
1979
3230
  * Throw if a migration backup stream exists for this stream.
@@ -2089,7 +3340,7 @@ var ConsumerProvider = class {
2089
3340
  };
2090
3341
 
2091
3342
  // src/server/infrastructure/message.provider.ts
2092
- import { Logger as Logger8 } from "@nestjs/common";
3343
+ import { Logger as Logger12 } from "@nestjs/common";
2093
3344
  import { DeliverPolicy as DeliverPolicy2 } from "@nats-io/jetstream";
2094
3345
  import {
2095
3346
  catchError,
@@ -2109,7 +3360,7 @@ var MessageProvider = class {
2109
3360
  this.consumeOptionsMap = consumeOptionsMap;
2110
3361
  this.consumerRecoveryFn = consumerRecoveryFn;
2111
3362
  }
2112
- logger = new Logger8("Jetstream:Message");
3363
+ logger = new Logger12("Jetstream:Message");
2113
3364
  activeIterators = /* @__PURE__ */ new Set();
2114
3365
  orderedReadyResolve = null;
2115
3366
  orderedReadyReject = null;
@@ -2229,13 +3480,17 @@ var MessageProvider = class {
2229
3480
  }
2230
3481
  const defaults = { idle_heartbeat: 5e3 };
2231
3482
  const userOptions = this.consumeOptionsMap.get(kind) ?? {};
2232
- const messages = await consumer.consume({ ...defaults, ...userOptions });
3483
+ const messages = await consumer.consume({
3484
+ ...defaults,
3485
+ ...userOptions,
3486
+ callback: (msg) => {
3487
+ target$.next(msg);
3488
+ }
3489
+ });
2233
3490
  this.activeIterators.add(messages);
2234
3491
  this.monitorConsumerHealth(messages, consumerName2);
2235
3492
  try {
2236
- for await (const msg of messages) {
2237
- target$.next(msg);
2238
- }
3493
+ await messages.closed();
2239
3494
  } finally {
2240
3495
  this.activeIterators.delete(messages);
2241
3496
  }
@@ -2327,11 +3582,16 @@ var MessageProvider = class {
2327
3582
  })
2328
3583
  );
2329
3584
  }
2330
- /** Single iteration: create ordered consumer -> iterate messages. */
3585
+ /** Single iteration: create ordered consumer -> push messages into the subject. */
2331
3586
  async consumeOrderedOnce(streamName2, consumerOpts) {
2332
3587
  const js = this.connection.getJetStreamClient();
2333
3588
  const consumer = await js.consumers.get(streamName2, consumerOpts);
2334
- const messages = await consumer.consume();
3589
+ const orderedMessages$ = this.orderedMessages$;
3590
+ const messages = await consumer.consume({
3591
+ callback: (msg) => {
3592
+ orderedMessages$.next(msg);
3593
+ }
3594
+ });
2335
3595
  if (this.orderedReadyResolve) {
2336
3596
  this.orderedReadyResolve();
2337
3597
  this.orderedReadyResolve = null;
@@ -2339,9 +3599,7 @@ var MessageProvider = class {
2339
3599
  }
2340
3600
  this.activeIterators.add(messages);
2341
3601
  try {
2342
- for await (const msg of messages) {
2343
- this.orderedMessages$.next(msg);
2344
- }
3602
+ await messages.closed();
2345
3603
  } finally {
2346
3604
  this.activeIterators.delete(messages);
2347
3605
  }
@@ -2349,7 +3607,7 @@ var MessageProvider = class {
2349
3607
  };
2350
3608
 
2351
3609
  // src/server/infrastructure/metadata.provider.ts
2352
- import { Logger as Logger9 } from "@nestjs/common";
3610
+ import { Logger as Logger13 } from "@nestjs/common";
2353
3611
  import { Kvm } from "@nats-io/kv";
2354
3612
  var MetadataProvider = class {
2355
3613
  constructor(options, connection) {
@@ -2358,7 +3616,7 @@ var MetadataProvider = class {
2358
3616
  this.replicas = options.metadata?.replicas ?? DEFAULT_METADATA_REPLICAS;
2359
3617
  this.ttl = Math.max(options.metadata?.ttl ?? DEFAULT_METADATA_TTL, MIN_METADATA_TTL);
2360
3618
  }
2361
- logger = new Logger9("Jetstream:Metadata");
3619
+ logger = new Logger13("Jetstream:Metadata");
2362
3620
  bucketName;
2363
3621
  replicas;
2364
3622
  ttl;
@@ -2451,7 +3709,7 @@ var MetadataProvider = class {
2451
3709
  };
2452
3710
 
2453
3711
  // src/server/routing/pattern-registry.ts
2454
- import { Logger as Logger10 } from "@nestjs/common";
3712
+ import { Logger as Logger14 } from "@nestjs/common";
2455
3713
  var HANDLER_LABELS = {
2456
3714
  ["broadcast" /* Broadcast */]: "broadcast" /* Broadcast */,
2457
3715
  ["ordered" /* Ordered */]: "ordered" /* Ordered */,
@@ -2462,7 +3720,7 @@ var PatternRegistry = class {
2462
3720
  constructor(options) {
2463
3721
  this.options = options;
2464
3722
  }
2465
- logger = new Logger10("Jetstream:PatternRegistry");
3723
+ logger = new Logger14("Jetstream:PatternRegistry");
2466
3724
  registry = /* @__PURE__ */ new Map();
2467
3725
  // Cached after registerHandlers() — the registry is immutable from that point
2468
3726
  cachedPatterns = null;
@@ -2619,9 +3877,13 @@ var PatternRegistry = class {
2619
3877
  };
2620
3878
 
2621
3879
  // src/server/routing/event.router.ts
2622
- import { Logger as Logger11 } from "@nestjs/common";
2623
- import { concatMap, from as from2, mergeMap } from "rxjs";
3880
+ import { Logger as Logger15 } from "@nestjs/common";
2624
3881
  import { headers as natsHeaders3 } from "@nats-io/transport-node";
3882
+ var eventConsumeKindFor = (kind) => {
3883
+ if (kind === "broadcast" /* Broadcast */) return "broadcast" /* Broadcast */;
3884
+ if (kind === "ordered" /* Ordered */) return "ordered" /* Ordered */;
3885
+ return "event" /* Event */;
3886
+ };
2625
3887
  var EventRouter = class {
2626
3888
  constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig, processingConfig, ackWaitMap, connection, options) {
2627
3889
  this.messageProvider = messageProvider;
@@ -2633,9 +3895,22 @@ var EventRouter = class {
2633
3895
  this.ackWaitMap = ackWaitMap;
2634
3896
  this.connection = connection;
2635
3897
  this.options = options;
3898
+ if (options) {
3899
+ const derived = deriveOtelAttrs(options);
3900
+ this.otel = derived.otel;
3901
+ this.serviceName = derived.serviceName;
3902
+ this.serverEndpoint = derived.serverEndpoint;
3903
+ } else {
3904
+ this.otel = resolveOtelOptions({ enabled: false });
3905
+ this.serviceName = "";
3906
+ this.serverEndpoint = null;
3907
+ }
2636
3908
  }
2637
- logger = new Logger11("Jetstream:EventRouter");
3909
+ logger = new Logger15("Jetstream:EventRouter");
2638
3910
  subscriptions = [];
3911
+ otel;
3912
+ serviceName;
3913
+ serverEndpoint;
2639
3914
  /**
2640
3915
  * Update the max_deliver thresholds from actual NATS consumer configs.
2641
3916
  * Called after consumers are ensured so the DLQ map reflects reality.
@@ -2659,15 +3934,233 @@ var EventRouter = class {
2659
3934
  }
2660
3935
  this.subscriptions.length = 0;
2661
3936
  }
2662
- /** Subscribe to a message stream and route each message. */
3937
+ /** Subscribe to a message stream and route each message to its handler. */
2663
3938
  subscribeToStream(stream$, kind) {
2664
3939
  const isOrdered = kind === "ordered" /* Ordered */;
3940
+ const patternRegistry = this.patternRegistry;
3941
+ const codec = this.codec;
3942
+ const eventBus = this.eventBus;
3943
+ const logger5 = this.logger;
3944
+ const deadLetterConfig = this.deadLetterConfig;
3945
+ const otel = this.otel;
3946
+ const serviceName = this.serviceName;
3947
+ const serverEndpoint = this.serverEndpoint;
3948
+ const spanKind = eventConsumeKindFor(kind);
2665
3949
  const ackExtensionInterval = isOrdered ? null : resolveAckExtensionInterval(this.getAckExtensionConfig(kind), this.ackWaitMap?.get(kind));
3950
+ const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
2666
3951
  const concurrency = this.getConcurrency(kind);
2667
- const route = (msg) => from2(
2668
- isOrdered ? this.handleOrderedSafe(msg) : this.handleSafe(msg, ackExtensionInterval, kind)
2669
- );
2670
- const subscription = stream$.pipe(isOrdered ? concatMap(route) : mergeMap(route, concurrency)).subscribe();
3952
+ const hasDlqCheck = deadLetterConfig !== void 0;
3953
+ const emitRouted = eventBus.hasHook("messageRouted" /* MessageRouted */);
3954
+ const isDeadLetter = (msg) => {
3955
+ if (!hasDlqCheck) return false;
3956
+ const maxDeliver = deadLetterConfig.maxDeliverByStream?.get(msg.info.stream);
3957
+ if (maxDeliver === void 0 || maxDeliver <= 0) return false;
3958
+ return msg.info.deliveryCount >= maxDeliver;
3959
+ };
3960
+ const handleDeadLetter = hasDlqCheck ? (msg, data, err) => this.handleDeadLetter(msg, data, err) : null;
3961
+ const settleSuccess = (msg, ctx) => {
3962
+ if (ctx.shouldTerminate) msg.term(ctx.terminateReason);
3963
+ else if (ctx.shouldRetry) msg.nak(ctx.retryDelay);
3964
+ else msg.ack();
3965
+ };
3966
+ const settleFailure = async (msg, data, err) => {
3967
+ if (handleDeadLetter !== null && isDeadLetter(msg)) {
3968
+ await handleDeadLetter(msg, data, err);
3969
+ return;
3970
+ }
3971
+ msg.nak();
3972
+ };
3973
+ const resolveEvent = (msg) => {
3974
+ const subject = msg.subject;
3975
+ try {
3976
+ const handler = patternRegistry.getHandler(subject);
3977
+ if (!handler) {
3978
+ msg.term(`No handler for event: ${subject}`);
3979
+ logger5.error(`No handler for subject: ${subject}`);
3980
+ return null;
3981
+ }
3982
+ let data;
3983
+ try {
3984
+ data = codec.decode(msg.data);
3985
+ } catch (err) {
3986
+ msg.term("Decode error");
3987
+ logger5.error(`Decode error for ${subject}:`, err);
3988
+ return null;
3989
+ }
3990
+ if (emitRouted) eventBus.emitMessageRouted(subject, "event" /* Event */);
3991
+ return { handler, data };
3992
+ } catch (err) {
3993
+ logger5.error(`Unexpected error in ${kind} event router`, err);
3994
+ try {
3995
+ msg.term("Unexpected router error");
3996
+ } catch (termErr) {
3997
+ logger5.error(`Failed to terminate message ${subject}:`, termErr);
3998
+ }
3999
+ return null;
4000
+ }
4001
+ };
4002
+ const handleSafe = (msg) => {
4003
+ const resolved = resolveEvent(msg);
4004
+ if (resolved === null) return void 0;
4005
+ const { handler, data } = resolved;
4006
+ const ctx = new RpcContext([msg]);
4007
+ const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
4008
+ let pending;
4009
+ try {
4010
+ pending = withConsumeSpan(
4011
+ {
4012
+ subject: msg.subject,
4013
+ msg,
4014
+ info: msg.info,
4015
+ kind: spanKind,
4016
+ payloadBytes: msg.data.length,
4017
+ handlerMetadata: { pattern: msg.subject },
4018
+ serviceName,
4019
+ endpoint: serverEndpoint
4020
+ },
4021
+ otel,
4022
+ () => unwrapResult(handler(data, ctx))
4023
+ );
4024
+ } catch (err) {
4025
+ eventBus.emit(
4026
+ "error" /* Error */,
4027
+ err instanceof Error ? err : new Error(String(err)),
4028
+ `${kind}-handler:${msg.subject}`
4029
+ );
4030
+ return settleFailure(msg, data, err).finally(() => {
4031
+ if (stopAckExtension !== null) stopAckExtension();
4032
+ });
4033
+ }
4034
+ if (!isPromiseLike2(pending)) {
4035
+ settleSuccess(msg, ctx);
4036
+ if (stopAckExtension !== null) stopAckExtension();
4037
+ return void 0;
4038
+ }
4039
+ return pending.then(
4040
+ () => {
4041
+ settleSuccess(msg, ctx);
4042
+ if (stopAckExtension !== null) stopAckExtension();
4043
+ },
4044
+ async (err) => {
4045
+ eventBus.emit(
4046
+ "error" /* Error */,
4047
+ err instanceof Error ? err : new Error(String(err)),
4048
+ `${kind}-handler:${msg.subject}`
4049
+ );
4050
+ try {
4051
+ await settleFailure(msg, data, err);
4052
+ } finally {
4053
+ if (stopAckExtension !== null) stopAckExtension();
4054
+ }
4055
+ }
4056
+ );
4057
+ };
4058
+ const handleOrderedSafe = (msg) => {
4059
+ const subject = msg.subject;
4060
+ let handler;
4061
+ let data;
4062
+ try {
4063
+ handler = patternRegistry.getHandler(subject);
4064
+ if (!handler) {
4065
+ logger5.error(`No handler for subject: ${subject}`);
4066
+ return void 0;
4067
+ }
4068
+ try {
4069
+ data = codec.decode(msg.data);
4070
+ } catch (err) {
4071
+ logger5.error(`Decode error for ${subject}:`, err);
4072
+ return void 0;
4073
+ }
4074
+ if (emitRouted) eventBus.emitMessageRouted(subject, "event" /* Event */);
4075
+ } catch (err) {
4076
+ logger5.error(`Ordered handler error (${subject}):`, err);
4077
+ return void 0;
4078
+ }
4079
+ const ctx = new RpcContext([msg]);
4080
+ const warnIfSettlementAttempted = () => {
4081
+ if (ctx.shouldRetry || ctx.shouldTerminate) {
4082
+ logger5.warn(
4083
+ `retry()/terminate() ignored for ordered message ${subject} \u2014 ordered consumers auto-acknowledge`
4084
+ );
4085
+ }
4086
+ };
4087
+ let pending;
4088
+ try {
4089
+ pending = withConsumeSpan(
4090
+ {
4091
+ subject: msg.subject,
4092
+ msg,
4093
+ info: msg.info,
4094
+ kind: spanKind,
4095
+ payloadBytes: msg.data.length,
4096
+ handlerMetadata: { pattern: msg.subject },
4097
+ serviceName,
4098
+ endpoint: serverEndpoint
4099
+ },
4100
+ otel,
4101
+ () => unwrapResult(handler(data, ctx))
4102
+ );
4103
+ } catch (err) {
4104
+ logger5.error(`Ordered handler error (${subject}):`, err);
4105
+ return void 0;
4106
+ }
4107
+ if (!isPromiseLike2(pending)) {
4108
+ warnIfSettlementAttempted();
4109
+ return void 0;
4110
+ }
4111
+ return pending.then(warnIfSettlementAttempted, (err) => {
4112
+ logger5.error(`Ordered handler error (${subject}):`, err);
4113
+ });
4114
+ };
4115
+ const route = isOrdered ? handleOrderedSafe : handleSafe;
4116
+ const maxActive = isOrdered ? 1 : concurrency ?? Number.POSITIVE_INFINITY;
4117
+ const backlogWarnThreshold = 1e3;
4118
+ let active = 0;
4119
+ let backlogWarned = false;
4120
+ const backlog = [];
4121
+ const onAsyncDone = () => {
4122
+ active--;
4123
+ drainBacklog();
4124
+ };
4125
+ const drainBacklog = () => {
4126
+ while (active < maxActive) {
4127
+ const next = backlog.shift();
4128
+ if (next === void 0) return;
4129
+ active++;
4130
+ const result = route(next);
4131
+ if (result !== void 0) {
4132
+ void result.finally(onAsyncDone);
4133
+ } else {
4134
+ active--;
4135
+ }
4136
+ }
4137
+ if (backlog.length < backlogWarnThreshold) backlogWarned = false;
4138
+ };
4139
+ const subscription = stream$.subscribe({
4140
+ next: (msg) => {
4141
+ if (active >= maxActive) {
4142
+ backlog.push(msg);
4143
+ if (!backlogWarned && backlog.length >= backlogWarnThreshold) {
4144
+ backlogWarned = true;
4145
+ logger5.warn(
4146
+ `${kind} backlog reached ${backlog.length} messages \u2014 consumer may be falling behind`
4147
+ );
4148
+ }
4149
+ return;
4150
+ }
4151
+ active++;
4152
+ const result = route(msg);
4153
+ if (result !== void 0) {
4154
+ void result.finally(onAsyncDone);
4155
+ } else {
4156
+ active--;
4157
+ if (backlog.length > 0) drainBacklog();
4158
+ }
4159
+ },
4160
+ error: (err) => {
4161
+ logger5.error(`Stream error in ${kind} router`, err);
4162
+ }
4163
+ });
2671
4164
  this.subscriptions.push(subscription);
2672
4165
  }
2673
4166
  getConcurrency(kind) {
@@ -2680,94 +4173,11 @@ var EventRouter = class {
2680
4173
  if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.ackExtension;
2681
4174
  return void 0;
2682
4175
  }
2683
- /** Handle a single event message with error isolation. */
2684
- async handleSafe(msg, ackExtensionInterval, kind) {
2685
- try {
2686
- const resolved = this.decodeMessage(msg);
2687
- if (!resolved) return;
2688
- await this.executeHandler(
2689
- resolved.handler,
2690
- resolved.data,
2691
- resolved.ctx,
2692
- msg,
2693
- ackExtensionInterval
2694
- );
2695
- } catch (err) {
2696
- this.logger.error(`Unexpected error in ${kind} event router`, err);
2697
- }
2698
- }
2699
- /** Handle an ordered message with error isolation. */
2700
- async handleOrderedSafe(msg) {
2701
- try {
2702
- const resolved = this.decodeMessage(msg, true);
2703
- if (!resolved) return;
2704
- await unwrapResult(resolved.handler(resolved.data, resolved.ctx));
2705
- if (resolved.ctx.shouldRetry || resolved.ctx.shouldTerminate) {
2706
- this.logger.warn(
2707
- `retry()/terminate() ignored for ordered message ${msg.subject} \u2014 ordered consumers auto-acknowledge`
2708
- );
2709
- }
2710
- } catch (err) {
2711
- this.logger.error(`Ordered handler error (${msg.subject}):`, err);
2712
- }
2713
- }
2714
- /** Resolve handler, decode payload, and build context. Returns null on failure. */
2715
- decodeMessage(msg, isOrdered = false) {
2716
- const handler = this.patternRegistry.getHandler(msg.subject);
2717
- if (!handler) {
2718
- if (!isOrdered) msg.term(`No handler for event: ${msg.subject}`);
2719
- this.logger.error(`No handler for subject: ${msg.subject}`);
2720
- return null;
2721
- }
2722
- let data;
2723
- try {
2724
- data = this.codec.decode(msg.data);
2725
- } catch (err) {
2726
- if (!isOrdered) msg.term("Decode error");
2727
- this.logger.error(`Decode error for ${msg.subject}:`, err);
2728
- return null;
2729
- }
2730
- this.eventBus.emitMessageRouted(msg.subject, "event" /* Event */);
2731
- return { handler, data, ctx: new RpcContext([msg]) };
2732
- }
2733
- /** Execute handler, then ack on success or nak/dead-letter on failure. */
2734
- async executeHandler(handler, data, ctx, msg, ackExtensionInterval) {
2735
- const stopAckExtension = startAckExtensionTimer(msg, ackExtensionInterval);
2736
- try {
2737
- await unwrapResult(handler(data, ctx));
2738
- if (ctx.shouldTerminate) {
2739
- msg.term(ctx.terminateReason);
2740
- } else if (ctx.shouldRetry) {
2741
- msg.nak(ctx.retryDelay);
2742
- } else {
2743
- msg.ack();
2744
- }
2745
- } catch (err) {
2746
- this.logger.error(`Event handler error (${msg.subject}):`, err);
2747
- if (this.isDeadLetter(msg)) {
2748
- await this.handleDeadLetter(msg, data, err);
2749
- } else {
2750
- msg.nak();
2751
- }
2752
- } finally {
2753
- stopAckExtension?.();
2754
- }
2755
- }
2756
- /** Check if the message has exhausted all delivery attempts. */
2757
- isDeadLetter(msg) {
2758
- if (!this.deadLetterConfig) return false;
2759
- const maxDeliver = this.deadLetterConfig.maxDeliverByStream.get(msg.info.stream);
2760
- if (maxDeliver === void 0 || maxDeliver <= 0) return false;
2761
- return msg.info.deliveryCount >= maxDeliver;
2762
- }
2763
- /** Handle a dead letter: invoke callback, then term or nak based on result. */
2764
4176
  /**
2765
- * Fallback execution for a dead letter when DLQ is disabled, or when
2766
- * publishing to the DLQ stream fails (due to network or NATS errors).
2767
- *
2768
- * Triggers the user-provided `onDeadLetter` hook for logging/alerting.
2769
- * On success, terminates the message. On error, leaves it unacknowledged (nak)
2770
- * so NATS can retry the delivery on the next cycle.
4177
+ * Last-resort path for a dead letter: invoke `onDeadLetter`, then `term` on
4178
+ * success or `nak` on hook failure so NATS retries on the next delivery
4179
+ * cycle. Used when DLQ stream isn't configured, or when publishing to it
4180
+ * failed and we still have to surface the message somewhere observable.
2771
4181
  */
2772
4182
  async fallbackToOnDeadLetterCallback(info, msg) {
2773
4183
  if (!this.deadLetterConfig) {
@@ -2859,21 +4269,37 @@ var EventRouter = class {
2859
4269
  streamSequence: msg.info.streamSequence,
2860
4270
  timestamp: new Date(msg.info.timestampNanos / 1e6).toISOString()
2861
4271
  };
2862
- this.eventBus.emit("deadLetter" /* DeadLetter */, info);
2863
- if (!this.options?.dlq) {
2864
- await this.fallbackToOnDeadLetterCallback(info, msg);
2865
- } else {
2866
- await this.publishToDlq(msg, info, error);
2867
- }
4272
+ await withDeadLetterSpan(
4273
+ {
4274
+ msg,
4275
+ // Pattern resolution mirrors event-routing: when a registered
4276
+ // pattern matches, surface it on the DLQ span so APM can filter
4277
+ // dead letters by handler without parsing the subject. Falls back
4278
+ // to the subject itself when no glob handler is in play.
4279
+ pattern: this.patternRegistry.getHandler(msg.subject) ? msg.subject : void 0,
4280
+ finalDeliveryCount: msg.info.deliveryCount,
4281
+ reason: error instanceof Error ? error.message : String(error),
4282
+ serviceName: this.serviceName,
4283
+ endpoint: this.serverEndpoint
4284
+ },
4285
+ this.otel,
4286
+ async () => {
4287
+ this.eventBus.emit("deadLetter" /* DeadLetter */, info);
4288
+ if (!this.options?.dlq) {
4289
+ await this.fallbackToOnDeadLetterCallback(info, msg);
4290
+ } else {
4291
+ await this.publishToDlq(msg, info, error);
4292
+ }
4293
+ }
4294
+ );
2868
4295
  }
2869
4296
  };
2870
4297
 
2871
4298
  // src/server/routing/rpc.router.ts
2872
- import { Logger as Logger12 } from "@nestjs/common";
4299
+ import { Logger as Logger16 } from "@nestjs/common";
2873
4300
  import { headers } from "@nats-io/transport-node";
2874
- import { from as from3, mergeMap as mergeMap2 } from "rxjs";
2875
4301
  var RpcRouter = class {
2876
- constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap) {
4302
+ constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap, options) {
2877
4303
  this.messageProvider = messageProvider;
2878
4304
  this.patternRegistry = patternRegistry;
2879
4305
  this.connection = connection;
@@ -2883,13 +4309,26 @@ var RpcRouter = class {
2883
4309
  this.ackWaitMap = ackWaitMap;
2884
4310
  this.timeout = rpcOptions?.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT;
2885
4311
  this.concurrency = rpcOptions?.concurrency;
4312
+ if (options) {
4313
+ const derived = deriveOtelAttrs(options);
4314
+ this.otel = derived.otel;
4315
+ this.serviceName = derived.serviceName;
4316
+ this.serverEndpoint = derived.serverEndpoint;
4317
+ } else {
4318
+ this.otel = resolveOtelOptions({ enabled: false });
4319
+ this.serviceName = "";
4320
+ this.serverEndpoint = null;
4321
+ }
2886
4322
  }
2887
- logger = new Logger12("Jetstream:RpcRouter");
4323
+ logger = new Logger16("Jetstream:RpcRouter");
2888
4324
  timeout;
2889
4325
  concurrency;
2890
4326
  resolvedAckExtensionInterval;
2891
4327
  subscription = null;
2892
4328
  cachedNc = null;
4329
+ otel;
4330
+ serviceName;
4331
+ serverEndpoint;
2893
4332
  /** Lazily resolve the ack extension interval (needs ackWaitMap populated at runtime). */
2894
4333
  get ackExtensionInterval() {
2895
4334
  if (this.resolvedAckExtensionInterval !== void 0) return this.resolvedAckExtensionInterval;
@@ -2902,99 +4341,213 @@ var RpcRouter = class {
2902
4341
  /** Start routing command messages to handlers. */
2903
4342
  async start() {
2904
4343
  this.cachedNc = await this.connection.getConnection();
2905
- this.subscription = this.messageProvider.commands$.pipe(mergeMap2((msg) => from3(this.handleSafe(msg)), this.concurrency)).subscribe();
2906
- }
2907
- /** Stop routing and unsubscribe. */
2908
- destroy() {
2909
- this.subscription?.unsubscribe();
2910
- this.subscription = null;
2911
- }
2912
- /** Handle a single RPC command message with error isolation. */
2913
- async handleSafe(msg) {
2914
- try {
2915
- const handler = this.patternRegistry.getHandler(msg.subject);
2916
- if (!handler) {
2917
- msg.term(`No handler for RPC: ${msg.subject}`);
2918
- this.logger.error(`No handler for RPC subject: ${msg.subject}`);
2919
- return;
2920
- }
2921
- const { headers: msgHeaders } = msg;
2922
- const replyTo = msgHeaders?.get("x-reply-to" /* ReplyTo */);
2923
- const correlationId = msgHeaders?.get("x-correlation-id" /* CorrelationId */);
2924
- if (!replyTo || !correlationId) {
2925
- msg.term("Missing required headers (reply-to or correlation-id)");
2926
- this.logger.error(`Missing headers for RPC: ${msg.subject}`);
2927
- return;
2928
- }
2929
- let data;
2930
- try {
2931
- data = this.codec.decode(msg.data);
2932
- } catch (err) {
2933
- msg.term("Decode error");
2934
- this.logger.error(`Decode error for RPC ${msg.subject}:`, err);
2935
- return;
2936
- }
2937
- this.eventBus.emitMessageRouted(msg.subject, "rpc" /* Rpc */);
2938
- await this.executeHandler(handler, data, msg, replyTo, correlationId);
2939
- } catch (err) {
2940
- this.logger.error("Unexpected error in RPC router", err);
2941
- }
2942
- }
2943
- /** Execute handler, publish response, settle message. */
2944
- async executeHandler(handler, data, msg, replyTo, correlationId) {
2945
- const nc = this.cachedNc ?? await this.connection.getConnection();
2946
- const ctx = new RpcContext([msg]);
2947
- let settled = false;
2948
- const stopAckExtension = startAckExtensionTimer(msg, this.ackExtensionInterval);
2949
- const timeoutId = setTimeout(() => {
2950
- if (settled) return;
2951
- settled = true;
2952
- stopAckExtension?.();
2953
- this.logger.error(`RPC timeout (${this.timeout}ms): ${msg.subject}`);
2954
- this.eventBus.emit("rpcTimeout" /* RpcTimeout */, msg.subject, correlationId);
2955
- msg.term("Handler timeout");
2956
- }, this.timeout);
2957
- try {
2958
- const result = await unwrapResult(handler(data, ctx));
2959
- if (settled) return;
2960
- settled = true;
2961
- clearTimeout(timeoutId);
2962
- stopAckExtension?.();
2963
- msg.ack();
4344
+ const nc = this.cachedNc;
4345
+ const patternRegistry = this.patternRegistry;
4346
+ const codec = this.codec;
4347
+ const eventBus = this.eventBus;
4348
+ const logger5 = this.logger;
4349
+ const timeout = this.timeout;
4350
+ const ackExtensionInterval = this.ackExtensionInterval;
4351
+ const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
4352
+ const maxActive = this.concurrency ?? Number.POSITIVE_INFINITY;
4353
+ const otel = this.otel;
4354
+ const serviceName = this.serviceName;
4355
+ const serverEndpoint = this.serverEndpoint;
4356
+ const emitRpcTimeout = (subject, correlationId) => {
4357
+ eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
4358
+ };
4359
+ const publishReply = (replyTo, correlationId, payload) => {
2964
4360
  try {
2965
4361
  const hdrs = headers();
2966
4362
  hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
2967
- nc.publish(replyTo, this.codec.encode(result), { headers: hdrs });
4363
+ nc.publish(replyTo, codec.encode(payload), { headers: hdrs });
2968
4364
  } catch (publishErr) {
2969
- this.logger.error(`Failed to publish RPC response for ${msg.subject}`, publishErr);
4365
+ logger5.error(`Failed to publish RPC response`, publishErr);
2970
4366
  }
2971
- } catch (err) {
2972
- if (settled) return;
2973
- settled = true;
2974
- clearTimeout(timeoutId);
2975
- stopAckExtension?.();
4367
+ };
4368
+ const publishErrorReply = (replyTo, correlationId, subject, err) => {
2976
4369
  try {
2977
4370
  const hdrs = headers();
2978
4371
  hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
2979
4372
  hdrs.set("x-error" /* Error */, "true");
2980
- nc.publish(replyTo, this.codec.encode(serializeError(err)), { headers: hdrs });
4373
+ nc.publish(replyTo, codec.encode(serializeError(err)), { headers: hdrs });
2981
4374
  } catch (encodeErr) {
2982
- this.logger.error(`Failed to encode RPC error for ${msg.subject}`, encodeErr);
4375
+ logger5.error(`Failed to encode RPC error for ${subject}`, encodeErr);
2983
4376
  }
2984
- msg.term(`Handler error: ${msg.subject}`);
2985
- }
4377
+ };
4378
+ const resolveCommand = (msg) => {
4379
+ const subject = msg.subject;
4380
+ try {
4381
+ const handler = patternRegistry.getHandler(subject);
4382
+ if (!handler) {
4383
+ msg.term(`No handler for RPC: ${subject}`);
4384
+ logger5.error(`No handler for RPC subject: ${subject}`);
4385
+ return null;
4386
+ }
4387
+ const msgHeaders = msg.headers;
4388
+ const replyTo = msgHeaders?.get("x-reply-to" /* ReplyTo */);
4389
+ const correlationId = msgHeaders?.get("x-correlation-id" /* CorrelationId */);
4390
+ if (!replyTo || !correlationId) {
4391
+ msg.term("Missing required headers (reply-to or correlation-id)");
4392
+ logger5.error(`Missing headers for RPC: ${subject}`);
4393
+ return null;
4394
+ }
4395
+ let data;
4396
+ try {
4397
+ data = codec.decode(msg.data);
4398
+ } catch (err) {
4399
+ msg.term("Decode error");
4400
+ logger5.error(`Decode error for RPC ${subject}:`, err);
4401
+ return null;
4402
+ }
4403
+ eventBus.emitMessageRouted(subject, "rpc" /* Rpc */);
4404
+ return { handler, data, replyTo, correlationId };
4405
+ } catch (err) {
4406
+ logger5.error("Unexpected error in RPC router", err);
4407
+ try {
4408
+ msg.term("Unexpected router error");
4409
+ } catch (termErr) {
4410
+ logger5.error(`Failed to terminate RPC message ${subject}:`, termErr);
4411
+ }
4412
+ return null;
4413
+ }
4414
+ };
4415
+ const handleSafe = (msg) => {
4416
+ const resolved = resolveCommand(msg);
4417
+ if (resolved === null) return void 0;
4418
+ const { handler, data, replyTo, correlationId } = resolved;
4419
+ const subject = msg.subject;
4420
+ const ctx = new RpcContext([msg]);
4421
+ const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
4422
+ const reportHandlerError = (err) => {
4423
+ eventBus.emit(
4424
+ "error" /* Error */,
4425
+ err instanceof Error ? err : new Error(String(err)),
4426
+ `rpc-handler:${subject}`
4427
+ );
4428
+ publishErrorReply(replyTo, correlationId, subject, err);
4429
+ msg.term(`Handler error: ${subject}`);
4430
+ };
4431
+ const abortController = new AbortController();
4432
+ let pending;
4433
+ try {
4434
+ pending = withConsumeSpan(
4435
+ {
4436
+ subject,
4437
+ msg,
4438
+ info: msg.info,
4439
+ kind: "rpc" /* Rpc */,
4440
+ payloadBytes: msg.data.length,
4441
+ handlerMetadata: { pattern: subject },
4442
+ serviceName,
4443
+ endpoint: serverEndpoint
4444
+ },
4445
+ otel,
4446
+ () => unwrapResult(handler(data, ctx)),
4447
+ { signal: abortController.signal, timeoutLabel: "rpc.handler.timeout" }
4448
+ );
4449
+ } catch (err) {
4450
+ if (stopAckExtension !== null) stopAckExtension();
4451
+ reportHandlerError(err);
4452
+ return void 0;
4453
+ }
4454
+ if (!isPromiseLike2(pending)) {
4455
+ if (stopAckExtension !== null) stopAckExtension();
4456
+ msg.ack();
4457
+ publishReply(replyTo, correlationId, pending);
4458
+ return void 0;
4459
+ }
4460
+ let settled = false;
4461
+ const timeoutId = setTimeout(() => {
4462
+ if (settled) return;
4463
+ settled = true;
4464
+ if (stopAckExtension !== null) stopAckExtension();
4465
+ abortController.abort();
4466
+ emitRpcTimeout(subject, correlationId);
4467
+ msg.term("Handler timeout");
4468
+ }, timeout);
4469
+ return pending.then(
4470
+ (result) => {
4471
+ if (settled) return;
4472
+ settled = true;
4473
+ clearTimeout(timeoutId);
4474
+ if (stopAckExtension !== null) stopAckExtension();
4475
+ msg.ack();
4476
+ publishReply(replyTo, correlationId, result);
4477
+ },
4478
+ (err) => {
4479
+ if (settled) return;
4480
+ settled = true;
4481
+ clearTimeout(timeoutId);
4482
+ if (stopAckExtension !== null) stopAckExtension();
4483
+ reportHandlerError(err);
4484
+ }
4485
+ );
4486
+ };
4487
+ const backlogWarnThreshold = 1e3;
4488
+ let active = 0;
4489
+ let backlogWarned = false;
4490
+ const backlog = [];
4491
+ const onAsyncDone = () => {
4492
+ active--;
4493
+ drainBacklog();
4494
+ };
4495
+ const drainBacklog = () => {
4496
+ while (active < maxActive) {
4497
+ const next = backlog.shift();
4498
+ if (next === void 0) return;
4499
+ active++;
4500
+ const result = handleSafe(next);
4501
+ if (result !== void 0) {
4502
+ void result.finally(onAsyncDone);
4503
+ } else {
4504
+ active--;
4505
+ }
4506
+ }
4507
+ if (backlog.length < backlogWarnThreshold) backlogWarned = false;
4508
+ };
4509
+ this.subscription = this.messageProvider.commands$.subscribe({
4510
+ next: (msg) => {
4511
+ if (active >= maxActive) {
4512
+ backlog.push(msg);
4513
+ if (!backlogWarned && backlog.length >= backlogWarnThreshold) {
4514
+ backlogWarned = true;
4515
+ logger5.warn(
4516
+ `RPC backlog reached ${backlog.length} messages \u2014 consumer may be falling behind`
4517
+ );
4518
+ }
4519
+ return;
4520
+ }
4521
+ active++;
4522
+ const result = handleSafe(msg);
4523
+ if (result !== void 0) {
4524
+ void result.finally(onAsyncDone);
4525
+ } else {
4526
+ active--;
4527
+ if (backlog.length > 0) drainBacklog();
4528
+ }
4529
+ },
4530
+ error: (err) => {
4531
+ logger5.error("Stream error in RPC router", err);
4532
+ }
4533
+ });
4534
+ }
4535
+ /** Stop routing and unsubscribe. */
4536
+ destroy() {
4537
+ this.subscription?.unsubscribe();
4538
+ this.subscription = null;
2986
4539
  }
2987
4540
  };
2988
4541
 
2989
4542
  // src/shutdown/shutdown.manager.ts
2990
- import { Logger as Logger13 } from "@nestjs/common";
4543
+ import { Logger as Logger17 } from "@nestjs/common";
2991
4544
  var ShutdownManager = class {
2992
4545
  constructor(connection, eventBus, timeout) {
2993
4546
  this.connection = connection;
2994
4547
  this.eventBus = eventBus;
2995
4548
  this.timeout = timeout;
2996
4549
  }
2997
- logger = new Logger13("Jetstream:Shutdown");
4550
+ logger = new Logger17("Jetstream:Shutdown");
2998
4551
  shutdownPromise;
2999
4552
  /**
3000
4553
  * Execute the full shutdown sequence.
@@ -3034,9 +4587,6 @@ var JetstreamModule = class {
3034
4587
  this.shutdownManager = shutdownManager;
3035
4588
  this.strategy = strategy;
3036
4589
  }
3037
- // -------------------------------------------------------------------
3038
- // forRoot — global module registration
3039
- // -------------------------------------------------------------------
3040
4590
  /**
3041
4591
  * Register the JetStream transport globally.
3042
4592
  *
@@ -3063,9 +4613,6 @@ var JetstreamModule = class {
3063
4613
  ]
3064
4614
  };
3065
4615
  }
3066
- // -------------------------------------------------------------------
3067
- // forRootAsync — async global module registration
3068
- // -------------------------------------------------------------------
3069
4616
  /**
3070
4617
  * Register the JetStream transport globally with async configuration.
3071
4618
  *
@@ -3094,9 +4641,6 @@ var JetstreamModule = class {
3094
4641
  ]
3095
4642
  };
3096
4643
  }
3097
- // -------------------------------------------------------------------
3098
- // forFeature — per-module client registration
3099
- // -------------------------------------------------------------------
3100
4644
  /**
3101
4645
  * Register a lightweight client proxy for a target service.
3102
4646
  *
@@ -3122,9 +4666,6 @@ var JetstreamModule = class {
3122
4666
  exports: [clientToken]
3123
4667
  };
3124
4668
  }
3125
- // -------------------------------------------------------------------
3126
- // Provider factories
3127
- // -------------------------------------------------------------------
3128
4669
  static createCoreProviders(options) {
3129
4670
  return [
3130
4671
  {
@@ -3142,8 +4683,8 @@ var JetstreamModule = class {
3142
4683
  provide: JETSTREAM_EVENT_BUS,
3143
4684
  inject: [JETSTREAM_OPTIONS],
3144
4685
  useFactory: (options) => {
3145
- const logger = new Logger14("Jetstream:Module");
3146
- return new EventBus(logger, options.hooks);
4686
+ const logger5 = new Logger18("Jetstream:Module");
4687
+ return new EventBus(logger5, options.hooks);
3147
4688
  }
3148
4689
  },
3149
4690
  // Codec — global encode/decode
@@ -3182,10 +4723,8 @@ var JetstreamModule = class {
3182
4723
  );
3183
4724
  }
3184
4725
  },
3185
- // ---------------------------------------------------------------
3186
4726
  // Consumer infrastructure — only created when consumer !== false.
3187
4727
  // Providers return null when consumer is disabled (publisher-only mode).
3188
- // ---------------------------------------------------------------
3189
4728
  // PatternRegistry — subject-to-handler mapping
3190
4729
  {
3191
4730
  provide: PatternRegistry,
@@ -3221,8 +4760,14 @@ var JetstreamModule = class {
3221
4760
  // MessageProvider — pull-based message consumption
3222
4761
  {
3223
4762
  provide: MessageProvider,
3224
- inject: [JETSTREAM_OPTIONS, JETSTREAM_CONNECTION, JETSTREAM_EVENT_BUS, ConsumerProvider],
3225
- useFactory: (options, connection, eventBus, consumerProvider) => {
4763
+ inject: [
4764
+ JETSTREAM_OPTIONS,
4765
+ JETSTREAM_CONNECTION,
4766
+ JETSTREAM_EVENT_BUS,
4767
+ ConsumerProvider,
4768
+ StreamProvider
4769
+ ],
4770
+ useFactory: (options, connection, eventBus, consumerProvider, streamProvider) => {
3226
4771
  if (options.consumer === false) return null;
3227
4772
  const consumeOptionsMap = /* @__PURE__ */ new Map();
3228
4773
  if (options.events?.consume)
@@ -3232,10 +4777,22 @@ var JetstreamModule = class {
3232
4777
  if (options.rpc?.mode === "jetstream" && options.rpc.consume) {
3233
4778
  consumeOptionsMap.set("cmd" /* Command */, options.rpc.consume);
3234
4779
  }
3235
- const consumerRecoveryFn = consumerProvider ? async (kind) => {
3236
- const jsm = await connection.getJetStreamManager();
3237
- return consumerProvider.recoverConsumer(jsm, kind);
3238
- } : void 0;
4780
+ const derived = deriveOtelAttrs(options);
4781
+ const { otel, serverEndpoint: otelEndpoint, serviceName: otelServiceName } = derived;
4782
+ const consumerRecoveryFn = consumerProvider && streamProvider ? async (kind) => withSelfHealingSpan(
4783
+ otel,
4784
+ {
4785
+ serviceName: otelServiceName,
4786
+ endpoint: otelEndpoint,
4787
+ consumer: consumerProvider.getConsumerName(kind),
4788
+ stream: streamProvider.getStreamName(kind),
4789
+ reason: "consumer not found"
4790
+ },
4791
+ async () => {
4792
+ const jsm = await connection.getJetStreamManager();
4793
+ return consumerProvider.recoverConsumer(jsm, kind);
4794
+ }
4795
+ ) : void 0;
3239
4796
  return new MessageProvider(connection, eventBus, consumeOptionsMap, consumerRecoveryFn);
3240
4797
  }
3241
4798
  },
@@ -3306,7 +4863,8 @@ var JetstreamModule = class {
3306
4863
  codec,
3307
4864
  eventBus,
3308
4865
  rpcOptions,
3309
- ackWaitMap
4866
+ ackWaitMap,
4867
+ options
3310
4868
  );
3311
4869
  }
3312
4870
  },
@@ -3409,9 +4967,6 @@ var JetstreamModule = class {
3409
4967
  }
3410
4968
  ];
3411
4969
  }
3412
- // -------------------------------------------------------------------
3413
- // Lifecycle hooks
3414
- // -------------------------------------------------------------------
3415
4970
  /**
3416
4971
  * Gracefully shut down the transport on application termination.
3417
4972
  */
@@ -3430,14 +4985,25 @@ JetstreamModule = __decorateClass([
3430
4985
  __decorateParam(1, Inject(JetstreamStrategy))
3431
4986
  ], JetstreamModule);
3432
4987
  export {
4988
+ ConsumeKind,
4989
+ DEFAULT_BROADCAST_CONSUMER_CONFIG,
4990
+ DEFAULT_BROADCAST_STREAM_CONFIG,
4991
+ DEFAULT_COMMAND_CONSUMER_CONFIG,
4992
+ DEFAULT_COMMAND_STREAM_CONFIG,
4993
+ DEFAULT_DLQ_STREAM_CONFIG,
4994
+ DEFAULT_EVENT_CONSUMER_CONFIG,
4995
+ DEFAULT_EVENT_STREAM_CONFIG,
4996
+ DEFAULT_JETSTREAM_RPC_TIMEOUT,
3433
4997
  DEFAULT_METADATA_BUCKET,
3434
4998
  DEFAULT_METADATA_HISTORY,
3435
4999
  DEFAULT_METADATA_REPLICAS,
3436
5000
  DEFAULT_METADATA_TTL,
3437
- EventBus,
5001
+ DEFAULT_ORDERED_STREAM_CONFIG,
5002
+ DEFAULT_RPC_TIMEOUT,
5003
+ DEFAULT_SHUTDOWN_TIMEOUT,
5004
+ DEFAULT_TRACES,
3438
5005
  JETSTREAM_CODEC,
3439
5006
  JETSTREAM_CONNECTION,
3440
- JETSTREAM_EVENT_BUS,
3441
5007
  JETSTREAM_OPTIONS,
3442
5008
  JetstreamClient,
3443
5009
  JetstreamDlqHeader,
@@ -3447,12 +5013,18 @@ export {
3447
5013
  JetstreamRecord,
3448
5014
  JetstreamRecordBuilder,
3449
5015
  JetstreamStrategy,
5016
+ JetstreamTrace,
3450
5017
  JsonCodec,
3451
5018
  MIN_METADATA_TTL,
3452
5019
  MessageKind,
5020
+ MsgpackCodec,
5021
+ NatsErrorCode,
3453
5022
  PatternPrefix,
5023
+ PublishKind,
5024
+ RESERVED_HEADERS,
3454
5025
  RpcContext,
3455
5026
  StreamKind,
5027
+ TRACER_NAME,
3456
5028
  TransportEvent,
3457
5029
  buildBroadcastSubject,
3458
5030
  buildSubject,