@horizon-republic/nestjs-jetstream 2.9.0 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -398,6 +398,7 @@ var nanosToGoDuration = (nanos) => {
398
398
  };
399
399
 
400
400
  // src/client/jetstream.client.ts
401
+ var BROADCAST_SUBJECT_PREFIX = "broadcast.";
401
402
  var JetstreamClient = class extends ClientProxy {
402
403
  constructor(rootOptions, targetServiceName, connection, codec, eventBus) {
403
404
  super();
@@ -407,12 +408,33 @@ var JetstreamClient = class extends ClientProxy {
407
408
  this.eventBus = eventBus;
408
409
  this.targetName = targetServiceName;
409
410
  this.callerName = internalName(this.rootOptions.name);
411
+ const targetInternal = internalName(targetServiceName);
412
+ this.eventSubjectPrefix = `${targetInternal}.${"ev" /* Event */}.`;
413
+ this.commandSubjectPrefix = `${targetInternal}.${"cmd" /* Command */}.`;
414
+ this.orderedSubjectPrefix = `${targetInternal}.${"ordered" /* Ordered */}.`;
415
+ this.isCoreMode = isCoreRpcMode(this.rootOptions.rpc);
416
+ this.defaultRpcTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? this.rootOptions.rpc?.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT : this.rootOptions.rpc?.timeout ?? DEFAULT_RPC_TIMEOUT;
410
417
  }
411
418
  logger = new Logger("Jetstream:Client");
412
419
  /** Target service name this client sends messages to. */
413
420
  targetName;
414
421
  /** Pre-cached caller name derived from rootOptions.name, computed once in constructor. */
415
422
  callerName;
423
+ /**
424
+ * Subject prefixes of the form `{serviceName}__microservice.{kind}.` — one
425
+ * per stream kind this client may publish to. Built once in the constructor
426
+ * so producing a full subject is a single string concat with the user pattern.
427
+ */
428
+ eventSubjectPrefix;
429
+ commandSubjectPrefix;
430
+ orderedSubjectPrefix;
431
+ /**
432
+ * RPC configuration snapshots. The values are derived from rootOptions at
433
+ * construction time so the publish hot path never has to re-run
434
+ * isCoreRpcMode / getRpcTimeout on every call.
435
+ */
436
+ isCoreMode;
437
+ defaultRpcTimeout;
416
438
  /** Shared inbox for JetStream-mode RPC responses. */
417
439
  inbox = null;
418
440
  inboxSubscription = null;
@@ -422,6 +444,12 @@ var JetstreamClient = class extends ClientProxy {
422
444
  pendingTimeouts = /* @__PURE__ */ new Map();
423
445
  /** Subscription to connection status events for disconnect handling. */
424
446
  statusSubscription = null;
447
+ /**
448
+ * Cached readiness flag. Once `connect()` has wired the inbox and status
449
+ * subscription, subsequent publishes skip the `await connect()` microtask
450
+ * and reach for the underlying connection synchronously instead.
451
+ */
452
+ readyForPublish = false;
425
453
  /**
426
454
  * Establish connection. Called automatically by NestJS on first use.
427
455
  *
@@ -432,7 +460,7 @@ var JetstreamClient = class extends ClientProxy {
432
460
  */
433
461
  async connect() {
434
462
  const nc = await this.connection.getConnection();
435
- if (isJetStreamRpcMode(this.rootOptions.rpc) && !this.inboxSubscription) {
463
+ if (!this.isCoreMode && !this.inboxSubscription) {
436
464
  this.setupInbox(nc);
437
465
  }
438
466
  this.statusSubscription ??= this.connection.status$.subscribe((status) => {
@@ -440,12 +468,14 @@ var JetstreamClient = class extends ClientProxy {
440
468
  this.handleDisconnect();
441
469
  }
442
470
  });
471
+ this.readyForPublish = true;
443
472
  return nc;
444
473
  }
445
474
  /** Clean up resources: reject pending RPCs, unsubscribe from status events. */
446
475
  async close() {
447
476
  this.statusSubscription?.unsubscribe();
448
477
  this.statusSubscription = null;
478
+ this.readyForPublish = false;
449
479
  this.rejectPendingRpcs(new Error("Client closed"));
450
480
  }
451
481
  /**
@@ -469,7 +499,7 @@ var JetstreamClient = class extends ClientProxy {
469
499
  * set to the original event subject.
470
500
  */
471
501
  async dispatchEvent(packet) {
472
- await this.connect();
502
+ if (!this.readyForPublish) await this.connect();
473
503
  const { data, hdrs, messageId, schedule, ttl } = this.extractRecordData(packet.data);
474
504
  const eventSubject = this.buildEventSubject(packet.pattern);
475
505
  const msgHeaders = this.buildHeaders(hdrs, { subject: eventSubject });
@@ -508,7 +538,7 @@ var JetstreamClient = class extends ClientProxy {
508
538
  * JetStream mode: publishes to stream + waits for inbox response.
509
539
  */
510
540
  publish(packet, callback) {
511
- const subject = buildSubject(this.targetName, "cmd" /* Command */, packet.pattern);
541
+ const subject = this.commandSubjectPrefix + packet.pattern;
512
542
  const { data, hdrs, timeout, messageId, schedule, ttl } = this.extractRecordData(packet.data);
513
543
  if (schedule) {
514
544
  this.logger.warn(
@@ -525,7 +555,7 @@ var JetstreamClient = class extends ClientProxy {
525
555
  callback({ err: new Error("Internal transport error"), response: null, isDisposed: true });
526
556
  };
527
557
  let jetStreamCorrelationId = null;
528
- if (isCoreRpcMode(this.rootOptions.rpc)) {
558
+ if (this.isCoreMode) {
529
559
  this.publishCoreRpc(subject, data, hdrs, timeout, callback).catch(onUnhandled);
530
560
  } else {
531
561
  jetStreamCorrelationId = nuid.next();
@@ -550,8 +580,8 @@ var JetstreamClient = class extends ClientProxy {
550
580
  /** Core mode: nc.request() with timeout. */
551
581
  async publishCoreRpc(subject, data, customHeaders, timeout, callback) {
552
582
  try {
553
- const nc = await this.connect();
554
- const effectiveTimeout = timeout ?? this.getRpcTimeout();
583
+ const nc = this.readyForPublish ? this.connection.unwrap : await this.connect();
584
+ const effectiveTimeout = timeout ?? this.defaultRpcTimeout;
555
585
  const hdrs = this.buildHeaders(customHeaders, { subject });
556
586
  const response = await nc.request(subject, this.codec.encode(data), {
557
587
  timeout: effectiveTimeout,
@@ -573,10 +603,10 @@ var JetstreamClient = class extends ClientProxy {
573
603
  /** JetStream mode: publish to stream + wait for inbox response. */
574
604
  async publishJetStreamRpc(subject, data, callback, options) {
575
605
  const { headers: customHeaders, correlationId, messageId } = options;
576
- const effectiveTimeout = options.timeout ?? this.getRpcTimeout();
606
+ const effectiveTimeout = options.timeout ?? this.defaultRpcTimeout;
577
607
  this.pendingMessages.set(correlationId, callback);
578
608
  try {
579
- await this.connect();
609
+ if (!this.readyForPublish) await this.connect();
580
610
  if (!this.pendingMessages.has(correlationId)) return;
581
611
  if (!this.inbox) {
582
612
  this.pendingMessages.delete(correlationId);
@@ -622,6 +652,7 @@ var JetstreamClient = class extends ClientProxy {
622
652
  handleDisconnect() {
623
653
  this.rejectPendingRpcs(new Error("Connection lost"));
624
654
  this.inbox = null;
655
+ this.readyForPublish = false;
625
656
  }
626
657
  /** Reject all pending RPC callbacks, clear timeouts, and tear down inbox. */
627
658
  rejectPendingRpcs(error) {
@@ -685,19 +716,22 @@ var JetstreamClient = class extends ClientProxy {
685
716
  this.pendingMessages.delete(correlationId);
686
717
  }
687
718
  }
688
- /** Build event subject — workqueue, broadcast, or ordered. */
719
+ /**
720
+ * Resolve a user pattern to a fully-qualified NATS subject, dispatching
721
+ * between the event, broadcast, and ordered prefixes.
722
+ *
723
+ * The leading-char check short-circuits the `startsWith` comparisons for
724
+ * patterns that cannot possibly carry a broadcast/ordered marker, which is
725
+ * the overwhelmingly common case.
726
+ */
689
727
  buildEventSubject(pattern) {
690
- if (pattern.startsWith("broadcast:" /* Broadcast */)) {
691
- return buildBroadcastSubject(pattern.slice("broadcast:" /* Broadcast */.length));
692
- }
693
- if (pattern.startsWith("ordered:" /* Ordered */)) {
694
- return buildSubject(
695
- this.targetName,
696
- "ordered" /* Ordered */,
697
- pattern.slice("ordered:" /* Ordered */.length)
698
- );
728
+ if (pattern.charCodeAt(0) === 98 && pattern.startsWith("broadcast:" /* Broadcast */)) {
729
+ return BROADCAST_SUBJECT_PREFIX + pattern.slice("broadcast:" /* Broadcast */.length);
699
730
  }
700
- return buildSubject(this.targetName, "ev" /* Event */, pattern);
731
+ if (pattern.charCodeAt(0) === 111 && pattern.startsWith("ordered:" /* Ordered */)) {
732
+ return this.orderedSubjectPrefix + pattern.slice("ordered:" /* Ordered */.length);
733
+ }
734
+ return this.eventSubjectPrefix + pattern;
701
735
  }
702
736
  /** Build NATS headers merging custom headers with transport headers. */
703
737
  buildHeaders(customHeaders, transport) {
@@ -722,7 +756,7 @@ var JetstreamClient = class extends ClientProxy {
722
756
  if (rawData instanceof JetstreamRecord) {
723
757
  return {
724
758
  data: rawData.data,
725
- hdrs: rawData.headers.size > 0 ? new Map(rawData.headers) : null,
759
+ hdrs: rawData.headers.size > 0 ? rawData.headers : null,
726
760
  timeout: rawData.timeout,
727
761
  messageId: rawData.messageId,
728
762
  schedule: rawData.schedule,
@@ -765,11 +799,6 @@ var JetstreamClient = class extends ClientProxy {
765
799
  const pattern = withoutPrefix.slice(dotIndex + 1);
766
800
  return `${targetPrefix}_sch.${pattern}`;
767
801
  }
768
- getRpcTimeout() {
769
- if (!this.rootOptions.rpc) return DEFAULT_RPC_TIMEOUT;
770
- const defaultTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? DEFAULT_JETSTREAM_RPC_TIMEOUT : DEFAULT_RPC_TIMEOUT;
771
- return this.rootOptions.rpc.timeout ?? defaultTimeout;
772
- }
773
802
  };
774
803
 
775
804
  // src/codec/json.codec.ts
@@ -784,6 +813,19 @@ var JsonCodec = class {
784
813
  }
785
814
  };
786
815
 
816
+ // src/codec/msgpack.codec.ts
817
+ var MsgpackCodec = class {
818
+ constructor(packr) {
819
+ this.packr = packr;
820
+ }
821
+ encode(data) {
822
+ return this.packr.pack(data);
823
+ }
824
+ decode(data) {
825
+ return this.packr.unpack(data);
826
+ }
827
+ };
828
+
787
829
  // src/connection/connection.provider.ts
788
830
  import { Logger as Logger2 } from "@nestjs/common";
789
831
  import {
@@ -1001,6 +1043,15 @@ var EventBus = class {
1001
1043
  kind
1002
1044
  );
1003
1045
  }
1046
+ /**
1047
+ * Check whether a hook is registered for the given event.
1048
+ *
1049
+ * Used by the routing hot path to elide the emit call entirely when the
1050
+ * transport owner did not register a listener.
1051
+ */
1052
+ hasHook(event) {
1053
+ return this.hooks[event] !== void 0;
1054
+ }
1004
1055
  callHook(event, hook, ...args) {
1005
1056
  try {
1006
1057
  const result = hook(...args);
@@ -1383,16 +1434,71 @@ var resolveAckExtensionInterval = (config, ackWaitNanos) => {
1383
1434
  const interval = Math.floor(ackWaitNanos / 1e6 / 2);
1384
1435
  return Math.max(interval, MIN_ACK_EXTENSION_INTERVAL);
1385
1436
  };
1437
+ var AckExtensionPool = class {
1438
+ entries = /* @__PURE__ */ new Set();
1439
+ handle = null;
1440
+ handleFireAt = 0;
1441
+ schedule(msg, interval) {
1442
+ const entry = {
1443
+ msg,
1444
+ interval,
1445
+ nextFireAt: Date.now() + interval,
1446
+ active: true
1447
+ };
1448
+ this.entries.add(entry);
1449
+ this.ensureWake(entry.nextFireAt);
1450
+ return entry;
1451
+ }
1452
+ cancel(entry) {
1453
+ if (!entry.active) return;
1454
+ entry.active = false;
1455
+ this.entries.delete(entry);
1456
+ if (this.entries.size === 0 && this.handle !== null) {
1457
+ clearTimeout(this.handle);
1458
+ this.handle = null;
1459
+ this.handleFireAt = 0;
1460
+ }
1461
+ }
1462
+ /**
1463
+ * Ensure the shared timer will fire no later than `dueAt`. If an earlier
1464
+ * wake is already scheduled, leave it; otherwise replace it with a tighter one.
1465
+ */
1466
+ ensureWake(dueAt) {
1467
+ if (this.handle !== null && this.handleFireAt <= dueAt) return;
1468
+ if (this.handle !== null) clearTimeout(this.handle);
1469
+ const delay = Math.max(0, dueAt - Date.now());
1470
+ const handle = setTimeout(() => {
1471
+ this.tick();
1472
+ }, delay);
1473
+ if (typeof handle.unref === "function") handle.unref();
1474
+ this.handle = handle;
1475
+ this.handleFireAt = dueAt;
1476
+ }
1477
+ tick() {
1478
+ this.handle = null;
1479
+ this.handleFireAt = 0;
1480
+ const now = Date.now();
1481
+ let earliest = Infinity;
1482
+ for (const entry of this.entries) {
1483
+ if (!entry.active) continue;
1484
+ if (entry.nextFireAt <= now) {
1485
+ try {
1486
+ entry.msg.working();
1487
+ } catch {
1488
+ }
1489
+ entry.nextFireAt = now + entry.interval;
1490
+ }
1491
+ if (entry.nextFireAt < earliest) earliest = entry.nextFireAt;
1492
+ }
1493
+ if (earliest !== Infinity) this.ensureWake(earliest);
1494
+ }
1495
+ };
1496
+ var pool = new AckExtensionPool();
1386
1497
  var startAckExtensionTimer = (msg, interval) => {
1387
1498
  if (interval === null || interval <= 0) return null;
1388
- const timer2 = setInterval(() => {
1389
- try {
1390
- msg.working();
1391
- } catch {
1392
- }
1393
- }, interval);
1499
+ const entry = pool.schedule(msg, interval);
1394
1500
  return () => {
1395
- clearInterval(timer2);
1501
+ pool.cancel(entry);
1396
1502
  };
1397
1503
  };
1398
1504
 
@@ -1406,11 +1512,8 @@ var serializeError = (err) => {
1406
1512
 
1407
1513
  // src/utils/unwrap-result.ts
1408
1514
  import { isObservable } from "rxjs";
1409
- var RESOLVED_VOID = Promise.resolve(void 0);
1410
- var RESOLVED_NULL = Promise.resolve(null);
1411
1515
  var unwrapResult = (result) => {
1412
- if (result === void 0) return RESOLVED_VOID;
1413
- if (result === null) return RESOLVED_NULL;
1516
+ if (result === void 0 || result === null) return result;
1414
1517
  if (isObservable(result)) {
1415
1518
  return subscribeToFirst(result);
1416
1519
  }
@@ -1419,8 +1522,9 @@ var unwrapResult = (result) => {
1419
1522
  (resolved) => isObservable(resolved) ? subscribeToFirst(resolved) : resolved
1420
1523
  );
1421
1524
  }
1422
- return Promise.resolve(result);
1525
+ return result;
1423
1526
  };
1527
+ var isPromiseLike = (value) => value !== null && typeof value === "object" && typeof value.then === "function";
1424
1528
  var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
1425
1529
  let done = false;
1426
1530
  let subscription = null;
@@ -1508,7 +1612,8 @@ var CoreRpcServer = class {
1508
1612
  }
1509
1613
  const ctx = new RpcContext([msg]);
1510
1614
  try {
1511
- const result = await unwrapResult(handler(data, ctx));
1615
+ const raw = unwrapResult(handler(data, ctx));
1616
+ const result = isPromiseLike(raw) ? await raw : raw;
1512
1617
  msg.respond(this.codec.encode(result));
1513
1618
  } catch (err) {
1514
1619
  this.logger.error(`Handler error for Core RPC ${msg.subject}:`, err);
@@ -1531,6 +1636,14 @@ var CoreRpcServer = class {
1531
1636
  import { Logger as Logger6 } from "@nestjs/common";
1532
1637
  import { JetStreamApiError as JetStreamApiError2 } from "@nats-io/jetstream";
1533
1638
 
1639
+ // src/server/infrastructure/nats-error-codes.ts
1640
+ var NatsErrorCode = /* @__PURE__ */ ((NatsErrorCode2) => {
1641
+ NatsErrorCode2[NatsErrorCode2["ConsumerNotFound"] = 10014] = "ConsumerNotFound";
1642
+ NatsErrorCode2[NatsErrorCode2["ConsumerAlreadyExists"] = 10148] = "ConsumerAlreadyExists";
1643
+ NatsErrorCode2[NatsErrorCode2["StreamNotFound"] = 10059] = "StreamNotFound";
1644
+ return NatsErrorCode2;
1645
+ })(NatsErrorCode || {});
1646
+
1534
1647
  // src/server/infrastructure/stream-config-diff.ts
1535
1648
  var TRANSPORT_CONTROLLED_PROPERTIES = /* @__PURE__ */ new Set([
1536
1649
  "retention"
@@ -2229,13 +2342,17 @@ var MessageProvider = class {
2229
2342
  }
2230
2343
  const defaults = { idle_heartbeat: 5e3 };
2231
2344
  const userOptions = this.consumeOptionsMap.get(kind) ?? {};
2232
- const messages = await consumer.consume({ ...defaults, ...userOptions });
2345
+ const messages = await consumer.consume({
2346
+ ...defaults,
2347
+ ...userOptions,
2348
+ callback: (msg) => {
2349
+ target$.next(msg);
2350
+ }
2351
+ });
2233
2352
  this.activeIterators.add(messages);
2234
2353
  this.monitorConsumerHealth(messages, consumerName2);
2235
2354
  try {
2236
- for await (const msg of messages) {
2237
- target$.next(msg);
2238
- }
2355
+ await messages.closed();
2239
2356
  } finally {
2240
2357
  this.activeIterators.delete(messages);
2241
2358
  }
@@ -2327,11 +2444,16 @@ var MessageProvider = class {
2327
2444
  })
2328
2445
  );
2329
2446
  }
2330
- /** Single iteration: create ordered consumer -> iterate messages. */
2447
+ /** Single iteration: create ordered consumer -> push messages into the subject. */
2331
2448
  async consumeOrderedOnce(streamName2, consumerOpts) {
2332
2449
  const js = this.connection.getJetStreamClient();
2333
2450
  const consumer = await js.consumers.get(streamName2, consumerOpts);
2334
- const messages = await consumer.consume();
2451
+ const orderedMessages$ = this.orderedMessages$;
2452
+ const messages = await consumer.consume({
2453
+ callback: (msg) => {
2454
+ orderedMessages$.next(msg);
2455
+ }
2456
+ });
2335
2457
  if (this.orderedReadyResolve) {
2336
2458
  this.orderedReadyResolve();
2337
2459
  this.orderedReadyResolve = null;
@@ -2339,9 +2461,7 @@ var MessageProvider = class {
2339
2461
  }
2340
2462
  this.activeIterators.add(messages);
2341
2463
  try {
2342
- for await (const msg of messages) {
2343
- this.orderedMessages$.next(msg);
2344
- }
2464
+ await messages.closed();
2345
2465
  } finally {
2346
2466
  this.activeIterators.delete(messages);
2347
2467
  }
@@ -2620,7 +2740,6 @@ var PatternRegistry = class {
2620
2740
 
2621
2741
  // src/server/routing/event.router.ts
2622
2742
  import { Logger as Logger11 } from "@nestjs/common";
2623
- import { concatMap, from as from2, mergeMap } from "rxjs";
2624
2743
  import { headers as natsHeaders3 } from "@nats-io/transport-node";
2625
2744
  var EventRouter = class {
2626
2745
  constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig, processingConfig, ackWaitMap, connection, options) {
@@ -2659,15 +2778,194 @@ var EventRouter = class {
2659
2778
  }
2660
2779
  this.subscriptions.length = 0;
2661
2780
  }
2662
- /** Subscribe to a message stream and route each message. */
2781
+ /** Subscribe to a message stream and route each message to its handler. */
2663
2782
  subscribeToStream(stream$, kind) {
2664
2783
  const isOrdered = kind === "ordered" /* Ordered */;
2784
+ const patternRegistry = this.patternRegistry;
2785
+ const codec = this.codec;
2786
+ const eventBus = this.eventBus;
2787
+ const logger = this.logger;
2788
+ const deadLetterConfig = this.deadLetterConfig;
2665
2789
  const ackExtensionInterval = isOrdered ? null : resolveAckExtensionInterval(this.getAckExtensionConfig(kind), this.ackWaitMap?.get(kind));
2790
+ const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
2666
2791
  const concurrency = this.getConcurrency(kind);
2667
- const route = (msg) => from2(
2668
- isOrdered ? this.handleOrderedSafe(msg) : this.handleSafe(msg, ackExtensionInterval, kind)
2669
- );
2670
- const subscription = stream$.pipe(isOrdered ? concatMap(route) : mergeMap(route, concurrency)).subscribe();
2792
+ const hasDlqCheck = deadLetterConfig !== void 0;
2793
+ const emitRouted = eventBus.hasHook("messageRouted" /* MessageRouted */);
2794
+ const isDeadLetter = (msg) => {
2795
+ if (!hasDlqCheck) return false;
2796
+ const maxDeliver = deadLetterConfig.maxDeliverByStream?.get(msg.info.stream);
2797
+ if (maxDeliver === void 0 || maxDeliver <= 0) return false;
2798
+ return msg.info.deliveryCount >= maxDeliver;
2799
+ };
2800
+ const handleDeadLetter = hasDlqCheck ? (msg, data, err) => this.handleDeadLetter(msg, data, err) : null;
2801
+ const settleSuccess = (msg, ctx) => {
2802
+ if (ctx.shouldTerminate) msg.term(ctx.terminateReason);
2803
+ else if (ctx.shouldRetry) msg.nak(ctx.retryDelay);
2804
+ else msg.ack();
2805
+ };
2806
+ const settleFailure = async (msg, data, err) => {
2807
+ if (handleDeadLetter !== null && isDeadLetter(msg)) {
2808
+ await handleDeadLetter(msg, data, err);
2809
+ return;
2810
+ }
2811
+ msg.nak();
2812
+ };
2813
+ const resolveEvent = (msg) => {
2814
+ const subject = msg.subject;
2815
+ try {
2816
+ const handler = patternRegistry.getHandler(subject);
2817
+ if (!handler) {
2818
+ msg.term(`No handler for event: ${subject}`);
2819
+ logger.error(`No handler for subject: ${subject}`);
2820
+ return null;
2821
+ }
2822
+ let data;
2823
+ try {
2824
+ data = codec.decode(msg.data);
2825
+ } catch (err) {
2826
+ msg.term("Decode error");
2827
+ logger.error(`Decode error for ${subject}:`, err);
2828
+ return null;
2829
+ }
2830
+ if (emitRouted) eventBus.emitMessageRouted(subject, "event" /* Event */);
2831
+ return { handler, data };
2832
+ } catch (err) {
2833
+ logger.error(`Unexpected error in ${kind} event router`, err);
2834
+ try {
2835
+ msg.term("Unexpected router error");
2836
+ } catch (termErr) {
2837
+ logger.error(`Failed to terminate message ${subject}:`, termErr);
2838
+ }
2839
+ return null;
2840
+ }
2841
+ };
2842
+ const handleSafe = (msg) => {
2843
+ const resolved = resolveEvent(msg);
2844
+ if (resolved === null) return void 0;
2845
+ const { handler, data } = resolved;
2846
+ const ctx = new RpcContext([msg]);
2847
+ const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
2848
+ let pending;
2849
+ try {
2850
+ pending = unwrapResult(handler(data, ctx));
2851
+ } catch (err) {
2852
+ logger.error(`Event handler error (${msg.subject}) in ${kind} router:`, err);
2853
+ if (stopAckExtension !== null) stopAckExtension();
2854
+ return settleFailure(msg, data, err);
2855
+ }
2856
+ if (!isPromiseLike(pending)) {
2857
+ settleSuccess(msg, ctx);
2858
+ if (stopAckExtension !== null) stopAckExtension();
2859
+ return void 0;
2860
+ }
2861
+ return pending.then(
2862
+ () => {
2863
+ settleSuccess(msg, ctx);
2864
+ if (stopAckExtension !== null) stopAckExtension();
2865
+ },
2866
+ async (err) => {
2867
+ logger.error(`Event handler error (${msg.subject}) in ${kind} router:`, err);
2868
+ try {
2869
+ await settleFailure(msg, data, err);
2870
+ } finally {
2871
+ if (stopAckExtension !== null) stopAckExtension();
2872
+ }
2873
+ }
2874
+ );
2875
+ };
2876
+ const handleOrderedSafe = (msg) => {
2877
+ const subject = msg.subject;
2878
+ let handler;
2879
+ let data;
2880
+ try {
2881
+ handler = patternRegistry.getHandler(subject);
2882
+ if (!handler) {
2883
+ logger.error(`No handler for subject: ${subject}`);
2884
+ return void 0;
2885
+ }
2886
+ try {
2887
+ data = codec.decode(msg.data);
2888
+ } catch (err) {
2889
+ logger.error(`Decode error for ${subject}:`, err);
2890
+ return void 0;
2891
+ }
2892
+ if (emitRouted) eventBus.emitMessageRouted(subject, "event" /* Event */);
2893
+ } catch (err) {
2894
+ logger.error(`Ordered handler error (${subject}):`, err);
2895
+ return void 0;
2896
+ }
2897
+ const ctx = new RpcContext([msg]);
2898
+ const warnIfSettlementAttempted = () => {
2899
+ if (ctx.shouldRetry || ctx.shouldTerminate) {
2900
+ logger.warn(
2901
+ `retry()/terminate() ignored for ordered message ${subject} \u2014 ordered consumers auto-acknowledge`
2902
+ );
2903
+ }
2904
+ };
2905
+ let pending;
2906
+ try {
2907
+ pending = unwrapResult(handler(data, ctx));
2908
+ } catch (err) {
2909
+ logger.error(`Ordered handler error (${subject}):`, err);
2910
+ return void 0;
2911
+ }
2912
+ if (!isPromiseLike(pending)) {
2913
+ warnIfSettlementAttempted();
2914
+ return void 0;
2915
+ }
2916
+ return pending.then(warnIfSettlementAttempted, (err) => {
2917
+ logger.error(`Ordered handler error (${subject}):`, err);
2918
+ });
2919
+ };
2920
+ const route = isOrdered ? handleOrderedSafe : handleSafe;
2921
+ const maxActive = isOrdered ? 1 : concurrency ?? Number.POSITIVE_INFINITY;
2922
+ const backlogWarnThreshold = 1e3;
2923
+ let active = 0;
2924
+ let backlogWarned = false;
2925
+ const backlog = [];
2926
+ const onAsyncDone = () => {
2927
+ active--;
2928
+ drainBacklog();
2929
+ };
2930
+ const drainBacklog = () => {
2931
+ while (active < maxActive) {
2932
+ const next = backlog.shift();
2933
+ if (next === void 0) return;
2934
+ active++;
2935
+ const result = route(next);
2936
+ if (result !== void 0) {
2937
+ void result.finally(onAsyncDone);
2938
+ } else {
2939
+ active--;
2940
+ }
2941
+ }
2942
+ if (backlog.length < backlogWarnThreshold) backlogWarned = false;
2943
+ };
2944
+ const subscription = stream$.subscribe({
2945
+ next: (msg) => {
2946
+ if (active >= maxActive) {
2947
+ backlog.push(msg);
2948
+ if (!backlogWarned && backlog.length >= backlogWarnThreshold) {
2949
+ backlogWarned = true;
2950
+ logger.warn(
2951
+ `${kind} backlog reached ${backlog.length} messages \u2014 consumer may be falling behind`
2952
+ );
2953
+ }
2954
+ return;
2955
+ }
2956
+ active++;
2957
+ const result = route(msg);
2958
+ if (result !== void 0) {
2959
+ void result.finally(onAsyncDone);
2960
+ } else {
2961
+ active--;
2962
+ if (backlog.length > 0) drainBacklog();
2963
+ }
2964
+ },
2965
+ error: (err) => {
2966
+ logger.error(`Stream error in ${kind} router`, err);
2967
+ }
2968
+ });
2671
2969
  this.subscriptions.push(subscription);
2672
2970
  }
2673
2971
  getConcurrency(kind) {
@@ -2680,86 +2978,6 @@ var EventRouter = class {
2680
2978
  if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.ackExtension;
2681
2979
  return void 0;
2682
2980
  }
2683
- /** Handle a single event message with error isolation. */
2684
- async handleSafe(msg, ackExtensionInterval, kind) {
2685
- try {
2686
- const resolved = this.decodeMessage(msg);
2687
- if (!resolved) return;
2688
- await this.executeHandler(
2689
- resolved.handler,
2690
- resolved.data,
2691
- resolved.ctx,
2692
- msg,
2693
- ackExtensionInterval
2694
- );
2695
- } catch (err) {
2696
- this.logger.error(`Unexpected error in ${kind} event router`, err);
2697
- }
2698
- }
2699
- /** Handle an ordered message with error isolation. */
2700
- async handleOrderedSafe(msg) {
2701
- try {
2702
- const resolved = this.decodeMessage(msg, true);
2703
- if (!resolved) return;
2704
- await unwrapResult(resolved.handler(resolved.data, resolved.ctx));
2705
- if (resolved.ctx.shouldRetry || resolved.ctx.shouldTerminate) {
2706
- this.logger.warn(
2707
- `retry()/terminate() ignored for ordered message ${msg.subject} \u2014 ordered consumers auto-acknowledge`
2708
- );
2709
- }
2710
- } catch (err) {
2711
- this.logger.error(`Ordered handler error (${msg.subject}):`, err);
2712
- }
2713
- }
2714
- /** Resolve handler, decode payload, and build context. Returns null on failure. */
2715
- decodeMessage(msg, isOrdered = false) {
2716
- const handler = this.patternRegistry.getHandler(msg.subject);
2717
- if (!handler) {
2718
- if (!isOrdered) msg.term(`No handler for event: ${msg.subject}`);
2719
- this.logger.error(`No handler for subject: ${msg.subject}`);
2720
- return null;
2721
- }
2722
- let data;
2723
- try {
2724
- data = this.codec.decode(msg.data);
2725
- } catch (err) {
2726
- if (!isOrdered) msg.term("Decode error");
2727
- this.logger.error(`Decode error for ${msg.subject}:`, err);
2728
- return null;
2729
- }
2730
- this.eventBus.emitMessageRouted(msg.subject, "event" /* Event */);
2731
- return { handler, data, ctx: new RpcContext([msg]) };
2732
- }
2733
- /** Execute handler, then ack on success or nak/dead-letter on failure. */
2734
- async executeHandler(handler, data, ctx, msg, ackExtensionInterval) {
2735
- const stopAckExtension = startAckExtensionTimer(msg, ackExtensionInterval);
2736
- try {
2737
- await unwrapResult(handler(data, ctx));
2738
- if (ctx.shouldTerminate) {
2739
- msg.term(ctx.terminateReason);
2740
- } else if (ctx.shouldRetry) {
2741
- msg.nak(ctx.retryDelay);
2742
- } else {
2743
- msg.ack();
2744
- }
2745
- } catch (err) {
2746
- this.logger.error(`Event handler error (${msg.subject}):`, err);
2747
- if (this.isDeadLetter(msg)) {
2748
- await this.handleDeadLetter(msg, data, err);
2749
- } else {
2750
- msg.nak();
2751
- }
2752
- } finally {
2753
- stopAckExtension?.();
2754
- }
2755
- }
2756
- /** Check if the message has exhausted all delivery attempts. */
2757
- isDeadLetter(msg) {
2758
- if (!this.deadLetterConfig) return false;
2759
- const maxDeliver = this.deadLetterConfig.maxDeliverByStream.get(msg.info.stream);
2760
- if (maxDeliver === void 0 || maxDeliver <= 0) return false;
2761
- return msg.info.deliveryCount >= maxDeliver;
2762
- }
2763
2981
  /** Handle a dead letter: invoke callback, then term or nak based on result. */
2764
2982
  /**
2765
2983
  * Fallback execution for a dead letter when DLQ is disabled, or when
@@ -2871,7 +3089,6 @@ var EventRouter = class {
2871
3089
  // src/server/routing/rpc.router.ts
2872
3090
  import { Logger as Logger12 } from "@nestjs/common";
2873
3091
  import { headers } from "@nats-io/transport-node";
2874
- import { from as from3, mergeMap as mergeMap2 } from "rxjs";
2875
3092
  var RpcRouter = class {
2876
3093
  constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap) {
2877
3094
  this.messageProvider = messageProvider;
@@ -2902,87 +3119,178 @@ var RpcRouter = class {
2902
3119
  /** Start routing command messages to handlers. */
2903
3120
  async start() {
2904
3121
  this.cachedNc = await this.connection.getConnection();
2905
- this.subscription = this.messageProvider.commands$.pipe(mergeMap2((msg) => from3(this.handleSafe(msg)), this.concurrency)).subscribe();
2906
- }
2907
- /** Stop routing and unsubscribe. */
2908
- destroy() {
2909
- this.subscription?.unsubscribe();
2910
- this.subscription = null;
2911
- }
2912
- /** Handle a single RPC command message with error isolation. */
2913
- async handleSafe(msg) {
2914
- try {
2915
- const handler = this.patternRegistry.getHandler(msg.subject);
2916
- if (!handler) {
2917
- msg.term(`No handler for RPC: ${msg.subject}`);
2918
- this.logger.error(`No handler for RPC subject: ${msg.subject}`);
2919
- return;
2920
- }
2921
- const { headers: msgHeaders } = msg;
2922
- const replyTo = msgHeaders?.get("x-reply-to" /* ReplyTo */);
2923
- const correlationId = msgHeaders?.get("x-correlation-id" /* CorrelationId */);
2924
- if (!replyTo || !correlationId) {
2925
- msg.term("Missing required headers (reply-to or correlation-id)");
2926
- this.logger.error(`Missing headers for RPC: ${msg.subject}`);
2927
- return;
2928
- }
2929
- let data;
2930
- try {
2931
- data = this.codec.decode(msg.data);
2932
- } catch (err) {
2933
- msg.term("Decode error");
2934
- this.logger.error(`Decode error for RPC ${msg.subject}:`, err);
2935
- return;
2936
- }
2937
- this.eventBus.emitMessageRouted(msg.subject, "rpc" /* Rpc */);
2938
- await this.executeHandler(handler, data, msg, replyTo, correlationId);
2939
- } catch (err) {
2940
- this.logger.error("Unexpected error in RPC router", err);
2941
- }
2942
- }
2943
- /** Execute handler, publish response, settle message. */
2944
- async executeHandler(handler, data, msg, replyTo, correlationId) {
2945
- const nc = this.cachedNc ?? await this.connection.getConnection();
2946
- const ctx = new RpcContext([msg]);
2947
- let settled = false;
2948
- const stopAckExtension = startAckExtensionTimer(msg, this.ackExtensionInterval);
2949
- const timeoutId = setTimeout(() => {
2950
- if (settled) return;
2951
- settled = true;
2952
- stopAckExtension?.();
2953
- this.logger.error(`RPC timeout (${this.timeout}ms): ${msg.subject}`);
2954
- this.eventBus.emit("rpcTimeout" /* RpcTimeout */, msg.subject, correlationId);
2955
- msg.term("Handler timeout");
2956
- }, this.timeout);
2957
- try {
2958
- const result = await unwrapResult(handler(data, ctx));
2959
- if (settled) return;
2960
- settled = true;
2961
- clearTimeout(timeoutId);
2962
- stopAckExtension?.();
2963
- msg.ack();
3122
+ const nc = this.cachedNc;
3123
+ const patternRegistry = this.patternRegistry;
3124
+ const codec = this.codec;
3125
+ const eventBus = this.eventBus;
3126
+ const logger = this.logger;
3127
+ const timeout = this.timeout;
3128
+ const ackExtensionInterval = this.ackExtensionInterval;
3129
+ const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
3130
+ const maxActive = this.concurrency ?? Number.POSITIVE_INFINITY;
3131
+ const emitRpcTimeout = (subject, correlationId) => {
3132
+ eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
3133
+ };
3134
+ const publishReply = (replyTo, correlationId, payload) => {
2964
3135
  try {
2965
3136
  const hdrs = headers();
2966
3137
  hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
2967
- nc.publish(replyTo, this.codec.encode(result), { headers: hdrs });
3138
+ nc.publish(replyTo, codec.encode(payload), { headers: hdrs });
2968
3139
  } catch (publishErr) {
2969
- this.logger.error(`Failed to publish RPC response for ${msg.subject}`, publishErr);
3140
+ logger.error(`Failed to publish RPC response`, publishErr);
2970
3141
  }
2971
- } catch (err) {
2972
- if (settled) return;
2973
- settled = true;
2974
- clearTimeout(timeoutId);
2975
- stopAckExtension?.();
3142
+ };
3143
+ const publishErrorReply = (replyTo, correlationId, subject, err) => {
2976
3144
  try {
2977
3145
  const hdrs = headers();
2978
3146
  hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
2979
3147
  hdrs.set("x-error" /* Error */, "true");
2980
- nc.publish(replyTo, this.codec.encode(serializeError(err)), { headers: hdrs });
3148
+ nc.publish(replyTo, codec.encode(serializeError(err)), { headers: hdrs });
2981
3149
  } catch (encodeErr) {
2982
- this.logger.error(`Failed to encode RPC error for ${msg.subject}`, encodeErr);
3150
+ logger.error(`Failed to encode RPC error for ${subject}`, encodeErr);
2983
3151
  }
2984
- msg.term(`Handler error: ${msg.subject}`);
2985
- }
3152
+ };
3153
+ const resolveCommand = (msg) => {
3154
+ const subject = msg.subject;
3155
+ try {
3156
+ const handler = patternRegistry.getHandler(subject);
3157
+ if (!handler) {
3158
+ msg.term(`No handler for RPC: ${subject}`);
3159
+ logger.error(`No handler for RPC subject: ${subject}`);
3160
+ return null;
3161
+ }
3162
+ const msgHeaders = msg.headers;
3163
+ const replyTo = msgHeaders?.get("x-reply-to" /* ReplyTo */);
3164
+ const correlationId = msgHeaders?.get("x-correlation-id" /* CorrelationId */);
3165
+ if (!replyTo || !correlationId) {
3166
+ msg.term("Missing required headers (reply-to or correlation-id)");
3167
+ logger.error(`Missing headers for RPC: ${subject}`);
3168
+ return null;
3169
+ }
3170
+ let data;
3171
+ try {
3172
+ data = codec.decode(msg.data);
3173
+ } catch (err) {
3174
+ msg.term("Decode error");
3175
+ logger.error(`Decode error for RPC ${subject}:`, err);
3176
+ return null;
3177
+ }
3178
+ eventBus.emitMessageRouted(subject, "rpc" /* Rpc */);
3179
+ return { handler, data, replyTo, correlationId };
3180
+ } catch (err) {
3181
+ logger.error("Unexpected error in RPC router", err);
3182
+ try {
3183
+ msg.term("Unexpected router error");
3184
+ } catch (termErr) {
3185
+ logger.error(`Failed to terminate RPC message ${subject}:`, termErr);
3186
+ }
3187
+ return null;
3188
+ }
3189
+ };
3190
+ const handleSafe = (msg) => {
3191
+ const resolved = resolveCommand(msg);
3192
+ if (resolved === null) return void 0;
3193
+ const { handler, data, replyTo, correlationId } = resolved;
3194
+ const subject = msg.subject;
3195
+ const ctx = new RpcContext([msg]);
3196
+ const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
3197
+ let pending;
3198
+ try {
3199
+ pending = unwrapResult(handler(data, ctx));
3200
+ } catch (err) {
3201
+ if (stopAckExtension !== null) stopAckExtension();
3202
+ logger.error(`RPC handler error (${subject}):`, err);
3203
+ publishErrorReply(replyTo, correlationId, subject, err);
3204
+ msg.term(`Handler error: ${subject}`);
3205
+ return void 0;
3206
+ }
3207
+ if (!isPromiseLike(pending)) {
3208
+ if (stopAckExtension !== null) stopAckExtension();
3209
+ msg.ack();
3210
+ publishReply(replyTo, correlationId, pending);
3211
+ return void 0;
3212
+ }
3213
+ let settled = false;
3214
+ const timeoutId = setTimeout(() => {
3215
+ if (settled) return;
3216
+ settled = true;
3217
+ if (stopAckExtension !== null) stopAckExtension();
3218
+ logger.error(`RPC timeout (${timeout}ms): ${subject}`);
3219
+ emitRpcTimeout(subject, correlationId);
3220
+ msg.term("Handler timeout");
3221
+ }, timeout);
3222
+ return pending.then(
3223
+ (result) => {
3224
+ if (settled) return;
3225
+ settled = true;
3226
+ clearTimeout(timeoutId);
3227
+ if (stopAckExtension !== null) stopAckExtension();
3228
+ msg.ack();
3229
+ publishReply(replyTo, correlationId, result);
3230
+ },
3231
+ (err) => {
3232
+ if (settled) return;
3233
+ settled = true;
3234
+ clearTimeout(timeoutId);
3235
+ if (stopAckExtension !== null) stopAckExtension();
3236
+ logger.error(`RPC handler error (${subject}):`, err);
3237
+ publishErrorReply(replyTo, correlationId, subject, err);
3238
+ msg.term(`Handler error: ${subject}`);
3239
+ }
3240
+ );
3241
+ };
3242
+ const backlogWarnThreshold = 1e3;
3243
+ let active = 0;
3244
+ let backlogWarned = false;
3245
+ const backlog = [];
3246
+ const onAsyncDone = () => {
3247
+ active--;
3248
+ drainBacklog();
3249
+ };
3250
+ const drainBacklog = () => {
3251
+ while (active < maxActive) {
3252
+ const next = backlog.shift();
3253
+ if (next === void 0) return;
3254
+ active++;
3255
+ const result = handleSafe(next);
3256
+ if (result !== void 0) {
3257
+ void result.finally(onAsyncDone);
3258
+ } else {
3259
+ active--;
3260
+ }
3261
+ }
3262
+ if (backlog.length < backlogWarnThreshold) backlogWarned = false;
3263
+ };
3264
+ this.subscription = this.messageProvider.commands$.subscribe({
3265
+ next: (msg) => {
3266
+ if (active >= maxActive) {
3267
+ backlog.push(msg);
3268
+ if (!backlogWarned && backlog.length >= backlogWarnThreshold) {
3269
+ backlogWarned = true;
3270
+ logger.warn(
3271
+ `RPC backlog reached ${backlog.length} messages \u2014 consumer may be falling behind`
3272
+ );
3273
+ }
3274
+ return;
3275
+ }
3276
+ active++;
3277
+ const result = handleSafe(msg);
3278
+ if (result !== void 0) {
3279
+ void result.finally(onAsyncDone);
3280
+ } else {
3281
+ active--;
3282
+ if (backlog.length > 0) drainBacklog();
3283
+ }
3284
+ },
3285
+ error: (err) => {
3286
+ logger.error("Stream error in RPC router", err);
3287
+ }
3288
+ });
3289
+ }
3290
+ /** Stop routing and unsubscribe. */
3291
+ destroy() {
3292
+ this.subscription?.unsubscribe();
3293
+ this.subscription = null;
2986
3294
  }
2987
3295
  };
2988
3296
 
@@ -3430,14 +3738,23 @@ JetstreamModule = __decorateClass([
3430
3738
  __decorateParam(1, Inject(JetstreamStrategy))
3431
3739
  ], JetstreamModule);
3432
3740
  export {
3741
+ DEFAULT_BROADCAST_CONSUMER_CONFIG,
3742
+ DEFAULT_BROADCAST_STREAM_CONFIG,
3743
+ DEFAULT_COMMAND_CONSUMER_CONFIG,
3744
+ DEFAULT_COMMAND_STREAM_CONFIG,
3745
+ DEFAULT_DLQ_STREAM_CONFIG,
3746
+ DEFAULT_EVENT_CONSUMER_CONFIG,
3747
+ DEFAULT_EVENT_STREAM_CONFIG,
3748
+ DEFAULT_JETSTREAM_RPC_TIMEOUT,
3433
3749
  DEFAULT_METADATA_BUCKET,
3434
3750
  DEFAULT_METADATA_HISTORY,
3435
3751
  DEFAULT_METADATA_REPLICAS,
3436
3752
  DEFAULT_METADATA_TTL,
3437
- EventBus,
3753
+ DEFAULT_ORDERED_STREAM_CONFIG,
3754
+ DEFAULT_RPC_TIMEOUT,
3755
+ DEFAULT_SHUTDOWN_TIMEOUT,
3438
3756
  JETSTREAM_CODEC,
3439
3757
  JETSTREAM_CONNECTION,
3440
- JETSTREAM_EVENT_BUS,
3441
3758
  JETSTREAM_OPTIONS,
3442
3759
  JetstreamClient,
3443
3760
  JetstreamDlqHeader,
@@ -3450,7 +3767,10 @@ export {
3450
3767
  JsonCodec,
3451
3768
  MIN_METADATA_TTL,
3452
3769
  MessageKind,
3770
+ MsgpackCodec,
3771
+ NatsErrorCode,
3453
3772
  PatternPrefix,
3773
+ RESERVED_HEADERS,
3454
3774
  RpcContext,
3455
3775
  StreamKind,
3456
3776
  TransportEvent,