@horizon-republic/nestjs-jetstream 2.5.1 → 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.cjs CHANGED
@@ -42,9 +42,14 @@ __export(index_exports, {
42
42
  JetstreamRecordBuilder: () => JetstreamRecordBuilder,
43
43
  JetstreamStrategy: () => JetstreamStrategy,
44
44
  JsonCodec: () => JsonCodec,
45
+ MessageKind: () => MessageKind,
46
+ PatternPrefix: () => PatternPrefix,
45
47
  RpcContext: () => RpcContext,
48
+ StreamKind: () => StreamKind,
46
49
  TransportEvent: () => TransportEvent,
47
50
  getClientToken: () => getClientToken,
51
+ isCoreRpcMode: () => isCoreRpcMode,
52
+ isJetStreamRpcMode: () => isJetStreamRpcMode,
48
53
  toNanos: () => toNanos
49
54
  });
50
55
  module.exports = __toCommonJS(index_exports);
@@ -58,6 +63,11 @@ var import_microservices = require("@nestjs/microservices");
58
63
  var import_nats2 = require("nats");
59
64
 
60
65
  // src/interfaces/hooks.interface.ts
66
+ var MessageKind = /* @__PURE__ */ ((MessageKind2) => {
67
+ MessageKind2["Event"] = "event";
68
+ MessageKind2["Rpc"] = "rpc";
69
+ return MessageKind2;
70
+ })(MessageKind || {});
61
71
  var TransportEvent = /* @__PURE__ */ ((TransportEvent2) => {
62
72
  TransportEvent2["Connect"] = "connect";
63
73
  TransportEvent2["Disconnect"] = "disconnect";
@@ -71,6 +81,15 @@ var TransportEvent = /* @__PURE__ */ ((TransportEvent2) => {
71
81
  return TransportEvent2;
72
82
  })(TransportEvent || {});
73
83
 
84
+ // src/interfaces/stream.interface.ts
85
+ var StreamKind = /* @__PURE__ */ ((StreamKind2) => {
86
+ StreamKind2["Event"] = "ev";
87
+ StreamKind2["Command"] = "cmd";
88
+ StreamKind2["Broadcast"] = "broadcast";
89
+ StreamKind2["Ordered"] = "ordered";
90
+ return StreamKind2;
91
+ })(StreamKind || {});
92
+
74
93
  // src/jetstream.constants.ts
75
94
  var import_nats = require("nats");
76
95
  var JETSTREAM_OPTIONS = /* @__PURE__ */ Symbol("JETSTREAM_OPTIONS");
@@ -95,7 +114,7 @@ var baseStreamConfig = {
95
114
  num_replicas: 1,
96
115
  discard: import_nats.DiscardPolicy.Old,
97
116
  allow_direct: true,
98
- compression: import_nats.StoreCompression.None
117
+ compression: import_nats.StoreCompression.S2
99
118
  };
100
119
  var DEFAULT_EVENT_STREAM_CONFIG = {
101
120
  ...baseStreamConfig,
@@ -187,13 +206,20 @@ var internalName = (name) => `${name}__microservice`;
187
206
  var buildSubject = (serviceName, kind, pattern) => `${internalName(serviceName)}.${kind}.${pattern}`;
188
207
  var buildBroadcastSubject = (pattern) => `broadcast.${pattern}`;
189
208
  var streamName = (serviceName, kind) => {
190
- if (kind === "broadcast") return "broadcast-stream";
209
+ if (kind === "broadcast" /* Broadcast */) return "broadcast-stream";
191
210
  return `${internalName(serviceName)}_${kind}-stream`;
192
211
  };
193
212
  var consumerName = (serviceName, kind) => {
194
- if (kind === "broadcast") return `${internalName(serviceName)}_broadcast-consumer`;
213
+ if (kind === "broadcast" /* Broadcast */) return `${internalName(serviceName)}_broadcast-consumer`;
195
214
  return `${internalName(serviceName)}_${kind}-consumer`;
196
215
  };
216
+ var PatternPrefix = /* @__PURE__ */ ((PatternPrefix2) => {
217
+ PatternPrefix2["Broadcast"] = "broadcast:";
218
+ PatternPrefix2["Ordered"] = "ordered:";
219
+ return PatternPrefix2;
220
+ })(PatternPrefix || {});
221
+ var isJetStreamRpcMode = (rpc) => rpc?.mode === "jetstream";
222
+ var isCoreRpcMode = (rpc) => !rpc || rpc.mode === "core";
197
223
 
198
224
  // src/client/jetstream.record.ts
199
225
  var JetstreamRecord = class {
@@ -308,10 +334,13 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
308
334
  this.codec = codec;
309
335
  this.eventBus = eventBus;
310
336
  this.targetName = targetServiceName;
337
+ this.callerName = internalName(this.rootOptions.name);
311
338
  }
312
339
  logger = new import_common.Logger("Jetstream:Client");
313
340
  /** Target service name this client sends messages to. */
314
341
  targetName;
342
+ /** Pre-cached caller name derived from rootOptions.name, computed once in constructor. */
343
+ callerName;
315
344
  /** Shared inbox for JetStream-mode RPC responses. */
316
345
  inbox = null;
317
346
  inboxSubscription = null;
@@ -331,7 +360,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
331
360
  */
332
361
  async connect() {
333
362
  const nc = await this.connection.getConnection();
334
- if (this.isJetStreamRpcMode() && !this.inboxSubscription) {
363
+ if (isJetStreamRpcMode(this.rootOptions.rpc) && !this.inboxSubscription) {
335
364
  this.setupInbox(nc);
336
365
  }
337
366
  this.statusSubscription ??= this.connection.status$.subscribe((status) => {
@@ -366,11 +395,11 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
366
395
  * depending on the subject prefix.
367
396
  */
368
397
  async dispatchEvent(packet) {
369
- const nc = await this.connect();
398
+ await this.connect();
370
399
  const { data, hdrs, messageId } = this.extractRecordData(packet.data);
371
400
  const subject = this.buildEventSubject(packet.pattern);
372
401
  const msgHeaders = this.buildHeaders(hdrs, { subject });
373
- const ack = await nc.jetstream().publish(subject, this.codec.encode(data), {
402
+ const ack = await this.connection.getJetStreamClient().publish(subject, this.codec.encode(data), {
374
403
  headers: msgHeaders,
375
404
  msgID: messageId ?? crypto.randomUUID()
376
405
  });
@@ -386,26 +415,23 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
386
415
  * JetStream mode: publishes to stream + waits for inbox response.
387
416
  */
388
417
  publish(packet, callback) {
389
- const subject = buildSubject(this.targetName, "cmd", packet.pattern);
418
+ const subject = buildSubject(this.targetName, "cmd" /* Command */, packet.pattern);
390
419
  const { data, hdrs, timeout, messageId } = this.extractRecordData(packet.data);
391
420
  const onUnhandled = (err) => {
392
421
  this.logger.error("Unhandled publish error:", err);
393
422
  callback({ err: new Error("Internal transport error"), response: null, isDisposed: true });
394
423
  };
395
424
  let jetStreamCorrelationId = null;
396
- if (this.isCoreRpcMode()) {
425
+ if (isCoreRpcMode(this.rootOptions.rpc)) {
397
426
  this.publishCoreRpc(subject, data, hdrs, timeout, callback).catch(onUnhandled);
398
427
  } else {
399
428
  jetStreamCorrelationId = crypto.randomUUID();
400
- this.publishJetStreamRpc(
401
- subject,
402
- data,
403
- hdrs,
429
+ this.publishJetStreamRpc(subject, data, callback, {
430
+ headers: hdrs,
404
431
  timeout,
405
- callback,
406
- jetStreamCorrelationId,
432
+ correlationId: jetStreamCorrelationId,
407
433
  messageId
408
- ).catch(onUnhandled);
434
+ }).catch(onUnhandled);
409
435
  }
410
436
  return () => {
411
437
  if (jetStreamCorrelationId) {
@@ -442,35 +468,46 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
442
468
  }
443
469
  }
444
470
  /** JetStream mode: publish to stream + wait for inbox response. */
445
- async publishJetStreamRpc(subject, data, customHeaders, timeout, callback, correlationId = crypto.randomUUID(), messageId) {
446
- const effectiveTimeout = timeout ?? this.getRpcTimeout();
471
+ async publishJetStreamRpc(subject, data, callback, options) {
472
+ const { headers: customHeaders, correlationId, messageId } = options;
473
+ const effectiveTimeout = options.timeout ?? this.getRpcTimeout();
447
474
  this.pendingMessages.set(correlationId, callback);
448
- const timeoutId = setTimeout(() => {
449
- if (!this.pendingMessages.has(correlationId)) return;
450
- this.pendingTimeouts.delete(correlationId);
451
- this.pendingMessages.delete(correlationId);
452
- this.logger.error(`JetStream RPC timeout (${effectiveTimeout}ms): ${subject}`);
453
- this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
454
- callback({ err: new Error("RPC timeout"), response: null, isDisposed: true });
455
- }, effectiveTimeout);
456
- this.pendingTimeouts.set(correlationId, timeoutId);
457
475
  try {
458
- const nc = await this.connect();
476
+ await this.connect();
477
+ if (!this.pendingMessages.has(correlationId)) return;
459
478
  if (!this.inbox) {
460
- throw new Error("Inbox not initialized \u2014 JetStream RPC mode requires a connected inbox");
479
+ this.pendingMessages.delete(correlationId);
480
+ callback({
481
+ err: new Error("Inbox not initialized \u2014 JetStream RPC mode requires a connected inbox"),
482
+ response: null,
483
+ isDisposed: true
484
+ });
485
+ return;
461
486
  }
487
+ const timeoutId = setTimeout(() => {
488
+ if (!this.pendingMessages.has(correlationId)) return;
489
+ this.pendingTimeouts.delete(correlationId);
490
+ this.pendingMessages.delete(correlationId);
491
+ this.logger.error(`JetStream RPC timeout (${effectiveTimeout}ms): ${subject}`);
492
+ this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
493
+ callback({ err: new Error("RPC timeout"), response: null, isDisposed: true });
494
+ }, effectiveTimeout);
495
+ this.pendingTimeouts.set(correlationId, timeoutId);
462
496
  const hdrs = this.buildHeaders(customHeaders, {
463
497
  subject,
464
498
  correlationId,
465
499
  replyTo: this.inbox
466
500
  });
467
- await nc.jetstream().publish(subject, this.codec.encode(data), {
501
+ await this.connection.getJetStreamClient().publish(subject, this.codec.encode(data), {
468
502
  headers: hdrs,
469
503
  msgID: messageId ?? crypto.randomUUID()
470
504
  });
471
505
  } catch (err) {
472
- clearTimeout(timeoutId);
473
- this.pendingTimeouts.delete(correlationId);
506
+ const existingTimeout = this.pendingTimeouts.get(correlationId);
507
+ if (existingTimeout) {
508
+ clearTimeout(existingTimeout);
509
+ this.pendingTimeouts.delete(correlationId);
510
+ }
474
511
  if (!this.pendingMessages.has(correlationId)) return;
475
512
  this.pendingMessages.delete(correlationId);
476
513
  const error = err instanceof Error ? err : new Error("Unknown error");
@@ -546,19 +583,23 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
546
583
  }
547
584
  /** Build event subject — workqueue, broadcast, or ordered. */
548
585
  buildEventSubject(pattern) {
549
- if (pattern.startsWith("broadcast:")) {
550
- return buildBroadcastSubject(pattern.slice("broadcast:".length));
551
- }
552
- if (pattern.startsWith("ordered:")) {
553
- return buildSubject(this.targetName, "ordered", pattern.slice("ordered:".length));
586
+ if (pattern.startsWith("broadcast:" /* Broadcast */)) {
587
+ return buildBroadcastSubject(pattern.slice("broadcast:" /* Broadcast */.length));
588
+ }
589
+ if (pattern.startsWith("ordered:" /* Ordered */)) {
590
+ return buildSubject(
591
+ this.targetName,
592
+ "ordered" /* Ordered */,
593
+ pattern.slice("ordered:" /* Ordered */.length)
594
+ );
554
595
  }
555
- return buildSubject(this.targetName, "ev", pattern);
596
+ return buildSubject(this.targetName, "ev" /* Event */, pattern);
556
597
  }
557
598
  /** Build NATS headers merging custom headers with transport headers. */
558
599
  buildHeaders(customHeaders, transport) {
559
600
  const hdrs = (0, import_nats2.headers)();
560
601
  hdrs.set("x-subject" /* Subject */, transport.subject);
561
- hdrs.set("x-caller-name" /* CallerName */, internalName(this.rootOptions.name));
602
+ hdrs.set("x-caller-name" /* CallerName */, this.callerName);
562
603
  if (transport.correlationId) {
563
604
  hdrs.set("x-correlation-id" /* CorrelationId */, transport.correlationId);
564
605
  }
@@ -584,15 +625,9 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
584
625
  }
585
626
  return { data: rawData, hdrs: null, timeout: void 0, messageId: void 0 };
586
627
  }
587
- isCoreRpcMode() {
588
- return !this.rootOptions.rpc || this.rootOptions.rpc.mode === "core";
589
- }
590
- isJetStreamRpcMode() {
591
- return this.rootOptions.rpc?.mode === "jetstream";
592
- }
593
628
  getRpcTimeout() {
594
629
  if (!this.rootOptions.rpc) return DEFAULT_RPC_TIMEOUT;
595
- const defaultTimeout = this.isJetStreamRpcMode() ? DEFAULT_JETSTREAM_RPC_TIMEOUT : DEFAULT_RPC_TIMEOUT;
630
+ const defaultTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? DEFAULT_JETSTREAM_RPC_TIMEOUT : DEFAULT_RPC_TIMEOUT;
596
631
  return this.rootOptions.rpc.timeout ?? defaultTimeout;
597
632
  }
598
633
  };
@@ -613,6 +648,10 @@ var JsonCodec = class {
613
648
  var import_common2 = require("@nestjs/common");
614
649
  var import_nats4 = require("nats");
615
650
  var import_rxjs = require("rxjs");
651
+ var DEFAULT_OPTIONS = {
652
+ maxReconnectAttempts: -1,
653
+ reconnectTimeWait: 1e3
654
+ };
616
655
  var ConnectionProvider = class {
617
656
  constructor(options, eventBus) {
618
657
  this.options = options;
@@ -632,6 +671,7 @@ var ConnectionProvider = class {
632
671
  logger = new import_common2.Logger("Jetstream:Connection");
633
672
  connection = null;
634
673
  connectionPromise = null;
674
+ jsClient = null;
635
675
  jsmInstance = null;
636
676
  jsmPromise = null;
637
677
  /**
@@ -670,6 +710,22 @@ var ConnectionProvider = class {
670
710
  });
671
711
  return this.jsmPromise;
672
712
  }
713
+ /**
714
+ * Get a cached JetStream client.
715
+ *
716
+ * Invalidated automatically on reconnect and shutdown so consumers always
717
+ * operate against the live connection.
718
+ *
719
+ * @returns The cached JetStreamClient.
720
+ * @throws Error if the connection has not been established yet.
721
+ */
722
+ getJetStreamClient() {
723
+ if (!this.connection || this.connection.isClosed()) {
724
+ throw new Error("Not connected \u2014 call getConnection() before getJetStreamClient()");
725
+ }
726
+ this.jsClient ??= this.connection.jetstream();
727
+ return this.jsClient;
728
+ }
673
729
  /** Direct access to the raw NATS connection, or `null` if not yet connected. */
674
730
  get unwrap() {
675
731
  return this.connection;
@@ -698,6 +754,7 @@ var ConnectionProvider = class {
698
754
  } finally {
699
755
  this.connection = null;
700
756
  this.connectionPromise = null;
757
+ this.jsClient = null;
701
758
  this.jsmInstance = null;
702
759
  this.jsmPromise = null;
703
760
  }
@@ -707,6 +764,7 @@ var ConnectionProvider = class {
707
764
  const name = internalName(this.options.name);
708
765
  try {
709
766
  const nc = await (0, import_nats4.connect)({
767
+ ...DEFAULT_OPTIONS,
710
768
  ...this.options.connectionOptions,
711
769
  servers: this.options.servers,
712
770
  name
@@ -732,6 +790,7 @@ var ConnectionProvider = class {
732
790
  this.eventBus.emit("disconnect" /* Disconnect */);
733
791
  break;
734
792
  case import_nats4.Events.Reconnect:
793
+ this.jsClient = null;
735
794
  this.jsmInstance = null;
736
795
  this.jsmPromise = null;
737
796
  this.eventBus.emit("reconnect" /* Reconnect */, nc.getServer());
@@ -850,7 +909,7 @@ JetstreamHealthIndicator = __decorateClass([
850
909
  // src/server/strategy.ts
851
910
  var import_microservices2 = require("@nestjs/microservices");
852
911
  var JetstreamStrategy = class extends import_microservices2.Server {
853
- constructor(options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer) {
912
+ constructor(options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer, ackWaitMap = /* @__PURE__ */ new Map()) {
854
913
  super();
855
914
  this.options = options;
856
915
  this.connection = connection;
@@ -861,6 +920,7 @@ var JetstreamStrategy = class extends import_microservices2.Server {
861
920
  this.eventRouter = eventRouter;
862
921
  this.rpcRouter = rpcRouter;
863
922
  this.coreRpcServer = coreRpcServer;
923
+ this.ackWaitMap = ackWaitMap;
864
924
  }
865
925
  transportId = /* @__PURE__ */ Symbol("jetstream-transport");
866
926
  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
@@ -878,17 +938,17 @@ var JetstreamStrategy = class extends import_microservices2.Server {
878
938
  }
879
939
  this.started = true;
880
940
  this.patternRegistry.registerHandlers(this.getHandlers());
881
- const streamKinds = this.resolveStreamKinds();
882
- const durableKinds = this.resolveDurableConsumerKinds();
941
+ const { streams: streamKinds, durableConsumers: durableKinds } = this.resolveRequiredKinds();
883
942
  if (streamKinds.length > 0) {
884
943
  await this.streamProvider.ensureStreams(streamKinds);
885
944
  if (durableKinds.length > 0) {
886
945
  const consumers = await this.consumerProvider.ensureConsumers(durableKinds);
946
+ this.populateAckWaitMap(consumers);
887
947
  this.eventRouter.updateMaxDeliverMap(this.buildMaxDeliverMap(consumers));
888
948
  this.messageProvider.start(consumers);
889
949
  }
890
950
  if (this.patternRegistry.hasOrderedHandlers()) {
891
- const orderedStreamName = this.streamProvider.getStreamName("ordered");
951
+ const orderedStreamName = this.streamProvider.getStreamName("ordered" /* Ordered */);
892
952
  await this.messageProvider.startOrdered(
893
953
  orderedStreamName,
894
954
  this.patternRegistry.getOrderedSubjects(),
@@ -898,11 +958,11 @@ var JetstreamStrategy = class extends import_microservices2.Server {
898
958
  if (this.patternRegistry.hasEventHandlers() || this.patternRegistry.hasBroadcastHandlers() || this.patternRegistry.hasOrderedHandlers()) {
899
959
  this.eventRouter.start();
900
960
  }
901
- if (this.isJetStreamRpcMode() && this.patternRegistry.hasRpcHandlers()) {
961
+ if (isJetStreamRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
902
962
  this.rpcRouter.start();
903
963
  }
904
964
  }
905
- if (this.isCoreRpcMode() && this.patternRegistry.hasRpcHandlers()) {
965
+ if (isCoreRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
906
966
  await this.coreRpcServer.start();
907
967
  }
908
968
  callback();
@@ -943,36 +1003,34 @@ var JetstreamStrategy = class extends import_microservices2.Server {
943
1003
  getPatternRegistry() {
944
1004
  return this.patternRegistry;
945
1005
  }
946
- /** Determine which JetStream streams are needed. */
947
- resolveStreamKinds() {
948
- const kinds = [];
1006
+ /** Determine which streams and durable consumers are needed. */
1007
+ resolveRequiredKinds() {
1008
+ const streams = [];
1009
+ const durableConsumers = [];
949
1010
  if (this.patternRegistry.hasEventHandlers()) {
950
- kinds.push("ev");
951
- }
952
- if (this.patternRegistry.hasOrderedHandlers()) {
953
- kinds.push("ordered");
1011
+ streams.push("ev" /* Event */);
1012
+ durableConsumers.push("ev" /* Event */);
954
1013
  }
955
- if (this.isJetStreamRpcMode() && this.patternRegistry.hasRpcHandlers()) {
956
- kinds.push("cmd");
1014
+ if (isJetStreamRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
1015
+ streams.push("cmd" /* Command */);
1016
+ durableConsumers.push("cmd" /* Command */);
957
1017
  }
958
1018
  if (this.patternRegistry.hasBroadcastHandlers()) {
959
- kinds.push("broadcast");
1019
+ streams.push("broadcast" /* Broadcast */);
1020
+ durableConsumers.push("broadcast" /* Broadcast */);
960
1021
  }
961
- return kinds;
962
- }
963
- /** Determine which stream kinds need durable consumers (ordered consumers are ephemeral). */
964
- resolveDurableConsumerKinds() {
965
- const kinds = [];
966
- if (this.patternRegistry.hasEventHandlers()) {
967
- kinds.push("ev");
968
- }
969
- if (this.isJetStreamRpcMode() && this.patternRegistry.hasRpcHandlers()) {
970
- kinds.push("cmd");
1022
+ if (this.patternRegistry.hasOrderedHandlers()) {
1023
+ streams.push("ordered" /* Ordered */);
971
1024
  }
972
- if (this.patternRegistry.hasBroadcastHandlers()) {
973
- kinds.push("broadcast");
1025
+ return { streams, durableConsumers };
1026
+ }
1027
+ /** Populate the shared ack_wait map from actual NATS consumer configs. */
1028
+ populateAckWaitMap(consumers) {
1029
+ for (const [kind, info] of consumers) {
1030
+ if (info.config.ack_wait) {
1031
+ this.ackWaitMap.set(kind, info.config.ack_wait);
1032
+ }
974
1033
  }
975
- return kinds;
976
1034
  }
977
1035
  /** Build max_deliver map from actual NATS consumer configs (not options). */
978
1036
  buildMaxDeliverMap(consumers) {
@@ -986,12 +1044,6 @@ var JetstreamStrategy = class extends import_microservices2.Server {
986
1044
  }
987
1045
  return map;
988
1046
  }
989
- isCoreRpcMode() {
990
- return !this.options.rpc || this.options.rpc.mode === "core";
991
- }
992
- isJetStreamRpcMode() {
993
- return this.options.rpc?.mode === "jetstream";
994
- }
995
1047
  };
996
1048
 
997
1049
  // src/server/core-rpc.server.ts
@@ -1037,6 +1089,32 @@ var RpcContext = class extends import_microservices3.BaseRpcContext {
1037
1089
  }
1038
1090
  };
1039
1091
 
1092
+ // src/utils/ack-extension.ts
1093
+ var DEFAULT_ACK_EXTENSION_INTERVAL = 5e3;
1094
+ var MIN_ACK_EXTENSION_INTERVAL = 500;
1095
+ var resolveAckExtensionInterval = (config, ackWaitNanos) => {
1096
+ if (config === false || config === void 0) return null;
1097
+ if (typeof config === "number") {
1098
+ if (!Number.isFinite(config) || config <= 0) return null;
1099
+ return Math.floor(config);
1100
+ }
1101
+ if (!ackWaitNanos) return DEFAULT_ACK_EXTENSION_INTERVAL;
1102
+ const interval = Math.floor(ackWaitNanos / 1e6 / 2);
1103
+ return Math.max(interval, MIN_ACK_EXTENSION_INTERVAL);
1104
+ };
1105
+ var startAckExtensionTimer = (msg, interval) => {
1106
+ if (interval === null || interval <= 0) return null;
1107
+ const timer2 = setInterval(() => {
1108
+ try {
1109
+ msg.working();
1110
+ } catch {
1111
+ }
1112
+ }, interval);
1113
+ return () => {
1114
+ clearInterval(timer2);
1115
+ };
1116
+ };
1117
+
1040
1118
  // src/utils/serialize-error.ts
1041
1119
  var import_microservices4 = require("@nestjs/microservices");
1042
1120
  var serializeError = (err) => {
@@ -1123,13 +1201,17 @@ var CoreRpcServer = class {
1123
1201
  }
1124
1202
  /** Handle an incoming Core NATS request. */
1125
1203
  async handleRequest(msg) {
1204
+ if (!msg.reply) {
1205
+ this.logger.warn(`Ignoring fire-and-forget message on RPC subject: ${msg.subject}`);
1206
+ return;
1207
+ }
1126
1208
  const handler = this.patternRegistry.getHandler(msg.subject);
1127
1209
  if (!handler) {
1128
1210
  this.logger.warn(`No handler for Core RPC: ${msg.subject}`);
1129
1211
  this.respondWithError(msg, new Error(`No handler for subject: ${msg.subject}`));
1130
1212
  return;
1131
1213
  }
1132
- this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc");
1214
+ this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc" /* Rpc */);
1133
1215
  let data;
1134
1216
  try {
1135
1217
  data = this.codec.decode(msg.data);
@@ -1187,14 +1269,14 @@ var StreamProvider = class {
1187
1269
  getSubjects(kind) {
1188
1270
  const name = internalName(this.options.name);
1189
1271
  switch (kind) {
1190
- case "ev":
1191
- return [`${name}.ev.>`];
1192
- case "cmd":
1193
- return [`${name}.cmd.>`];
1194
- case "broadcast":
1272
+ case "ev" /* Event */:
1273
+ return [`${name}.${"ev" /* Event */}.>`];
1274
+ case "cmd" /* Command */:
1275
+ return [`${name}.${"cmd" /* Command */}.>`];
1276
+ case "broadcast" /* Broadcast */:
1195
1277
  return ["broadcast.>"];
1196
- case "ordered":
1197
- return [`${name}.ordered.>`];
1278
+ case "ordered" /* Ordered */:
1279
+ return [`${name}.${"ordered" /* Ordered */}.>`];
1198
1280
  }
1199
1281
  }
1200
1282
  /** Ensure a single stream exists, creating or updating as needed. */
@@ -1231,26 +1313,26 @@ var StreamProvider = class {
1231
1313
  /** Get default config for a stream kind. */
1232
1314
  getDefaults(kind) {
1233
1315
  switch (kind) {
1234
- case "ev":
1316
+ case "ev" /* Event */:
1235
1317
  return DEFAULT_EVENT_STREAM_CONFIG;
1236
- case "cmd":
1318
+ case "cmd" /* Command */:
1237
1319
  return DEFAULT_COMMAND_STREAM_CONFIG;
1238
- case "broadcast":
1320
+ case "broadcast" /* Broadcast */:
1239
1321
  return DEFAULT_BROADCAST_STREAM_CONFIG;
1240
- case "ordered":
1322
+ case "ordered" /* Ordered */:
1241
1323
  return DEFAULT_ORDERED_STREAM_CONFIG;
1242
1324
  }
1243
1325
  }
1244
1326
  /** Get user-provided overrides for a stream kind. */
1245
1327
  getOverrides(kind) {
1246
1328
  switch (kind) {
1247
- case "ev":
1329
+ case "ev" /* Event */:
1248
1330
  return this.options.events?.stream ?? {};
1249
- case "cmd":
1331
+ case "cmd" /* Command */:
1250
1332
  return this.options.rpc?.mode === "jetstream" ? this.options.rpc.stream ?? {} : {};
1251
- case "broadcast":
1333
+ case "broadcast" /* Broadcast */:
1252
1334
  return this.options.broadcast?.stream ?? {};
1253
- case "ordered":
1335
+ case "ordered" /* Ordered */:
1254
1336
  return this.options.ordered?.stream ?? {};
1255
1337
  }
1256
1338
  }
@@ -1313,7 +1395,7 @@ var ConsumerProvider = class {
1313
1395
  const serviceName = internalName(this.options.name);
1314
1396
  const defaults = this.getDefaults(kind);
1315
1397
  const overrides = this.getOverrides(kind);
1316
- if (kind === "broadcast") {
1398
+ if (kind === "broadcast" /* Broadcast */) {
1317
1399
  const broadcastPatterns = this.patternRegistry.getBroadcastPatterns();
1318
1400
  if (broadcastPatterns.length === 0) {
1319
1401
  throw new Error("Broadcast consumer requested but no broadcast patterns are registered");
@@ -1335,7 +1417,10 @@ var ConsumerProvider = class {
1335
1417
  filter_subjects: broadcastPatterns
1336
1418
  };
1337
1419
  }
1338
- const filter_subject = kind === "ev" ? `${serviceName}.ev.>` : `${serviceName}.cmd.>`;
1420
+ if (kind !== "ev" /* Event */ && kind !== "cmd" /* Command */) {
1421
+ throw new Error(`Unexpected durable consumer kind: ${kind}`);
1422
+ }
1423
+ const filter_subject = `${serviceName}.${kind}.>`;
1339
1424
  return {
1340
1425
  ...defaults,
1341
1426
  ...overrides,
@@ -1347,26 +1432,26 @@ var ConsumerProvider = class {
1347
1432
  /** Get default config for a consumer kind. */
1348
1433
  getDefaults(kind) {
1349
1434
  switch (kind) {
1350
- case "ev":
1435
+ case "ev" /* Event */:
1351
1436
  return DEFAULT_EVENT_CONSUMER_CONFIG;
1352
- case "cmd":
1437
+ case "cmd" /* Command */:
1353
1438
  return DEFAULT_COMMAND_CONSUMER_CONFIG;
1354
- case "broadcast":
1439
+ case "broadcast" /* Broadcast */:
1355
1440
  return DEFAULT_BROADCAST_CONSUMER_CONFIG;
1356
- case "ordered":
1441
+ case "ordered" /* Ordered */:
1357
1442
  throw new Error("Ordered consumers are ephemeral and should not use durable config");
1358
1443
  }
1359
1444
  }
1360
1445
  /** Get user-provided overrides for a consumer kind. */
1361
1446
  getOverrides(kind) {
1362
1447
  switch (kind) {
1363
- case "ev":
1448
+ case "ev" /* Event */:
1364
1449
  return this.options.events?.consumer ?? {};
1365
- case "cmd":
1450
+ case "cmd" /* Command */:
1366
1451
  return this.options.rpc?.mode === "jetstream" ? this.options.rpc.consumer ?? {} : {};
1367
- case "broadcast":
1452
+ case "broadcast" /* Broadcast */:
1368
1453
  return this.options.broadcast?.consumer ?? {};
1369
- case "ordered":
1454
+ case "ordered" /* Ordered */:
1370
1455
  throw new Error("Ordered consumers are ephemeral and should not use durable config");
1371
1456
  }
1372
1457
  }
@@ -1377,9 +1462,10 @@ var import_common7 = require("@nestjs/common");
1377
1462
  var import_nats8 = require("nats");
1378
1463
  var import_rxjs3 = require("rxjs");
1379
1464
  var MessageProvider = class {
1380
- constructor(connection, eventBus) {
1465
+ constructor(connection, eventBus, consumeOptionsMap = /* @__PURE__ */ new Map()) {
1381
1466
  this.connection = connection;
1382
1467
  this.eventBus = eventBus;
1468
+ this.consumeOptionsMap = consumeOptionsMap;
1383
1469
  }
1384
1470
  logger = new import_common7.Logger("Jetstream:Message");
1385
1471
  activeIterators = /* @__PURE__ */ new Set();
@@ -1429,6 +1515,7 @@ var MessageProvider = class {
1429
1515
  *
1430
1516
  * @param streamName - JetStream stream to consume from.
1431
1517
  * @param filterSubjects - NATS subjects to filter on.
1518
+ * @param orderedConfig - Optional overrides for ordered consumer options.
1432
1519
  */
1433
1520
  async startOrdered(streamName2, filterSubjects, orderedConfig) {
1434
1521
  const consumerOpts = { filterSubjects };
@@ -1454,6 +1541,11 @@ var MessageProvider = class {
1454
1541
  }
1455
1542
  /** Stop all consumer flows and reinitialize subjects for potential restart. */
1456
1543
  destroy() {
1544
+ if (this.orderedReadyReject) {
1545
+ this.orderedReadyReject(new Error("Destroyed before ordered consumer connected"));
1546
+ this.orderedReadyResolve = null;
1547
+ this.orderedReadyReject = null;
1548
+ }
1457
1549
  this.destroy$.next();
1458
1550
  this.destroy$.complete();
1459
1551
  for (const messages of this.activeIterators) {
@@ -1473,42 +1565,17 @@ var MessageProvider = class {
1473
1565
  /** Create a self-healing consumer flow for a specific kind. */
1474
1566
  createFlow(kind, info) {
1475
1567
  const target$ = this.getTargetSubject(kind);
1476
- let consecutiveFailures = 0;
1477
- let lastRunFailed = false;
1478
- return (0, import_rxjs3.defer)(() => this.consumeOnce(info, target$)).pipe(
1479
- (0, import_rxjs3.tap)(() => {
1480
- lastRunFailed = false;
1481
- }),
1482
- (0, import_rxjs3.catchError)((err) => {
1483
- consecutiveFailures++;
1484
- lastRunFailed = true;
1485
- this.logger.error(`Consumer ${info.name} error, will restart:`, err);
1486
- this.eventBus.emit(
1487
- "error" /* Error */,
1488
- err instanceof Error ? err : new Error(String(err)),
1489
- "message-provider"
1490
- );
1491
- return import_rxjs3.EMPTY;
1492
- }),
1493
- (0, import_rxjs3.repeat)({
1494
- delay: () => {
1495
- if (!lastRunFailed) {
1496
- consecutiveFailures = 0;
1497
- }
1498
- const delay = Math.min(100 * Math.pow(2, consecutiveFailures), 3e4);
1499
- this.logger.warn(`Consumer ${info.name} stream ended, restarting in ${delay}ms...`);
1500
- return (0, import_rxjs3.timer)(delay);
1501
- }
1502
- }),
1503
- (0, import_rxjs3.takeUntil)(this.destroy$)
1504
- );
1568
+ return this.createSelfHealingFlow(() => this.consumeOnce(kind, info, target$), info.name);
1505
1569
  }
1506
1570
  /** Single iteration: get consumer -> pull messages -> emit to subject. */
1507
- async consumeOnce(info, target$) {
1508
- const js = (await this.connection.getConnection()).jetstream();
1571
+ async consumeOnce(kind, info, target$) {
1572
+ const js = this.connection.getJetStreamClient();
1509
1573
  const consumer = await js.consumers.get(info.stream_name, info.name);
1510
- const messages = await consumer.consume();
1574
+ const defaults = { idle_heartbeat: 5e3 };
1575
+ const userOptions = this.consumeOptionsMap.get(kind) ?? {};
1576
+ const messages = await consumer.consume({ ...defaults, ...userOptions });
1511
1577
  this.activeIterators.add(messages);
1578
+ this.monitorConsumerHealth(messages, info.name);
1512
1579
  try {
1513
1580
  for await (const msg of messages) {
1514
1581
  target$.next(msg);
@@ -1520,47 +1587,73 @@ var MessageProvider = class {
1520
1587
  /** Get the target subject for a consumer kind. */
1521
1588
  getTargetSubject(kind) {
1522
1589
  switch (kind) {
1523
- case "ev":
1590
+ case "ev" /* Event */:
1524
1591
  return this.eventMessages$;
1525
- case "cmd":
1592
+ case "cmd" /* Command */:
1526
1593
  return this.commandMessages$;
1527
- case "broadcast":
1594
+ case "broadcast" /* Broadcast */:
1528
1595
  return this.broadcastMessages$;
1529
- case "ordered":
1596
+ case "ordered" /* Ordered */:
1530
1597
  return this.orderedMessages$;
1598
+ default: {
1599
+ const _exhaustive = kind;
1600
+ throw new Error(`Unknown stream kind: ${_exhaustive}`);
1601
+ }
1531
1602
  }
1532
1603
  }
1604
+ /** Monitor heartbeats and restart the consumer iterator on prolonged silence. */
1605
+ monitorConsumerHealth(messages, name) {
1606
+ (async () => {
1607
+ for await (const status of await messages.status()) {
1608
+ if (status.type === import_nats8.ConsumerEvents.HeartbeatsMissed && status.data >= 2) {
1609
+ this.logger.warn(`Consumer ${name}: ${status.data} heartbeats missed, restarting`);
1610
+ messages.stop();
1611
+ break;
1612
+ }
1613
+ }
1614
+ })().catch((err) => {
1615
+ if (err) {
1616
+ this.logger.debug(`Consumer ${name} health monitor ended:`, err);
1617
+ }
1618
+ });
1619
+ }
1533
1620
  /** Create a self-healing ordered consumer flow. */
1534
1621
  createOrderedFlow(streamName2, consumerOpts) {
1622
+ return this.createSelfHealingFlow(
1623
+ () => this.consumeOrderedOnce(streamName2, consumerOpts),
1624
+ "ordered" /* Ordered */,
1625
+ (err) => {
1626
+ if (this.orderedReadyReject) {
1627
+ this.orderedReadyReject(err);
1628
+ this.orderedReadyReject = null;
1629
+ this.orderedReadyResolve = null;
1630
+ }
1631
+ }
1632
+ );
1633
+ }
1634
+ /** Shared self-healing flow: defer -> retry with exponential backoff on error/completion. */
1635
+ createSelfHealingFlow(source, label, onFirstError) {
1535
1636
  let consecutiveFailures = 0;
1536
- let lastRunFailed = false;
1537
- return (0, import_rxjs3.defer)(() => this.consumeOrderedOnce(streamName2, consumerOpts)).pipe(
1637
+ return (0, import_rxjs3.defer)(source).pipe(
1538
1638
  (0, import_rxjs3.tap)(() => {
1539
- lastRunFailed = false;
1639
+ consecutiveFailures = 0;
1540
1640
  }),
1541
1641
  (0, import_rxjs3.catchError)((err) => {
1542
1642
  consecutiveFailures++;
1543
- lastRunFailed = true;
1544
- this.logger.error("Ordered consumer error, will restart:", err);
1643
+ this.logger.error(`Consumer ${label} error, will restart:`, err);
1545
1644
  this.eventBus.emit(
1546
1645
  "error" /* Error */,
1547
1646
  err instanceof Error ? err : new Error(String(err)),
1548
1647
  "message-provider"
1549
1648
  );
1550
- if (this.orderedReadyReject) {
1551
- this.orderedReadyReject(err);
1552
- this.orderedReadyReject = null;
1553
- this.orderedReadyResolve = null;
1554
- }
1649
+ onFirstError?.(err);
1650
+ onFirstError = void 0;
1555
1651
  return import_rxjs3.EMPTY;
1556
1652
  }),
1557
1653
  (0, import_rxjs3.repeat)({
1558
1654
  delay: () => {
1559
- if (!lastRunFailed) {
1560
- consecutiveFailures = 0;
1561
- }
1562
1655
  const delay = Math.min(100 * Math.pow(2, consecutiveFailures), 3e4);
1563
- this.logger.warn(`Ordered consumer stream ended, restarting in ${delay}ms...`);
1656
+ this.logger.warn(`Consumer ${label} stream ended, restarting in ${delay}ms...`);
1564
1657
  return (0, import_rxjs3.timer)(delay);
1565
1658
  }
1566
1659
  })
@@ -1568,7 +1661,7 @@ var MessageProvider = class {
1568
1661
  }
1569
1662
  /** Single iteration: create ordered consumer -> iterate messages. */
1570
1663
  async consumeOrderedOnce(streamName2, consumerOpts) {
1571
- const js = (await this.connection.getConnection()).jetstream();
1664
+ const js = this.connection.getJetStreamClient();
1572
1665
  const consumer = await js.consumers.get(streamName2, consumerOpts);
1573
1666
  const messages = await consumer.consume();
1574
1667
  if (this.orderedReadyResolve) {
@@ -1589,12 +1682,20 @@ var MessageProvider = class {
1589
1682
 
1590
1683
  // src/server/routing/pattern-registry.ts
1591
1684
  var import_common8 = require("@nestjs/common");
1685
+ var HANDLER_LABELS = {
1686
+ ["broadcast" /* Broadcast */]: "broadcast" /* Broadcast */,
1687
+ ["ordered" /* Ordered */]: "ordered" /* Ordered */,
1688
+ ["ev" /* Event */]: "event" /* Event */,
1689
+ ["cmd" /* Command */]: "rpc" /* Rpc */
1690
+ };
1592
1691
  var PatternRegistry = class {
1593
1692
  constructor(options) {
1594
1693
  this.options = options;
1595
1694
  }
1596
1695
  logger = new import_common8.Logger("Jetstream:PatternRegistry");
1597
1696
  registry = /* @__PURE__ */ new Map();
1697
+ // Cached after registerHandlers() — the registry is immutable from that point
1698
+ cachedPatterns = null;
1598
1699
  /**
1599
1700
  * Register all handlers from the NestJS strategy.
1600
1701
  *
@@ -1612,16 +1713,12 @@ var PatternRegistry = class {
1612
1713
  `Handler "${pattern}" cannot be both broadcast and ordered. Use one or the other.`
1613
1714
  );
1614
1715
  }
1615
- let fullSubject;
1616
- if (isBroadcast) {
1617
- fullSubject = buildBroadcastSubject(pattern);
1618
- } else if (isOrdered) {
1619
- fullSubject = buildSubject(serviceName, "ordered", pattern);
1620
- } else if (isEvent) {
1621
- fullSubject = buildSubject(serviceName, "ev", pattern);
1622
- } else {
1623
- fullSubject = buildSubject(serviceName, "cmd", pattern);
1624
- }
1716
+ let kind;
1717
+ if (isBroadcast) kind = "broadcast" /* Broadcast */;
1718
+ else if (isOrdered) kind = "ordered" /* Ordered */;
1719
+ else if (isEvent) kind = "ev" /* Event */;
1720
+ else kind = "cmd" /* Command */;
1721
+ const fullSubject = kind === "broadcast" /* Broadcast */ ? buildBroadcastSubject(pattern) : buildSubject(serviceName, kind, pattern);
1625
1722
  this.registry.set(fullSubject, {
1626
1723
  handler,
1627
1724
  pattern,
@@ -1629,18 +1726,9 @@ var PatternRegistry = class {
1629
1726
  isBroadcast,
1630
1727
  isOrdered
1631
1728
  });
1632
- let kind;
1633
- if (isBroadcast) {
1634
- kind = "broadcast";
1635
- } else if (isOrdered) {
1636
- kind = "ordered";
1637
- } else if (isEvent) {
1638
- kind = "event";
1639
- } else {
1640
- kind = "rpc";
1641
- }
1642
- this.logger.debug(`Registered ${kind}: ${pattern} -> ${fullSubject}`);
1729
+ this.logger.debug(`Registered ${HANDLER_LABELS[kind]}: ${pattern} -> ${fullSubject}`);
1643
1730
  }
1731
+ this.cachedPatterns = this.buildPatternsByKind();
1644
1732
  this.logSummary();
1645
1733
  }
1646
1734
  /** Find handler for a full NATS subject. */
@@ -1649,33 +1737,53 @@ var PatternRegistry = class {
1649
1737
  }
1650
1738
  /** Get all registered broadcast patterns (for consumer filter_subject setup). */
1651
1739
  getBroadcastPatterns() {
1652
- return Array.from(this.registry.values()).filter((r) => r.isBroadcast).map((r) => `broadcast.${r.pattern}`);
1740
+ return this.getPatternsByKind().broadcasts.map((p) => buildBroadcastSubject(p));
1653
1741
  }
1654
- /** Check if any broadcast handlers are registered. */
1655
1742
  hasBroadcastHandlers() {
1656
- return Array.from(this.registry.values()).some((r) => r.isBroadcast);
1743
+ return this.getPatternsByKind().broadcasts.length > 0;
1657
1744
  }
1658
- /** Check if any RPC (command) handlers are registered. */
1659
1745
  hasRpcHandlers() {
1660
- return Array.from(this.registry.values()).some(
1661
- (r) => !r.isEvent && !r.isBroadcast && !r.isOrdered
1662
- );
1746
+ return this.getPatternsByKind().commands.length > 0;
1663
1747
  }
1664
- /** Check if any workqueue event handlers are registered. */
1665
1748
  hasEventHandlers() {
1666
- return Array.from(this.registry.values()).some((r) => r.isEvent && !r.isBroadcast);
1749
+ return this.getPatternsByKind().events.length > 0;
1667
1750
  }
1668
- /** Check if any ordered event handlers are registered. */
1669
1751
  hasOrderedHandlers() {
1670
- return Array.from(this.registry.values()).some((r) => r.isOrdered);
1752
+ return this.getPatternsByKind().ordered.length > 0;
1671
1753
  }
1672
1754
  /** Get fully-qualified NATS subjects for ordered handlers. */
1673
1755
  getOrderedSubjects() {
1674
- const name = internalName(this.options.name);
1675
- return Array.from(this.registry.values()).filter((r) => r.isOrdered).map((r) => `${name}.ordered.${r.pattern}`);
1756
+ return this.getPatternsByKind().ordered.map(
1757
+ (p) => buildSubject(this.options.name, "ordered" /* Ordered */, p)
1758
+ );
1676
1759
  }
1677
- /** Get patterns grouped by kind. */
1760
+ /** Get patterns grouped by kind (cached after registration). */
1678
1761
  getPatternsByKind() {
1762
+ const patterns = this.cachedPatterns ?? this.buildPatternsByKind();
1763
+ return {
1764
+ events: [...patterns.events],
1765
+ commands: [...patterns.commands],
1766
+ broadcasts: [...patterns.broadcasts],
1767
+ ordered: [...patterns.ordered]
1768
+ };
1769
+ }
1770
+ /** Normalize a full NATS subject back to the user-facing pattern. */
1771
+ normalizeSubject(subject) {
1772
+ const name = internalName(this.options.name);
1773
+ const prefixes = [
1774
+ `${name}.${"cmd" /* Command */}.`,
1775
+ `${name}.${"ev" /* Event */}.`,
1776
+ `${name}.${"ordered" /* Ordered */}.`,
1777
+ `${"broadcast" /* Broadcast */}.`
1778
+ ];
1779
+ for (const prefix of prefixes) {
1780
+ if (subject.startsWith(prefix)) {
1781
+ return subject.slice(prefix.length);
1782
+ }
1783
+ }
1784
+ return subject;
1785
+ }
1786
+ buildPatternsByKind() {
1679
1787
  const events = [];
1680
1788
  const commands = [];
1681
1789
  const broadcasts = [];
@@ -1688,18 +1796,6 @@ var PatternRegistry = class {
1688
1796
  }
1689
1797
  return { events, commands, broadcasts, ordered };
1690
1798
  }
1691
- /** Normalize a full NATS subject back to the user-facing pattern. */
1692
- normalizeSubject(subject) {
1693
- const name = internalName(this.options.name);
1694
- const prefixes = [`${name}.cmd.`, `${name}.ev.`, `${name}.ordered.`, "broadcast."];
1695
- for (const prefix of prefixes) {
1696
- if (subject.startsWith(prefix)) {
1697
- return subject.slice(prefix.length);
1698
- }
1699
- }
1700
- return subject;
1701
- }
1702
- /** Log a summary of all registered handlers. */
1703
1799
  logSummary() {
1704
1800
  const { events, commands, broadcasts, ordered } = this.getPatternsByKind();
1705
1801
  const parts = [
@@ -1718,12 +1814,14 @@ var PatternRegistry = class {
1718
1814
  var import_common9 = require("@nestjs/common");
1719
1815
  var import_rxjs4 = require("rxjs");
1720
1816
  var EventRouter = class {
1721
- constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig) {
1817
+ constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig, processingConfig, ackWaitMap) {
1722
1818
  this.messageProvider = messageProvider;
1723
1819
  this.patternRegistry = patternRegistry;
1724
1820
  this.codec = codec;
1725
1821
  this.eventBus = eventBus;
1726
1822
  this.deadLetterConfig = deadLetterConfig;
1823
+ this.processingConfig = processingConfig;
1824
+ this.ackWaitMap = ackWaitMap;
1727
1825
  }
1728
1826
  logger = new import_common9.Logger("Jetstream:EventRouter");
1729
1827
  subscriptions = [];
@@ -1737,10 +1835,10 @@ var EventRouter = class {
1737
1835
  }
1738
1836
  /** Start routing event, broadcast, and ordered messages to handlers. */
1739
1837
  start() {
1740
- this.subscribeToStream(this.messageProvider.events$, "workqueue");
1741
- this.subscribeToStream(this.messageProvider.broadcasts$, "broadcast");
1838
+ this.subscribeToStream(this.messageProvider.events$, "ev" /* Event */);
1839
+ this.subscribeToStream(this.messageProvider.broadcasts$, "broadcast" /* Broadcast */);
1742
1840
  if (this.patternRegistry.hasOrderedHandlers()) {
1743
- this.subscribeToStream(this.messageProvider.ordered$, "ordered", true);
1841
+ this.subscribeToStream(this.messageProvider.ordered$, "ordered" /* Ordered */);
1744
1842
  }
1745
1843
  }
1746
1844
  /** Stop routing and unsubscribe from all streams. */
@@ -1751,38 +1849,71 @@ var EventRouter = class {
1751
1849
  this.subscriptions.length = 0;
1752
1850
  }
1753
1851
  /** Subscribe to a message stream and route each message. */
1754
- subscribeToStream(stream$, label, isOrdered = false) {
1755
- const route = (msg) => (0, import_rxjs4.defer)(() => isOrdered ? this.handleOrdered(msg) : this.handle(msg)).pipe(
1852
+ subscribeToStream(stream$, kind) {
1853
+ const isOrdered = kind === "ordered" /* Ordered */;
1854
+ const ackExtensionInterval = isOrdered ? null : resolveAckExtensionInterval(this.getAckExtensionConfig(kind), this.ackWaitMap?.get(kind));
1855
+ const concurrency = this.getConcurrency(kind);
1856
+ const route = (msg) => (0, import_rxjs4.defer)(
1857
+ () => isOrdered ? this.handleOrdered(msg) : this.handle(msg, ackExtensionInterval)
1858
+ ).pipe(
1756
1859
  (0, import_rxjs4.catchError)((err) => {
1757
- this.logger.error(`Unexpected error in ${label} event router`, err);
1860
+ this.logger.error(`Unexpected error in ${kind} event router`, err);
1758
1861
  return import_rxjs4.EMPTY;
1759
1862
  })
1760
1863
  );
1761
- const subscription = stream$.pipe(isOrdered ? (0, import_rxjs4.concatMap)(route) : (0, import_rxjs4.mergeMap)(route)).subscribe();
1864
+ const subscription = stream$.pipe(isOrdered ? (0, import_rxjs4.concatMap)(route) : (0, import_rxjs4.mergeMap)(route, concurrency)).subscribe();
1762
1865
  this.subscriptions.push(subscription);
1763
1866
  }
1867
+ getConcurrency(kind) {
1868
+ if (kind === "ev" /* Event */) return this.processingConfig?.events?.concurrency;
1869
+ if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.concurrency;
1870
+ return void 0;
1871
+ }
1872
+ getAckExtensionConfig(kind) {
1873
+ if (kind === "ev" /* Event */) return this.processingConfig?.events?.ackExtension;
1874
+ if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.ackExtension;
1875
+ return void 0;
1876
+ }
1764
1877
  /** Handle a single event message: decode -> execute handler -> ack/nak. */
1765
- handle(msg) {
1878
+ handle(msg, ackExtensionInterval) {
1879
+ const resolved = this.decodeMessage(msg);
1880
+ if (!resolved) return import_rxjs4.EMPTY;
1881
+ return (0, import_rxjs4.from)(
1882
+ this.executeHandler(resolved.handler, resolved.data, resolved.ctx, msg, ackExtensionInterval)
1883
+ );
1884
+ }
1885
+ /** Handle an ordered message: decode -> execute handler -> no ack/nak. */
1886
+ handleOrdered(msg) {
1887
+ const resolved = this.decodeMessage(msg, true);
1888
+ if (!resolved) return import_rxjs4.EMPTY;
1889
+ return (0, import_rxjs4.from)(
1890
+ unwrapResult(resolved.handler(resolved.data, resolved.ctx)).catch((err) => {
1891
+ this.logger.error(`Ordered handler error (${msg.subject}):`, err);
1892
+ })
1893
+ );
1894
+ }
1895
+ /** Resolve handler, decode payload, and build context. Returns null on failure. */
1896
+ decodeMessage(msg, isOrdered = false) {
1766
1897
  const handler = this.patternRegistry.getHandler(msg.subject);
1767
1898
  if (!handler) {
1768
- msg.term(`No handler for event: ${msg.subject}`);
1769
- this.logger.error(`No handler for event subject: ${msg.subject}`);
1770
- return import_rxjs4.EMPTY;
1899
+ if (!isOrdered) msg.term(`No handler for event: ${msg.subject}`);
1900
+ this.logger.error(`No handler for subject: ${msg.subject}`);
1901
+ return null;
1771
1902
  }
1772
1903
  let data;
1773
1904
  try {
1774
1905
  data = this.codec.decode(msg.data);
1775
1906
  } catch (err) {
1776
- msg.term("Decode error");
1907
+ if (!isOrdered) msg.term("Decode error");
1777
1908
  this.logger.error(`Decode error for ${msg.subject}:`, err);
1778
- return import_rxjs4.EMPTY;
1909
+ return null;
1779
1910
  }
1780
- this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "event");
1781
- const ctx = new RpcContext([msg]);
1782
- return (0, import_rxjs4.from)(this.executeHandler(handler, data, ctx, msg));
1911
+ this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "event" /* Event */);
1912
+ return { handler, data, ctx: new RpcContext([msg]) };
1783
1913
  }
1784
1914
  /** Execute handler, then ack on success or nak/dead-letter on failure. */
1785
- async executeHandler(handler, data, ctx, msg) {
1915
+ async executeHandler(handler, data, ctx, msg, ackExtensionInterval) {
1916
+ const stopAckExtension = startAckExtensionTimer(msg, ackExtensionInterval);
1786
1917
  try {
1787
1918
  await unwrapResult(handler(data, ctx));
1788
1919
  msg.ack();
@@ -1793,30 +1924,10 @@ var EventRouter = class {
1793
1924
  } else {
1794
1925
  msg.nak();
1795
1926
  }
1927
+ } finally {
1928
+ stopAckExtension?.();
1796
1929
  }
1797
1930
  }
1798
- /** Handle an ordered message: decode -> execute handler -> no ack/nak. */
1799
- handleOrdered(msg) {
1800
- const handler = this.patternRegistry.getHandler(msg.subject);
1801
- if (!handler) {
1802
- this.logger.error(`No handler for ordered subject: ${msg.subject}`);
1803
- return import_rxjs4.EMPTY;
1804
- }
1805
- let data;
1806
- try {
1807
- data = this.codec.decode(msg.data);
1808
- } catch (err) {
1809
- this.logger.error(`Decode error for ordered ${msg.subject}:`, err);
1810
- return import_rxjs4.EMPTY;
1811
- }
1812
- this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "event");
1813
- const ctx = new RpcContext([msg]);
1814
- return (0, import_rxjs4.from)(
1815
- unwrapResult(handler(data, ctx)).catch((err) => {
1816
- this.logger.error(`Ordered handler error (${msg.subject}):`, err);
1817
- })
1818
- );
1819
- }
1820
1931
  /** Check if the message has exhausted all delivery attempts. */
1821
1932
  isDeadLetter(msg) {
1822
1933
  if (!this.deadLetterConfig) return false;
@@ -1837,7 +1948,10 @@ var EventRouter = class {
1837
1948
  timestamp: new Date(msg.info.timestampNanos / 1e6).toISOString()
1838
1949
  };
1839
1950
  this.eventBus.emit("deadLetter" /* DeadLetter */, info);
1840
- if (!this.deadLetterConfig) return;
1951
+ if (!this.deadLetterConfig) {
1952
+ msg.term("Dead letter config unavailable");
1953
+ return;
1954
+ }
1841
1955
  try {
1842
1956
  await this.deadLetterConfig.onDeadLetter(info);
1843
1957
  msg.term("Dead letter processed");
@@ -1853,17 +1967,31 @@ var import_common10 = require("@nestjs/common");
1853
1967
  var import_nats9 = require("nats");
1854
1968
  var import_rxjs5 = require("rxjs");
1855
1969
  var RpcRouter = class {
1856
- constructor(messageProvider, patternRegistry, connection, codec, eventBus, timeout) {
1970
+ constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap) {
1857
1971
  this.messageProvider = messageProvider;
1858
1972
  this.patternRegistry = patternRegistry;
1859
1973
  this.connection = connection;
1860
1974
  this.codec = codec;
1861
1975
  this.eventBus = eventBus;
1862
- this.timeout = timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT;
1976
+ this.rpcOptions = rpcOptions;
1977
+ this.ackWaitMap = ackWaitMap;
1978
+ this.timeout = rpcOptions?.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT;
1979
+ this.concurrency = rpcOptions?.concurrency;
1863
1980
  }
1864
1981
  logger = new import_common10.Logger("Jetstream:RpcRouter");
1865
1982
  timeout;
1983
+ concurrency;
1984
+ resolvedAckExtensionInterval;
1866
1985
  subscription = null;
1986
+ /** Lazily resolve the ack extension interval (needs ackWaitMap populated at runtime). */
1987
+ get ackExtensionInterval() {
1988
+ if (this.resolvedAckExtensionInterval !== void 0) return this.resolvedAckExtensionInterval;
1989
+ this.resolvedAckExtensionInterval = resolveAckExtensionInterval(
1990
+ this.rpcOptions?.ackExtension,
1991
+ this.ackWaitMap?.get("cmd" /* Command */)
1992
+ );
1993
+ return this.resolvedAckExtensionInterval;
1994
+ }
1867
1995
  /** Start routing command messages to handlers. */
1868
1996
  start() {
1869
1997
  this.subscription = this.messageProvider.commands$.pipe(
@@ -1873,7 +2001,8 @@ var RpcRouter = class {
1873
2001
  this.logger.error("Unexpected error in RPC router", err);
1874
2002
  return import_rxjs5.EMPTY;
1875
2003
  })
1876
- )
2004
+ ),
2005
+ this.concurrency
1877
2006
  )
1878
2007
  ).subscribe();
1879
2008
  }
@@ -1905,7 +2034,7 @@ var RpcRouter = class {
1905
2034
  this.logger.error(`Decode error for RPC ${msg.subject}:`, err);
1906
2035
  return import_rxjs5.EMPTY;
1907
2036
  }
1908
- this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc");
2037
+ this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc" /* Rpc */);
1909
2038
  return (0, import_rxjs5.from)(this.executeHandler(handler, data, msg, replyTo, correlationId));
1910
2039
  }
1911
2040
  /** Execute handler, publish response, settle message. */
@@ -1915,9 +2044,11 @@ var RpcRouter = class {
1915
2044
  const hdrs = (0, import_nats9.headers)();
1916
2045
  hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
1917
2046
  let settled = false;
2047
+ const stopAckExtension = startAckExtensionTimer(msg, this.ackExtensionInterval);
1918
2048
  const timeoutId = setTimeout(() => {
1919
2049
  if (settled) return;
1920
2050
  settled = true;
2051
+ stopAckExtension?.();
1921
2052
  this.logger.error(`RPC timeout (${this.timeout}ms): ${msg.subject}`);
1922
2053
  this.eventBus.emit("rpcTimeout" /* RpcTimeout */, msg.subject, correlationId);
1923
2054
  msg.term("Handler timeout");
@@ -1927,6 +2058,7 @@ var RpcRouter = class {
1927
2058
  if (settled) return;
1928
2059
  settled = true;
1929
2060
  clearTimeout(timeoutId);
2061
+ stopAckExtension?.();
1930
2062
  msg.ack();
1931
2063
  try {
1932
2064
  nc.publish(replyTo, this.codec.encode(result), { headers: hdrs });
@@ -1937,6 +2069,7 @@ var RpcRouter = class {
1937
2069
  if (settled) return;
1938
2070
  settled = true;
1939
2071
  clearTimeout(timeoutId);
2072
+ stopAckExtension?.();
1940
2073
  try {
1941
2074
  hdrs.set("x-error" /* Error */, "true");
1942
2075
  nc.publish(replyTo, this.codec.encode(serializeError(err)), { headers: hdrs });
@@ -1960,7 +2093,7 @@ var ShutdownManager = class {
1960
2093
  /**
1961
2094
  * Execute the full shutdown sequence.
1962
2095
  *
1963
- * @param strategy Optional strategy to close (stops consumers and subscriptions).
2096
+ * @param strategy Optional stoppable to close (stops consumers and subscriptions).
1964
2097
  */
1965
2098
  async shutdown(strategy) {
1966
2099
  this.eventBus.emit("shutdownStart" /* ShutdownStart */);
@@ -1983,6 +2116,7 @@ var ShutdownManager = class {
1983
2116
  };
1984
2117
 
1985
2118
  // src/jetstream.module.ts
2119
+ var JETSTREAM_ACK_WAIT_MAP = /* @__PURE__ */ Symbol("JETSTREAM_ACK_WAIT_MAP");
1986
2120
  var JetstreamModule = class {
1987
2121
  constructor(shutdownManager, strategy) {
1988
2122
  this.shutdownManager = shutdownManager;
@@ -2167,13 +2301,26 @@ var JetstreamModule = class {
2167
2301
  return new ConsumerProvider(options, connection, streamProvider, patternRegistry);
2168
2302
  }
2169
2303
  },
2304
+ // Shared ack_wait map — populated by strategy after ensureConsumers()
2305
+ {
2306
+ provide: JETSTREAM_ACK_WAIT_MAP,
2307
+ useFactory: () => /* @__PURE__ */ new Map()
2308
+ },
2170
2309
  // MessageProvider — pull-based message consumption
2171
2310
  {
2172
2311
  provide: MessageProvider,
2173
2312
  inject: [JETSTREAM_OPTIONS, JETSTREAM_CONNECTION, JETSTREAM_EVENT_BUS],
2174
2313
  useFactory: (options, connection, eventBus) => {
2175
2314
  if (options.consumer === false) return null;
2176
- return new MessageProvider(connection, eventBus);
2315
+ const consumeOptionsMap = /* @__PURE__ */ new Map();
2316
+ if (options.events?.consume)
2317
+ consumeOptionsMap.set("ev" /* Event */, options.events.consume);
2318
+ if (options.broadcast?.consume)
2319
+ consumeOptionsMap.set("broadcast" /* Broadcast */, options.broadcast.consume);
2320
+ if (options.rpc?.mode === "jetstream" && options.rpc.consume) {
2321
+ consumeOptionsMap.set("cmd" /* Command */, options.rpc.consume);
2322
+ }
2323
+ return new MessageProvider(connection, eventBus, consumeOptionsMap);
2177
2324
  }
2178
2325
  },
2179
2326
  // EventRouter — routes event and broadcast messages to handlers
@@ -2184,20 +2331,33 @@ var JetstreamModule = class {
2184
2331
  MessageProvider,
2185
2332
  PatternRegistry,
2186
2333
  JETSTREAM_CODEC,
2187
- JETSTREAM_EVENT_BUS
2334
+ JETSTREAM_EVENT_BUS,
2335
+ JETSTREAM_ACK_WAIT_MAP
2188
2336
  ],
2189
- useFactory: (options, messageProvider, patternRegistry, codec, eventBus) => {
2337
+ useFactory: (options, messageProvider, patternRegistry, codec, eventBus, ackWaitMap) => {
2190
2338
  if (options.consumer === false) return null;
2191
2339
  const deadLetterConfig = options.onDeadLetter ? {
2192
2340
  maxDeliverByStream: /* @__PURE__ */ new Map(),
2193
2341
  onDeadLetter: options.onDeadLetter
2194
2342
  } : void 0;
2343
+ const processingConfig = {
2344
+ events: {
2345
+ concurrency: options.events?.concurrency,
2346
+ ackExtension: options.events?.ackExtension
2347
+ },
2348
+ broadcast: {
2349
+ concurrency: options.broadcast?.concurrency,
2350
+ ackExtension: options.broadcast?.ackExtension
2351
+ }
2352
+ };
2195
2353
  return new EventRouter(
2196
2354
  messageProvider,
2197
2355
  patternRegistry,
2198
2356
  codec,
2199
2357
  eventBus,
2200
- deadLetterConfig
2358
+ deadLetterConfig,
2359
+ processingConfig,
2360
+ ackWaitMap
2201
2361
  );
2202
2362
  }
2203
2363
  },
@@ -2210,18 +2370,24 @@ var JetstreamModule = class {
2210
2370
  PatternRegistry,
2211
2371
  JETSTREAM_CONNECTION,
2212
2372
  JETSTREAM_CODEC,
2213
- JETSTREAM_EVENT_BUS
2373
+ JETSTREAM_EVENT_BUS,
2374
+ JETSTREAM_ACK_WAIT_MAP
2214
2375
  ],
2215
- useFactory: (options, messageProvider, patternRegistry, connection, codec, eventBus) => {
2376
+ useFactory: (options, messageProvider, patternRegistry, connection, codec, eventBus, ackWaitMap) => {
2216
2377
  if (options.consumer === false) return null;
2217
- const timeout = options.rpc?.mode === "jetstream" ? options.rpc.timeout : void 0;
2378
+ const rpcOptions = options.rpc?.mode === "jetstream" ? {
2379
+ timeout: options.rpc.timeout,
2380
+ concurrency: options.rpc.concurrency,
2381
+ ackExtension: options.rpc.ackExtension
2382
+ } : void 0;
2218
2383
  return new RpcRouter(
2219
2384
  messageProvider,
2220
2385
  patternRegistry,
2221
2386
  connection,
2222
2387
  codec,
2223
2388
  eventBus,
2224
- timeout
2389
+ rpcOptions,
2390
+ ackWaitMap
2225
2391
  );
2226
2392
  }
2227
2393
  },
@@ -2252,9 +2418,10 @@ var JetstreamModule = class {
2252
2418
  MessageProvider,
2253
2419
  EventRouter,
2254
2420
  RpcRouter,
2255
- CoreRpcServer
2421
+ CoreRpcServer,
2422
+ JETSTREAM_ACK_WAIT_MAP
2256
2423
  ],
2257
- useFactory: (options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer) => {
2424
+ useFactory: (options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer, ackWaitMap) => {
2258
2425
  if (options.consumer === false) return null;
2259
2426
  return new JetstreamStrategy(
2260
2427
  options,
@@ -2265,7 +2432,8 @@ var JetstreamModule = class {
2265
2432
  messageProvider,
2266
2433
  eventRouter,
2267
2434
  rpcRouter,
2268
- coreRpcServer
2435
+ coreRpcServer,
2436
+ ackWaitMap
2269
2437
  );
2270
2438
  }
2271
2439
  }
@@ -2346,8 +2514,13 @@ JetstreamModule = __decorateClass([
2346
2514
  JetstreamRecordBuilder,
2347
2515
  JetstreamStrategy,
2348
2516
  JsonCodec,
2517
+ MessageKind,
2518
+ PatternPrefix,
2349
2519
  RpcContext,
2520
+ StreamKind,
2350
2521
  TransportEvent,
2351
2522
  getClientToken,
2523
+ isCoreRpcMode,
2524
+ isJetStreamRpcMode,
2352
2525
  toNanos
2353
2526
  });