@horizon-republic/nestjs-jetstream 2.12.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -71,6 +71,7 @@ __export(index_exports, {
71
71
  JetstreamTrace: () => JetstreamTrace,
72
72
  JsonCodec: () => JsonCodec,
73
73
  MIN_METADATA_TTL: () => MIN_METADATA_TTL,
74
+ ManagementMode: () => ManagementMode,
74
75
  MessageKind: () => MessageKind,
75
76
  MsgpackCodec: () => MsgpackCodec,
76
77
  NatsErrorCode: () => NatsErrorCode,
@@ -96,14 +97,14 @@ __export(index_exports, {
96
97
  module.exports = __toCommonJS(index_exports);
97
98
 
98
99
  // src/jetstream.module.ts
99
- var import_common21 = require("@nestjs/common");
100
+ var import_common24 = require("@nestjs/common");
100
101
 
101
102
  // src/client/jetstream.client.ts
102
- var import_common5 = require("@nestjs/common");
103
+ var import_common6 = require("@nestjs/common");
103
104
  var import_microservices = require("@nestjs/microservices");
104
105
  var import_api8 = require("@opentelemetry/api");
105
106
  var import_nuid = require("@nats-io/nuid");
106
- var import_transport_node = require("@nats-io/transport-node");
107
+ var import_transport_node2 = require("@nats-io/transport-node");
107
108
 
108
109
  // src/interfaces/hooks.interface.ts
109
110
  var MessageKind = /* @__PURE__ */ ((MessageKind2) => {
@@ -128,6 +129,13 @@ var TransportEvent = /* @__PURE__ */ ((TransportEvent2) => {
128
129
  return TransportEvent2;
129
130
  })(TransportEvent || {});
130
131
 
132
+ // src/interfaces/options.interface.ts
133
+ var ManagementMode = /* @__PURE__ */ ((ManagementMode2) => {
134
+ ManagementMode2["Auto"] = "auto";
135
+ ManagementMode2["Manual"] = "manual";
136
+ return ManagementMode2;
137
+ })(ManagementMode || {});
138
+
131
139
  // src/interfaces/stream.interface.ts
132
140
  var StreamKind = /* @__PURE__ */ ((StreamKind2) => {
133
141
  StreamKind2["Event"] = "ev";
@@ -277,8 +285,27 @@ var RESERVED_HEADERS = /* @__PURE__ */ new Set([
277
285
  ]);
278
286
  var NATS_CONTROL_HEADER_PREFIX = "nats-";
279
287
  var internalName = (name) => `${name}__microservice`;
280
- var buildSubject = (serviceName, kind, pattern) => `${internalName(serviceName)}.${kind}.${pattern}`;
281
- var buildBroadcastSubject = (pattern) => `broadcast.${pattern}`;
288
+ var subjectPrefix = (serviceName, kind) => `${internalName(serviceName)}.${kind}.`;
289
+ var buildSubject = (serviceName, kind, pattern) => `${subjectPrefix(serviceName, kind)}${pattern}`;
290
+ var BROADCAST_SUBJECT_PREFIX = "broadcast.";
291
+ var SCHEDULE_SEGMENT = "_sch.";
292
+ var buildBroadcastSubject = (pattern) => `${BROADCAST_SUBJECT_PREFIX}${pattern}`;
293
+ var conventionScheduleSubjectBase = (targetName, eventSubject) => {
294
+ if (eventSubject.startsWith(BROADCAST_SUBJECT_PREFIX)) {
295
+ const bare = eventSubject.slice(BROADCAST_SUBJECT_PREFIX.length);
296
+ return `${BROADCAST_SUBJECT_PREFIX}${SCHEDULE_SEGMENT}${bare}`;
297
+ }
298
+ const targetPrefix = `${internalName(targetName)}.`;
299
+ if (!eventSubject.startsWith(targetPrefix)) {
300
+ throw new Error(`Unexpected event subject format: ${eventSubject}`);
301
+ }
302
+ const withoutPrefix = eventSubject.slice(targetPrefix.length);
303
+ const dotIndex = withoutPrefix.indexOf(".");
304
+ if (dotIndex === -1) {
305
+ throw new Error(`Event subject missing pattern segment: ${eventSubject}`);
306
+ }
307
+ return `${targetPrefix}${SCHEDULE_SEGMENT}${withoutPrefix.slice(dotIndex + 1)}`;
308
+ };
282
309
  var streamName = (serviceName, kind) => {
283
310
  if (kind === "broadcast" /* Broadcast */) return "broadcast-stream";
284
311
  return `${internalName(serviceName)}_${kind}-stream`;
@@ -298,6 +325,118 @@ var PatternPrefix = /* @__PURE__ */ ((PatternPrefix2) => {
298
325
  var isJetStreamRpcMode = (rpc) => rpc?.mode === "jetstream";
299
326
  var isCoreRpcMode = (rpc) => !rpc || rpc.mode === "core";
300
327
 
328
+ // src/server/infrastructure/management.ts
329
+ var kindOptionsBlock = (options, kind) => {
330
+ switch (kind) {
331
+ case "ev" /* Event */:
332
+ return options.events;
333
+ case "broadcast" /* Broadcast */:
334
+ return options.broadcast;
335
+ case "ordered" /* Ordered */:
336
+ return options.ordered;
337
+ case "cmd" /* Command */:
338
+ return isJetStreamRpcMode(options.rpc) ? options.rpc : void 0;
339
+ /* v8 ignore next 5 -- exhaustive switch guard, unreachable */
340
+ default: {
341
+ const _exhaustive = kind;
342
+ throw new Error(`Unhandled StreamKind: ${String(_exhaustive)}`);
343
+ }
344
+ }
345
+ };
346
+ var entityManagementFor = (options, kind) => {
347
+ if (kind === "dlq") return options.dlq?.management;
348
+ return kindOptionsBlock(options, kind)?.management;
349
+ };
350
+ var resolveManagementMode = (options, kind, entity) => entityManagementFor(options, kind)?.[entity] ?? options.provisioning?.management ?? "auto" /* Auto */;
351
+
352
+ // src/server/infrastructure/name-resolver.ts
353
+ var NameResolver = class {
354
+ constructor(options) {
355
+ this.options = options;
356
+ this.dlq = options.dlq?.stream?.name ?? dlqStreamName(options.name);
357
+ this.kinds = this.buildKindMap();
358
+ }
359
+ kinds;
360
+ dlq;
361
+ streamName(kind) {
362
+ return this.get(kind).stream;
363
+ }
364
+ consumerName(kind) {
365
+ return this.get(kind).consumer;
366
+ }
367
+ dlqStreamName() {
368
+ return this.dlq;
369
+ }
370
+ subject(kind, pattern) {
371
+ return `${this.get(kind).prefix}${pattern}`;
372
+ }
373
+ filterSubject(kind) {
374
+ return `${this.get(kind).prefix}>`;
375
+ }
376
+ schedulePrefix(kind) {
377
+ return this.get(kind).schedulePrefix;
378
+ }
379
+ hasCustomPrefix(kind) {
380
+ return this.get(kind).custom;
381
+ }
382
+ /**
383
+ * Map a resolved event subject back to its schedule-holder base subject
384
+ * (the `_sch` namespace twin, without the per-message unique suffix).
385
+ */
386
+ scheduleSubjectBase(eventSubject) {
387
+ for (const kind of ["broadcast" /* Broadcast */, "ev" /* Event */, "ordered" /* Ordered */]) {
388
+ const { prefix, schedulePrefix } = this.get(kind);
389
+ if (eventSubject.startsWith(prefix)) {
390
+ return `${schedulePrefix}${eventSubject.slice(prefix.length)}`;
391
+ }
392
+ }
393
+ throw new Error(`Unexpected event subject format: ${eventSubject}`);
394
+ }
395
+ get(kind) {
396
+ const entry = this.kinds.get(kind);
397
+ if (!entry) throw new Error(`Unknown StreamKind: ${String(kind)}`);
398
+ return entry;
399
+ }
400
+ buildKindMap() {
401
+ const map = /* @__PURE__ */ new Map();
402
+ const { name } = this.options;
403
+ for (const kind of Object.values(StreamKind)) {
404
+ const block = kindOptionsBlock(this.options, kind);
405
+ const customPrefix = block?.subjectPrefix;
406
+ const custom = customPrefix !== void 0;
407
+ const prefix = custom ? this.normalizePrefix(customPrefix) : this.conventionPrefix(name, kind);
408
+ map.set(kind, {
409
+ stream: block?.stream?.name ?? streamName(name, kind),
410
+ consumer: block?.consumer?.durable_name ?? consumerName(name, kind),
411
+ prefix,
412
+ schedulePrefix: custom ? `${prefix}${SCHEDULE_SEGMENT}` : this.conventionSchedulePrefix(name, kind),
413
+ custom
414
+ });
415
+ }
416
+ return map;
417
+ }
418
+ normalizePrefix(raw) {
419
+ let end = raw.length;
420
+ while (end > 0 && raw[end - 1] === ".") end -= 1;
421
+ const trimmed = raw.slice(0, end);
422
+ const tokens = trimmed.split(".");
423
+ if (trimmed.length === 0 || tokens.some((t) => t.length === 0 || t === ">")) {
424
+ throw new Error(
425
+ `Invalid subjectPrefix "${raw}": expected non-empty dot-separated tokens without ">".`
426
+ );
427
+ }
428
+ return `${trimmed}.`;
429
+ }
430
+ conventionPrefix(name, kind) {
431
+ if (kind === "broadcast" /* Broadcast */) return BROADCAST_SUBJECT_PREFIX;
432
+ return subjectPrefix(name, kind);
433
+ }
434
+ conventionSchedulePrefix(name, kind) {
435
+ if (kind === "broadcast" /* Broadcast */) return `${BROADCAST_SUBJECT_PREFIX}${SCHEDULE_SEGMENT}`;
436
+ return `${internalName(name)}.${SCHEDULE_SEGMENT}`;
437
+ }
438
+ };
439
+
301
440
  // src/otel/constants.ts
302
441
  var TRACER_NAME = "@horizon-republic/nestjs-jetstream";
303
442
 
@@ -598,7 +737,7 @@ var extractContext = (ctx, carrier, getter) => import_api.propagation.extract(ct
598
737
 
599
738
  // src/otel/tracer.ts
600
739
  var import_api2 = require("@opentelemetry/api");
601
- var PACKAGE_VERSION = true ? "2.12.0" : "0.0.0";
740
+ var PACKAGE_VERSION = true ? "2.13.0" : "0.0.0";
602
741
  var getTracer = () => import_api2.trace.getTracer(TRACER_NAME, PACKAGE_VERSION);
603
742
 
604
743
  // src/otel/carrier.ts
@@ -1391,17 +1530,135 @@ var nanosToGoDuration = (nanos) => {
1391
1530
  return `${nanos}ns`;
1392
1531
  };
1393
1532
 
1533
+ // src/client/rpc-reply-inbox.ts
1534
+ var import_common5 = require("@nestjs/common");
1535
+ var import_transport_node = require("@nats-io/transport-node");
1536
+ var RpcReplyInbox = class {
1537
+ constructor(codec, inboxPrefix) {
1538
+ this.codec = codec;
1539
+ this.inboxPrefix = inboxPrefix;
1540
+ }
1541
+ logger = new import_common5.Logger("Jetstream:RpcInbox");
1542
+ inbox = null;
1543
+ subscription = null;
1544
+ pending = /* @__PURE__ */ new Map();
1545
+ timeouts = /* @__PURE__ */ new Map();
1546
+ /** Reply-to subject for outgoing requests; null until {@link setup} ran. */
1547
+ get address() {
1548
+ return this.inbox;
1549
+ }
1550
+ /** True while the inbox subscription is live. */
1551
+ get active() {
1552
+ return this.subscription !== null;
1553
+ }
1554
+ /** Create the inbox subject and subscribe reply routing on it. */
1555
+ setup(nc) {
1556
+ this.inbox = (0, import_transport_node.createInbox)(this.inboxPrefix);
1557
+ this.subscription = nc.subscribe(this.inbox, {
1558
+ callback: (err, msg) => {
1559
+ if (err) {
1560
+ this.logger.error("Inbox subscription error:", err);
1561
+ return;
1562
+ }
1563
+ this.route(msg);
1564
+ }
1565
+ });
1566
+ this.logger.debug(`Inbox subscription: ${this.inbox}`);
1567
+ }
1568
+ /** Register the callback that settles the round-trip for `correlationId`. */
1569
+ register(correlationId, callback) {
1570
+ this.pending.set(correlationId, callback);
1571
+ }
1572
+ /**
1573
+ * Arm the request deadline. When it fires while the request is still
1574
+ * pending, both registry entries are removed before `onExpired` runs.
1575
+ */
1576
+ armTimeout(correlationId, ms, onExpired) {
1577
+ const existing = this.timeouts.get(correlationId);
1578
+ if (existing !== void 0) clearTimeout(existing);
1579
+ const timeoutId = setTimeout(() => {
1580
+ if (!this.pending.has(correlationId)) return;
1581
+ this.timeouts.delete(correlationId);
1582
+ this.pending.delete(correlationId);
1583
+ onExpired();
1584
+ }, ms);
1585
+ this.timeouts.set(correlationId, timeoutId);
1586
+ }
1587
+ /** True while the request has not been settled or discarded. */
1588
+ has(correlationId) {
1589
+ return this.pending.has(correlationId);
1590
+ }
1591
+ /**
1592
+ * Drop a request without invoking its callback: clears the deadline and
1593
+ * returns whether the request was still pending.
1594
+ */
1595
+ discard(correlationId) {
1596
+ const timeoutId = this.timeouts.get(correlationId);
1597
+ if (timeoutId !== void 0) {
1598
+ clearTimeout(timeoutId);
1599
+ this.timeouts.delete(correlationId);
1600
+ }
1601
+ return this.pending.delete(correlationId);
1602
+ }
1603
+ /** Fail every pending request with `error` and tear the inbox down. */
1604
+ rejectAll(error) {
1605
+ for (const callback of this.pending.values()) {
1606
+ callback({ err: error, response: null, isDisposed: true });
1607
+ }
1608
+ for (const timeoutId of this.timeouts.values()) {
1609
+ clearTimeout(timeoutId);
1610
+ }
1611
+ this.pending.clear();
1612
+ this.timeouts.clear();
1613
+ this.subscription?.unsubscribe();
1614
+ this.subscription = null;
1615
+ this.inbox = null;
1616
+ }
1617
+ /** Route an inbox reply to the matching pending callback. */
1618
+ route(msg) {
1619
+ const correlationId = msg.headers?.get("x-correlation-id" /* CorrelationId */);
1620
+ if (!correlationId) {
1621
+ this.logger.warn("Inbox reply without correlation-id, ignoring");
1622
+ return;
1623
+ }
1624
+ const callback = this.pending.get(correlationId);
1625
+ if (!callback) {
1626
+ this.logger.warn(`No pending handler for correlation-id: ${correlationId}`);
1627
+ return;
1628
+ }
1629
+ const timeoutId = this.timeouts.get(correlationId);
1630
+ if (timeoutId) {
1631
+ clearTimeout(timeoutId);
1632
+ this.timeouts.delete(correlationId);
1633
+ }
1634
+ try {
1635
+ const decoded = this.codec.decode(msg.data);
1636
+ if (msg.headers?.get("x-error" /* Error */)) {
1637
+ callback({ err: decoded, response: null, isDisposed: true });
1638
+ } else {
1639
+ callback({ err: null, response: decoded, isDisposed: true });
1640
+ }
1641
+ } catch (err) {
1642
+ callback({
1643
+ err: err instanceof Error ? err : new Error("Decode error"),
1644
+ response: null,
1645
+ isDisposed: true
1646
+ });
1647
+ } finally {
1648
+ this.pending.delete(correlationId);
1649
+ }
1650
+ }
1651
+ };
1652
+
1394
1653
  // src/client/jetstream.client.ts
1395
- var BROADCAST_SUBJECT_PREFIX = "broadcast.";
1396
1654
  var detectEventKind = (pattern) => {
1397
1655
  if (pattern.startsWith("broadcast:" /* Broadcast */)) return "broadcast" /* Broadcast */;
1398
1656
  if (pattern.startsWith("ordered:" /* Ordered */)) return "ordered" /* Ordered */;
1399
1657
  return "event" /* Event */;
1400
1658
  };
1401
- var declaredEventPattern = (pattern) => {
1402
- if (pattern.startsWith("broadcast:" /* Broadcast */))
1403
- return pattern.slice("broadcast:" /* Broadcast */.length);
1404
- if (pattern.startsWith("ordered:" /* Ordered */)) return pattern.slice("ordered:" /* Ordered */.length);
1659
+ var declaredEventPattern = (pattern, kind) => {
1660
+ if (kind === "broadcast" /* Broadcast */) return pattern.slice("broadcast:" /* Broadcast */.length);
1661
+ if (kind === "ordered" /* Ordered */) return pattern.slice("ordered:" /* Ordered */.length);
1405
1662
  return pattern;
1406
1663
  };
1407
1664
  var eventStreamKind = (kind) => {
@@ -1410,7 +1667,7 @@ var eventStreamKind = (kind) => {
1410
1667
  return "ev" /* Event */;
1411
1668
  };
1412
1669
  var JetstreamClient = class extends import_microservices.ClientProxy {
1413
- constructor(rootOptions, targetServiceName, connection, codec, eventBus) {
1670
+ constructor(rootOptions, targetServiceName, connection, codec, eventBus, names) {
1414
1671
  super();
1415
1672
  this.rootOptions = rootOptions;
1416
1673
  this.connection = connection;
@@ -1418,29 +1675,34 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1418
1675
  this.eventBus = eventBus;
1419
1676
  this.targetName = targetServiceName;
1420
1677
  this.callerName = internalName(this.rootOptions.name);
1678
+ const isSelf = targetServiceName === rootOptions.name;
1679
+ const ownNames = names ?? (isSelf ? new NameResolver(rootOptions) : null);
1680
+ this.selfNames = isSelf ? ownNames : null;
1681
+ this.broadcastPrefix = ownNames ? ownNames.subject("broadcast" /* Broadcast */, "") : BROADCAST_SUBJECT_PREFIX;
1421
1682
  const targetInternal = internalName(targetServiceName);
1422
1683
  this.eventSubjectPrefix = `${targetInternal}.${"ev" /* Event */}.`;
1423
1684
  this.commandSubjectPrefix = `${targetInternal}.${"cmd" /* Command */}.`;
1424
1685
  this.orderedSubjectPrefix = `${targetInternal}.${"ordered" /* Ordered */}.`;
1686
+ this.rpcInbox = new RpcReplyInbox(codec, this.callerName);
1425
1687
  this.isCoreMode = isCoreRpcMode(this.rootOptions.rpc);
1426
- this.defaultRpcTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? this.rootOptions.rpc?.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT : this.rootOptions.rpc?.timeout ?? DEFAULT_RPC_TIMEOUT;
1688
+ this.defaultRpcTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? this.rootOptions.rpc.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT : this.rootOptions.rpc?.timeout ?? DEFAULT_RPC_TIMEOUT;
1427
1689
  const derived = deriveOtelAttrs(this.rootOptions);
1428
1690
  this.otel = derived.otel;
1429
1691
  this.serverEndpoint = derived.serverEndpoint;
1430
1692
  }
1431
- logger = new import_common5.Logger("Jetstream:Client");
1693
+ logger = new import_common6.Logger("Jetstream:Client");
1432
1694
  /** Target service name this client sends messages to. */
1433
1695
  targetName;
1434
1696
  /** Pre-cached caller name derived from rootOptions.name, computed once in constructor. */
1435
1697
  callerName;
1436
- /**
1437
- * Subject prefixes of the form `{serviceName}__microservice.{kind}.` — one
1438
- * per stream kind this client may publish to. Built once in the constructor
1439
- * so producing a full subject is a single string concat with the user pattern.
1440
- */
1698
+ /** Convention subject prefixes for foreign targets; one per stream kind. */
1441
1699
  eventSubjectPrefix;
1442
1700
  commandSubjectPrefix;
1443
1701
  orderedSubjectPrefix;
1702
+ /** Broadcast subject prefix derived from the own resolver; never null. */
1703
+ broadcastPrefix;
1704
+ /** Resolver for self-target subjects; null when targeting a foreign service. */
1705
+ selfNames;
1444
1706
  /**
1445
1707
  * RPC configuration snapshots. The values are derived from rootOptions at
1446
1708
  * construction time so the publish hot path never has to re-run
@@ -1452,13 +1714,8 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1452
1714
  otel;
1453
1715
  /** Server endpoint parts used for `server.address` / `server.port` span attributes. */
1454
1716
  serverEndpoint;
1455
- /** Shared inbox for JetStream-mode RPC responses. */
1456
- inbox = null;
1457
- inboxSubscription = null;
1458
- /** Pending JetStream-mode RPC callbacks, keyed by correlation ID. */
1459
- pendingMessages = /* @__PURE__ */ new Map();
1460
- /** Pending JetStream-mode RPC timeouts, keyed by correlation ID. */
1461
- pendingTimeouts = /* @__PURE__ */ new Map();
1717
+ /** Reply inbox and pending-request registry for JetStream-mode RPC. */
1718
+ rpcInbox;
1462
1719
  /** Subscription to connection status events for disconnect handling. */
1463
1720
  statusSubscription = null;
1464
1721
  /**
@@ -1477,8 +1734,8 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1477
1734
  */
1478
1735
  async connect() {
1479
1736
  const nc = await this.connection.getConnection();
1480
- if (!this.isCoreMode && !this.inboxSubscription) {
1481
- this.setupInbox(nc);
1737
+ if (!this.isCoreMode && !this.rpcInbox.active) {
1738
+ this.rpcInbox.setup(nc);
1482
1739
  }
1483
1740
  this.statusSubscription ??= this.connection.status$.subscribe((status) => {
1484
1741
  if (status.type === "disconnect") {
@@ -1493,7 +1750,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1493
1750
  this.statusSubscription?.unsubscribe();
1494
1751
  this.statusSubscription = null;
1495
1752
  this.readyForPublish = false;
1496
- this.rejectPendingRpcs(new Error("Client closed"));
1753
+ this.rpcInbox.rejectAll(new Error("Client closed"));
1497
1754
  }
1498
1755
  /**
1499
1756
  * Direct access to the raw NATS connection.
@@ -1503,7 +1760,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1503
1760
  unwrap() {
1504
1761
  const nc = this.connection.unwrap;
1505
1762
  if (!nc) {
1506
- throw new Error("Not connected \u2014 call connect() before unwrap()");
1763
+ throw new Error("Not connected; call connect() before unwrap()");
1507
1764
  }
1508
1765
  return nc;
1509
1766
  }
@@ -1530,7 +1787,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1530
1787
  const encoded = this.codec.encode(data);
1531
1788
  const effectiveMsgId = messageId ?? import_nuid.nuid.next();
1532
1789
  const record = packet.data instanceof JetstreamRecord ? packet.data : new JetstreamRecord(data, /* @__PURE__ */ new Map());
1533
- const declaredPattern = declaredEventPattern(packet.pattern);
1790
+ const declaredPattern = declaredEventPattern(packet.pattern, publishKind);
1534
1791
  const streamKind = eventStreamKind(publishKind);
1535
1792
  const startedAt = performance.now();
1536
1793
  try {
@@ -1550,13 +1807,6 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1550
1807
  },
1551
1808
  this.otel,
1552
1809
  async () => {
1553
- const warnIfDuplicate = (kindLabel, ack2) => {
1554
- if (ack2.duplicate) {
1555
- this.logger.warn(
1556
- `Duplicate ${kindLabel} publish detected: ${publishSubject} (seq: ${ack2.seq})`
1557
- );
1558
- }
1559
- };
1560
1810
  if (schedule) {
1561
1811
  const ack2 = await this.connection.getJetStreamClient().publish(publishSubject, encoded, {
1562
1812
  headers: msgHeaders,
@@ -1567,7 +1817,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1567
1817
  ttl
1568
1818
  }
1569
1819
  });
1570
- warnIfDuplicate("scheduled", ack2);
1820
+ this.warnIfDuplicate("scheduled", publishSubject, ack2);
1571
1821
  return;
1572
1822
  }
1573
1823
  const ack = await this.connection.getJetStreamClient().publish(publishSubject, encoded, {
@@ -1575,7 +1825,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1575
1825
  msgID: effectiveMsgId,
1576
1826
  ttl
1577
1827
  });
1578
- warnIfDuplicate("event", ack);
1828
+ this.warnIfDuplicate("event", publishSubject, ack);
1579
1829
  }
1580
1830
  );
1581
1831
  this.reportPublished(declaredPattern, streamKind, startedAt, "success");
@@ -1592,7 +1842,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1592
1842
  * JetStream mode: publishes to stream + waits for inbox response.
1593
1843
  */
1594
1844
  publish(packet, callback) {
1595
- const subject = this.commandSubjectPrefix + packet.pattern;
1845
+ const subject = this.selfNames ? this.selfNames.subject("cmd" /* Command */, packet.pattern) : this.commandSubjectPrefix + packet.pattern;
1596
1846
  const { data, hdrs, timeout, messageId, schedule, ttl } = this.extractRecordData(packet.data);
1597
1847
  if (schedule) {
1598
1848
  this.logger.warn(
@@ -1625,12 +1875,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1625
1875
  }
1626
1876
  return () => {
1627
1877
  if (jetStreamCorrelationId) {
1628
- const timeoutId = this.pendingTimeouts.get(jetStreamCorrelationId);
1629
- if (timeoutId) {
1630
- clearTimeout(timeoutId);
1631
- this.pendingTimeouts.delete(jetStreamCorrelationId);
1632
- }
1633
- this.pendingMessages.delete(jetStreamCorrelationId);
1878
+ this.rpcInbox.discard(jetStreamCorrelationId);
1634
1879
  }
1635
1880
  };
1636
1881
  }
@@ -1674,7 +1919,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1674
1919
  }
1675
1920
  } catch (err) {
1676
1921
  const error = err instanceof Error ? err : new Error("Unknown error");
1677
- if (error instanceof import_transport_node.TimeoutError) {
1922
+ if (error instanceof import_transport_node2.TimeoutError) {
1678
1923
  spanHandle.finish({ kind: "timeout" /* Timeout */ });
1679
1924
  this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, "");
1680
1925
  this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "success");
@@ -1695,7 +1940,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1695
1940
  const hdrs = this.buildHeaders(customHeaders, {
1696
1941
  subject,
1697
1942
  correlationId,
1698
- replyTo: this.inbox ?? ""
1943
+ replyTo: this.rpcInbox.address ?? ""
1699
1944
  });
1700
1945
  const encoded = this.codec.encode(data);
1701
1946
  const spanHandle = beginRpcClientSpan(
@@ -1712,7 +1957,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1712
1957
  this.otel
1713
1958
  );
1714
1959
  const startedAt = performance.now();
1715
- this.pendingMessages.set(correlationId, (packet) => {
1960
+ const settleRoundTrip = (packet) => {
1716
1961
  if (packet.err) {
1717
1962
  if (packet.err instanceof Error) {
1718
1963
  spanHandle.finish({ kind: "error" /* Error */, error: packet.err });
@@ -1725,30 +1970,25 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1725
1970
  this.reportRpcCompleted(declaredPattern, startedAt, "success");
1726
1971
  }
1727
1972
  callback(packet);
1728
- });
1729
- const timeoutId = setTimeout(() => {
1730
- if (!this.pendingMessages.has(correlationId)) return;
1731
- this.pendingTimeouts.delete(correlationId);
1732
- this.pendingMessages.delete(correlationId);
1973
+ };
1974
+ this.rpcInbox.register(correlationId, settleRoundTrip);
1975
+ this.rpcInbox.armTimeout(correlationId, effectiveTimeout, () => {
1733
1976
  spanHandle.finish({ kind: "timeout" /* Timeout */ });
1734
1977
  this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
1735
1978
  this.reportRpcCompleted(declaredPattern, startedAt, "timeout");
1736
1979
  callback({ err: new Error(RPC_TIMEOUT_MESSAGE), response: null, isDisposed: true });
1737
- }, effectiveTimeout);
1738
- this.pendingTimeouts.set(correlationId, timeoutId);
1980
+ });
1739
1981
  try {
1740
1982
  if (!this.readyForPublish) await this.connect();
1741
- if (!this.pendingMessages.has(correlationId)) return;
1742
- if (!this.inbox) {
1743
- clearTimeout(timeoutId);
1744
- this.pendingTimeouts.delete(correlationId);
1745
- this.pendingMessages.delete(correlationId);
1983
+ if (!this.rpcInbox.has(correlationId)) return;
1984
+ if (!this.rpcInbox.address) {
1985
+ this.rpcInbox.discard(correlationId);
1746
1986
  const inboxError = new Error("Inbox not initialized");
1747
1987
  spanHandle.finish({ kind: "error" /* Error */, error: inboxError });
1748
1988
  this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "error");
1749
1989
  this.reportRpcCompleted(declaredPattern, startedAt, "error");
1750
1990
  callback({
1751
- err: new Error("Inbox not initialized \u2014 JetStream RPC mode requires a connected inbox"),
1991
+ err: new Error("Inbox not initialized; JetStream RPC mode requires a connected inbox"),
1752
1992
  response: null,
1753
1993
  isDisposed: true
1754
1994
  });
@@ -1768,13 +2008,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1768
2008
  }
1769
2009
  this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "success");
1770
2010
  } catch (err) {
1771
- const existingTimeout = this.pendingTimeouts.get(correlationId);
1772
- if (existingTimeout) {
1773
- clearTimeout(existingTimeout);
1774
- this.pendingTimeouts.delete(correlationId);
1775
- }
1776
- if (!this.pendingMessages.has(correlationId)) return;
1777
- this.pendingMessages.delete(correlationId);
2011
+ if (!this.rpcInbox.discard(correlationId)) return;
1778
2012
  const error = err instanceof Error ? err : new Error("Unknown error");
1779
2013
  spanHandle.finish({ kind: "error" /* Error */, error });
1780
2014
  this.eventBus.emit("error" /* Error */, error, `jetstream-rpc-publish:${subject}`);
@@ -1783,6 +2017,11 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1783
2017
  callback({ err: error, response: null, isDisposed: true });
1784
2018
  }
1785
2019
  }
2020
+ warnIfDuplicate(kindLabel, subject, ack) {
2021
+ if (ack.duplicate) {
2022
+ this.logger.warn(`Duplicate ${kindLabel} publish detected: ${subject} (seq: ${ack.seq})`);
2023
+ }
2024
+ }
1786
2025
  // hasHook is per-emit so late subscribers (JetstreamMetricsService during
1787
2026
  // OnApplicationBootstrap) still receive events.
1788
2027
  reportPublished(declaredPattern, kind, startedAt, status) {
@@ -1806,92 +2045,28 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1806
2045
  }
1807
2046
  /** Fail-fast all pending JetStream RPC callbacks on connection loss. */
1808
2047
  handleDisconnect() {
1809
- this.rejectPendingRpcs(new Error("Connection lost"));
1810
- this.inbox = null;
2048
+ this.rpcInbox.rejectAll(new Error("Connection lost"));
1811
2049
  this.readyForPublish = false;
1812
2050
  }
1813
- /** Reject all pending RPC callbacks, clear timeouts, and tear down inbox. */
1814
- rejectPendingRpcs(error) {
1815
- for (const callback of this.pendingMessages.values()) {
1816
- callback({ err: error, response: null, isDisposed: true });
1817
- }
1818
- for (const timeoutId of this.pendingTimeouts.values()) {
1819
- clearTimeout(timeoutId);
1820
- }
1821
- this.pendingMessages.clear();
1822
- this.pendingTimeouts.clear();
1823
- this.inboxSubscription?.unsubscribe();
1824
- this.inboxSubscription = null;
1825
- this.inbox = null;
1826
- }
1827
- /** Setup shared inbox subscription for JetStream RPC responses. */
1828
- setupInbox(nc) {
1829
- this.inbox = (0, import_transport_node.createInbox)(internalName(this.rootOptions.name));
1830
- this.inboxSubscription = nc.subscribe(this.inbox, {
1831
- callback: (err, msg) => {
1832
- if (err) {
1833
- this.logger.error("Inbox subscription error:", err);
1834
- return;
1835
- }
1836
- this.routeInboxReply(msg);
1837
- }
1838
- });
1839
- this.logger.debug(`Inbox subscription: ${this.inbox}`);
1840
- }
1841
- /** Route an inbox reply to the matching pending callback. */
1842
- routeInboxReply(msg) {
1843
- const correlationId = msg.headers?.get("x-correlation-id" /* CorrelationId */);
1844
- if (!correlationId) {
1845
- this.logger.warn("Inbox reply without correlation-id, ignoring");
1846
- return;
1847
- }
1848
- const callback = this.pendingMessages.get(correlationId);
1849
- if (!callback) {
1850
- this.logger.warn(`No pending handler for correlation-id: ${correlationId}`);
1851
- return;
1852
- }
1853
- const timeoutId = this.pendingTimeouts.get(correlationId);
1854
- if (timeoutId) {
1855
- clearTimeout(timeoutId);
1856
- this.pendingTimeouts.delete(correlationId);
1857
- }
1858
- try {
1859
- const decoded = this.codec.decode(msg.data);
1860
- if (msg.headers?.get("x-error" /* Error */)) {
1861
- callback({ err: decoded, response: null, isDisposed: true });
1862
- } else {
1863
- callback({ err: null, response: decoded, isDisposed: true });
1864
- }
1865
- } catch (err) {
1866
- callback({
1867
- err: err instanceof Error ? err : new Error("Decode error"),
1868
- response: null,
1869
- isDisposed: true
1870
- });
1871
- } finally {
1872
- this.pendingMessages.delete(correlationId);
1873
- }
1874
- }
1875
2051
  /**
1876
- * Resolve a user pattern to a fully-qualified NATS subject, dispatching
1877
- * between the event, broadcast, and ordered prefixes.
1878
- *
1879
- * The leading-char check short-circuits the `startsWith` comparisons for
1880
- * patterns that cannot possibly carry a broadcast/ordered marker, which is
1881
- * the overwhelmingly common case.
2052
+ * Resolve a user pattern to a fully-qualified NATS subject. Self-targets go
2053
+ * through the resolver so custom subjectPrefix options are honoured.
1882
2054
  */
1883
2055
  buildEventSubject(pattern) {
1884
- if (pattern.charCodeAt(0) === 98 && pattern.startsWith("broadcast:" /* Broadcast */)) {
1885
- return BROADCAST_SUBJECT_PREFIX + pattern.slice("broadcast:" /* Broadcast */.length);
2056
+ if (pattern.startsWith("broadcast:" /* Broadcast */)) {
2057
+ return this.broadcastPrefix + pattern.slice("broadcast:" /* Broadcast */.length);
1886
2058
  }
1887
- if (pattern.charCodeAt(0) === 111 && pattern.startsWith("ordered:" /* Ordered */)) {
1888
- return this.orderedSubjectPrefix + pattern.slice("ordered:" /* Ordered */.length);
2059
+ if (pattern.startsWith("ordered:" /* Ordered */)) {
2060
+ const bare = pattern.slice("ordered:" /* Ordered */.length);
2061
+ if (this.selfNames) return this.selfNames.subject("ordered" /* Ordered */, bare);
2062
+ return this.orderedSubjectPrefix + bare;
1889
2063
  }
2064
+ if (this.selfNames) return this.selfNames.subject("ev" /* Event */, pattern);
1890
2065
  return this.eventSubjectPrefix + pattern;
1891
2066
  }
1892
2067
  /** Build NATS headers merging custom headers with transport headers. */
1893
2068
  buildHeaders(customHeaders, transport) {
1894
- const hdrs = (0, import_transport_node.headers)();
2069
+ const hdrs = (0, import_transport_node2.headers)();
1895
2070
  hdrs.set("x-subject" /* Subject */, transport.subject);
1896
2071
  hdrs.set("x-caller-name" /* CallerName */, this.callerName);
1897
2072
  if (transport.correlationId) {
@@ -1931,33 +2106,16 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
1931
2106
  /**
1932
2107
  * Build a schedule-holder subject for NATS message scheduling.
1933
2108
  *
1934
- * The schedule-holder subject resides in the same stream as the target but
1935
- * uses a separate `_sch` namespace that is NOT matched by any consumer filter.
1936
- * NATS holds the message and publishes it to the target subject after the delay.
1937
- *
1938
- * A unique per-message suffix is appended because the server stores schedules
1939
- * as rollup messages one active schedule per subject (ADR-51). Without it,
1940
- * concurrent schedules of the same pattern would silently replace each other.
1941
- *
1942
- * Examples:
1943
- * - `{svc}__microservice.ev.order.reminder` → `{svc}__microservice._sch.order.reminder.<nuid>`
1944
- * - `broadcast.config.updated` → `broadcast._sch.config.updated.<nuid>`
2109
+ * The holder lives in the same stream as the target but under the `_sch`
2110
+ * namespace no consumer filter matches; the server publishes it to the
2111
+ * target subject when the schedule fires. The unique per-message suffix
2112
+ * exists because the server stores schedules as rollup messages, one
2113
+ * active schedule per subject (ADR-51): without it, concurrent schedules
2114
+ * of the same pattern would silently replace each other.
1945
2115
  */
1946
2116
  buildScheduleSubject(eventSubject) {
1947
- if (eventSubject.startsWith("broadcast.")) {
1948
- return `${eventSubject.replace("broadcast.", "broadcast._sch.")}.${import_nuid.nuid.next()}`;
1949
- }
1950
- const targetPrefix = `${internalName(this.targetName)}.`;
1951
- if (!eventSubject.startsWith(targetPrefix)) {
1952
- throw new Error(`Unexpected event subject format: ${eventSubject}`);
1953
- }
1954
- const withoutPrefix = eventSubject.slice(targetPrefix.length);
1955
- const dotIndex = withoutPrefix.indexOf(".");
1956
- if (dotIndex === -1) {
1957
- throw new Error(`Event subject missing pattern segment: ${eventSubject}`);
1958
- }
1959
- const pattern = withoutPrefix.slice(dotIndex + 1);
1960
- return `${targetPrefix}_sch.${pattern}.${import_nuid.nuid.next()}`;
2117
+ const base = this.selfNames ? this.selfNames.scheduleSubjectBase(eventSubject) : conventionScheduleSubjectBase(this.targetName, eventSubject);
2118
+ return `${base}.${import_nuid.nuid.next()}`;
1961
2119
  }
1962
2120
  };
1963
2121
 
@@ -1989,9 +2147,9 @@ var MsgpackCodec = class {
1989
2147
  };
1990
2148
 
1991
2149
  // src/connection/connection.provider.ts
1992
- var import_common6 = require("@nestjs/common");
1993
- var import_transport_node2 = require("@nats-io/transport-node");
1994
- var import_jetstream8 = require("@nats-io/jetstream");
2150
+ var import_common7 = require("@nestjs/common");
2151
+ var import_transport_node3 = require("@nats-io/transport-node");
2152
+ var import_jetstream11 = require("@nats-io/jetstream");
1995
2153
  var import_rxjs = require("rxjs");
1996
2154
  var DEFAULT_OPTIONS = {
1997
2155
  maxReconnectAttempts: -1,
@@ -2017,7 +2175,7 @@ var ConnectionProvider = class {
2017
2175
  nc$;
2018
2176
  /** Live stream of connection status events (no replay). */
2019
2177
  status$;
2020
- logger = new import_common6.Logger("Jetstream:Connection");
2178
+ logger = new import_common7.Logger("Jetstream:Connection");
2021
2179
  connection = null;
2022
2180
  connectionPromise = null;
2023
2181
  jsClient = null;
@@ -2028,7 +2186,7 @@ var ConnectionProvider = class {
2028
2186
  otelEndpoint;
2029
2187
  lifecycleSpan = null;
2030
2188
  /**
2031
- * Establish NATS connection. Idempotent returns cached connection on subsequent calls.
2189
+ * Establish NATS connection. Idempotent: returns cached connection on subsequent calls.
2032
2190
  *
2033
2191
  * @throws Error if connection is refused (fail fast).
2034
2192
  */
@@ -2067,9 +2225,9 @@ var ConnectionProvider = class {
2067
2225
  */
2068
2226
  getJetStreamClient() {
2069
2227
  if (!this.connection || this.connection.isClosed()) {
2070
- throw new Error("Not connected \u2014 call getConnection() before getJetStreamClient()");
2228
+ throw new Error("Not connected; call getConnection() before getJetStreamClient()");
2071
2229
  }
2072
- this.jsClient ??= (0, import_jetstream8.jetstream)(this.connection);
2230
+ this.jsClient ??= (0, import_jetstream11.jetstream)(this.connection);
2073
2231
  return this.jsClient;
2074
2232
  }
2075
2233
  /** Direct access to the raw NATS connection, or `null` if not yet connected. */
@@ -2079,7 +2237,7 @@ var ConnectionProvider = class {
2079
2237
  /**
2080
2238
  * Gracefully shut down the connection.
2081
2239
  *
2082
- * Sequence: drain wait for close. Falls back to force-close on error.
2240
+ * Sequence: drain -> wait for close. Falls back to force-close on error.
2083
2241
  */
2084
2242
  async shutdown() {
2085
2243
  if (this.connectionPromise) {
@@ -2118,7 +2276,7 @@ var ConnectionProvider = class {
2118
2276
  async initJetStreamManager() {
2119
2277
  try {
2120
2278
  const nc = await this.getConnection();
2121
- this.jsmInstance = await (0, import_jetstream8.jetstreamManager)(nc);
2279
+ this.jsmInstance = await (0, import_jetstream11.jetstreamManager)(nc);
2122
2280
  this.logger.log("JetStream manager initialized");
2123
2281
  return this.jsmInstance;
2124
2282
  } finally {
@@ -2128,7 +2286,7 @@ var ConnectionProvider = class {
2128
2286
  /** Internal: establish the physical connection with reconnect monitoring. */
2129
2287
  async establish() {
2130
2288
  try {
2131
- const nc = await (0, import_transport_node2.connect)({
2289
+ const nc = await (0, import_transport_node3.connect)({
2132
2290
  ...DEFAULT_OPTIONS,
2133
2291
  // Default the NATS connection name to the OTel-derived service name so
2134
2292
  // `nats server info` lines up with span attributes, but let user-supplied
@@ -2282,12 +2440,12 @@ var EventBus = class {
2282
2440
  };
2283
2441
 
2284
2442
  // src/health/jetstream.health-indicator.ts
2285
- var import_common7 = require("@nestjs/common");
2443
+ var import_common8 = require("@nestjs/common");
2286
2444
  var JetstreamHealthIndicator = class {
2287
2445
  constructor(connection) {
2288
2446
  this.connection = connection;
2289
2447
  }
2290
- logger = new import_common7.Logger("Jetstream:Health");
2448
+ logger = new import_common8.Logger("Jetstream:Health");
2291
2449
  /**
2292
2450
  * Plain health status check.
2293
2451
  *
@@ -2317,7 +2475,7 @@ var JetstreamHealthIndicator = class {
2317
2475
  * Returns `{ [key]: { status: 'up', ... } }` on success.
2318
2476
  * Throws an error with `{ [key]: { status: 'down', ... } }` on failure.
2319
2477
  *
2320
- * The thrown error sets `isHealthCheckError: true` and `causes` the
2478
+ * The thrown error sets `isHealthCheckError: true` and `causes`, the
2321
2479
  * duck-type contract that Terminus `HealthCheckExecutor` uses to distinguish
2322
2480
  * health failures from unexpected exceptions. Works with both Terminus v10
2323
2481
  * (`instanceof HealthCheckError`) and v11+ (`error?.isHealthCheckError`).
@@ -2344,14 +2502,14 @@ var JetstreamHealthIndicator = class {
2344
2502
  }
2345
2503
  };
2346
2504
  JetstreamHealthIndicator = __decorateClass([
2347
- (0, import_common7.Injectable)()
2505
+ (0, import_common8.Injectable)()
2348
2506
  ], JetstreamHealthIndicator);
2349
2507
 
2350
2508
  // src/metrics/metrics.module.ts
2351
- var import_common11 = require("@nestjs/common");
2509
+ var import_common12 = require("@nestjs/common");
2352
2510
 
2353
2511
  // src/server/routing/pattern-registry.ts
2354
- var import_common8 = require("@nestjs/common");
2512
+ var import_common9 = require("@nestjs/common");
2355
2513
  var HANDLER_LABELS = {
2356
2514
  ["broadcast" /* Broadcast */]: "broadcast" /* Broadcast */,
2357
2515
  ["ordered" /* Ordered */]: "ordered" /* Ordered */,
@@ -2359,12 +2517,13 @@ var HANDLER_LABELS = {
2359
2517
  ["cmd" /* Command */]: "rpc" /* Rpc */
2360
2518
  };
2361
2519
  var PatternRegistry = class {
2362
- constructor(options) {
2520
+ constructor(options, names) {
2363
2521
  this.options = options;
2522
+ this.names = names;
2364
2523
  }
2365
- logger = new import_common8.Logger("Jetstream:PatternRegistry");
2524
+ logger = new import_common9.Logger("Jetstream:PatternRegistry");
2366
2525
  registry = /* @__PURE__ */ new Map();
2367
- // Cached after registerHandlers() the registry is immutable from that point
2526
+ // Cached after registerHandlers(); the registry is immutable from that point
2368
2527
  cachedPatterns = null;
2369
2528
  _hasEvents = false;
2370
2529
  _hasCommands = false;
@@ -2377,7 +2536,6 @@ var PatternRegistry = class {
2377
2536
  * @param handlers Map of pattern -> MessageHandler from `Server.getHandlers()`.
2378
2537
  */
2379
2538
  registerHandlers(handlers) {
2380
- const serviceName = this.options.name;
2381
2539
  for (const [pattern, handler] of handlers) {
2382
2540
  const extras = handler.extras;
2383
2541
  const isEvent = handler.isEventHandler ?? false;
@@ -2394,7 +2552,7 @@ var PatternRegistry = class {
2394
2552
  else if (isOrdered) kind = "ordered" /* Ordered */;
2395
2553
  else if (isEvent) kind = "ev" /* Event */;
2396
2554
  else kind = "cmd" /* Command */;
2397
- const fullSubject = kind === "broadcast" /* Broadcast */ ? buildBroadcastSubject(pattern) : buildSubject(serviceName, kind, pattern);
2555
+ const fullSubject = this.names.subject(kind, pattern);
2398
2556
  this.registry.set(fullSubject, {
2399
2557
  handler,
2400
2558
  pattern,
@@ -2421,7 +2579,7 @@ var PatternRegistry = class {
2421
2579
  * Resolve the declared pattern and {@link StreamKind} for a full NATS subject.
2422
2580
  *
2423
2581
  * Returns `null` when the subject is not registered. The declared pattern is
2424
- * the value the user passed to `@EventPattern`/`@MessagePattern` stable and
2582
+ * the value the user passed to `@EventPattern`/`@MessagePattern`: stable and
2425
2583
  * bounded, suitable for use as a Prometheus label without cardinality risk.
2426
2584
  */
2427
2585
  resolveDeclared(subject) {
@@ -2431,7 +2589,17 @@ var PatternRegistry = class {
2431
2589
  }
2432
2590
  /** Get all registered broadcast patterns (for consumer filter_subject setup). */
2433
2591
  getBroadcastPatterns() {
2434
- return this.getPatternsByKind().broadcasts.map((p) => buildBroadcastSubject(p));
2592
+ return this.getPatternsByKind().broadcasts.map(
2593
+ (p) => this.names.subject("broadcast" /* Broadcast */, p)
2594
+ );
2595
+ }
2596
+ /** Get registered event patterns as raw user-declared patterns. */
2597
+ getEventPatterns() {
2598
+ return this.getPatternsByKind().events;
2599
+ }
2600
+ /** Get registered command patterns as raw user-declared patterns. */
2601
+ getCommandPatterns() {
2602
+ return this.getPatternsByKind().commands;
2435
2603
  }
2436
2604
  hasBroadcastHandlers() {
2437
2605
  return this._hasBroadcasts;
@@ -2447,9 +2615,7 @@ var PatternRegistry = class {
2447
2615
  }
2448
2616
  /** Get fully-qualified NATS subjects for ordered handlers. */
2449
2617
  getOrderedSubjects() {
2450
- return this.getPatternsByKind().ordered.map(
2451
- (p) => buildSubject(this.options.name, "ordered" /* Ordered */, p)
2452
- );
2618
+ return this.getPatternsByKind().ordered.map((p) => this.names.subject("ordered" /* Ordered */, p));
2453
2619
  }
2454
2620
  /** Check if any registered handler has metadata. */
2455
2621
  hasMetadata() {
@@ -2481,22 +2647,6 @@ var PatternRegistry = class {
2481
2647
  ordered: [...patterns.ordered]
2482
2648
  };
2483
2649
  }
2484
- /** Normalize a full NATS subject back to the user-facing pattern. */
2485
- normalizeSubject(subject) {
2486
- const name = internalName(this.options.name);
2487
- const prefixes = [
2488
- `${name}.${"cmd" /* Command */}.`,
2489
- `${name}.${"ev" /* Event */}.`,
2490
- `${name}.${"ordered" /* Ordered */}.`,
2491
- `${"broadcast" /* Broadcast */}.`
2492
- ];
2493
- for (const prefix of prefixes) {
2494
- if (subject.startsWith(prefix)) {
2495
- return subject.slice(prefix.length);
2496
- }
2497
- }
2498
- return subject;
2499
- }
2500
2650
  buildPatternsByKind() {
2501
2651
  const events = [];
2502
2652
  const commands = [];
@@ -2551,7 +2701,7 @@ var ERROR_CONTEXT_PREFIXES = [
2551
2701
  ["consume", "consume"],
2552
2702
  ["core-rpc-handler", "handler"],
2553
2703
  ["rpc-handler", "handler"],
2554
- // EventRouter formats contexts as `${StreamKind.*}-handler:...` the enum
2704
+ // EventRouter formats contexts as `${StreamKind.*}-handler:...`; the enum
2555
2705
  // uses short forms (`ev`, `ordered`, `broadcast`) so both surface in the wild.
2556
2706
  ["ev-handler", "handler"],
2557
2707
  ["event-handler", "handler"],
@@ -2569,7 +2719,7 @@ var STREAM_KIND_LABEL = {
2569
2719
  };
2570
2720
 
2571
2721
  // src/metrics/metrics.service.ts
2572
- var import_common10 = require("@nestjs/common");
2722
+ var import_common11 = require("@nestjs/common");
2573
2723
 
2574
2724
  // src/metrics/error-context-mapper.ts
2575
2725
  var mapErrorContext = (context7) => {
@@ -2692,12 +2842,12 @@ var createMetrics = (opts) => {
2692
2842
  };
2693
2843
 
2694
2844
  // src/metrics/poll-runner.ts
2695
- var import_common9 = require("@nestjs/common");
2845
+ var import_common10 = require("@nestjs/common");
2696
2846
  var PollRunner = class {
2697
2847
  constructor(opts) {
2698
2848
  this.opts = opts;
2699
2849
  }
2700
- logger = new import_common9.Logger("Jetstream:Metrics:Poll");
2850
+ logger = new import_common10.Logger("Jetstream:Metrics:Poll");
2701
2851
  timer = null;
2702
2852
  inFlight = null;
2703
2853
  start() {
@@ -2706,7 +2856,7 @@ var PollRunner = class {
2706
2856
  if (this.opts.targets.length === 0) return;
2707
2857
  this.timer = setInterval(() => {
2708
2858
  if (this.inFlight !== null) {
2709
- this.logger.warn("Skipping poll tick \u2014 previous cycle still in flight");
2859
+ this.logger.warn("Skipping poll tick; previous cycle still in flight");
2710
2860
  return;
2711
2861
  }
2712
2862
  this.inFlight = this.tick().finally(() => {
@@ -2769,15 +2919,16 @@ var PollRunner = class {
2769
2919
 
2770
2920
  // src/metrics/metrics.service.ts
2771
2921
  var JetstreamMetricsService = class {
2772
- constructor(eventBus, config, promClient, options, patternRegistry, connection = null) {
2922
+ constructor(eventBus, config, promClient, options, patternRegistry, connection = null, names = null) {
2773
2923
  this.eventBus = eventBus;
2774
2924
  this.config = config;
2775
2925
  this.promClient = promClient;
2776
2926
  this.options = options;
2777
2927
  this.patternRegistry = patternRegistry;
2778
2928
  this.connection = connection;
2929
+ this.names = names;
2779
2930
  }
2780
- logger = new import_common10.Logger("Jetstream:Metrics");
2931
+ logger = new import_common11.Logger("Jetstream:Metrics");
2781
2932
  metrics = null;
2782
2933
  pollRunner = null;
2783
2934
  activeServers = /* @__PURE__ */ new Set();
@@ -2786,7 +2937,7 @@ var JetstreamMetricsService = class {
2786
2937
  if (!this.options.metrics || !this.config || !this.promClient) return;
2787
2938
  if (!this.config.register) {
2788
2939
  throw new Error(
2789
- "JetstreamMetricsService requires a prom-client Registry \u2014 none was resolved by JetstreamMetricsModule."
2940
+ "JetstreamMetricsService requires a prom-client Registry; none was resolved by JetstreamMetricsModule."
2790
2941
  );
2791
2942
  }
2792
2943
  this.metrics = createMetrics({
@@ -2813,7 +2964,7 @@ var JetstreamMetricsService = class {
2813
2964
  }
2814
2965
  /**
2815
2966
  * NATS connects during early bootstrap, before this service subscribes to
2816
- * the EventBus the initial `Connect` emission misses us. Mirror the
2967
+ * the EventBus, so the initial `Connect` emission misses us. Mirror the
2817
2968
  * current state here so `connection_up` reflects reality the moment metrics
2818
2969
  * come online; later disconnects/reconnects update it normally.
2819
2970
  */
@@ -2846,26 +2997,32 @@ var JetstreamMetricsService = class {
2846
2997
  if (registry.hasEventHandlers()) {
2847
2998
  targets.push({
2848
2999
  kind: "ev" /* Event */,
2849
- stream: streamName(this.options.name, "ev" /* Event */),
2850
- consumer: consumerName(this.options.name, "ev" /* Event */)
3000
+ stream: this.resolveStreamName("ev" /* Event */),
3001
+ consumer: this.resolveConsumerName("ev" /* Event */)
2851
3002
  });
2852
3003
  }
2853
3004
  if (registry.hasRpcHandlers() && isJetStreamRpcMode(this.options.rpc)) {
2854
3005
  targets.push({
2855
3006
  kind: "cmd" /* Command */,
2856
- stream: streamName(this.options.name, "cmd" /* Command */),
2857
- consumer: consumerName(this.options.name, "cmd" /* Command */)
3007
+ stream: this.resolveStreamName("cmd" /* Command */),
3008
+ consumer: this.resolveConsumerName("cmd" /* Command */)
2858
3009
  });
2859
3010
  }
2860
3011
  if (registry.hasBroadcastHandlers()) {
2861
3012
  targets.push({
2862
3013
  kind: "broadcast" /* Broadcast */,
2863
- stream: streamName(this.options.name, "broadcast" /* Broadcast */),
2864
- consumer: consumerName(this.options.name, "broadcast" /* Broadcast */)
3014
+ stream: this.resolveStreamName("broadcast" /* Broadcast */),
3015
+ consumer: this.resolveConsumerName("broadcast" /* Broadcast */)
2865
3016
  });
2866
3017
  }
2867
3018
  return targets;
2868
3019
  }
3020
+ resolveStreamName(kind) {
3021
+ return this.names ? this.names.streamName(kind) : streamName(this.options.name, kind);
3022
+ }
3023
+ resolveConsumerName(kind) {
3024
+ return this.names ? this.names.consumerName(kind) : consumerName(this.options.name, kind);
3025
+ }
2869
3026
  subscribeToEvents() {
2870
3027
  this.eventBus.subscribe("connect" /* Connect */, this.onConnect);
2871
3028
  this.eventBus.subscribe("disconnect" /* Disconnect */, this.onDisconnect);
@@ -2900,7 +3057,7 @@ var JetstreamMetricsService = class {
2900
3057
  const subjectLabel = declared?.pattern ?? UNMATCHED_SUBJECT_LABEL;
2901
3058
  this.metrics?.rpcTimeoutTotal.labels({ subject: subjectLabel }).inc();
2902
3059
  };
2903
- // `_kind` collapses broadcast/ordered into MessageKind.Event we use
3060
+ // `_kind` collapses broadcast/ordered into MessageKind.Event; we use
2904
3061
  // declared.kind from PatternRegistry for the precise label instead.
2905
3062
  onMessageRouted = (subject, _kind) => {
2906
3063
  if (!this.metrics) return;
@@ -2910,7 +3067,7 @@ var JetstreamMetricsService = class {
2910
3067
  return;
2911
3068
  }
2912
3069
  this.metrics.messagesReceivedTotal.labels({
2913
- stream: streamName(this.options.name, declared.kind),
3070
+ stream: this.resolveStreamName(declared.kind),
2914
3071
  subject: declared.pattern,
2915
3072
  kind: STREAM_KIND_LABEL[declared.kind]
2916
3073
  }).inc();
@@ -2926,7 +3083,7 @@ var JetstreamMetricsService = class {
2926
3083
  };
2927
3084
  onHandlerCompleted = (pattern, kind, durationMs, status) => {
2928
3085
  if (!this.metrics) return;
2929
- const stream = streamName(this.options.name, kind);
3086
+ const stream = this.resolveStreamName(kind);
2930
3087
  const kindLabel = STREAM_KIND_LABEL[kind];
2931
3088
  const labels = { stream, subject: pattern, kind: kindLabel, status };
2932
3089
  this.metrics.messagesProcessedTotal.labels(labels).inc();
@@ -2946,13 +3103,14 @@ var JetstreamMetricsService = class {
2946
3103
  }
2947
3104
  };
2948
3105
  JetstreamMetricsService = __decorateClass([
2949
- (0, import_common10.Injectable)(),
2950
- __decorateParam(1, (0, import_common10.Inject)(JETSTREAM_METRICS_CONFIG)),
2951
- __decorateParam(2, (0, import_common10.Inject)(JETSTREAM_METRICS_PROM_CLIENT)),
2952
- __decorateParam(3, (0, import_common10.Inject)(JETSTREAM_OPTIONS)),
2953
- __decorateParam(4, (0, import_common10.Optional)()),
2954
- __decorateParam(5, (0, import_common10.Optional)()),
2955
- __decorateParam(5, (0, import_common10.Inject)(JETSTREAM_CONNECTION))
3106
+ (0, import_common11.Injectable)(),
3107
+ __decorateParam(1, (0, import_common11.Inject)(JETSTREAM_METRICS_CONFIG)),
3108
+ __decorateParam(2, (0, import_common11.Inject)(JETSTREAM_METRICS_PROM_CLIENT)),
3109
+ __decorateParam(3, (0, import_common11.Inject)(JETSTREAM_OPTIONS)),
3110
+ __decorateParam(4, (0, import_common11.Optional)()),
3111
+ __decorateParam(5, (0, import_common11.Optional)()),
3112
+ __decorateParam(5, (0, import_common11.Inject)(JETSTREAM_CONNECTION)),
3113
+ __decorateParam(6, (0, import_common11.Optional)())
2956
3114
  ], JetstreamMetricsService);
2957
3115
 
2958
3116
  // src/metrics/metrics.module.ts
@@ -3007,9 +3165,18 @@ var JetstreamMetricsModule = class {
3007
3165
  JETSTREAM_METRICS_PROM_CLIENT,
3008
3166
  JETSTREAM_OPTIONS,
3009
3167
  { token: PatternRegistry, optional: true },
3010
- { token: JETSTREAM_CONNECTION, optional: true }
3168
+ { token: JETSTREAM_CONNECTION, optional: true },
3169
+ { token: NameResolver, optional: true }
3011
3170
  ],
3012
- useFactory: (eventBus, cfg, runtime, opts, patternRegistry, connection) => new JetstreamMetricsService(eventBus, cfg, runtime, opts, patternRegistry, connection)
3171
+ useFactory: (eventBus, cfg, runtime, opts, patternRegistry, connection, names) => new JetstreamMetricsService(
3172
+ eventBus,
3173
+ cfg,
3174
+ runtime,
3175
+ opts,
3176
+ patternRegistry,
3177
+ connection,
3178
+ names
3179
+ )
3013
3180
  };
3014
3181
  return {
3015
3182
  module: JetstreamMetricsModule,
@@ -3024,7 +3191,7 @@ var JetstreamMetricsModule = class {
3024
3191
  }
3025
3192
  };
3026
3193
  JetstreamMetricsModule = __decorateClass([
3027
- (0, import_common11.Module)({})
3194
+ (0, import_common12.Module)({})
3028
3195
  ], JetstreamMetricsModule);
3029
3196
 
3030
3197
  // src/server/strategy.ts
@@ -3070,31 +3237,22 @@ var JetstreamStrategy = class extends import_microservices2.Server {
3070
3237
  this.started = false;
3071
3238
  }
3072
3239
  /**
3073
- * Override NestJS `Server.addHandler` to fail-fast on duplicate pattern registration.
3074
- *
3075
- * The base class silently overwrites duplicate RPC handlers (last wins) and appends
3076
- * duplicate event handlers to a linked list. Both behaviors are hazardous in a
3077
- * JetStream context: silent overwrite drops a handler the user wrote, and double
3078
- * event dispatch double-acks/double-processes the same JetStream message.
3240
+ * Override NestJS `Server.addHandler` to fail fast on duplicate pattern registration.
3079
3241
  *
3080
- * We treat any pattern collision as a fatal misconfiguration so it surfaces at
3081
- * bootstrap instead of in production traffic.
3242
+ * The base class silently overwrites duplicate RPC handlers and chains duplicate event
3243
+ * handlers, which would double-ack the same JetStream message. Any collision is treated
3244
+ * as a fatal misconfiguration so it surfaces at bootstrap, not in production traffic.
3082
3245
  */
3083
3246
  addHandler(pattern, callback, isEventHandler = false, extras = {}) {
3084
3247
  const normalizedPattern = this.normalizePattern(pattern);
3085
3248
  if (this.messageHandlers.has(normalizedPattern)) {
3086
3249
  throw new Error(
3087
- `Duplicate handler registered for pattern "${normalizedPattern}". Each @EventPattern() / @MessagePattern() value must be unique within a microservice \u2014 find and remove the second declaration.`
3250
+ `Duplicate handler registered for pattern "${normalizedPattern}". Each @EventPattern() / @MessagePattern() value must be unique within a microservice; find and remove the second declaration.`
3088
3251
  );
3089
3252
  }
3090
3253
  super.addHandler(pattern, callback, isEventHandler, extras);
3091
3254
  }
3092
- /**
3093
- * Register event listener (required by Server base class).
3094
- *
3095
- * Stores callbacks for client use. Primary lifecycle events
3096
- * are routed through EventBus.
3097
- */
3255
+ /** Register event listener (required by Server base class); lifecycle events use EventBus. */
3098
3256
  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
3099
3257
  on(event, callback) {
3100
3258
  const existing = this.listeners.get(event) ?? [];
@@ -3109,7 +3267,7 @@ var JetstreamStrategy = class extends import_microservices2.Server {
3109
3267
  unwrap() {
3110
3268
  const nc = this.connection.unwrap;
3111
3269
  if (!nc) {
3112
- throw new Error("Not connected \u2014 transport has not started");
3270
+ throw new Error("Not connected; transport has not started");
3113
3271
  }
3114
3272
  return nc;
3115
3273
  }
@@ -3119,7 +3277,7 @@ var JetstreamStrategy = class extends import_microservices2.Server {
3119
3277
  }
3120
3278
  async doListen(callback) {
3121
3279
  if (this.started) {
3122
- this.logger.warn("listen() called more than once \u2014 ignoring");
3280
+ this.logger.warn("listen() called more than once; ignoring");
3123
3281
  return;
3124
3282
  }
3125
3283
  this.started = true;
@@ -3210,8 +3368,8 @@ var JetstreamStrategy = class extends import_microservices2.Server {
3210
3368
  };
3211
3369
 
3212
3370
  // src/server/core-rpc.server.ts
3213
- var import_common12 = require("@nestjs/common");
3214
- var import_transport_node3 = require("@nats-io/transport-node");
3371
+ var import_common13 = require("@nestjs/common");
3372
+ var import_transport_node4 = require("@nats-io/transport-node");
3215
3373
 
3216
3374
  // src/context/rpc.context.ts
3217
3375
  var import_microservices3 = require("@nestjs/microservices");
@@ -3287,7 +3445,7 @@ var RpcContext = class extends import_microservices3.BaseRpcContext {
3287
3445
  retry(opts) {
3288
3446
  this.assertJetStream("retry");
3289
3447
  if (this._shouldTerminate) {
3290
- throw new Error("Cannot retry \u2014 terminate() was already called");
3448
+ throw new Error("Cannot retry; terminate() was already called");
3291
3449
  }
3292
3450
  this._shouldRetry = true;
3293
3451
  this._retryDelay = opts?.delayMs;
@@ -3304,7 +3462,7 @@ var RpcContext = class extends import_microservices3.BaseRpcContext {
3304
3462
  terminate(reason) {
3305
3463
  this.assertJetStream("terminate");
3306
3464
  if (this._shouldRetry) {
3307
- throw new Error("Cannot terminate \u2014 retry() was already called");
3465
+ throw new Error("Cannot terminate; retry() was already called");
3308
3466
  }
3309
3467
  this._shouldTerminate = true;
3310
3468
  this._terminateReason = reason;
@@ -3313,7 +3471,7 @@ var RpcContext = class extends import_microservices3.BaseRpcContext {
3313
3471
  asJetStream() {
3314
3472
  return this.isJetStream() ? this.args[0] : null;
3315
3473
  }
3316
- /** Ensure the message is JetStream settlement actions are not available for Core NATS. */
3474
+ /** Ensure the message is JetStream; settlement actions are not available for Core NATS. */
3317
3475
  assertJetStream(method) {
3318
3476
  if (!this.isJetStream()) {
3319
3477
  throw new Error(`${method}() is only available for JetStream messages`);
@@ -3478,17 +3636,18 @@ var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
3478
3636
 
3479
3637
  // src/server/core-rpc.server.ts
3480
3638
  var CoreRpcServer = class {
3481
- constructor(options, connection, patternRegistry, codec, eventBus) {
3639
+ constructor(options, connection, patternRegistry, codec, eventBus, names) {
3482
3640
  this.connection = connection;
3483
3641
  this.patternRegistry = patternRegistry;
3484
3642
  this.codec = codec;
3485
3643
  this.eventBus = eventBus;
3644
+ this.names = names;
3486
3645
  const derived = deriveOtelAttrs(options);
3487
3646
  this.otel = derived.otel;
3488
3647
  this.serviceName = derived.serviceName;
3489
3648
  this.serverEndpoint = derived.serverEndpoint;
3490
3649
  }
3491
- logger = new import_common12.Logger("Jetstream:CoreRpc");
3650
+ logger = new import_common13.Logger("Jetstream:CoreRpc");
3492
3651
  subscription = null;
3493
3652
  otel;
3494
3653
  serviceName;
@@ -3496,7 +3655,7 @@ var CoreRpcServer = class {
3496
3655
  /** Start listening for RPC requests on the command subject. */
3497
3656
  async start() {
3498
3657
  const nc = await this.connection.getConnection();
3499
- const subject = `${this.serviceName}.cmd.>`;
3658
+ const subject = this.names ? this.names.filterSubject("cmd" /* Command */) : `${this.serviceName}.cmd.>`;
3500
3659
  const queue = `${this.serviceName}_cmd_queue`;
3501
3660
  this.subscription = nc.subscribe(subject, {
3502
3661
  queue,
@@ -3589,7 +3748,7 @@ var CoreRpcServer = class {
3589
3748
  /** Send an error response back to the caller with x-error header. */
3590
3749
  respondWithError(msg, error) {
3591
3750
  try {
3592
- const hdrs = (0, import_transport_node3.headers)();
3751
+ const hdrs = (0, import_transport_node4.headers)();
3593
3752
  hdrs.set("x-error" /* Error */, "true");
3594
3753
  msg.respond(this.codec.encode(serializeError(error)), { headers: hdrs });
3595
3754
  } catch {
@@ -3599,8 +3758,8 @@ var CoreRpcServer = class {
3599
3758
  };
3600
3759
 
3601
3760
  // src/server/infrastructure/stream.provider.ts
3602
- var import_common14 = require("@nestjs/common");
3603
- var import_jetstream19 = require("@nats-io/jetstream");
3761
+ var import_common15 = require("@nestjs/common");
3762
+ var import_jetstream22 = require("@nats-io/jetstream");
3604
3763
 
3605
3764
  // src/server/infrastructure/nats-error-codes.ts
3606
3765
  var NatsErrorCode = /* @__PURE__ */ ((NatsErrorCode2) => {
@@ -3613,7 +3772,7 @@ var NatsErrorCode = /* @__PURE__ */ ((NatsErrorCode2) => {
3613
3772
  })(NatsErrorCode || {});
3614
3773
 
3615
3774
  // src/server/infrastructure/provisioning-budget.ts
3616
- var import_jetstream16 = require("@nats-io/jetstream");
3775
+ var import_jetstream19 = require("@nats-io/jetstream");
3617
3776
  var GIB = 1024 ** 3;
3618
3777
  var fmt = (bytes) => `${(bytes / GIB).toFixed(2)} GiB`;
3619
3778
  var resolveTierBudget = (info, replicas) => {
@@ -3628,7 +3787,7 @@ var resolveTierBudget = (info, replicas) => {
3628
3787
  var groupByReplicas = (reservations) => {
3629
3788
  const groups = /* @__PURE__ */ new Map();
3630
3789
  for (const r of reservations) {
3631
- if (r.storage !== import_jetstream16.StorageType.File) continue;
3790
+ if (r.storage !== import_jetstream19.StorageType.File) continue;
3632
3791
  const prev = groups.get(r.numReplicas) ?? 0;
3633
3792
  groups.set(r.numReplicas, prev + r.maxBytes * r.numReplicas);
3634
3793
  }
@@ -3669,7 +3828,7 @@ var assertStorageBudget = async (jsm, serviceName, reservations, logger5) => {
3669
3828
  );
3670
3829
  }
3671
3830
  } catch (err) {
3672
- logger5.debug(`Storage preflight skipped \u2014 account info unavailable: ${String(err)}`);
3831
+ logger5.debug(`Storage preflight skipped; account info unavailable: ${String(err)}`);
3673
3832
  }
3674
3833
  };
3675
3834
 
@@ -3690,7 +3849,7 @@ var JetstreamProvisioningError = class _JetstreamProvisioningError extends Error
3690
3849
  numReplicas;
3691
3850
  reservation;
3692
3851
  constructor(fields) {
3693
- const reservationNote = fields.reservation !== void 0 ? ` reservation=${fields.reservation}B (max_bytes=${fields.maxBytes}B \xD7 replicas=${fields.numReplicas}).` : "";
3852
+ const reservationNote = fields.reservation !== void 0 ? ` reservation=${fields.reservation}B (max_bytes=${fields.maxBytes}B x replicas=${fields.numReplicas}).` : "";
3694
3853
  super(
3695
3854
  `JetStream ${fields.entity} provisioning failed for "${fields.target}" (kind=${fields.kind}): ${fields.errDescription} [err_code=${fields.errCode}].${reservationNote} ${fields.remediation}`,
3696
3855
  { cause: fields.cause }
@@ -3727,7 +3886,7 @@ var mapProvisioningError = (err, ctx) => {
3727
3886
  };
3728
3887
 
3729
3888
  // src/server/infrastructure/provisioning-summary.ts
3730
- var import_jetstream17 = require("@nats-io/jetstream");
3889
+ var import_jetstream20 = require("@nats-io/jetstream");
3731
3890
  var GIB2 = 1024 ** 3;
3732
3891
  var NANOS_PER_SECOND = 1e9;
3733
3892
  var NANOS_PER_HOUR = 3600 * NANOS_PER_SECOND;
@@ -3742,18 +3901,22 @@ var formatAge = (nanos) => {
3742
3901
  if (nanos >= NANOS_PER_HOUR) return `${(nanos / NANOS_PER_HOUR).toFixed(1)}h`;
3743
3902
  return `${(nanos / NANOS_PER_SECOND).toFixed(0)}s`;
3744
3903
  };
3745
- var formatProvisioningSummary = (serviceName, reservations) => {
3746
- const lines = [`Provisioning ${reservations.length} stream(s) for "${serviceName}":`];
3904
+ var formatProvisioningSummary = (serviceName, reservations, external = []) => {
3905
+ const totalStreams = reservations.length + external.length;
3906
+ const lines = [`Provisioning ${totalStreams} stream(s) for "${serviceName}":`];
3747
3907
  let totalFileMaxBytes = 0;
3748
3908
  for (const r of reservations) {
3749
- if (r.storage === import_jetstream17.StorageType.File) totalFileMaxBytes += r.maxBytes;
3909
+ if (r.storage === import_jetstream20.StorageType.File) totalFileMaxBytes += r.maxBytes;
3750
3910
  const clusterReservation = r.maxBytes * r.numReplicas;
3751
3911
  lines.push(
3752
- ` \u2022 ${r.name} [${r.kind}] storage=${r.storage} replicas=${r.numReplicas} max_bytes=${formatBytes(r.maxBytes)} max_age=${formatAge(r.maxAge)} retention=${r.retention} \u2192 cluster reservation ${formatBytes(clusterReservation)}`
3912
+ ` - ${r.name} [${r.kind}] storage=${r.storage} replicas=${r.numReplicas} max_bytes=${formatBytes(r.maxBytes)} max_age=${formatAge(r.maxAge)} retention=${r.retention} -> cluster reservation ${formatBytes(clusterReservation)}`
3753
3913
  );
3754
3914
  }
3915
+ for (const e of external) {
3916
+ lines.push(` - ${e.name} [${e.kind}] external (bound)`);
3917
+ }
3755
3918
  lines.push(
3756
- ` \u03A3 per-node file-backed footprint \u2248 ${formatBytes(totalFileMaxBytes)} (sum of max_bytes; worst case replicas = nodes). Ensure the NATS server max_file_store accommodates the sum across ALL services.`
3919
+ ` Total per-node file-backed footprint ~ ${formatBytes(totalFileMaxBytes)} (sum of max_bytes; worst case replicas = nodes). Ensure the NATS server max_file_store accommodates the sum across ALL services.`
3757
3920
  );
3758
3921
  return lines.join("\n");
3759
3922
  };
@@ -3814,8 +3977,8 @@ var isEqual = (a, b) => {
3814
3977
  };
3815
3978
 
3816
3979
  // src/server/infrastructure/stream-migration.ts
3817
- var import_common13 = require("@nestjs/common");
3818
- var import_jetstream18 = require("@nats-io/jetstream");
3980
+ var import_common14 = require("@nestjs/common");
3981
+ var import_jetstream21 = require("@nats-io/jetstream");
3819
3982
  var MIGRATION_BACKUP_SUFFIX = "__migration_backup";
3820
3983
  var DEFAULT_SOURCING_TIMEOUT_MS = 3e4;
3821
3984
  var SOURCING_POLL_INTERVAL_MS = 100;
@@ -3827,7 +3990,7 @@ var StreamMigration = class {
3827
3990
  this.sourcingTimeoutMs = sourcingTimeoutMs;
3828
3991
  this.peerWaitMs = peerWaitMs;
3829
3992
  }
3830
- logger = new import_common13.Logger("Jetstream:Stream");
3993
+ logger = new import_common14.Logger("Jetstream:Stream");
3831
3994
  async migrate(jsm, streamName2, newConfig) {
3832
3995
  const backupName = `${streamName2}${MIGRATION_BACKUP_SUFFIX}`;
3833
3996
  const startTime = Date.now();
@@ -3846,7 +4009,7 @@ var StreamMigration = class {
3846
4009
  await jsm.streams.update(streamName2, { ...currentInfo.config, subjects: [] });
3847
4010
  drainedCount = (await jsm.streams.info(streamName2)).state.messages;
3848
4011
  if (drainedCount > 0) {
3849
- this.logger.log(` Phase 2/4: Backing up ${drainedCount} messages \u2192 ${backupName}`);
4012
+ this.logger.log(` Phase 2/4: Backing up ${drainedCount} messages -> ${backupName}`);
3850
4013
  await jsm.streams.add({
3851
4014
  ...currentInfo.config,
3852
4015
  name: backupName,
@@ -3867,7 +4030,7 @@ var StreamMigration = class {
3867
4030
  } catch (err) {
3868
4031
  if (originalDeleted) {
3869
4032
  this.logger.error(
3870
- `Migration of ${streamName2} failed after the original was deleted. Backup ${backupName} preserved \u2014 restoration resumes on the next startup.`
4033
+ `Migration of ${streamName2} failed after the original was deleted. Backup ${backupName} preserved; restoration resumes on the next startup.`
3871
4034
  );
3872
4035
  } else {
3873
4036
  await this.rollbackBeforeDelete(jsm, streamName2, currentInfo, backupName);
@@ -3880,11 +4043,8 @@ var StreamMigration = class {
3880
4043
  );
3881
4044
  }
3882
4045
  /**
3883
- * Detect and finish a migration that a previous process left unfinished.
3884
- * Safe against concurrent instances: a backup fresh enough to belong to a
3885
- * live migration is left alone.
3886
- *
3887
- * @returns true when recovery work was performed.
4046
+ * Finish a migration a previous process left unfinished; a backup fresh
4047
+ * enough to belong to a live peer migration is left alone.
3888
4048
  */
3889
4049
  async recoverInterrupted(jsm, streamName2, desiredConfig) {
3890
4050
  const backupName = `${streamName2}${MIGRATION_BACKUP_SUFFIX}`;
@@ -3938,11 +4098,9 @@ var StreamMigration = class {
3938
4098
  await jsm.streams.update(streamName2, { ...streamConfig, sources: [] });
3939
4099
  }
3940
4100
  /**
3941
- * Wait until `sourceName` is fully drained into `streamName`. Lag-based, so
3942
- * concurrent live publishes to the target cannot fake completion the way a
3943
- * bare message-count comparison could. A freshly attached source reports
3944
- * `lag: 0, active: -1` before its first sync — `active >= 0` filters that
3945
- * false positive out (verified against NATS 2.12.6).
4101
+ * Lag-based drain check: live publishes cannot fake completion. A fresh
4102
+ * source reports lag 0 / active -1 before its first sync (NATS 2.12.6),
4103
+ * hence the active guard.
3946
4104
  */
3947
4105
  async waitForSourceDrained(jsm, streamName2, sourceName, minimumMessages) {
3948
4106
  const deadline = Date.now() + this.sourcingTimeoutMs;
@@ -3959,17 +4117,13 @@ var StreamMigration = class {
3959
4117
  );
3960
4118
  }
3961
4119
  /**
3962
- * A backup already present when migrate() begins belongs to another
3963
- * instance migrating right now (rolling deploy) wait for it to finish.
3964
- * Stale leftovers are handled by recoverInterrupted() before migrate() runs,
3965
- * so a timeout here means something is genuinely stuck.
3966
- *
3967
- * @returns true when a peer's backup was observed and cleared.
4120
+ * A backup present at migrate() start is a live peer migration; wait it
4121
+ * out. Stale leftovers were already handled by recoverInterrupted().
3968
4122
  */
3969
4123
  async waitOutPeerMigration(jsm, backupName) {
3970
4124
  if (await this.tryInfo(jsm, backupName) === null) return false;
3971
4125
  this.logger.warn(
3972
- `Migration backup ${backupName} exists \u2014 another instance appears to be migrating; waiting`
4126
+ `Migration backup ${backupName} exists; another instance appears to be migrating; waiting`
3973
4127
  );
3974
4128
  const deadline = Date.now() + this.peerWaitMs;
3975
4129
  while (Date.now() < deadline) {
@@ -3990,7 +4144,7 @@ var StreamMigration = class {
3990
4144
  }
3991
4145
  } catch (rollbackErr) {
3992
4146
  this.logger.error(
3993
- `Rollback of ${streamName2} after a failed migration also failed \u2014 the stream may be left quiesced:`,
4147
+ `Rollback of ${streamName2} after a failed migration also failed; the stream may be left quiesced:`,
3994
4148
  rollbackErr
3995
4149
  );
3996
4150
  }
@@ -4006,7 +4160,7 @@ var StreamMigration = class {
4006
4160
  try {
4007
4161
  return await jsm.streams.info(name);
4008
4162
  } catch (err) {
4009
- if (err instanceof import_jetstream18.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
4163
+ if (err instanceof import_jetstream21.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
4010
4164
  return null;
4011
4165
  }
4012
4166
  throw err;
@@ -4014,17 +4168,33 @@ var StreamMigration = class {
4014
4168
  }
4015
4169
  };
4016
4170
 
4171
+ // src/server/infrastructure/subject-utils.ts
4172
+ var subjectCovers = (broad, narrow) => {
4173
+ if (broad === narrow) return false;
4174
+ const broadTokens = broad.split(".");
4175
+ const narrowTokens = narrow.split(".");
4176
+ for (let i = 0; i < broadTokens.length; i += 1) {
4177
+ if (broadTokens[i] === ">") return i < narrowTokens.length;
4178
+ if (i >= narrowTokens.length || narrowTokens[i] === ">") return false;
4179
+ if (broadTokens[i] !== "*" && broadTokens[i] !== narrowTokens[i]) return false;
4180
+ }
4181
+ return broadTokens.length === narrowTokens.length;
4182
+ };
4183
+ var coversOrEquals = (broad, subject) => broad === subject || subjectCovers(broad, subject);
4184
+
4017
4185
  // src/server/infrastructure/stream.provider.ts
4018
4186
  var StreamProvider = class {
4019
- constructor(options, connection) {
4187
+ constructor(options, connection, names, binder) {
4020
4188
  this.options = options;
4021
4189
  this.connection = connection;
4190
+ this.names = names;
4191
+ this.binder = binder;
4022
4192
  const derived = deriveOtelAttrs(options);
4023
4193
  this.otel = derived.otel;
4024
4194
  this.otelServiceName = derived.serviceName;
4025
4195
  this.otelEndpoint = derived.serverEndpoint;
4026
4196
  }
4027
- logger = new import_common14.Logger("Jetstream:Stream");
4197
+ logger = new import_common15.Logger("Jetstream:Stream");
4028
4198
  migration = new StreamMigration();
4029
4199
  otel;
4030
4200
  otelServiceName;
@@ -4038,47 +4208,48 @@ var StreamProvider = class {
4038
4208
  */
4039
4209
  async ensureStreams(kinds) {
4040
4210
  const jsm = await this.connection.getJetStreamManager();
4041
- const reservations = kinds.map((kind) => this.buildReservation(kind, this.buildConfig(kind)));
4211
+ const { autoKinds, externalKinds } = this.partitionByManagement(kinds);
4212
+ const reservations = autoKinds.map(
4213
+ (kind) => this.buildReservation(kind, this.buildConfig(kind))
4214
+ );
4215
+ const external = externalKinds.map((kind) => ({
4216
+ kind,
4217
+ name: this.names.streamName(kind)
4218
+ }));
4219
+ const dlqIsManual = !!this.options.dlq && resolveManagementMode(this.options, "dlq", "stream") === "manual" /* Manual */;
4042
4220
  if (this.options.dlq) {
4043
- reservations.push(this.buildReservation("dlq", this.buildDlqConfig()));
4221
+ if (dlqIsManual) {
4222
+ external.push({ kind: "dlq", name: this.names.dlqStreamName() });
4223
+ } else {
4224
+ reservations.push(this.buildReservation("dlq", this.buildDlqConfig()));
4225
+ }
4044
4226
  }
4045
4227
  this.logger.log(`
4046
- ${formatProvisioningSummary(this.options.name, reservations)}`);
4047
- if (this.options.provisioning?.preflightStorageCheck) {
4228
+ ${formatProvisioningSummary(this.options.name, reservations, external)}`);
4229
+ if (this.options.provisioning?.preflightStorageCheck && reservations.length > 0) {
4048
4230
  await assertStorageBudget(jsm, this.options.name, reservations, this.logger);
4049
4231
  }
4050
- await Promise.all(kinds.map((kind) => this.ensureStream(jsm, kind)));
4232
+ await Promise.all([
4233
+ ...autoKinds.map((kind) => this.ensureStream(jsm, kind)),
4234
+ ...externalKinds.map((kind) => this.bindStream(jsm, kind))
4235
+ ]);
4051
4236
  if (this.options.dlq) {
4052
- await this.ensureDlqStream(jsm);
4237
+ if (dlqIsManual) {
4238
+ await this.bindDlqStream(jsm);
4239
+ } else {
4240
+ await this.ensureDlqStream(jsm);
4241
+ }
4053
4242
  }
4054
4243
  }
4055
4244
  /** Get the stream name for a given kind. */
4056
4245
  getStreamName(kind) {
4057
- return streamName(this.options.name, kind);
4246
+ return this.names.streamName(kind);
4058
4247
  }
4059
4248
  /** Get the subjects pattern for a given kind. */
4060
4249
  getSubjects(kind) {
4061
- const name = internalName(this.options.name);
4062
- switch (kind) {
4063
- case "ev" /* Event */: {
4064
- const subjects = [`${name}.${"ev" /* Event */}.>`];
4065
- if (this.isSchedulingEnabled(kind)) {
4066
- subjects.push(`${name}._sch.>`);
4067
- }
4068
- return subjects;
4069
- }
4070
- case "cmd" /* Command */:
4071
- return [`${name}.${"cmd" /* Command */}.>`];
4072
- case "broadcast" /* Broadcast */: {
4073
- const subjects = ["broadcast.>"];
4074
- if (this.isSchedulingEnabled(kind)) {
4075
- subjects.push("broadcast._sch.>");
4076
- }
4077
- return subjects;
4078
- }
4079
- case "ordered" /* Ordered */:
4080
- return [`${name}.${"ordered" /* Ordered */}.>`];
4081
- }
4250
+ const filter = this.names.filterSubject(kind);
4251
+ const dedicatedSchedule = kind === "ev" /* Event */ && this.isSchedulingEnabled(kind) && !this.names.hasCustomPrefix(kind);
4252
+ return dedicatedSchedule ? [filter, `${this.names.schedulePrefix(kind)}>`] : [filter];
4082
4253
  }
4083
4254
  /** Ensure a single stream exists, creating or updating as needed. */
4084
4255
  async ensureStream(jsm, kind) {
@@ -4103,7 +4274,7 @@ ${formatProvisioningSummary(this.options.name, reservations)}`);
4103
4274
  const currentInfo = await jsm.streams.info(config.name);
4104
4275
  return await this.handleExistingStream(jsm, currentInfo, config, ctx);
4105
4276
  } catch (err) {
4106
- if (err instanceof import_jetstream19.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
4277
+ if (err instanceof import_jetstream22.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
4107
4278
  this.logger.log(`Creating stream: ${config.name}`);
4108
4279
  return await this.runStreamOp(ctx, () => jsm.streams.add(config));
4109
4280
  }
@@ -4134,7 +4305,7 @@ ${formatProvisioningSummary(this.options.name, reservations)}`);
4134
4305
  const currentInfo = await jsm.streams.info(config.name);
4135
4306
  return await this.handleExistingStream(jsm, currentInfo, config, ctx);
4136
4307
  } catch (err) {
4137
- if (err instanceof import_jetstream19.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
4308
+ if (err instanceof import_jetstream22.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
4138
4309
  this.logger.log(`Creating DLQ stream: ${config.name}`);
4139
4310
  return await this.runStreamOp(ctx, () => jsm.streams.add(config));
4140
4311
  }
@@ -4145,7 +4316,8 @@ ${formatProvisioningSummary(this.options.name, reservations)}`);
4145
4316
  }
4146
4317
  async handleExistingStream(jsm, currentInfo, config, ctx) {
4147
4318
  if (this.isSharedStream(config.name)) {
4148
- config.subjects = [.../* @__PURE__ */ new Set([...config.subjects, ...currentInfo.config.subjects])];
4319
+ const merged = [.../* @__PURE__ */ new Set([...config.subjects, ...currentInfo.config.subjects])];
4320
+ config.subjects = merged.filter((s) => !merged.some((other) => subjectCovers(other, s)));
4149
4321
  }
4150
4322
  const diff = compareStreamConfig(currentInfo.config, config);
4151
4323
  if (!diff.hasChanges) {
@@ -4154,7 +4326,7 @@ ${formatProvisioningSummary(this.options.name, reservations)}`);
4154
4326
  }
4155
4327
  this.logChanges(config.name, diff, !!this.options.allowDestructiveMigration);
4156
4328
  if (diff.hasTransportControlledConflicts) {
4157
- const conflicts = diff.changes.filter((c) => c.mutability === "transport-controlled").map((c) => `${c.property}: ${JSON.stringify(c.current)} \u2192 ${JSON.stringify(c.desired)}`).join(", ");
4329
+ const conflicts = diff.changes.filter((c) => c.mutability === "transport-controlled").map((c) => `${c.property}: ${JSON.stringify(c.current)} -> ${JSON.stringify(c.desired)}`).join(", ");
4158
4330
  throw new Error(
4159
4331
  `Stream ${config.name} has transport-controlled config conflicts that cannot be migrated: ${conflicts}. The retention policy is managed by the transport and must match the stream kind.`
4160
4332
  );
@@ -4204,13 +4376,13 @@ ${formatProvisioningSummary(this.options.name, reservations)}`);
4204
4376
  }
4205
4377
  logChanges(streamName2, diff, migrationEnabled) {
4206
4378
  for (const c of diff.changes) {
4207
- const detail = `${c.property}: ${JSON.stringify(c.current)} \u2192 ${JSON.stringify(c.desired)}`;
4379
+ const detail = `${c.property}: ${JSON.stringify(c.current)} -> ${JSON.stringify(c.desired)}`;
4208
4380
  if (c.mutability === "transport-controlled") {
4209
4381
  this.logger.error(
4210
- `Stream ${streamName2}: ${detail} \u2014 transport-controlled, cannot be changed`
4382
+ `Stream ${streamName2}: ${detail}; transport-controlled, cannot be changed`
4211
4383
  );
4212
4384
  } else if (c.mutability === "immutable" && !migrationEnabled) {
4213
- this.logger.warn(`Stream ${streamName2}: ${detail} \u2014 requires allowDestructiveMigration`);
4385
+ this.logger.warn(`Stream ${streamName2}: ${detail}; requires allowDestructiveMigration`);
4214
4386
  } else {
4215
4387
  this.logger.log(`Stream ${streamName2}: ${detail}`);
4216
4388
  }
@@ -4221,12 +4393,12 @@ ${formatProvisioningSummary(this.options.name, reservations)}`);
4221
4393
  return {
4222
4394
  kind,
4223
4395
  name: config.name,
4224
- storage: config.storage ?? import_jetstream19.StorageType.File,
4396
+ storage: config.storage ?? import_jetstream22.StorageType.File,
4225
4397
  numReplicas: config.num_replicas ?? 1,
4226
4398
  maxBytes: mb !== void 0 && mb >= 0 ? mb : 0,
4227
4399
  // NATS uses -1 for unlimited
4228
4400
  maxAge: config.max_age ?? 0,
4229
- retention: config.retention ?? import_jetstream19.RetentionPolicy.Limits
4401
+ retention: config.retention ?? import_jetstream22.RetentionPolicy.Limits
4230
4402
  };
4231
4403
  }
4232
4404
  errorContext(kind, config) {
@@ -4242,39 +4414,77 @@ ${formatProvisioningSummary(this.options.name, reservations)}`);
4242
4414
  try {
4243
4415
  return await op();
4244
4416
  } catch (err) {
4245
- if (err instanceof import_jetstream19.JetStreamApiError) {
4417
+ if (err instanceof import_jetstream22.JetStreamApiError) {
4246
4418
  throw mapProvisioningError(err, ctx);
4247
4419
  }
4248
4420
  throw err;
4249
4421
  }
4250
4422
  }
4251
- /** The broadcast stream is global — every service in the cluster shares it. */
4252
- isSharedStream(name) {
4253
- return name === this.getStreamName("broadcast" /* Broadcast */);
4423
+ partitionByManagement(kinds) {
4424
+ const autoKinds = [];
4425
+ const externalKinds = [];
4426
+ for (const kind of kinds) {
4427
+ if (resolveManagementMode(this.options, kind, "stream") === "manual" /* Manual */) {
4428
+ externalKinds.push(kind);
4429
+ } else {
4430
+ autoKinds.push(kind);
4431
+ }
4432
+ }
4433
+ return { autoKinds, externalKinds };
4254
4434
  }
4255
- /** Build the full stream config by merging defaults with user overrides. */
4256
- buildConfig(kind) {
4257
- const name = this.getStreamName(kind);
4258
- const subjects = this.getSubjects(kind);
4259
- const description = kind === "broadcast" /* Broadcast */ ? "JetStream broadcast stream (shared across services)" : `JetStream ${kind} stream for ${this.options.name}`;
4260
- const defaults = this.getDefaults(kind);
4261
- const overrides = this.getOverrides(kind);
4262
- return {
4263
- ...defaults,
4264
- ...overrides,
4265
- name,
4266
- subjects,
4267
- description
4268
- };
4435
+ async bindStream(jsm, kind) {
4436
+ const name = this.names.streamName(kind);
4437
+ return withProvisioningSpan(
4438
+ this.otel,
4439
+ {
4440
+ serviceName: this.otelServiceName,
4441
+ endpoint: this.otelEndpoint,
4442
+ entity: "stream",
4443
+ name,
4444
+ action: "bind"
4445
+ },
4446
+ () => this.binder.bindStream(jsm, kind)
4447
+ );
4448
+ }
4449
+ async bindDlqStream(jsm) {
4450
+ const name = this.names.dlqStreamName();
4451
+ return withProvisioningSpan(
4452
+ this.otel,
4453
+ {
4454
+ serviceName: this.otelServiceName,
4455
+ endpoint: this.otelEndpoint,
4456
+ entity: "stream",
4457
+ name,
4458
+ action: "bind"
4459
+ },
4460
+ () => this.binder.bindDlqStream(jsm)
4461
+ );
4462
+ }
4463
+ /** The broadcast stream is global; every service in the cluster shares it. */
4464
+ isSharedStream(name) {
4465
+ return name === this.getStreamName("broadcast" /* Broadcast */);
4466
+ }
4467
+ /** Build the full stream config by merging defaults with user overrides. */
4468
+ buildConfig(kind) {
4469
+ const name = this.getStreamName(kind);
4470
+ const subjects = this.getSubjects(kind);
4471
+ const description = kind === "broadcast" /* Broadcast */ ? "JetStream broadcast stream (shared across services)" : `JetStream ${kind} stream for ${this.options.name}`;
4472
+ const defaults = this.getDefaults(kind);
4473
+ const overrides = this.getOverrides(kind);
4474
+ return {
4475
+ ...defaults,
4476
+ ...overrides,
4477
+ name,
4478
+ subjects,
4479
+ description
4480
+ };
4269
4481
  }
4270
4482
  /**
4271
- * Build the stream configuration for the Dead-Letter Queue (DLQ).
4272
- *
4273
- * Merges the library default DLQ config with user-provided overrides.
4274
- * Ensures transport-controlled settings like retention are safely decoupled.
4483
+ * Build the DLQ stream config: library defaults merged with user overrides,
4484
+ * with transport-controlled settings like retention stripped.
4275
4485
  */
4276
4486
  buildDlqConfig() {
4277
- const name = dlqStreamName(this.options.name);
4487
+ const name = this.names.dlqStreamName();
4278
4488
  const subjects = [name];
4279
4489
  const description = `JetStream DLQ stream for ${this.options.name}`;
4280
4490
  const overrides = this.options.dlq?.stream ?? {};
@@ -4307,22 +4517,7 @@ ${formatProvisioningSummary(this.options.name, reservations)}`);
4307
4517
  }
4308
4518
  /** Get user-provided overrides for a stream kind, stripping transport-controlled properties. */
4309
4519
  getOverrides(kind) {
4310
- let overrides;
4311
- switch (kind) {
4312
- case "ev" /* Event */:
4313
- overrides = this.options.events?.stream ?? {};
4314
- break;
4315
- case "cmd" /* Command */:
4316
- overrides = this.options.rpc?.mode === "jetstream" ? this.options.rpc.stream ?? {} : {};
4317
- break;
4318
- case "broadcast" /* Broadcast */:
4319
- overrides = this.options.broadcast?.stream ?? {};
4320
- break;
4321
- case "ordered" /* Ordered */:
4322
- overrides = this.options.ordered?.stream ?? {};
4323
- break;
4324
- }
4325
- return this.stripTransportControlled(overrides);
4520
+ return this.stripTransportControlled(kindOptionsBlock(this.options, kind)?.stream ?? {});
4326
4521
  }
4327
4522
  /**
4328
4523
  * Remove transport-controlled properties from user overrides.
@@ -4332,7 +4527,7 @@ ${formatProvisioningSummary(this.options.name, reservations)}`);
4332
4527
  stripTransportControlled(overrides) {
4333
4528
  if (!("retention" in overrides)) return overrides;
4334
4529
  this.logger.debug(
4335
- "Stripping user-provided retention override \u2014 retention is managed by the transport"
4530
+ "Stripping user-provided retention override; retention is managed by the transport"
4336
4531
  );
4337
4532
  const cleaned = { ...overrides };
4338
4533
  delete cleaned.retention;
@@ -4341,20 +4536,22 @@ ${formatProvisioningSummary(this.options.name, reservations)}`);
4341
4536
  };
4342
4537
 
4343
4538
  // src/server/infrastructure/consumer.provider.ts
4344
- var import_common15 = require("@nestjs/common");
4345
- var import_jetstream21 = require("@nats-io/jetstream");
4539
+ var import_common16 = require("@nestjs/common");
4540
+ var import_jetstream24 = require("@nats-io/jetstream");
4346
4541
  var ConsumerProvider = class {
4347
- constructor(options, connection, streamProvider, patternRegistry) {
4542
+ constructor(options, connection, streamProvider, patternRegistry, names, binder) {
4348
4543
  this.options = options;
4349
4544
  this.connection = connection;
4350
4545
  this.streamProvider = streamProvider;
4351
4546
  this.patternRegistry = patternRegistry;
4547
+ this.names = names;
4548
+ this.binder = binder;
4352
4549
  const derived = deriveOtelAttrs(options);
4353
4550
  this.otel = derived.otel;
4354
4551
  this.otelServiceName = derived.serviceName;
4355
4552
  this.otelEndpoint = derived.serverEndpoint;
4356
4553
  }
4357
- logger = new import_common15.Logger("Jetstream:Consumer");
4554
+ logger = new import_common16.Logger("Jetstream:Consumer");
4358
4555
  otel;
4359
4556
  otelServiceName;
4360
4557
  otelEndpoint;
@@ -4376,24 +4573,33 @@ var ConsumerProvider = class {
4376
4573
  }
4377
4574
  /** Get the consumer name for a given kind. */
4378
4575
  getConsumerName(kind) {
4379
- return consumerName(this.options.name, kind);
4576
+ return this.names.consumerName(kind);
4380
4577
  }
4381
4578
  /**
4382
4579
  * Ensure a single consumer exists with the desired config.
4383
- * Used at **startup** — creates or updates the consumer to match
4384
- * the current pod's configuration.
4580
+ * Startup path: creates or updates the consumer to match the current pod's configuration.
4385
4581
  */
4386
4582
  async ensureConsumer(jsm, kind) {
4387
4583
  const stream = this.streamProvider.getStreamName(kind);
4388
4584
  const config = this.buildConfig(kind);
4389
4585
  const name = config.durable_name;
4586
+ const spanAttrs = {
4587
+ serviceName: this.otelServiceName,
4588
+ endpoint: this.otelEndpoint,
4589
+ entity: "consumer",
4590
+ name
4591
+ };
4592
+ if (resolveManagementMode(this.options, kind, "consumer") === "manual" /* Manual */) {
4593
+ return withProvisioningSpan(
4594
+ this.otel,
4595
+ { ...spanAttrs, action: "bind" },
4596
+ () => this.binder.bindConsumer(jsm, kind)
4597
+ );
4598
+ }
4390
4599
  return withProvisioningSpan(
4391
4600
  this.otel,
4392
4601
  {
4393
- serviceName: this.otelServiceName,
4394
- endpoint: this.otelEndpoint,
4395
- entity: "consumer",
4396
- name,
4602
+ ...spanAttrs,
4397
4603
  action: "ensure"
4398
4604
  },
4399
4605
  async () => {
@@ -4404,7 +4610,7 @@ var ConsumerProvider = class {
4404
4610
  this.logger.debug(`Consumer exists, updating: ${name}`);
4405
4611
  return await this.runConsumerOp(ctx, () => jsm.consumers.update(stream, name, config));
4406
4612
  } catch (err) {
4407
- if (!(err instanceof import_jetstream21.JetStreamApiError) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
4613
+ if (!(err instanceof import_jetstream24.JetStreamApiError) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
4408
4614
  throw err;
4409
4615
  }
4410
4616
  return await this.createConsumer(jsm, stream, name, kind, config);
@@ -4414,28 +4620,40 @@ var ConsumerProvider = class {
4414
4620
  }
4415
4621
  /**
4416
4622
  * Recover a consumer that disappeared during runtime.
4417
- * Used by **self-healing** — creates if missing, but NEVER updates config.
4418
4623
  *
4419
- * If a migration backup stream exists, another pod is mid-migration we
4420
- * throw so the self-healing retry loop waits with backoff until migration
4421
- * completes and the backup is cleaned up.
4422
- *
4423
- * This prevents old pods from:
4424
- * - Overwriting a newer pod's consumer config during rolling updates
4425
- * - Creating consumers during migration (which would consume and delete
4426
- * workqueue messages while they're being restored)
4624
+ * Self-healing path: creates if missing but never updates config, so an old pod
4625
+ * cannot overwrite a newer pod's config during rolling updates. If a migration
4626
+ * backup stream exists, throws so the retry loop backs off until migration completes;
4627
+ * creating a consumer mid-migration would eat workqueue messages being restored.
4427
4628
  */
4428
4629
  async recoverConsumer(jsm, kind) {
4429
4630
  const stream = this.streamProvider.getStreamName(kind);
4430
4631
  const config = this.buildConfig(kind);
4431
4632
  const name = config.durable_name;
4633
+ const spanAttrs = {
4634
+ serviceName: this.otelServiceName,
4635
+ endpoint: this.otelEndpoint,
4636
+ entity: "consumer",
4637
+ name
4638
+ };
4639
+ if (resolveManagementMode(this.options, kind, "consumer") === "manual" /* Manual */) {
4640
+ return withProvisioningSpan(this.otel, { ...spanAttrs, action: "rebind" }, async () => {
4641
+ try {
4642
+ return await jsm.consumers.info(stream, name);
4643
+ } catch (err) {
4644
+ if (err instanceof import_jetstream24.JetStreamApiError && err.apiError().err_code === 10014 /* ConsumerNotFound */) {
4645
+ throw new Error(
4646
+ `Consumer ${name} on ${stream} is externally managed and currently absent; waiting for it to be restored.`
4647
+ );
4648
+ }
4649
+ throw err;
4650
+ }
4651
+ });
4652
+ }
4432
4653
  return withProvisioningSpan(
4433
4654
  this.otel,
4434
4655
  {
4435
- serviceName: this.otelServiceName,
4436
- endpoint: this.otelEndpoint,
4437
- entity: "consumer",
4438
- name,
4656
+ ...spanAttrs,
4439
4657
  action: "recover"
4440
4658
  },
4441
4659
  async () => {
@@ -4444,7 +4662,7 @@ var ConsumerProvider = class {
4444
4662
  try {
4445
4663
  return await jsm.consumers.info(stream, name);
4446
4664
  } catch (err) {
4447
- if (!(err instanceof import_jetstream21.JetStreamApiError) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
4665
+ if (!(err instanceof import_jetstream24.JetStreamApiError) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
4448
4666
  throw err;
4449
4667
  }
4450
4668
  return await this.createConsumer(jsm, stream, name, kind, config);
@@ -4465,26 +4683,24 @@ var ConsumerProvider = class {
4465
4683
  `Stream ${stream} is being migrated (backup ${backupName} exists). Waiting for migration to complete before recovering consumer.`
4466
4684
  );
4467
4685
  } catch (err) {
4468
- if (err instanceof import_jetstream21.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
4686
+ if (err instanceof import_jetstream24.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
4469
4687
  return;
4470
4688
  }
4471
4689
  throw err;
4472
4690
  }
4473
4691
  }
4474
- /**
4475
- * Create a consumer, handling the race where another pod creates it first.
4476
- */
4692
+ /** Create a consumer, handling the race where another pod creates it first. */
4477
4693
  async createConsumer(jsm, stream, name, kind, config) {
4478
4694
  this.logger.log(`Creating consumer: ${name}`);
4479
4695
  const ctx = { entity: "consumer", name, kind };
4480
4696
  try {
4481
4697
  return await jsm.consumers.add(stream, config);
4482
4698
  } catch (addErr) {
4483
- if (addErr instanceof import_jetstream21.JetStreamApiError && addErr.apiError().err_code === 10148 /* ConsumerAlreadyExists */) {
4699
+ if (addErr instanceof import_jetstream24.JetStreamApiError && addErr.apiError().err_code === 10148 /* ConsumerAlreadyExists */) {
4484
4700
  this.logger.debug(`Consumer ${name} created by another pod, using existing`);
4485
4701
  return await jsm.consumers.info(stream, name);
4486
4702
  }
4487
- if (addErr instanceof import_jetstream21.JetStreamApiError) {
4703
+ if (addErr instanceof import_jetstream24.JetStreamApiError) {
4488
4704
  throw mapProvisioningError(addErr, ctx);
4489
4705
  }
4490
4706
  throw addErr;
@@ -4494,17 +4710,15 @@ var ConsumerProvider = class {
4494
4710
  try {
4495
4711
  return await op();
4496
4712
  } catch (err) {
4497
- if (err instanceof import_jetstream21.JetStreamApiError) {
4713
+ if (err instanceof import_jetstream24.JetStreamApiError) {
4498
4714
  throw mapProvisioningError(err, ctx);
4499
4715
  }
4500
4716
  throw err;
4501
4717
  }
4502
4718
  }
4503
4719
  /** Build consumer config by merging defaults with user overrides. */
4504
- // eslint-disable-next-line @typescript-eslint/naming-convention -- NATS API uses snake_case
4505
4720
  buildConfig(kind) {
4506
- const name = this.getConsumerName(kind);
4507
- const serviceName = internalName(this.options.name);
4721
+ const durableName = this.getConsumerName(kind);
4508
4722
  const defaults = this.getDefaults(kind);
4509
4723
  const overrides = this.getOverrides(kind);
4510
4724
  if (kind === "broadcast" /* Broadcast */) {
@@ -4516,31 +4730,54 @@ var ConsumerProvider = class {
4516
4730
  return {
4517
4731
  ...defaults,
4518
4732
  ...overrides,
4519
- name,
4520
- durable_name: name,
4733
+ name: durableName,
4734
+ durable_name: durableName,
4521
4735
  filter_subject: broadcastPatterns[0]
4522
4736
  };
4523
4737
  }
4524
4738
  return {
4525
4739
  ...defaults,
4526
4740
  ...overrides,
4527
- name,
4528
- durable_name: name,
4741
+ name: durableName,
4742
+ durable_name: durableName,
4529
4743
  filter_subjects: broadcastPatterns
4530
4744
  };
4531
4745
  }
4532
4746
  if (kind !== "ev" /* Event */ && kind !== "cmd" /* Command */) {
4533
4747
  throw new Error(`Unexpected durable consumer kind: ${kind}`);
4534
4748
  }
4535
- const filter_subject = `${serviceName}.${kind}.>`;
4749
+ if (this.names.hasCustomPrefix(kind)) {
4750
+ return this.buildCustomPrefixConfig(kind, durableName, defaults, overrides);
4751
+ }
4752
+ const filter_subject = this.names.filterSubject(kind);
4536
4753
  return {
4537
4754
  ...defaults,
4538
4755
  ...overrides,
4539
- name,
4540
- durable_name: name,
4756
+ name: durableName,
4757
+ durable_name: durableName,
4541
4758
  filter_subject
4542
4759
  };
4543
4760
  }
4761
+ buildCustomPrefixConfig(kind, durableName, defaults, overrides) {
4762
+ const patterns = kind === "ev" /* Event */ ? this.patternRegistry.getEventPatterns() : this.patternRegistry.getCommandPatterns();
4763
+ const subjects = patterns.map((p) => this.names.subject(kind, p));
4764
+ if (subjects.length === 1) {
4765
+ return {
4766
+ ...defaults,
4767
+ ...overrides,
4768
+ name: durableName,
4769
+ durable_name: durableName,
4770
+ filter_subject: subjects[0]
4771
+ };
4772
+ }
4773
+ return {
4774
+ ...defaults,
4775
+ ...overrides,
4776
+ name: durableName,
4777
+ durable_name: durableName,
4778
+ filter_subjects: subjects
4779
+ };
4780
+ }
4544
4781
  /** Get default config for a consumer kind. */
4545
4782
  getDefaults(kind) {
4546
4783
  switch (kind) {
@@ -4561,27 +4798,13 @@ var ConsumerProvider = class {
4561
4798
  }
4562
4799
  /** Get user-provided overrides for a consumer kind. */
4563
4800
  getOverrides(kind) {
4564
- switch (kind) {
4565
- case "ev" /* Event */:
4566
- return this.options.events?.consumer ?? {};
4567
- case "cmd" /* Command */:
4568
- return this.options.rpc?.mode === "jetstream" ? this.options.rpc.consumer ?? {} : {};
4569
- case "broadcast" /* Broadcast */:
4570
- return this.options.broadcast?.consumer ?? {};
4571
- case "ordered" /* Ordered */:
4572
- throw new Error("Ordered consumers are ephemeral and should not use durable config");
4573
- /* v8 ignore next 5 -- exhaustive switch guard, unreachable */
4574
- default: {
4575
- const _exhaustive = kind;
4576
- throw new Error(`Unexpected StreamKind: ${_exhaustive}`);
4577
- }
4578
- }
4801
+ return kindOptionsBlock(this.options, kind)?.consumer ?? {};
4579
4802
  }
4580
4803
  };
4581
4804
 
4582
4805
  // src/server/infrastructure/message.provider.ts
4583
- var import_common16 = require("@nestjs/common");
4584
- var import_jetstream23 = require("@nats-io/jetstream");
4806
+ var import_common17 = require("@nestjs/common");
4807
+ var import_jetstream26 = require("@nats-io/jetstream");
4585
4808
  var import_rxjs3 = require("rxjs");
4586
4809
  var MessageProvider = class {
4587
4810
  constructor(connection, eventBus, consumeOptionsMap = /* @__PURE__ */ new Map(), consumerRecoveryFn) {
@@ -4590,7 +4813,7 @@ var MessageProvider = class {
4590
4813
  this.consumeOptionsMap = consumeOptionsMap;
4591
4814
  this.consumerRecoveryFn = consumerRecoveryFn;
4592
4815
  }
4593
- logger = new import_common16.Logger("Jetstream:Message");
4816
+ logger = new import_common17.Logger("Jetstream:Message");
4594
4817
  activeIterators = /* @__PURE__ */ new Set();
4595
4818
  orderedReadyResolve = null;
4596
4819
  orderedReadyReject = null;
@@ -4633,7 +4856,7 @@ var MessageProvider = class {
4633
4856
  /**
4634
4857
  * Start an ordered consumer for strict sequential delivery.
4635
4858
  *
4636
- * Unlike durable consumers, ordered consumers are ephemeral created at
4859
+ * Unlike durable consumers, ordered consumers are ephemeral: created at
4637
4860
  * consumption time, no durable state. nats.js handles auto-recreation.
4638
4861
  *
4639
4862
  * @param streamName - JetStream stream to consume from.
@@ -4642,7 +4865,7 @@ var MessageProvider = class {
4642
4865
  */
4643
4866
  async startOrdered(streamName2, filterSubjects, orderedConfig) {
4644
4867
  const consumerOpts = { filter_subjects: filterSubjects };
4645
- if (orderedConfig?.deliverPolicy !== void 0 && orderedConfig.deliverPolicy !== import_jetstream23.DeliverPolicy.All) {
4868
+ if (orderedConfig?.deliverPolicy !== void 0 && orderedConfig.deliverPolicy !== import_jetstream26.DeliverPolicy.All) {
4646
4869
  consumerOpts.deliver_policy = orderedConfig.deliverPolicy;
4647
4870
  }
4648
4871
  if (orderedConfig?.optStartSeq !== void 0) {
@@ -4847,7 +5070,7 @@ var MessageProvider = class {
4847
5070
  };
4848
5071
 
4849
5072
  // src/server/infrastructure/metadata.provider.ts
4850
- var import_common17 = require("@nestjs/common");
5073
+ var import_common18 = require("@nestjs/common");
4851
5074
  var import_kv = require("@nats-io/kv");
4852
5075
  var MetadataProvider = class {
4853
5076
  constructor(options, connection) {
@@ -4856,7 +5079,7 @@ var MetadataProvider = class {
4856
5079
  this.replicas = options.metadata?.replicas ?? DEFAULT_METADATA_REPLICAS;
4857
5080
  this.ttl = Math.max(options.metadata?.ttl ?? DEFAULT_METADATA_TTL, MIN_METADATA_TTL);
4858
5081
  }
4859
- logger = new import_common17.Logger("Jetstream:Metadata");
5082
+ logger = new import_common18.Logger("Jetstream:Metadata");
4860
5083
  bucketName;
4861
5084
  replicas;
4862
5085
  ttl;
@@ -4864,16 +5087,12 @@ var MetadataProvider = class {
4864
5087
  heartbeatTimer;
4865
5088
  cachedKv;
4866
5089
  /**
4867
- * Write handler metadata entries to the KV bucket and start heartbeat.
5090
+ * Write handler metadata entries to the KV bucket and start the heartbeat.
4868
5091
  *
4869
- * Creates the bucket if it doesn't exist (idempotent).
4870
- * Skips silently when entries map is empty.
4871
- * Starts a heartbeat interval that refreshes entries every `ttl / 2`
4872
- * to prevent TTL expiry while the pod is alive.
5092
+ * Creates the bucket if missing; skips silently when the map is empty.
5093
+ * Non-critical: errors are logged but do not prevent transport startup.
4873
5094
  *
4874
- * Non-critical errors are logged but do not prevent transport startup.
4875
- *
4876
- * @param entries Map of KV key → metadata object.
5095
+ * @param entries Map of KV key to metadata object.
4877
5096
  */
4878
5097
  async publish(entries) {
4879
5098
  if (entries.size === 0) return;
@@ -4949,396 +5168,626 @@ var MetadataProvider = class {
4949
5168
  };
4950
5169
 
4951
5170
  // src/server/routing/event.router.ts
4952
- var import_common18 = require("@nestjs/common");
4953
- var import_transport_node4 = require("@nats-io/transport-node");
4954
- var DLQ_PUBLISH_ATTEMPTS = 3;
4955
- var eventConsumeKindFor = (kind) => {
4956
- if (kind === "broadcast" /* Broadcast */) return "broadcast" /* Broadcast */;
4957
- if (kind === "ordered" /* Ordered */) return "ordered" /* Ordered */;
4958
- return "event" /* Event */;
5171
+ var import_common20 = require("@nestjs/common");
5172
+
5173
+ // src/server/routing/concurrency-gate.ts
5174
+ var BACKLOG_WARN_THRESHOLD = 1e3;
5175
+ var ConcurrencyGate = class {
5176
+ constructor(maxActive, route, parkTimer, logger5, label) {
5177
+ this.maxActive = maxActive;
5178
+ this.route = route;
5179
+ this.parkTimer = parkTimer;
5180
+ this.logger = logger5;
5181
+ this.label = label;
5182
+ }
5183
+ active = 0;
5184
+ backlogWarned = false;
5185
+ backlog = [];
5186
+ /** Entry point for each incoming message. */
5187
+ push(msg) {
5188
+ if (this.active >= this.maxActive) {
5189
+ this.backlog.push({
5190
+ msg,
5191
+ stopAckExtension: this.parkTimer ? this.parkTimer(msg) : null
5192
+ });
5193
+ if (!this.backlogWarned && this.backlog.length >= BACKLOG_WARN_THRESHOLD) {
5194
+ this.backlogWarned = true;
5195
+ this.logger.warn(
5196
+ `${this.label} backlog reached ${this.backlog.length} messages; consumer may be falling behind`
5197
+ );
5198
+ }
5199
+ return;
5200
+ }
5201
+ this.active++;
5202
+ const result = this.routeSafely(msg);
5203
+ if (result !== void 0) {
5204
+ this.trackAsync(result, msg);
5205
+ } else {
5206
+ this.active--;
5207
+ if (this.backlog.length > 0) this.drainBacklog();
5208
+ }
5209
+ }
5210
+ /** Stop parked timers and drop the backlog. */
5211
+ dispose() {
5212
+ for (const queued of this.backlog) {
5213
+ queued.stopAckExtension?.();
5214
+ }
5215
+ this.backlog.length = 0;
5216
+ }
5217
+ onAsyncDone = () => {
5218
+ this.active--;
5219
+ this.drainBacklog();
5220
+ };
5221
+ /** A throw here must not leak the concurrency slot or kill the subscription. */
5222
+ routeSafely(msg) {
5223
+ try {
5224
+ return this.route(msg);
5225
+ } catch (err) {
5226
+ this.logger.error(`Unexpected routing failure for ${msg.subject}:`, err);
5227
+ return void 0;
5228
+ }
5229
+ }
5230
+ trackAsync(result, msg) {
5231
+ void result.catch((err) => {
5232
+ this.logger.error(`Unexpected routing failure for ${msg.subject}:`, err);
5233
+ }).finally(this.onAsyncDone);
5234
+ }
5235
+ drainBacklog() {
5236
+ while (this.active < this.maxActive) {
5237
+ const next = this.backlog.shift();
5238
+ if (next === void 0) break;
5239
+ next.stopAckExtension?.();
5240
+ this.active++;
5241
+ const result = this.routeSafely(next.msg);
5242
+ if (result !== void 0) {
5243
+ this.trackAsync(result, next.msg);
5244
+ } else {
5245
+ this.active--;
5246
+ }
5247
+ }
5248
+ if (this.backlog.length < BACKLOG_WARN_THRESHOLD) this.backlogWarned = false;
5249
+ }
4959
5250
  };
4960
- var EventRouter = class {
4961
- constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig, processingConfig, ackWaitMap, connection, options) {
4962
- this.messageProvider = messageProvider;
5251
+
5252
+ // src/server/routing/dead-letter-capture.ts
5253
+ var import_common19 = require("@nestjs/common");
5254
+ var import_transport_node5 = require("@nats-io/transport-node");
5255
+ var DLQ_PUBLISH_ATTEMPTS = 3;
5256
+ var DeadLetterCapture = class {
5257
+ constructor(patternRegistry, eventBus, deadLetterConfig, otel, serviceName, serverEndpoint, connection, options, names) {
4963
5258
  this.patternRegistry = patternRegistry;
4964
- this.codec = codec;
4965
5259
  this.eventBus = eventBus;
4966
5260
  this.deadLetterConfig = deadLetterConfig;
4967
- this.processingConfig = processingConfig;
4968
- this.ackWaitMap = ackWaitMap;
5261
+ this.otel = otel;
5262
+ this.serviceName = serviceName;
5263
+ this.serverEndpoint = serverEndpoint;
4969
5264
  this.connection = connection;
4970
5265
  this.options = options;
4971
- if (options) {
4972
- const derived = deriveOtelAttrs(options);
4973
- this.otel = derived.otel;
4974
- this.serviceName = derived.serviceName;
4975
- this.serverEndpoint = derived.serverEndpoint;
4976
- } else {
4977
- this.otel = resolveOtelOptions({ enabled: false });
4978
- this.serviceName = "";
4979
- this.serverEndpoint = null;
5266
+ this.names = names;
5267
+ }
5268
+ logger = new import_common19.Logger("Jetstream:DeadLetter");
5269
+ /** True when this delivery is the consumer's last attempt for the message. */
5270
+ isFinalDelivery(msg) {
5271
+ const maxDeliver = this.deadLetterConfig.maxDeliverByStream?.get(msg.info.stream);
5272
+ if (maxDeliver === void 0 || maxDeliver <= 0) return false;
5273
+ return msg.info.deliveryCount >= maxDeliver;
5274
+ }
5275
+ /** Emit the dead-letter event and route the message to the DLQ or the fallback callback. */
5276
+ async capture(msg, data, error) {
5277
+ const info = {
5278
+ subject: msg.subject,
5279
+ data,
5280
+ headers: msg.headers,
5281
+ error,
5282
+ deliveryCount: msg.info.deliveryCount,
5283
+ stream: msg.info.stream,
5284
+ streamSequence: msg.info.streamSequence,
5285
+ timestamp: new Date(msg.info.timestampNanos / 1e6).toISOString()
5286
+ };
5287
+ await withDeadLetterSpan(
5288
+ {
5289
+ msg,
5290
+ // Surface the registered pattern so APM can filter dead letters by
5291
+ // handler; falls back to the raw subject when no handler matches.
5292
+ pattern: this.patternRegistry.getHandler(msg.subject) ? msg.subject : void 0,
5293
+ finalDeliveryCount: msg.info.deliveryCount,
5294
+ reason: error instanceof Error ? error.message : String(error),
5295
+ serviceName: this.serviceName,
5296
+ endpoint: this.serverEndpoint
5297
+ },
5298
+ this.otel,
5299
+ async () => {
5300
+ this.eventBus.emit("deadLetter" /* DeadLetter */, info);
5301
+ if (!this.options?.dlq) {
5302
+ await this.fallbackToOnDeadLetterCallback(info, msg);
5303
+ } else {
5304
+ await this.publishToDlq(msg, info, error);
5305
+ }
5306
+ }
5307
+ );
5308
+ }
5309
+ /**
5310
+ * Publish the dead letter to the DLQ stream with diagnostic headers. On
5311
+ * success the `onDeadLetter` callback is notified and the message termed;
5312
+ * on failure everything falls back to the callback to avoid silent loss.
5313
+ */
5314
+ async publishToDlq(msg, info, error) {
5315
+ const serviceName = this.options?.name;
5316
+ if (!this.connection || !serviceName) {
5317
+ this.logger.error(
5318
+ `Cannot publish to DLQ for ${msg.subject}: Connection or Module Options unavailable`
5319
+ );
5320
+ await this.fallbackToOnDeadLetterCallback(info, msg);
5321
+ return;
5322
+ }
5323
+ const dlqStreamOverride = this.options.dlq?.stream?.name;
5324
+ const destinationSubject = this.names ? this.names.dlqStreamName() : dlqStreamOverride ?? dlqStreamName(serviceName);
5325
+ const hdrs = this.buildDlqHeaders(msg);
5326
+ hdrs.set("x-dead-letter-reason" /* DeadLetterReason */, this.extractErrorReason(error));
5327
+ hdrs.set("x-original-subject" /* OriginalSubject */, msg.subject);
5328
+ hdrs.set("x-original-stream" /* OriginalStream */, msg.info.stream);
5329
+ hdrs.set("x-failed-at" /* FailedAt */, (/* @__PURE__ */ new Date()).toISOString());
5330
+ hdrs.set("x-delivery-count" /* DeliveryCount */, msg.info.deliveryCount.toString());
5331
+ try {
5332
+ await this.publishWithRetry(this.connection, destinationSubject, msg.data, hdrs);
5333
+ this.logger.log(`Message sent to DLQ: ${msg.subject}`);
5334
+ await this.notifyDeadLetterCallback(info, msg);
5335
+ } catch (publishErr) {
5336
+ this.logger.error(`Failed to publish to DLQ for ${msg.subject}:`, publishErr);
5337
+ await this.fallbackToOnDeadLetterCallback(info, msg);
4980
5338
  }
4981
5339
  }
4982
- logger = new import_common18.Logger("Jetstream:EventRouter");
4983
- subscriptions = [];
4984
- otel;
4985
- serviceName;
4986
- serverEndpoint;
4987
5340
  /**
4988
- * Update the max_deliver thresholds from actual NATS consumer configs.
4989
- * Called after consumers are ensured so the DLQ map reflects reality.
5341
+ * Past max_deliver the server never redelivers, so these in-process attempts
5342
+ * are the only second chance a dead letter gets. No artificial delay: an
5343
+ * unreachable broker already spaces attempts via its own request timeout.
4990
5344
  */
4991
- updateMaxDeliverMap(consumerMaxDelivers) {
4992
- if (!this.deadLetterConfig) return;
4993
- this.deadLetterConfig.maxDeliverByStream = consumerMaxDelivers;
5345
+ async publishWithRetry(connection, subject, data, headers2) {
5346
+ let lastErr;
5347
+ for (let attempt = 1; attempt <= DLQ_PUBLISH_ATTEMPTS; attempt += 1) {
5348
+ try {
5349
+ await connection.getJetStreamClient().publish(subject, data, { headers: headers2 });
5350
+ return;
5351
+ } catch (err) {
5352
+ lastErr = err;
5353
+ if (attempt < DLQ_PUBLISH_ATTEMPTS) {
5354
+ this.logger.warn(
5355
+ `DLQ publish attempt ${attempt}/${DLQ_PUBLISH_ATTEMPTS} failed for ${subject}, retrying`
5356
+ );
5357
+ }
5358
+ }
5359
+ }
5360
+ throw lastErr;
4994
5361
  }
4995
- /** Start routing event, broadcast, and ordered messages to handlers. */
4996
- start() {
4997
- this.subscribeToStream(this.messageProvider.events$, "ev" /* Event */);
4998
- this.subscribeToStream(this.messageProvider.broadcasts$, "broadcast" /* Broadcast */);
4999
- if (this.patternRegistry.hasOrderedHandlers()) {
5000
- this.subscribeToStream(this.messageProvider.ordered$, "ordered" /* Ordered */);
5362
+ /**
5363
+ * Copy headers for the DLQ republish, dropping NATS control headers: a
5364
+ * copied Nats-TTL would expire the DLQ entry, Nats-Msg-Id trips dedup.
5365
+ */
5366
+ buildDlqHeaders(msg) {
5367
+ const hdrs = (0, import_transport_node5.headers)();
5368
+ if (!msg.headers) return hdrs;
5369
+ for (const [k, v] of msg.headers) {
5370
+ if (k.toLowerCase().startsWith(NATS_CONTROL_HEADER_PREFIX)) continue;
5371
+ for (const val of v) {
5372
+ hdrs.append(k, val);
5373
+ }
5001
5374
  }
5375
+ return hdrs;
5002
5376
  }
5003
- /** Stop routing and unsubscribe from all streams. */
5004
- destroy() {
5005
- for (const sub of this.subscriptions) {
5006
- sub.unsubscribe();
5377
+ async notifyDeadLetterCallback(info, msg) {
5378
+ if (this.deadLetterConfig.onDeadLetter) {
5379
+ try {
5380
+ await this.deadLetterConfig.onDeadLetter(info);
5381
+ } catch (hookErr) {
5382
+ this.logger.warn(
5383
+ `onDeadLetter callback failed after successful DLQ publish for ${msg.subject}`,
5384
+ hookErr
5385
+ );
5386
+ }
5007
5387
  }
5008
- this.subscriptions.length = 0;
5388
+ settleQuietly(this.logger, `Failed to term ${msg.subject}:`, () => {
5389
+ msg.term("Moved to DLQ stream");
5390
+ });
5009
5391
  }
5010
- /** Subscribe to a message stream and route each message to its handler. */
5011
- subscribeToStream(stream$, kind) {
5012
- const isOrdered = kind === "ordered" /* Ordered */;
5013
- const patternRegistry = this.patternRegistry;
5014
- const codec = this.codec;
5015
- const eventBus = this.eventBus;
5016
- const logger5 = this.logger;
5017
- const deadLetterConfig = this.deadLetterConfig;
5018
- const otel = this.otel;
5019
- const serviceName = this.serviceName;
5020
- const serverEndpoint = this.serverEndpoint;
5021
- const spanKind = eventConsumeKindFor(kind);
5022
- const ackExtensionInterval = isOrdered ? null : resolveAckExtensionInterval(this.getAckExtensionConfig(kind), this.ackWaitMap?.get(kind));
5023
- const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
5024
- const concurrency = this.getConcurrency(kind);
5025
- const hasDlqCheck = deadLetterConfig !== void 0;
5026
- const reportHandlerCompleted = (msg, startedAt, status) => {
5027
- if (!eventBus.hasHook("handlerCompleted" /* HandlerCompleted */)) return;
5028
- const declared = patternRegistry.resolveDeclared(msg.subject);
5029
- const pattern = declared?.pattern ?? msg.subject;
5030
- const declaredKind = declared?.kind ?? kind;
5031
- const durationMs = performance.now() - startedAt;
5032
- eventBus.emit("handlerCompleted" /* HandlerCompleted */, pattern, declaredKind, durationMs, status);
5033
- };
5034
- const isDeadLetter = (msg) => {
5035
- if (!hasDlqCheck) return false;
5036
- const maxDeliver = deadLetterConfig.maxDeliverByStream?.get(msg.info.stream);
5037
- if (maxDeliver === void 0 || maxDeliver <= 0) return false;
5038
- return msg.info.deliveryCount >= maxDeliver;
5039
- };
5040
- const handleDeadLetter = hasDlqCheck ? (msg, data, err) => this.handleDeadLetter(msg, data, err) : null;
5041
- const settleSuccess = (msg, ctx, data) => {
5042
- if (ctx.shouldTerminate) {
5043
- settleQuietly(logger5, `Failed to term ${msg.subject}:`, () => {
5044
- msg.term(ctx.terminateReason);
5045
- });
5046
- return void 0;
5047
- }
5048
- if (ctx.shouldRetry) {
5049
- if (handleDeadLetter !== null && isDeadLetter(msg)) {
5050
- return handleDeadLetter(
5051
- msg,
5052
- data,
5053
- new Error("Retry requested on the final delivery attempt")
5054
- );
5055
- }
5056
- settleQuietly(logger5, `Failed to nak ${msg.subject}:`, () => {
5057
- msg.nak(ctx.retryDelay);
5058
- });
5059
- return void 0;
5060
- }
5061
- settleQuietly(logger5, `Failed to ack ${msg.subject}:`, () => {
5062
- msg.ack();
5392
+ /**
5393
+ * Last resort: invoke onDeadLetter, then term on success. On failure the
5394
+ * message is nak'd: never redelivered past max_deliver, but preserved.
5395
+ */
5396
+ async fallbackToOnDeadLetterCallback(info, msg) {
5397
+ const onDeadLetter = this.deadLetterConfig.onDeadLetter;
5398
+ if (!onDeadLetter) {
5399
+ this.logger.error(
5400
+ `Dead letter for ${msg.subject} could not be captured (DLQ publish failed, no onDeadLetter callback); leaving the message in the stream`
5401
+ );
5402
+ settleQuietly(this.logger, `Failed to nak ${msg.subject}:`, () => {
5403
+ msg.nak();
5404
+ });
5405
+ return;
5406
+ }
5407
+ try {
5408
+ await onDeadLetter(info);
5409
+ settleQuietly(this.logger, `Failed to term ${msg.subject}:`, () => {
5410
+ msg.term("Dead letter processed via fallback callback");
5411
+ });
5412
+ } catch (hookErr) {
5413
+ this.logger.error(
5414
+ `Fallback onDeadLetter callback failed for ${msg.subject}; the message stays in the stream and will not be redelivered (max_deliver exhausted); recover it manually:`,
5415
+ hookErr
5416
+ );
5417
+ settleQuietly(this.logger, `Failed to nak ${msg.subject}:`, () => {
5418
+ msg.nak();
5419
+ });
5420
+ }
5421
+ }
5422
+ extractErrorReason(error) {
5423
+ if (error instanceof Error) {
5424
+ return error.message;
5425
+ }
5426
+ if (typeof error === "object" && error !== null && "message" in error) {
5427
+ return String(error.message);
5428
+ }
5429
+ return String(error);
5430
+ }
5431
+ };
5432
+
5433
+ // src/server/routing/settlement.ts
5434
+ var statusForContext = (ctx) => {
5435
+ if (ctx.shouldTerminate) return "terminated";
5436
+ if (ctx.shouldRetry) return "retried";
5437
+ return "success";
5438
+ };
5439
+ var createSettlement = (logger5, capture) => {
5440
+ const settleSuccess = (msg, ctx, data) => {
5441
+ if (ctx.shouldTerminate) {
5442
+ settleQuietly(logger5, `Failed to term ${msg.subject}:`, () => {
5443
+ msg.term(ctx.terminateReason);
5063
5444
  });
5064
5445
  return void 0;
5065
- };
5066
- const settleFailure = async (msg, data, err) => {
5067
- if (handleDeadLetter !== null && isDeadLetter(msg)) {
5068
- await handleDeadLetter(msg, data, err);
5069
- return;
5446
+ }
5447
+ if (ctx.shouldRetry) {
5448
+ if (capture?.isFinalDelivery(msg)) {
5449
+ return capture.capture(
5450
+ msg,
5451
+ data,
5452
+ new Error("Retry requested on the final delivery attempt")
5453
+ );
5070
5454
  }
5071
5455
  settleQuietly(logger5, `Failed to nak ${msg.subject}:`, () => {
5072
- msg.nak();
5456
+ msg.nak(ctx.retryDelay);
5073
5457
  });
5074
- };
5075
- const captureUnroutable = (capture, msg, err) => {
5458
+ return void 0;
5459
+ }
5460
+ settleQuietly(logger5, `Failed to ack ${msg.subject}:`, () => {
5461
+ msg.ack();
5462
+ });
5463
+ return void 0;
5464
+ };
5465
+ const settleFailure = async (msg, data, err) => {
5466
+ if (capture?.isFinalDelivery(msg)) {
5467
+ await capture.capture(msg, data, err);
5468
+ return;
5469
+ }
5470
+ settleQuietly(logger5, `Failed to nak ${msg.subject}:`, () => {
5471
+ msg.nak();
5472
+ });
5473
+ };
5474
+ return { settleSuccess, settleFailure };
5475
+ };
5476
+
5477
+ // src/server/routing/event-pipeline.ts
5478
+ var createHandlerReporter = (rctx) => {
5479
+ const { eventBus, patternRegistry, kind } = rctx;
5480
+ return (msg, startedAt, status) => {
5481
+ if (!eventBus.hasHook("handlerCompleted" /* HandlerCompleted */)) return;
5482
+ const declared = patternRegistry.resolveDeclared(msg.subject);
5483
+ const pattern = declared?.pattern ?? msg.subject;
5484
+ const declaredKind = declared?.kind ?? kind;
5485
+ const durationMs = performance.now() - startedAt;
5486
+ eventBus.emit("handlerCompleted" /* HandlerCompleted */, pattern, declaredKind, durationMs, status);
5487
+ };
5488
+ };
5489
+ var createEventResolver = (rctx) => {
5490
+ const { patternRegistry, codec, eventBus, logger: logger5, capture, kind } = rctx;
5491
+ const captureUnroutable = (activeCapture, msg, err) => {
5492
+ let data;
5493
+ try {
5494
+ data = codec.decode(msg.data);
5495
+ } catch {
5496
+ data = void 0;
5497
+ }
5498
+ return activeCapture.capture(msg, data, err).catch((captureErr) => {
5499
+ logger5.error(`Dead-letter capture failed for unroutable ${msg.subject}:`, captureErr);
5500
+ });
5501
+ };
5502
+ return (msg) => {
5503
+ const subject = msg.subject;
5504
+ try {
5505
+ const handler = patternRegistry.getHandler(subject);
5506
+ if (!handler) {
5507
+ logger5.error(`No handler for subject: ${subject}`);
5508
+ if (capture !== null) {
5509
+ return captureUnroutable(capture, msg, new Error(`No handler for event: ${subject}`));
5510
+ }
5511
+ msg.term(`No handler for event: ${subject}`);
5512
+ return null;
5513
+ }
5076
5514
  let data;
5077
5515
  try {
5078
5516
  data = codec.decode(msg.data);
5079
- } catch {
5080
- data = void 0;
5081
- }
5082
- return capture(msg, data, err).catch((captureErr) => {
5083
- logger5.error(`Dead-letter capture failed for unroutable ${msg.subject}:`, captureErr);
5084
- });
5085
- };
5086
- const resolveEvent = (msg) => {
5087
- const subject = msg.subject;
5088
- try {
5089
- const handler = patternRegistry.getHandler(subject);
5090
- if (!handler) {
5091
- logger5.error(`No handler for subject: ${subject}`);
5092
- if (handleDeadLetter !== null) {
5093
- return captureUnroutable(
5094
- handleDeadLetter,
5095
- msg,
5096
- new Error(`No handler for event: ${subject}`)
5097
- );
5098
- }
5099
- msg.term(`No handler for event: ${subject}`);
5100
- return null;
5101
- }
5102
- let data;
5103
- try {
5104
- data = codec.decode(msg.data);
5105
- } catch (err) {
5106
- logger5.error(`Decode error for ${subject}:`, err);
5107
- if (handleDeadLetter !== null) {
5108
- return captureUnroutable(
5109
- handleDeadLetter,
5110
- msg,
5111
- new Error(`Decode error: ${err instanceof Error ? err.message : String(err)}`)
5112
- );
5113
- }
5114
- msg.term("Decode error");
5115
- return null;
5116
- }
5117
- eventBus.emitMessageRouted(subject, "event" /* Event */);
5118
- return { handler, data };
5119
5517
  } catch (err) {
5120
- logger5.error(`Unexpected error in ${kind} event router`, err);
5121
- try {
5122
- msg.term("Unexpected router error");
5123
- } catch (termErr) {
5124
- logger5.error(`Failed to terminate message ${subject}:`, termErr);
5518
+ logger5.error(`Decode error for ${subject}:`, err);
5519
+ if (capture !== null) {
5520
+ return captureUnroutable(
5521
+ capture,
5522
+ msg,
5523
+ new Error(`Decode error: ${err instanceof Error ? err.message : String(err)}`)
5524
+ );
5125
5525
  }
5526
+ msg.term("Decode error");
5126
5527
  return null;
5127
5528
  }
5128
- };
5129
- const statusForContext = (ctx) => {
5130
- if (ctx.shouldTerminate) return "terminated";
5131
- if (ctx.shouldRetry) return "retried";
5132
- return "success";
5133
- };
5134
- const handleSafe = (msg) => {
5135
- const resolved = resolveEvent(msg);
5136
- if (resolved === null) return void 0;
5137
- if (isPromiseLike2(resolved)) return resolved;
5138
- const { handler, data } = resolved;
5139
- const ctx = new RpcContext([msg]);
5140
- const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
5141
- const startedAt = performance.now();
5142
- let pending;
5529
+ eventBus.emitMessageRouted(subject, "event" /* Event */);
5530
+ return { handler, data };
5531
+ } catch (err) {
5532
+ logger5.error(`Unexpected error in ${kind} event router`, err);
5143
5533
  try {
5144
- pending = withConsumeSpan(
5145
- {
5146
- subject: msg.subject,
5147
- msg,
5148
- info: msg.info,
5149
- kind: spanKind,
5150
- payloadBytes: msg.data.length,
5151
- handlerMetadata: { pattern: msg.subject },
5152
- serviceName,
5153
- endpoint: serverEndpoint
5154
- },
5155
- otel,
5156
- () => unwrapResult(handler(data, ctx))
5157
- );
5158
- } catch (err) {
5534
+ msg.term("Unexpected router error");
5535
+ } catch (termErr) {
5536
+ logger5.error(`Failed to terminate message ${subject}:`, termErr);
5537
+ }
5538
+ return null;
5539
+ }
5540
+ };
5541
+ };
5542
+ var createWorkqueuePipeline = (rctx) => {
5543
+ const { kind, spanKind, logger: logger5, eventBus, otel, serviceName, serverEndpoint } = rctx;
5544
+ const { ackExtensionInterval } = rctx;
5545
+ const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
5546
+ const reportHandlerCompleted = createHandlerReporter(rctx);
5547
+ const resolveEvent = createEventResolver(rctx);
5548
+ const { settleSuccess, settleFailure } = createSettlement(logger5, rctx.capture);
5549
+ return (msg) => {
5550
+ const resolved = resolveEvent(msg);
5551
+ if (resolved === null) return void 0;
5552
+ if (isPromiseLike2(resolved)) return resolved;
5553
+ const { handler, data } = resolved;
5554
+ const ctx = new RpcContext([msg]);
5555
+ const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
5556
+ const startedAt = performance.now();
5557
+ let pending;
5558
+ try {
5559
+ pending = withConsumeSpan(
5560
+ {
5561
+ subject: msg.subject,
5562
+ msg,
5563
+ info: msg.info,
5564
+ kind: spanKind,
5565
+ payloadBytes: msg.data.length,
5566
+ handlerMetadata: { pattern: msg.subject },
5567
+ serviceName,
5568
+ endpoint: serverEndpoint
5569
+ },
5570
+ otel,
5571
+ () => unwrapResult(handler(data, ctx))
5572
+ );
5573
+ } catch (err) {
5574
+ eventBus.emit(
5575
+ "error" /* Error */,
5576
+ err instanceof Error ? err : new Error(String(err)),
5577
+ `${kind}-handler:${msg.subject}`
5578
+ );
5579
+ reportHandlerCompleted(msg, startedAt, "error");
5580
+ return settleFailure(msg, data, err).finally(() => {
5581
+ if (stopAckExtension !== null) stopAckExtension();
5582
+ });
5583
+ }
5584
+ if (!isPromiseLike2(pending)) {
5585
+ const settled = settleSuccess(msg, ctx, data);
5586
+ reportHandlerCompleted(msg, startedAt, statusForContext(ctx));
5587
+ if (settled === void 0) {
5588
+ if (stopAckExtension !== null) stopAckExtension();
5589
+ return void 0;
5590
+ }
5591
+ return settled.finally(() => {
5592
+ if (stopAckExtension !== null) stopAckExtension();
5593
+ });
5594
+ }
5595
+ return pending.then(
5596
+ async () => {
5597
+ try {
5598
+ await settleSuccess(msg, ctx, data);
5599
+ reportHandlerCompleted(msg, startedAt, statusForContext(ctx));
5600
+ } finally {
5601
+ if (stopAckExtension !== null) stopAckExtension();
5602
+ }
5603
+ },
5604
+ async (err) => {
5159
5605
  eventBus.emit(
5160
5606
  "error" /* Error */,
5161
5607
  err instanceof Error ? err : new Error(String(err)),
5162
5608
  `${kind}-handler:${msg.subject}`
5163
5609
  );
5164
5610
  reportHandlerCompleted(msg, startedAt, "error");
5165
- return settleFailure(msg, data, err).finally(() => {
5166
- if (stopAckExtension !== null) stopAckExtension();
5167
- });
5168
- }
5169
- if (!isPromiseLike2(pending)) {
5170
- const settled = settleSuccess(msg, ctx, data);
5171
- reportHandlerCompleted(msg, startedAt, statusForContext(ctx));
5172
- if (settled === void 0) {
5611
+ try {
5612
+ await settleFailure(msg, data, err);
5613
+ } finally {
5173
5614
  if (stopAckExtension !== null) stopAckExtension();
5174
- return void 0;
5175
5615
  }
5176
- return settled.finally(() => {
5177
- if (stopAckExtension !== null) stopAckExtension();
5178
- });
5179
5616
  }
5180
- return pending.then(
5181
- async () => {
5182
- try {
5183
- await settleSuccess(msg, ctx, data);
5184
- reportHandlerCompleted(msg, startedAt, statusForContext(ctx));
5185
- } finally {
5186
- if (stopAckExtension !== null) stopAckExtension();
5187
- }
5188
- },
5189
- async (err) => {
5190
- eventBus.emit(
5191
- "error" /* Error */,
5192
- err instanceof Error ? err : new Error(String(err)),
5193
- `${kind}-handler:${msg.subject}`
5194
- );
5195
- reportHandlerCompleted(msg, startedAt, "error");
5196
- try {
5197
- await settleFailure(msg, data, err);
5198
- } finally {
5199
- if (stopAckExtension !== null) stopAckExtension();
5200
- }
5201
- }
5202
- );
5203
- };
5204
- const handleOrderedSafe = (msg) => {
5205
- const subject = msg.subject;
5206
- let handler;
5207
- let data;
5208
- try {
5209
- handler = patternRegistry.getHandler(subject);
5210
- if (!handler) {
5211
- logger5.error(`No handler for subject: ${subject}`);
5212
- return void 0;
5213
- }
5214
- try {
5215
- data = codec.decode(msg.data);
5216
- } catch (err) {
5217
- logger5.error(`Decode error for ${subject}:`, err);
5218
- return void 0;
5219
- }
5220
- eventBus.emitMessageRouted(subject, "event" /* Event */);
5221
- } catch (err) {
5222
- logger5.error(`Ordered handler error (${subject}):`, err);
5617
+ );
5618
+ };
5619
+ };
5620
+ var createOrderedPipeline = (rctx) => {
5621
+ const { spanKind, codec, logger: logger5, eventBus, patternRegistry, otel } = rctx;
5622
+ const { serviceName, serverEndpoint } = rctx;
5623
+ const reportHandlerCompleted = createHandlerReporter(rctx);
5624
+ return (msg) => {
5625
+ const subject = msg.subject;
5626
+ let handler;
5627
+ let data;
5628
+ try {
5629
+ handler = patternRegistry.getHandler(subject);
5630
+ if (!handler) {
5631
+ logger5.error(`No handler for subject: ${subject}`);
5223
5632
  return void 0;
5224
5633
  }
5225
- const ctx = new RpcContext([msg]);
5226
- const warnIfSettlementAttempted = () => {
5227
- if (ctx.shouldRetry || ctx.shouldTerminate) {
5228
- logger5.warn(
5229
- `retry()/terminate() ignored for ordered message ${subject} \u2014 ordered consumers auto-acknowledge`
5230
- );
5231
- }
5232
- };
5233
- const startedAt = performance.now();
5234
- let pending;
5235
5634
  try {
5236
- pending = withConsumeSpan(
5237
- {
5238
- subject: msg.subject,
5239
- msg,
5240
- info: msg.info,
5241
- kind: spanKind,
5242
- payloadBytes: msg.data.length,
5243
- handlerMetadata: { pattern: msg.subject },
5244
- serviceName,
5245
- endpoint: serverEndpoint
5246
- },
5247
- otel,
5248
- () => unwrapResult(handler(data, ctx))
5249
- );
5635
+ data = codec.decode(msg.data);
5250
5636
  } catch (err) {
5251
- logger5.error(`Ordered handler error (${subject}):`, err);
5252
- reportHandlerCompleted(msg, startedAt, "error");
5637
+ logger5.error(`Decode error for ${subject}:`, err);
5253
5638
  return void 0;
5254
5639
  }
5255
- if (!isPromiseLike2(pending)) {
5256
- warnIfSettlementAttempted();
5257
- reportHandlerCompleted(msg, startedAt, "success");
5258
- return void 0;
5640
+ eventBus.emitMessageRouted(subject, "event" /* Event */);
5641
+ } catch (err) {
5642
+ logger5.error(`Ordered handler error (${subject}):`, err);
5643
+ return void 0;
5644
+ }
5645
+ const ctx = new RpcContext([msg]);
5646
+ const warnIfSettlementAttempted = () => {
5647
+ if (ctx.shouldRetry || ctx.shouldTerminate) {
5648
+ logger5.warn(
5649
+ `retry()/terminate() ignored for ordered message ${subject}; ordered consumers auto-acknowledge`
5650
+ );
5259
5651
  }
5260
- return pending.then(
5261
- () => {
5262
- warnIfSettlementAttempted();
5263
- reportHandlerCompleted(msg, startedAt, "success");
5652
+ };
5653
+ const startedAt = performance.now();
5654
+ let pending;
5655
+ try {
5656
+ pending = withConsumeSpan(
5657
+ {
5658
+ subject: msg.subject,
5659
+ msg,
5660
+ info: msg.info,
5661
+ kind: spanKind,
5662
+ payloadBytes: msg.data.length,
5663
+ handlerMetadata: { pattern: msg.subject },
5664
+ serviceName,
5665
+ endpoint: serverEndpoint
5264
5666
  },
5265
- (err) => {
5266
- logger5.error(`Ordered handler error (${subject}):`, err);
5267
- reportHandlerCompleted(msg, startedAt, "error");
5268
- }
5667
+ otel,
5668
+ () => unwrapResult(handler(data, ctx))
5269
5669
  );
5270
- };
5271
- const route = isOrdered ? handleOrderedSafe : handleSafe;
5272
- const maxActive = isOrdered ? 1 : concurrency ?? Number.POSITIVE_INFINITY;
5273
- const backlogWarnThreshold = 1e3;
5274
- let active = 0;
5275
- let backlogWarned = false;
5276
- const backlog = [];
5277
- const onAsyncDone = () => {
5278
- active--;
5279
- drainBacklog();
5280
- };
5281
- const routeSafely = (msg) => {
5282
- try {
5283
- return route(msg);
5284
- } catch (err) {
5285
- logger5.error(`Unexpected routing failure for ${msg.subject}:`, err);
5286
- return void 0;
5287
- }
5288
- };
5289
- const trackAsync = (result, msg) => {
5290
- void result.catch((err) => {
5291
- logger5.error(`Unexpected routing failure for ${msg.subject}:`, err);
5292
- }).finally(onAsyncDone);
5293
- };
5294
- const drainBacklog = () => {
5295
- while (active < maxActive) {
5296
- const next = backlog.shift();
5297
- if (next === void 0) return;
5298
- next.stopAckExtension?.();
5299
- active++;
5300
- const result = routeSafely(next.msg);
5301
- if (result !== void 0) {
5302
- trackAsync(result, next.msg);
5303
- } else {
5304
- active--;
5305
- }
5670
+ } catch (err) {
5671
+ logger5.error(`Ordered handler error (${subject}):`, err);
5672
+ reportHandlerCompleted(msg, startedAt, "error");
5673
+ return void 0;
5674
+ }
5675
+ if (!isPromiseLike2(pending)) {
5676
+ warnIfSettlementAttempted();
5677
+ reportHandlerCompleted(msg, startedAt, "success");
5678
+ return void 0;
5679
+ }
5680
+ return pending.then(
5681
+ () => {
5682
+ warnIfSettlementAttempted();
5683
+ reportHandlerCompleted(msg, startedAt, "success");
5684
+ },
5685
+ (err) => {
5686
+ logger5.error(`Ordered handler error (${subject}):`, err);
5687
+ reportHandlerCompleted(msg, startedAt, "error");
5306
5688
  }
5307
- if (backlog.length < backlogWarnThreshold) backlogWarned = false;
5689
+ );
5690
+ };
5691
+ };
5692
+
5693
+ // src/server/routing/event.router.ts
5694
+ var eventConsumeKindFor = (kind) => {
5695
+ if (kind === "broadcast" /* Broadcast */) return "broadcast" /* Broadcast */;
5696
+ if (kind === "ordered" /* Ordered */) return "ordered" /* Ordered */;
5697
+ return "event" /* Event */;
5698
+ };
5699
+ var EventRouter = class {
5700
+ constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig, processingConfig, ackWaitMap, connection, options, names) {
5701
+ this.messageProvider = messageProvider;
5702
+ this.patternRegistry = patternRegistry;
5703
+ this.codec = codec;
5704
+ this.eventBus = eventBus;
5705
+ this.deadLetterConfig = deadLetterConfig;
5706
+ this.processingConfig = processingConfig;
5707
+ this.ackWaitMap = ackWaitMap;
5708
+ if (options) {
5709
+ const derived = deriveOtelAttrs(options);
5710
+ this.otel = derived.otel;
5711
+ this.serviceName = derived.serviceName;
5712
+ this.serverEndpoint = derived.serverEndpoint;
5713
+ } else {
5714
+ this.otel = resolveOtelOptions({ enabled: false });
5715
+ this.serviceName = "";
5716
+ this.serverEndpoint = null;
5717
+ }
5718
+ this.capture = deadLetterConfig ? new DeadLetterCapture(
5719
+ patternRegistry,
5720
+ eventBus,
5721
+ deadLetterConfig,
5722
+ this.otel,
5723
+ this.serviceName,
5724
+ this.serverEndpoint,
5725
+ connection,
5726
+ options,
5727
+ names
5728
+ ) : null;
5729
+ }
5730
+ logger = new import_common20.Logger("Jetstream:EventRouter");
5731
+ subscriptions = [];
5732
+ otel;
5733
+ serviceName;
5734
+ serverEndpoint;
5735
+ capture;
5736
+ /**
5737
+ * Update the max_deliver thresholds from actual NATS consumer configs.
5738
+ * Called after consumers are ensured so the DLQ map reflects reality.
5739
+ */
5740
+ updateMaxDeliverMap(consumerMaxDelivers) {
5741
+ if (!this.deadLetterConfig) return;
5742
+ this.deadLetterConfig.maxDeliverByStream = consumerMaxDelivers;
5743
+ }
5744
+ /** Start routing event, broadcast, and ordered messages to handlers. */
5745
+ start() {
5746
+ this.subscribeToStream(this.messageProvider.events$, "ev" /* Event */);
5747
+ this.subscribeToStream(this.messageProvider.broadcasts$, "broadcast" /* Broadcast */);
5748
+ if (this.patternRegistry.hasOrderedHandlers()) {
5749
+ this.subscribeToStream(this.messageProvider.ordered$, "ordered" /* Ordered */);
5750
+ }
5751
+ }
5752
+ /** Stop routing and unsubscribe from all streams. */
5753
+ destroy() {
5754
+ for (const sub of this.subscriptions) {
5755
+ sub.unsubscribe();
5756
+ }
5757
+ this.subscriptions.length = 0;
5758
+ }
5759
+ /** Assemble the pipeline and concurrency gate for one stream and subscribe. */
5760
+ subscribeToStream(stream$, kind) {
5761
+ const isOrdered = kind === "ordered" /* Ordered */;
5762
+ const ackExtensionInterval = isOrdered ? null : resolveAckExtensionInterval(this.getAckExtensionConfig(kind), this.ackWaitMap?.get(kind));
5763
+ const rctx = {
5764
+ kind,
5765
+ spanKind: eventConsumeKindFor(kind),
5766
+ codec: this.codec,
5767
+ logger: this.logger,
5768
+ eventBus: this.eventBus,
5769
+ patternRegistry: this.patternRegistry,
5770
+ otel: this.otel,
5771
+ serviceName: this.serviceName,
5772
+ serverEndpoint: this.serverEndpoint,
5773
+ ackExtensionInterval,
5774
+ capture: this.capture
5308
5775
  };
5309
- const subscription = stream$.subscribe({
5310
- next: (msg) => {
5311
- if (active >= maxActive) {
5312
- backlog.push({
5313
- msg,
5314
- stopAckExtension: hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null
5315
- });
5316
- if (!backlogWarned && backlog.length >= backlogWarnThreshold) {
5317
- backlogWarned = true;
5318
- logger5.warn(
5319
- `${kind} backlog reached ${backlog.length} messages \u2014 consumer may be falling behind`
5320
- );
5321
- }
5322
- return;
5323
- }
5324
- active++;
5325
- const result = routeSafely(msg);
5326
- if (result !== void 0) {
5327
- trackAsync(result, msg);
5328
- } else {
5329
- active--;
5330
- if (backlog.length > 0) drainBacklog();
5331
- }
5776
+ const route = isOrdered ? createOrderedPipeline(rctx) : createWorkqueuePipeline(rctx);
5777
+ const maxActive = isOrdered ? 1 : this.getConcurrency(kind) ?? Number.POSITIVE_INFINITY;
5778
+ const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
5779
+ const parkTimer = hasAckExtension ? (msg) => startAckExtensionTimer(msg, ackExtensionInterval) : null;
5780
+ const gate = new ConcurrencyGate(maxActive, route, parkTimer, this.logger, kind);
5781
+ const subscription = stream$.subscribe({
5782
+ next: (msg) => {
5783
+ gate.push(msg);
5332
5784
  },
5333
5785
  error: (err) => {
5334
- logger5.error(`Stream error in ${kind} router`, err);
5786
+ this.logger.error(`Stream error in ${kind} router`, err);
5335
5787
  }
5336
5788
  });
5337
5789
  subscription.add(() => {
5338
- for (const queued of backlog) {
5339
- queued.stopAckExtension?.();
5340
- }
5341
- backlog.length = 0;
5790
+ gate.dispose();
5342
5791
  });
5343
5792
  this.subscriptions.push(subscription);
5344
5793
  }
@@ -5352,178 +5801,11 @@ var EventRouter = class {
5352
5801
  if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.ackExtension;
5353
5802
  return void 0;
5354
5803
  }
5355
- /**
5356
- * Last-resort path for a dead letter: invoke `onDeadLetter`, then `term` on
5357
- * success. On failure the message is nak'd to release it, but the server
5358
- * never redelivers past `max_deliver` — it stays in the stream for manual
5359
- * recovery. Used when the DLQ stream isn't configured, or when publishing
5360
- * to it failed and we still have to surface the message somewhere.
5361
- */
5362
- async fallbackToOnDeadLetterCallback(info, msg) {
5363
- const onDeadLetter = this.deadLetterConfig?.onDeadLetter;
5364
- if (!onDeadLetter) {
5365
- this.logger.error(
5366
- `Dead letter for ${msg.subject} could not be captured (DLQ publish failed, no onDeadLetter callback) \u2014 leaving the message in the stream`
5367
- );
5368
- settleQuietly(this.logger, `Failed to nak ${msg.subject}:`, () => {
5369
- msg.nak();
5370
- });
5371
- return;
5372
- }
5373
- try {
5374
- await onDeadLetter(info);
5375
- settleQuietly(this.logger, `Failed to term ${msg.subject}:`, () => {
5376
- msg.term("Dead letter processed via fallback callback");
5377
- });
5378
- } catch (hookErr) {
5379
- this.logger.error(
5380
- `Fallback onDeadLetter callback failed for ${msg.subject} \u2014 the message stays in the stream and will not be redelivered (max_deliver exhausted); recover it manually:`,
5381
- hookErr
5382
- );
5383
- settleQuietly(this.logger, `Failed to nak ${msg.subject}:`, () => {
5384
- msg.nak();
5385
- });
5386
- }
5387
- }
5388
- /**
5389
- * Copy the original message headers for the DLQ republish, dropping NATS
5390
- * server control headers: a copied Nats-TTL expires the DLQ entry (or gets
5391
- * the publish rejected when the DLQ stream has no allow_msg_ttl), a copied
5392
- * Nats-Msg-Id collides with the DLQ dedup window.
5393
- */
5394
- buildDlqHeaders(msg) {
5395
- const hdrs = (0, import_transport_node4.headers)();
5396
- if (!msg.headers) return hdrs;
5397
- for (const [k, v] of msg.headers) {
5398
- if (k.toLowerCase().startsWith(NATS_CONTROL_HEADER_PREFIX)) continue;
5399
- for (const val of v) {
5400
- hdrs.append(k, val);
5401
- }
5402
- }
5403
- return hdrs;
5404
- }
5405
- /**
5406
- * Attempt the DLQ publish up to {@link DLQ_PUBLISH_ATTEMPTS} times.
5407
- *
5408
- * Past `max_deliver` the server never redelivers, so an in-process retry is
5409
- * the only second chance a dead letter gets. There is no artificial delay
5410
- * between attempts: when the broker is unreachable each publish already
5411
- * spends its own request timeout, which spaces the attempts naturally.
5412
- */
5413
- async publishToDlqWithRetry(connection, subject, data, headers2) {
5414
- let lastErr;
5415
- for (let attempt = 1; attempt <= DLQ_PUBLISH_ATTEMPTS; attempt += 1) {
5416
- try {
5417
- await connection.getJetStreamClient().publish(subject, data, { headers: headers2 });
5418
- return;
5419
- } catch (err) {
5420
- lastErr = err;
5421
- if (attempt < DLQ_PUBLISH_ATTEMPTS) {
5422
- this.logger.warn(
5423
- `DLQ publish attempt ${attempt}/${DLQ_PUBLISH_ATTEMPTS} failed for ${subject}, retrying`
5424
- );
5425
- }
5426
- }
5427
- }
5428
- throw lastErr;
5429
- }
5430
- /**
5431
- * Publish a dead letter to the configured Dead-Letter Queue (DLQ) stream.
5432
- *
5433
- * Appends diagnostic metadata headers to the original message and preserves
5434
- * the primary payload. If publishing succeeds, it notifies the standard
5435
- * `onDeadLetter` callback and terminates the message. If it fails, it falls
5436
- * back to the callback entirely to prevent silent data loss.
5437
- */
5438
- async publishToDlq(msg, info, error) {
5439
- const serviceName = this.options?.name;
5440
- if (!this.connection || !serviceName) {
5441
- this.logger.error(
5442
- `Cannot publish to DLQ for ${msg.subject}: Connection or Module Options unavailable`
5443
- );
5444
- await this.fallbackToOnDeadLetterCallback(info, msg);
5445
- return;
5446
- }
5447
- const destinationSubject = dlqStreamName(serviceName);
5448
- const hdrs = this.buildDlqHeaders(msg);
5449
- let reason = String(error);
5450
- if (error instanceof Error) {
5451
- reason = error.message;
5452
- } else if (typeof error === "object" && error !== null && "message" in error) {
5453
- reason = String(error.message);
5454
- }
5455
- hdrs.set("x-dead-letter-reason" /* DeadLetterReason */, reason);
5456
- hdrs.set("x-original-subject" /* OriginalSubject */, msg.subject);
5457
- hdrs.set("x-original-stream" /* OriginalStream */, msg.info.stream);
5458
- hdrs.set("x-failed-at" /* FailedAt */, (/* @__PURE__ */ new Date()).toISOString());
5459
- hdrs.set("x-delivery-count" /* DeliveryCount */, msg.info.deliveryCount.toString());
5460
- try {
5461
- await this.publishToDlqWithRetry(this.connection, destinationSubject, msg.data, hdrs);
5462
- this.logger.log(`Message sent to DLQ: ${msg.subject}`);
5463
- if (this.deadLetterConfig?.onDeadLetter) {
5464
- try {
5465
- await this.deadLetterConfig.onDeadLetter(info);
5466
- } catch (hookErr) {
5467
- this.logger.warn(
5468
- `onDeadLetter callback failed after successful DLQ publish for ${msg.subject}`,
5469
- hookErr
5470
- );
5471
- }
5472
- }
5473
- settleQuietly(this.logger, `Failed to term ${msg.subject}:`, () => {
5474
- msg.term("Moved to DLQ stream");
5475
- });
5476
- } catch (publishErr) {
5477
- this.logger.error(`Failed to publish to DLQ for ${msg.subject}:`, publishErr);
5478
- await this.fallbackToOnDeadLetterCallback(info, msg);
5479
- }
5480
- }
5481
- /**
5482
- * Orchestrates the handling of a message that has exhausted delivery limits.
5483
- *
5484
- * Emits a system event and delegates either to the robust DLQ stream publisher
5485
- * or directly to the fallback callback based on the active module configuration.
5486
- */
5487
- async handleDeadLetter(msg, data, error) {
5488
- const info = {
5489
- subject: msg.subject,
5490
- data,
5491
- headers: msg.headers,
5492
- error,
5493
- deliveryCount: msg.info.deliveryCount,
5494
- stream: msg.info.stream,
5495
- streamSequence: msg.info.streamSequence,
5496
- timestamp: new Date(msg.info.timestampNanos / 1e6).toISOString()
5497
- };
5498
- await withDeadLetterSpan(
5499
- {
5500
- msg,
5501
- // Pattern resolution mirrors event-routing: when a registered
5502
- // pattern matches, surface it on the DLQ span so APM can filter
5503
- // dead letters by handler without parsing the subject. Falls back
5504
- // to the subject itself when no glob handler is in play.
5505
- pattern: this.patternRegistry.getHandler(msg.subject) ? msg.subject : void 0,
5506
- finalDeliveryCount: msg.info.deliveryCount,
5507
- reason: error instanceof Error ? error.message : String(error),
5508
- serviceName: this.serviceName,
5509
- endpoint: this.serverEndpoint
5510
- },
5511
- this.otel,
5512
- async () => {
5513
- this.eventBus.emit("deadLetter" /* DeadLetter */, info);
5514
- if (!this.options?.dlq) {
5515
- await this.fallbackToOnDeadLetterCallback(info, msg);
5516
- } else {
5517
- await this.publishToDlq(msg, info, error);
5518
- }
5519
- }
5520
- );
5521
- }
5522
5804
  };
5523
5805
 
5524
5806
  // src/server/routing/rpc.router.ts
5525
- var import_common19 = require("@nestjs/common");
5526
- var import_transport_node5 = require("@nats-io/transport-node");
5807
+ var import_common21 = require("@nestjs/common");
5808
+ var import_transport_node6 = require("@nats-io/transport-node");
5527
5809
  var RpcRouter = class {
5528
5810
  constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap, options) {
5529
5811
  this.messageProvider = messageProvider;
@@ -5546,7 +5828,7 @@ var RpcRouter = class {
5546
5828
  this.serverEndpoint = null;
5547
5829
  }
5548
5830
  }
5549
- logger = new import_common19.Logger("Jetstream:RpcRouter");
5831
+ logger = new import_common21.Logger("Jetstream:RpcRouter");
5550
5832
  timeout;
5551
5833
  concurrency;
5552
5834
  resolvedAckExtensionInterval;
@@ -5597,7 +5879,7 @@ var RpcRouter = class {
5597
5879
  };
5598
5880
  const publishReply = (replyTo, correlationId, payload) => {
5599
5881
  try {
5600
- const hdrs = (0, import_transport_node5.headers)();
5882
+ const hdrs = (0, import_transport_node6.headers)();
5601
5883
  hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
5602
5884
  nc.publish(replyTo, codec.encode(payload), { headers: hdrs });
5603
5885
  } catch (publishErr) {
@@ -5606,7 +5888,7 @@ var RpcRouter = class {
5606
5888
  };
5607
5889
  const publishErrorReply = (replyTo, correlationId, subject, err) => {
5608
5890
  try {
5609
- const hdrs = (0, import_transport_node5.headers)();
5891
+ const hdrs = (0, import_transport_node6.headers)();
5610
5892
  hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
5611
5893
  hdrs.set("x-error" /* Error */, "true");
5612
5894
  nc.publish(replyTo, codec.encode(serializeError(err)), { headers: hdrs });
@@ -5737,75 +6019,18 @@ var RpcRouter = class {
5737
6019
  }
5738
6020
  );
5739
6021
  };
5740
- const backlogWarnThreshold = 1e3;
5741
- let active = 0;
5742
- let backlogWarned = false;
5743
- const backlog = [];
5744
- const onAsyncDone = () => {
5745
- active--;
5746
- drainBacklog();
5747
- };
5748
- const routeSafely = (msg) => {
5749
- try {
5750
- return handleSafe(msg);
5751
- } catch (err) {
5752
- logger5.error(`Unexpected routing failure for ${msg.subject}:`, err);
5753
- return void 0;
5754
- }
5755
- };
5756
- const trackAsync = (result, msg) => {
5757
- void result.catch((err) => {
5758
- logger5.error(`Unexpected routing failure for ${msg.subject}:`, err);
5759
- }).finally(onAsyncDone);
5760
- };
5761
- const drainBacklog = () => {
5762
- while (active < maxActive) {
5763
- const next = backlog.shift();
5764
- if (next === void 0) return;
5765
- next.stopAckExtension?.();
5766
- active++;
5767
- const result = routeSafely(next.msg);
5768
- if (result !== void 0) {
5769
- trackAsync(result, next.msg);
5770
- } else {
5771
- active--;
5772
- }
5773
- }
5774
- if (backlog.length < backlogWarnThreshold) backlogWarned = false;
5775
- };
6022
+ const parkTimer = hasAckExtension ? (msg) => startAckExtensionTimer(msg, ackExtensionInterval) : null;
6023
+ const gate = new ConcurrencyGate(maxActive, handleSafe, parkTimer, logger5, "RPC");
5776
6024
  this.subscription = this.messageProvider.commands$.subscribe({
5777
6025
  next: (msg) => {
5778
- if (active >= maxActive) {
5779
- backlog.push({
5780
- msg,
5781
- stopAckExtension: hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null
5782
- });
5783
- if (!backlogWarned && backlog.length >= backlogWarnThreshold) {
5784
- backlogWarned = true;
5785
- logger5.warn(
5786
- `RPC backlog reached ${backlog.length} messages \u2014 consumer may be falling behind`
5787
- );
5788
- }
5789
- return;
5790
- }
5791
- active++;
5792
- const result = routeSafely(msg);
5793
- if (result !== void 0) {
5794
- trackAsync(result, msg);
5795
- } else {
5796
- active--;
5797
- if (backlog.length > 0) drainBacklog();
5798
- }
6026
+ gate.push(msg);
5799
6027
  },
5800
6028
  error: (err) => {
5801
6029
  logger5.error("Stream error in RPC router", err);
5802
6030
  }
5803
6031
  });
5804
6032
  this.subscription.add(() => {
5805
- for (const queued of backlog) {
5806
- queued.stopAckExtension?.();
5807
- }
5808
- backlog.length = 0;
6033
+ gate.dispose();
5809
6034
  });
5810
6035
  }
5811
6036
  /** Stop routing and unsubscribe. */
@@ -5815,20 +6040,220 @@ var RpcRouter = class {
5815
6040
  }
5816
6041
  };
5817
6042
 
6043
+ // src/server/infrastructure/infrastructure-binder.ts
6044
+ var import_common22 = require("@nestjs/common");
6045
+ var import_jetstream30 = require("@nats-io/jetstream");
6046
+ var WORKQUEUE_KINDS = /* @__PURE__ */ new Set(["ev" /* Event */, "cmd" /* Command */]);
6047
+ var manualRemediation = (entity) => `Management mode is Manual; the ${entity} must be provisioned externally before boot.`;
6048
+ var isSchedulingEnabled = (options, kind) => kindOptionsBlock(options, kind)?.stream?.allow_msg_schedules === true;
6049
+ var resolveAckExtension = (options, kind) => kindOptionsBlock(options, kind)?.ackExtension;
6050
+ var filterCoversSubject = (filter_subject, filter_subjects, subject) => {
6051
+ if (filter_subject !== void 0) {
6052
+ return coversOrEquals(filter_subject, subject);
6053
+ }
6054
+ if (filter_subjects !== void 0) {
6055
+ return filter_subjects.some((f) => coversOrEquals(f, subject));
6056
+ }
6057
+ return true;
6058
+ };
6059
+ var InfrastructureBinder = class {
6060
+ constructor(options, names, registry) {
6061
+ this.options = options;
6062
+ this.names = names;
6063
+ this.registry = registry;
6064
+ }
6065
+ logger = new import_common22.Logger("Jetstream:Binder");
6066
+ async bindStream(jsm, kind) {
6067
+ const name = this.names.streamName(kind);
6068
+ const info = await this.fetchStream(jsm, name, kind);
6069
+ await this.warnOnOrphanedMigrationBackup(jsm, name);
6070
+ if (isSchedulingEnabled(this.options, kind)) {
6071
+ this.assertScheduleCoverage(info, kind);
6072
+ this.warnOnSchedulesDisabled(info, kind);
6073
+ }
6074
+ if (WORKQUEUE_KINDS.has(kind)) {
6075
+ this.warnOnRetention(info, kind);
6076
+ }
6077
+ return info;
6078
+ }
6079
+ async bindDlqStream(jsm) {
6080
+ const dlqName = this.names.dlqStreamName();
6081
+ const info = await this.fetchStream(jsm, dlqName, "dlq");
6082
+ await this.warnOnOrphanedMigrationBackup(jsm, dlqName);
6083
+ this.assertDlqSubjectCoverage(info);
6084
+ return info;
6085
+ }
6086
+ async bindConsumer(jsm, kind) {
6087
+ const info = await this.fetchConsumer(jsm, kind);
6088
+ this.assertHandlersCovered(info, kind);
6089
+ this.assertScheduleHoldersNotConsumed(info, kind);
6090
+ this.warnOnUnlimitedDelivery(info, kind);
6091
+ this.warnOnShortAckWait(info, kind);
6092
+ return info;
6093
+ }
6094
+ async fetchStream(jsm, name, kind) {
6095
+ try {
6096
+ return await jsm.streams.info(name);
6097
+ } catch (err) {
6098
+ if (err instanceof import_jetstream30.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
6099
+ const api = err.apiError();
6100
+ throw new JetstreamProvisioningError({
6101
+ entity: "stream",
6102
+ target: name,
6103
+ kind: String(kind),
6104
+ errCode: api.err_code,
6105
+ errDescription: api.description,
6106
+ remediation: manualRemediation("stream"),
6107
+ cause: err
6108
+ });
6109
+ }
6110
+ throw err;
6111
+ }
6112
+ }
6113
+ async fetchConsumer(jsm, kind) {
6114
+ const stream = this.names.streamName(kind);
6115
+ const consumer = this.names.consumerName(kind);
6116
+ try {
6117
+ return await jsm.consumers.info(stream, consumer);
6118
+ } catch (err) {
6119
+ if (err instanceof import_jetstream30.JetStreamApiError && err.apiError().err_code === 10014 /* ConsumerNotFound */) {
6120
+ const api = err.apiError();
6121
+ throw new JetstreamProvisioningError({
6122
+ entity: "consumer",
6123
+ target: `${consumer} on stream "${stream}"`,
6124
+ kind: String(kind),
6125
+ errCode: api.err_code,
6126
+ errDescription: api.description,
6127
+ remediation: manualRemediation("consumer"),
6128
+ cause: err
6129
+ });
6130
+ }
6131
+ throw err;
6132
+ }
6133
+ }
6134
+ assertHandlersCovered(info, kind) {
6135
+ const subjects = this.resolveHandlerSubjects(kind);
6136
+ if (subjects.length === 0) return;
6137
+ const { filter_subject, filter_subjects } = info.config;
6138
+ const uncovered = subjects.filter(
6139
+ (s) => !filterCoversSubject(filter_subject, filter_subjects, s)
6140
+ );
6141
+ if (uncovered.length > 0) {
6142
+ throw new Error(
6143
+ `Consumer "${this.names.consumerName(kind)}" (kind=${String(kind)}) does not cover the following registered handler subjects: ${uncovered.join(", ")}. Update the consumer's filter_subject / filter_subjects to include them.`
6144
+ );
6145
+ }
6146
+ }
6147
+ assertDlqSubjectCoverage(info) {
6148
+ const dlqSubject = this.names.dlqStreamName();
6149
+ const covered = info.config.subjects.some((s) => coversOrEquals(s, dlqSubject));
6150
+ if (!covered) {
6151
+ throw new Error(
6152
+ `DLQ stream "${dlqSubject}" subjects do not cover "${dlqSubject}" (dead letters publish to a subject equal to the stream name). Add it to the stream's subjects list.`
6153
+ );
6154
+ }
6155
+ }
6156
+ assertScheduleCoverage(info, kind) {
6157
+ const scheduleWildcard = `${this.names.schedulePrefix(kind)}>`;
6158
+ const covered = info.config.subjects.some((s) => coversOrEquals(s, scheduleWildcard));
6159
+ if (!covered) {
6160
+ throw new Error(
6161
+ `Stream "${this.names.streamName(kind)}" (kind=${String(kind)}) has scheduling enabled (allow_msg_schedules=true) but its subjects do not cover the schedule prefix "${this.names.schedulePrefix(kind)}". Add "${scheduleWildcard}" to the stream's subjects.`
6162
+ );
6163
+ }
6164
+ }
6165
+ assertScheduleHoldersNotConsumed(info, kind) {
6166
+ if (!isSchedulingEnabled(this.options, kind)) return;
6167
+ const scheduleWildcard = `${this.names.schedulePrefix(kind)}>`;
6168
+ const { filter_subject, filter_subjects } = info.config;
6169
+ const filters = filter_subjects ?? (filter_subject !== void 0 ? [filter_subject] : []);
6170
+ const swallowing = filters.length === 0 ? ["<no filter, consumes the whole stream>"] : filters.filter((f) => coversOrEquals(f, scheduleWildcard));
6171
+ if (swallowing.length > 0) {
6172
+ throw new Error(
6173
+ `Consumer "${this.names.consumerName(kind)}" (kind=${String(kind)}) filter ${swallowing.join(", ")} also matches the schedule namespace "${this.names.schedulePrefix(kind)}". Consuming schedule holders removes pending schedules from the stream. Use exact filter_subjects for the registered handler subjects instead.`
6174
+ );
6175
+ }
6176
+ }
6177
+ async warnOnOrphanedMigrationBackup(jsm, streamName2) {
6178
+ const backupName = `${streamName2}${MIGRATION_BACKUP_SUFFIX}`;
6179
+ try {
6180
+ await jsm.streams.info(backupName);
6181
+ } catch {
6182
+ return;
6183
+ }
6184
+ this.logger.warn(
6185
+ `Found migration backup "${backupName}" for the externally managed stream "${streamName2}". A previous Auto-managed migration was interrupted and undelivered messages may still reside in the backup. Recover them by sourcing the backup back, or re-enable Auto management for one boot to let the library finish the recovery.`
6186
+ );
6187
+ }
6188
+ warnOnSchedulesDisabled(info, kind) {
6189
+ if (info.config.allow_msg_schedules === true) return;
6190
+ this.logger.warn(
6191
+ `Stream "${this.names.streamName(kind)}" (kind=${String(kind)}) does not report allow_msg_schedules=true, but scheduling is enabled in the application options. Scheduled publishes will be rejected by the server until the stream allows message schedules.`
6192
+ );
6193
+ }
6194
+ warnOnRetention(info, kind) {
6195
+ if (info.config.retention !== import_jetstream30.RetentionPolicy.Workqueue) {
6196
+ this.logger.warn(
6197
+ `Stream "${this.names.streamName(kind)}" (kind=${String(kind)}) retention is "${String(info.config.retention)}"; expected "workqueue" for reliable at-least-once delivery.`
6198
+ );
6199
+ }
6200
+ }
6201
+ warnOnUnlimitedDelivery(info, kind) {
6202
+ if (!this.options.dlq) return;
6203
+ const maxDeliver = info.config.max_deliver;
6204
+ if (maxDeliver === void 0 || maxDeliver <= 0) {
6205
+ this.logger.warn(
6206
+ `Consumer "${this.names.consumerName(kind)}" (kind=${String(kind)}) has unlimited max_deliver but options.dlq is enabled; messages will never be dead-lettered. Set max_deliver > 0 on the consumer.`
6207
+ );
6208
+ }
6209
+ }
6210
+ warnOnShortAckWait(info, kind) {
6211
+ const ackExtConfig = resolveAckExtension(this.options, kind);
6212
+ if (ackExtConfig === void 0 || ackExtConfig === false) return;
6213
+ const ackWaitNanos = info.config.ack_wait;
6214
+ const intervalMs = resolveAckExtensionInterval(ackExtConfig, ackWaitNanos);
6215
+ if (intervalMs === null) return;
6216
+ const ackWaitMs = ackWaitNanos !== void 0 ? ackWaitNanos / 1e6 : void 0;
6217
+ if (ackWaitMs !== void 0 && ackWaitMs < intervalMs) {
6218
+ this.logger.warn(
6219
+ `Consumer "${this.names.consumerName(kind)}" (kind=${String(kind)}) ack_wait (${ackWaitMs}ms) is shorter than the ackExtension interval (${intervalMs}ms). Messages may redeliver before the handler finishes. Increase ack_wait.`
6220
+ );
6221
+ }
6222
+ }
6223
+ resolveHandlerSubjects(kind) {
6224
+ const patterns = this.registry.getPatternsByKind();
6225
+ switch (kind) {
6226
+ case "ev" /* Event */:
6227
+ return patterns.events.map((p) => this.names.subject("ev" /* Event */, p));
6228
+ case "cmd" /* Command */:
6229
+ return patterns.commands.map((p) => this.names.subject("cmd" /* Command */, p));
6230
+ case "broadcast" /* Broadcast */:
6231
+ return this.registry.getBroadcastPatterns();
6232
+ case "ordered" /* Ordered */:
6233
+ return this.registry.getOrderedSubjects();
6234
+ /* v8 ignore next 5 -- exhaustive switch guard, unreachable */
6235
+ default: {
6236
+ const _exhaustive = kind;
6237
+ throw new Error(`Unhandled StreamKind: ${String(_exhaustive)}`);
6238
+ }
6239
+ }
6240
+ }
6241
+ };
6242
+
5818
6243
  // src/shutdown/shutdown.manager.ts
5819
- var import_common20 = require("@nestjs/common");
6244
+ var import_common23 = require("@nestjs/common");
5820
6245
  var ShutdownManager = class {
5821
6246
  constructor(connection, eventBus, timeout) {
5822
6247
  this.connection = connection;
5823
6248
  this.eventBus = eventBus;
5824
6249
  this.timeout = timeout;
5825
6250
  }
5826
- logger = new import_common20.Logger("Jetstream:Shutdown");
6251
+ logger = new import_common23.Logger("Jetstream:Shutdown");
5827
6252
  shutdownPromise;
5828
6253
  /**
5829
6254
  * Execute the full shutdown sequence.
5830
6255
  *
5831
- * Idempotent concurrent or repeated calls return the same promise.
6256
+ * Idempotent: concurrent or repeated calls return the same promise.
5832
6257
  *
5833
6258
  * @param strategy Optional stoppable to close (stops consumers and subscriptions).
5834
6259
  */
@@ -5858,6 +6283,12 @@ var ShutdownManager = class {
5858
6283
 
5859
6284
  // src/jetstream.module.ts
5860
6285
  var JETSTREAM_ACK_WAIT_MAP = /* @__PURE__ */ Symbol("JETSTREAM_ACK_WAIT_MAP");
6286
+ var DESTRUCTIVE_MIGRATION_MANUAL_WARNING = "allowDestructiveMigration has no effect under provisioning.management: Manual; the library never migrates externally managed streams.";
6287
+ var warnIfManualWithDestructive = (options, logger5) => {
6288
+ if (options.allowDestructiveMigration && options.provisioning?.management === "manual" /* Manual */) {
6289
+ logger5.warn(DESTRUCTIVE_MIGRATION_MANUAL_WARNING);
6290
+ }
6291
+ };
5861
6292
  var JetstreamModule = class {
5862
6293
  constructor(shutdownManager, strategy) {
5863
6294
  this.shutdownManager = shutdownManager;
@@ -5887,7 +6318,8 @@ var JetstreamModule = class {
5887
6318
  PatternRegistry,
5888
6319
  ShutdownManager,
5889
6320
  JetstreamStrategy,
5890
- JetstreamHealthIndicator
6321
+ JetstreamHealthIndicator,
6322
+ NameResolver
5891
6323
  ]
5892
6324
  };
5893
6325
  }
@@ -5916,7 +6348,8 @@ var JetstreamModule = class {
5916
6348
  PatternRegistry,
5917
6349
  ShutdownManager,
5918
6350
  JetstreamStrategy,
5919
- JetstreamHealthIndicator
6351
+ JetstreamHealthIndicator,
6352
+ NameResolver
5920
6353
  ]
5921
6354
  };
5922
6355
  }
@@ -5933,10 +6366,23 @@ var JetstreamModule = class {
5933
6366
  const clientToken = getClientToken(options.name);
5934
6367
  const clientProvider = {
5935
6368
  provide: clientToken,
5936
- inject: [JETSTREAM_OPTIONS, JETSTREAM_CONNECTION, JETSTREAM_CODEC, JETSTREAM_EVENT_BUS],
5937
- useFactory: (rootOptions, connection, rootCodec, eventBus) => {
6369
+ inject: [
6370
+ JETSTREAM_OPTIONS,
6371
+ JETSTREAM_CONNECTION,
6372
+ JETSTREAM_CODEC,
6373
+ JETSTREAM_EVENT_BUS,
6374
+ { token: NameResolver, optional: true }
6375
+ ],
6376
+ useFactory: (rootOptions, connection, rootCodec, eventBus, names) => {
5938
6377
  const codec = options.codec ?? rootCodec;
5939
- return new JetstreamClient(rootOptions, options.name, connection, codec, eventBus);
6378
+ return new JetstreamClient(
6379
+ rootOptions,
6380
+ options.name,
6381
+ connection,
6382
+ codec,
6383
+ eventBus,
6384
+ names ?? void 0
6385
+ );
5940
6386
  }
5941
6387
  };
5942
6388
  return {
@@ -5957,16 +6403,14 @@ var JetstreamModule = class {
5957
6403
  /** Create providers that depend on JETSTREAM_OPTIONS (shared by sync and async). */
5958
6404
  static createCoreDependentProviders() {
5959
6405
  return [
5960
- // EventBus — hook system with Logger fallback
5961
6406
  {
5962
6407
  provide: JETSTREAM_EVENT_BUS,
5963
6408
  inject: [JETSTREAM_OPTIONS],
5964
6409
  useFactory: (options) => {
5965
- const logger5 = new import_common21.Logger("Jetstream:Module");
6410
+ const logger5 = new import_common24.Logger("Jetstream:Module");
5966
6411
  return new EventBus(logger5, options.hooks);
5967
6412
  }
5968
6413
  },
5969
- // Codec — global encode/decode
5970
6414
  {
5971
6415
  provide: JETSTREAM_CODEC,
5972
6416
  inject: [JETSTREAM_OPTIONS],
@@ -5974,7 +6418,6 @@ var JetstreamModule = class {
5974
6418
  return options.codec ?? new JsonCodec();
5975
6419
  }
5976
6420
  },
5977
- // ConnectionProvider — single NATS connection
5978
6421
  {
5979
6422
  provide: JETSTREAM_CONNECTION,
5980
6423
  inject: [JETSTREAM_OPTIONS, JETSTREAM_EVENT_BUS],
@@ -5982,7 +6425,6 @@ var JetstreamModule = class {
5982
6425
  return new ConnectionProvider(options, eventBus);
5983
6426
  }
5984
6427
  },
5985
- // JetstreamHealthIndicator — health check for NATS connection
5986
6428
  {
5987
6429
  provide: JetstreamHealthIndicator,
5988
6430
  inject: [JETSTREAM_CONNECTION],
@@ -5990,7 +6432,6 @@ var JetstreamModule = class {
5990
6432
  return new JetstreamHealthIndicator(connection);
5991
6433
  }
5992
6434
  },
5993
- // ShutdownManager — graceful shutdown orchestration
5994
6435
  {
5995
6436
  provide: ShutdownManager,
5996
6437
  inject: [JETSTREAM_CONNECTION, JETSTREAM_EVENT_BUS, JETSTREAM_OPTIONS],
@@ -6002,41 +6443,69 @@ var JetstreamModule = class {
6002
6443
  );
6003
6444
  }
6004
6445
  },
6005
- // Consumer infrastructure only created when consumer !== false.
6006
- // Providers return null when consumer is disabled (publisher-only mode).
6007
- // PatternRegistry — subject-to-handler mapping
6446
+ // Consumer infrastructure providers below return null when consumer === false
6447
+ // (publisher-only mode). NameResolver is the exception: clients need it too.
6008
6448
  {
6009
- provide: PatternRegistry,
6449
+ provide: NameResolver,
6010
6450
  inject: [JETSTREAM_OPTIONS],
6011
6451
  useFactory: (options) => {
6452
+ const logger5 = new import_common24.Logger("Jetstream:Module");
6453
+ warnIfManualWithDestructive(options, logger5);
6454
+ return new NameResolver(options);
6455
+ }
6456
+ },
6457
+ {
6458
+ provide: PatternRegistry,
6459
+ inject: [JETSTREAM_OPTIONS, NameResolver],
6460
+ useFactory: (options, names) => {
6461
+ if (options.consumer === false) return null;
6462
+ return new PatternRegistry(options, names);
6463
+ }
6464
+ },
6465
+ {
6466
+ provide: InfrastructureBinder,
6467
+ inject: [JETSTREAM_OPTIONS, NameResolver, PatternRegistry],
6468
+ useFactory: (options, names, registry) => {
6012
6469
  if (options.consumer === false) return null;
6013
- return new PatternRegistry(options);
6470
+ return new InfrastructureBinder(options, names, registry);
6014
6471
  }
6015
6472
  },
6016
- // StreamProvider — JetStream stream lifecycle
6017
6473
  {
6018
6474
  provide: StreamProvider,
6019
- inject: [JETSTREAM_OPTIONS, JETSTREAM_CONNECTION],
6020
- useFactory: (options, connection) => {
6475
+ inject: [JETSTREAM_OPTIONS, JETSTREAM_CONNECTION, NameResolver, InfrastructureBinder],
6476
+ useFactory: (options, connection, names, binder) => {
6021
6477
  if (options.consumer === false) return null;
6022
- return new StreamProvider(options, connection);
6478
+ return new StreamProvider(options, connection, names, binder);
6023
6479
  }
6024
6480
  },
6025
- // ConsumerProvider JetStream consumer lifecycle (receives PatternRegistry for broadcast filtering)
6481
+ // ConsumerProvider needs PatternRegistry for broadcast filtering.
6026
6482
  {
6027
6483
  provide: ConsumerProvider,
6028
- inject: [JETSTREAM_OPTIONS, JETSTREAM_CONNECTION, StreamProvider, PatternRegistry],
6029
- useFactory: (options, connection, streamProvider, patternRegistry) => {
6484
+ inject: [
6485
+ JETSTREAM_OPTIONS,
6486
+ JETSTREAM_CONNECTION,
6487
+ StreamProvider,
6488
+ PatternRegistry,
6489
+ NameResolver,
6490
+ InfrastructureBinder
6491
+ ],
6492
+ useFactory: (options, connection, streamProvider, patternRegistry, names, binder) => {
6030
6493
  if (options.consumer === false) return null;
6031
- return new ConsumerProvider(options, connection, streamProvider, patternRegistry);
6494
+ return new ConsumerProvider(
6495
+ options,
6496
+ connection,
6497
+ streamProvider,
6498
+ patternRegistry,
6499
+ names,
6500
+ binder
6501
+ );
6032
6502
  }
6033
6503
  },
6034
- // Shared ack_wait map populated by strategy after ensureConsumers()
6504
+ // Shared ack_wait map, populated by the strategy after ensureConsumers().
6035
6505
  {
6036
6506
  provide: JETSTREAM_ACK_WAIT_MAP,
6037
6507
  useFactory: () => /* @__PURE__ */ new Map()
6038
6508
  },
6039
- // MessageProvider — pull-based message consumption
6040
6509
  {
6041
6510
  provide: MessageProvider,
6042
6511
  inject: [
@@ -6075,7 +6544,6 @@ var JetstreamModule = class {
6075
6544
  return new MessageProvider(connection, eventBus, consumeOptionsMap, consumerRecoveryFn);
6076
6545
  }
6077
6546
  },
6078
- // EventRouter — routes event and broadcast messages to handlers
6079
6547
  {
6080
6548
  provide: EventRouter,
6081
6549
  inject: [
@@ -6085,9 +6553,10 @@ var JetstreamModule = class {
6085
6553
  JETSTREAM_CODEC,
6086
6554
  JETSTREAM_EVENT_BUS,
6087
6555
  JETSTREAM_ACK_WAIT_MAP,
6088
- JETSTREAM_CONNECTION
6556
+ JETSTREAM_CONNECTION,
6557
+ NameResolver
6089
6558
  ],
6090
- useFactory: (options, messageProvider, patternRegistry, codec, eventBus, ackWaitMap, connection) => {
6559
+ useFactory: (options, messageProvider, patternRegistry, codec, eventBus, ackWaitMap, connection, names) => {
6091
6560
  if (options.consumer === false) return null;
6092
6561
  const deadLetterConfig = options.onDeadLetter || options.dlq ? {
6093
6562
  maxDeliverByStream: /* @__PURE__ */ new Map(),
@@ -6112,11 +6581,11 @@ var JetstreamModule = class {
6112
6581
  processingConfig,
6113
6582
  ackWaitMap,
6114
6583
  connection,
6115
- options
6584
+ options,
6585
+ names
6116
6586
  );
6117
6587
  }
6118
6588
  },
6119
- // RpcRouter — routes RPC command messages in JetStream mode
6120
6589
  {
6121
6590
  provide: RpcRouter,
6122
6591
  inject: [
@@ -6147,7 +6616,6 @@ var JetstreamModule = class {
6147
6616
  );
6148
6617
  }
6149
6618
  },
6150
- // CoreRpcServer — RPC via NATS Core request/reply
6151
6619
  {
6152
6620
  provide: CoreRpcServer,
6153
6621
  inject: [
@@ -6155,14 +6623,14 @@ var JetstreamModule = class {
6155
6623
  JETSTREAM_CONNECTION,
6156
6624
  PatternRegistry,
6157
6625
  JETSTREAM_CODEC,
6158
- JETSTREAM_EVENT_BUS
6626
+ JETSTREAM_EVENT_BUS,
6627
+ NameResolver
6159
6628
  ],
6160
- useFactory: (options, connection, patternRegistry, codec, eventBus) => {
6629
+ useFactory: (options, connection, patternRegistry, codec, eventBus, names) => {
6161
6630
  if (options.consumer === false) return null;
6162
- return new CoreRpcServer(options, connection, patternRegistry, codec, eventBus);
6631
+ return new CoreRpcServer(options, connection, patternRegistry, codec, eventBus, names);
6163
6632
  }
6164
6633
  },
6165
- // MetadataProvider — handler metadata KV registry (decoupled from stream/consumer infra)
6166
6634
  {
6167
6635
  provide: MetadataProvider,
6168
6636
  inject: [JETSTREAM_OPTIONS, JETSTREAM_CONNECTION],
@@ -6171,7 +6639,6 @@ var JetstreamModule = class {
6171
6639
  return new MetadataProvider(options, connection);
6172
6640
  }
6173
6641
  },
6174
- // JetstreamStrategy — server-side transport (only when consumer enabled)
6175
6642
  {
6176
6643
  provide: JetstreamStrategy,
6177
6644
  inject: [
@@ -6256,12 +6723,12 @@ var JetstreamModule = class {
6256
6723
  }
6257
6724
  };
6258
6725
  JetstreamModule = __decorateClass([
6259
- (0, import_common21.Global)(),
6260
- (0, import_common21.Module)({}),
6261
- __decorateParam(0, (0, import_common21.Optional)()),
6262
- __decorateParam(0, (0, import_common21.Inject)(ShutdownManager)),
6263
- __decorateParam(1, (0, import_common21.Optional)()),
6264
- __decorateParam(1, (0, import_common21.Inject)(JetstreamStrategy))
6726
+ (0, import_common24.Global)(),
6727
+ (0, import_common24.Module)({}),
6728
+ __decorateParam(0, (0, import_common24.Optional)()),
6729
+ __decorateParam(0, (0, import_common24.Inject)(ShutdownManager)),
6730
+ __decorateParam(1, (0, import_common24.Optional)()),
6731
+ __decorateParam(1, (0, import_common24.Inject)(JetstreamStrategy))
6265
6732
  ], JetstreamModule);
6266
6733
  // Annotate the CommonJS export names for ESM import in node:
6267
6734
  0 && (module.exports = {
@@ -6297,6 +6764,7 @@ JetstreamModule = __decorateClass([
6297
6764
  JetstreamTrace,
6298
6765
  JsonCodec,
6299
6766
  MIN_METADATA_TTL,
6767
+ ManagementMode,
6300
6768
  MessageKind,
6301
6769
  MsgpackCodec,
6302
6770
  NatsErrorCode,