@horizon-republic/nestjs-jetstream 2.5.1 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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());
@@ -755,6 +809,23 @@ var EventBus = class {
755
809
  emit(event, ...args) {
756
810
  const hook = this.hooks[event];
757
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) {
758
829
  try {
759
830
  const result = hook(...args);
760
831
  if (result && typeof result.catch === "function") {
@@ -834,7 +905,7 @@ JetstreamHealthIndicator = __decorateClass([
834
905
  // src/server/strategy.ts
835
906
  import { Server } from "@nestjs/microservices";
836
907
  var JetstreamStrategy = class extends Server {
837
- constructor(options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer) {
908
+ constructor(options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer, ackWaitMap = /* @__PURE__ */ new Map()) {
838
909
  super();
839
910
  this.options = options;
840
911
  this.connection = connection;
@@ -845,6 +916,7 @@ var JetstreamStrategy = class extends Server {
845
916
  this.eventRouter = eventRouter;
846
917
  this.rpcRouter = rpcRouter;
847
918
  this.coreRpcServer = coreRpcServer;
919
+ this.ackWaitMap = ackWaitMap;
848
920
  }
849
921
  transportId = /* @__PURE__ */ Symbol("jetstream-transport");
850
922
  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
@@ -862,17 +934,17 @@ var JetstreamStrategy = class extends Server {
862
934
  }
863
935
  this.started = true;
864
936
  this.patternRegistry.registerHandlers(this.getHandlers());
865
- const streamKinds = this.resolveStreamKinds();
866
- const durableKinds = this.resolveDurableConsumerKinds();
937
+ const { streams: streamKinds, durableConsumers: durableKinds } = this.resolveRequiredKinds();
867
938
  if (streamKinds.length > 0) {
868
939
  await this.streamProvider.ensureStreams(streamKinds);
869
940
  if (durableKinds.length > 0) {
870
941
  const consumers = await this.consumerProvider.ensureConsumers(durableKinds);
942
+ this.populateAckWaitMap(consumers);
871
943
  this.eventRouter.updateMaxDeliverMap(this.buildMaxDeliverMap(consumers));
872
944
  this.messageProvider.start(consumers);
873
945
  }
874
946
  if (this.patternRegistry.hasOrderedHandlers()) {
875
- const orderedStreamName = this.streamProvider.getStreamName("ordered");
947
+ const orderedStreamName = this.streamProvider.getStreamName("ordered" /* Ordered */);
876
948
  await this.messageProvider.startOrdered(
877
949
  orderedStreamName,
878
950
  this.patternRegistry.getOrderedSubjects(),
@@ -882,11 +954,11 @@ var JetstreamStrategy = class extends Server {
882
954
  if (this.patternRegistry.hasEventHandlers() || this.patternRegistry.hasBroadcastHandlers() || this.patternRegistry.hasOrderedHandlers()) {
883
955
  this.eventRouter.start();
884
956
  }
885
- if (this.isJetStreamRpcMode() && this.patternRegistry.hasRpcHandlers()) {
886
- this.rpcRouter.start();
957
+ if (isJetStreamRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
958
+ await this.rpcRouter.start();
887
959
  }
888
960
  }
889
- if (this.isCoreRpcMode() && this.patternRegistry.hasRpcHandlers()) {
961
+ if (isCoreRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
890
962
  await this.coreRpcServer.start();
891
963
  }
892
964
  callback();
@@ -927,36 +999,34 @@ var JetstreamStrategy = class extends Server {
927
999
  getPatternRegistry() {
928
1000
  return this.patternRegistry;
929
1001
  }
930
- /** Determine which JetStream streams are needed. */
931
- resolveStreamKinds() {
932
- const kinds = [];
1002
+ /** Determine which streams and durable consumers are needed. */
1003
+ resolveRequiredKinds() {
1004
+ const streams = [];
1005
+ const durableConsumers = [];
933
1006
  if (this.patternRegistry.hasEventHandlers()) {
934
- kinds.push("ev");
1007
+ streams.push("ev" /* Event */);
1008
+ durableConsumers.push("ev" /* Event */);
935
1009
  }
936
- if (this.patternRegistry.hasOrderedHandlers()) {
937
- kinds.push("ordered");
938
- }
939
- if (this.isJetStreamRpcMode() && this.patternRegistry.hasRpcHandlers()) {
940
- kinds.push("cmd");
1010
+ if (isJetStreamRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
1011
+ streams.push("cmd" /* Command */);
1012
+ durableConsumers.push("cmd" /* Command */);
941
1013
  }
942
1014
  if (this.patternRegistry.hasBroadcastHandlers()) {
943
- kinds.push("broadcast");
944
- }
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");
1015
+ streams.push("broadcast" /* Broadcast */);
1016
+ durableConsumers.push("broadcast" /* Broadcast */);
952
1017
  }
953
- if (this.isJetStreamRpcMode() && this.patternRegistry.hasRpcHandlers()) {
954
- kinds.push("cmd");
1018
+ if (this.patternRegistry.hasOrderedHandlers()) {
1019
+ streams.push("ordered" /* Ordered */);
955
1020
  }
956
- if (this.patternRegistry.hasBroadcastHandlers()) {
957
- kinds.push("broadcast");
1021
+ return { streams, durableConsumers };
1022
+ }
1023
+ /** Populate the shared ack_wait map from actual NATS consumer configs. */
1024
+ populateAckWaitMap(consumers) {
1025
+ for (const [kind, info] of consumers) {
1026
+ if (info.config.ack_wait) {
1027
+ this.ackWaitMap.set(kind, info.config.ack_wait);
1028
+ }
958
1029
  }
959
- return kinds;
960
1030
  }
961
1031
  /** Build max_deliver map from actual NATS consumer configs (not options). */
962
1032
  buildMaxDeliverMap(consumers) {
@@ -970,12 +1040,6 @@ var JetstreamStrategy = class extends Server {
970
1040
  }
971
1041
  return map;
972
1042
  }
973
- isCoreRpcMode() {
974
- return !this.options.rpc || this.options.rpc.mode === "core";
975
- }
976
- isJetStreamRpcMode() {
977
- return this.options.rpc?.mode === "jetstream";
978
- }
979
1043
  };
980
1044
 
981
1045
  // src/server/core-rpc.server.ts
@@ -985,6 +1049,13 @@ import { headers as natsHeaders2 } from "nats";
985
1049
  // src/context/rpc.context.ts
986
1050
  import { BaseRpcContext } from "@nestjs/microservices";
987
1051
  var RpcContext = class extends BaseRpcContext {
1052
+ _shouldRetry = false;
1053
+ _retryDelay;
1054
+ _shouldTerminate = false;
1055
+ _terminateReason;
1056
+ // ---------------------------------------------------------------------------
1057
+ // Message accessors
1058
+ // ---------------------------------------------------------------------------
988
1059
  /**
989
1060
  * Get the underlying NATS message.
990
1061
  *
@@ -1019,6 +1090,122 @@ var RpcContext = class extends BaseRpcContext {
1019
1090
  isJetStream() {
1020
1091
  return "ack" in this.args[0];
1021
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
+ }
1183
+ };
1184
+
1185
+ // src/utils/ack-extension.ts
1186
+ var DEFAULT_ACK_EXTENSION_INTERVAL = 5e3;
1187
+ var MIN_ACK_EXTENSION_INTERVAL = 500;
1188
+ var resolveAckExtensionInterval = (config, ackWaitNanos) => {
1189
+ if (config === false || config === void 0) return null;
1190
+ if (typeof config === "number") {
1191
+ if (!Number.isFinite(config) || config <= 0) return null;
1192
+ return Math.floor(config);
1193
+ }
1194
+ if (!ackWaitNanos) return DEFAULT_ACK_EXTENSION_INTERVAL;
1195
+ const interval = Math.floor(ackWaitNanos / 1e6 / 2);
1196
+ return Math.max(interval, MIN_ACK_EXTENSION_INTERVAL);
1197
+ };
1198
+ var startAckExtensionTimer = (msg, interval) => {
1199
+ if (interval === null || interval <= 0) return null;
1200
+ const timer2 = setInterval(() => {
1201
+ try {
1202
+ msg.working();
1203
+ } catch {
1204
+ }
1205
+ }, interval);
1206
+ return () => {
1207
+ clearInterval(timer2);
1208
+ };
1022
1209
  };
1023
1210
 
1024
1211
  // src/utils/serialize-error.ts
@@ -1031,15 +1218,20 @@ var serializeError = (err) => {
1031
1218
 
1032
1219
  // src/utils/unwrap-result.ts
1033
1220
  import { isObservable } from "rxjs";
1034
- var unwrapResult = async (result) => {
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;
1035
1226
  if (isObservable(result)) {
1036
1227
  return subscribeToFirst(result);
1037
1228
  }
1038
- const resolved = await result;
1039
- if (isObservable(resolved)) {
1040
- return subscribeToFirst(resolved);
1229
+ if (typeof result.then === "function") {
1230
+ return result.then(
1231
+ (resolved) => isObservable(resolved) ? subscribeToFirst(resolved) : resolved
1232
+ );
1041
1233
  }
1042
- return resolved;
1234
+ return Promise.resolve(result);
1043
1235
  };
1044
1236
  var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
1045
1237
  let done = false;
@@ -1107,13 +1299,17 @@ var CoreRpcServer = class {
1107
1299
  }
1108
1300
  /** Handle an incoming Core NATS request. */
1109
1301
  async handleRequest(msg) {
1302
+ if (!msg.reply) {
1303
+ this.logger.warn(`Ignoring fire-and-forget message on RPC subject: ${msg.subject}`);
1304
+ return;
1305
+ }
1110
1306
  const handler = this.patternRegistry.getHandler(msg.subject);
1111
1307
  if (!handler) {
1112
1308
  this.logger.warn(`No handler for Core RPC: ${msg.subject}`);
1113
1309
  this.respondWithError(msg, new Error(`No handler for subject: ${msg.subject}`));
1114
1310
  return;
1115
1311
  }
1116
- this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc");
1312
+ this.eventBus.emitMessageRouted(msg.subject, "rpc" /* Rpc */);
1117
1313
  let data;
1118
1314
  try {
1119
1315
  data = this.codec.decode(msg.data);
@@ -1171,14 +1367,14 @@ var StreamProvider = class {
1171
1367
  getSubjects(kind) {
1172
1368
  const name = internalName(this.options.name);
1173
1369
  switch (kind) {
1174
- case "ev":
1175
- return [`${name}.ev.>`];
1176
- case "cmd":
1177
- return [`${name}.cmd.>`];
1178
- case "broadcast":
1370
+ case "ev" /* Event */:
1371
+ return [`${name}.${"ev" /* Event */}.>`];
1372
+ case "cmd" /* Command */:
1373
+ return [`${name}.${"cmd" /* Command */}.>`];
1374
+ case "broadcast" /* Broadcast */:
1179
1375
  return ["broadcast.>"];
1180
- case "ordered":
1181
- return [`${name}.ordered.>`];
1376
+ case "ordered" /* Ordered */:
1377
+ return [`${name}.${"ordered" /* Ordered */}.>`];
1182
1378
  }
1183
1379
  }
1184
1380
  /** Ensure a single stream exists, creating or updating as needed. */
@@ -1215,26 +1411,26 @@ var StreamProvider = class {
1215
1411
  /** Get default config for a stream kind. */
1216
1412
  getDefaults(kind) {
1217
1413
  switch (kind) {
1218
- case "ev":
1414
+ case "ev" /* Event */:
1219
1415
  return DEFAULT_EVENT_STREAM_CONFIG;
1220
- case "cmd":
1416
+ case "cmd" /* Command */:
1221
1417
  return DEFAULT_COMMAND_STREAM_CONFIG;
1222
- case "broadcast":
1418
+ case "broadcast" /* Broadcast */:
1223
1419
  return DEFAULT_BROADCAST_STREAM_CONFIG;
1224
- case "ordered":
1420
+ case "ordered" /* Ordered */:
1225
1421
  return DEFAULT_ORDERED_STREAM_CONFIG;
1226
1422
  }
1227
1423
  }
1228
1424
  /** Get user-provided overrides for a stream kind. */
1229
1425
  getOverrides(kind) {
1230
1426
  switch (kind) {
1231
- case "ev":
1427
+ case "ev" /* Event */:
1232
1428
  return this.options.events?.stream ?? {};
1233
- case "cmd":
1429
+ case "cmd" /* Command */:
1234
1430
  return this.options.rpc?.mode === "jetstream" ? this.options.rpc.stream ?? {} : {};
1235
- case "broadcast":
1431
+ case "broadcast" /* Broadcast */:
1236
1432
  return this.options.broadcast?.stream ?? {};
1237
- case "ordered":
1433
+ case "ordered" /* Ordered */:
1238
1434
  return this.options.ordered?.stream ?? {};
1239
1435
  }
1240
1436
  }
@@ -1297,7 +1493,7 @@ var ConsumerProvider = class {
1297
1493
  const serviceName = internalName(this.options.name);
1298
1494
  const defaults = this.getDefaults(kind);
1299
1495
  const overrides = this.getOverrides(kind);
1300
- if (kind === "broadcast") {
1496
+ if (kind === "broadcast" /* Broadcast */) {
1301
1497
  const broadcastPatterns = this.patternRegistry.getBroadcastPatterns();
1302
1498
  if (broadcastPatterns.length === 0) {
1303
1499
  throw new Error("Broadcast consumer requested but no broadcast patterns are registered");
@@ -1319,7 +1515,10 @@ var ConsumerProvider = class {
1319
1515
  filter_subjects: broadcastPatterns
1320
1516
  };
1321
1517
  }
1322
- const filter_subject = kind === "ev" ? `${serviceName}.ev.>` : `${serviceName}.cmd.>`;
1518
+ if (kind !== "ev" /* Event */ && kind !== "cmd" /* Command */) {
1519
+ throw new Error(`Unexpected durable consumer kind: ${kind}`);
1520
+ }
1521
+ const filter_subject = `${serviceName}.${kind}.>`;
1323
1522
  return {
1324
1523
  ...defaults,
1325
1524
  ...overrides,
@@ -1331,26 +1530,26 @@ var ConsumerProvider = class {
1331
1530
  /** Get default config for a consumer kind. */
1332
1531
  getDefaults(kind) {
1333
1532
  switch (kind) {
1334
- case "ev":
1533
+ case "ev" /* Event */:
1335
1534
  return DEFAULT_EVENT_CONSUMER_CONFIG;
1336
- case "cmd":
1535
+ case "cmd" /* Command */:
1337
1536
  return DEFAULT_COMMAND_CONSUMER_CONFIG;
1338
- case "broadcast":
1537
+ case "broadcast" /* Broadcast */:
1339
1538
  return DEFAULT_BROADCAST_CONSUMER_CONFIG;
1340
- case "ordered":
1539
+ case "ordered" /* Ordered */:
1341
1540
  throw new Error("Ordered consumers are ephemeral and should not use durable config");
1342
1541
  }
1343
1542
  }
1344
1543
  /** Get user-provided overrides for a consumer kind. */
1345
1544
  getOverrides(kind) {
1346
1545
  switch (kind) {
1347
- case "ev":
1546
+ case "ev" /* Event */:
1348
1547
  return this.options.events?.consumer ?? {};
1349
- case "cmd":
1548
+ case "cmd" /* Command */:
1350
1549
  return this.options.rpc?.mode === "jetstream" ? this.options.rpc.consumer ?? {} : {};
1351
- case "broadcast":
1550
+ case "broadcast" /* Broadcast */:
1352
1551
  return this.options.broadcast?.consumer ?? {};
1353
- case "ordered":
1552
+ case "ordered" /* Ordered */:
1354
1553
  throw new Error("Ordered consumers are ephemeral and should not use durable config");
1355
1554
  }
1356
1555
  }
@@ -1358,7 +1557,10 @@ var ConsumerProvider = class {
1358
1557
 
1359
1558
  // src/server/infrastructure/message.provider.ts
1360
1559
  import { Logger as Logger7 } from "@nestjs/common";
1361
- import { DeliverPolicy as DeliverPolicy2 } from "nats";
1560
+ import {
1561
+ ConsumerEvents,
1562
+ DeliverPolicy as DeliverPolicy2
1563
+ } from "nats";
1362
1564
  import {
1363
1565
  catchError,
1364
1566
  defer as defer2,
@@ -1371,9 +1573,10 @@ import {
1371
1573
  timer
1372
1574
  } from "rxjs";
1373
1575
  var MessageProvider = class {
1374
- constructor(connection, eventBus) {
1576
+ constructor(connection, eventBus, consumeOptionsMap = /* @__PURE__ */ new Map()) {
1375
1577
  this.connection = connection;
1376
1578
  this.eventBus = eventBus;
1579
+ this.consumeOptionsMap = consumeOptionsMap;
1377
1580
  }
1378
1581
  logger = new Logger7("Jetstream:Message");
1379
1582
  activeIterators = /* @__PURE__ */ new Set();
@@ -1423,6 +1626,7 @@ var MessageProvider = class {
1423
1626
  *
1424
1627
  * @param streamName - JetStream stream to consume from.
1425
1628
  * @param filterSubjects - NATS subjects to filter on.
1629
+ * @param orderedConfig - Optional overrides for ordered consumer options.
1426
1630
  */
1427
1631
  async startOrdered(streamName2, filterSubjects, orderedConfig) {
1428
1632
  const consumerOpts = { filterSubjects };
@@ -1448,6 +1652,11 @@ var MessageProvider = class {
1448
1652
  }
1449
1653
  /** Stop all consumer flows and reinitialize subjects for potential restart. */
1450
1654
  destroy() {
1655
+ if (this.orderedReadyReject) {
1656
+ this.orderedReadyReject(new Error("Destroyed before ordered consumer connected"));
1657
+ this.orderedReadyResolve = null;
1658
+ this.orderedReadyReject = null;
1659
+ }
1451
1660
  this.destroy$.next();
1452
1661
  this.destroy$.complete();
1453
1662
  for (const messages of this.activeIterators) {
@@ -1467,42 +1676,17 @@ var MessageProvider = class {
1467
1676
  /** Create a self-healing consumer flow for a specific kind. */
1468
1677
  createFlow(kind, info) {
1469
1678
  const target$ = this.getTargetSubject(kind);
1470
- let consecutiveFailures = 0;
1471
- let lastRunFailed = false;
1472
- return defer2(() => this.consumeOnce(info, target$)).pipe(
1473
- tap(() => {
1474
- lastRunFailed = false;
1475
- }),
1476
- catchError((err) => {
1477
- consecutiveFailures++;
1478
- lastRunFailed = true;
1479
- this.logger.error(`Consumer ${info.name} error, will restart:`, err);
1480
- this.eventBus.emit(
1481
- "error" /* Error */,
1482
- err instanceof Error ? err : new Error(String(err)),
1483
- "message-provider"
1484
- );
1485
- return EMPTY;
1486
- }),
1487
- repeat({
1488
- delay: () => {
1489
- if (!lastRunFailed) {
1490
- consecutiveFailures = 0;
1491
- }
1492
- const delay = Math.min(100 * Math.pow(2, consecutiveFailures), 3e4);
1493
- this.logger.warn(`Consumer ${info.name} stream ended, restarting in ${delay}ms...`);
1494
- return timer(delay);
1495
- }
1496
- }),
1497
- takeUntil(this.destroy$)
1498
- );
1679
+ return this.createSelfHealingFlow(() => this.consumeOnce(kind, info, target$), info.name);
1499
1680
  }
1500
1681
  /** Single iteration: get consumer -> pull messages -> emit to subject. */
1501
- async consumeOnce(info, target$) {
1502
- const js = (await this.connection.getConnection()).jetstream();
1682
+ async consumeOnce(kind, info, target$) {
1683
+ const js = this.connection.getJetStreamClient();
1503
1684
  const consumer = await js.consumers.get(info.stream_name, info.name);
1504
- const messages = await consumer.consume();
1685
+ const defaults = { idle_heartbeat: 5e3 };
1686
+ const userOptions = this.consumeOptionsMap.get(kind) ?? {};
1687
+ const messages = await consumer.consume({ ...defaults, ...userOptions });
1505
1688
  this.activeIterators.add(messages);
1689
+ this.monitorConsumerHealth(messages, info.name);
1506
1690
  try {
1507
1691
  for await (const msg of messages) {
1508
1692
  target$.next(msg);
@@ -1514,47 +1698,73 @@ var MessageProvider = class {
1514
1698
  /** Get the target subject for a consumer kind. */
1515
1699
  getTargetSubject(kind) {
1516
1700
  switch (kind) {
1517
- case "ev":
1701
+ case "ev" /* Event */:
1518
1702
  return this.eventMessages$;
1519
- case "cmd":
1703
+ case "cmd" /* Command */:
1520
1704
  return this.commandMessages$;
1521
- case "broadcast":
1705
+ case "broadcast" /* Broadcast */:
1522
1706
  return this.broadcastMessages$;
1523
- case "ordered":
1707
+ case "ordered" /* Ordered */:
1524
1708
  return this.orderedMessages$;
1709
+ default: {
1710
+ const _exhaustive = kind;
1711
+ throw new Error(`Unknown stream kind: ${_exhaustive}`);
1712
+ }
1525
1713
  }
1526
1714
  }
1715
+ /** Monitor heartbeats and restart the consumer iterator on prolonged silence. */
1716
+ monitorConsumerHealth(messages, name) {
1717
+ (async () => {
1718
+ for await (const status of await messages.status()) {
1719
+ if (status.type === ConsumerEvents.HeartbeatsMissed && status.data >= 2) {
1720
+ this.logger.warn(`Consumer ${name}: ${status.data} heartbeats missed, restarting`);
1721
+ messages.stop();
1722
+ break;
1723
+ }
1724
+ }
1725
+ })().catch((err) => {
1726
+ if (err) {
1727
+ this.logger.debug(`Consumer ${name} health monitor ended:`, err);
1728
+ }
1729
+ });
1730
+ }
1527
1731
  /** Create a self-healing ordered consumer flow. */
1528
1732
  createOrderedFlow(streamName2, consumerOpts) {
1733
+ return this.createSelfHealingFlow(
1734
+ () => this.consumeOrderedOnce(streamName2, consumerOpts),
1735
+ "ordered" /* Ordered */,
1736
+ (err) => {
1737
+ if (this.orderedReadyReject) {
1738
+ this.orderedReadyReject(err);
1739
+ this.orderedReadyReject = null;
1740
+ this.orderedReadyResolve = null;
1741
+ }
1742
+ }
1743
+ );
1744
+ }
1745
+ /** Shared self-healing flow: defer -> retry with exponential backoff on error/completion. */
1746
+ createSelfHealingFlow(source, label, onFirstError) {
1529
1747
  let consecutiveFailures = 0;
1530
- let lastRunFailed = false;
1531
- return defer2(() => this.consumeOrderedOnce(streamName2, consumerOpts)).pipe(
1748
+ return defer2(source).pipe(
1532
1749
  tap(() => {
1533
- lastRunFailed = false;
1750
+ consecutiveFailures = 0;
1534
1751
  }),
1535
1752
  catchError((err) => {
1536
1753
  consecutiveFailures++;
1537
- lastRunFailed = true;
1538
- this.logger.error("Ordered consumer error, will restart:", err);
1754
+ this.logger.error(`Consumer ${label} error, will restart:`, err);
1539
1755
  this.eventBus.emit(
1540
1756
  "error" /* Error */,
1541
1757
  err instanceof Error ? err : new Error(String(err)),
1542
1758
  "message-provider"
1543
1759
  );
1544
- if (this.orderedReadyReject) {
1545
- this.orderedReadyReject(err);
1546
- this.orderedReadyReject = null;
1547
- this.orderedReadyResolve = null;
1548
- }
1760
+ onFirstError?.(err);
1761
+ onFirstError = void 0;
1549
1762
  return EMPTY;
1550
1763
  }),
1551
1764
  repeat({
1552
1765
  delay: () => {
1553
- if (!lastRunFailed) {
1554
- consecutiveFailures = 0;
1555
- }
1556
1766
  const delay = Math.min(100 * Math.pow(2, consecutiveFailures), 3e4);
1557
- this.logger.warn(`Ordered consumer stream ended, restarting in ${delay}ms...`);
1767
+ this.logger.warn(`Consumer ${label} stream ended, restarting in ${delay}ms...`);
1558
1768
  return timer(delay);
1559
1769
  }
1560
1770
  })
@@ -1562,7 +1772,7 @@ var MessageProvider = class {
1562
1772
  }
1563
1773
  /** Single iteration: create ordered consumer -> iterate messages. */
1564
1774
  async consumeOrderedOnce(streamName2, consumerOpts) {
1565
- const js = (await this.connection.getConnection()).jetstream();
1775
+ const js = this.connection.getJetStreamClient();
1566
1776
  const consumer = await js.consumers.get(streamName2, consumerOpts);
1567
1777
  const messages = await consumer.consume();
1568
1778
  if (this.orderedReadyResolve) {
@@ -1583,12 +1793,24 @@ var MessageProvider = class {
1583
1793
 
1584
1794
  // src/server/routing/pattern-registry.ts
1585
1795
  import { Logger as Logger8 } from "@nestjs/common";
1796
+ var HANDLER_LABELS = {
1797
+ ["broadcast" /* Broadcast */]: "broadcast" /* Broadcast */,
1798
+ ["ordered" /* Ordered */]: "ordered" /* Ordered */,
1799
+ ["ev" /* Event */]: "event" /* Event */,
1800
+ ["cmd" /* Command */]: "rpc" /* Rpc */
1801
+ };
1586
1802
  var PatternRegistry = class {
1587
1803
  constructor(options) {
1588
1804
  this.options = options;
1589
1805
  }
1590
1806
  logger = new Logger8("Jetstream:PatternRegistry");
1591
1807
  registry = /* @__PURE__ */ new Map();
1808
+ // Cached after registerHandlers() — the registry is immutable from that point
1809
+ cachedPatterns = null;
1810
+ _hasEvents = false;
1811
+ _hasCommands = false;
1812
+ _hasBroadcasts = false;
1813
+ _hasOrdered = false;
1592
1814
  /**
1593
1815
  * Register all handlers from the NestJS strategy.
1594
1816
  *
@@ -1606,16 +1828,12 @@ var PatternRegistry = class {
1606
1828
  `Handler "${pattern}" cannot be both broadcast and ordered. Use one or the other.`
1607
1829
  );
1608
1830
  }
1609
- let fullSubject;
1610
- if (isBroadcast) {
1611
- fullSubject = buildBroadcastSubject(pattern);
1612
- } else if (isOrdered) {
1613
- fullSubject = buildSubject(serviceName, "ordered", pattern);
1614
- } else if (isEvent) {
1615
- fullSubject = buildSubject(serviceName, "ev", pattern);
1616
- } else {
1617
- fullSubject = buildSubject(serviceName, "cmd", pattern);
1618
- }
1831
+ let kind;
1832
+ if (isBroadcast) kind = "broadcast" /* Broadcast */;
1833
+ else if (isOrdered) kind = "ordered" /* Ordered */;
1834
+ else if (isEvent) kind = "ev" /* Event */;
1835
+ else kind = "cmd" /* Command */;
1836
+ const fullSubject = kind === "broadcast" /* Broadcast */ ? buildBroadcastSubject(pattern) : buildSubject(serviceName, kind, pattern);
1619
1837
  this.registry.set(fullSubject, {
1620
1838
  handler,
1621
1839
  pattern,
@@ -1623,18 +1841,13 @@ var PatternRegistry = class {
1623
1841
  isBroadcast,
1624
1842
  isOrdered
1625
1843
  });
1626
- let kind;
1627
- if (isBroadcast) {
1628
- kind = "broadcast";
1629
- } else if (isOrdered) {
1630
- kind = "ordered";
1631
- } else if (isEvent) {
1632
- kind = "event";
1633
- } else {
1634
- kind = "rpc";
1635
- }
1636
- this.logger.debug(`Registered ${kind}: ${pattern} -> ${fullSubject}`);
1844
+ this.logger.debug(`Registered ${HANDLER_LABELS[kind]}: ${pattern} -> ${fullSubject}`);
1637
1845
  }
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;
1638
1851
  this.logSummary();
1639
1852
  }
1640
1853
  /** Find handler for a full NATS subject. */
@@ -1643,33 +1856,53 @@ var PatternRegistry = class {
1643
1856
  }
1644
1857
  /** Get all registered broadcast patterns (for consumer filter_subject setup). */
1645
1858
  getBroadcastPatterns() {
1646
- return Array.from(this.registry.values()).filter((r) => r.isBroadcast).map((r) => `broadcast.${r.pattern}`);
1859
+ return this.getPatternsByKind().broadcasts.map((p) => buildBroadcastSubject(p));
1647
1860
  }
1648
- /** Check if any broadcast handlers are registered. */
1649
1861
  hasBroadcastHandlers() {
1650
- return Array.from(this.registry.values()).some((r) => r.isBroadcast);
1862
+ return this._hasBroadcasts;
1651
1863
  }
1652
- /** Check if any RPC (command) handlers are registered. */
1653
1864
  hasRpcHandlers() {
1654
- return Array.from(this.registry.values()).some(
1655
- (r) => !r.isEvent && !r.isBroadcast && !r.isOrdered
1656
- );
1865
+ return this._hasCommands;
1657
1866
  }
1658
- /** Check if any workqueue event handlers are registered. */
1659
1867
  hasEventHandlers() {
1660
- return Array.from(this.registry.values()).some((r) => r.isEvent && !r.isBroadcast);
1868
+ return this._hasEvents;
1661
1869
  }
1662
- /** Check if any ordered event handlers are registered. */
1663
1870
  hasOrderedHandlers() {
1664
- return Array.from(this.registry.values()).some((r) => r.isOrdered);
1871
+ return this._hasOrdered;
1665
1872
  }
1666
1873
  /** Get fully-qualified NATS subjects for ordered handlers. */
1667
1874
  getOrderedSubjects() {
1668
- const name = internalName(this.options.name);
1669
- return Array.from(this.registry.values()).filter((r) => r.isOrdered).map((r) => `${name}.ordered.${r.pattern}`);
1875
+ return this.getPatternsByKind().ordered.map(
1876
+ (p) => buildSubject(this.options.name, "ordered" /* Ordered */, p)
1877
+ );
1670
1878
  }
1671
- /** Get patterns grouped by kind. */
1879
+ /** Get patterns grouped by kind (cached after registration). */
1672
1880
  getPatternsByKind() {
1881
+ const patterns = this.cachedPatterns ?? this.buildPatternsByKind();
1882
+ return {
1883
+ events: [...patterns.events],
1884
+ commands: [...patterns.commands],
1885
+ broadcasts: [...patterns.broadcasts],
1886
+ ordered: [...patterns.ordered]
1887
+ };
1888
+ }
1889
+ /** Normalize a full NATS subject back to the user-facing pattern. */
1890
+ normalizeSubject(subject) {
1891
+ const name = internalName(this.options.name);
1892
+ const prefixes = [
1893
+ `${name}.${"cmd" /* Command */}.`,
1894
+ `${name}.${"ev" /* Event */}.`,
1895
+ `${name}.${"ordered" /* Ordered */}.`,
1896
+ `${"broadcast" /* Broadcast */}.`
1897
+ ];
1898
+ for (const prefix of prefixes) {
1899
+ if (subject.startsWith(prefix)) {
1900
+ return subject.slice(prefix.length);
1901
+ }
1902
+ }
1903
+ return subject;
1904
+ }
1905
+ buildPatternsByKind() {
1673
1906
  const events = [];
1674
1907
  const commands = [];
1675
1908
  const broadcasts = [];
@@ -1682,18 +1915,6 @@ var PatternRegistry = class {
1682
1915
  }
1683
1916
  return { events, commands, broadcasts, ordered };
1684
1917
  }
1685
- /** Normalize a full NATS subject back to the user-facing pattern. */
1686
- normalizeSubject(subject) {
1687
- const name = internalName(this.options.name);
1688
- const prefixes = [`${name}.cmd.`, `${name}.ev.`, `${name}.ordered.`, "broadcast."];
1689
- for (const prefix of prefixes) {
1690
- if (subject.startsWith(prefix)) {
1691
- return subject.slice(prefix.length);
1692
- }
1693
- }
1694
- return subject;
1695
- }
1696
- /** Log a summary of all registered handlers. */
1697
1918
  logSummary() {
1698
1919
  const { events, commands, broadcasts, ordered } = this.getPatternsByKind();
1699
1920
  const parts = [
@@ -1710,21 +1931,16 @@ var PatternRegistry = class {
1710
1931
 
1711
1932
  // src/server/routing/event.router.ts
1712
1933
  import { Logger as Logger9 } from "@nestjs/common";
1713
- import {
1714
- catchError as catchError2,
1715
- concatMap,
1716
- defer as defer3,
1717
- EMPTY as EMPTY2,
1718
- from as from2,
1719
- mergeMap
1720
- } from "rxjs";
1934
+ import { concatMap, from as from2, mergeMap } from "rxjs";
1721
1935
  var EventRouter = class {
1722
- constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig) {
1936
+ constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig, processingConfig, ackWaitMap) {
1723
1937
  this.messageProvider = messageProvider;
1724
1938
  this.patternRegistry = patternRegistry;
1725
1939
  this.codec = codec;
1726
1940
  this.eventBus = eventBus;
1727
1941
  this.deadLetterConfig = deadLetterConfig;
1942
+ this.processingConfig = processingConfig;
1943
+ this.ackWaitMap = ackWaitMap;
1728
1944
  }
1729
1945
  logger = new Logger9("Jetstream:EventRouter");
1730
1946
  subscriptions = [];
@@ -1738,10 +1954,10 @@ var EventRouter = class {
1738
1954
  }
1739
1955
  /** Start routing event, broadcast, and ordered messages to handlers. */
1740
1956
  start() {
1741
- this.subscribeToStream(this.messageProvider.events$, "workqueue");
1742
- this.subscribeToStream(this.messageProvider.broadcasts$, "broadcast");
1957
+ this.subscribeToStream(this.messageProvider.events$, "ev" /* Event */);
1958
+ this.subscribeToStream(this.messageProvider.broadcasts$, "broadcast" /* Broadcast */);
1743
1959
  if (this.patternRegistry.hasOrderedHandlers()) {
1744
- this.subscribeToStream(this.messageProvider.ordered$, "ordered", true);
1960
+ this.subscribeToStream(this.messageProvider.ordered$, "ordered" /* Ordered */);
1745
1961
  }
1746
1962
  }
1747
1963
  /** Stop routing and unsubscribe from all streams. */
@@ -1752,41 +1968,88 @@ var EventRouter = class {
1752
1968
  this.subscriptions.length = 0;
1753
1969
  }
1754
1970
  /** Subscribe to a message stream and route each message. */
1755
- subscribeToStream(stream$, label, isOrdered = false) {
1756
- const route = (msg) => defer3(() => isOrdered ? this.handleOrdered(msg) : this.handle(msg)).pipe(
1757
- catchError2((err) => {
1758
- this.logger.error(`Unexpected error in ${label} event router`, err);
1759
- return EMPTY2;
1760
- })
1971
+ subscribeToStream(stream$, kind) {
1972
+ const isOrdered = kind === "ordered" /* Ordered */;
1973
+ const ackExtensionInterval = isOrdered ? null : resolveAckExtensionInterval(this.getAckExtensionConfig(kind), this.ackWaitMap?.get(kind));
1974
+ const concurrency = this.getConcurrency(kind);
1975
+ const route = (msg) => from2(
1976
+ isOrdered ? this.handleOrderedSafe(msg) : this.handleSafe(msg, ackExtensionInterval, kind)
1761
1977
  );
1762
- const subscription = stream$.pipe(isOrdered ? concatMap(route) : mergeMap(route)).subscribe();
1978
+ const subscription = stream$.pipe(isOrdered ? concatMap(route) : mergeMap(route, concurrency)).subscribe();
1763
1979
  this.subscriptions.push(subscription);
1764
1980
  }
1765
- /** Handle a single event message: decode -> execute handler -> ack/nak. */
1766
- handle(msg) {
1981
+ getConcurrency(kind) {
1982
+ if (kind === "ev" /* Event */) return this.processingConfig?.events?.concurrency;
1983
+ if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.concurrency;
1984
+ return void 0;
1985
+ }
1986
+ getAckExtensionConfig(kind) {
1987
+ if (kind === "ev" /* Event */) return this.processingConfig?.events?.ackExtension;
1988
+ if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.ackExtension;
1989
+ return void 0;
1990
+ }
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
+ }
2006
+ }
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
+ }
2021
+ }
2022
+ /** Resolve handler, decode payload, and build context. Returns null on failure. */
2023
+ decodeMessage(msg, isOrdered = false) {
1767
2024
  const handler = this.patternRegistry.getHandler(msg.subject);
1768
2025
  if (!handler) {
1769
- msg.term(`No handler for event: ${msg.subject}`);
1770
- this.logger.error(`No handler for event subject: ${msg.subject}`);
1771
- return EMPTY2;
2026
+ if (!isOrdered) msg.term(`No handler for event: ${msg.subject}`);
2027
+ this.logger.error(`No handler for subject: ${msg.subject}`);
2028
+ return null;
1772
2029
  }
1773
2030
  let data;
1774
2031
  try {
1775
2032
  data = this.codec.decode(msg.data);
1776
2033
  } catch (err) {
1777
- msg.term("Decode error");
2034
+ if (!isOrdered) msg.term("Decode error");
1778
2035
  this.logger.error(`Decode error for ${msg.subject}:`, err);
1779
- return EMPTY2;
2036
+ return null;
1780
2037
  }
1781
- this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "event");
1782
- const ctx = new RpcContext([msg]);
1783
- return from2(this.executeHandler(handler, data, ctx, msg));
2038
+ this.eventBus.emitMessageRouted(msg.subject, "event" /* Event */);
2039
+ return { handler, data, ctx: new RpcContext([msg]) };
1784
2040
  }
1785
2041
  /** Execute handler, then ack on success or nak/dead-letter on failure. */
1786
- async executeHandler(handler, data, ctx, msg) {
2042
+ async executeHandler(handler, data, ctx, msg, ackExtensionInterval) {
2043
+ const stopAckExtension = startAckExtensionTimer(msg, ackExtensionInterval);
1787
2044
  try {
1788
2045
  await unwrapResult(handler(data, ctx));
1789
- msg.ack();
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
+ }
1790
2053
  } catch (err) {
1791
2054
  this.logger.error(`Event handler error (${msg.subject}):`, err);
1792
2055
  if (this.isDeadLetter(msg)) {
@@ -1794,30 +2057,10 @@ var EventRouter = class {
1794
2057
  } else {
1795
2058
  msg.nak();
1796
2059
  }
2060
+ } finally {
2061
+ stopAckExtension?.();
1797
2062
  }
1798
2063
  }
1799
- /** Handle an ordered message: decode -> execute handler -> no ack/nak. */
1800
- handleOrdered(msg) {
1801
- const handler = this.patternRegistry.getHandler(msg.subject);
1802
- if (!handler) {
1803
- this.logger.error(`No handler for ordered subject: ${msg.subject}`);
1804
- return EMPTY2;
1805
- }
1806
- let data;
1807
- try {
1808
- data = this.codec.decode(msg.data);
1809
- } catch (err) {
1810
- this.logger.error(`Decode error for ordered ${msg.subject}:`, err);
1811
- return EMPTY2;
1812
- }
1813
- this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "event");
1814
- const ctx = new RpcContext([msg]);
1815
- return from2(
1816
- unwrapResult(handler(data, ctx)).catch((err) => {
1817
- this.logger.error(`Ordered handler error (${msg.subject}):`, err);
1818
- })
1819
- );
1820
- }
1821
2064
  /** Check if the message has exhausted all delivery attempts. */
1822
2065
  isDeadLetter(msg) {
1823
2066
  if (!this.deadLetterConfig) return false;
@@ -1838,7 +2081,10 @@ var EventRouter = class {
1838
2081
  timestamp: new Date(msg.info.timestampNanos / 1e6).toISOString()
1839
2082
  };
1840
2083
  this.eventBus.emit("deadLetter" /* DeadLetter */, info);
1841
- if (!this.deadLetterConfig) return;
2084
+ if (!this.deadLetterConfig) {
2085
+ msg.term("Dead letter config unavailable");
2086
+ return;
2087
+ }
1842
2088
  try {
1843
2089
  await this.deadLetterConfig.onDeadLetter(info);
1844
2090
  msg.term("Dead letter processed");
@@ -1852,73 +2098,85 @@ var EventRouter = class {
1852
2098
  // src/server/routing/rpc.router.ts
1853
2099
  import { Logger as Logger10 } from "@nestjs/common";
1854
2100
  import { headers } from "nats";
1855
- import { catchError as catchError3, defer as defer4, EMPTY as EMPTY3, from as from3, mergeMap as mergeMap2 } from "rxjs";
2101
+ import { from as from3, mergeMap as mergeMap2 } from "rxjs";
1856
2102
  var RpcRouter = class {
1857
- constructor(messageProvider, patternRegistry, connection, codec, eventBus, timeout) {
2103
+ constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap) {
1858
2104
  this.messageProvider = messageProvider;
1859
2105
  this.patternRegistry = patternRegistry;
1860
2106
  this.connection = connection;
1861
2107
  this.codec = codec;
1862
2108
  this.eventBus = eventBus;
1863
- this.timeout = timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT;
2109
+ this.rpcOptions = rpcOptions;
2110
+ this.ackWaitMap = ackWaitMap;
2111
+ this.timeout = rpcOptions?.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT;
2112
+ this.concurrency = rpcOptions?.concurrency;
1864
2113
  }
1865
2114
  logger = new Logger10("Jetstream:RpcRouter");
1866
2115
  timeout;
2116
+ concurrency;
2117
+ resolvedAckExtensionInterval;
1867
2118
  subscription = null;
2119
+ cachedNc = null;
2120
+ /** Lazily resolve the ack extension interval (needs ackWaitMap populated at runtime). */
2121
+ get ackExtensionInterval() {
2122
+ if (this.resolvedAckExtensionInterval !== void 0) return this.resolvedAckExtensionInterval;
2123
+ this.resolvedAckExtensionInterval = resolveAckExtensionInterval(
2124
+ this.rpcOptions?.ackExtension,
2125
+ this.ackWaitMap?.get("cmd" /* Command */)
2126
+ );
2127
+ return this.resolvedAckExtensionInterval;
2128
+ }
1868
2129
  /** Start routing command messages to handlers. */
1869
- start() {
1870
- this.subscription = this.messageProvider.commands$.pipe(
1871
- mergeMap2(
1872
- (msg) => defer4(() => this.handle(msg)).pipe(
1873
- catchError3((err) => {
1874
- this.logger.error("Unexpected error in RPC router", err);
1875
- return EMPTY3;
1876
- })
1877
- )
1878
- )
1879
- ).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();
1880
2133
  }
1881
2134
  /** Stop routing and unsubscribe. */
1882
2135
  destroy() {
1883
2136
  this.subscription?.unsubscribe();
1884
2137
  this.subscription = null;
1885
2138
  }
1886
- /** Handle a single RPC command message. */
1887
- handle(msg) {
1888
- const handler = this.patternRegistry.getHandler(msg.subject);
1889
- if (!handler) {
1890
- msg.term(`No handler for RPC: ${msg.subject}`);
1891
- this.logger.error(`No handler for RPC subject: ${msg.subject}`);
1892
- return EMPTY3;
1893
- }
1894
- const replyTo = msg.headers?.get("x-reply-to" /* ReplyTo */);
1895
- const correlationId = msg.headers?.get("x-correlation-id" /* CorrelationId */);
1896
- if (!replyTo || !correlationId) {
1897
- msg.term("Missing required headers (reply-to or correlation-id)");
1898
- this.logger.error(`Missing headers for RPC: ${msg.subject}`);
1899
- return EMPTY3;
1900
- }
1901
- let data;
2139
+ /** Handle a single RPC command message with error isolation. */
2140
+ async handleSafe(msg) {
1902
2141
  try {
1903
- data = this.codec.decode(msg.data);
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);
1904
2166
  } catch (err) {
1905
- msg.term("Decode error");
1906
- this.logger.error(`Decode error for RPC ${msg.subject}:`, err);
1907
- return EMPTY3;
2167
+ this.logger.error("Unexpected error in RPC router", err);
1908
2168
  }
1909
- this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc");
1910
- return from3(this.executeHandler(handler, data, msg, replyTo, correlationId));
1911
2169
  }
1912
2170
  /** Execute handler, publish response, settle message. */
1913
2171
  async executeHandler(handler, data, msg, replyTo, correlationId) {
1914
- const nc = await this.connection.getConnection();
2172
+ const nc = this.cachedNc ?? await this.connection.getConnection();
1915
2173
  const ctx = new RpcContext([msg]);
1916
- const hdrs = headers();
1917
- hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
1918
2174
  let settled = false;
2175
+ const stopAckExtension = startAckExtensionTimer(msg, this.ackExtensionInterval);
1919
2176
  const timeoutId = setTimeout(() => {
1920
2177
  if (settled) return;
1921
2178
  settled = true;
2179
+ stopAckExtension?.();
1922
2180
  this.logger.error(`RPC timeout (${this.timeout}ms): ${msg.subject}`);
1923
2181
  this.eventBus.emit("rpcTimeout" /* RpcTimeout */, msg.subject, correlationId);
1924
2182
  msg.term("Handler timeout");
@@ -1928,8 +2186,11 @@ var RpcRouter = class {
1928
2186
  if (settled) return;
1929
2187
  settled = true;
1930
2188
  clearTimeout(timeoutId);
2189
+ stopAckExtension?.();
1931
2190
  msg.ack();
1932
2191
  try {
2192
+ const hdrs = headers();
2193
+ hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
1933
2194
  nc.publish(replyTo, this.codec.encode(result), { headers: hdrs });
1934
2195
  } catch (publishErr) {
1935
2196
  this.logger.error(`Failed to publish RPC response for ${msg.subject}`, publishErr);
@@ -1938,7 +2199,10 @@ var RpcRouter = class {
1938
2199
  if (settled) return;
1939
2200
  settled = true;
1940
2201
  clearTimeout(timeoutId);
2202
+ stopAckExtension?.();
1941
2203
  try {
2204
+ const hdrs = headers();
2205
+ hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
1942
2206
  hdrs.set("x-error" /* Error */, "true");
1943
2207
  nc.publish(replyTo, this.codec.encode(serializeError(err)), { headers: hdrs });
1944
2208
  } catch (encodeErr) {
@@ -1961,7 +2225,7 @@ var ShutdownManager = class {
1961
2225
  /**
1962
2226
  * Execute the full shutdown sequence.
1963
2227
  *
1964
- * @param strategy Optional strategy to close (stops consumers and subscriptions).
2228
+ * @param strategy Optional stoppable to close (stops consumers and subscriptions).
1965
2229
  */
1966
2230
  async shutdown(strategy) {
1967
2231
  this.eventBus.emit("shutdownStart" /* ShutdownStart */);
@@ -1984,6 +2248,7 @@ var ShutdownManager = class {
1984
2248
  };
1985
2249
 
1986
2250
  // src/jetstream.module.ts
2251
+ var JETSTREAM_ACK_WAIT_MAP = /* @__PURE__ */ Symbol("JETSTREAM_ACK_WAIT_MAP");
1987
2252
  var JetstreamModule = class {
1988
2253
  constructor(shutdownManager, strategy) {
1989
2254
  this.shutdownManager = shutdownManager;
@@ -2168,13 +2433,26 @@ var JetstreamModule = class {
2168
2433
  return new ConsumerProvider(options, connection, streamProvider, patternRegistry);
2169
2434
  }
2170
2435
  },
2436
+ // Shared ack_wait map — populated by strategy after ensureConsumers()
2437
+ {
2438
+ provide: JETSTREAM_ACK_WAIT_MAP,
2439
+ useFactory: () => /* @__PURE__ */ new Map()
2440
+ },
2171
2441
  // MessageProvider — pull-based message consumption
2172
2442
  {
2173
2443
  provide: MessageProvider,
2174
2444
  inject: [JETSTREAM_OPTIONS, JETSTREAM_CONNECTION, JETSTREAM_EVENT_BUS],
2175
2445
  useFactory: (options, connection, eventBus) => {
2176
2446
  if (options.consumer === false) return null;
2177
- return new MessageProvider(connection, eventBus);
2447
+ const consumeOptionsMap = /* @__PURE__ */ new Map();
2448
+ if (options.events?.consume)
2449
+ consumeOptionsMap.set("ev" /* Event */, options.events.consume);
2450
+ if (options.broadcast?.consume)
2451
+ consumeOptionsMap.set("broadcast" /* Broadcast */, options.broadcast.consume);
2452
+ if (options.rpc?.mode === "jetstream" && options.rpc.consume) {
2453
+ consumeOptionsMap.set("cmd" /* Command */, options.rpc.consume);
2454
+ }
2455
+ return new MessageProvider(connection, eventBus, consumeOptionsMap);
2178
2456
  }
2179
2457
  },
2180
2458
  // EventRouter — routes event and broadcast messages to handlers
@@ -2185,20 +2463,33 @@ var JetstreamModule = class {
2185
2463
  MessageProvider,
2186
2464
  PatternRegistry,
2187
2465
  JETSTREAM_CODEC,
2188
- JETSTREAM_EVENT_BUS
2466
+ JETSTREAM_EVENT_BUS,
2467
+ JETSTREAM_ACK_WAIT_MAP
2189
2468
  ],
2190
- useFactory: (options, messageProvider, patternRegistry, codec, eventBus) => {
2469
+ useFactory: (options, messageProvider, patternRegistry, codec, eventBus, ackWaitMap) => {
2191
2470
  if (options.consumer === false) return null;
2192
2471
  const deadLetterConfig = options.onDeadLetter ? {
2193
2472
  maxDeliverByStream: /* @__PURE__ */ new Map(),
2194
2473
  onDeadLetter: options.onDeadLetter
2195
2474
  } : void 0;
2475
+ const processingConfig = {
2476
+ events: {
2477
+ concurrency: options.events?.concurrency,
2478
+ ackExtension: options.events?.ackExtension
2479
+ },
2480
+ broadcast: {
2481
+ concurrency: options.broadcast?.concurrency,
2482
+ ackExtension: options.broadcast?.ackExtension
2483
+ }
2484
+ };
2196
2485
  return new EventRouter(
2197
2486
  messageProvider,
2198
2487
  patternRegistry,
2199
2488
  codec,
2200
2489
  eventBus,
2201
- deadLetterConfig
2490
+ deadLetterConfig,
2491
+ processingConfig,
2492
+ ackWaitMap
2202
2493
  );
2203
2494
  }
2204
2495
  },
@@ -2211,18 +2502,24 @@ var JetstreamModule = class {
2211
2502
  PatternRegistry,
2212
2503
  JETSTREAM_CONNECTION,
2213
2504
  JETSTREAM_CODEC,
2214
- JETSTREAM_EVENT_BUS
2505
+ JETSTREAM_EVENT_BUS,
2506
+ JETSTREAM_ACK_WAIT_MAP
2215
2507
  ],
2216
- useFactory: (options, messageProvider, patternRegistry, connection, codec, eventBus) => {
2508
+ useFactory: (options, messageProvider, patternRegistry, connection, codec, eventBus, ackWaitMap) => {
2217
2509
  if (options.consumer === false) return null;
2218
- const timeout = options.rpc?.mode === "jetstream" ? options.rpc.timeout : void 0;
2510
+ const rpcOptions = options.rpc?.mode === "jetstream" ? {
2511
+ timeout: options.rpc.timeout,
2512
+ concurrency: options.rpc.concurrency,
2513
+ ackExtension: options.rpc.ackExtension
2514
+ } : void 0;
2219
2515
  return new RpcRouter(
2220
2516
  messageProvider,
2221
2517
  patternRegistry,
2222
2518
  connection,
2223
2519
  codec,
2224
2520
  eventBus,
2225
- timeout
2521
+ rpcOptions,
2522
+ ackWaitMap
2226
2523
  );
2227
2524
  }
2228
2525
  },
@@ -2253,9 +2550,10 @@ var JetstreamModule = class {
2253
2550
  MessageProvider,
2254
2551
  EventRouter,
2255
2552
  RpcRouter,
2256
- CoreRpcServer
2553
+ CoreRpcServer,
2554
+ JETSTREAM_ACK_WAIT_MAP
2257
2555
  ],
2258
- useFactory: (options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer) => {
2556
+ useFactory: (options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer, ackWaitMap) => {
2259
2557
  if (options.consumer === false) return null;
2260
2558
  return new JetstreamStrategy(
2261
2559
  options,
@@ -2266,7 +2564,8 @@ var JetstreamModule = class {
2266
2564
  messageProvider,
2267
2565
  eventRouter,
2268
2566
  rpcRouter,
2269
- coreRpcServer
2567
+ coreRpcServer,
2568
+ ackWaitMap
2270
2569
  );
2271
2570
  }
2272
2571
  }
@@ -2346,8 +2645,13 @@ export {
2346
2645
  JetstreamRecordBuilder,
2347
2646
  JetstreamStrategy,
2348
2647
  JsonCodec,
2648
+ MessageKind,
2649
+ PatternPrefix,
2349
2650
  RpcContext,
2651
+ StreamKind,
2350
2652
  TransportEvent,
2351
2653
  getClientToken,
2654
+ isCoreRpcMode,
2655
+ isJetStreamRpcMode,
2352
2656
  toNanos
2353
2657
  };