@horizon-republic/nestjs-jetstream 2.6.1 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,36 @@
1
+ <div align="center">
2
+
1
3
  # @horizon-republic/nestjs-jetstream
2
4
 
3
- Ship reliable microservices with NATS JetStream and NestJS. Events, broadcast, ordered delivery, and RPC — with two lines of config.
5
+ **Ship reliable microservices with NATS JetStream and NestJS.**
6
+ Events, broadcast, ordered delivery, and RPC — with two lines of config.
4
7
 
5
8
  [![npm version](https://img.shields.io/npm/v/@horizon-republic/nestjs-jetstream.svg)](https://www.npmjs.com/package/@horizon-republic/nestjs-jetstream)
6
9
  [![codecov](https://codecov.io/github/HorizonRepublic/nestjs-jetstream/graph/badge.svg?token=40IPSWFMT4)](https://codecov.io/github/HorizonRepublic/nestjs-jetstream)
10
+ [![CI](https://github.com/HorizonRepublic/nestjs-jetstream/actions/workflows/coverage.yml/badge.svg)](https://github.com/HorizonRepublic/nestjs-jetstream/actions)
11
+ [![Documentation](https://img.shields.io/badge/docs-online-brightgreen.svg)](https://horizonrepublic.github.io/nestjs-jetstream/)
12
+
13
+ [![Node.js](https://img.shields.io/node/v/@horizon-republic/nestjs-jetstream.svg)](https://nodejs.org)
14
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue.svg)](https://www.typescriptlang.org)
7
15
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
- [![Socket Badge](https://badge.socket.dev/npm/package/@horizon-republic/nestjs-jetstream)](https://socket.dev/npm/package/@horizon-republic/nestjs-jetstream)
16
+
17
+ </div>
18
+
19
+ ---
20
+
21
+ ## Why this library?
22
+
23
+ NestJS ships with a NATS transport, but it's fire-and-forget. Messages vanish if no one's listening. This library adds JetStream — so your messages **survive restarts**, **retry on failure**, and **replay for new consumers**.
24
+
25
+ You keep writing `@EventPattern()` and `@MessagePattern()`. The library handles streams, consumers, and subjects automatically.
26
+
27
+ ## What's inside
28
+
29
+ **Delivery modes** — workqueue (one consumer), broadcast (all consumers), ordered (sequential), and dual-mode RPC (Core or JetStream-backed).
30
+
31
+ **Production-ready** — dead letter queue, health checks, graceful shutdown with drain, lifecycle hooks for observability.
32
+
33
+ **Flexible** — pluggable codecs (JSON/MsgPack/Protobuf), per-stream configuration, publisher-only mode for API gateways.
9
34
 
10
35
  ## Quick Start
11
36
 
@@ -44,9 +69,13 @@ export class OrdersController {
44
69
 
45
70
  **[Read the full documentation →](https://horizonrepublic.github.io/nestjs-jetstream/)**
46
71
 
47
- - [Getting Started](https://horizonrepublic.github.io/nestjs-jetstream/docs/getting-started) installation, module setup, first handler
48
- - [Guides](https://horizonrepublic.github.io/nestjs-jetstream/docs/guides/health-checks) — health checks, graceful shutdown, lifecycle hooks
49
- - [API Reference](https://horizonrepublic.github.io/nestjs-jetstream/docs/reference/api/) full TypeDoc-generated API
72
+ | Section | What you'll learn |
73
+ |---------|-------------------|
74
+ | [Getting Started](https://horizonrepublic.github.io/nestjs-jetstream/docs/getting-started/installation) | Installation, module setup, first handler |
75
+ | [Messaging Patterns](https://horizonrepublic.github.io/nestjs-jetstream/docs/patterns/rpc) | RPC, Events, Broadcast, Ordered Events |
76
+ | [Guides](https://horizonrepublic.github.io/nestjs-jetstream/docs/guides/record-builder) | Handler context, DLQ, health checks, performance tuning |
77
+ | [Migration](https://horizonrepublic.github.io/nestjs-jetstream/docs/guides/migration) | From built-in NATS transport or between versions |
78
+ | [API Reference](https://horizonrepublic.github.io/nestjs-jetstream/docs/reference/api/) | Full TypeDoc-generated API |
50
79
 
51
80
  ## Links
52
81
 
package/dist/index.cjs CHANGED
@@ -47,9 +47,13 @@ __export(index_exports, {
47
47
  RpcContext: () => RpcContext,
48
48
  StreamKind: () => StreamKind,
49
49
  TransportEvent: () => TransportEvent,
50
+ buildSubject: () => buildSubject,
51
+ consumerName: () => consumerName,
50
52
  getClientToken: () => getClientToken,
53
+ internalName: () => internalName,
51
54
  isCoreRpcMode: () => isCoreRpcMode,
52
55
  isJetStreamRpcMode: () => isJetStreamRpcMode,
56
+ streamName: () => streamName,
53
57
  toNanos: () => toNanos
54
58
  });
55
59
  module.exports = __toCommonJS(index_exports);
@@ -830,6 +834,23 @@ var EventBus = class {
830
834
  emit(event, ...args) {
831
835
  const hook = this.hooks[event];
832
836
  if (!hook) return;
837
+ this.callHook(event, hook, ...args);
838
+ }
839
+ /**
840
+ * Hot-path optimized emit for MessageRouted events.
841
+ * Avoids rest/spread overhead of the generic `emit()`.
842
+ */
843
+ emitMessageRouted(subject, kind) {
844
+ const hook = this.hooks["messageRouted" /* MessageRouted */];
845
+ if (!hook) return;
846
+ this.callHook(
847
+ "messageRouted" /* MessageRouted */,
848
+ hook,
849
+ subject,
850
+ kind
851
+ );
852
+ }
853
+ callHook(event, hook, ...args) {
833
854
  try {
834
855
  const result = hook(...args);
835
856
  if (result && typeof result.catch === "function") {
@@ -959,7 +980,7 @@ var JetstreamStrategy = class extends import_microservices2.Server {
959
980
  this.eventRouter.start();
960
981
  }
961
982
  if (isJetStreamRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
962
- this.rpcRouter.start();
983
+ await this.rpcRouter.start();
963
984
  }
964
985
  }
965
986
  if (isCoreRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
@@ -1053,6 +1074,13 @@ var import_nats5 = require("nats");
1053
1074
  // src/context/rpc.context.ts
1054
1075
  var import_microservices3 = require("@nestjs/microservices");
1055
1076
  var RpcContext = class extends import_microservices3.BaseRpcContext {
1077
+ _shouldRetry = false;
1078
+ _retryDelay;
1079
+ _shouldTerminate = false;
1080
+ _terminateReason;
1081
+ // ---------------------------------------------------------------------------
1082
+ // Message accessors
1083
+ // ---------------------------------------------------------------------------
1056
1084
  /**
1057
1085
  * Get the underlying NATS message.
1058
1086
  *
@@ -1087,6 +1115,96 @@ var RpcContext = class extends import_microservices3.BaseRpcContext {
1087
1115
  isJetStream() {
1088
1116
  return "ack" in this.args[0];
1089
1117
  }
1118
+ // ---------------------------------------------------------------------------
1119
+ // JetStream metadata (return undefined for Core NATS messages)
1120
+ // ---------------------------------------------------------------------------
1121
+ /** How many times this message has been delivered. */
1122
+ getDeliveryCount() {
1123
+ return this.asJetStream()?.info.deliveryCount;
1124
+ }
1125
+ /** The JetStream stream this message belongs to. */
1126
+ getStream() {
1127
+ return this.asJetStream()?.info.stream;
1128
+ }
1129
+ /** The stream sequence number. */
1130
+ getSequence() {
1131
+ return this.asJetStream()?.seq;
1132
+ }
1133
+ /** The message timestamp as a `Date` (derived from `info.timestampNanos`). */
1134
+ getTimestamp() {
1135
+ const nanos = this.asJetStream()?.info.timestampNanos;
1136
+ return typeof nanos === "number" ? new Date(nanos / 1e6) : void 0;
1137
+ }
1138
+ /** The name of the service that published this message (from `x-caller-name` header). */
1139
+ getCallerName() {
1140
+ return this.getHeader("x-caller-name" /* CallerName */);
1141
+ }
1142
+ // ---------------------------------------------------------------------------
1143
+ // Handler-controlled settlement
1144
+ // ---------------------------------------------------------------------------
1145
+ /**
1146
+ * Signal the transport to retry (nak) this message instead of acknowledging it.
1147
+ *
1148
+ * Use for business-level retries without throwing errors.
1149
+ * Only affects JetStream event handlers (workqueue/broadcast).
1150
+ *
1151
+ * @param opts - Optional delay in ms before redelivery.
1152
+ * @throws Error if {@link terminate} was already called.
1153
+ */
1154
+ retry(opts) {
1155
+ this.assertJetStream("retry");
1156
+ if (this._shouldTerminate) {
1157
+ throw new Error("Cannot retry \u2014 terminate() was already called");
1158
+ }
1159
+ this._shouldRetry = true;
1160
+ this._retryDelay = opts?.delayMs;
1161
+ }
1162
+ /**
1163
+ * Signal the transport to permanently reject (term) this message.
1164
+ *
1165
+ * Use when a message is no longer relevant and should not be retried or sent to DLQ.
1166
+ * Only affects JetStream event handlers (workqueue/broadcast).
1167
+ *
1168
+ * @param reason - Optional reason for termination (logged by NATS).
1169
+ * @throws Error if {@link retry} was already called.
1170
+ */
1171
+ terminate(reason) {
1172
+ this.assertJetStream("terminate");
1173
+ if (this._shouldRetry) {
1174
+ throw new Error("Cannot terminate \u2014 retry() was already called");
1175
+ }
1176
+ this._shouldTerminate = true;
1177
+ this._terminateReason = reason;
1178
+ }
1179
+ /** Narrow to JsMsg or return null for Core messages. Used by metadata getters. */
1180
+ asJetStream() {
1181
+ return this.isJetStream() ? this.args[0] : null;
1182
+ }
1183
+ /** Ensure the message is JetStream — settlement actions are not available for Core NATS. */
1184
+ assertJetStream(method) {
1185
+ if (!this.isJetStream()) {
1186
+ throw new Error(`${method}() is only available for JetStream messages`);
1187
+ }
1188
+ }
1189
+ // ---------------------------------------------------------------------------
1190
+ // Transport-facing state (read by EventRouter)
1191
+ // ---------------------------------------------------------------------------
1192
+ /** @internal */
1193
+ get shouldRetry() {
1194
+ return this._shouldRetry;
1195
+ }
1196
+ /** @internal */
1197
+ get retryDelay() {
1198
+ return this._retryDelay;
1199
+ }
1200
+ /** @internal */
1201
+ get shouldTerminate() {
1202
+ return this._shouldTerminate;
1203
+ }
1204
+ /** @internal */
1205
+ get terminateReason() {
1206
+ return this._terminateReason;
1207
+ }
1090
1208
  };
1091
1209
 
1092
1210
  // src/utils/ack-extension.ts
@@ -1125,15 +1243,20 @@ var serializeError = (err) => {
1125
1243
 
1126
1244
  // src/utils/unwrap-result.ts
1127
1245
  var import_rxjs2 = require("rxjs");
1128
- var unwrapResult = async (result) => {
1246
+ var RESOLVED_VOID = Promise.resolve(void 0);
1247
+ var RESOLVED_NULL = Promise.resolve(null);
1248
+ var unwrapResult = (result) => {
1249
+ if (result === void 0) return RESOLVED_VOID;
1250
+ if (result === null) return RESOLVED_NULL;
1129
1251
  if ((0, import_rxjs2.isObservable)(result)) {
1130
1252
  return subscribeToFirst(result);
1131
1253
  }
1132
- const resolved = await result;
1133
- if ((0, import_rxjs2.isObservable)(resolved)) {
1134
- return subscribeToFirst(resolved);
1254
+ if (typeof result.then === "function") {
1255
+ return result.then(
1256
+ (resolved) => (0, import_rxjs2.isObservable)(resolved) ? subscribeToFirst(resolved) : resolved
1257
+ );
1135
1258
  }
1136
- return resolved;
1259
+ return Promise.resolve(result);
1137
1260
  };
1138
1261
  var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
1139
1262
  let done = false;
@@ -1211,7 +1334,7 @@ var CoreRpcServer = class {
1211
1334
  this.respondWithError(msg, new Error(`No handler for subject: ${msg.subject}`));
1212
1335
  return;
1213
1336
  }
1214
- this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc" /* Rpc */);
1337
+ this.eventBus.emitMessageRouted(msg.subject, "rpc" /* Rpc */);
1215
1338
  let data;
1216
1339
  try {
1217
1340
  data = this.codec.decode(msg.data);
@@ -1696,6 +1819,10 @@ var PatternRegistry = class {
1696
1819
  registry = /* @__PURE__ */ new Map();
1697
1820
  // Cached after registerHandlers() — the registry is immutable from that point
1698
1821
  cachedPatterns = null;
1822
+ _hasEvents = false;
1823
+ _hasCommands = false;
1824
+ _hasBroadcasts = false;
1825
+ _hasOrdered = false;
1699
1826
  /**
1700
1827
  * Register all handlers from the NestJS strategy.
1701
1828
  *
@@ -1729,6 +1856,10 @@ var PatternRegistry = class {
1729
1856
  this.logger.debug(`Registered ${HANDLER_LABELS[kind]}: ${pattern} -> ${fullSubject}`);
1730
1857
  }
1731
1858
  this.cachedPatterns = this.buildPatternsByKind();
1859
+ this._hasEvents = this.cachedPatterns.events.length > 0;
1860
+ this._hasCommands = this.cachedPatterns.commands.length > 0;
1861
+ this._hasBroadcasts = this.cachedPatterns.broadcasts.length > 0;
1862
+ this._hasOrdered = this.cachedPatterns.ordered.length > 0;
1732
1863
  this.logSummary();
1733
1864
  }
1734
1865
  /** Find handler for a full NATS subject. */
@@ -1740,16 +1871,16 @@ var PatternRegistry = class {
1740
1871
  return this.getPatternsByKind().broadcasts.map((p) => buildBroadcastSubject(p));
1741
1872
  }
1742
1873
  hasBroadcastHandlers() {
1743
- return this.getPatternsByKind().broadcasts.length > 0;
1874
+ return this._hasBroadcasts;
1744
1875
  }
1745
1876
  hasRpcHandlers() {
1746
- return this.getPatternsByKind().commands.length > 0;
1877
+ return this._hasCommands;
1747
1878
  }
1748
1879
  hasEventHandlers() {
1749
- return this.getPatternsByKind().events.length > 0;
1880
+ return this._hasEvents;
1750
1881
  }
1751
1882
  hasOrderedHandlers() {
1752
- return this.getPatternsByKind().ordered.length > 0;
1883
+ return this._hasOrdered;
1753
1884
  }
1754
1885
  /** Get fully-qualified NATS subjects for ordered handlers. */
1755
1886
  getOrderedSubjects() {
@@ -1853,13 +1984,8 @@ var EventRouter = class {
1853
1984
  const isOrdered = kind === "ordered" /* Ordered */;
1854
1985
  const ackExtensionInterval = isOrdered ? null : resolveAckExtensionInterval(this.getAckExtensionConfig(kind), this.ackWaitMap?.get(kind));
1855
1986
  const concurrency = this.getConcurrency(kind);
1856
- const route = (msg) => (0, import_rxjs4.defer)(
1857
- () => isOrdered ? this.handleOrdered(msg) : this.handle(msg, ackExtensionInterval)
1858
- ).pipe(
1859
- (0, import_rxjs4.catchError)((err) => {
1860
- this.logger.error(`Unexpected error in ${kind} event router`, err);
1861
- return import_rxjs4.EMPTY;
1862
- })
1987
+ const route = (msg) => (0, import_rxjs4.from)(
1988
+ isOrdered ? this.handleOrderedSafe(msg) : this.handleSafe(msg, ackExtensionInterval, kind)
1863
1989
  );
1864
1990
  const subscription = stream$.pipe(isOrdered ? (0, import_rxjs4.concatMap)(route) : (0, import_rxjs4.mergeMap)(route, concurrency)).subscribe();
1865
1991
  this.subscriptions.push(subscription);
@@ -1874,23 +2000,36 @@ var EventRouter = class {
1874
2000
  if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.ackExtension;
1875
2001
  return void 0;
1876
2002
  }
1877
- /** Handle a single event message: decode -> execute handler -> ack/nak. */
1878
- handle(msg, ackExtensionInterval) {
1879
- const resolved = this.decodeMessage(msg);
1880
- if (!resolved) return import_rxjs4.EMPTY;
1881
- return (0, import_rxjs4.from)(
1882
- this.executeHandler(resolved.handler, resolved.data, resolved.ctx, msg, ackExtensionInterval)
1883
- );
2003
+ /** Handle a single event message with error isolation. */
2004
+ async handleSafe(msg, ackExtensionInterval, kind) {
2005
+ try {
2006
+ const resolved = this.decodeMessage(msg);
2007
+ if (!resolved) return;
2008
+ await this.executeHandler(
2009
+ resolved.handler,
2010
+ resolved.data,
2011
+ resolved.ctx,
2012
+ msg,
2013
+ ackExtensionInterval
2014
+ );
2015
+ } catch (err) {
2016
+ this.logger.error(`Unexpected error in ${kind} event router`, err);
2017
+ }
1884
2018
  }
1885
- /** Handle an ordered message: decode -> execute handler -> no ack/nak. */
1886
- handleOrdered(msg) {
1887
- const resolved = this.decodeMessage(msg, true);
1888
- if (!resolved) return import_rxjs4.EMPTY;
1889
- return (0, import_rxjs4.from)(
1890
- unwrapResult(resolved.handler(resolved.data, resolved.ctx)).catch((err) => {
1891
- this.logger.error(`Ordered handler error (${msg.subject}):`, err);
1892
- })
1893
- );
2019
+ /** Handle an ordered message with error isolation. */
2020
+ async handleOrderedSafe(msg) {
2021
+ try {
2022
+ const resolved = this.decodeMessage(msg, true);
2023
+ if (!resolved) return;
2024
+ await unwrapResult(resolved.handler(resolved.data, resolved.ctx));
2025
+ if (resolved.ctx.shouldRetry || resolved.ctx.shouldTerminate) {
2026
+ this.logger.warn(
2027
+ `retry()/terminate() ignored for ordered message ${msg.subject} \u2014 ordered consumers auto-acknowledge`
2028
+ );
2029
+ }
2030
+ } catch (err) {
2031
+ this.logger.error(`Ordered handler error (${msg.subject}):`, err);
2032
+ }
1894
2033
  }
1895
2034
  /** Resolve handler, decode payload, and build context. Returns null on failure. */
1896
2035
  decodeMessage(msg, isOrdered = false) {
@@ -1908,7 +2047,7 @@ var EventRouter = class {
1908
2047
  this.logger.error(`Decode error for ${msg.subject}:`, err);
1909
2048
  return null;
1910
2049
  }
1911
- this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "event" /* Event */);
2050
+ this.eventBus.emitMessageRouted(msg.subject, "event" /* Event */);
1912
2051
  return { handler, data, ctx: new RpcContext([msg]) };
1913
2052
  }
1914
2053
  /** Execute handler, then ack on success or nak/dead-letter on failure. */
@@ -1916,7 +2055,13 @@ var EventRouter = class {
1916
2055
  const stopAckExtension = startAckExtensionTimer(msg, ackExtensionInterval);
1917
2056
  try {
1918
2057
  await unwrapResult(handler(data, ctx));
1919
- msg.ack();
2058
+ if (ctx.shouldTerminate) {
2059
+ msg.term(ctx.terminateReason);
2060
+ } else if (ctx.shouldRetry) {
2061
+ msg.nak(ctx.retryDelay);
2062
+ } else {
2063
+ msg.ack();
2064
+ }
1920
2065
  } catch (err) {
1921
2066
  this.logger.error(`Event handler error (${msg.subject}):`, err);
1922
2067
  if (this.isDeadLetter(msg)) {
@@ -1983,6 +2128,7 @@ var RpcRouter = class {
1983
2128
  concurrency;
1984
2129
  resolvedAckExtensionInterval;
1985
2130
  subscription = null;
2131
+ cachedNc = null;
1986
2132
  /** Lazily resolve the ack extension interval (needs ackWaitMap populated at runtime). */
1987
2133
  get ackExtensionInterval() {
1988
2134
  if (this.resolvedAckExtensionInterval !== void 0) return this.resolvedAckExtensionInterval;
@@ -1993,56 +2139,50 @@ var RpcRouter = class {
1993
2139
  return this.resolvedAckExtensionInterval;
1994
2140
  }
1995
2141
  /** Start routing command messages to handlers. */
1996
- start() {
1997
- this.subscription = this.messageProvider.commands$.pipe(
1998
- (0, import_rxjs5.mergeMap)(
1999
- (msg) => (0, import_rxjs5.defer)(() => this.handle(msg)).pipe(
2000
- (0, import_rxjs5.catchError)((err) => {
2001
- this.logger.error("Unexpected error in RPC router", err);
2002
- return import_rxjs5.EMPTY;
2003
- })
2004
- ),
2005
- this.concurrency
2006
- )
2007
- ).subscribe();
2142
+ async start() {
2143
+ this.cachedNc = await this.connection.getConnection();
2144
+ this.subscription = this.messageProvider.commands$.pipe((0, import_rxjs5.mergeMap)((msg) => (0, import_rxjs5.from)(this.handleSafe(msg)), this.concurrency)).subscribe();
2008
2145
  }
2009
2146
  /** Stop routing and unsubscribe. */
2010
2147
  destroy() {
2011
2148
  this.subscription?.unsubscribe();
2012
2149
  this.subscription = null;
2013
2150
  }
2014
- /** Handle a single RPC command message. */
2015
- handle(msg) {
2016
- const handler = this.patternRegistry.getHandler(msg.subject);
2017
- if (!handler) {
2018
- msg.term(`No handler for RPC: ${msg.subject}`);
2019
- this.logger.error(`No handler for RPC subject: ${msg.subject}`);
2020
- return import_rxjs5.EMPTY;
2021
- }
2022
- const replyTo = msg.headers?.get("x-reply-to" /* ReplyTo */);
2023
- const correlationId = msg.headers?.get("x-correlation-id" /* CorrelationId */);
2024
- if (!replyTo || !correlationId) {
2025
- msg.term("Missing required headers (reply-to or correlation-id)");
2026
- this.logger.error(`Missing headers for RPC: ${msg.subject}`);
2027
- return import_rxjs5.EMPTY;
2028
- }
2029
- let data;
2151
+ /** Handle a single RPC command message with error isolation. */
2152
+ async handleSafe(msg) {
2030
2153
  try {
2031
- data = this.codec.decode(msg.data);
2154
+ const handler = this.patternRegistry.getHandler(msg.subject);
2155
+ if (!handler) {
2156
+ msg.term(`No handler for RPC: ${msg.subject}`);
2157
+ this.logger.error(`No handler for RPC subject: ${msg.subject}`);
2158
+ return;
2159
+ }
2160
+ const { headers: msgHeaders } = msg;
2161
+ const replyTo = msgHeaders?.get("x-reply-to" /* ReplyTo */);
2162
+ const correlationId = msgHeaders?.get("x-correlation-id" /* CorrelationId */);
2163
+ if (!replyTo || !correlationId) {
2164
+ msg.term("Missing required headers (reply-to or correlation-id)");
2165
+ this.logger.error(`Missing headers for RPC: ${msg.subject}`);
2166
+ return;
2167
+ }
2168
+ let data;
2169
+ try {
2170
+ data = this.codec.decode(msg.data);
2171
+ } catch (err) {
2172
+ msg.term("Decode error");
2173
+ this.logger.error(`Decode error for RPC ${msg.subject}:`, err);
2174
+ return;
2175
+ }
2176
+ this.eventBus.emitMessageRouted(msg.subject, "rpc" /* Rpc */);
2177
+ await this.executeHandler(handler, data, msg, replyTo, correlationId);
2032
2178
  } catch (err) {
2033
- msg.term("Decode error");
2034
- this.logger.error(`Decode error for RPC ${msg.subject}:`, err);
2035
- return import_rxjs5.EMPTY;
2179
+ this.logger.error("Unexpected error in RPC router", err);
2036
2180
  }
2037
- this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc" /* Rpc */);
2038
- return (0, import_rxjs5.from)(this.executeHandler(handler, data, msg, replyTo, correlationId));
2039
2181
  }
2040
2182
  /** Execute handler, publish response, settle message. */
2041
2183
  async executeHandler(handler, data, msg, replyTo, correlationId) {
2042
- const nc = await this.connection.getConnection();
2184
+ const nc = this.cachedNc ?? await this.connection.getConnection();
2043
2185
  const ctx = new RpcContext([msg]);
2044
- const hdrs = (0, import_nats9.headers)();
2045
- hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
2046
2186
  let settled = false;
2047
2187
  const stopAckExtension = startAckExtensionTimer(msg, this.ackExtensionInterval);
2048
2188
  const timeoutId = setTimeout(() => {
@@ -2061,6 +2201,8 @@ var RpcRouter = class {
2061
2201
  stopAckExtension?.();
2062
2202
  msg.ack();
2063
2203
  try {
2204
+ const hdrs = (0, import_nats9.headers)();
2205
+ hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
2064
2206
  nc.publish(replyTo, this.codec.encode(result), { headers: hdrs });
2065
2207
  } catch (publishErr) {
2066
2208
  this.logger.error(`Failed to publish RPC response for ${msg.subject}`, publishErr);
@@ -2071,6 +2213,8 @@ var RpcRouter = class {
2071
2213
  clearTimeout(timeoutId);
2072
2214
  stopAckExtension?.();
2073
2215
  try {
2216
+ const hdrs = (0, import_nats9.headers)();
2217
+ hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
2074
2218
  hdrs.set("x-error" /* Error */, "true");
2075
2219
  nc.publish(replyTo, this.codec.encode(serializeError(err)), { headers: hdrs });
2076
2220
  } catch (encodeErr) {
@@ -2519,8 +2663,12 @@ JetstreamModule = __decorateClass([
2519
2663
  RpcContext,
2520
2664
  StreamKind,
2521
2665
  TransportEvent,
2666
+ buildSubject,
2667
+ consumerName,
2522
2668
  getClientToken,
2669
+ internalName,
2523
2670
  isCoreRpcMode,
2524
2671
  isJetStreamRpcMode,
2672
+ streamName,
2525
2673
  toNanos
2526
2674
  });