@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 +34 -5
- package/dist/index.cjs +221 -73
- package/dist/index.d.cts +122 -14
- package/dist/index.d.ts +122 -14
- package/dist/index.js +219 -82
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -809,6 +809,23 @@ var EventBus = class {
|
|
|
809
809
|
emit(event, ...args) {
|
|
810
810
|
const hook = this.hooks[event];
|
|
811
811
|
if (!hook) return;
|
|
812
|
+
this.callHook(event, hook, ...args);
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Hot-path optimized emit for MessageRouted events.
|
|
816
|
+
* Avoids rest/spread overhead of the generic `emit()`.
|
|
817
|
+
*/
|
|
818
|
+
emitMessageRouted(subject, kind) {
|
|
819
|
+
const hook = this.hooks["messageRouted" /* MessageRouted */];
|
|
820
|
+
if (!hook) return;
|
|
821
|
+
this.callHook(
|
|
822
|
+
"messageRouted" /* MessageRouted */,
|
|
823
|
+
hook,
|
|
824
|
+
subject,
|
|
825
|
+
kind
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
callHook(event, hook, ...args) {
|
|
812
829
|
try {
|
|
813
830
|
const result = hook(...args);
|
|
814
831
|
if (result && typeof result.catch === "function") {
|
|
@@ -938,7 +955,7 @@ var JetstreamStrategy = class extends Server {
|
|
|
938
955
|
this.eventRouter.start();
|
|
939
956
|
}
|
|
940
957
|
if (isJetStreamRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
|
|
941
|
-
this.rpcRouter.start();
|
|
958
|
+
await this.rpcRouter.start();
|
|
942
959
|
}
|
|
943
960
|
}
|
|
944
961
|
if (isCoreRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
|
|
@@ -1032,6 +1049,13 @@ import { headers as natsHeaders2 } from "nats";
|
|
|
1032
1049
|
// src/context/rpc.context.ts
|
|
1033
1050
|
import { BaseRpcContext } from "@nestjs/microservices";
|
|
1034
1051
|
var RpcContext = class extends BaseRpcContext {
|
|
1052
|
+
_shouldRetry = false;
|
|
1053
|
+
_retryDelay;
|
|
1054
|
+
_shouldTerminate = false;
|
|
1055
|
+
_terminateReason;
|
|
1056
|
+
// ---------------------------------------------------------------------------
|
|
1057
|
+
// Message accessors
|
|
1058
|
+
// ---------------------------------------------------------------------------
|
|
1035
1059
|
/**
|
|
1036
1060
|
* Get the underlying NATS message.
|
|
1037
1061
|
*
|
|
@@ -1066,6 +1090,96 @@ var RpcContext = class extends BaseRpcContext {
|
|
|
1066
1090
|
isJetStream() {
|
|
1067
1091
|
return "ack" in this.args[0];
|
|
1068
1092
|
}
|
|
1093
|
+
// ---------------------------------------------------------------------------
|
|
1094
|
+
// JetStream metadata (return undefined for Core NATS messages)
|
|
1095
|
+
// ---------------------------------------------------------------------------
|
|
1096
|
+
/** How many times this message has been delivered. */
|
|
1097
|
+
getDeliveryCount() {
|
|
1098
|
+
return this.asJetStream()?.info.deliveryCount;
|
|
1099
|
+
}
|
|
1100
|
+
/** The JetStream stream this message belongs to. */
|
|
1101
|
+
getStream() {
|
|
1102
|
+
return this.asJetStream()?.info.stream;
|
|
1103
|
+
}
|
|
1104
|
+
/** The stream sequence number. */
|
|
1105
|
+
getSequence() {
|
|
1106
|
+
return this.asJetStream()?.seq;
|
|
1107
|
+
}
|
|
1108
|
+
/** The message timestamp as a `Date` (derived from `info.timestampNanos`). */
|
|
1109
|
+
getTimestamp() {
|
|
1110
|
+
const nanos = this.asJetStream()?.info.timestampNanos;
|
|
1111
|
+
return typeof nanos === "number" ? new Date(nanos / 1e6) : void 0;
|
|
1112
|
+
}
|
|
1113
|
+
/** The name of the service that published this message (from `x-caller-name` header). */
|
|
1114
|
+
getCallerName() {
|
|
1115
|
+
return this.getHeader("x-caller-name" /* CallerName */);
|
|
1116
|
+
}
|
|
1117
|
+
// ---------------------------------------------------------------------------
|
|
1118
|
+
// Handler-controlled settlement
|
|
1119
|
+
// ---------------------------------------------------------------------------
|
|
1120
|
+
/**
|
|
1121
|
+
* Signal the transport to retry (nak) this message instead of acknowledging it.
|
|
1122
|
+
*
|
|
1123
|
+
* Use for business-level retries without throwing errors.
|
|
1124
|
+
* Only affects JetStream event handlers (workqueue/broadcast).
|
|
1125
|
+
*
|
|
1126
|
+
* @param opts - Optional delay in ms before redelivery.
|
|
1127
|
+
* @throws Error if {@link terminate} was already called.
|
|
1128
|
+
*/
|
|
1129
|
+
retry(opts) {
|
|
1130
|
+
this.assertJetStream("retry");
|
|
1131
|
+
if (this._shouldTerminate) {
|
|
1132
|
+
throw new Error("Cannot retry \u2014 terminate() was already called");
|
|
1133
|
+
}
|
|
1134
|
+
this._shouldRetry = true;
|
|
1135
|
+
this._retryDelay = opts?.delayMs;
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Signal the transport to permanently reject (term) this message.
|
|
1139
|
+
*
|
|
1140
|
+
* Use when a message is no longer relevant and should not be retried or sent to DLQ.
|
|
1141
|
+
* Only affects JetStream event handlers (workqueue/broadcast).
|
|
1142
|
+
*
|
|
1143
|
+
* @param reason - Optional reason for termination (logged by NATS).
|
|
1144
|
+
* @throws Error if {@link retry} was already called.
|
|
1145
|
+
*/
|
|
1146
|
+
terminate(reason) {
|
|
1147
|
+
this.assertJetStream("terminate");
|
|
1148
|
+
if (this._shouldRetry) {
|
|
1149
|
+
throw new Error("Cannot terminate \u2014 retry() was already called");
|
|
1150
|
+
}
|
|
1151
|
+
this._shouldTerminate = true;
|
|
1152
|
+
this._terminateReason = reason;
|
|
1153
|
+
}
|
|
1154
|
+
/** Narrow to JsMsg or return null for Core messages. Used by metadata getters. */
|
|
1155
|
+
asJetStream() {
|
|
1156
|
+
return this.isJetStream() ? this.args[0] : null;
|
|
1157
|
+
}
|
|
1158
|
+
/** Ensure the message is JetStream — settlement actions are not available for Core NATS. */
|
|
1159
|
+
assertJetStream(method) {
|
|
1160
|
+
if (!this.isJetStream()) {
|
|
1161
|
+
throw new Error(`${method}() is only available for JetStream messages`);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
// ---------------------------------------------------------------------------
|
|
1165
|
+
// Transport-facing state (read by EventRouter)
|
|
1166
|
+
// ---------------------------------------------------------------------------
|
|
1167
|
+
/** @internal */
|
|
1168
|
+
get shouldRetry() {
|
|
1169
|
+
return this._shouldRetry;
|
|
1170
|
+
}
|
|
1171
|
+
/** @internal */
|
|
1172
|
+
get retryDelay() {
|
|
1173
|
+
return this._retryDelay;
|
|
1174
|
+
}
|
|
1175
|
+
/** @internal */
|
|
1176
|
+
get shouldTerminate() {
|
|
1177
|
+
return this._shouldTerminate;
|
|
1178
|
+
}
|
|
1179
|
+
/** @internal */
|
|
1180
|
+
get terminateReason() {
|
|
1181
|
+
return this._terminateReason;
|
|
1182
|
+
}
|
|
1069
1183
|
};
|
|
1070
1184
|
|
|
1071
1185
|
// src/utils/ack-extension.ts
|
|
@@ -1104,15 +1218,20 @@ var serializeError = (err) => {
|
|
|
1104
1218
|
|
|
1105
1219
|
// src/utils/unwrap-result.ts
|
|
1106
1220
|
import { isObservable } from "rxjs";
|
|
1107
|
-
var
|
|
1221
|
+
var RESOLVED_VOID = Promise.resolve(void 0);
|
|
1222
|
+
var RESOLVED_NULL = Promise.resolve(null);
|
|
1223
|
+
var unwrapResult = (result) => {
|
|
1224
|
+
if (result === void 0) return RESOLVED_VOID;
|
|
1225
|
+
if (result === null) return RESOLVED_NULL;
|
|
1108
1226
|
if (isObservable(result)) {
|
|
1109
1227
|
return subscribeToFirst(result);
|
|
1110
1228
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1229
|
+
if (typeof result.then === "function") {
|
|
1230
|
+
return result.then(
|
|
1231
|
+
(resolved) => isObservable(resolved) ? subscribeToFirst(resolved) : resolved
|
|
1232
|
+
);
|
|
1114
1233
|
}
|
|
1115
|
-
return
|
|
1234
|
+
return Promise.resolve(result);
|
|
1116
1235
|
};
|
|
1117
1236
|
var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
|
|
1118
1237
|
let done = false;
|
|
@@ -1190,7 +1309,7 @@ var CoreRpcServer = class {
|
|
|
1190
1309
|
this.respondWithError(msg, new Error(`No handler for subject: ${msg.subject}`));
|
|
1191
1310
|
return;
|
|
1192
1311
|
}
|
|
1193
|
-
this.eventBus.
|
|
1312
|
+
this.eventBus.emitMessageRouted(msg.subject, "rpc" /* Rpc */);
|
|
1194
1313
|
let data;
|
|
1195
1314
|
try {
|
|
1196
1315
|
data = this.codec.decode(msg.data);
|
|
@@ -1688,6 +1807,10 @@ var PatternRegistry = class {
|
|
|
1688
1807
|
registry = /* @__PURE__ */ new Map();
|
|
1689
1808
|
// Cached after registerHandlers() — the registry is immutable from that point
|
|
1690
1809
|
cachedPatterns = null;
|
|
1810
|
+
_hasEvents = false;
|
|
1811
|
+
_hasCommands = false;
|
|
1812
|
+
_hasBroadcasts = false;
|
|
1813
|
+
_hasOrdered = false;
|
|
1691
1814
|
/**
|
|
1692
1815
|
* Register all handlers from the NestJS strategy.
|
|
1693
1816
|
*
|
|
@@ -1721,6 +1844,10 @@ var PatternRegistry = class {
|
|
|
1721
1844
|
this.logger.debug(`Registered ${HANDLER_LABELS[kind]}: ${pattern} -> ${fullSubject}`);
|
|
1722
1845
|
}
|
|
1723
1846
|
this.cachedPatterns = this.buildPatternsByKind();
|
|
1847
|
+
this._hasEvents = this.cachedPatterns.events.length > 0;
|
|
1848
|
+
this._hasCommands = this.cachedPatterns.commands.length > 0;
|
|
1849
|
+
this._hasBroadcasts = this.cachedPatterns.broadcasts.length > 0;
|
|
1850
|
+
this._hasOrdered = this.cachedPatterns.ordered.length > 0;
|
|
1724
1851
|
this.logSummary();
|
|
1725
1852
|
}
|
|
1726
1853
|
/** Find handler for a full NATS subject. */
|
|
@@ -1732,16 +1859,16 @@ var PatternRegistry = class {
|
|
|
1732
1859
|
return this.getPatternsByKind().broadcasts.map((p) => buildBroadcastSubject(p));
|
|
1733
1860
|
}
|
|
1734
1861
|
hasBroadcastHandlers() {
|
|
1735
|
-
return this.
|
|
1862
|
+
return this._hasBroadcasts;
|
|
1736
1863
|
}
|
|
1737
1864
|
hasRpcHandlers() {
|
|
1738
|
-
return this.
|
|
1865
|
+
return this._hasCommands;
|
|
1739
1866
|
}
|
|
1740
1867
|
hasEventHandlers() {
|
|
1741
|
-
return this.
|
|
1868
|
+
return this._hasEvents;
|
|
1742
1869
|
}
|
|
1743
1870
|
hasOrderedHandlers() {
|
|
1744
|
-
return this.
|
|
1871
|
+
return this._hasOrdered;
|
|
1745
1872
|
}
|
|
1746
1873
|
/** Get fully-qualified NATS subjects for ordered handlers. */
|
|
1747
1874
|
getOrderedSubjects() {
|
|
@@ -1804,14 +1931,7 @@ var PatternRegistry = class {
|
|
|
1804
1931
|
|
|
1805
1932
|
// src/server/routing/event.router.ts
|
|
1806
1933
|
import { Logger as Logger9 } from "@nestjs/common";
|
|
1807
|
-
import {
|
|
1808
|
-
catchError as catchError2,
|
|
1809
|
-
concatMap,
|
|
1810
|
-
defer as defer3,
|
|
1811
|
-
EMPTY as EMPTY2,
|
|
1812
|
-
from as from2,
|
|
1813
|
-
mergeMap
|
|
1814
|
-
} from "rxjs";
|
|
1934
|
+
import { concatMap, from as from2, mergeMap } from "rxjs";
|
|
1815
1935
|
var EventRouter = class {
|
|
1816
1936
|
constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig, processingConfig, ackWaitMap) {
|
|
1817
1937
|
this.messageProvider = messageProvider;
|
|
@@ -1852,13 +1972,8 @@ var EventRouter = class {
|
|
|
1852
1972
|
const isOrdered = kind === "ordered" /* Ordered */;
|
|
1853
1973
|
const ackExtensionInterval = isOrdered ? null : resolveAckExtensionInterval(this.getAckExtensionConfig(kind), this.ackWaitMap?.get(kind));
|
|
1854
1974
|
const concurrency = this.getConcurrency(kind);
|
|
1855
|
-
const route = (msg) =>
|
|
1856
|
-
|
|
1857
|
-
).pipe(
|
|
1858
|
-
catchError2((err) => {
|
|
1859
|
-
this.logger.error(`Unexpected error in ${kind} event router`, err);
|
|
1860
|
-
return EMPTY2;
|
|
1861
|
-
})
|
|
1975
|
+
const route = (msg) => from2(
|
|
1976
|
+
isOrdered ? this.handleOrderedSafe(msg) : this.handleSafe(msg, ackExtensionInterval, kind)
|
|
1862
1977
|
);
|
|
1863
1978
|
const subscription = stream$.pipe(isOrdered ? concatMap(route) : mergeMap(route, concurrency)).subscribe();
|
|
1864
1979
|
this.subscriptions.push(subscription);
|
|
@@ -1873,23 +1988,36 @@ var EventRouter = class {
|
|
|
1873
1988
|
if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.ackExtension;
|
|
1874
1989
|
return void 0;
|
|
1875
1990
|
}
|
|
1876
|
-
/** Handle a single event message
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
this.executeHandler(
|
|
1882
|
-
|
|
1991
|
+
/** Handle a single event message with error isolation. */
|
|
1992
|
+
async handleSafe(msg, ackExtensionInterval, kind) {
|
|
1993
|
+
try {
|
|
1994
|
+
const resolved = this.decodeMessage(msg);
|
|
1995
|
+
if (!resolved) return;
|
|
1996
|
+
await this.executeHandler(
|
|
1997
|
+
resolved.handler,
|
|
1998
|
+
resolved.data,
|
|
1999
|
+
resolved.ctx,
|
|
2000
|
+
msg,
|
|
2001
|
+
ackExtensionInterval
|
|
2002
|
+
);
|
|
2003
|
+
} catch (err) {
|
|
2004
|
+
this.logger.error(`Unexpected error in ${kind} event router`, err);
|
|
2005
|
+
}
|
|
1883
2006
|
}
|
|
1884
|
-
/** Handle an ordered message
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
unwrapResult(resolved.handler(resolved.data, resolved.ctx))
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
2007
|
+
/** Handle an ordered message with error isolation. */
|
|
2008
|
+
async handleOrderedSafe(msg) {
|
|
2009
|
+
try {
|
|
2010
|
+
const resolved = this.decodeMessage(msg, true);
|
|
2011
|
+
if (!resolved) return;
|
|
2012
|
+
await unwrapResult(resolved.handler(resolved.data, resolved.ctx));
|
|
2013
|
+
if (resolved.ctx.shouldRetry || resolved.ctx.shouldTerminate) {
|
|
2014
|
+
this.logger.warn(
|
|
2015
|
+
`retry()/terminate() ignored for ordered message ${msg.subject} \u2014 ordered consumers auto-acknowledge`
|
|
2016
|
+
);
|
|
2017
|
+
}
|
|
2018
|
+
} catch (err) {
|
|
2019
|
+
this.logger.error(`Ordered handler error (${msg.subject}):`, err);
|
|
2020
|
+
}
|
|
1893
2021
|
}
|
|
1894
2022
|
/** Resolve handler, decode payload, and build context. Returns null on failure. */
|
|
1895
2023
|
decodeMessage(msg, isOrdered = false) {
|
|
@@ -1907,7 +2035,7 @@ var EventRouter = class {
|
|
|
1907
2035
|
this.logger.error(`Decode error for ${msg.subject}:`, err);
|
|
1908
2036
|
return null;
|
|
1909
2037
|
}
|
|
1910
|
-
this.eventBus.
|
|
2038
|
+
this.eventBus.emitMessageRouted(msg.subject, "event" /* Event */);
|
|
1911
2039
|
return { handler, data, ctx: new RpcContext([msg]) };
|
|
1912
2040
|
}
|
|
1913
2041
|
/** Execute handler, then ack on success or nak/dead-letter on failure. */
|
|
@@ -1915,7 +2043,13 @@ var EventRouter = class {
|
|
|
1915
2043
|
const stopAckExtension = startAckExtensionTimer(msg, ackExtensionInterval);
|
|
1916
2044
|
try {
|
|
1917
2045
|
await unwrapResult(handler(data, ctx));
|
|
1918
|
-
|
|
2046
|
+
if (ctx.shouldTerminate) {
|
|
2047
|
+
msg.term(ctx.terminateReason);
|
|
2048
|
+
} else if (ctx.shouldRetry) {
|
|
2049
|
+
msg.nak(ctx.retryDelay);
|
|
2050
|
+
} else {
|
|
2051
|
+
msg.ack();
|
|
2052
|
+
}
|
|
1919
2053
|
} catch (err) {
|
|
1920
2054
|
this.logger.error(`Event handler error (${msg.subject}):`, err);
|
|
1921
2055
|
if (this.isDeadLetter(msg)) {
|
|
@@ -1964,7 +2098,7 @@ var EventRouter = class {
|
|
|
1964
2098
|
// src/server/routing/rpc.router.ts
|
|
1965
2099
|
import { Logger as Logger10 } from "@nestjs/common";
|
|
1966
2100
|
import { headers } from "nats";
|
|
1967
|
-
import {
|
|
2101
|
+
import { from as from3, mergeMap as mergeMap2 } from "rxjs";
|
|
1968
2102
|
var RpcRouter = class {
|
|
1969
2103
|
constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap) {
|
|
1970
2104
|
this.messageProvider = messageProvider;
|
|
@@ -1982,6 +2116,7 @@ var RpcRouter = class {
|
|
|
1982
2116
|
concurrency;
|
|
1983
2117
|
resolvedAckExtensionInterval;
|
|
1984
2118
|
subscription = null;
|
|
2119
|
+
cachedNc = null;
|
|
1985
2120
|
/** Lazily resolve the ack extension interval (needs ackWaitMap populated at runtime). */
|
|
1986
2121
|
get ackExtensionInterval() {
|
|
1987
2122
|
if (this.resolvedAckExtensionInterval !== void 0) return this.resolvedAckExtensionInterval;
|
|
@@ -1992,56 +2127,50 @@ var RpcRouter = class {
|
|
|
1992
2127
|
return this.resolvedAckExtensionInterval;
|
|
1993
2128
|
}
|
|
1994
2129
|
/** Start routing command messages to handlers. */
|
|
1995
|
-
start() {
|
|
1996
|
-
this.
|
|
1997
|
-
|
|
1998
|
-
(msg) => defer4(() => this.handle(msg)).pipe(
|
|
1999
|
-
catchError3((err) => {
|
|
2000
|
-
this.logger.error("Unexpected error in RPC router", err);
|
|
2001
|
-
return EMPTY3;
|
|
2002
|
-
})
|
|
2003
|
-
),
|
|
2004
|
-
this.concurrency
|
|
2005
|
-
)
|
|
2006
|
-
).subscribe();
|
|
2130
|
+
async start() {
|
|
2131
|
+
this.cachedNc = await this.connection.getConnection();
|
|
2132
|
+
this.subscription = this.messageProvider.commands$.pipe(mergeMap2((msg) => from3(this.handleSafe(msg)), this.concurrency)).subscribe();
|
|
2007
2133
|
}
|
|
2008
2134
|
/** Stop routing and unsubscribe. */
|
|
2009
2135
|
destroy() {
|
|
2010
2136
|
this.subscription?.unsubscribe();
|
|
2011
2137
|
this.subscription = null;
|
|
2012
2138
|
}
|
|
2013
|
-
/** Handle a single RPC command message. */
|
|
2014
|
-
|
|
2015
|
-
const handler = this.patternRegistry.getHandler(msg.subject);
|
|
2016
|
-
if (!handler) {
|
|
2017
|
-
msg.term(`No handler for RPC: ${msg.subject}`);
|
|
2018
|
-
this.logger.error(`No handler for RPC subject: ${msg.subject}`);
|
|
2019
|
-
return EMPTY3;
|
|
2020
|
-
}
|
|
2021
|
-
const replyTo = msg.headers?.get("x-reply-to" /* ReplyTo */);
|
|
2022
|
-
const correlationId = msg.headers?.get("x-correlation-id" /* CorrelationId */);
|
|
2023
|
-
if (!replyTo || !correlationId) {
|
|
2024
|
-
msg.term("Missing required headers (reply-to or correlation-id)");
|
|
2025
|
-
this.logger.error(`Missing headers for RPC: ${msg.subject}`);
|
|
2026
|
-
return EMPTY3;
|
|
2027
|
-
}
|
|
2028
|
-
let data;
|
|
2139
|
+
/** Handle a single RPC command message with error isolation. */
|
|
2140
|
+
async handleSafe(msg) {
|
|
2029
2141
|
try {
|
|
2030
|
-
|
|
2142
|
+
const handler = this.patternRegistry.getHandler(msg.subject);
|
|
2143
|
+
if (!handler) {
|
|
2144
|
+
msg.term(`No handler for RPC: ${msg.subject}`);
|
|
2145
|
+
this.logger.error(`No handler for RPC subject: ${msg.subject}`);
|
|
2146
|
+
return;
|
|
2147
|
+
}
|
|
2148
|
+
const { headers: msgHeaders } = msg;
|
|
2149
|
+
const replyTo = msgHeaders?.get("x-reply-to" /* ReplyTo */);
|
|
2150
|
+
const correlationId = msgHeaders?.get("x-correlation-id" /* CorrelationId */);
|
|
2151
|
+
if (!replyTo || !correlationId) {
|
|
2152
|
+
msg.term("Missing required headers (reply-to or correlation-id)");
|
|
2153
|
+
this.logger.error(`Missing headers for RPC: ${msg.subject}`);
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
let data;
|
|
2157
|
+
try {
|
|
2158
|
+
data = this.codec.decode(msg.data);
|
|
2159
|
+
} catch (err) {
|
|
2160
|
+
msg.term("Decode error");
|
|
2161
|
+
this.logger.error(`Decode error for RPC ${msg.subject}:`, err);
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
this.eventBus.emitMessageRouted(msg.subject, "rpc" /* Rpc */);
|
|
2165
|
+
await this.executeHandler(handler, data, msg, replyTo, correlationId);
|
|
2031
2166
|
} catch (err) {
|
|
2032
|
-
|
|
2033
|
-
this.logger.error(`Decode error for RPC ${msg.subject}:`, err);
|
|
2034
|
-
return EMPTY3;
|
|
2167
|
+
this.logger.error("Unexpected error in RPC router", err);
|
|
2035
2168
|
}
|
|
2036
|
-
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc" /* Rpc */);
|
|
2037
|
-
return from3(this.executeHandler(handler, data, msg, replyTo, correlationId));
|
|
2038
2169
|
}
|
|
2039
2170
|
/** Execute handler, publish response, settle message. */
|
|
2040
2171
|
async executeHandler(handler, data, msg, replyTo, correlationId) {
|
|
2041
|
-
const nc = await this.connection.getConnection();
|
|
2172
|
+
const nc = this.cachedNc ?? await this.connection.getConnection();
|
|
2042
2173
|
const ctx = new RpcContext([msg]);
|
|
2043
|
-
const hdrs = headers();
|
|
2044
|
-
hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
|
|
2045
2174
|
let settled = false;
|
|
2046
2175
|
const stopAckExtension = startAckExtensionTimer(msg, this.ackExtensionInterval);
|
|
2047
2176
|
const timeoutId = setTimeout(() => {
|
|
@@ -2060,6 +2189,8 @@ var RpcRouter = class {
|
|
|
2060
2189
|
stopAckExtension?.();
|
|
2061
2190
|
msg.ack();
|
|
2062
2191
|
try {
|
|
2192
|
+
const hdrs = headers();
|
|
2193
|
+
hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
|
|
2063
2194
|
nc.publish(replyTo, this.codec.encode(result), { headers: hdrs });
|
|
2064
2195
|
} catch (publishErr) {
|
|
2065
2196
|
this.logger.error(`Failed to publish RPC response for ${msg.subject}`, publishErr);
|
|
@@ -2070,6 +2201,8 @@ var RpcRouter = class {
|
|
|
2070
2201
|
clearTimeout(timeoutId);
|
|
2071
2202
|
stopAckExtension?.();
|
|
2072
2203
|
try {
|
|
2204
|
+
const hdrs = headers();
|
|
2205
|
+
hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
|
|
2073
2206
|
hdrs.set("x-error" /* Error */, "true");
|
|
2074
2207
|
nc.publish(replyTo, this.codec.encode(serializeError(err)), { headers: hdrs });
|
|
2075
2208
|
} catch (encodeErr) {
|
|
@@ -2517,8 +2650,12 @@ export {
|
|
|
2517
2650
|
RpcContext,
|
|
2518
2651
|
StreamKind,
|
|
2519
2652
|
TransportEvent,
|
|
2653
|
+
buildSubject,
|
|
2654
|
+
consumerName,
|
|
2520
2655
|
getClientToken,
|
|
2656
|
+
internalName,
|
|
2521
2657
|
isCoreRpcMode,
|
|
2522
2658
|
isJetStreamRpcMode,
|
|
2659
|
+
streamName,
|
|
2523
2660
|
toNanos
|
|
2524
2661
|
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@horizon-republic/nestjs-jetstream",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.1",
|
|
4
4
|
"description": "A NestJS transport for NATS with JetStream events, broadcast fan-out, and Core/JetStream RPC.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/HorizonRepublic/nestjs-jetstream.git"
|
|
8
8
|
},
|
|
9
|
-
"homepage": "https://github.
|
|
9
|
+
"homepage": "https://horizonrepublic.github.io/nestjs-jetstream/",
|
|
10
10
|
"bugs": {
|
|
11
11
|
"url": "https://github.com/HorizonRepublic/nestjs-jetstream/issues"
|
|
12
12
|
},
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"@nestjs/platform-express": "^11.1.17",
|
|
68
68
|
"@nestjs/testing": "^11.1.17",
|
|
69
69
|
"@types/node": "^25.5.0",
|
|
70
|
-
"@vitest/coverage-v8": "^4.1.
|
|
70
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
71
71
|
"eslint": "^10.1.0",
|
|
72
72
|
"eslint-config-prettier": "^10.1.8",
|
|
73
73
|
"eslint-plugin-prefer-arrow": "^1.2.3",
|
|
@@ -77,9 +77,9 @@
|
|
|
77
77
|
"prettier": "^3.8.1",
|
|
78
78
|
"tsup": "^8.5.1",
|
|
79
79
|
"tsx": "^4.21.0",
|
|
80
|
-
"typescript": "~5.9.
|
|
80
|
+
"typescript": "~5.9.3",
|
|
81
81
|
"typescript-eslint": "^8.57.2",
|
|
82
|
-
"vitest": "^4.1.
|
|
82
|
+
"vitest": "^4.1.2"
|
|
83
83
|
},
|
|
84
84
|
"scripts": {
|
|
85
85
|
"build": "tsup",
|