@horizon-republic/nestjs-jetstream 2.5.0 → 2.6.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/dist/index.js CHANGED
@@ -29,6 +29,11 @@ import {
29
29
  } from "nats";
30
30
 
31
31
  // src/interfaces/hooks.interface.ts
32
+ var MessageKind = /* @__PURE__ */ ((MessageKind2) => {
33
+ MessageKind2["Event"] = "event";
34
+ MessageKind2["Rpc"] = "rpc";
35
+ return MessageKind2;
36
+ })(MessageKind || {});
32
37
  var TransportEvent = /* @__PURE__ */ ((TransportEvent2) => {
33
38
  TransportEvent2["Connect"] = "connect";
34
39
  TransportEvent2["Disconnect"] = "disconnect";
@@ -42,6 +47,15 @@ var TransportEvent = /* @__PURE__ */ ((TransportEvent2) => {
42
47
  return TransportEvent2;
43
48
  })(TransportEvent || {});
44
49
 
50
+ // src/interfaces/stream.interface.ts
51
+ var StreamKind = /* @__PURE__ */ ((StreamKind2) => {
52
+ StreamKind2["Event"] = "ev";
53
+ StreamKind2["Command"] = "cmd";
54
+ StreamKind2["Broadcast"] = "broadcast";
55
+ StreamKind2["Ordered"] = "ordered";
56
+ return StreamKind2;
57
+ })(StreamKind || {});
58
+
45
59
  // src/jetstream.constants.ts
46
60
  import {
47
61
  AckPolicy,
@@ -74,7 +88,7 @@ var baseStreamConfig = {
74
88
  num_replicas: 1,
75
89
  discard: DiscardPolicy.Old,
76
90
  allow_direct: true,
77
- compression: StoreCompression.None
91
+ compression: StoreCompression.S2
78
92
  };
79
93
  var DEFAULT_EVENT_STREAM_CONFIG = {
80
94
  ...baseStreamConfig,
@@ -166,13 +180,20 @@ var internalName = (name) => `${name}__microservice`;
166
180
  var buildSubject = (serviceName, kind, pattern) => `${internalName(serviceName)}.${kind}.${pattern}`;
167
181
  var buildBroadcastSubject = (pattern) => `broadcast.${pattern}`;
168
182
  var streamName = (serviceName, kind) => {
169
- if (kind === "broadcast") return "broadcast-stream";
183
+ if (kind === "broadcast" /* Broadcast */) return "broadcast-stream";
170
184
  return `${internalName(serviceName)}_${kind}-stream`;
171
185
  };
172
186
  var consumerName = (serviceName, kind) => {
173
- if (kind === "broadcast") return `${internalName(serviceName)}_broadcast-consumer`;
187
+ if (kind === "broadcast" /* Broadcast */) return `${internalName(serviceName)}_broadcast-consumer`;
174
188
  return `${internalName(serviceName)}_${kind}-consumer`;
175
189
  };
190
+ var PatternPrefix = /* @__PURE__ */ ((PatternPrefix2) => {
191
+ PatternPrefix2["Broadcast"] = "broadcast:";
192
+ PatternPrefix2["Ordered"] = "ordered:";
193
+ return PatternPrefix2;
194
+ })(PatternPrefix || {});
195
+ var isJetStreamRpcMode = (rpc) => rpc?.mode === "jetstream";
196
+ var isCoreRpcMode = (rpc) => !rpc || rpc.mode === "core";
176
197
 
177
198
  // src/client/jetstream.record.ts
178
199
  var JetstreamRecord = class {
@@ -287,10 +308,13 @@ var JetstreamClient = class extends ClientProxy {
287
308
  this.codec = codec;
288
309
  this.eventBus = eventBus;
289
310
  this.targetName = targetServiceName;
311
+ this.callerName = internalName(this.rootOptions.name);
290
312
  }
291
313
  logger = new Logger("Jetstream:Client");
292
314
  /** Target service name this client sends messages to. */
293
315
  targetName;
316
+ /** Pre-cached caller name derived from rootOptions.name, computed once in constructor. */
317
+ callerName;
294
318
  /** Shared inbox for JetStream-mode RPC responses. */
295
319
  inbox = null;
296
320
  inboxSubscription = null;
@@ -310,7 +334,7 @@ var JetstreamClient = class extends ClientProxy {
310
334
  */
311
335
  async connect() {
312
336
  const nc = await this.connection.getConnection();
313
- if (this.isJetStreamRpcMode() && !this.inboxSubscription) {
337
+ if (isJetStreamRpcMode(this.rootOptions.rpc) && !this.inboxSubscription) {
314
338
  this.setupInbox(nc);
315
339
  }
316
340
  this.statusSubscription ??= this.connection.status$.subscribe((status) => {
@@ -345,11 +369,11 @@ var JetstreamClient = class extends ClientProxy {
345
369
  * depending on the subject prefix.
346
370
  */
347
371
  async dispatchEvent(packet) {
348
- const nc = await this.connect();
372
+ await this.connect();
349
373
  const { data, hdrs, messageId } = this.extractRecordData(packet.data);
350
374
  const subject = this.buildEventSubject(packet.pattern);
351
375
  const msgHeaders = this.buildHeaders(hdrs, { subject });
352
- const ack = await nc.jetstream().publish(subject, this.codec.encode(data), {
376
+ const ack = await this.connection.getJetStreamClient().publish(subject, this.codec.encode(data), {
353
377
  headers: msgHeaders,
354
378
  msgID: messageId ?? crypto.randomUUID()
355
379
  });
@@ -365,26 +389,23 @@ var JetstreamClient = class extends ClientProxy {
365
389
  * JetStream mode: publishes to stream + waits for inbox response.
366
390
  */
367
391
  publish(packet, callback) {
368
- const subject = buildSubject(this.targetName, "cmd", packet.pattern);
392
+ const subject = buildSubject(this.targetName, "cmd" /* Command */, packet.pattern);
369
393
  const { data, hdrs, timeout, messageId } = this.extractRecordData(packet.data);
370
394
  const onUnhandled = (err) => {
371
395
  this.logger.error("Unhandled publish error:", err);
372
396
  callback({ err: new Error("Internal transport error"), response: null, isDisposed: true });
373
397
  };
374
398
  let jetStreamCorrelationId = null;
375
- if (this.isCoreRpcMode()) {
399
+ if (isCoreRpcMode(this.rootOptions.rpc)) {
376
400
  this.publishCoreRpc(subject, data, hdrs, timeout, callback).catch(onUnhandled);
377
401
  } else {
378
402
  jetStreamCorrelationId = crypto.randomUUID();
379
- this.publishJetStreamRpc(
380
- subject,
381
- data,
382
- hdrs,
403
+ this.publishJetStreamRpc(subject, data, callback, {
404
+ headers: hdrs,
383
405
  timeout,
384
- callback,
385
- jetStreamCorrelationId,
406
+ correlationId: jetStreamCorrelationId,
386
407
  messageId
387
- ).catch(onUnhandled);
408
+ }).catch(onUnhandled);
388
409
  }
389
410
  return () => {
390
411
  if (jetStreamCorrelationId) {
@@ -421,35 +442,46 @@ var JetstreamClient = class extends ClientProxy {
421
442
  }
422
443
  }
423
444
  /** JetStream mode: publish to stream + wait for inbox response. */
424
- async publishJetStreamRpc(subject, data, customHeaders, timeout, callback, correlationId = crypto.randomUUID(), messageId) {
425
- const effectiveTimeout = timeout ?? this.getRpcTimeout();
445
+ async publishJetStreamRpc(subject, data, callback, options) {
446
+ const { headers: customHeaders, correlationId, messageId } = options;
447
+ const effectiveTimeout = options.timeout ?? this.getRpcTimeout();
426
448
  this.pendingMessages.set(correlationId, callback);
427
- const timeoutId = setTimeout(() => {
428
- if (!this.pendingMessages.has(correlationId)) return;
429
- this.pendingTimeouts.delete(correlationId);
430
- this.pendingMessages.delete(correlationId);
431
- this.logger.error(`JetStream RPC timeout (${effectiveTimeout}ms): ${subject}`);
432
- this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
433
- callback({ err: new Error("RPC timeout"), response: null, isDisposed: true });
434
- }, effectiveTimeout);
435
- this.pendingTimeouts.set(correlationId, timeoutId);
436
449
  try {
437
- const nc = await this.connect();
450
+ await this.connect();
451
+ if (!this.pendingMessages.has(correlationId)) return;
438
452
  if (!this.inbox) {
439
- throw new Error("Inbox not initialized \u2014 JetStream RPC mode requires a connected inbox");
453
+ this.pendingMessages.delete(correlationId);
454
+ callback({
455
+ err: new Error("Inbox not initialized \u2014 JetStream RPC mode requires a connected inbox"),
456
+ response: null,
457
+ isDisposed: true
458
+ });
459
+ return;
440
460
  }
461
+ const timeoutId = setTimeout(() => {
462
+ if (!this.pendingMessages.has(correlationId)) return;
463
+ this.pendingTimeouts.delete(correlationId);
464
+ this.pendingMessages.delete(correlationId);
465
+ this.logger.error(`JetStream RPC timeout (${effectiveTimeout}ms): ${subject}`);
466
+ this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
467
+ callback({ err: new Error("RPC timeout"), response: null, isDisposed: true });
468
+ }, effectiveTimeout);
469
+ this.pendingTimeouts.set(correlationId, timeoutId);
441
470
  const hdrs = this.buildHeaders(customHeaders, {
442
471
  subject,
443
472
  correlationId,
444
473
  replyTo: this.inbox
445
474
  });
446
- await nc.jetstream().publish(subject, this.codec.encode(data), {
475
+ await this.connection.getJetStreamClient().publish(subject, this.codec.encode(data), {
447
476
  headers: hdrs,
448
477
  msgID: messageId ?? crypto.randomUUID()
449
478
  });
450
479
  } catch (err) {
451
- clearTimeout(timeoutId);
452
- this.pendingTimeouts.delete(correlationId);
480
+ const existingTimeout = this.pendingTimeouts.get(correlationId);
481
+ if (existingTimeout) {
482
+ clearTimeout(existingTimeout);
483
+ this.pendingTimeouts.delete(correlationId);
484
+ }
453
485
  if (!this.pendingMessages.has(correlationId)) return;
454
486
  this.pendingMessages.delete(correlationId);
455
487
  const error = err instanceof Error ? err : new Error("Unknown error");
@@ -525,19 +557,23 @@ var JetstreamClient = class extends ClientProxy {
525
557
  }
526
558
  /** Build event subject — workqueue, broadcast, or ordered. */
527
559
  buildEventSubject(pattern) {
528
- if (pattern.startsWith("broadcast:")) {
529
- return buildBroadcastSubject(pattern.slice("broadcast:".length));
530
- }
531
- if (pattern.startsWith("ordered:")) {
532
- return buildSubject(this.targetName, "ordered", pattern.slice("ordered:".length));
560
+ if (pattern.startsWith("broadcast:" /* Broadcast */)) {
561
+ return buildBroadcastSubject(pattern.slice("broadcast:" /* Broadcast */.length));
562
+ }
563
+ if (pattern.startsWith("ordered:" /* Ordered */)) {
564
+ return buildSubject(
565
+ this.targetName,
566
+ "ordered" /* Ordered */,
567
+ pattern.slice("ordered:" /* Ordered */.length)
568
+ );
533
569
  }
534
- return buildSubject(this.targetName, "ev", pattern);
570
+ return buildSubject(this.targetName, "ev" /* Event */, pattern);
535
571
  }
536
572
  /** Build NATS headers merging custom headers with transport headers. */
537
573
  buildHeaders(customHeaders, transport) {
538
574
  const hdrs = natsHeaders();
539
575
  hdrs.set("x-subject" /* Subject */, transport.subject);
540
- hdrs.set("x-caller-name" /* CallerName */, internalName(this.rootOptions.name));
576
+ hdrs.set("x-caller-name" /* CallerName */, this.callerName);
541
577
  if (transport.correlationId) {
542
578
  hdrs.set("x-correlation-id" /* CorrelationId */, transport.correlationId);
543
579
  }
@@ -563,15 +599,9 @@ var JetstreamClient = class extends ClientProxy {
563
599
  }
564
600
  return { data: rawData, hdrs: null, timeout: void 0, messageId: void 0 };
565
601
  }
566
- isCoreRpcMode() {
567
- return !this.rootOptions.rpc || this.rootOptions.rpc.mode === "core";
568
- }
569
- isJetStreamRpcMode() {
570
- return this.rootOptions.rpc?.mode === "jetstream";
571
- }
572
602
  getRpcTimeout() {
573
603
  if (!this.rootOptions.rpc) return DEFAULT_RPC_TIMEOUT;
574
- const defaultTimeout = this.isJetStreamRpcMode() ? DEFAULT_JETSTREAM_RPC_TIMEOUT : DEFAULT_RPC_TIMEOUT;
604
+ const defaultTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? DEFAULT_JETSTREAM_RPC_TIMEOUT : DEFAULT_RPC_TIMEOUT;
575
605
  return this.rootOptions.rpc.timeout ?? defaultTimeout;
576
606
  }
577
607
  };
@@ -597,6 +627,10 @@ import {
597
627
  NatsError
598
628
  } from "nats";
599
629
  import { defer, from, share, shareReplay, switchMap } from "rxjs";
630
+ var DEFAULT_OPTIONS = {
631
+ maxReconnectAttempts: -1,
632
+ reconnectTimeWait: 1e3
633
+ };
600
634
  var ConnectionProvider = class {
601
635
  constructor(options, eventBus) {
602
636
  this.options = options;
@@ -616,6 +650,7 @@ var ConnectionProvider = class {
616
650
  logger = new Logger2("Jetstream:Connection");
617
651
  connection = null;
618
652
  connectionPromise = null;
653
+ jsClient = null;
619
654
  jsmInstance = null;
620
655
  jsmPromise = null;
621
656
  /**
@@ -654,6 +689,22 @@ var ConnectionProvider = class {
654
689
  });
655
690
  return this.jsmPromise;
656
691
  }
692
+ /**
693
+ * Get a cached JetStream client.
694
+ *
695
+ * Invalidated automatically on reconnect and shutdown so consumers always
696
+ * operate against the live connection.
697
+ *
698
+ * @returns The cached JetStreamClient.
699
+ * @throws Error if the connection has not been established yet.
700
+ */
701
+ getJetStreamClient() {
702
+ if (!this.connection || this.connection.isClosed()) {
703
+ throw new Error("Not connected \u2014 call getConnection() before getJetStreamClient()");
704
+ }
705
+ this.jsClient ??= this.connection.jetstream();
706
+ return this.jsClient;
707
+ }
657
708
  /** Direct access to the raw NATS connection, or `null` if not yet connected. */
658
709
  get unwrap() {
659
710
  return this.connection;
@@ -682,6 +733,7 @@ var ConnectionProvider = class {
682
733
  } finally {
683
734
  this.connection = null;
684
735
  this.connectionPromise = null;
736
+ this.jsClient = null;
685
737
  this.jsmInstance = null;
686
738
  this.jsmPromise = null;
687
739
  }
@@ -691,6 +743,7 @@ var ConnectionProvider = class {
691
743
  const name = internalName(this.options.name);
692
744
  try {
693
745
  const nc = await connect({
746
+ ...DEFAULT_OPTIONS,
694
747
  ...this.options.connectionOptions,
695
748
  servers: this.options.servers,
696
749
  name
@@ -716,6 +769,7 @@ var ConnectionProvider = class {
716
769
  this.eventBus.emit("disconnect" /* Disconnect */);
717
770
  break;
718
771
  case Events2.Reconnect:
772
+ this.jsClient = null;
719
773
  this.jsmInstance = null;
720
774
  this.jsmPromise = null;
721
775
  this.eventBus.emit("reconnect" /* Reconnect */, nc.getServer());
@@ -834,7 +888,7 @@ JetstreamHealthIndicator = __decorateClass([
834
888
  // src/server/strategy.ts
835
889
  import { Server } from "@nestjs/microservices";
836
890
  var JetstreamStrategy = class extends Server {
837
- constructor(options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer) {
891
+ constructor(options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer, ackWaitMap = /* @__PURE__ */ new Map()) {
838
892
  super();
839
893
  this.options = options;
840
894
  this.connection = connection;
@@ -845,6 +899,7 @@ var JetstreamStrategy = class extends Server {
845
899
  this.eventRouter = eventRouter;
846
900
  this.rpcRouter = rpcRouter;
847
901
  this.coreRpcServer = coreRpcServer;
902
+ this.ackWaitMap = ackWaitMap;
848
903
  }
849
904
  transportId = /* @__PURE__ */ Symbol("jetstream-transport");
850
905
  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
@@ -862,17 +917,17 @@ var JetstreamStrategy = class extends Server {
862
917
  }
863
918
  this.started = true;
864
919
  this.patternRegistry.registerHandlers(this.getHandlers());
865
- const streamKinds = this.resolveStreamKinds();
866
- const durableKinds = this.resolveDurableConsumerKinds();
920
+ const { streams: streamKinds, durableConsumers: durableKinds } = this.resolveRequiredKinds();
867
921
  if (streamKinds.length > 0) {
868
922
  await this.streamProvider.ensureStreams(streamKinds);
869
923
  if (durableKinds.length > 0) {
870
924
  const consumers = await this.consumerProvider.ensureConsumers(durableKinds);
925
+ this.populateAckWaitMap(consumers);
871
926
  this.eventRouter.updateMaxDeliverMap(this.buildMaxDeliverMap(consumers));
872
927
  this.messageProvider.start(consumers);
873
928
  }
874
929
  if (this.patternRegistry.hasOrderedHandlers()) {
875
- const orderedStreamName = this.streamProvider.getStreamName("ordered");
930
+ const orderedStreamName = this.streamProvider.getStreamName("ordered" /* Ordered */);
876
931
  await this.messageProvider.startOrdered(
877
932
  orderedStreamName,
878
933
  this.patternRegistry.getOrderedSubjects(),
@@ -882,11 +937,11 @@ var JetstreamStrategy = class extends Server {
882
937
  if (this.patternRegistry.hasEventHandlers() || this.patternRegistry.hasBroadcastHandlers() || this.patternRegistry.hasOrderedHandlers()) {
883
938
  this.eventRouter.start();
884
939
  }
885
- if (this.isJetStreamRpcMode() && this.patternRegistry.hasRpcHandlers()) {
940
+ if (isJetStreamRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
886
941
  this.rpcRouter.start();
887
942
  }
888
943
  }
889
- if (this.isCoreRpcMode() && this.patternRegistry.hasRpcHandlers()) {
944
+ if (isCoreRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
890
945
  await this.coreRpcServer.start();
891
946
  }
892
947
  callback();
@@ -927,36 +982,34 @@ var JetstreamStrategy = class extends Server {
927
982
  getPatternRegistry() {
928
983
  return this.patternRegistry;
929
984
  }
930
- /** Determine which JetStream streams are needed. */
931
- resolveStreamKinds() {
932
- const kinds = [];
985
+ /** Determine which streams and durable consumers are needed. */
986
+ resolveRequiredKinds() {
987
+ const streams = [];
988
+ const durableConsumers = [];
933
989
  if (this.patternRegistry.hasEventHandlers()) {
934
- kinds.push("ev");
935
- }
936
- if (this.patternRegistry.hasOrderedHandlers()) {
937
- kinds.push("ordered");
990
+ streams.push("ev" /* Event */);
991
+ durableConsumers.push("ev" /* Event */);
938
992
  }
939
- if (this.isJetStreamRpcMode() && this.patternRegistry.hasRpcHandlers()) {
940
- kinds.push("cmd");
993
+ if (isJetStreamRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
994
+ streams.push("cmd" /* Command */);
995
+ durableConsumers.push("cmd" /* Command */);
941
996
  }
942
997
  if (this.patternRegistry.hasBroadcastHandlers()) {
943
- kinds.push("broadcast");
998
+ streams.push("broadcast" /* Broadcast */);
999
+ durableConsumers.push("broadcast" /* Broadcast */);
944
1000
  }
945
- return kinds;
946
- }
947
- /** Determine which stream kinds need durable consumers (ordered consumers are ephemeral). */
948
- resolveDurableConsumerKinds() {
949
- const kinds = [];
950
- if (this.patternRegistry.hasEventHandlers()) {
951
- kinds.push("ev");
952
- }
953
- if (this.isJetStreamRpcMode() && this.patternRegistry.hasRpcHandlers()) {
954
- kinds.push("cmd");
1001
+ if (this.patternRegistry.hasOrderedHandlers()) {
1002
+ streams.push("ordered" /* Ordered */);
955
1003
  }
956
- if (this.patternRegistry.hasBroadcastHandlers()) {
957
- kinds.push("broadcast");
1004
+ return { streams, durableConsumers };
1005
+ }
1006
+ /** Populate the shared ack_wait map from actual NATS consumer configs. */
1007
+ populateAckWaitMap(consumers) {
1008
+ for (const [kind, info] of consumers) {
1009
+ if (info.config.ack_wait) {
1010
+ this.ackWaitMap.set(kind, info.config.ack_wait);
1011
+ }
958
1012
  }
959
- return kinds;
960
1013
  }
961
1014
  /** Build max_deliver map from actual NATS consumer configs (not options). */
962
1015
  buildMaxDeliverMap(consumers) {
@@ -970,12 +1023,6 @@ var JetstreamStrategy = class extends Server {
970
1023
  }
971
1024
  return map;
972
1025
  }
973
- isCoreRpcMode() {
974
- return !this.options.rpc || this.options.rpc.mode === "core";
975
- }
976
- isJetStreamRpcMode() {
977
- return this.options.rpc?.mode === "jetstream";
978
- }
979
1026
  };
980
1027
 
981
1028
  // src/server/core-rpc.server.ts
@@ -1021,6 +1068,32 @@ var RpcContext = class extends BaseRpcContext {
1021
1068
  }
1022
1069
  };
1023
1070
 
1071
+ // src/utils/ack-extension.ts
1072
+ var DEFAULT_ACK_EXTENSION_INTERVAL = 5e3;
1073
+ var MIN_ACK_EXTENSION_INTERVAL = 500;
1074
+ var resolveAckExtensionInterval = (config, ackWaitNanos) => {
1075
+ if (config === false || config === void 0) return null;
1076
+ if (typeof config === "number") {
1077
+ if (!Number.isFinite(config) || config <= 0) return null;
1078
+ return Math.floor(config);
1079
+ }
1080
+ if (!ackWaitNanos) return DEFAULT_ACK_EXTENSION_INTERVAL;
1081
+ const interval = Math.floor(ackWaitNanos / 1e6 / 2);
1082
+ return Math.max(interval, MIN_ACK_EXTENSION_INTERVAL);
1083
+ };
1084
+ var startAckExtensionTimer = (msg, interval) => {
1085
+ if (interval === null || interval <= 0) return null;
1086
+ const timer2 = setInterval(() => {
1087
+ try {
1088
+ msg.working();
1089
+ } catch {
1090
+ }
1091
+ }, interval);
1092
+ return () => {
1093
+ clearInterval(timer2);
1094
+ };
1095
+ };
1096
+
1024
1097
  // src/utils/serialize-error.ts
1025
1098
  import { RpcException } from "@nestjs/microservices";
1026
1099
  var serializeError = (err) => {
@@ -1052,7 +1125,12 @@ var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
1052
1125
  subscription?.unsubscribe();
1053
1126
  }
1054
1127
  },
1055
- error: reject,
1128
+ error: (err) => {
1129
+ if (!done) {
1130
+ done = true;
1131
+ reject(err);
1132
+ }
1133
+ },
1056
1134
  complete: () => {
1057
1135
  if (!done) resolve(void 0);
1058
1136
  }
@@ -1102,13 +1180,17 @@ var CoreRpcServer = class {
1102
1180
  }
1103
1181
  /** Handle an incoming Core NATS request. */
1104
1182
  async handleRequest(msg) {
1183
+ if (!msg.reply) {
1184
+ this.logger.warn(`Ignoring fire-and-forget message on RPC subject: ${msg.subject}`);
1185
+ return;
1186
+ }
1105
1187
  const handler = this.patternRegistry.getHandler(msg.subject);
1106
1188
  if (!handler) {
1107
1189
  this.logger.warn(`No handler for Core RPC: ${msg.subject}`);
1108
1190
  this.respondWithError(msg, new Error(`No handler for subject: ${msg.subject}`));
1109
1191
  return;
1110
1192
  }
1111
- this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc");
1193
+ this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc" /* Rpc */);
1112
1194
  let data;
1113
1195
  try {
1114
1196
  data = this.codec.decode(msg.data);
@@ -1166,14 +1248,14 @@ var StreamProvider = class {
1166
1248
  getSubjects(kind) {
1167
1249
  const name = internalName(this.options.name);
1168
1250
  switch (kind) {
1169
- case "ev":
1170
- return [`${name}.ev.>`];
1171
- case "cmd":
1172
- return [`${name}.cmd.>`];
1173
- case "broadcast":
1251
+ case "ev" /* Event */:
1252
+ return [`${name}.${"ev" /* Event */}.>`];
1253
+ case "cmd" /* Command */:
1254
+ return [`${name}.${"cmd" /* Command */}.>`];
1255
+ case "broadcast" /* Broadcast */:
1174
1256
  return ["broadcast.>"];
1175
- case "ordered":
1176
- return [`${name}.ordered.>`];
1257
+ case "ordered" /* Ordered */:
1258
+ return [`${name}.${"ordered" /* Ordered */}.>`];
1177
1259
  }
1178
1260
  }
1179
1261
  /** Ensure a single stream exists, creating or updating as needed. */
@@ -1210,26 +1292,26 @@ var StreamProvider = class {
1210
1292
  /** Get default config for a stream kind. */
1211
1293
  getDefaults(kind) {
1212
1294
  switch (kind) {
1213
- case "ev":
1295
+ case "ev" /* Event */:
1214
1296
  return DEFAULT_EVENT_STREAM_CONFIG;
1215
- case "cmd":
1297
+ case "cmd" /* Command */:
1216
1298
  return DEFAULT_COMMAND_STREAM_CONFIG;
1217
- case "broadcast":
1299
+ case "broadcast" /* Broadcast */:
1218
1300
  return DEFAULT_BROADCAST_STREAM_CONFIG;
1219
- case "ordered":
1301
+ case "ordered" /* Ordered */:
1220
1302
  return DEFAULT_ORDERED_STREAM_CONFIG;
1221
1303
  }
1222
1304
  }
1223
1305
  /** Get user-provided overrides for a stream kind. */
1224
1306
  getOverrides(kind) {
1225
1307
  switch (kind) {
1226
- case "ev":
1308
+ case "ev" /* Event */:
1227
1309
  return this.options.events?.stream ?? {};
1228
- case "cmd":
1310
+ case "cmd" /* Command */:
1229
1311
  return this.options.rpc?.mode === "jetstream" ? this.options.rpc.stream ?? {} : {};
1230
- case "broadcast":
1312
+ case "broadcast" /* Broadcast */:
1231
1313
  return this.options.broadcast?.stream ?? {};
1232
- case "ordered":
1314
+ case "ordered" /* Ordered */:
1233
1315
  return this.options.ordered?.stream ?? {};
1234
1316
  }
1235
1317
  }
@@ -1292,7 +1374,7 @@ var ConsumerProvider = class {
1292
1374
  const serviceName = internalName(this.options.name);
1293
1375
  const defaults = this.getDefaults(kind);
1294
1376
  const overrides = this.getOverrides(kind);
1295
- if (kind === "broadcast") {
1377
+ if (kind === "broadcast" /* Broadcast */) {
1296
1378
  const broadcastPatterns = this.patternRegistry.getBroadcastPatterns();
1297
1379
  if (broadcastPatterns.length === 0) {
1298
1380
  throw new Error("Broadcast consumer requested but no broadcast patterns are registered");
@@ -1314,7 +1396,10 @@ var ConsumerProvider = class {
1314
1396
  filter_subjects: broadcastPatterns
1315
1397
  };
1316
1398
  }
1317
- const filter_subject = kind === "ev" ? `${serviceName}.ev.>` : `${serviceName}.cmd.>`;
1399
+ if (kind !== "ev" /* Event */ && kind !== "cmd" /* Command */) {
1400
+ throw new Error(`Unexpected durable consumer kind: ${kind}`);
1401
+ }
1402
+ const filter_subject = `${serviceName}.${kind}.>`;
1318
1403
  return {
1319
1404
  ...defaults,
1320
1405
  ...overrides,
@@ -1326,26 +1411,26 @@ var ConsumerProvider = class {
1326
1411
  /** Get default config for a consumer kind. */
1327
1412
  getDefaults(kind) {
1328
1413
  switch (kind) {
1329
- case "ev":
1414
+ case "ev" /* Event */:
1330
1415
  return DEFAULT_EVENT_CONSUMER_CONFIG;
1331
- case "cmd":
1416
+ case "cmd" /* Command */:
1332
1417
  return DEFAULT_COMMAND_CONSUMER_CONFIG;
1333
- case "broadcast":
1418
+ case "broadcast" /* Broadcast */:
1334
1419
  return DEFAULT_BROADCAST_CONSUMER_CONFIG;
1335
- case "ordered":
1420
+ case "ordered" /* Ordered */:
1336
1421
  throw new Error("Ordered consumers are ephemeral and should not use durable config");
1337
1422
  }
1338
1423
  }
1339
1424
  /** Get user-provided overrides for a consumer kind. */
1340
1425
  getOverrides(kind) {
1341
1426
  switch (kind) {
1342
- case "ev":
1427
+ case "ev" /* Event */:
1343
1428
  return this.options.events?.consumer ?? {};
1344
- case "cmd":
1429
+ case "cmd" /* Command */:
1345
1430
  return this.options.rpc?.mode === "jetstream" ? this.options.rpc.consumer ?? {} : {};
1346
- case "broadcast":
1431
+ case "broadcast" /* Broadcast */:
1347
1432
  return this.options.broadcast?.consumer ?? {};
1348
- case "ordered":
1433
+ case "ordered" /* Ordered */:
1349
1434
  throw new Error("Ordered consumers are ephemeral and should not use durable config");
1350
1435
  }
1351
1436
  }
@@ -1353,7 +1438,10 @@ var ConsumerProvider = class {
1353
1438
 
1354
1439
  // src/server/infrastructure/message.provider.ts
1355
1440
  import { Logger as Logger7 } from "@nestjs/common";
1356
- import { DeliverPolicy as DeliverPolicy2 } from "nats";
1441
+ import {
1442
+ ConsumerEvents,
1443
+ DeliverPolicy as DeliverPolicy2
1444
+ } from "nats";
1357
1445
  import {
1358
1446
  catchError,
1359
1447
  defer as defer2,
@@ -1366,9 +1454,10 @@ import {
1366
1454
  timer
1367
1455
  } from "rxjs";
1368
1456
  var MessageProvider = class {
1369
- constructor(connection, eventBus) {
1457
+ constructor(connection, eventBus, consumeOptionsMap = /* @__PURE__ */ new Map()) {
1370
1458
  this.connection = connection;
1371
1459
  this.eventBus = eventBus;
1460
+ this.consumeOptionsMap = consumeOptionsMap;
1372
1461
  }
1373
1462
  logger = new Logger7("Jetstream:Message");
1374
1463
  activeIterators = /* @__PURE__ */ new Set();
@@ -1418,6 +1507,7 @@ var MessageProvider = class {
1418
1507
  *
1419
1508
  * @param streamName - JetStream stream to consume from.
1420
1509
  * @param filterSubjects - NATS subjects to filter on.
1510
+ * @param orderedConfig - Optional overrides for ordered consumer options.
1421
1511
  */
1422
1512
  async startOrdered(streamName2, filterSubjects, orderedConfig) {
1423
1513
  const consumerOpts = { filterSubjects };
@@ -1443,6 +1533,11 @@ var MessageProvider = class {
1443
1533
  }
1444
1534
  /** Stop all consumer flows and reinitialize subjects for potential restart. */
1445
1535
  destroy() {
1536
+ if (this.orderedReadyReject) {
1537
+ this.orderedReadyReject(new Error("Destroyed before ordered consumer connected"));
1538
+ this.orderedReadyResolve = null;
1539
+ this.orderedReadyReject = null;
1540
+ }
1446
1541
  this.destroy$.next();
1447
1542
  this.destroy$.complete();
1448
1543
  for (const messages of this.activeIterators) {
@@ -1462,42 +1557,17 @@ var MessageProvider = class {
1462
1557
  /** Create a self-healing consumer flow for a specific kind. */
1463
1558
  createFlow(kind, info) {
1464
1559
  const target$ = this.getTargetSubject(kind);
1465
- let consecutiveFailures = 0;
1466
- let lastRunFailed = false;
1467
- return defer2(() => this.consumeOnce(info, target$)).pipe(
1468
- tap(() => {
1469
- lastRunFailed = false;
1470
- }),
1471
- catchError((err) => {
1472
- consecutiveFailures++;
1473
- lastRunFailed = true;
1474
- this.logger.error(`Consumer ${info.name} error, will restart:`, err);
1475
- this.eventBus.emit(
1476
- "error" /* Error */,
1477
- err instanceof Error ? err : new Error(String(err)),
1478
- "message-provider"
1479
- );
1480
- return EMPTY;
1481
- }),
1482
- repeat({
1483
- delay: () => {
1484
- if (!lastRunFailed) {
1485
- consecutiveFailures = 0;
1486
- }
1487
- const delay = Math.min(100 * Math.pow(2, consecutiveFailures), 3e4);
1488
- this.logger.warn(`Consumer ${info.name} stream ended, restarting in ${delay}ms...`);
1489
- return timer(delay);
1490
- }
1491
- }),
1492
- takeUntil(this.destroy$)
1493
- );
1560
+ return this.createSelfHealingFlow(() => this.consumeOnce(kind, info, target$), info.name);
1494
1561
  }
1495
1562
  /** Single iteration: get consumer -> pull messages -> emit to subject. */
1496
- async consumeOnce(info, target$) {
1497
- const js = (await this.connection.getConnection()).jetstream();
1563
+ async consumeOnce(kind, info, target$) {
1564
+ const js = this.connection.getJetStreamClient();
1498
1565
  const consumer = await js.consumers.get(info.stream_name, info.name);
1499
- const messages = await consumer.consume();
1566
+ const defaults = { idle_heartbeat: 5e3 };
1567
+ const userOptions = this.consumeOptionsMap.get(kind) ?? {};
1568
+ const messages = await consumer.consume({ ...defaults, ...userOptions });
1500
1569
  this.activeIterators.add(messages);
1570
+ this.monitorConsumerHealth(messages, info.name);
1501
1571
  try {
1502
1572
  for await (const msg of messages) {
1503
1573
  target$.next(msg);
@@ -1509,47 +1579,73 @@ var MessageProvider = class {
1509
1579
  /** Get the target subject for a consumer kind. */
1510
1580
  getTargetSubject(kind) {
1511
1581
  switch (kind) {
1512
- case "ev":
1582
+ case "ev" /* Event */:
1513
1583
  return this.eventMessages$;
1514
- case "cmd":
1584
+ case "cmd" /* Command */:
1515
1585
  return this.commandMessages$;
1516
- case "broadcast":
1586
+ case "broadcast" /* Broadcast */:
1517
1587
  return this.broadcastMessages$;
1518
- case "ordered":
1588
+ case "ordered" /* Ordered */:
1519
1589
  return this.orderedMessages$;
1590
+ default: {
1591
+ const _exhaustive = kind;
1592
+ throw new Error(`Unknown stream kind: ${_exhaustive}`);
1593
+ }
1520
1594
  }
1521
1595
  }
1596
+ /** Monitor heartbeats and restart the consumer iterator on prolonged silence. */
1597
+ monitorConsumerHealth(messages, name) {
1598
+ (async () => {
1599
+ for await (const status of await messages.status()) {
1600
+ if (status.type === ConsumerEvents.HeartbeatsMissed && status.data >= 2) {
1601
+ this.logger.warn(`Consumer ${name}: ${status.data} heartbeats missed, restarting`);
1602
+ messages.stop();
1603
+ break;
1604
+ }
1605
+ }
1606
+ })().catch((err) => {
1607
+ if (err) {
1608
+ this.logger.debug(`Consumer ${name} health monitor ended:`, err);
1609
+ }
1610
+ });
1611
+ }
1522
1612
  /** Create a self-healing ordered consumer flow. */
1523
1613
  createOrderedFlow(streamName2, consumerOpts) {
1614
+ return this.createSelfHealingFlow(
1615
+ () => this.consumeOrderedOnce(streamName2, consumerOpts),
1616
+ "ordered" /* Ordered */,
1617
+ (err) => {
1618
+ if (this.orderedReadyReject) {
1619
+ this.orderedReadyReject(err);
1620
+ this.orderedReadyReject = null;
1621
+ this.orderedReadyResolve = null;
1622
+ }
1623
+ }
1624
+ );
1625
+ }
1626
+ /** Shared self-healing flow: defer -> retry with exponential backoff on error/completion. */
1627
+ createSelfHealingFlow(source, label, onFirstError) {
1524
1628
  let consecutiveFailures = 0;
1525
- let lastRunFailed = false;
1526
- return defer2(() => this.consumeOrderedOnce(streamName2, consumerOpts)).pipe(
1629
+ return defer2(source).pipe(
1527
1630
  tap(() => {
1528
- lastRunFailed = false;
1631
+ consecutiveFailures = 0;
1529
1632
  }),
1530
1633
  catchError((err) => {
1531
1634
  consecutiveFailures++;
1532
- lastRunFailed = true;
1533
- this.logger.error("Ordered consumer error, will restart:", err);
1635
+ this.logger.error(`Consumer ${label} error, will restart:`, err);
1534
1636
  this.eventBus.emit(
1535
1637
  "error" /* Error */,
1536
1638
  err instanceof Error ? err : new Error(String(err)),
1537
1639
  "message-provider"
1538
1640
  );
1539
- if (this.orderedReadyReject) {
1540
- this.orderedReadyReject(err);
1541
- this.orderedReadyReject = null;
1542
- this.orderedReadyResolve = null;
1543
- }
1641
+ onFirstError?.(err);
1642
+ onFirstError = void 0;
1544
1643
  return EMPTY;
1545
1644
  }),
1546
1645
  repeat({
1547
1646
  delay: () => {
1548
- if (!lastRunFailed) {
1549
- consecutiveFailures = 0;
1550
- }
1551
1647
  const delay = Math.min(100 * Math.pow(2, consecutiveFailures), 3e4);
1552
- this.logger.warn(`Ordered consumer stream ended, restarting in ${delay}ms...`);
1648
+ this.logger.warn(`Consumer ${label} stream ended, restarting in ${delay}ms...`);
1553
1649
  return timer(delay);
1554
1650
  }
1555
1651
  })
@@ -1557,7 +1653,7 @@ var MessageProvider = class {
1557
1653
  }
1558
1654
  /** Single iteration: create ordered consumer -> iterate messages. */
1559
1655
  async consumeOrderedOnce(streamName2, consumerOpts) {
1560
- const js = (await this.connection.getConnection()).jetstream();
1656
+ const js = this.connection.getJetStreamClient();
1561
1657
  const consumer = await js.consumers.get(streamName2, consumerOpts);
1562
1658
  const messages = await consumer.consume();
1563
1659
  if (this.orderedReadyResolve) {
@@ -1578,12 +1674,20 @@ var MessageProvider = class {
1578
1674
 
1579
1675
  // src/server/routing/pattern-registry.ts
1580
1676
  import { Logger as Logger8 } from "@nestjs/common";
1677
+ var HANDLER_LABELS = {
1678
+ ["broadcast" /* Broadcast */]: "broadcast" /* Broadcast */,
1679
+ ["ordered" /* Ordered */]: "ordered" /* Ordered */,
1680
+ ["ev" /* Event */]: "event" /* Event */,
1681
+ ["cmd" /* Command */]: "rpc" /* Rpc */
1682
+ };
1581
1683
  var PatternRegistry = class {
1582
1684
  constructor(options) {
1583
1685
  this.options = options;
1584
1686
  }
1585
1687
  logger = new Logger8("Jetstream:PatternRegistry");
1586
1688
  registry = /* @__PURE__ */ new Map();
1689
+ // Cached after registerHandlers() — the registry is immutable from that point
1690
+ cachedPatterns = null;
1587
1691
  /**
1588
1692
  * Register all handlers from the NestJS strategy.
1589
1693
  *
@@ -1601,16 +1705,12 @@ var PatternRegistry = class {
1601
1705
  `Handler "${pattern}" cannot be both broadcast and ordered. Use one or the other.`
1602
1706
  );
1603
1707
  }
1604
- let fullSubject;
1605
- if (isBroadcast) {
1606
- fullSubject = buildBroadcastSubject(pattern);
1607
- } else if (isOrdered) {
1608
- fullSubject = buildSubject(serviceName, "ordered", pattern);
1609
- } else if (isEvent) {
1610
- fullSubject = buildSubject(serviceName, "ev", pattern);
1611
- } else {
1612
- fullSubject = buildSubject(serviceName, "cmd", pattern);
1613
- }
1708
+ let kind;
1709
+ if (isBroadcast) kind = "broadcast" /* Broadcast */;
1710
+ else if (isOrdered) kind = "ordered" /* Ordered */;
1711
+ else if (isEvent) kind = "ev" /* Event */;
1712
+ else kind = "cmd" /* Command */;
1713
+ const fullSubject = kind === "broadcast" /* Broadcast */ ? buildBroadcastSubject(pattern) : buildSubject(serviceName, kind, pattern);
1614
1714
  this.registry.set(fullSubject, {
1615
1715
  handler,
1616
1716
  pattern,
@@ -1618,18 +1718,9 @@ var PatternRegistry = class {
1618
1718
  isBroadcast,
1619
1719
  isOrdered
1620
1720
  });
1621
- let kind;
1622
- if (isBroadcast) {
1623
- kind = "broadcast";
1624
- } else if (isOrdered) {
1625
- kind = "ordered";
1626
- } else if (isEvent) {
1627
- kind = "event";
1628
- } else {
1629
- kind = "rpc";
1630
- }
1631
- this.logger.debug(`Registered ${kind}: ${pattern} -> ${fullSubject}`);
1721
+ this.logger.debug(`Registered ${HANDLER_LABELS[kind]}: ${pattern} -> ${fullSubject}`);
1632
1722
  }
1723
+ this.cachedPatterns = this.buildPatternsByKind();
1633
1724
  this.logSummary();
1634
1725
  }
1635
1726
  /** Find handler for a full NATS subject. */
@@ -1638,33 +1729,53 @@ var PatternRegistry = class {
1638
1729
  }
1639
1730
  /** Get all registered broadcast patterns (for consumer filter_subject setup). */
1640
1731
  getBroadcastPatterns() {
1641
- return Array.from(this.registry.values()).filter((r) => r.isBroadcast).map((r) => `broadcast.${r.pattern}`);
1732
+ return this.getPatternsByKind().broadcasts.map((p) => buildBroadcastSubject(p));
1642
1733
  }
1643
- /** Check if any broadcast handlers are registered. */
1644
1734
  hasBroadcastHandlers() {
1645
- return Array.from(this.registry.values()).some((r) => r.isBroadcast);
1735
+ return this.getPatternsByKind().broadcasts.length > 0;
1646
1736
  }
1647
- /** Check if any RPC (command) handlers are registered. */
1648
1737
  hasRpcHandlers() {
1649
- return Array.from(this.registry.values()).some(
1650
- (r) => !r.isEvent && !r.isBroadcast && !r.isOrdered
1651
- );
1738
+ return this.getPatternsByKind().commands.length > 0;
1652
1739
  }
1653
- /** Check if any workqueue event handlers are registered. */
1654
1740
  hasEventHandlers() {
1655
- return Array.from(this.registry.values()).some((r) => r.isEvent && !r.isBroadcast);
1741
+ return this.getPatternsByKind().events.length > 0;
1656
1742
  }
1657
- /** Check if any ordered event handlers are registered. */
1658
1743
  hasOrderedHandlers() {
1659
- return Array.from(this.registry.values()).some((r) => r.isOrdered);
1744
+ return this.getPatternsByKind().ordered.length > 0;
1660
1745
  }
1661
1746
  /** Get fully-qualified NATS subjects for ordered handlers. */
1662
1747
  getOrderedSubjects() {
1663
- const name = internalName(this.options.name);
1664
- return Array.from(this.registry.values()).filter((r) => r.isOrdered).map((r) => `${name}.ordered.${r.pattern}`);
1748
+ return this.getPatternsByKind().ordered.map(
1749
+ (p) => buildSubject(this.options.name, "ordered" /* Ordered */, p)
1750
+ );
1665
1751
  }
1666
- /** Get patterns grouped by kind. */
1752
+ /** Get patterns grouped by kind (cached after registration). */
1667
1753
  getPatternsByKind() {
1754
+ const patterns = this.cachedPatterns ?? this.buildPatternsByKind();
1755
+ return {
1756
+ events: [...patterns.events],
1757
+ commands: [...patterns.commands],
1758
+ broadcasts: [...patterns.broadcasts],
1759
+ ordered: [...patterns.ordered]
1760
+ };
1761
+ }
1762
+ /** Normalize a full NATS subject back to the user-facing pattern. */
1763
+ normalizeSubject(subject) {
1764
+ const name = internalName(this.options.name);
1765
+ const prefixes = [
1766
+ `${name}.${"cmd" /* Command */}.`,
1767
+ `${name}.${"ev" /* Event */}.`,
1768
+ `${name}.${"ordered" /* Ordered */}.`,
1769
+ `${"broadcast" /* Broadcast */}.`
1770
+ ];
1771
+ for (const prefix of prefixes) {
1772
+ if (subject.startsWith(prefix)) {
1773
+ return subject.slice(prefix.length);
1774
+ }
1775
+ }
1776
+ return subject;
1777
+ }
1778
+ buildPatternsByKind() {
1668
1779
  const events = [];
1669
1780
  const commands = [];
1670
1781
  const broadcasts = [];
@@ -1677,18 +1788,6 @@ var PatternRegistry = class {
1677
1788
  }
1678
1789
  return { events, commands, broadcasts, ordered };
1679
1790
  }
1680
- /** Normalize a full NATS subject back to the user-facing pattern. */
1681
- normalizeSubject(subject) {
1682
- const name = internalName(this.options.name);
1683
- const prefixes = [`${name}.cmd.`, `${name}.ev.`, `${name}.ordered.`, "broadcast."];
1684
- for (const prefix of prefixes) {
1685
- if (subject.startsWith(prefix)) {
1686
- return subject.slice(prefix.length);
1687
- }
1688
- }
1689
- return subject;
1690
- }
1691
- /** Log a summary of all registered handlers. */
1692
1791
  logSummary() {
1693
1792
  const { events, commands, broadcasts, ordered } = this.getPatternsByKind();
1694
1793
  const parts = [
@@ -1714,12 +1813,14 @@ import {
1714
1813
  mergeMap
1715
1814
  } from "rxjs";
1716
1815
  var EventRouter = class {
1717
- constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig) {
1816
+ constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig, processingConfig, ackWaitMap) {
1718
1817
  this.messageProvider = messageProvider;
1719
1818
  this.patternRegistry = patternRegistry;
1720
1819
  this.codec = codec;
1721
1820
  this.eventBus = eventBus;
1722
1821
  this.deadLetterConfig = deadLetterConfig;
1822
+ this.processingConfig = processingConfig;
1823
+ this.ackWaitMap = ackWaitMap;
1723
1824
  }
1724
1825
  logger = new Logger9("Jetstream:EventRouter");
1725
1826
  subscriptions = [];
@@ -1733,10 +1834,10 @@ var EventRouter = class {
1733
1834
  }
1734
1835
  /** Start routing event, broadcast, and ordered messages to handlers. */
1735
1836
  start() {
1736
- this.subscribeToStream(this.messageProvider.events$, "workqueue");
1737
- this.subscribeToStream(this.messageProvider.broadcasts$, "broadcast");
1837
+ this.subscribeToStream(this.messageProvider.events$, "ev" /* Event */);
1838
+ this.subscribeToStream(this.messageProvider.broadcasts$, "broadcast" /* Broadcast */);
1738
1839
  if (this.patternRegistry.hasOrderedHandlers()) {
1739
- this.subscribeToStream(this.messageProvider.ordered$, "ordered", true);
1840
+ this.subscribeToStream(this.messageProvider.ordered$, "ordered" /* Ordered */);
1740
1841
  }
1741
1842
  }
1742
1843
  /** Stop routing and unsubscribe from all streams. */
@@ -1747,38 +1848,71 @@ var EventRouter = class {
1747
1848
  this.subscriptions.length = 0;
1748
1849
  }
1749
1850
  /** Subscribe to a message stream and route each message. */
1750
- subscribeToStream(stream$, label, isOrdered = false) {
1751
- const route = (msg) => defer3(() => isOrdered ? this.handleOrdered(msg) : this.handle(msg)).pipe(
1851
+ subscribeToStream(stream$, kind) {
1852
+ const isOrdered = kind === "ordered" /* Ordered */;
1853
+ const ackExtensionInterval = isOrdered ? null : resolveAckExtensionInterval(this.getAckExtensionConfig(kind), this.ackWaitMap?.get(kind));
1854
+ const concurrency = this.getConcurrency(kind);
1855
+ const route = (msg) => defer3(
1856
+ () => isOrdered ? this.handleOrdered(msg) : this.handle(msg, ackExtensionInterval)
1857
+ ).pipe(
1752
1858
  catchError2((err) => {
1753
- this.logger.error(`Unexpected error in ${label} event router`, err);
1859
+ this.logger.error(`Unexpected error in ${kind} event router`, err);
1754
1860
  return EMPTY2;
1755
1861
  })
1756
1862
  );
1757
- const subscription = stream$.pipe(isOrdered ? concatMap(route) : mergeMap(route)).subscribe();
1863
+ const subscription = stream$.pipe(isOrdered ? concatMap(route) : mergeMap(route, concurrency)).subscribe();
1758
1864
  this.subscriptions.push(subscription);
1759
1865
  }
1866
+ getConcurrency(kind) {
1867
+ if (kind === "ev" /* Event */) return this.processingConfig?.events?.concurrency;
1868
+ if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.concurrency;
1869
+ return void 0;
1870
+ }
1871
+ getAckExtensionConfig(kind) {
1872
+ if (kind === "ev" /* Event */) return this.processingConfig?.events?.ackExtension;
1873
+ if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.ackExtension;
1874
+ return void 0;
1875
+ }
1760
1876
  /** Handle a single event message: decode -> execute handler -> ack/nak. */
1761
- handle(msg) {
1877
+ handle(msg, ackExtensionInterval) {
1878
+ const resolved = this.decodeMessage(msg);
1879
+ if (!resolved) return EMPTY2;
1880
+ return from2(
1881
+ this.executeHandler(resolved.handler, resolved.data, resolved.ctx, msg, ackExtensionInterval)
1882
+ );
1883
+ }
1884
+ /** Handle an ordered message: decode -> execute handler -> no ack/nak. */
1885
+ handleOrdered(msg) {
1886
+ const resolved = this.decodeMessage(msg, true);
1887
+ if (!resolved) return EMPTY2;
1888
+ return from2(
1889
+ unwrapResult(resolved.handler(resolved.data, resolved.ctx)).catch((err) => {
1890
+ this.logger.error(`Ordered handler error (${msg.subject}):`, err);
1891
+ })
1892
+ );
1893
+ }
1894
+ /** Resolve handler, decode payload, and build context. Returns null on failure. */
1895
+ decodeMessage(msg, isOrdered = false) {
1762
1896
  const handler = this.patternRegistry.getHandler(msg.subject);
1763
1897
  if (!handler) {
1764
- msg.term(`No handler for event: ${msg.subject}`);
1765
- this.logger.error(`No handler for event subject: ${msg.subject}`);
1766
- return EMPTY2;
1898
+ if (!isOrdered) msg.term(`No handler for event: ${msg.subject}`);
1899
+ this.logger.error(`No handler for subject: ${msg.subject}`);
1900
+ return null;
1767
1901
  }
1768
1902
  let data;
1769
1903
  try {
1770
1904
  data = this.codec.decode(msg.data);
1771
1905
  } catch (err) {
1772
- msg.term("Decode error");
1906
+ if (!isOrdered) msg.term("Decode error");
1773
1907
  this.logger.error(`Decode error for ${msg.subject}:`, err);
1774
- return EMPTY2;
1908
+ return null;
1775
1909
  }
1776
- this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "event");
1777
- const ctx = new RpcContext([msg]);
1778
- return from2(this.executeHandler(handler, data, ctx, msg));
1910
+ this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "event" /* Event */);
1911
+ return { handler, data, ctx: new RpcContext([msg]) };
1779
1912
  }
1780
1913
  /** Execute handler, then ack on success or nak/dead-letter on failure. */
1781
- async executeHandler(handler, data, ctx, msg) {
1914
+ async executeHandler(handler, data, ctx, msg, ackExtensionInterval) {
1915
+ const stopAckExtension = startAckExtensionTimer(msg, ackExtensionInterval);
1782
1916
  try {
1783
1917
  await unwrapResult(handler(data, ctx));
1784
1918
  msg.ack();
@@ -1789,30 +1923,10 @@ var EventRouter = class {
1789
1923
  } else {
1790
1924
  msg.nak();
1791
1925
  }
1926
+ } finally {
1927
+ stopAckExtension?.();
1792
1928
  }
1793
1929
  }
1794
- /** Handle an ordered message: decode -> execute handler -> no ack/nak. */
1795
- handleOrdered(msg) {
1796
- const handler = this.patternRegistry.getHandler(msg.subject);
1797
- if (!handler) {
1798
- this.logger.error(`No handler for ordered subject: ${msg.subject}`);
1799
- return EMPTY2;
1800
- }
1801
- let data;
1802
- try {
1803
- data = this.codec.decode(msg.data);
1804
- } catch (err) {
1805
- this.logger.error(`Decode error for ordered ${msg.subject}:`, err);
1806
- return EMPTY2;
1807
- }
1808
- this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "event");
1809
- const ctx = new RpcContext([msg]);
1810
- return from2(
1811
- unwrapResult(handler(data, ctx)).catch((err) => {
1812
- this.logger.error(`Ordered handler error (${msg.subject}):`, err);
1813
- })
1814
- );
1815
- }
1816
1930
  /** Check if the message has exhausted all delivery attempts. */
1817
1931
  isDeadLetter(msg) {
1818
1932
  if (!this.deadLetterConfig) return false;
@@ -1833,7 +1947,10 @@ var EventRouter = class {
1833
1947
  timestamp: new Date(msg.info.timestampNanos / 1e6).toISOString()
1834
1948
  };
1835
1949
  this.eventBus.emit("deadLetter" /* DeadLetter */, info);
1836
- if (!this.deadLetterConfig) return;
1950
+ if (!this.deadLetterConfig) {
1951
+ msg.term("Dead letter config unavailable");
1952
+ return;
1953
+ }
1837
1954
  try {
1838
1955
  await this.deadLetterConfig.onDeadLetter(info);
1839
1956
  msg.term("Dead letter processed");
@@ -1849,17 +1966,31 @@ import { Logger as Logger10 } from "@nestjs/common";
1849
1966
  import { headers } from "nats";
1850
1967
  import { catchError as catchError3, defer as defer4, EMPTY as EMPTY3, from as from3, mergeMap as mergeMap2 } from "rxjs";
1851
1968
  var RpcRouter = class {
1852
- constructor(messageProvider, patternRegistry, connection, codec, eventBus, timeout) {
1969
+ constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap) {
1853
1970
  this.messageProvider = messageProvider;
1854
1971
  this.patternRegistry = patternRegistry;
1855
1972
  this.connection = connection;
1856
1973
  this.codec = codec;
1857
1974
  this.eventBus = eventBus;
1858
- this.timeout = timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT;
1975
+ this.rpcOptions = rpcOptions;
1976
+ this.ackWaitMap = ackWaitMap;
1977
+ this.timeout = rpcOptions?.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT;
1978
+ this.concurrency = rpcOptions?.concurrency;
1859
1979
  }
1860
1980
  logger = new Logger10("Jetstream:RpcRouter");
1861
1981
  timeout;
1982
+ concurrency;
1983
+ resolvedAckExtensionInterval;
1862
1984
  subscription = null;
1985
+ /** Lazily resolve the ack extension interval (needs ackWaitMap populated at runtime). */
1986
+ get ackExtensionInterval() {
1987
+ if (this.resolvedAckExtensionInterval !== void 0) return this.resolvedAckExtensionInterval;
1988
+ this.resolvedAckExtensionInterval = resolveAckExtensionInterval(
1989
+ this.rpcOptions?.ackExtension,
1990
+ this.ackWaitMap?.get("cmd" /* Command */)
1991
+ );
1992
+ return this.resolvedAckExtensionInterval;
1993
+ }
1863
1994
  /** Start routing command messages to handlers. */
1864
1995
  start() {
1865
1996
  this.subscription = this.messageProvider.commands$.pipe(
@@ -1869,7 +2000,8 @@ var RpcRouter = class {
1869
2000
  this.logger.error("Unexpected error in RPC router", err);
1870
2001
  return EMPTY3;
1871
2002
  })
1872
- )
2003
+ ),
2004
+ this.concurrency
1873
2005
  )
1874
2006
  ).subscribe();
1875
2007
  }
@@ -1901,7 +2033,7 @@ var RpcRouter = class {
1901
2033
  this.logger.error(`Decode error for RPC ${msg.subject}:`, err);
1902
2034
  return EMPTY3;
1903
2035
  }
1904
- this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc");
2036
+ this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc" /* Rpc */);
1905
2037
  return from3(this.executeHandler(handler, data, msg, replyTo, correlationId));
1906
2038
  }
1907
2039
  /** Execute handler, publish response, settle message. */
@@ -1911,9 +2043,11 @@ var RpcRouter = class {
1911
2043
  const hdrs = headers();
1912
2044
  hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
1913
2045
  let settled = false;
2046
+ const stopAckExtension = startAckExtensionTimer(msg, this.ackExtensionInterval);
1914
2047
  const timeoutId = setTimeout(() => {
1915
2048
  if (settled) return;
1916
2049
  settled = true;
2050
+ stopAckExtension?.();
1917
2051
  this.logger.error(`RPC timeout (${this.timeout}ms): ${msg.subject}`);
1918
2052
  this.eventBus.emit("rpcTimeout" /* RpcTimeout */, msg.subject, correlationId);
1919
2053
  msg.term("Handler timeout");
@@ -1923,6 +2057,7 @@ var RpcRouter = class {
1923
2057
  if (settled) return;
1924
2058
  settled = true;
1925
2059
  clearTimeout(timeoutId);
2060
+ stopAckExtension?.();
1926
2061
  msg.ack();
1927
2062
  try {
1928
2063
  nc.publish(replyTo, this.codec.encode(result), { headers: hdrs });
@@ -1933,6 +2068,7 @@ var RpcRouter = class {
1933
2068
  if (settled) return;
1934
2069
  settled = true;
1935
2070
  clearTimeout(timeoutId);
2071
+ stopAckExtension?.();
1936
2072
  try {
1937
2073
  hdrs.set("x-error" /* Error */, "true");
1938
2074
  nc.publish(replyTo, this.codec.encode(serializeError(err)), { headers: hdrs });
@@ -1956,7 +2092,7 @@ var ShutdownManager = class {
1956
2092
  /**
1957
2093
  * Execute the full shutdown sequence.
1958
2094
  *
1959
- * @param strategy Optional strategy to close (stops consumers and subscriptions).
2095
+ * @param strategy Optional stoppable to close (stops consumers and subscriptions).
1960
2096
  */
1961
2097
  async shutdown(strategy) {
1962
2098
  this.eventBus.emit("shutdownStart" /* ShutdownStart */);
@@ -1979,6 +2115,7 @@ var ShutdownManager = class {
1979
2115
  };
1980
2116
 
1981
2117
  // src/jetstream.module.ts
2118
+ var JETSTREAM_ACK_WAIT_MAP = /* @__PURE__ */ Symbol("JETSTREAM_ACK_WAIT_MAP");
1982
2119
  var JetstreamModule = class {
1983
2120
  constructor(shutdownManager, strategy) {
1984
2121
  this.shutdownManager = shutdownManager;
@@ -2163,13 +2300,26 @@ var JetstreamModule = class {
2163
2300
  return new ConsumerProvider(options, connection, streamProvider, patternRegistry);
2164
2301
  }
2165
2302
  },
2303
+ // Shared ack_wait map — populated by strategy after ensureConsumers()
2304
+ {
2305
+ provide: JETSTREAM_ACK_WAIT_MAP,
2306
+ useFactory: () => /* @__PURE__ */ new Map()
2307
+ },
2166
2308
  // MessageProvider — pull-based message consumption
2167
2309
  {
2168
2310
  provide: MessageProvider,
2169
2311
  inject: [JETSTREAM_OPTIONS, JETSTREAM_CONNECTION, JETSTREAM_EVENT_BUS],
2170
2312
  useFactory: (options, connection, eventBus) => {
2171
2313
  if (options.consumer === false) return null;
2172
- return new MessageProvider(connection, eventBus);
2314
+ const consumeOptionsMap = /* @__PURE__ */ new Map();
2315
+ if (options.events?.consume)
2316
+ consumeOptionsMap.set("ev" /* Event */, options.events.consume);
2317
+ if (options.broadcast?.consume)
2318
+ consumeOptionsMap.set("broadcast" /* Broadcast */, options.broadcast.consume);
2319
+ if (options.rpc?.mode === "jetstream" && options.rpc.consume) {
2320
+ consumeOptionsMap.set("cmd" /* Command */, options.rpc.consume);
2321
+ }
2322
+ return new MessageProvider(connection, eventBus, consumeOptionsMap);
2173
2323
  }
2174
2324
  },
2175
2325
  // EventRouter — routes event and broadcast messages to handlers
@@ -2180,20 +2330,33 @@ var JetstreamModule = class {
2180
2330
  MessageProvider,
2181
2331
  PatternRegistry,
2182
2332
  JETSTREAM_CODEC,
2183
- JETSTREAM_EVENT_BUS
2333
+ JETSTREAM_EVENT_BUS,
2334
+ JETSTREAM_ACK_WAIT_MAP
2184
2335
  ],
2185
- useFactory: (options, messageProvider, patternRegistry, codec, eventBus) => {
2336
+ useFactory: (options, messageProvider, patternRegistry, codec, eventBus, ackWaitMap) => {
2186
2337
  if (options.consumer === false) return null;
2187
2338
  const deadLetterConfig = options.onDeadLetter ? {
2188
2339
  maxDeliverByStream: /* @__PURE__ */ new Map(),
2189
2340
  onDeadLetter: options.onDeadLetter
2190
2341
  } : void 0;
2342
+ const processingConfig = {
2343
+ events: {
2344
+ concurrency: options.events?.concurrency,
2345
+ ackExtension: options.events?.ackExtension
2346
+ },
2347
+ broadcast: {
2348
+ concurrency: options.broadcast?.concurrency,
2349
+ ackExtension: options.broadcast?.ackExtension
2350
+ }
2351
+ };
2191
2352
  return new EventRouter(
2192
2353
  messageProvider,
2193
2354
  patternRegistry,
2194
2355
  codec,
2195
2356
  eventBus,
2196
- deadLetterConfig
2357
+ deadLetterConfig,
2358
+ processingConfig,
2359
+ ackWaitMap
2197
2360
  );
2198
2361
  }
2199
2362
  },
@@ -2206,18 +2369,24 @@ var JetstreamModule = class {
2206
2369
  PatternRegistry,
2207
2370
  JETSTREAM_CONNECTION,
2208
2371
  JETSTREAM_CODEC,
2209
- JETSTREAM_EVENT_BUS
2372
+ JETSTREAM_EVENT_BUS,
2373
+ JETSTREAM_ACK_WAIT_MAP
2210
2374
  ],
2211
- useFactory: (options, messageProvider, patternRegistry, connection, codec, eventBus) => {
2375
+ useFactory: (options, messageProvider, patternRegistry, connection, codec, eventBus, ackWaitMap) => {
2212
2376
  if (options.consumer === false) return null;
2213
- const timeout = options.rpc?.mode === "jetstream" ? options.rpc.timeout : void 0;
2377
+ const rpcOptions = options.rpc?.mode === "jetstream" ? {
2378
+ timeout: options.rpc.timeout,
2379
+ concurrency: options.rpc.concurrency,
2380
+ ackExtension: options.rpc.ackExtension
2381
+ } : void 0;
2214
2382
  return new RpcRouter(
2215
2383
  messageProvider,
2216
2384
  patternRegistry,
2217
2385
  connection,
2218
2386
  codec,
2219
2387
  eventBus,
2220
- timeout
2388
+ rpcOptions,
2389
+ ackWaitMap
2221
2390
  );
2222
2391
  }
2223
2392
  },
@@ -2248,9 +2417,10 @@ var JetstreamModule = class {
2248
2417
  MessageProvider,
2249
2418
  EventRouter,
2250
2419
  RpcRouter,
2251
- CoreRpcServer
2420
+ CoreRpcServer,
2421
+ JETSTREAM_ACK_WAIT_MAP
2252
2422
  ],
2253
- useFactory: (options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer) => {
2423
+ useFactory: (options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer, ackWaitMap) => {
2254
2424
  if (options.consumer === false) return null;
2255
2425
  return new JetstreamStrategy(
2256
2426
  options,
@@ -2261,7 +2431,8 @@ var JetstreamModule = class {
2261
2431
  messageProvider,
2262
2432
  eventRouter,
2263
2433
  rpcRouter,
2264
- coreRpcServer
2434
+ coreRpcServer,
2435
+ ackWaitMap
2265
2436
  );
2266
2437
  }
2267
2438
  }
@@ -2341,8 +2512,13 @@ export {
2341
2512
  JetstreamRecordBuilder,
2342
2513
  JetstreamStrategy,
2343
2514
  JsonCodec,
2515
+ MessageKind,
2516
+ PatternPrefix,
2344
2517
  RpcContext,
2518
+ StreamKind,
2345
2519
  TransportEvent,
2346
2520
  getClientToken,
2521
+ isCoreRpcMode,
2522
+ isJetStreamRpcMode,
2347
2523
  toNanos
2348
2524
  };