@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/README.md +15 -2
- package/dist/index.cjs +544 -212
- package/dist/index.d.cts +125 -22
- package/dist/index.d.ts +125 -22
- package/dist/index.js +526 -206
- package/package.json +22 -7
package/dist/index.cjs
CHANGED
|
@@ -7,11 +7,11 @@ var __export = (target, all) => {
|
|
|
7
7
|
for (var name in all)
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
|
-
var __copyProps = (to,
|
|
11
|
-
if (
|
|
12
|
-
for (let key of __getOwnPropNames(
|
|
10
|
+
var __copyProps = (to, from2, except, desc) => {
|
|
11
|
+
if (from2 && typeof from2 === "object" || typeof from2 === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from2))
|
|
13
13
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () =>
|
|
14
|
+
__defProp(to, key, { get: () => from2[key], enumerable: !(desc = __getOwnPropDesc(from2, key)) || desc.enumerable });
|
|
15
15
|
}
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
@@ -29,14 +29,23 @@ var __decorateParam = (index, decorator) => (target, key) => decorator(target, k
|
|
|
29
29
|
// src/index.ts
|
|
30
30
|
var index_exports = {};
|
|
31
31
|
__export(index_exports, {
|
|
32
|
+
DEFAULT_BROADCAST_CONSUMER_CONFIG: () => DEFAULT_BROADCAST_CONSUMER_CONFIG,
|
|
33
|
+
DEFAULT_BROADCAST_STREAM_CONFIG: () => DEFAULT_BROADCAST_STREAM_CONFIG,
|
|
34
|
+
DEFAULT_COMMAND_CONSUMER_CONFIG: () => DEFAULT_COMMAND_CONSUMER_CONFIG,
|
|
35
|
+
DEFAULT_COMMAND_STREAM_CONFIG: () => DEFAULT_COMMAND_STREAM_CONFIG,
|
|
36
|
+
DEFAULT_DLQ_STREAM_CONFIG: () => DEFAULT_DLQ_STREAM_CONFIG,
|
|
37
|
+
DEFAULT_EVENT_CONSUMER_CONFIG: () => DEFAULT_EVENT_CONSUMER_CONFIG,
|
|
38
|
+
DEFAULT_EVENT_STREAM_CONFIG: () => DEFAULT_EVENT_STREAM_CONFIG,
|
|
39
|
+
DEFAULT_JETSTREAM_RPC_TIMEOUT: () => DEFAULT_JETSTREAM_RPC_TIMEOUT,
|
|
32
40
|
DEFAULT_METADATA_BUCKET: () => DEFAULT_METADATA_BUCKET,
|
|
33
41
|
DEFAULT_METADATA_HISTORY: () => DEFAULT_METADATA_HISTORY,
|
|
34
42
|
DEFAULT_METADATA_REPLICAS: () => DEFAULT_METADATA_REPLICAS,
|
|
35
43
|
DEFAULT_METADATA_TTL: () => DEFAULT_METADATA_TTL,
|
|
36
|
-
|
|
44
|
+
DEFAULT_ORDERED_STREAM_CONFIG: () => DEFAULT_ORDERED_STREAM_CONFIG,
|
|
45
|
+
DEFAULT_RPC_TIMEOUT: () => DEFAULT_RPC_TIMEOUT,
|
|
46
|
+
DEFAULT_SHUTDOWN_TIMEOUT: () => DEFAULT_SHUTDOWN_TIMEOUT,
|
|
37
47
|
JETSTREAM_CODEC: () => JETSTREAM_CODEC,
|
|
38
48
|
JETSTREAM_CONNECTION: () => JETSTREAM_CONNECTION,
|
|
39
|
-
JETSTREAM_EVENT_BUS: () => JETSTREAM_EVENT_BUS,
|
|
40
49
|
JETSTREAM_OPTIONS: () => JETSTREAM_OPTIONS,
|
|
41
50
|
JetstreamClient: () => JetstreamClient,
|
|
42
51
|
JetstreamDlqHeader: () => JetstreamDlqHeader,
|
|
@@ -49,7 +58,10 @@ __export(index_exports, {
|
|
|
49
58
|
JsonCodec: () => JsonCodec,
|
|
50
59
|
MIN_METADATA_TTL: () => MIN_METADATA_TTL,
|
|
51
60
|
MessageKind: () => MessageKind,
|
|
61
|
+
MsgpackCodec: () => MsgpackCodec,
|
|
62
|
+
NatsErrorCode: () => NatsErrorCode,
|
|
52
63
|
PatternPrefix: () => PatternPrefix,
|
|
64
|
+
RESERVED_HEADERS: () => RESERVED_HEADERS,
|
|
53
65
|
RpcContext: () => RpcContext,
|
|
54
66
|
StreamKind: () => StreamKind,
|
|
55
67
|
TransportEvent: () => TransportEvent,
|
|
@@ -438,6 +450,7 @@ var nanosToGoDuration = (nanos) => {
|
|
|
438
450
|
};
|
|
439
451
|
|
|
440
452
|
// src/client/jetstream.client.ts
|
|
453
|
+
var BROADCAST_SUBJECT_PREFIX = "broadcast.";
|
|
441
454
|
var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
442
455
|
constructor(rootOptions, targetServiceName, connection, codec, eventBus) {
|
|
443
456
|
super();
|
|
@@ -447,12 +460,33 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
447
460
|
this.eventBus = eventBus;
|
|
448
461
|
this.targetName = targetServiceName;
|
|
449
462
|
this.callerName = internalName(this.rootOptions.name);
|
|
463
|
+
const targetInternal = internalName(targetServiceName);
|
|
464
|
+
this.eventSubjectPrefix = `${targetInternal}.${"ev" /* Event */}.`;
|
|
465
|
+
this.commandSubjectPrefix = `${targetInternal}.${"cmd" /* Command */}.`;
|
|
466
|
+
this.orderedSubjectPrefix = `${targetInternal}.${"ordered" /* Ordered */}.`;
|
|
467
|
+
this.isCoreMode = isCoreRpcMode(this.rootOptions.rpc);
|
|
468
|
+
this.defaultRpcTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? this.rootOptions.rpc?.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT : this.rootOptions.rpc?.timeout ?? DEFAULT_RPC_TIMEOUT;
|
|
450
469
|
}
|
|
451
470
|
logger = new import_common.Logger("Jetstream:Client");
|
|
452
471
|
/** Target service name this client sends messages to. */
|
|
453
472
|
targetName;
|
|
454
473
|
/** Pre-cached caller name derived from rootOptions.name, computed once in constructor. */
|
|
455
474
|
callerName;
|
|
475
|
+
/**
|
|
476
|
+
* Subject prefixes of the form `{serviceName}__microservice.{kind}.` — one
|
|
477
|
+
* per stream kind this client may publish to. Built once in the constructor
|
|
478
|
+
* so producing a full subject is a single string concat with the user pattern.
|
|
479
|
+
*/
|
|
480
|
+
eventSubjectPrefix;
|
|
481
|
+
commandSubjectPrefix;
|
|
482
|
+
orderedSubjectPrefix;
|
|
483
|
+
/**
|
|
484
|
+
* RPC configuration snapshots. The values are derived from rootOptions at
|
|
485
|
+
* construction time so the publish hot path never has to re-run
|
|
486
|
+
* isCoreRpcMode / getRpcTimeout on every call.
|
|
487
|
+
*/
|
|
488
|
+
isCoreMode;
|
|
489
|
+
defaultRpcTimeout;
|
|
456
490
|
/** Shared inbox for JetStream-mode RPC responses. */
|
|
457
491
|
inbox = null;
|
|
458
492
|
inboxSubscription = null;
|
|
@@ -462,6 +496,12 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
462
496
|
pendingTimeouts = /* @__PURE__ */ new Map();
|
|
463
497
|
/** Subscription to connection status events for disconnect handling. */
|
|
464
498
|
statusSubscription = null;
|
|
499
|
+
/**
|
|
500
|
+
* Cached readiness flag. Once `connect()` has wired the inbox and status
|
|
501
|
+
* subscription, subsequent publishes skip the `await connect()` microtask
|
|
502
|
+
* and reach for the underlying connection synchronously instead.
|
|
503
|
+
*/
|
|
504
|
+
readyForPublish = false;
|
|
465
505
|
/**
|
|
466
506
|
* Establish connection. Called automatically by NestJS on first use.
|
|
467
507
|
*
|
|
@@ -472,7 +512,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
472
512
|
*/
|
|
473
513
|
async connect() {
|
|
474
514
|
const nc = await this.connection.getConnection();
|
|
475
|
-
if (
|
|
515
|
+
if (!this.isCoreMode && !this.inboxSubscription) {
|
|
476
516
|
this.setupInbox(nc);
|
|
477
517
|
}
|
|
478
518
|
this.statusSubscription ??= this.connection.status$.subscribe((status) => {
|
|
@@ -480,12 +520,14 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
480
520
|
this.handleDisconnect();
|
|
481
521
|
}
|
|
482
522
|
});
|
|
523
|
+
this.readyForPublish = true;
|
|
483
524
|
return nc;
|
|
484
525
|
}
|
|
485
526
|
/** Clean up resources: reject pending RPCs, unsubscribe from status events. */
|
|
486
527
|
async close() {
|
|
487
528
|
this.statusSubscription?.unsubscribe();
|
|
488
529
|
this.statusSubscription = null;
|
|
530
|
+
this.readyForPublish = false;
|
|
489
531
|
this.rejectPendingRpcs(new Error("Client closed"));
|
|
490
532
|
}
|
|
491
533
|
/**
|
|
@@ -509,7 +551,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
509
551
|
* set to the original event subject.
|
|
510
552
|
*/
|
|
511
553
|
async dispatchEvent(packet) {
|
|
512
|
-
await this.connect();
|
|
554
|
+
if (!this.readyForPublish) await this.connect();
|
|
513
555
|
const { data, hdrs, messageId, schedule, ttl } = this.extractRecordData(packet.data);
|
|
514
556
|
const eventSubject = this.buildEventSubject(packet.pattern);
|
|
515
557
|
const msgHeaders = this.buildHeaders(hdrs, { subject: eventSubject });
|
|
@@ -548,7 +590,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
548
590
|
* JetStream mode: publishes to stream + waits for inbox response.
|
|
549
591
|
*/
|
|
550
592
|
publish(packet, callback) {
|
|
551
|
-
const subject =
|
|
593
|
+
const subject = this.commandSubjectPrefix + packet.pattern;
|
|
552
594
|
const { data, hdrs, timeout, messageId, schedule, ttl } = this.extractRecordData(packet.data);
|
|
553
595
|
if (schedule) {
|
|
554
596
|
this.logger.warn(
|
|
@@ -565,7 +607,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
565
607
|
callback({ err: new Error("Internal transport error"), response: null, isDisposed: true });
|
|
566
608
|
};
|
|
567
609
|
let jetStreamCorrelationId = null;
|
|
568
|
-
if (
|
|
610
|
+
if (this.isCoreMode) {
|
|
569
611
|
this.publishCoreRpc(subject, data, hdrs, timeout, callback).catch(onUnhandled);
|
|
570
612
|
} else {
|
|
571
613
|
jetStreamCorrelationId = import_nuid.nuid.next();
|
|
@@ -590,8 +632,8 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
590
632
|
/** Core mode: nc.request() with timeout. */
|
|
591
633
|
async publishCoreRpc(subject, data, customHeaders, timeout, callback) {
|
|
592
634
|
try {
|
|
593
|
-
const nc = await this.connect();
|
|
594
|
-
const effectiveTimeout = timeout ?? this.
|
|
635
|
+
const nc = this.readyForPublish ? this.connection.unwrap : await this.connect();
|
|
636
|
+
const effectiveTimeout = timeout ?? this.defaultRpcTimeout;
|
|
595
637
|
const hdrs = this.buildHeaders(customHeaders, { subject });
|
|
596
638
|
const response = await nc.request(subject, this.codec.encode(data), {
|
|
597
639
|
timeout: effectiveTimeout,
|
|
@@ -613,10 +655,10 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
613
655
|
/** JetStream mode: publish to stream + wait for inbox response. */
|
|
614
656
|
async publishJetStreamRpc(subject, data, callback, options) {
|
|
615
657
|
const { headers: customHeaders, correlationId, messageId } = options;
|
|
616
|
-
const effectiveTimeout = options.timeout ?? this.
|
|
658
|
+
const effectiveTimeout = options.timeout ?? this.defaultRpcTimeout;
|
|
617
659
|
this.pendingMessages.set(correlationId, callback);
|
|
618
660
|
try {
|
|
619
|
-
await this.connect();
|
|
661
|
+
if (!this.readyForPublish) await this.connect();
|
|
620
662
|
if (!this.pendingMessages.has(correlationId)) return;
|
|
621
663
|
if (!this.inbox) {
|
|
622
664
|
this.pendingMessages.delete(correlationId);
|
|
@@ -662,6 +704,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
662
704
|
handleDisconnect() {
|
|
663
705
|
this.rejectPendingRpcs(new Error("Connection lost"));
|
|
664
706
|
this.inbox = null;
|
|
707
|
+
this.readyForPublish = false;
|
|
665
708
|
}
|
|
666
709
|
/** Reject all pending RPC callbacks, clear timeouts, and tear down inbox. */
|
|
667
710
|
rejectPendingRpcs(error) {
|
|
@@ -725,19 +768,22 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
725
768
|
this.pendingMessages.delete(correlationId);
|
|
726
769
|
}
|
|
727
770
|
}
|
|
728
|
-
/**
|
|
771
|
+
/**
|
|
772
|
+
* Resolve a user pattern to a fully-qualified NATS subject, dispatching
|
|
773
|
+
* between the event, broadcast, and ordered prefixes.
|
|
774
|
+
*
|
|
775
|
+
* The leading-char check short-circuits the `startsWith` comparisons for
|
|
776
|
+
* patterns that cannot possibly carry a broadcast/ordered marker, which is
|
|
777
|
+
* the overwhelmingly common case.
|
|
778
|
+
*/
|
|
729
779
|
buildEventSubject(pattern) {
|
|
730
|
-
if (pattern.startsWith("broadcast:" /* Broadcast */)) {
|
|
731
|
-
return
|
|
732
|
-
}
|
|
733
|
-
if (pattern.startsWith("ordered:" /* Ordered */)) {
|
|
734
|
-
return buildSubject(
|
|
735
|
-
this.targetName,
|
|
736
|
-
"ordered" /* Ordered */,
|
|
737
|
-
pattern.slice("ordered:" /* Ordered */.length)
|
|
738
|
-
);
|
|
780
|
+
if (pattern.charCodeAt(0) === 98 && pattern.startsWith("broadcast:" /* Broadcast */)) {
|
|
781
|
+
return BROADCAST_SUBJECT_PREFIX + pattern.slice("broadcast:" /* Broadcast */.length);
|
|
739
782
|
}
|
|
740
|
-
|
|
783
|
+
if (pattern.charCodeAt(0) === 111 && pattern.startsWith("ordered:" /* Ordered */)) {
|
|
784
|
+
return this.orderedSubjectPrefix + pattern.slice("ordered:" /* Ordered */.length);
|
|
785
|
+
}
|
|
786
|
+
return this.eventSubjectPrefix + pattern;
|
|
741
787
|
}
|
|
742
788
|
/** Build NATS headers merging custom headers with transport headers. */
|
|
743
789
|
buildHeaders(customHeaders, transport) {
|
|
@@ -762,7 +808,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
762
808
|
if (rawData instanceof JetstreamRecord) {
|
|
763
809
|
return {
|
|
764
810
|
data: rawData.data,
|
|
765
|
-
hdrs: rawData.headers.size > 0 ?
|
|
811
|
+
hdrs: rawData.headers.size > 0 ? rawData.headers : null,
|
|
766
812
|
timeout: rawData.timeout,
|
|
767
813
|
messageId: rawData.messageId,
|
|
768
814
|
schedule: rawData.schedule,
|
|
@@ -805,11 +851,6 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
805
851
|
const pattern = withoutPrefix.slice(dotIndex + 1);
|
|
806
852
|
return `${targetPrefix}_sch.${pattern}`;
|
|
807
853
|
}
|
|
808
|
-
getRpcTimeout() {
|
|
809
|
-
if (!this.rootOptions.rpc) return DEFAULT_RPC_TIMEOUT;
|
|
810
|
-
const defaultTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? DEFAULT_JETSTREAM_RPC_TIMEOUT : DEFAULT_RPC_TIMEOUT;
|
|
811
|
-
return this.rootOptions.rpc.timeout ?? defaultTimeout;
|
|
812
|
-
}
|
|
813
854
|
};
|
|
814
855
|
|
|
815
856
|
// src/codec/json.codec.ts
|
|
@@ -824,6 +865,19 @@ var JsonCodec = class {
|
|
|
824
865
|
}
|
|
825
866
|
};
|
|
826
867
|
|
|
868
|
+
// src/codec/msgpack.codec.ts
|
|
869
|
+
var MsgpackCodec = class {
|
|
870
|
+
constructor(packr) {
|
|
871
|
+
this.packr = packr;
|
|
872
|
+
}
|
|
873
|
+
encode(data) {
|
|
874
|
+
return this.packr.pack(data);
|
|
875
|
+
}
|
|
876
|
+
decode(data) {
|
|
877
|
+
return this.packr.unpack(data);
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
|
|
827
881
|
// src/connection/connection.provider.ts
|
|
828
882
|
var import_common2 = require("@nestjs/common");
|
|
829
883
|
var import_transport_node2 = require("@nats-io/transport-node");
|
|
@@ -1036,6 +1090,15 @@ var EventBus = class {
|
|
|
1036
1090
|
kind
|
|
1037
1091
|
);
|
|
1038
1092
|
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Check whether a hook is registered for the given event.
|
|
1095
|
+
*
|
|
1096
|
+
* Used by the routing hot path to elide the emit call entirely when the
|
|
1097
|
+
* transport owner did not register a listener.
|
|
1098
|
+
*/
|
|
1099
|
+
hasHook(event) {
|
|
1100
|
+
return this.hooks[event] !== void 0;
|
|
1101
|
+
}
|
|
1039
1102
|
callHook(event, hook, ...args) {
|
|
1040
1103
|
try {
|
|
1041
1104
|
const result = hook(...args);
|
|
@@ -1418,16 +1481,71 @@ var resolveAckExtensionInterval = (config, ackWaitNanos) => {
|
|
|
1418
1481
|
const interval = Math.floor(ackWaitNanos / 1e6 / 2);
|
|
1419
1482
|
return Math.max(interval, MIN_ACK_EXTENSION_INTERVAL);
|
|
1420
1483
|
};
|
|
1484
|
+
var AckExtensionPool = class {
|
|
1485
|
+
entries = /* @__PURE__ */ new Set();
|
|
1486
|
+
handle = null;
|
|
1487
|
+
handleFireAt = 0;
|
|
1488
|
+
schedule(msg, interval) {
|
|
1489
|
+
const entry = {
|
|
1490
|
+
msg,
|
|
1491
|
+
interval,
|
|
1492
|
+
nextFireAt: Date.now() + interval,
|
|
1493
|
+
active: true
|
|
1494
|
+
};
|
|
1495
|
+
this.entries.add(entry);
|
|
1496
|
+
this.ensureWake(entry.nextFireAt);
|
|
1497
|
+
return entry;
|
|
1498
|
+
}
|
|
1499
|
+
cancel(entry) {
|
|
1500
|
+
if (!entry.active) return;
|
|
1501
|
+
entry.active = false;
|
|
1502
|
+
this.entries.delete(entry);
|
|
1503
|
+
if (this.entries.size === 0 && this.handle !== null) {
|
|
1504
|
+
clearTimeout(this.handle);
|
|
1505
|
+
this.handle = null;
|
|
1506
|
+
this.handleFireAt = 0;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Ensure the shared timer will fire no later than `dueAt`. If an earlier
|
|
1511
|
+
* wake is already scheduled, leave it; otherwise replace it with a tighter one.
|
|
1512
|
+
*/
|
|
1513
|
+
ensureWake(dueAt) {
|
|
1514
|
+
if (this.handle !== null && this.handleFireAt <= dueAt) return;
|
|
1515
|
+
if (this.handle !== null) clearTimeout(this.handle);
|
|
1516
|
+
const delay = Math.max(0, dueAt - Date.now());
|
|
1517
|
+
const handle = setTimeout(() => {
|
|
1518
|
+
this.tick();
|
|
1519
|
+
}, delay);
|
|
1520
|
+
if (typeof handle.unref === "function") handle.unref();
|
|
1521
|
+
this.handle = handle;
|
|
1522
|
+
this.handleFireAt = dueAt;
|
|
1523
|
+
}
|
|
1524
|
+
tick() {
|
|
1525
|
+
this.handle = null;
|
|
1526
|
+
this.handleFireAt = 0;
|
|
1527
|
+
const now = Date.now();
|
|
1528
|
+
let earliest = Infinity;
|
|
1529
|
+
for (const entry of this.entries) {
|
|
1530
|
+
if (!entry.active) continue;
|
|
1531
|
+
if (entry.nextFireAt <= now) {
|
|
1532
|
+
try {
|
|
1533
|
+
entry.msg.working();
|
|
1534
|
+
} catch {
|
|
1535
|
+
}
|
|
1536
|
+
entry.nextFireAt = now + entry.interval;
|
|
1537
|
+
}
|
|
1538
|
+
if (entry.nextFireAt < earliest) earliest = entry.nextFireAt;
|
|
1539
|
+
}
|
|
1540
|
+
if (earliest !== Infinity) this.ensureWake(earliest);
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
var pool = new AckExtensionPool();
|
|
1421
1544
|
var startAckExtensionTimer = (msg, interval) => {
|
|
1422
1545
|
if (interval === null || interval <= 0) return null;
|
|
1423
|
-
const
|
|
1424
|
-
try {
|
|
1425
|
-
msg.working();
|
|
1426
|
-
} catch {
|
|
1427
|
-
}
|
|
1428
|
-
}, interval);
|
|
1546
|
+
const entry = pool.schedule(msg, interval);
|
|
1429
1547
|
return () => {
|
|
1430
|
-
|
|
1548
|
+
pool.cancel(entry);
|
|
1431
1549
|
};
|
|
1432
1550
|
};
|
|
1433
1551
|
|
|
@@ -1441,11 +1559,8 @@ var serializeError = (err) => {
|
|
|
1441
1559
|
|
|
1442
1560
|
// src/utils/unwrap-result.ts
|
|
1443
1561
|
var import_rxjs2 = require("rxjs");
|
|
1444
|
-
var RESOLVED_VOID = Promise.resolve(void 0);
|
|
1445
|
-
var RESOLVED_NULL = Promise.resolve(null);
|
|
1446
1562
|
var unwrapResult = (result) => {
|
|
1447
|
-
if (result === void 0) return
|
|
1448
|
-
if (result === null) return RESOLVED_NULL;
|
|
1563
|
+
if (result === void 0 || result === null) return result;
|
|
1449
1564
|
if ((0, import_rxjs2.isObservable)(result)) {
|
|
1450
1565
|
return subscribeToFirst(result);
|
|
1451
1566
|
}
|
|
@@ -1454,8 +1569,9 @@ var unwrapResult = (result) => {
|
|
|
1454
1569
|
(resolved) => (0, import_rxjs2.isObservable)(resolved) ? subscribeToFirst(resolved) : resolved
|
|
1455
1570
|
);
|
|
1456
1571
|
}
|
|
1457
|
-
return
|
|
1572
|
+
return result;
|
|
1458
1573
|
};
|
|
1574
|
+
var isPromiseLike = (value) => value !== null && typeof value === "object" && typeof value.then === "function";
|
|
1459
1575
|
var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
|
|
1460
1576
|
let done = false;
|
|
1461
1577
|
let subscription = null;
|
|
@@ -1543,7 +1659,8 @@ var CoreRpcServer = class {
|
|
|
1543
1659
|
}
|
|
1544
1660
|
const ctx = new RpcContext([msg]);
|
|
1545
1661
|
try {
|
|
1546
|
-
const
|
|
1662
|
+
const raw = unwrapResult(handler(data, ctx));
|
|
1663
|
+
const result = isPromiseLike(raw) ? await raw : raw;
|
|
1547
1664
|
msg.respond(this.codec.encode(result));
|
|
1548
1665
|
} catch (err) {
|
|
1549
1666
|
this.logger.error(`Handler error for Core RPC ${msg.subject}:`, err);
|
|
@@ -1566,6 +1683,14 @@ var CoreRpcServer = class {
|
|
|
1566
1683
|
var import_common6 = require("@nestjs/common");
|
|
1567
1684
|
var import_jetstream14 = require("@nats-io/jetstream");
|
|
1568
1685
|
|
|
1686
|
+
// src/server/infrastructure/nats-error-codes.ts
|
|
1687
|
+
var NatsErrorCode = /* @__PURE__ */ ((NatsErrorCode2) => {
|
|
1688
|
+
NatsErrorCode2[NatsErrorCode2["ConsumerNotFound"] = 10014] = "ConsumerNotFound";
|
|
1689
|
+
NatsErrorCode2[NatsErrorCode2["ConsumerAlreadyExists"] = 10148] = "ConsumerAlreadyExists";
|
|
1690
|
+
NatsErrorCode2[NatsErrorCode2["StreamNotFound"] = 10059] = "StreamNotFound";
|
|
1691
|
+
return NatsErrorCode2;
|
|
1692
|
+
})(NatsErrorCode || {});
|
|
1693
|
+
|
|
1569
1694
|
// src/server/infrastructure/stream-config-diff.ts
|
|
1570
1695
|
var TRANSPORT_CONTROLLED_PROPERTIES = /* @__PURE__ */ new Set([
|
|
1571
1696
|
"retention"
|
|
@@ -2254,13 +2379,17 @@ var MessageProvider = class {
|
|
|
2254
2379
|
}
|
|
2255
2380
|
const defaults = { idle_heartbeat: 5e3 };
|
|
2256
2381
|
const userOptions = this.consumeOptionsMap.get(kind) ?? {};
|
|
2257
|
-
const messages = await consumer.consume({
|
|
2382
|
+
const messages = await consumer.consume({
|
|
2383
|
+
...defaults,
|
|
2384
|
+
...userOptions,
|
|
2385
|
+
callback: (msg) => {
|
|
2386
|
+
target$.next(msg);
|
|
2387
|
+
}
|
|
2388
|
+
});
|
|
2258
2389
|
this.activeIterators.add(messages);
|
|
2259
2390
|
this.monitorConsumerHealth(messages, consumerName2);
|
|
2260
2391
|
try {
|
|
2261
|
-
|
|
2262
|
-
target$.next(msg);
|
|
2263
|
-
}
|
|
2392
|
+
await messages.closed();
|
|
2264
2393
|
} finally {
|
|
2265
2394
|
this.activeIterators.delete(messages);
|
|
2266
2395
|
}
|
|
@@ -2352,11 +2481,16 @@ var MessageProvider = class {
|
|
|
2352
2481
|
})
|
|
2353
2482
|
);
|
|
2354
2483
|
}
|
|
2355
|
-
/** Single iteration: create ordered consumer ->
|
|
2484
|
+
/** Single iteration: create ordered consumer -> push messages into the subject. */
|
|
2356
2485
|
async consumeOrderedOnce(streamName2, consumerOpts) {
|
|
2357
2486
|
const js = this.connection.getJetStreamClient();
|
|
2358
2487
|
const consumer = await js.consumers.get(streamName2, consumerOpts);
|
|
2359
|
-
const
|
|
2488
|
+
const orderedMessages$ = this.orderedMessages$;
|
|
2489
|
+
const messages = await consumer.consume({
|
|
2490
|
+
callback: (msg) => {
|
|
2491
|
+
orderedMessages$.next(msg);
|
|
2492
|
+
}
|
|
2493
|
+
});
|
|
2360
2494
|
if (this.orderedReadyResolve) {
|
|
2361
2495
|
this.orderedReadyResolve();
|
|
2362
2496
|
this.orderedReadyResolve = null;
|
|
@@ -2364,9 +2498,7 @@ var MessageProvider = class {
|
|
|
2364
2498
|
}
|
|
2365
2499
|
this.activeIterators.add(messages);
|
|
2366
2500
|
try {
|
|
2367
|
-
|
|
2368
|
-
this.orderedMessages$.next(msg);
|
|
2369
|
-
}
|
|
2501
|
+
await messages.closed();
|
|
2370
2502
|
} finally {
|
|
2371
2503
|
this.activeIterators.delete(messages);
|
|
2372
2504
|
}
|
|
@@ -2645,7 +2777,6 @@ var PatternRegistry = class {
|
|
|
2645
2777
|
|
|
2646
2778
|
// src/server/routing/event.router.ts
|
|
2647
2779
|
var import_common11 = require("@nestjs/common");
|
|
2648
|
-
var import_rxjs4 = require("rxjs");
|
|
2649
2780
|
var import_transport_node4 = require("@nats-io/transport-node");
|
|
2650
2781
|
var EventRouter = class {
|
|
2651
2782
|
constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig, processingConfig, ackWaitMap, connection, options) {
|
|
@@ -2684,15 +2815,194 @@ var EventRouter = class {
|
|
|
2684
2815
|
}
|
|
2685
2816
|
this.subscriptions.length = 0;
|
|
2686
2817
|
}
|
|
2687
|
-
/** Subscribe to a message stream and route each message. */
|
|
2818
|
+
/** Subscribe to a message stream and route each message to its handler. */
|
|
2688
2819
|
subscribeToStream(stream$, kind) {
|
|
2689
2820
|
const isOrdered = kind === "ordered" /* Ordered */;
|
|
2821
|
+
const patternRegistry = this.patternRegistry;
|
|
2822
|
+
const codec = this.codec;
|
|
2823
|
+
const eventBus = this.eventBus;
|
|
2824
|
+
const logger = this.logger;
|
|
2825
|
+
const deadLetterConfig = this.deadLetterConfig;
|
|
2690
2826
|
const ackExtensionInterval = isOrdered ? null : resolveAckExtensionInterval(this.getAckExtensionConfig(kind), this.ackWaitMap?.get(kind));
|
|
2827
|
+
const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
|
|
2691
2828
|
const concurrency = this.getConcurrency(kind);
|
|
2692
|
-
const
|
|
2693
|
-
|
|
2694
|
-
)
|
|
2695
|
-
|
|
2829
|
+
const hasDlqCheck = deadLetterConfig !== void 0;
|
|
2830
|
+
const emitRouted = eventBus.hasHook("messageRouted" /* MessageRouted */);
|
|
2831
|
+
const isDeadLetter = (msg) => {
|
|
2832
|
+
if (!hasDlqCheck) return false;
|
|
2833
|
+
const maxDeliver = deadLetterConfig.maxDeliverByStream?.get(msg.info.stream);
|
|
2834
|
+
if (maxDeliver === void 0 || maxDeliver <= 0) return false;
|
|
2835
|
+
return msg.info.deliveryCount >= maxDeliver;
|
|
2836
|
+
};
|
|
2837
|
+
const handleDeadLetter = hasDlqCheck ? (msg, data, err) => this.handleDeadLetter(msg, data, err) : null;
|
|
2838
|
+
const settleSuccess = (msg, ctx) => {
|
|
2839
|
+
if (ctx.shouldTerminate) msg.term(ctx.terminateReason);
|
|
2840
|
+
else if (ctx.shouldRetry) msg.nak(ctx.retryDelay);
|
|
2841
|
+
else msg.ack();
|
|
2842
|
+
};
|
|
2843
|
+
const settleFailure = async (msg, data, err) => {
|
|
2844
|
+
if (handleDeadLetter !== null && isDeadLetter(msg)) {
|
|
2845
|
+
await handleDeadLetter(msg, data, err);
|
|
2846
|
+
return;
|
|
2847
|
+
}
|
|
2848
|
+
msg.nak();
|
|
2849
|
+
};
|
|
2850
|
+
const resolveEvent = (msg) => {
|
|
2851
|
+
const subject = msg.subject;
|
|
2852
|
+
try {
|
|
2853
|
+
const handler = patternRegistry.getHandler(subject);
|
|
2854
|
+
if (!handler) {
|
|
2855
|
+
msg.term(`No handler for event: ${subject}`);
|
|
2856
|
+
logger.error(`No handler for subject: ${subject}`);
|
|
2857
|
+
return null;
|
|
2858
|
+
}
|
|
2859
|
+
let data;
|
|
2860
|
+
try {
|
|
2861
|
+
data = codec.decode(msg.data);
|
|
2862
|
+
} catch (err) {
|
|
2863
|
+
msg.term("Decode error");
|
|
2864
|
+
logger.error(`Decode error for ${subject}:`, err);
|
|
2865
|
+
return null;
|
|
2866
|
+
}
|
|
2867
|
+
if (emitRouted) eventBus.emitMessageRouted(subject, "event" /* Event */);
|
|
2868
|
+
return { handler, data };
|
|
2869
|
+
} catch (err) {
|
|
2870
|
+
logger.error(`Unexpected error in ${kind} event router`, err);
|
|
2871
|
+
try {
|
|
2872
|
+
msg.term("Unexpected router error");
|
|
2873
|
+
} catch (termErr) {
|
|
2874
|
+
logger.error(`Failed to terminate message ${subject}:`, termErr);
|
|
2875
|
+
}
|
|
2876
|
+
return null;
|
|
2877
|
+
}
|
|
2878
|
+
};
|
|
2879
|
+
const handleSafe = (msg) => {
|
|
2880
|
+
const resolved = resolveEvent(msg);
|
|
2881
|
+
if (resolved === null) return void 0;
|
|
2882
|
+
const { handler, data } = resolved;
|
|
2883
|
+
const ctx = new RpcContext([msg]);
|
|
2884
|
+
const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
|
|
2885
|
+
let pending;
|
|
2886
|
+
try {
|
|
2887
|
+
pending = unwrapResult(handler(data, ctx));
|
|
2888
|
+
} catch (err) {
|
|
2889
|
+
logger.error(`Event handler error (${msg.subject}) in ${kind} router:`, err);
|
|
2890
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
2891
|
+
return settleFailure(msg, data, err);
|
|
2892
|
+
}
|
|
2893
|
+
if (!isPromiseLike(pending)) {
|
|
2894
|
+
settleSuccess(msg, ctx);
|
|
2895
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
2896
|
+
return void 0;
|
|
2897
|
+
}
|
|
2898
|
+
return pending.then(
|
|
2899
|
+
() => {
|
|
2900
|
+
settleSuccess(msg, ctx);
|
|
2901
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
2902
|
+
},
|
|
2903
|
+
async (err) => {
|
|
2904
|
+
logger.error(`Event handler error (${msg.subject}) in ${kind} router:`, err);
|
|
2905
|
+
try {
|
|
2906
|
+
await settleFailure(msg, data, err);
|
|
2907
|
+
} finally {
|
|
2908
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
);
|
|
2912
|
+
};
|
|
2913
|
+
const handleOrderedSafe = (msg) => {
|
|
2914
|
+
const subject = msg.subject;
|
|
2915
|
+
let handler;
|
|
2916
|
+
let data;
|
|
2917
|
+
try {
|
|
2918
|
+
handler = patternRegistry.getHandler(subject);
|
|
2919
|
+
if (!handler) {
|
|
2920
|
+
logger.error(`No handler for subject: ${subject}`);
|
|
2921
|
+
return void 0;
|
|
2922
|
+
}
|
|
2923
|
+
try {
|
|
2924
|
+
data = codec.decode(msg.data);
|
|
2925
|
+
} catch (err) {
|
|
2926
|
+
logger.error(`Decode error for ${subject}:`, err);
|
|
2927
|
+
return void 0;
|
|
2928
|
+
}
|
|
2929
|
+
if (emitRouted) eventBus.emitMessageRouted(subject, "event" /* Event */);
|
|
2930
|
+
} catch (err) {
|
|
2931
|
+
logger.error(`Ordered handler error (${subject}):`, err);
|
|
2932
|
+
return void 0;
|
|
2933
|
+
}
|
|
2934
|
+
const ctx = new RpcContext([msg]);
|
|
2935
|
+
const warnIfSettlementAttempted = () => {
|
|
2936
|
+
if (ctx.shouldRetry || ctx.shouldTerminate) {
|
|
2937
|
+
logger.warn(
|
|
2938
|
+
`retry()/terminate() ignored for ordered message ${subject} \u2014 ordered consumers auto-acknowledge`
|
|
2939
|
+
);
|
|
2940
|
+
}
|
|
2941
|
+
};
|
|
2942
|
+
let pending;
|
|
2943
|
+
try {
|
|
2944
|
+
pending = unwrapResult(handler(data, ctx));
|
|
2945
|
+
} catch (err) {
|
|
2946
|
+
logger.error(`Ordered handler error (${subject}):`, err);
|
|
2947
|
+
return void 0;
|
|
2948
|
+
}
|
|
2949
|
+
if (!isPromiseLike(pending)) {
|
|
2950
|
+
warnIfSettlementAttempted();
|
|
2951
|
+
return void 0;
|
|
2952
|
+
}
|
|
2953
|
+
return pending.then(warnIfSettlementAttempted, (err) => {
|
|
2954
|
+
logger.error(`Ordered handler error (${subject}):`, err);
|
|
2955
|
+
});
|
|
2956
|
+
};
|
|
2957
|
+
const route = isOrdered ? handleOrderedSafe : handleSafe;
|
|
2958
|
+
const maxActive = isOrdered ? 1 : concurrency ?? Number.POSITIVE_INFINITY;
|
|
2959
|
+
const backlogWarnThreshold = 1e3;
|
|
2960
|
+
let active = 0;
|
|
2961
|
+
let backlogWarned = false;
|
|
2962
|
+
const backlog = [];
|
|
2963
|
+
const onAsyncDone = () => {
|
|
2964
|
+
active--;
|
|
2965
|
+
drainBacklog();
|
|
2966
|
+
};
|
|
2967
|
+
const drainBacklog = () => {
|
|
2968
|
+
while (active < maxActive) {
|
|
2969
|
+
const next = backlog.shift();
|
|
2970
|
+
if (next === void 0) return;
|
|
2971
|
+
active++;
|
|
2972
|
+
const result = route(next);
|
|
2973
|
+
if (result !== void 0) {
|
|
2974
|
+
void result.finally(onAsyncDone);
|
|
2975
|
+
} else {
|
|
2976
|
+
active--;
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
if (backlog.length < backlogWarnThreshold) backlogWarned = false;
|
|
2980
|
+
};
|
|
2981
|
+
const subscription = stream$.subscribe({
|
|
2982
|
+
next: (msg) => {
|
|
2983
|
+
if (active >= maxActive) {
|
|
2984
|
+
backlog.push(msg);
|
|
2985
|
+
if (!backlogWarned && backlog.length >= backlogWarnThreshold) {
|
|
2986
|
+
backlogWarned = true;
|
|
2987
|
+
logger.warn(
|
|
2988
|
+
`${kind} backlog reached ${backlog.length} messages \u2014 consumer may be falling behind`
|
|
2989
|
+
);
|
|
2990
|
+
}
|
|
2991
|
+
return;
|
|
2992
|
+
}
|
|
2993
|
+
active++;
|
|
2994
|
+
const result = route(msg);
|
|
2995
|
+
if (result !== void 0) {
|
|
2996
|
+
void result.finally(onAsyncDone);
|
|
2997
|
+
} else {
|
|
2998
|
+
active--;
|
|
2999
|
+
if (backlog.length > 0) drainBacklog();
|
|
3000
|
+
}
|
|
3001
|
+
},
|
|
3002
|
+
error: (err) => {
|
|
3003
|
+
logger.error(`Stream error in ${kind} router`, err);
|
|
3004
|
+
}
|
|
3005
|
+
});
|
|
2696
3006
|
this.subscriptions.push(subscription);
|
|
2697
3007
|
}
|
|
2698
3008
|
getConcurrency(kind) {
|
|
@@ -2705,86 +3015,6 @@ var EventRouter = class {
|
|
|
2705
3015
|
if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.ackExtension;
|
|
2706
3016
|
return void 0;
|
|
2707
3017
|
}
|
|
2708
|
-
/** Handle a single event message with error isolation. */
|
|
2709
|
-
async handleSafe(msg, ackExtensionInterval, kind) {
|
|
2710
|
-
try {
|
|
2711
|
-
const resolved = this.decodeMessage(msg);
|
|
2712
|
-
if (!resolved) return;
|
|
2713
|
-
await this.executeHandler(
|
|
2714
|
-
resolved.handler,
|
|
2715
|
-
resolved.data,
|
|
2716
|
-
resolved.ctx,
|
|
2717
|
-
msg,
|
|
2718
|
-
ackExtensionInterval
|
|
2719
|
-
);
|
|
2720
|
-
} catch (err) {
|
|
2721
|
-
this.logger.error(`Unexpected error in ${kind} event router`, err);
|
|
2722
|
-
}
|
|
2723
|
-
}
|
|
2724
|
-
/** Handle an ordered message with error isolation. */
|
|
2725
|
-
async handleOrderedSafe(msg) {
|
|
2726
|
-
try {
|
|
2727
|
-
const resolved = this.decodeMessage(msg, true);
|
|
2728
|
-
if (!resolved) return;
|
|
2729
|
-
await unwrapResult(resolved.handler(resolved.data, resolved.ctx));
|
|
2730
|
-
if (resolved.ctx.shouldRetry || resolved.ctx.shouldTerminate) {
|
|
2731
|
-
this.logger.warn(
|
|
2732
|
-
`retry()/terminate() ignored for ordered message ${msg.subject} \u2014 ordered consumers auto-acknowledge`
|
|
2733
|
-
);
|
|
2734
|
-
}
|
|
2735
|
-
} catch (err) {
|
|
2736
|
-
this.logger.error(`Ordered handler error (${msg.subject}):`, err);
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
/** Resolve handler, decode payload, and build context. Returns null on failure. */
|
|
2740
|
-
decodeMessage(msg, isOrdered = false) {
|
|
2741
|
-
const handler = this.patternRegistry.getHandler(msg.subject);
|
|
2742
|
-
if (!handler) {
|
|
2743
|
-
if (!isOrdered) msg.term(`No handler for event: ${msg.subject}`);
|
|
2744
|
-
this.logger.error(`No handler for subject: ${msg.subject}`);
|
|
2745
|
-
return null;
|
|
2746
|
-
}
|
|
2747
|
-
let data;
|
|
2748
|
-
try {
|
|
2749
|
-
data = this.codec.decode(msg.data);
|
|
2750
|
-
} catch (err) {
|
|
2751
|
-
if (!isOrdered) msg.term("Decode error");
|
|
2752
|
-
this.logger.error(`Decode error for ${msg.subject}:`, err);
|
|
2753
|
-
return null;
|
|
2754
|
-
}
|
|
2755
|
-
this.eventBus.emitMessageRouted(msg.subject, "event" /* Event */);
|
|
2756
|
-
return { handler, data, ctx: new RpcContext([msg]) };
|
|
2757
|
-
}
|
|
2758
|
-
/** Execute handler, then ack on success or nak/dead-letter on failure. */
|
|
2759
|
-
async executeHandler(handler, data, ctx, msg, ackExtensionInterval) {
|
|
2760
|
-
const stopAckExtension = startAckExtensionTimer(msg, ackExtensionInterval);
|
|
2761
|
-
try {
|
|
2762
|
-
await unwrapResult(handler(data, ctx));
|
|
2763
|
-
if (ctx.shouldTerminate) {
|
|
2764
|
-
msg.term(ctx.terminateReason);
|
|
2765
|
-
} else if (ctx.shouldRetry) {
|
|
2766
|
-
msg.nak(ctx.retryDelay);
|
|
2767
|
-
} else {
|
|
2768
|
-
msg.ack();
|
|
2769
|
-
}
|
|
2770
|
-
} catch (err) {
|
|
2771
|
-
this.logger.error(`Event handler error (${msg.subject}):`, err);
|
|
2772
|
-
if (this.isDeadLetter(msg)) {
|
|
2773
|
-
await this.handleDeadLetter(msg, data, err);
|
|
2774
|
-
} else {
|
|
2775
|
-
msg.nak();
|
|
2776
|
-
}
|
|
2777
|
-
} finally {
|
|
2778
|
-
stopAckExtension?.();
|
|
2779
|
-
}
|
|
2780
|
-
}
|
|
2781
|
-
/** Check if the message has exhausted all delivery attempts. */
|
|
2782
|
-
isDeadLetter(msg) {
|
|
2783
|
-
if (!this.deadLetterConfig) return false;
|
|
2784
|
-
const maxDeliver = this.deadLetterConfig.maxDeliverByStream.get(msg.info.stream);
|
|
2785
|
-
if (maxDeliver === void 0 || maxDeliver <= 0) return false;
|
|
2786
|
-
return msg.info.deliveryCount >= maxDeliver;
|
|
2787
|
-
}
|
|
2788
3018
|
/** Handle a dead letter: invoke callback, then term or nak based on result. */
|
|
2789
3019
|
/**
|
|
2790
3020
|
* Fallback execution for a dead letter when DLQ is disabled, or when
|
|
@@ -2896,7 +3126,6 @@ var EventRouter = class {
|
|
|
2896
3126
|
// src/server/routing/rpc.router.ts
|
|
2897
3127
|
var import_common12 = require("@nestjs/common");
|
|
2898
3128
|
var import_transport_node5 = require("@nats-io/transport-node");
|
|
2899
|
-
var import_rxjs5 = require("rxjs");
|
|
2900
3129
|
var RpcRouter = class {
|
|
2901
3130
|
constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap) {
|
|
2902
3131
|
this.messageProvider = messageProvider;
|
|
@@ -2927,87 +3156,178 @@ var RpcRouter = class {
|
|
|
2927
3156
|
/** Start routing command messages to handlers. */
|
|
2928
3157
|
async start() {
|
|
2929
3158
|
this.cachedNc = await this.connection.getConnection();
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
this.
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
this.logger.error(`No handler for RPC subject: ${msg.subject}`);
|
|
2944
|
-
return;
|
|
2945
|
-
}
|
|
2946
|
-
const { headers: msgHeaders } = msg;
|
|
2947
|
-
const replyTo = msgHeaders?.get("x-reply-to" /* ReplyTo */);
|
|
2948
|
-
const correlationId = msgHeaders?.get("x-correlation-id" /* CorrelationId */);
|
|
2949
|
-
if (!replyTo || !correlationId) {
|
|
2950
|
-
msg.term("Missing required headers (reply-to or correlation-id)");
|
|
2951
|
-
this.logger.error(`Missing headers for RPC: ${msg.subject}`);
|
|
2952
|
-
return;
|
|
2953
|
-
}
|
|
2954
|
-
let data;
|
|
2955
|
-
try {
|
|
2956
|
-
data = this.codec.decode(msg.data);
|
|
2957
|
-
} catch (err) {
|
|
2958
|
-
msg.term("Decode error");
|
|
2959
|
-
this.logger.error(`Decode error for RPC ${msg.subject}:`, err);
|
|
2960
|
-
return;
|
|
2961
|
-
}
|
|
2962
|
-
this.eventBus.emitMessageRouted(msg.subject, "rpc" /* Rpc */);
|
|
2963
|
-
await this.executeHandler(handler, data, msg, replyTo, correlationId);
|
|
2964
|
-
} catch (err) {
|
|
2965
|
-
this.logger.error("Unexpected error in RPC router", err);
|
|
2966
|
-
}
|
|
2967
|
-
}
|
|
2968
|
-
/** Execute handler, publish response, settle message. */
|
|
2969
|
-
async executeHandler(handler, data, msg, replyTo, correlationId) {
|
|
2970
|
-
const nc = this.cachedNc ?? await this.connection.getConnection();
|
|
2971
|
-
const ctx = new RpcContext([msg]);
|
|
2972
|
-
let settled = false;
|
|
2973
|
-
const stopAckExtension = startAckExtensionTimer(msg, this.ackExtensionInterval);
|
|
2974
|
-
const timeoutId = setTimeout(() => {
|
|
2975
|
-
if (settled) return;
|
|
2976
|
-
settled = true;
|
|
2977
|
-
stopAckExtension?.();
|
|
2978
|
-
this.logger.error(`RPC timeout (${this.timeout}ms): ${msg.subject}`);
|
|
2979
|
-
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, msg.subject, correlationId);
|
|
2980
|
-
msg.term("Handler timeout");
|
|
2981
|
-
}, this.timeout);
|
|
2982
|
-
try {
|
|
2983
|
-
const result = await unwrapResult(handler(data, ctx));
|
|
2984
|
-
if (settled) return;
|
|
2985
|
-
settled = true;
|
|
2986
|
-
clearTimeout(timeoutId);
|
|
2987
|
-
stopAckExtension?.();
|
|
2988
|
-
msg.ack();
|
|
3159
|
+
const nc = this.cachedNc;
|
|
3160
|
+
const patternRegistry = this.patternRegistry;
|
|
3161
|
+
const codec = this.codec;
|
|
3162
|
+
const eventBus = this.eventBus;
|
|
3163
|
+
const logger = this.logger;
|
|
3164
|
+
const timeout = this.timeout;
|
|
3165
|
+
const ackExtensionInterval = this.ackExtensionInterval;
|
|
3166
|
+
const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
|
|
3167
|
+
const maxActive = this.concurrency ?? Number.POSITIVE_INFINITY;
|
|
3168
|
+
const emitRpcTimeout = (subject, correlationId) => {
|
|
3169
|
+
eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
|
|
3170
|
+
};
|
|
3171
|
+
const publishReply = (replyTo, correlationId, payload) => {
|
|
2989
3172
|
try {
|
|
2990
3173
|
const hdrs = (0, import_transport_node5.headers)();
|
|
2991
3174
|
hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
|
|
2992
|
-
nc.publish(replyTo,
|
|
3175
|
+
nc.publish(replyTo, codec.encode(payload), { headers: hdrs });
|
|
2993
3176
|
} catch (publishErr) {
|
|
2994
|
-
|
|
3177
|
+
logger.error(`Failed to publish RPC response`, publishErr);
|
|
2995
3178
|
}
|
|
2996
|
-
}
|
|
2997
|
-
|
|
2998
|
-
settled = true;
|
|
2999
|
-
clearTimeout(timeoutId);
|
|
3000
|
-
stopAckExtension?.();
|
|
3179
|
+
};
|
|
3180
|
+
const publishErrorReply = (replyTo, correlationId, subject, err) => {
|
|
3001
3181
|
try {
|
|
3002
3182
|
const hdrs = (0, import_transport_node5.headers)();
|
|
3003
3183
|
hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
|
|
3004
3184
|
hdrs.set("x-error" /* Error */, "true");
|
|
3005
|
-
nc.publish(replyTo,
|
|
3185
|
+
nc.publish(replyTo, codec.encode(serializeError(err)), { headers: hdrs });
|
|
3006
3186
|
} catch (encodeErr) {
|
|
3007
|
-
|
|
3187
|
+
logger.error(`Failed to encode RPC error for ${subject}`, encodeErr);
|
|
3008
3188
|
}
|
|
3009
|
-
|
|
3010
|
-
|
|
3189
|
+
};
|
|
3190
|
+
const resolveCommand = (msg) => {
|
|
3191
|
+
const subject = msg.subject;
|
|
3192
|
+
try {
|
|
3193
|
+
const handler = patternRegistry.getHandler(subject);
|
|
3194
|
+
if (!handler) {
|
|
3195
|
+
msg.term(`No handler for RPC: ${subject}`);
|
|
3196
|
+
logger.error(`No handler for RPC subject: ${subject}`);
|
|
3197
|
+
return null;
|
|
3198
|
+
}
|
|
3199
|
+
const msgHeaders = msg.headers;
|
|
3200
|
+
const replyTo = msgHeaders?.get("x-reply-to" /* ReplyTo */);
|
|
3201
|
+
const correlationId = msgHeaders?.get("x-correlation-id" /* CorrelationId */);
|
|
3202
|
+
if (!replyTo || !correlationId) {
|
|
3203
|
+
msg.term("Missing required headers (reply-to or correlation-id)");
|
|
3204
|
+
logger.error(`Missing headers for RPC: ${subject}`);
|
|
3205
|
+
return null;
|
|
3206
|
+
}
|
|
3207
|
+
let data;
|
|
3208
|
+
try {
|
|
3209
|
+
data = codec.decode(msg.data);
|
|
3210
|
+
} catch (err) {
|
|
3211
|
+
msg.term("Decode error");
|
|
3212
|
+
logger.error(`Decode error for RPC ${subject}:`, err);
|
|
3213
|
+
return null;
|
|
3214
|
+
}
|
|
3215
|
+
eventBus.emitMessageRouted(subject, "rpc" /* Rpc */);
|
|
3216
|
+
return { handler, data, replyTo, correlationId };
|
|
3217
|
+
} catch (err) {
|
|
3218
|
+
logger.error("Unexpected error in RPC router", err);
|
|
3219
|
+
try {
|
|
3220
|
+
msg.term("Unexpected router error");
|
|
3221
|
+
} catch (termErr) {
|
|
3222
|
+
logger.error(`Failed to terminate RPC message ${subject}:`, termErr);
|
|
3223
|
+
}
|
|
3224
|
+
return null;
|
|
3225
|
+
}
|
|
3226
|
+
};
|
|
3227
|
+
const handleSafe = (msg) => {
|
|
3228
|
+
const resolved = resolveCommand(msg);
|
|
3229
|
+
if (resolved === null) return void 0;
|
|
3230
|
+
const { handler, data, replyTo, correlationId } = resolved;
|
|
3231
|
+
const subject = msg.subject;
|
|
3232
|
+
const ctx = new RpcContext([msg]);
|
|
3233
|
+
const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
|
|
3234
|
+
let pending;
|
|
3235
|
+
try {
|
|
3236
|
+
pending = unwrapResult(handler(data, ctx));
|
|
3237
|
+
} catch (err) {
|
|
3238
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
3239
|
+
logger.error(`RPC handler error (${subject}):`, err);
|
|
3240
|
+
publishErrorReply(replyTo, correlationId, subject, err);
|
|
3241
|
+
msg.term(`Handler error: ${subject}`);
|
|
3242
|
+
return void 0;
|
|
3243
|
+
}
|
|
3244
|
+
if (!isPromiseLike(pending)) {
|
|
3245
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
3246
|
+
msg.ack();
|
|
3247
|
+
publishReply(replyTo, correlationId, pending);
|
|
3248
|
+
return void 0;
|
|
3249
|
+
}
|
|
3250
|
+
let settled = false;
|
|
3251
|
+
const timeoutId = setTimeout(() => {
|
|
3252
|
+
if (settled) return;
|
|
3253
|
+
settled = true;
|
|
3254
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
3255
|
+
logger.error(`RPC timeout (${timeout}ms): ${subject}`);
|
|
3256
|
+
emitRpcTimeout(subject, correlationId);
|
|
3257
|
+
msg.term("Handler timeout");
|
|
3258
|
+
}, timeout);
|
|
3259
|
+
return pending.then(
|
|
3260
|
+
(result) => {
|
|
3261
|
+
if (settled) return;
|
|
3262
|
+
settled = true;
|
|
3263
|
+
clearTimeout(timeoutId);
|
|
3264
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
3265
|
+
msg.ack();
|
|
3266
|
+
publishReply(replyTo, correlationId, result);
|
|
3267
|
+
},
|
|
3268
|
+
(err) => {
|
|
3269
|
+
if (settled) return;
|
|
3270
|
+
settled = true;
|
|
3271
|
+
clearTimeout(timeoutId);
|
|
3272
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
3273
|
+
logger.error(`RPC handler error (${subject}):`, err);
|
|
3274
|
+
publishErrorReply(replyTo, correlationId, subject, err);
|
|
3275
|
+
msg.term(`Handler error: ${subject}`);
|
|
3276
|
+
}
|
|
3277
|
+
);
|
|
3278
|
+
};
|
|
3279
|
+
const backlogWarnThreshold = 1e3;
|
|
3280
|
+
let active = 0;
|
|
3281
|
+
let backlogWarned = false;
|
|
3282
|
+
const backlog = [];
|
|
3283
|
+
const onAsyncDone = () => {
|
|
3284
|
+
active--;
|
|
3285
|
+
drainBacklog();
|
|
3286
|
+
};
|
|
3287
|
+
const drainBacklog = () => {
|
|
3288
|
+
while (active < maxActive) {
|
|
3289
|
+
const next = backlog.shift();
|
|
3290
|
+
if (next === void 0) return;
|
|
3291
|
+
active++;
|
|
3292
|
+
const result = handleSafe(next);
|
|
3293
|
+
if (result !== void 0) {
|
|
3294
|
+
void result.finally(onAsyncDone);
|
|
3295
|
+
} else {
|
|
3296
|
+
active--;
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
if (backlog.length < backlogWarnThreshold) backlogWarned = false;
|
|
3300
|
+
};
|
|
3301
|
+
this.subscription = this.messageProvider.commands$.subscribe({
|
|
3302
|
+
next: (msg) => {
|
|
3303
|
+
if (active >= maxActive) {
|
|
3304
|
+
backlog.push(msg);
|
|
3305
|
+
if (!backlogWarned && backlog.length >= backlogWarnThreshold) {
|
|
3306
|
+
backlogWarned = true;
|
|
3307
|
+
logger.warn(
|
|
3308
|
+
`RPC backlog reached ${backlog.length} messages \u2014 consumer may be falling behind`
|
|
3309
|
+
);
|
|
3310
|
+
}
|
|
3311
|
+
return;
|
|
3312
|
+
}
|
|
3313
|
+
active++;
|
|
3314
|
+
const result = handleSafe(msg);
|
|
3315
|
+
if (result !== void 0) {
|
|
3316
|
+
void result.finally(onAsyncDone);
|
|
3317
|
+
} else {
|
|
3318
|
+
active--;
|
|
3319
|
+
if (backlog.length > 0) drainBacklog();
|
|
3320
|
+
}
|
|
3321
|
+
},
|
|
3322
|
+
error: (err) => {
|
|
3323
|
+
logger.error("Stream error in RPC router", err);
|
|
3324
|
+
}
|
|
3325
|
+
});
|
|
3326
|
+
}
|
|
3327
|
+
/** Stop routing and unsubscribe. */
|
|
3328
|
+
destroy() {
|
|
3329
|
+
this.subscription?.unsubscribe();
|
|
3330
|
+
this.subscription = null;
|
|
3011
3331
|
}
|
|
3012
3332
|
};
|
|
3013
3333
|
|
|
@@ -3456,14 +3776,23 @@ JetstreamModule = __decorateClass([
|
|
|
3456
3776
|
], JetstreamModule);
|
|
3457
3777
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3458
3778
|
0 && (module.exports = {
|
|
3779
|
+
DEFAULT_BROADCAST_CONSUMER_CONFIG,
|
|
3780
|
+
DEFAULT_BROADCAST_STREAM_CONFIG,
|
|
3781
|
+
DEFAULT_COMMAND_CONSUMER_CONFIG,
|
|
3782
|
+
DEFAULT_COMMAND_STREAM_CONFIG,
|
|
3783
|
+
DEFAULT_DLQ_STREAM_CONFIG,
|
|
3784
|
+
DEFAULT_EVENT_CONSUMER_CONFIG,
|
|
3785
|
+
DEFAULT_EVENT_STREAM_CONFIG,
|
|
3786
|
+
DEFAULT_JETSTREAM_RPC_TIMEOUT,
|
|
3459
3787
|
DEFAULT_METADATA_BUCKET,
|
|
3460
3788
|
DEFAULT_METADATA_HISTORY,
|
|
3461
3789
|
DEFAULT_METADATA_REPLICAS,
|
|
3462
3790
|
DEFAULT_METADATA_TTL,
|
|
3463
|
-
|
|
3791
|
+
DEFAULT_ORDERED_STREAM_CONFIG,
|
|
3792
|
+
DEFAULT_RPC_TIMEOUT,
|
|
3793
|
+
DEFAULT_SHUTDOWN_TIMEOUT,
|
|
3464
3794
|
JETSTREAM_CODEC,
|
|
3465
3795
|
JETSTREAM_CONNECTION,
|
|
3466
|
-
JETSTREAM_EVENT_BUS,
|
|
3467
3796
|
JETSTREAM_OPTIONS,
|
|
3468
3797
|
JetstreamClient,
|
|
3469
3798
|
JetstreamDlqHeader,
|
|
@@ -3476,7 +3805,10 @@ JetstreamModule = __decorateClass([
|
|
|
3476
3805
|
JsonCodec,
|
|
3477
3806
|
MIN_METADATA_TTL,
|
|
3478
3807
|
MessageKind,
|
|
3808
|
+
MsgpackCodec,
|
|
3809
|
+
NatsErrorCode,
|
|
3479
3810
|
PatternPrefix,
|
|
3811
|
+
RESERVED_HEADERS,
|
|
3480
3812
|
RpcContext,
|
|
3481
3813
|
StreamKind,
|
|
3482
3814
|
TransportEvent,
|