@horizon-republic/nestjs-jetstream 2.10.0 → 2.11.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 +58 -71
- package/dist/index.cjs +988 -290
- package/dist/index.d.cts +209 -46
- package/dist/index.d.ts +209 -46
- package/dist/index.js +973 -281
- package/package.json +25 -20
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from2, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
20
30
|
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
@@ -85,7 +95,7 @@ __export(index_exports, {
|
|
|
85
95
|
module.exports = __toCommonJS(index_exports);
|
|
86
96
|
|
|
87
97
|
// src/jetstream.module.ts
|
|
88
|
-
var
|
|
98
|
+
var import_common21 = require("@nestjs/common");
|
|
89
99
|
|
|
90
100
|
// src/client/jetstream.client.ts
|
|
91
101
|
var import_common5 = require("@nestjs/common");
|
|
@@ -110,6 +120,10 @@ var TransportEvent = /* @__PURE__ */ ((TransportEvent2) => {
|
|
|
110
120
|
TransportEvent2["ShutdownStart"] = "shutdownStart";
|
|
111
121
|
TransportEvent2["ShutdownComplete"] = "shutdownComplete";
|
|
112
122
|
TransportEvent2["DeadLetter"] = "deadLetter";
|
|
123
|
+
TransportEvent2["ConsumerRecovered"] = "consumerRecovered";
|
|
124
|
+
TransportEvent2["HandlerCompleted"] = "handlerCompleted";
|
|
125
|
+
TransportEvent2["Published"] = "published";
|
|
126
|
+
TransportEvent2["RpcCompleted"] = "rpcCompleted";
|
|
113
127
|
return TransportEvent2;
|
|
114
128
|
})(TransportEvent || {});
|
|
115
129
|
|
|
@@ -498,6 +512,8 @@ var resolveCaptureBody = (option) => {
|
|
|
498
512
|
};
|
|
499
513
|
};
|
|
500
514
|
var resolveOtelOptions = (options = {}) => {
|
|
515
|
+
if (options === true) options = {};
|
|
516
|
+
if (options === false) options = { enabled: false };
|
|
501
517
|
return {
|
|
502
518
|
enabled: options.enabled ?? true,
|
|
503
519
|
traces: expandTracesOption(options.traces),
|
|
@@ -577,7 +593,7 @@ var extractContext = (ctx, carrier, getter) => import_api.propagation.extract(ct
|
|
|
577
593
|
|
|
578
594
|
// src/otel/tracer.ts
|
|
579
595
|
var import_api2 = require("@opentelemetry/api");
|
|
580
|
-
var PACKAGE_VERSION = true ? "2.
|
|
596
|
+
var PACKAGE_VERSION = true ? "2.11.1" : "0.0.0";
|
|
581
597
|
var getTracer = () => import_api2.trace.getTracer(TRACER_NAME, PACKAGE_VERSION);
|
|
582
598
|
|
|
583
599
|
// src/otel/carrier.ts
|
|
@@ -1368,6 +1384,17 @@ var detectEventKind = (pattern) => {
|
|
|
1368
1384
|
if (pattern.startsWith("ordered:" /* Ordered */)) return "ordered" /* Ordered */;
|
|
1369
1385
|
return "event" /* Event */;
|
|
1370
1386
|
};
|
|
1387
|
+
var declaredEventPattern = (pattern) => {
|
|
1388
|
+
if (pattern.startsWith("broadcast:" /* Broadcast */))
|
|
1389
|
+
return pattern.slice("broadcast:" /* Broadcast */.length);
|
|
1390
|
+
if (pattern.startsWith("ordered:" /* Ordered */)) return pattern.slice("ordered:" /* Ordered */.length);
|
|
1391
|
+
return pattern;
|
|
1392
|
+
};
|
|
1393
|
+
var eventStreamKind = (kind) => {
|
|
1394
|
+
if (kind === "broadcast" /* Broadcast */) return "broadcast" /* Broadcast */;
|
|
1395
|
+
if (kind === "ordered" /* Ordered */) return "ordered" /* Ordered */;
|
|
1396
|
+
return "ev" /* Event */;
|
|
1397
|
+
};
|
|
1371
1398
|
var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
1372
1399
|
constructor(rootOptions, targetServiceName, connection, codec, eventBus) {
|
|
1373
1400
|
super();
|
|
@@ -1483,50 +1510,60 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
1483
1510
|
const encoded = this.codec.encode(data);
|
|
1484
1511
|
const effectiveMsgId = messageId ?? import_nuid.nuid.next();
|
|
1485
1512
|
const record = packet.data instanceof JetstreamRecord ? packet.data : new JetstreamRecord(data, /* @__PURE__ */ new Map());
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1513
|
+
const publishKind = detectEventKind(packet.pattern);
|
|
1514
|
+
const declaredPattern = declaredEventPattern(packet.pattern);
|
|
1515
|
+
const streamKind = eventStreamKind(publishKind);
|
|
1516
|
+
const startedAt = performance.now();
|
|
1517
|
+
try {
|
|
1518
|
+
await withPublishSpan(
|
|
1519
|
+
{
|
|
1520
|
+
subject: publishSubject,
|
|
1521
|
+
pattern: packet.pattern,
|
|
1522
|
+
record,
|
|
1523
|
+
kind: publishKind,
|
|
1524
|
+
payloadBytes: encoded.length,
|
|
1525
|
+
payload: encoded,
|
|
1526
|
+
messageId: effectiveMsgId,
|
|
1527
|
+
headers: msgHeaders,
|
|
1528
|
+
serviceName: this.callerName,
|
|
1529
|
+
endpoint: this.serverEndpoint,
|
|
1530
|
+
scheduleTarget: schedule ? eventSubject : void 0
|
|
1531
|
+
},
|
|
1532
|
+
this.otel,
|
|
1533
|
+
async () => {
|
|
1534
|
+
const warnIfDuplicate = (kindLabel, ack2) => {
|
|
1535
|
+
if (ack2.duplicate) {
|
|
1536
|
+
this.logger.warn(
|
|
1537
|
+
`Duplicate ${kindLabel} publish detected: ${publishSubject} (seq: ${ack2.seq})`
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
if (schedule) {
|
|
1542
|
+
const ack2 = await this.connection.getJetStreamClient().publish(publishSubject, encoded, {
|
|
1543
|
+
headers: msgHeaders,
|
|
1544
|
+
msgID: effectiveMsgId,
|
|
1545
|
+
ttl,
|
|
1546
|
+
schedule: {
|
|
1547
|
+
specification: schedule.at,
|
|
1548
|
+
target: eventSubject
|
|
1549
|
+
}
|
|
1550
|
+
});
|
|
1551
|
+
warnIfDuplicate("scheduled", ack2);
|
|
1552
|
+
return;
|
|
1507
1553
|
}
|
|
1508
|
-
|
|
1509
|
-
if (schedule) {
|
|
1510
|
-
const ack2 = await this.connection.getJetStreamClient().publish(publishSubject, encoded, {
|
|
1554
|
+
const ack = await this.connection.getJetStreamClient().publish(publishSubject, encoded, {
|
|
1511
1555
|
headers: msgHeaders,
|
|
1512
1556
|
msgID: effectiveMsgId,
|
|
1513
|
-
ttl
|
|
1514
|
-
schedule: {
|
|
1515
|
-
specification: schedule.at,
|
|
1516
|
-
target: eventSubject
|
|
1517
|
-
}
|
|
1557
|
+
ttl
|
|
1518
1558
|
});
|
|
1519
|
-
warnIfDuplicate("
|
|
1520
|
-
return;
|
|
1559
|
+
warnIfDuplicate("event", ack);
|
|
1521
1560
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
}
|
|
1529
|
-
);
|
|
1561
|
+
);
|
|
1562
|
+
this.reportPublished(declaredPattern, streamKind, startedAt, "success");
|
|
1563
|
+
} catch (err) {
|
|
1564
|
+
this.reportPublished(declaredPattern, streamKind, startedAt, "error");
|
|
1565
|
+
throw err;
|
|
1566
|
+
}
|
|
1530
1567
|
return void 0;
|
|
1531
1568
|
}
|
|
1532
1569
|
/**
|
|
@@ -1554,14 +1591,17 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
1554
1591
|
};
|
|
1555
1592
|
let jetStreamCorrelationId = null;
|
|
1556
1593
|
if (this.isCoreMode) {
|
|
1557
|
-
this.publishCoreRpc(subject, data, hdrs, timeout, callback).catch(
|
|
1594
|
+
this.publishCoreRpc(subject, data, hdrs, timeout, callback, packet.pattern).catch(
|
|
1595
|
+
onUnhandled
|
|
1596
|
+
);
|
|
1558
1597
|
} else {
|
|
1559
1598
|
jetStreamCorrelationId = import_nuid.nuid.next();
|
|
1560
1599
|
this.publishJetStreamRpc(subject, data, callback, {
|
|
1561
1600
|
headers: hdrs,
|
|
1562
1601
|
timeout,
|
|
1563
1602
|
correlationId: jetStreamCorrelationId,
|
|
1564
|
-
messageId
|
|
1603
|
+
messageId,
|
|
1604
|
+
declaredPattern: packet.pattern
|
|
1565
1605
|
}).catch(onUnhandled);
|
|
1566
1606
|
}
|
|
1567
1607
|
return () => {
|
|
@@ -1576,7 +1616,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
1576
1616
|
};
|
|
1577
1617
|
}
|
|
1578
1618
|
/** Core mode: nc.request() with timeout. */
|
|
1579
|
-
async publishCoreRpc(subject, data, customHeaders, timeout, callback) {
|
|
1619
|
+
async publishCoreRpc(subject, data, customHeaders, timeout, callback, declaredPattern) {
|
|
1580
1620
|
const effectiveTimeout = timeout ?? this.defaultRpcTimeout;
|
|
1581
1621
|
const hdrs = this.buildHeaders(customHeaders, { subject });
|
|
1582
1622
|
const encoded = this.codec.encode(data);
|
|
@@ -1591,6 +1631,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
1591
1631
|
},
|
|
1592
1632
|
this.otel
|
|
1593
1633
|
);
|
|
1634
|
+
const startedAt = performance.now();
|
|
1594
1635
|
try {
|
|
1595
1636
|
const nc = this.readyForPublish ? this.connection.unwrap : await this.connect();
|
|
1596
1637
|
const response = await import_api8.context.with(
|
|
@@ -1603,9 +1644,13 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
1603
1644
|
const decoded = this.codec.decode(response.data);
|
|
1604
1645
|
if (response.headers?.get("x-error" /* Error */)) {
|
|
1605
1646
|
spanHandle.finish({ kind: "reply-error" /* ReplyError */, replyPayload: decoded });
|
|
1647
|
+
this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "success");
|
|
1648
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "error");
|
|
1606
1649
|
callback({ err: decoded, response: null, isDisposed: true });
|
|
1607
1650
|
} else {
|
|
1608
1651
|
spanHandle.finish({ kind: "ok" /* Ok */, reply: decoded });
|
|
1652
|
+
this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "success");
|
|
1653
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "success");
|
|
1609
1654
|
callback({ err: null, response: decoded, isDisposed: true });
|
|
1610
1655
|
}
|
|
1611
1656
|
} catch (err) {
|
|
@@ -1613,16 +1658,20 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
1613
1658
|
if (error instanceof import_transport_node.TimeoutError) {
|
|
1614
1659
|
spanHandle.finish({ kind: "timeout" /* Timeout */ });
|
|
1615
1660
|
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, "");
|
|
1661
|
+
this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "success");
|
|
1662
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "timeout");
|
|
1616
1663
|
} else {
|
|
1617
1664
|
spanHandle.finish({ kind: "error" /* Error */, error });
|
|
1618
1665
|
this.eventBus.emit("error" /* Error */, error, "client-rpc");
|
|
1666
|
+
this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "error");
|
|
1667
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "error");
|
|
1619
1668
|
}
|
|
1620
1669
|
callback({ err: error, response: null, isDisposed: true });
|
|
1621
1670
|
}
|
|
1622
1671
|
}
|
|
1623
1672
|
/** JetStream mode: publish to stream + wait for inbox response. */
|
|
1624
1673
|
async publishJetStreamRpc(subject, data, callback, options) {
|
|
1625
|
-
const { headers: customHeaders, correlationId, messageId } = options;
|
|
1674
|
+
const { headers: customHeaders, correlationId, messageId, declaredPattern } = options;
|
|
1626
1675
|
const effectiveTimeout = options.timeout ?? this.defaultRpcTimeout;
|
|
1627
1676
|
const hdrs = this.buildHeaders(customHeaders, {
|
|
1628
1677
|
subject,
|
|
@@ -1643,6 +1692,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
1643
1692
|
},
|
|
1644
1693
|
this.otel
|
|
1645
1694
|
);
|
|
1695
|
+
const startedAt = performance.now();
|
|
1646
1696
|
this.pendingMessages.set(correlationId, (packet) => {
|
|
1647
1697
|
if (packet.err) {
|
|
1648
1698
|
if (packet.err instanceof Error) {
|
|
@@ -1650,8 +1700,10 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
1650
1700
|
} else {
|
|
1651
1701
|
spanHandle.finish({ kind: "reply-error" /* ReplyError */, replyPayload: packet.err });
|
|
1652
1702
|
}
|
|
1703
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "error");
|
|
1653
1704
|
} else {
|
|
1654
1705
|
spanHandle.finish({ kind: "ok" /* Ok */, reply: packet.response });
|
|
1706
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "success");
|
|
1655
1707
|
}
|
|
1656
1708
|
callback(packet);
|
|
1657
1709
|
});
|
|
@@ -1661,6 +1713,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
1661
1713
|
this.pendingMessages.delete(correlationId);
|
|
1662
1714
|
spanHandle.finish({ kind: "timeout" /* Timeout */ });
|
|
1663
1715
|
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
|
|
1716
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "timeout");
|
|
1664
1717
|
callback({ err: new Error(RPC_TIMEOUT_MESSAGE), response: null, isDisposed: true });
|
|
1665
1718
|
}, effectiveTimeout);
|
|
1666
1719
|
this.pendingTimeouts.set(correlationId, timeoutId);
|
|
@@ -1673,6 +1726,8 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
1673
1726
|
this.pendingMessages.delete(correlationId);
|
|
1674
1727
|
const inboxError = new Error("Inbox not initialized");
|
|
1675
1728
|
spanHandle.finish({ kind: "error" /* Error */, error: inboxError });
|
|
1729
|
+
this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "error");
|
|
1730
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "error");
|
|
1676
1731
|
callback({
|
|
1677
1732
|
err: new Error("Inbox not initialized \u2014 JetStream RPC mode requires a connected inbox"),
|
|
1678
1733
|
response: null,
|
|
@@ -1687,6 +1742,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
1687
1742
|
msgID: messageId ?? import_nuid.nuid.next()
|
|
1688
1743
|
})
|
|
1689
1744
|
);
|
|
1745
|
+
this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "success");
|
|
1690
1746
|
} catch (err) {
|
|
1691
1747
|
const existingTimeout = this.pendingTimeouts.get(correlationId);
|
|
1692
1748
|
if (existingTimeout) {
|
|
@@ -1698,9 +1754,32 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
1698
1754
|
const error = err instanceof Error ? err : new Error("Unknown error");
|
|
1699
1755
|
spanHandle.finish({ kind: "error" /* Error */, error });
|
|
1700
1756
|
this.eventBus.emit("error" /* Error */, error, `jetstream-rpc-publish:${subject}`);
|
|
1757
|
+
this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "error");
|
|
1758
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "error");
|
|
1701
1759
|
callback({ err: error, response: null, isDisposed: true });
|
|
1702
1760
|
}
|
|
1703
1761
|
}
|
|
1762
|
+
// hasHook is per-emit so late subscribers (JetstreamMetricsService during
|
|
1763
|
+
// OnApplicationBootstrap) still receive events.
|
|
1764
|
+
reportPublished(declaredPattern, kind, startedAt, status) {
|
|
1765
|
+
if (!this.eventBus.hasHook("published" /* Published */)) return;
|
|
1766
|
+
this.eventBus.emit(
|
|
1767
|
+
"published" /* Published */,
|
|
1768
|
+
declaredPattern,
|
|
1769
|
+
kind,
|
|
1770
|
+
performance.now() - startedAt,
|
|
1771
|
+
status
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
reportRpcCompleted(declaredPattern, startedAt, status) {
|
|
1775
|
+
if (!this.eventBus.hasHook("rpcCompleted" /* RpcCompleted */)) return;
|
|
1776
|
+
this.eventBus.emit(
|
|
1777
|
+
"rpcCompleted" /* RpcCompleted */,
|
|
1778
|
+
declaredPattern,
|
|
1779
|
+
performance.now() - startedAt,
|
|
1780
|
+
status
|
|
1781
|
+
);
|
|
1782
|
+
}
|
|
1704
1783
|
/** Fail-fast all pending JetStream RPC callbacks on connection loss. */
|
|
1705
1784
|
handleDisconnect() {
|
|
1706
1785
|
this.rejectPendingRpcs(new Error("Connection lost"));
|
|
@@ -2106,43 +2185,53 @@ var ConnectionProvider = class {
|
|
|
2106
2185
|
var EventBus = class {
|
|
2107
2186
|
hooks;
|
|
2108
2187
|
logger;
|
|
2188
|
+
subscribers = /* @__PURE__ */ new Map();
|
|
2109
2189
|
constructor(logger5, hooks) {
|
|
2110
2190
|
this.logger = logger5;
|
|
2111
2191
|
this.hooks = hooks ?? {};
|
|
2112
2192
|
}
|
|
2113
2193
|
/**
|
|
2114
|
-
*
|
|
2115
|
-
*
|
|
2116
|
-
|
|
2117
|
-
|
|
2194
|
+
* Subscribe to a transport event. Used by built-in observers (e.g. metrics).
|
|
2195
|
+
* Multiple subscribers per event are supported; each is called independently.
|
|
2196
|
+
*/
|
|
2197
|
+
subscribe(event, handler) {
|
|
2198
|
+
const list = this.subscribers.get(event) ?? [];
|
|
2199
|
+
list.push(handler);
|
|
2200
|
+
this.subscribers.set(event, list);
|
|
2201
|
+
}
|
|
2202
|
+
/**
|
|
2203
|
+
* Emit a lifecycle event. Dispatches to all internal subscribers and the
|
|
2204
|
+
* registered user hook (if any).
|
|
2118
2205
|
*/
|
|
2119
2206
|
emit(event, ...args) {
|
|
2120
|
-
|
|
2121
|
-
if (!hook) return;
|
|
2122
|
-
this.callHook(event, hook, ...args);
|
|
2207
|
+
this.dispatch(event, args);
|
|
2123
2208
|
}
|
|
2124
2209
|
/**
|
|
2125
2210
|
* Hot-path optimized emit for MessageRouted events.
|
|
2126
2211
|
* Avoids rest/spread overhead of the generic `emit()`.
|
|
2127
2212
|
*/
|
|
2128
2213
|
emitMessageRouted(subject, kind) {
|
|
2129
|
-
|
|
2130
|
-
if (!hook) return;
|
|
2131
|
-
this.callHook(
|
|
2132
|
-
"messageRouted" /* MessageRouted */,
|
|
2133
|
-
hook,
|
|
2134
|
-
subject,
|
|
2135
|
-
kind
|
|
2136
|
-
);
|
|
2214
|
+
this.dispatch("messageRouted" /* MessageRouted */, [subject, kind]);
|
|
2137
2215
|
}
|
|
2138
2216
|
/**
|
|
2139
|
-
* Check whether
|
|
2140
|
-
*
|
|
2141
|
-
*
|
|
2142
|
-
* transport owner did not register a listener.
|
|
2217
|
+
* Check whether any listener (user hook or internal subscriber) is registered
|
|
2218
|
+
* for the given event. Used by routing hot path to elide the emit call when
|
|
2219
|
+
* no one is listening.
|
|
2143
2220
|
*/
|
|
2144
2221
|
hasHook(event) {
|
|
2145
|
-
return this.hooks[event] !== void 0;
|
|
2222
|
+
return this.hooks[event] !== void 0 || (this.subscribers.get(event)?.length ?? 0) > 0;
|
|
2223
|
+
}
|
|
2224
|
+
dispatch(event, args) {
|
|
2225
|
+
const subs = this.subscribers.get(event);
|
|
2226
|
+
if (subs?.length) {
|
|
2227
|
+
for (const sub of [...subs]) {
|
|
2228
|
+
this.callHook(event, sub, ...args);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
const hook = this.hooks[event];
|
|
2232
|
+
if (hook) {
|
|
2233
|
+
this.callHook(event, hook, ...args);
|
|
2234
|
+
}
|
|
2146
2235
|
}
|
|
2147
2236
|
callHook(event, hook, ...args) {
|
|
2148
2237
|
try {
|
|
@@ -2221,12 +2310,692 @@ var JetstreamHealthIndicator = class {
|
|
|
2221
2310
|
isHealthCheckError: true
|
|
2222
2311
|
});
|
|
2223
2312
|
}
|
|
2224
|
-
return { [key]: details };
|
|
2313
|
+
return { [key]: details };
|
|
2314
|
+
}
|
|
2315
|
+
};
|
|
2316
|
+
JetstreamHealthIndicator = __decorateClass([
|
|
2317
|
+
(0, import_common7.Injectable)()
|
|
2318
|
+
], JetstreamHealthIndicator);
|
|
2319
|
+
|
|
2320
|
+
// src/metrics/metrics.module.ts
|
|
2321
|
+
var import_common11 = require("@nestjs/common");
|
|
2322
|
+
|
|
2323
|
+
// src/server/routing/pattern-registry.ts
|
|
2324
|
+
var import_common8 = require("@nestjs/common");
|
|
2325
|
+
var HANDLER_LABELS = {
|
|
2326
|
+
["broadcast" /* Broadcast */]: "broadcast" /* Broadcast */,
|
|
2327
|
+
["ordered" /* Ordered */]: "ordered" /* Ordered */,
|
|
2328
|
+
["ev" /* Event */]: "event" /* Event */,
|
|
2329
|
+
["cmd" /* Command */]: "rpc" /* Rpc */
|
|
2330
|
+
};
|
|
2331
|
+
var PatternRegistry = class {
|
|
2332
|
+
constructor(options) {
|
|
2333
|
+
this.options = options;
|
|
2334
|
+
}
|
|
2335
|
+
logger = new import_common8.Logger("Jetstream:PatternRegistry");
|
|
2336
|
+
registry = /* @__PURE__ */ new Map();
|
|
2337
|
+
// Cached after registerHandlers() — the registry is immutable from that point
|
|
2338
|
+
cachedPatterns = null;
|
|
2339
|
+
_hasEvents = false;
|
|
2340
|
+
_hasCommands = false;
|
|
2341
|
+
_hasBroadcasts = false;
|
|
2342
|
+
_hasOrdered = false;
|
|
2343
|
+
_hasMetadata = false;
|
|
2344
|
+
/**
|
|
2345
|
+
* Register all handlers from the NestJS strategy.
|
|
2346
|
+
*
|
|
2347
|
+
* @param handlers Map of pattern -> MessageHandler from `Server.getHandlers()`.
|
|
2348
|
+
*/
|
|
2349
|
+
registerHandlers(handlers) {
|
|
2350
|
+
const serviceName = this.options.name;
|
|
2351
|
+
for (const [pattern, handler] of handlers) {
|
|
2352
|
+
const extras = handler.extras;
|
|
2353
|
+
const isEvent = handler.isEventHandler ?? false;
|
|
2354
|
+
const isBroadcast = !!extras?.broadcast;
|
|
2355
|
+
const isOrdered = !!extras?.ordered;
|
|
2356
|
+
const meta = extras?.meta;
|
|
2357
|
+
if (isBroadcast && isOrdered) {
|
|
2358
|
+
throw new Error(
|
|
2359
|
+
`Handler "${pattern}" cannot be both broadcast and ordered. Use one or the other.`
|
|
2360
|
+
);
|
|
2361
|
+
}
|
|
2362
|
+
let kind;
|
|
2363
|
+
if (isBroadcast) kind = "broadcast" /* Broadcast */;
|
|
2364
|
+
else if (isOrdered) kind = "ordered" /* Ordered */;
|
|
2365
|
+
else if (isEvent) kind = "ev" /* Event */;
|
|
2366
|
+
else kind = "cmd" /* Command */;
|
|
2367
|
+
const fullSubject = kind === "broadcast" /* Broadcast */ ? buildBroadcastSubject(pattern) : buildSubject(serviceName, kind, pattern);
|
|
2368
|
+
this.registry.set(fullSubject, {
|
|
2369
|
+
handler,
|
|
2370
|
+
pattern,
|
|
2371
|
+
isEvent: isEvent && !isOrdered,
|
|
2372
|
+
isBroadcast,
|
|
2373
|
+
isOrdered,
|
|
2374
|
+
meta
|
|
2375
|
+
});
|
|
2376
|
+
this.logger.debug(`Registered ${HANDLER_LABELS[kind]}: ${pattern} -> ${fullSubject}`);
|
|
2377
|
+
}
|
|
2378
|
+
this.cachedPatterns = this.buildPatternsByKind();
|
|
2379
|
+
this._hasEvents = this.cachedPatterns.events.length > 0;
|
|
2380
|
+
this._hasCommands = this.cachedPatterns.commands.length > 0;
|
|
2381
|
+
this._hasBroadcasts = this.cachedPatterns.broadcasts.length > 0;
|
|
2382
|
+
this._hasOrdered = this.cachedPatterns.ordered.length > 0;
|
|
2383
|
+
this._hasMetadata = [...this.registry.values()].some((entry) => entry.meta !== void 0);
|
|
2384
|
+
this.logSummary();
|
|
2385
|
+
}
|
|
2386
|
+
/** Find handler for a full NATS subject. */
|
|
2387
|
+
getHandler(subject) {
|
|
2388
|
+
return this.registry.get(subject)?.handler ?? null;
|
|
2389
|
+
}
|
|
2390
|
+
/**
|
|
2391
|
+
* Resolve the declared pattern and {@link StreamKind} for a full NATS subject.
|
|
2392
|
+
*
|
|
2393
|
+
* Returns `null` when the subject is not registered. The declared pattern is
|
|
2394
|
+
* the value the user passed to `@EventPattern`/`@MessagePattern` — stable and
|
|
2395
|
+
* bounded, suitable for use as a Prometheus label without cardinality risk.
|
|
2396
|
+
*/
|
|
2397
|
+
resolveDeclared(subject) {
|
|
2398
|
+
const entry = this.registry.get(subject);
|
|
2399
|
+
if (!entry) return null;
|
|
2400
|
+
return { pattern: entry.pattern, kind: this.resolveStreamKind(entry) };
|
|
2401
|
+
}
|
|
2402
|
+
/** Get all registered broadcast patterns (for consumer filter_subject setup). */
|
|
2403
|
+
getBroadcastPatterns() {
|
|
2404
|
+
return this.getPatternsByKind().broadcasts.map((p) => buildBroadcastSubject(p));
|
|
2405
|
+
}
|
|
2406
|
+
hasBroadcastHandlers() {
|
|
2407
|
+
return this._hasBroadcasts;
|
|
2408
|
+
}
|
|
2409
|
+
hasRpcHandlers() {
|
|
2410
|
+
return this._hasCommands;
|
|
2411
|
+
}
|
|
2412
|
+
hasEventHandlers() {
|
|
2413
|
+
return this._hasEvents;
|
|
2414
|
+
}
|
|
2415
|
+
hasOrderedHandlers() {
|
|
2416
|
+
return this._hasOrdered;
|
|
2417
|
+
}
|
|
2418
|
+
/** Get fully-qualified NATS subjects for ordered handlers. */
|
|
2419
|
+
getOrderedSubjects() {
|
|
2420
|
+
return this.getPatternsByKind().ordered.map(
|
|
2421
|
+
(p) => buildSubject(this.options.name, "ordered" /* Ordered */, p)
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2424
|
+
/** Check if any registered handler has metadata. */
|
|
2425
|
+
hasMetadata() {
|
|
2426
|
+
return this._hasMetadata;
|
|
2427
|
+
}
|
|
2428
|
+
/**
|
|
2429
|
+
* Get handler metadata entries for KV publishing.
|
|
2430
|
+
*
|
|
2431
|
+
* Returns a map of KV key -> metadata object for all handlers that have `meta`.
|
|
2432
|
+
* Key format: `{serviceName}.{kind}.{pattern}`.
|
|
2433
|
+
*/
|
|
2434
|
+
getMetadataEntries() {
|
|
2435
|
+
const entries = /* @__PURE__ */ new Map();
|
|
2436
|
+
for (const entry of this.registry.values()) {
|
|
2437
|
+
if (!entry.meta) continue;
|
|
2438
|
+
const kind = this.resolveStreamKind(entry);
|
|
2439
|
+
const key = metadataKey(this.options.name, kind, entry.pattern);
|
|
2440
|
+
entries.set(key, entry.meta);
|
|
2441
|
+
}
|
|
2442
|
+
return entries;
|
|
2443
|
+
}
|
|
2444
|
+
/** Get patterns grouped by kind (cached after registration). */
|
|
2445
|
+
getPatternsByKind() {
|
|
2446
|
+
const patterns = this.cachedPatterns ?? this.buildPatternsByKind();
|
|
2447
|
+
return {
|
|
2448
|
+
events: [...patterns.events],
|
|
2449
|
+
commands: [...patterns.commands],
|
|
2450
|
+
broadcasts: [...patterns.broadcasts],
|
|
2451
|
+
ordered: [...patterns.ordered]
|
|
2452
|
+
};
|
|
2453
|
+
}
|
|
2454
|
+
/** Normalize a full NATS subject back to the user-facing pattern. */
|
|
2455
|
+
normalizeSubject(subject) {
|
|
2456
|
+
const name = internalName(this.options.name);
|
|
2457
|
+
const prefixes = [
|
|
2458
|
+
`${name}.${"cmd" /* Command */}.`,
|
|
2459
|
+
`${name}.${"ev" /* Event */}.`,
|
|
2460
|
+
`${name}.${"ordered" /* Ordered */}.`,
|
|
2461
|
+
`${"broadcast" /* Broadcast */}.`
|
|
2462
|
+
];
|
|
2463
|
+
for (const prefix of prefixes) {
|
|
2464
|
+
if (subject.startsWith(prefix)) {
|
|
2465
|
+
return subject.slice(prefix.length);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
return subject;
|
|
2469
|
+
}
|
|
2470
|
+
buildPatternsByKind() {
|
|
2471
|
+
const events = [];
|
|
2472
|
+
const commands = [];
|
|
2473
|
+
const broadcasts = [];
|
|
2474
|
+
const ordered = [];
|
|
2475
|
+
for (const entry of this.registry.values()) {
|
|
2476
|
+
if (entry.isBroadcast) broadcasts.push(entry.pattern);
|
|
2477
|
+
else if (entry.isOrdered) ordered.push(entry.pattern);
|
|
2478
|
+
else if (entry.isEvent) events.push(entry.pattern);
|
|
2479
|
+
else commands.push(entry.pattern);
|
|
2480
|
+
}
|
|
2481
|
+
return { events, commands, broadcasts, ordered };
|
|
2482
|
+
}
|
|
2483
|
+
resolveStreamKind(entry) {
|
|
2484
|
+
if (entry.isBroadcast) return "broadcast" /* Broadcast */;
|
|
2485
|
+
if (entry.isOrdered) return "ordered" /* Ordered */;
|
|
2486
|
+
if (entry.isEvent) return "ev" /* Event */;
|
|
2487
|
+
return "cmd" /* Command */;
|
|
2488
|
+
}
|
|
2489
|
+
logSummary() {
|
|
2490
|
+
const { events, commands, broadcasts, ordered } = this.getPatternsByKind();
|
|
2491
|
+
const parts = [
|
|
2492
|
+
`${commands.length} RPC`,
|
|
2493
|
+
`${events.length} events`,
|
|
2494
|
+
`${broadcasts.length} broadcasts`
|
|
2495
|
+
];
|
|
2496
|
+
if (ordered.length > 0) {
|
|
2497
|
+
parts.push(`${ordered.length} ordered`);
|
|
2498
|
+
}
|
|
2499
|
+
this.logger.log(`Registered handlers: ${parts.join(", ")}`);
|
|
2500
|
+
}
|
|
2501
|
+
};
|
|
2502
|
+
|
|
2503
|
+
// src/metrics/metrics.constants.ts
|
|
2504
|
+
var JETSTREAM_METRICS_CONFIG = /* @__PURE__ */ Symbol("JETSTREAM_METRICS_CONFIG");
|
|
2505
|
+
var JETSTREAM_METRICS_REGISTRY = /* @__PURE__ */ Symbol("JETSTREAM_METRICS_REGISTRY");
|
|
2506
|
+
var JETSTREAM_METRICS_PROM_CLIENT = /* @__PURE__ */ Symbol("JETSTREAM_METRICS_PROM_CLIENT");
|
|
2507
|
+
var DEFAULT_METRICS_PREFIX = "jetstream_";
|
|
2508
|
+
var DEFAULT_POLL_INTERVAL_MS = 15e3;
|
|
2509
|
+
var DEFAULT_HISTOGRAM_BUCKETS = {
|
|
2510
|
+
handlerDuration: [1e-3, 5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
|
|
2511
|
+
publishDuration: [1e-3, 5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
|
|
2512
|
+
rpcDuration: [1e-3, 5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
|
|
2513
|
+
};
|
|
2514
|
+
var ERROR_CONTEXT_PREFIXES = [
|
|
2515
|
+
["connection", "connection"],
|
|
2516
|
+
["codec", "codec"],
|
|
2517
|
+
["client-rpc", "publish"],
|
|
2518
|
+
["jetstream-rpc-publish", "publish"],
|
|
2519
|
+
["publish", "publish"],
|
|
2520
|
+
["message-provider", "consume"],
|
|
2521
|
+
["consume", "consume"],
|
|
2522
|
+
["core-rpc-handler", "handler"],
|
|
2523
|
+
["rpc-handler", "handler"],
|
|
2524
|
+
// EventRouter formats contexts as `${StreamKind.*}-handler:...` — the enum
|
|
2525
|
+
// uses short forms (`ev`, `ordered`, `broadcast`) so both surface in the wild.
|
|
2526
|
+
["ev-handler", "handler"],
|
|
2527
|
+
["event-handler", "handler"],
|
|
2528
|
+
["broadcast-handler", "handler"],
|
|
2529
|
+
["ordered-handler", "handler"],
|
|
2530
|
+
["handler", "handler"],
|
|
2531
|
+
["shutdown", "shutdown"]
|
|
2532
|
+
];
|
|
2533
|
+
var UNMATCHED_SUBJECT_LABEL = "<unmatched>";
|
|
2534
|
+
var STREAM_KIND_LABEL = {
|
|
2535
|
+
["ev" /* Event */]: "event",
|
|
2536
|
+
["cmd" /* Command */]: "command",
|
|
2537
|
+
["broadcast" /* Broadcast */]: "broadcast",
|
|
2538
|
+
["ordered" /* Ordered */]: "ordered"
|
|
2539
|
+
};
|
|
2540
|
+
|
|
2541
|
+
// src/metrics/metrics.service.ts
|
|
2542
|
+
var import_common10 = require("@nestjs/common");
|
|
2543
|
+
|
|
2544
|
+
// src/metrics/error-context-mapper.ts
|
|
2545
|
+
var mapErrorContext = (context7) => {
|
|
2546
|
+
if (!context7) return "other";
|
|
2547
|
+
for (const [prefix, mapped] of ERROR_CONTEXT_PREFIXES) {
|
|
2548
|
+
if (context7 === prefix || context7.startsWith(`${prefix}:`)) {
|
|
2549
|
+
return mapped;
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
return "other";
|
|
2553
|
+
};
|
|
2554
|
+
|
|
2555
|
+
// src/metrics/metrics.factory.ts
|
|
2556
|
+
var createMetrics = (opts) => {
|
|
2557
|
+
const { register, promClient } = opts;
|
|
2558
|
+
const prefix = opts.prefix ?? DEFAULT_METRICS_PREFIX;
|
|
2559
|
+
const buckets = {
|
|
2560
|
+
handlerDuration: opts.buckets?.handlerDuration ?? DEFAULT_HISTOGRAM_BUCKETS.handlerDuration,
|
|
2561
|
+
publishDuration: opts.buckets?.publishDuration ?? DEFAULT_HISTOGRAM_BUCKETS.publishDuration,
|
|
2562
|
+
rpcDuration: opts.buckets?.rpcDuration ?? DEFAULT_HISTOGRAM_BUCKETS.rpcDuration
|
|
2563
|
+
};
|
|
2564
|
+
if (opts.defaultLabels && Object.keys(opts.defaultLabels).length > 0) {
|
|
2565
|
+
register.setDefaultLabels(opts.defaultLabels);
|
|
2566
|
+
}
|
|
2567
|
+
const counter = (name, help, labelNames) => new promClient.Counter({ name: `${prefix}${name}`, help, labelNames, registers: [register] });
|
|
2568
|
+
const histogram = (name, help, labelNames, bucketArr) => new promClient.Histogram({
|
|
2569
|
+
name: `${prefix}${name}`,
|
|
2570
|
+
help,
|
|
2571
|
+
labelNames,
|
|
2572
|
+
buckets: bucketArr,
|
|
2573
|
+
registers: [register]
|
|
2574
|
+
});
|
|
2575
|
+
const gauge = (name, help, labelNames) => new promClient.Gauge({ name: `${prefix}${name}`, help, labelNames, registers: [register] });
|
|
2576
|
+
return {
|
|
2577
|
+
messagesReceivedTotal: counter(
|
|
2578
|
+
"messages_received_total",
|
|
2579
|
+
"Total messages routed to a handler.",
|
|
2580
|
+
["stream", "subject", "kind"]
|
|
2581
|
+
),
|
|
2582
|
+
messagesProcessedTotal: counter(
|
|
2583
|
+
"messages_processed_total",
|
|
2584
|
+
"Total messages whose handler completed.",
|
|
2585
|
+
["stream", "subject", "kind", "status"]
|
|
2586
|
+
),
|
|
2587
|
+
messagesUnhandledTotal: counter(
|
|
2588
|
+
"messages_unhandled_total",
|
|
2589
|
+
"Messages received but not matching any registered handler.",
|
|
2590
|
+
["subject"]
|
|
2591
|
+
),
|
|
2592
|
+
messagesDeadLetterTotal: counter(
|
|
2593
|
+
"messages_dead_letter_total",
|
|
2594
|
+
"Messages routed to dead-letter after exhausting redelivery attempts.",
|
|
2595
|
+
["stream", "subject"]
|
|
2596
|
+
),
|
|
2597
|
+
publishTotal: counter(
|
|
2598
|
+
"publish_total",
|
|
2599
|
+
"Total publish/send operations performed by the client.",
|
|
2600
|
+
["subject", "kind", "status"]
|
|
2601
|
+
),
|
|
2602
|
+
rpcTimeoutTotal: counter("rpc_timeout_total", "RPC calls that exceeded the timeout deadline.", [
|
|
2603
|
+
"subject"
|
|
2604
|
+
]),
|
|
2605
|
+
consumerRecoveredTotal: counter(
|
|
2606
|
+
"consumer_recovered_total",
|
|
2607
|
+
"Self-healing recoveries after consume-loop failures.",
|
|
2608
|
+
["kind"]
|
|
2609
|
+
),
|
|
2610
|
+
errorsTotal: counter("errors_total", "Transport-level errors emitted on the EventBus.", [
|
|
2611
|
+
"context"
|
|
2612
|
+
]),
|
|
2613
|
+
handlerDurationSeconds: histogram(
|
|
2614
|
+
"handler_duration_seconds",
|
|
2615
|
+
"Wall-clock duration of handler execution.",
|
|
2616
|
+
["stream", "subject", "kind", "status"],
|
|
2617
|
+
buckets.handlerDuration
|
|
2618
|
+
),
|
|
2619
|
+
publishDurationSeconds: histogram(
|
|
2620
|
+
"publish_duration_seconds",
|
|
2621
|
+
"Wall-clock duration of client publish/send operations.",
|
|
2622
|
+
["subject", "kind", "status"],
|
|
2623
|
+
buckets.publishDuration
|
|
2624
|
+
),
|
|
2625
|
+
rpcDurationSeconds: histogram(
|
|
2626
|
+
"rpc_duration_seconds",
|
|
2627
|
+
"Wall-clock duration of RPC round-trips from client perspective.",
|
|
2628
|
+
["subject", "status"],
|
|
2629
|
+
buckets.rpcDuration
|
|
2630
|
+
),
|
|
2631
|
+
consumerNumPending: gauge(
|
|
2632
|
+
"consumer_num_pending",
|
|
2633
|
+
"Messages not yet delivered to this consumer.",
|
|
2634
|
+
["stream", "consumer", "kind"]
|
|
2635
|
+
),
|
|
2636
|
+
consumerNumAckPending: gauge(
|
|
2637
|
+
"consumer_num_ack_pending",
|
|
2638
|
+
"Messages delivered but not yet acked.",
|
|
2639
|
+
["stream", "consumer", "kind"]
|
|
2640
|
+
),
|
|
2641
|
+
consumerNumRedelivered: gauge(
|
|
2642
|
+
"consumer_num_redelivered",
|
|
2643
|
+
"Messages currently in redelivery state.",
|
|
2644
|
+
["stream", "consumer", "kind"]
|
|
2645
|
+
),
|
|
2646
|
+
consumerNumWaiting: gauge(
|
|
2647
|
+
"consumer_num_waiting",
|
|
2648
|
+
"Pull-request waiting count for this consumer.",
|
|
2649
|
+
["stream", "consumer", "kind"]
|
|
2650
|
+
),
|
|
2651
|
+
streamMessages: gauge("stream_messages", "Total messages stored in this stream.", ["stream"]),
|
|
2652
|
+
streamBytes: gauge("stream_bytes", "Total bytes stored in this stream.", ["stream"]),
|
|
2653
|
+
connectionUp: gauge("connection_up", "NATS connection state (1 connected, 0 disconnected).", [
|
|
2654
|
+
"server"
|
|
2655
|
+
]),
|
|
2656
|
+
metricsPollErrorsTotal: counter(
|
|
2657
|
+
"metrics_poll_errors_total",
|
|
2658
|
+
"Errors encountered while polling JetStreamManager for gauge data.",
|
|
2659
|
+
["target"]
|
|
2660
|
+
)
|
|
2661
|
+
};
|
|
2662
|
+
};
|
|
2663
|
+
|
|
2664
|
+
// src/metrics/poll-runner.ts
|
|
2665
|
+
var import_common9 = require("@nestjs/common");
|
|
2666
|
+
var PollRunner = class {
|
|
2667
|
+
constructor(opts) {
|
|
2668
|
+
this.opts = opts;
|
|
2669
|
+
}
|
|
2670
|
+
logger = new import_common9.Logger("Jetstream:Metrics:Poll");
|
|
2671
|
+
timer = null;
|
|
2672
|
+
inFlight = null;
|
|
2673
|
+
start() {
|
|
2674
|
+
if (this.timer !== null) return;
|
|
2675
|
+
if (this.opts.intervalMs <= 0) return;
|
|
2676
|
+
if (this.opts.targets.length === 0) return;
|
|
2677
|
+
this.timer = setInterval(() => {
|
|
2678
|
+
if (this.inFlight !== null) {
|
|
2679
|
+
this.logger.warn("Skipping poll tick \u2014 previous cycle still in flight");
|
|
2680
|
+
return;
|
|
2681
|
+
}
|
|
2682
|
+
this.inFlight = this.tick().finally(() => {
|
|
2683
|
+
this.inFlight = null;
|
|
2684
|
+
});
|
|
2685
|
+
}, this.opts.intervalMs);
|
|
2686
|
+
}
|
|
2687
|
+
async stop() {
|
|
2688
|
+
if (this.timer !== null) {
|
|
2689
|
+
clearInterval(this.timer);
|
|
2690
|
+
this.timer = null;
|
|
2691
|
+
}
|
|
2692
|
+
if (this.inFlight !== null) await this.inFlight;
|
|
2693
|
+
}
|
|
2694
|
+
/** @internal Visible for tests. Runs one poll cycle. */
|
|
2695
|
+
async tick() {
|
|
2696
|
+
let jsm;
|
|
2697
|
+
try {
|
|
2698
|
+
jsm = await this.opts.jsmFactory();
|
|
2699
|
+
} catch {
|
|
2700
|
+
this.recordPollError("jsm.connect");
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2703
|
+
await Promise.all([this.pollConsumers(jsm), this.pollStreams(jsm)]);
|
|
2704
|
+
}
|
|
2705
|
+
async pollConsumers(jsm) {
|
|
2706
|
+
for (const target of this.opts.targets) {
|
|
2707
|
+
try {
|
|
2708
|
+
const info = await jsm.consumers.info(target.stream, target.consumer);
|
|
2709
|
+
const labels = {
|
|
2710
|
+
stream: target.stream,
|
|
2711
|
+
consumer: target.consumer,
|
|
2712
|
+
kind: STREAM_KIND_LABEL[target.kind]
|
|
2713
|
+
};
|
|
2714
|
+
this.opts.metrics.consumerNumPending.labels(labels).set(info.num_pending);
|
|
2715
|
+
this.opts.metrics.consumerNumAckPending.labels(labels).set(info.num_ack_pending);
|
|
2716
|
+
this.opts.metrics.consumerNumRedelivered.labels(labels).set(info.num_redelivered);
|
|
2717
|
+
this.opts.metrics.consumerNumWaiting.labels(labels).set(info.num_waiting);
|
|
2718
|
+
} catch {
|
|
2719
|
+
this.recordPollError("consumer.info");
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
async pollStreams(jsm) {
|
|
2724
|
+
const uniqueStreams = new Set(this.opts.targets.map((t) => t.stream));
|
|
2725
|
+
for (const stream of uniqueStreams) {
|
|
2726
|
+
try {
|
|
2727
|
+
const info = await jsm.streams.info(stream);
|
|
2728
|
+
this.opts.metrics.streamMessages.labels({ stream }).set(info.state.messages);
|
|
2729
|
+
this.opts.metrics.streamBytes.labels({ stream }).set(info.state.bytes);
|
|
2730
|
+
} catch {
|
|
2731
|
+
this.recordPollError("stream.info");
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
recordPollError(target) {
|
|
2736
|
+
this.opts.metrics.metricsPollErrorsTotal.labels({ target }).inc();
|
|
2737
|
+
}
|
|
2738
|
+
};
|
|
2739
|
+
|
|
2740
|
+
// src/metrics/metrics.service.ts
|
|
2741
|
+
var JetstreamMetricsService = class {
|
|
2742
|
+
constructor(eventBus, config, promClient, options, patternRegistry, connection = null) {
|
|
2743
|
+
this.eventBus = eventBus;
|
|
2744
|
+
this.config = config;
|
|
2745
|
+
this.promClient = promClient;
|
|
2746
|
+
this.options = options;
|
|
2747
|
+
this.patternRegistry = patternRegistry;
|
|
2748
|
+
this.connection = connection;
|
|
2749
|
+
}
|
|
2750
|
+
logger = new import_common10.Logger("Jetstream:Metrics");
|
|
2751
|
+
metrics = null;
|
|
2752
|
+
pollRunner = null;
|
|
2753
|
+
activeServers = /* @__PURE__ */ new Set();
|
|
2754
|
+
async onApplicationBootstrap() {
|
|
2755
|
+
if (this.metrics !== null) return;
|
|
2756
|
+
if (!this.options.metrics || !this.config || !this.promClient) return;
|
|
2757
|
+
if (!this.config.register) {
|
|
2758
|
+
throw new Error(
|
|
2759
|
+
"JetstreamMetricsService requires a prom-client Registry \u2014 none was resolved by JetstreamMetricsModule."
|
|
2760
|
+
);
|
|
2761
|
+
}
|
|
2762
|
+
this.metrics = createMetrics({
|
|
2763
|
+
register: this.config.register,
|
|
2764
|
+
promClient: this.promClient,
|
|
2765
|
+
prefix: this.config.prefix,
|
|
2766
|
+
defaultLabels: this.config.defaultLabels,
|
|
2767
|
+
buckets: this.config.buckets
|
|
2768
|
+
});
|
|
2769
|
+
this.subscribeToEvents();
|
|
2770
|
+
this.syncInitialConnectionState();
|
|
2771
|
+
this.startPolling();
|
|
2772
|
+
this.logger.log(
|
|
2773
|
+
`Metrics enabled (prefix=${this.config.prefix ?? DEFAULT_METRICS_PREFIX}, poll=${this.getEffectivePollInterval()}ms)`
|
|
2774
|
+
);
|
|
2775
|
+
}
|
|
2776
|
+
async onModuleDestroy() {
|
|
2777
|
+
await this.pollRunner?.stop();
|
|
2778
|
+
this.pollRunner = null;
|
|
2779
|
+
}
|
|
2780
|
+
/** @internal Visible for tests. `0` disables polling. */
|
|
2781
|
+
getEffectivePollInterval() {
|
|
2782
|
+
return this.config?.pollInterval ?? DEFAULT_POLL_INTERVAL_MS;
|
|
2783
|
+
}
|
|
2784
|
+
/**
|
|
2785
|
+
* NATS connects during early bootstrap, before this service subscribes to
|
|
2786
|
+
* the EventBus — the initial `Connect` emission misses us. Mirror the
|
|
2787
|
+
* current state here so `connection_up` reflects reality the moment metrics
|
|
2788
|
+
* come online; later disconnects/reconnects update it normally.
|
|
2789
|
+
*/
|
|
2790
|
+
syncInitialConnectionState() {
|
|
2791
|
+
const nc = this.connection?.unwrap;
|
|
2792
|
+
if (!nc) return;
|
|
2793
|
+
const server = nc.getServer();
|
|
2794
|
+
this.activeServers.add(server);
|
|
2795
|
+
this.metrics?.connectionUp.labels({ server }).set(1);
|
|
2796
|
+
}
|
|
2797
|
+
/** Skips polling for publisher-only deployments and when no kinds are active. */
|
|
2798
|
+
startPolling() {
|
|
2799
|
+
const interval = this.getEffectivePollInterval();
|
|
2800
|
+
const connection = this.connection;
|
|
2801
|
+
if (interval <= 0 || !this.patternRegistry || !connection || !this.metrics) return;
|
|
2802
|
+
const targets = this.buildPollTargets();
|
|
2803
|
+
if (targets.length === 0) return;
|
|
2804
|
+
this.pollRunner = new PollRunner({
|
|
2805
|
+
intervalMs: interval,
|
|
2806
|
+
jsmFactory: async () => connection.getJetStreamManager(),
|
|
2807
|
+
metrics: this.metrics,
|
|
2808
|
+
targets
|
|
2809
|
+
});
|
|
2810
|
+
this.pollRunner.start();
|
|
2811
|
+
}
|
|
2812
|
+
buildPollTargets() {
|
|
2813
|
+
const registry = this.patternRegistry;
|
|
2814
|
+
if (!registry) return [];
|
|
2815
|
+
const targets = [];
|
|
2816
|
+
if (registry.hasEventHandlers()) {
|
|
2817
|
+
targets.push({
|
|
2818
|
+
kind: "ev" /* Event */,
|
|
2819
|
+
stream: streamName(this.options.name, "ev" /* Event */),
|
|
2820
|
+
consumer: consumerName(this.options.name, "ev" /* Event */)
|
|
2821
|
+
});
|
|
2822
|
+
}
|
|
2823
|
+
if (registry.hasRpcHandlers() && isJetStreamRpcMode(this.options.rpc)) {
|
|
2824
|
+
targets.push({
|
|
2825
|
+
kind: "cmd" /* Command */,
|
|
2826
|
+
stream: streamName(this.options.name, "cmd" /* Command */),
|
|
2827
|
+
consumer: consumerName(this.options.name, "cmd" /* Command */)
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2830
|
+
if (registry.hasBroadcastHandlers()) {
|
|
2831
|
+
targets.push({
|
|
2832
|
+
kind: "broadcast" /* Broadcast */,
|
|
2833
|
+
stream: streamName(this.options.name, "broadcast" /* Broadcast */),
|
|
2834
|
+
consumer: consumerName(this.options.name, "broadcast" /* Broadcast */)
|
|
2835
|
+
});
|
|
2836
|
+
}
|
|
2837
|
+
return targets;
|
|
2838
|
+
}
|
|
2839
|
+
subscribeToEvents() {
|
|
2840
|
+
this.eventBus.subscribe("connect" /* Connect */, this.onConnect);
|
|
2841
|
+
this.eventBus.subscribe("disconnect" /* Disconnect */, this.onDisconnect);
|
|
2842
|
+
this.eventBus.subscribe("reconnect" /* Reconnect */, this.onReconnect);
|
|
2843
|
+
this.eventBus.subscribe("error" /* Error */, this.onError);
|
|
2844
|
+
this.eventBus.subscribe("rpcTimeout" /* RpcTimeout */, this.onRpcTimeout);
|
|
2845
|
+
this.eventBus.subscribe("messageRouted" /* MessageRouted */, this.onMessageRouted);
|
|
2846
|
+
this.eventBus.subscribe("deadLetter" /* DeadLetter */, this.onDeadLetter);
|
|
2847
|
+
this.eventBus.subscribe("consumerRecovered" /* ConsumerRecovered */, this.onConsumerRecovered);
|
|
2848
|
+
this.eventBus.subscribe("handlerCompleted" /* HandlerCompleted */, this.onHandlerCompleted);
|
|
2849
|
+
this.eventBus.subscribe("published" /* Published */, this.onPublished);
|
|
2850
|
+
this.eventBus.subscribe("rpcCompleted" /* RpcCompleted */, this.onRpcCompleted);
|
|
2851
|
+
}
|
|
2852
|
+
onConnect = (server) => {
|
|
2853
|
+
this.activeServers.add(server);
|
|
2854
|
+
this.metrics?.connectionUp.labels({ server }).set(1);
|
|
2855
|
+
};
|
|
2856
|
+
onReconnect = (server) => {
|
|
2857
|
+
this.activeServers.add(server);
|
|
2858
|
+
this.metrics?.connectionUp.labels({ server }).set(1);
|
|
2859
|
+
};
|
|
2860
|
+
onDisconnect = () => {
|
|
2861
|
+
for (const server of this.activeServers) {
|
|
2862
|
+
this.metrics?.connectionUp.labels({ server }).set(0);
|
|
2863
|
+
}
|
|
2864
|
+
};
|
|
2865
|
+
onError = (_err, context7) => {
|
|
2866
|
+
this.metrics?.errorsTotal.labels({ context: mapErrorContext(context7) }).inc();
|
|
2867
|
+
};
|
|
2868
|
+
onRpcTimeout = (subject, _correlationId) => {
|
|
2869
|
+
const declared = this.resolveDeclared(subject);
|
|
2870
|
+
const subjectLabel = declared?.pattern ?? UNMATCHED_SUBJECT_LABEL;
|
|
2871
|
+
this.metrics?.rpcTimeoutTotal.labels({ subject: subjectLabel }).inc();
|
|
2872
|
+
};
|
|
2873
|
+
// `_kind` collapses broadcast/ordered into MessageKind.Event — we use
|
|
2874
|
+
// declared.kind from PatternRegistry for the precise label instead.
|
|
2875
|
+
onMessageRouted = (subject, _kind) => {
|
|
2876
|
+
if (!this.metrics) return;
|
|
2877
|
+
const declared = this.resolveDeclared(subject);
|
|
2878
|
+
if (!declared) {
|
|
2879
|
+
this.metrics.messagesUnhandledTotal.labels({ subject: UNMATCHED_SUBJECT_LABEL }).inc();
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
this.metrics.messagesReceivedTotal.labels({
|
|
2883
|
+
stream: streamName(this.options.name, declared.kind),
|
|
2884
|
+
subject: declared.pattern,
|
|
2885
|
+
kind: STREAM_KIND_LABEL[declared.kind]
|
|
2886
|
+
}).inc();
|
|
2887
|
+
};
|
|
2888
|
+
onDeadLetter = (info) => {
|
|
2889
|
+
const declared = this.resolveDeclared(info.subject);
|
|
2890
|
+
const subjectLabel = declared?.pattern ?? UNMATCHED_SUBJECT_LABEL;
|
|
2891
|
+
this.metrics?.messagesDeadLetterTotal.labels({ stream: info.stream, subject: subjectLabel }).inc();
|
|
2892
|
+
};
|
|
2893
|
+
onConsumerRecovered = (label, _attempts) => {
|
|
2894
|
+
const kindLabel = STREAM_KIND_LABEL[label] ?? String(label);
|
|
2895
|
+
this.metrics?.consumerRecoveredTotal.labels({ kind: kindLabel }).inc();
|
|
2896
|
+
};
|
|
2897
|
+
onHandlerCompleted = (pattern, kind, durationMs, status) => {
|
|
2898
|
+
if (!this.metrics) return;
|
|
2899
|
+
const stream = streamName(this.options.name, kind);
|
|
2900
|
+
const kindLabel = STREAM_KIND_LABEL[kind];
|
|
2901
|
+
const labels = { stream, subject: pattern, kind: kindLabel, status };
|
|
2902
|
+
this.metrics.messagesProcessedTotal.labels(labels).inc();
|
|
2903
|
+
this.metrics.handlerDurationSeconds.labels(labels).observe(durationMs / 1e3);
|
|
2904
|
+
};
|
|
2905
|
+
onPublished = (pattern, kind, durationMs, status) => {
|
|
2906
|
+
if (!this.metrics) return;
|
|
2907
|
+
const labels = { subject: pattern, kind: STREAM_KIND_LABEL[kind], status };
|
|
2908
|
+
this.metrics.publishTotal.labels(labels).inc();
|
|
2909
|
+
this.metrics.publishDurationSeconds.labels(labels).observe(durationMs / 1e3);
|
|
2910
|
+
};
|
|
2911
|
+
onRpcCompleted = (pattern, durationMs, status) => {
|
|
2912
|
+
this.metrics?.rpcDurationSeconds.labels({ subject: pattern, status }).observe(durationMs / 1e3);
|
|
2913
|
+
};
|
|
2914
|
+
resolveDeclared(subject) {
|
|
2915
|
+
return this.patternRegistry?.resolveDeclared(subject) ?? null;
|
|
2225
2916
|
}
|
|
2226
2917
|
};
|
|
2227
|
-
|
|
2228
|
-
(0,
|
|
2229
|
-
|
|
2918
|
+
JetstreamMetricsService = __decorateClass([
|
|
2919
|
+
(0, import_common10.Injectable)(),
|
|
2920
|
+
__decorateParam(1, (0, import_common10.Inject)(JETSTREAM_METRICS_CONFIG)),
|
|
2921
|
+
__decorateParam(2, (0, import_common10.Inject)(JETSTREAM_METRICS_PROM_CLIENT)),
|
|
2922
|
+
__decorateParam(3, (0, import_common10.Inject)(JETSTREAM_OPTIONS)),
|
|
2923
|
+
__decorateParam(4, (0, import_common10.Optional)()),
|
|
2924
|
+
__decorateParam(5, (0, import_common10.Optional)()),
|
|
2925
|
+
__decorateParam(5, (0, import_common10.Inject)(JETSTREAM_CONNECTION))
|
|
2926
|
+
], JetstreamMetricsService);
|
|
2927
|
+
|
|
2928
|
+
// src/metrics/metrics.module.ts
|
|
2929
|
+
var PROM_CLIENT_INSTALL_MESSAGE = "prom-client is required when JetstreamModule.forRoot({ metrics: ... }) is enabled. Install it with: pnpm add prom-client";
|
|
2930
|
+
var resolvePromClient = async () => {
|
|
2931
|
+
try {
|
|
2932
|
+
return await import("prom-client");
|
|
2933
|
+
} catch {
|
|
2934
|
+
throw new Error(PROM_CLIENT_INSTALL_MESSAGE);
|
|
2935
|
+
}
|
|
2936
|
+
};
|
|
2937
|
+
var normalizeMetricsConfig = (option, promClient) => {
|
|
2938
|
+
const user = option && option !== true ? option : {};
|
|
2939
|
+
return {
|
|
2940
|
+
register: user.register ?? promClient.register,
|
|
2941
|
+
prefix: user.prefix ?? DEFAULT_METRICS_PREFIX,
|
|
2942
|
+
defaultLabels: user.defaultLabels,
|
|
2943
|
+
pollInterval: user.pollInterval ?? DEFAULT_POLL_INTERVAL_MS,
|
|
2944
|
+
buckets: user.buckets
|
|
2945
|
+
};
|
|
2946
|
+
};
|
|
2947
|
+
var JetstreamMetricsModule = class {
|
|
2948
|
+
static forFeature() {
|
|
2949
|
+
const promClientProvider = {
|
|
2950
|
+
provide: JETSTREAM_METRICS_PROM_CLIENT,
|
|
2951
|
+
inject: [JETSTREAM_OPTIONS],
|
|
2952
|
+
useFactory: async (opts) => {
|
|
2953
|
+
if (!opts.metrics) return null;
|
|
2954
|
+
const mod = await resolvePromClient();
|
|
2955
|
+
return { Counter: mod.Counter, Histogram: mod.Histogram, Gauge: mod.Gauge };
|
|
2956
|
+
}
|
|
2957
|
+
};
|
|
2958
|
+
const configProvider = {
|
|
2959
|
+
provide: JETSTREAM_METRICS_CONFIG,
|
|
2960
|
+
inject: [JETSTREAM_OPTIONS],
|
|
2961
|
+
useFactory: async (opts) => {
|
|
2962
|
+
if (!opts.metrics) return null;
|
|
2963
|
+
const mod = await resolvePromClient();
|
|
2964
|
+
return normalizeMetricsConfig(opts.metrics, mod);
|
|
2965
|
+
}
|
|
2966
|
+
};
|
|
2967
|
+
const registryProvider = {
|
|
2968
|
+
provide: JETSTREAM_METRICS_REGISTRY,
|
|
2969
|
+
inject: [JETSTREAM_METRICS_CONFIG],
|
|
2970
|
+
useFactory: (cfg) => cfg?.register ?? null
|
|
2971
|
+
};
|
|
2972
|
+
const serviceProvider = {
|
|
2973
|
+
provide: JetstreamMetricsService,
|
|
2974
|
+
inject: [
|
|
2975
|
+
JETSTREAM_EVENT_BUS,
|
|
2976
|
+
JETSTREAM_METRICS_CONFIG,
|
|
2977
|
+
JETSTREAM_METRICS_PROM_CLIENT,
|
|
2978
|
+
JETSTREAM_OPTIONS,
|
|
2979
|
+
{ token: PatternRegistry, optional: true },
|
|
2980
|
+
{ token: JETSTREAM_CONNECTION, optional: true }
|
|
2981
|
+
],
|
|
2982
|
+
useFactory: (eventBus, cfg, runtime, opts, patternRegistry, connection) => new JetstreamMetricsService(eventBus, cfg, runtime, opts, patternRegistry, connection)
|
|
2983
|
+
};
|
|
2984
|
+
return {
|
|
2985
|
+
module: JetstreamMetricsModule,
|
|
2986
|
+
providers: [promClientProvider, configProvider, registryProvider, serviceProvider],
|
|
2987
|
+
exports: [
|
|
2988
|
+
JetstreamMetricsService,
|
|
2989
|
+
JETSTREAM_METRICS_CONFIG,
|
|
2990
|
+
JETSTREAM_METRICS_REGISTRY,
|
|
2991
|
+
JETSTREAM_METRICS_PROM_CLIENT
|
|
2992
|
+
]
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
};
|
|
2996
|
+
JetstreamMetricsModule = __decorateClass([
|
|
2997
|
+
(0, import_common11.Module)({})
|
|
2998
|
+
], JetstreamMetricsModule);
|
|
2230
2999
|
|
|
2231
3000
|
// src/server/strategy.ts
|
|
2232
3001
|
var import_microservices2 = require("@nestjs/microservices");
|
|
@@ -2302,6 +3071,26 @@ var JetstreamStrategy = class extends import_microservices2.Server {
|
|
|
2302
3071
|
this.messageProvider.destroy();
|
|
2303
3072
|
this.started = false;
|
|
2304
3073
|
}
|
|
3074
|
+
/**
|
|
3075
|
+
* Override NestJS `Server.addHandler` to fail-fast on duplicate pattern registration.
|
|
3076
|
+
*
|
|
3077
|
+
* The base class silently overwrites duplicate RPC handlers (last wins) and appends
|
|
3078
|
+
* duplicate event handlers to a linked list. Both behaviors are hazardous in a
|
|
3079
|
+
* JetStream context: silent overwrite drops a handler the user wrote, and double
|
|
3080
|
+
* event dispatch double-acks/double-processes the same JetStream message.
|
|
3081
|
+
*
|
|
3082
|
+
* We treat any pattern collision as a fatal misconfiguration so it surfaces at
|
|
3083
|
+
* bootstrap instead of in production traffic.
|
|
3084
|
+
*/
|
|
3085
|
+
addHandler(pattern, callback, isEventHandler = false, extras = {}) {
|
|
3086
|
+
const normalizedPattern = this.normalizePattern(pattern);
|
|
3087
|
+
if (this.messageHandlers.has(normalizedPattern)) {
|
|
3088
|
+
throw new Error(
|
|
3089
|
+
`Duplicate handler registered for pattern "${normalizedPattern}". Each @EventPattern() / @MessagePattern() value must be unique within a microservice \u2014 find and remove the second declaration.`
|
|
3090
|
+
);
|
|
3091
|
+
}
|
|
3092
|
+
super.addHandler(pattern, callback, isEventHandler, extras);
|
|
3093
|
+
}
|
|
2305
3094
|
/**
|
|
2306
3095
|
* Register event listener (required by Server base class).
|
|
2307
3096
|
*
|
|
@@ -2374,7 +3163,7 @@ var JetstreamStrategy = class extends import_microservices2.Server {
|
|
|
2374
3163
|
};
|
|
2375
3164
|
|
|
2376
3165
|
// src/server/core-rpc.server.ts
|
|
2377
|
-
var
|
|
3166
|
+
var import_common12 = require("@nestjs/common");
|
|
2378
3167
|
var import_transport_node3 = require("@nats-io/transport-node");
|
|
2379
3168
|
|
|
2380
3169
|
// src/context/rpc.context.ts
|
|
@@ -2643,7 +3432,7 @@ var CoreRpcServer = class {
|
|
|
2643
3432
|
this.serviceName = derived.serviceName;
|
|
2644
3433
|
this.serverEndpoint = derived.serverEndpoint;
|
|
2645
3434
|
}
|
|
2646
|
-
logger = new
|
|
3435
|
+
logger = new import_common12.Logger("Jetstream:CoreRpc");
|
|
2647
3436
|
subscription = null;
|
|
2648
3437
|
otel;
|
|
2649
3438
|
serviceName;
|
|
@@ -2696,6 +3485,7 @@ var CoreRpcServer = class {
|
|
|
2696
3485
|
return;
|
|
2697
3486
|
}
|
|
2698
3487
|
const ctx = new RpcContext([msg]);
|
|
3488
|
+
const startedAt = performance.now();
|
|
2699
3489
|
try {
|
|
2700
3490
|
const raw = await withConsumeSpan(
|
|
2701
3491
|
{
|
|
@@ -2714,6 +3504,7 @@ var CoreRpcServer = class {
|
|
|
2714
3504
|
}
|
|
2715
3505
|
);
|
|
2716
3506
|
msg.respond(this.codec.encode(raw));
|
|
3507
|
+
this.reportHandlerCompleted(msg, startedAt, "success");
|
|
2717
3508
|
} catch (err) {
|
|
2718
3509
|
this.eventBus.emit(
|
|
2719
3510
|
"error" /* Error */,
|
|
@@ -2721,7 +3512,23 @@ var CoreRpcServer = class {
|
|
|
2721
3512
|
`core-rpc-handler:${msg.subject}`
|
|
2722
3513
|
);
|
|
2723
3514
|
this.respondWithError(msg, err);
|
|
2724
|
-
|
|
3515
|
+
this.reportHandlerCompleted(msg, startedAt, "error");
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
// See EventRouter.reportHandlerCompleted for the rationale on declared
|
|
3519
|
+
// pattern + per-emit hasHook check.
|
|
3520
|
+
reportHandlerCompleted(msg, startedAt, status) {
|
|
3521
|
+
if (!this.eventBus.hasHook("handlerCompleted" /* HandlerCompleted */)) return;
|
|
3522
|
+
const declared = this.patternRegistry.resolveDeclared(msg.subject);
|
|
3523
|
+
const pattern = declared?.pattern ?? msg.subject;
|
|
3524
|
+
const kind = declared?.kind ?? "cmd" /* Command */;
|
|
3525
|
+
this.eventBus.emit(
|
|
3526
|
+
"handlerCompleted" /* HandlerCompleted */,
|
|
3527
|
+
pattern,
|
|
3528
|
+
kind,
|
|
3529
|
+
performance.now() - startedAt,
|
|
3530
|
+
status
|
|
3531
|
+
);
|
|
2725
3532
|
}
|
|
2726
3533
|
/** Send an error response back to the caller with x-error header. */
|
|
2727
3534
|
respondWithError(msg, error) {
|
|
@@ -2736,8 +3543,8 @@ var CoreRpcServer = class {
|
|
|
2736
3543
|
};
|
|
2737
3544
|
|
|
2738
3545
|
// src/server/infrastructure/stream.provider.ts
|
|
2739
|
-
var
|
|
2740
|
-
var
|
|
3546
|
+
var import_common14 = require("@nestjs/common");
|
|
3547
|
+
var import_jetstream17 = require("@nats-io/jetstream");
|
|
2741
3548
|
|
|
2742
3549
|
// src/server/infrastructure/nats-error-codes.ts
|
|
2743
3550
|
var NatsErrorCode = /* @__PURE__ */ ((NatsErrorCode2) => {
|
|
@@ -2803,8 +3610,8 @@ var isEqual = (a, b) => {
|
|
|
2803
3610
|
};
|
|
2804
3611
|
|
|
2805
3612
|
// src/server/infrastructure/stream-migration.ts
|
|
2806
|
-
var
|
|
2807
|
-
var
|
|
3613
|
+
var import_common13 = require("@nestjs/common");
|
|
3614
|
+
var import_jetstream16 = require("@nats-io/jetstream");
|
|
2808
3615
|
var MIGRATION_BACKUP_SUFFIX = "__migration_backup";
|
|
2809
3616
|
var DEFAULT_SOURCING_TIMEOUT_MS = 3e4;
|
|
2810
3617
|
var SOURCING_POLL_INTERVAL_MS = 100;
|
|
@@ -2812,7 +3619,7 @@ var StreamMigration = class {
|
|
|
2812
3619
|
constructor(sourcingTimeoutMs = DEFAULT_SOURCING_TIMEOUT_MS) {
|
|
2813
3620
|
this.sourcingTimeoutMs = sourcingTimeoutMs;
|
|
2814
3621
|
}
|
|
2815
|
-
logger = new
|
|
3622
|
+
logger = new import_common13.Logger("Jetstream:Stream");
|
|
2816
3623
|
async migrate(jsm, streamName2, newConfig) {
|
|
2817
3624
|
const backupName = `${streamName2}${MIGRATION_BACKUP_SUFFIX}`;
|
|
2818
3625
|
const startTime = Date.now();
|
|
@@ -2881,7 +3688,7 @@ var StreamMigration = class {
|
|
|
2881
3688
|
this.logger.warn(`Found orphaned migration backup stream: ${backupName}, cleaning up`);
|
|
2882
3689
|
await jsm.streams.delete(backupName);
|
|
2883
3690
|
} catch (err) {
|
|
2884
|
-
if (err instanceof
|
|
3691
|
+
if (err instanceof import_jetstream16.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
|
|
2885
3692
|
return;
|
|
2886
3693
|
}
|
|
2887
3694
|
throw err;
|
|
@@ -2899,7 +3706,7 @@ var StreamProvider = class {
|
|
|
2899
3706
|
this.otelServiceName = derived.serviceName;
|
|
2900
3707
|
this.otelEndpoint = derived.serverEndpoint;
|
|
2901
3708
|
}
|
|
2902
|
-
logger = new
|
|
3709
|
+
logger = new import_common14.Logger("Jetstream:Stream");
|
|
2903
3710
|
migration = new StreamMigration();
|
|
2904
3711
|
otel;
|
|
2905
3712
|
otelServiceName;
|
|
@@ -2964,7 +3771,7 @@ var StreamProvider = class {
|
|
|
2964
3771
|
const currentInfo = await jsm.streams.info(config.name);
|
|
2965
3772
|
return await this.handleExistingStream(jsm, currentInfo, config);
|
|
2966
3773
|
} catch (err) {
|
|
2967
|
-
if (err instanceof
|
|
3774
|
+
if (err instanceof import_jetstream17.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
|
|
2968
3775
|
this.logger.log(`Creating stream: ${config.name}`);
|
|
2969
3776
|
return await jsm.streams.add(config);
|
|
2970
3777
|
}
|
|
@@ -2991,7 +3798,7 @@ var StreamProvider = class {
|
|
|
2991
3798
|
const currentInfo = await jsm.streams.info(config.name);
|
|
2992
3799
|
return await this.handleExistingStream(jsm, currentInfo, config);
|
|
2993
3800
|
} catch (err) {
|
|
2994
|
-
if (err instanceof
|
|
3801
|
+
if (err instanceof import_jetstream17.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
|
|
2995
3802
|
this.logger.log(`Creating DLQ stream: ${config.name}`);
|
|
2996
3803
|
return await jsm.streams.add(config);
|
|
2997
3804
|
}
|
|
@@ -3154,8 +3961,8 @@ var StreamProvider = class {
|
|
|
3154
3961
|
};
|
|
3155
3962
|
|
|
3156
3963
|
// src/server/infrastructure/consumer.provider.ts
|
|
3157
|
-
var
|
|
3158
|
-
var
|
|
3964
|
+
var import_common15 = require("@nestjs/common");
|
|
3965
|
+
var import_jetstream19 = require("@nats-io/jetstream");
|
|
3159
3966
|
var ConsumerProvider = class {
|
|
3160
3967
|
constructor(options, connection, streamProvider, patternRegistry) {
|
|
3161
3968
|
this.options = options;
|
|
@@ -3167,7 +3974,7 @@ var ConsumerProvider = class {
|
|
|
3167
3974
|
this.otelServiceName = derived.serviceName;
|
|
3168
3975
|
this.otelEndpoint = derived.serverEndpoint;
|
|
3169
3976
|
}
|
|
3170
|
-
logger = new
|
|
3977
|
+
logger = new import_common15.Logger("Jetstream:Consumer");
|
|
3171
3978
|
otel;
|
|
3172
3979
|
otelServiceName;
|
|
3173
3980
|
otelEndpoint;
|
|
@@ -3216,7 +4023,7 @@ var ConsumerProvider = class {
|
|
|
3216
4023
|
this.logger.debug(`Consumer exists, updating: ${name}`);
|
|
3217
4024
|
return await jsm.consumers.update(stream, name, config);
|
|
3218
4025
|
} catch (err) {
|
|
3219
|
-
if (!(err instanceof
|
|
4026
|
+
if (!(err instanceof import_jetstream19.JetStreamApiError) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
|
|
3220
4027
|
throw err;
|
|
3221
4028
|
}
|
|
3222
4029
|
return await this.createConsumer(jsm, stream, name, config);
|
|
@@ -3256,7 +4063,7 @@ var ConsumerProvider = class {
|
|
|
3256
4063
|
try {
|
|
3257
4064
|
return await jsm.consumers.info(stream, name);
|
|
3258
4065
|
} catch (err) {
|
|
3259
|
-
if (!(err instanceof
|
|
4066
|
+
if (!(err instanceof import_jetstream19.JetStreamApiError) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
|
|
3260
4067
|
throw err;
|
|
3261
4068
|
}
|
|
3262
4069
|
return await this.createConsumer(jsm, stream, name, config);
|
|
@@ -3277,7 +4084,7 @@ var ConsumerProvider = class {
|
|
|
3277
4084
|
`Stream ${stream} is being migrated (backup ${backupName} exists). Waiting for migration to complete before recovering consumer.`
|
|
3278
4085
|
);
|
|
3279
4086
|
} catch (err) {
|
|
3280
|
-
if (err instanceof
|
|
4087
|
+
if (err instanceof import_jetstream19.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
|
|
3281
4088
|
return;
|
|
3282
4089
|
}
|
|
3283
4090
|
throw err;
|
|
@@ -3291,7 +4098,7 @@ var ConsumerProvider = class {
|
|
|
3291
4098
|
try {
|
|
3292
4099
|
return await jsm.consumers.add(stream, config);
|
|
3293
4100
|
} catch (addErr) {
|
|
3294
|
-
if (addErr instanceof
|
|
4101
|
+
if (addErr instanceof import_jetstream19.JetStreamApiError && addErr.apiError().err_code === 10148 /* ConsumerAlreadyExists */) {
|
|
3295
4102
|
this.logger.debug(`Consumer ${name} created by another pod, using existing`);
|
|
3296
4103
|
return await jsm.consumers.info(stream, name);
|
|
3297
4104
|
}
|
|
@@ -3378,8 +4185,8 @@ var ConsumerProvider = class {
|
|
|
3378
4185
|
};
|
|
3379
4186
|
|
|
3380
4187
|
// src/server/infrastructure/message.provider.ts
|
|
3381
|
-
var
|
|
3382
|
-
var
|
|
4188
|
+
var import_common16 = require("@nestjs/common");
|
|
4189
|
+
var import_jetstream21 = require("@nats-io/jetstream");
|
|
3383
4190
|
var import_rxjs3 = require("rxjs");
|
|
3384
4191
|
var MessageProvider = class {
|
|
3385
4192
|
constructor(connection, eventBus, consumeOptionsMap = /* @__PURE__ */ new Map(), consumerRecoveryFn) {
|
|
@@ -3388,7 +4195,7 @@ var MessageProvider = class {
|
|
|
3388
4195
|
this.consumeOptionsMap = consumeOptionsMap;
|
|
3389
4196
|
this.consumerRecoveryFn = consumerRecoveryFn;
|
|
3390
4197
|
}
|
|
3391
|
-
logger = new
|
|
4198
|
+
logger = new import_common16.Logger("Jetstream:Message");
|
|
3392
4199
|
activeIterators = /* @__PURE__ */ new Set();
|
|
3393
4200
|
orderedReadyResolve = null;
|
|
3394
4201
|
orderedReadyReject = null;
|
|
@@ -3440,7 +4247,7 @@ var MessageProvider = class {
|
|
|
3440
4247
|
*/
|
|
3441
4248
|
async startOrdered(streamName2, filterSubjects, orderedConfig) {
|
|
3442
4249
|
const consumerOpts = { filter_subjects: filterSubjects };
|
|
3443
|
-
if (orderedConfig?.deliverPolicy !== void 0 && orderedConfig.deliverPolicy !==
|
|
4250
|
+
if (orderedConfig?.deliverPolicy !== void 0 && orderedConfig.deliverPolicy !== import_jetstream21.DeliverPolicy.All) {
|
|
3444
4251
|
consumerOpts.deliver_policy = orderedConfig.deliverPolicy;
|
|
3445
4252
|
}
|
|
3446
4253
|
if (orderedConfig?.optStartSeq !== void 0) {
|
|
@@ -3486,10 +4293,13 @@ var MessageProvider = class {
|
|
|
3486
4293
|
/** Create a self-healing consumer flow for a specific kind. */
|
|
3487
4294
|
createFlow(kind, info) {
|
|
3488
4295
|
const target$ = this.getTargetSubject(kind);
|
|
3489
|
-
return this.createSelfHealingFlow(
|
|
4296
|
+
return this.createSelfHealingFlow(
|
|
4297
|
+
(onConnected) => this.consumeOnce(kind, info, target$, onConnected),
|
|
4298
|
+
info.name
|
|
4299
|
+
);
|
|
3490
4300
|
}
|
|
3491
4301
|
/** Single iteration: get consumer -> pull messages -> emit to subject. */
|
|
3492
|
-
async consumeOnce(kind, info, target
|
|
4302
|
+
async consumeOnce(kind, info, target$, onConnected) {
|
|
3493
4303
|
const js = this.connection.getJetStreamClient();
|
|
3494
4304
|
let consumer;
|
|
3495
4305
|
let consumerName2 = info.name;
|
|
@@ -3517,6 +4327,7 @@ var MessageProvider = class {
|
|
|
3517
4327
|
});
|
|
3518
4328
|
this.activeIterators.add(messages);
|
|
3519
4329
|
this.monitorConsumerHealth(messages, consumerName2);
|
|
4330
|
+
onConnected();
|
|
3520
4331
|
try {
|
|
3521
4332
|
await messages.closed();
|
|
3522
4333
|
} finally {
|
|
@@ -3571,7 +4382,7 @@ var MessageProvider = class {
|
|
|
3571
4382
|
/** Create a self-healing ordered consumer flow. */
|
|
3572
4383
|
createOrderedFlow(streamName2, consumerOpts) {
|
|
3573
4384
|
return this.createSelfHealingFlow(
|
|
3574
|
-
() => this.consumeOrderedOnce(streamName2, consumerOpts),
|
|
4385
|
+
(onConnected) => this.consumeOrderedOnce(streamName2, consumerOpts, onConnected),
|
|
3575
4386
|
"ordered" /* Ordered */,
|
|
3576
4387
|
(err) => {
|
|
3577
4388
|
if (this.orderedReadyReject) {
|
|
@@ -3585,10 +4396,15 @@ var MessageProvider = class {
|
|
|
3585
4396
|
/** Shared self-healing flow: defer -> retry with exponential backoff on error/completion. */
|
|
3586
4397
|
createSelfHealingFlow(source, label, onFirstError) {
|
|
3587
4398
|
let consecutiveFailures = 0;
|
|
3588
|
-
|
|
3589
|
-
(0
|
|
3590
|
-
|
|
3591
|
-
|
|
4399
|
+
const onConnected = () => {
|
|
4400
|
+
if (consecutiveFailures > 0) {
|
|
4401
|
+
const attempts = consecutiveFailures;
|
|
4402
|
+
this.logger.log(`Consumer ${label} recovered after ${attempts} failed attempt(s)`);
|
|
4403
|
+
this.eventBus.emit("consumerRecovered" /* ConsumerRecovered */, label, attempts);
|
|
4404
|
+
}
|
|
4405
|
+
consecutiveFailures = 0;
|
|
4406
|
+
};
|
|
4407
|
+
return (0, import_rxjs3.defer)(() => source(onConnected)).pipe(
|
|
3592
4408
|
(0, import_rxjs3.catchError)((err) => {
|
|
3593
4409
|
consecutiveFailures++;
|
|
3594
4410
|
this.logger.error(`Consumer ${label} error, will restart:`, err);
|
|
@@ -3611,7 +4427,7 @@ var MessageProvider = class {
|
|
|
3611
4427
|
);
|
|
3612
4428
|
}
|
|
3613
4429
|
/** Single iteration: create ordered consumer -> push messages into the subject. */
|
|
3614
|
-
async consumeOrderedOnce(streamName2, consumerOpts) {
|
|
4430
|
+
async consumeOrderedOnce(streamName2, consumerOpts, onConnected) {
|
|
3615
4431
|
const js = this.connection.getJetStreamClient();
|
|
3616
4432
|
const consumer = await js.consumers.get(streamName2, consumerOpts);
|
|
3617
4433
|
const orderedMessages$ = this.orderedMessages$;
|
|
@@ -3626,6 +4442,7 @@ var MessageProvider = class {
|
|
|
3626
4442
|
this.orderedReadyReject = null;
|
|
3627
4443
|
}
|
|
3628
4444
|
this.activeIterators.add(messages);
|
|
4445
|
+
onConnected();
|
|
3629
4446
|
try {
|
|
3630
4447
|
await messages.closed();
|
|
3631
4448
|
} finally {
|
|
@@ -3635,7 +4452,7 @@ var MessageProvider = class {
|
|
|
3635
4452
|
};
|
|
3636
4453
|
|
|
3637
4454
|
// src/server/infrastructure/metadata.provider.ts
|
|
3638
|
-
var
|
|
4455
|
+
var import_common17 = require("@nestjs/common");
|
|
3639
4456
|
var import_kv = require("@nats-io/kv");
|
|
3640
4457
|
var MetadataProvider = class {
|
|
3641
4458
|
constructor(options, connection) {
|
|
@@ -3644,7 +4461,7 @@ var MetadataProvider = class {
|
|
|
3644
4461
|
this.replicas = options.metadata?.replicas ?? DEFAULT_METADATA_REPLICAS;
|
|
3645
4462
|
this.ttl = Math.max(options.metadata?.ttl ?? DEFAULT_METADATA_TTL, MIN_METADATA_TTL);
|
|
3646
4463
|
}
|
|
3647
|
-
logger = new
|
|
4464
|
+
logger = new import_common17.Logger("Jetstream:Metadata");
|
|
3648
4465
|
bucketName;
|
|
3649
4466
|
replicas;
|
|
3650
4467
|
ttl;
|
|
@@ -3736,176 +4553,8 @@ var MetadataProvider = class {
|
|
|
3736
4553
|
}
|
|
3737
4554
|
};
|
|
3738
4555
|
|
|
3739
|
-
// src/server/routing/pattern-registry.ts
|
|
3740
|
-
var import_common14 = require("@nestjs/common");
|
|
3741
|
-
var HANDLER_LABELS = {
|
|
3742
|
-
["broadcast" /* Broadcast */]: "broadcast" /* Broadcast */,
|
|
3743
|
-
["ordered" /* Ordered */]: "ordered" /* Ordered */,
|
|
3744
|
-
["ev" /* Event */]: "event" /* Event */,
|
|
3745
|
-
["cmd" /* Command */]: "rpc" /* Rpc */
|
|
3746
|
-
};
|
|
3747
|
-
var PatternRegistry = class {
|
|
3748
|
-
constructor(options) {
|
|
3749
|
-
this.options = options;
|
|
3750
|
-
}
|
|
3751
|
-
logger = new import_common14.Logger("Jetstream:PatternRegistry");
|
|
3752
|
-
registry = /* @__PURE__ */ new Map();
|
|
3753
|
-
// Cached after registerHandlers() — the registry is immutable from that point
|
|
3754
|
-
cachedPatterns = null;
|
|
3755
|
-
_hasEvents = false;
|
|
3756
|
-
_hasCommands = false;
|
|
3757
|
-
_hasBroadcasts = false;
|
|
3758
|
-
_hasOrdered = false;
|
|
3759
|
-
_hasMetadata = false;
|
|
3760
|
-
/**
|
|
3761
|
-
* Register all handlers from the NestJS strategy.
|
|
3762
|
-
*
|
|
3763
|
-
* @param handlers Map of pattern -> MessageHandler from `Server.getHandlers()`.
|
|
3764
|
-
*/
|
|
3765
|
-
registerHandlers(handlers) {
|
|
3766
|
-
const serviceName = this.options.name;
|
|
3767
|
-
for (const [pattern, handler] of handlers) {
|
|
3768
|
-
const extras = handler.extras;
|
|
3769
|
-
const isEvent = handler.isEventHandler ?? false;
|
|
3770
|
-
const isBroadcast = !!extras?.broadcast;
|
|
3771
|
-
const isOrdered = !!extras?.ordered;
|
|
3772
|
-
const meta = extras?.meta;
|
|
3773
|
-
if (isBroadcast && isOrdered) {
|
|
3774
|
-
throw new Error(
|
|
3775
|
-
`Handler "${pattern}" cannot be both broadcast and ordered. Use one or the other.`
|
|
3776
|
-
);
|
|
3777
|
-
}
|
|
3778
|
-
let kind;
|
|
3779
|
-
if (isBroadcast) kind = "broadcast" /* Broadcast */;
|
|
3780
|
-
else if (isOrdered) kind = "ordered" /* Ordered */;
|
|
3781
|
-
else if (isEvent) kind = "ev" /* Event */;
|
|
3782
|
-
else kind = "cmd" /* Command */;
|
|
3783
|
-
const fullSubject = kind === "broadcast" /* Broadcast */ ? buildBroadcastSubject(pattern) : buildSubject(serviceName, kind, pattern);
|
|
3784
|
-
this.registry.set(fullSubject, {
|
|
3785
|
-
handler,
|
|
3786
|
-
pattern,
|
|
3787
|
-
isEvent: isEvent && !isOrdered,
|
|
3788
|
-
isBroadcast,
|
|
3789
|
-
isOrdered,
|
|
3790
|
-
meta
|
|
3791
|
-
});
|
|
3792
|
-
this.logger.debug(`Registered ${HANDLER_LABELS[kind]}: ${pattern} -> ${fullSubject}`);
|
|
3793
|
-
}
|
|
3794
|
-
this.cachedPatterns = this.buildPatternsByKind();
|
|
3795
|
-
this._hasEvents = this.cachedPatterns.events.length > 0;
|
|
3796
|
-
this._hasCommands = this.cachedPatterns.commands.length > 0;
|
|
3797
|
-
this._hasBroadcasts = this.cachedPatterns.broadcasts.length > 0;
|
|
3798
|
-
this._hasOrdered = this.cachedPatterns.ordered.length > 0;
|
|
3799
|
-
this._hasMetadata = [...this.registry.values()].some((entry) => entry.meta !== void 0);
|
|
3800
|
-
this.logSummary();
|
|
3801
|
-
}
|
|
3802
|
-
/** Find handler for a full NATS subject. */
|
|
3803
|
-
getHandler(subject) {
|
|
3804
|
-
return this.registry.get(subject)?.handler ?? null;
|
|
3805
|
-
}
|
|
3806
|
-
/** Get all registered broadcast patterns (for consumer filter_subject setup). */
|
|
3807
|
-
getBroadcastPatterns() {
|
|
3808
|
-
return this.getPatternsByKind().broadcasts.map((p) => buildBroadcastSubject(p));
|
|
3809
|
-
}
|
|
3810
|
-
hasBroadcastHandlers() {
|
|
3811
|
-
return this._hasBroadcasts;
|
|
3812
|
-
}
|
|
3813
|
-
hasRpcHandlers() {
|
|
3814
|
-
return this._hasCommands;
|
|
3815
|
-
}
|
|
3816
|
-
hasEventHandlers() {
|
|
3817
|
-
return this._hasEvents;
|
|
3818
|
-
}
|
|
3819
|
-
hasOrderedHandlers() {
|
|
3820
|
-
return this._hasOrdered;
|
|
3821
|
-
}
|
|
3822
|
-
/** Get fully-qualified NATS subjects for ordered handlers. */
|
|
3823
|
-
getOrderedSubjects() {
|
|
3824
|
-
return this.getPatternsByKind().ordered.map(
|
|
3825
|
-
(p) => buildSubject(this.options.name, "ordered" /* Ordered */, p)
|
|
3826
|
-
);
|
|
3827
|
-
}
|
|
3828
|
-
/** Check if any registered handler has metadata. */
|
|
3829
|
-
hasMetadata() {
|
|
3830
|
-
return this._hasMetadata;
|
|
3831
|
-
}
|
|
3832
|
-
/**
|
|
3833
|
-
* Get handler metadata entries for KV publishing.
|
|
3834
|
-
*
|
|
3835
|
-
* Returns a map of KV key -> metadata object for all handlers that have `meta`.
|
|
3836
|
-
* Key format: `{serviceName}.{kind}.{pattern}`.
|
|
3837
|
-
*/
|
|
3838
|
-
getMetadataEntries() {
|
|
3839
|
-
const entries = /* @__PURE__ */ new Map();
|
|
3840
|
-
for (const entry of this.registry.values()) {
|
|
3841
|
-
if (!entry.meta) continue;
|
|
3842
|
-
const kind = this.resolveStreamKind(entry);
|
|
3843
|
-
const key = metadataKey(this.options.name, kind, entry.pattern);
|
|
3844
|
-
entries.set(key, entry.meta);
|
|
3845
|
-
}
|
|
3846
|
-
return entries;
|
|
3847
|
-
}
|
|
3848
|
-
/** Get patterns grouped by kind (cached after registration). */
|
|
3849
|
-
getPatternsByKind() {
|
|
3850
|
-
const patterns = this.cachedPatterns ?? this.buildPatternsByKind();
|
|
3851
|
-
return {
|
|
3852
|
-
events: [...patterns.events],
|
|
3853
|
-
commands: [...patterns.commands],
|
|
3854
|
-
broadcasts: [...patterns.broadcasts],
|
|
3855
|
-
ordered: [...patterns.ordered]
|
|
3856
|
-
};
|
|
3857
|
-
}
|
|
3858
|
-
/** Normalize a full NATS subject back to the user-facing pattern. */
|
|
3859
|
-
normalizeSubject(subject) {
|
|
3860
|
-
const name = internalName(this.options.name);
|
|
3861
|
-
const prefixes = [
|
|
3862
|
-
`${name}.${"cmd" /* Command */}.`,
|
|
3863
|
-
`${name}.${"ev" /* Event */}.`,
|
|
3864
|
-
`${name}.${"ordered" /* Ordered */}.`,
|
|
3865
|
-
`${"broadcast" /* Broadcast */}.`
|
|
3866
|
-
];
|
|
3867
|
-
for (const prefix of prefixes) {
|
|
3868
|
-
if (subject.startsWith(prefix)) {
|
|
3869
|
-
return subject.slice(prefix.length);
|
|
3870
|
-
}
|
|
3871
|
-
}
|
|
3872
|
-
return subject;
|
|
3873
|
-
}
|
|
3874
|
-
buildPatternsByKind() {
|
|
3875
|
-
const events = [];
|
|
3876
|
-
const commands = [];
|
|
3877
|
-
const broadcasts = [];
|
|
3878
|
-
const ordered = [];
|
|
3879
|
-
for (const entry of this.registry.values()) {
|
|
3880
|
-
if (entry.isBroadcast) broadcasts.push(entry.pattern);
|
|
3881
|
-
else if (entry.isOrdered) ordered.push(entry.pattern);
|
|
3882
|
-
else if (entry.isEvent) events.push(entry.pattern);
|
|
3883
|
-
else commands.push(entry.pattern);
|
|
3884
|
-
}
|
|
3885
|
-
return { events, commands, broadcasts, ordered };
|
|
3886
|
-
}
|
|
3887
|
-
resolveStreamKind(entry) {
|
|
3888
|
-
if (entry.isBroadcast) return "broadcast" /* Broadcast */;
|
|
3889
|
-
if (entry.isOrdered) return "ordered" /* Ordered */;
|
|
3890
|
-
if (entry.isEvent) return "ev" /* Event */;
|
|
3891
|
-
return "cmd" /* Command */;
|
|
3892
|
-
}
|
|
3893
|
-
logSummary() {
|
|
3894
|
-
const { events, commands, broadcasts, ordered } = this.getPatternsByKind();
|
|
3895
|
-
const parts = [
|
|
3896
|
-
`${commands.length} RPC`,
|
|
3897
|
-
`${events.length} events`,
|
|
3898
|
-
`${broadcasts.length} broadcasts`
|
|
3899
|
-
];
|
|
3900
|
-
if (ordered.length > 0) {
|
|
3901
|
-
parts.push(`${ordered.length} ordered`);
|
|
3902
|
-
}
|
|
3903
|
-
this.logger.log(`Registered handlers: ${parts.join(", ")}`);
|
|
3904
|
-
}
|
|
3905
|
-
};
|
|
3906
|
-
|
|
3907
4556
|
// src/server/routing/event.router.ts
|
|
3908
|
-
var
|
|
4557
|
+
var import_common18 = require("@nestjs/common");
|
|
3909
4558
|
var import_transport_node4 = require("@nats-io/transport-node");
|
|
3910
4559
|
var eventConsumeKindFor = (kind) => {
|
|
3911
4560
|
if (kind === "broadcast" /* Broadcast */) return "broadcast" /* Broadcast */;
|
|
@@ -3934,7 +4583,7 @@ var EventRouter = class {
|
|
|
3934
4583
|
this.serverEndpoint = null;
|
|
3935
4584
|
}
|
|
3936
4585
|
}
|
|
3937
|
-
logger = new
|
|
4586
|
+
logger = new import_common18.Logger("Jetstream:EventRouter");
|
|
3938
4587
|
subscriptions = [];
|
|
3939
4588
|
otel;
|
|
3940
4589
|
serviceName;
|
|
@@ -3978,7 +4627,14 @@ var EventRouter = class {
|
|
|
3978
4627
|
const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
|
|
3979
4628
|
const concurrency = this.getConcurrency(kind);
|
|
3980
4629
|
const hasDlqCheck = deadLetterConfig !== void 0;
|
|
3981
|
-
const
|
|
4630
|
+
const reportHandlerCompleted = (msg, startedAt, status) => {
|
|
4631
|
+
if (!eventBus.hasHook("handlerCompleted" /* HandlerCompleted */)) return;
|
|
4632
|
+
const declared = patternRegistry.resolveDeclared(msg.subject);
|
|
4633
|
+
const pattern = declared?.pattern ?? msg.subject;
|
|
4634
|
+
const declaredKind = declared?.kind ?? kind;
|
|
4635
|
+
const durationMs = performance.now() - startedAt;
|
|
4636
|
+
eventBus.emit("handlerCompleted" /* HandlerCompleted */, pattern, declaredKind, durationMs, status);
|
|
4637
|
+
};
|
|
3982
4638
|
const isDeadLetter = (msg) => {
|
|
3983
4639
|
if (!hasDlqCheck) return false;
|
|
3984
4640
|
const maxDeliver = deadLetterConfig.maxDeliverByStream?.get(msg.info.stream);
|
|
@@ -4015,7 +4671,7 @@ var EventRouter = class {
|
|
|
4015
4671
|
logger5.error(`Decode error for ${subject}:`, err);
|
|
4016
4672
|
return null;
|
|
4017
4673
|
}
|
|
4018
|
-
|
|
4674
|
+
eventBus.emitMessageRouted(subject, "event" /* Event */);
|
|
4019
4675
|
return { handler, data };
|
|
4020
4676
|
} catch (err) {
|
|
4021
4677
|
logger5.error(`Unexpected error in ${kind} event router`, err);
|
|
@@ -4027,12 +4683,18 @@ var EventRouter = class {
|
|
|
4027
4683
|
return null;
|
|
4028
4684
|
}
|
|
4029
4685
|
};
|
|
4686
|
+
const statusForContext = (ctx) => {
|
|
4687
|
+
if (ctx.shouldTerminate) return "terminated";
|
|
4688
|
+
if (ctx.shouldRetry) return "retried";
|
|
4689
|
+
return "success";
|
|
4690
|
+
};
|
|
4030
4691
|
const handleSafe = (msg) => {
|
|
4031
4692
|
const resolved = resolveEvent(msg);
|
|
4032
4693
|
if (resolved === null) return void 0;
|
|
4033
4694
|
const { handler, data } = resolved;
|
|
4034
4695
|
const ctx = new RpcContext([msg]);
|
|
4035
4696
|
const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
|
|
4697
|
+
const startedAt = performance.now();
|
|
4036
4698
|
let pending;
|
|
4037
4699
|
try {
|
|
4038
4700
|
pending = withConsumeSpan(
|
|
@@ -4055,18 +4717,21 @@ var EventRouter = class {
|
|
|
4055
4717
|
err instanceof Error ? err : new Error(String(err)),
|
|
4056
4718
|
`${kind}-handler:${msg.subject}`
|
|
4057
4719
|
);
|
|
4720
|
+
reportHandlerCompleted(msg, startedAt, "error");
|
|
4058
4721
|
return settleFailure(msg, data, err).finally(() => {
|
|
4059
4722
|
if (stopAckExtension !== null) stopAckExtension();
|
|
4060
4723
|
});
|
|
4061
4724
|
}
|
|
4062
4725
|
if (!isPromiseLike2(pending)) {
|
|
4063
4726
|
settleSuccess(msg, ctx);
|
|
4727
|
+
reportHandlerCompleted(msg, startedAt, statusForContext(ctx));
|
|
4064
4728
|
if (stopAckExtension !== null) stopAckExtension();
|
|
4065
4729
|
return void 0;
|
|
4066
4730
|
}
|
|
4067
4731
|
return pending.then(
|
|
4068
4732
|
() => {
|
|
4069
4733
|
settleSuccess(msg, ctx);
|
|
4734
|
+
reportHandlerCompleted(msg, startedAt, statusForContext(ctx));
|
|
4070
4735
|
if (stopAckExtension !== null) stopAckExtension();
|
|
4071
4736
|
},
|
|
4072
4737
|
async (err) => {
|
|
@@ -4075,6 +4740,7 @@ var EventRouter = class {
|
|
|
4075
4740
|
err instanceof Error ? err : new Error(String(err)),
|
|
4076
4741
|
`${kind}-handler:${msg.subject}`
|
|
4077
4742
|
);
|
|
4743
|
+
reportHandlerCompleted(msg, startedAt, "error");
|
|
4078
4744
|
try {
|
|
4079
4745
|
await settleFailure(msg, data, err);
|
|
4080
4746
|
} finally {
|
|
@@ -4099,7 +4765,7 @@ var EventRouter = class {
|
|
|
4099
4765
|
logger5.error(`Decode error for ${subject}:`, err);
|
|
4100
4766
|
return void 0;
|
|
4101
4767
|
}
|
|
4102
|
-
|
|
4768
|
+
eventBus.emitMessageRouted(subject, "event" /* Event */);
|
|
4103
4769
|
} catch (err) {
|
|
4104
4770
|
logger5.error(`Ordered handler error (${subject}):`, err);
|
|
4105
4771
|
return void 0;
|
|
@@ -4112,6 +4778,7 @@ var EventRouter = class {
|
|
|
4112
4778
|
);
|
|
4113
4779
|
}
|
|
4114
4780
|
};
|
|
4781
|
+
const startedAt = performance.now();
|
|
4115
4782
|
let pending;
|
|
4116
4783
|
try {
|
|
4117
4784
|
pending = withConsumeSpan(
|
|
@@ -4130,15 +4797,24 @@ var EventRouter = class {
|
|
|
4130
4797
|
);
|
|
4131
4798
|
} catch (err) {
|
|
4132
4799
|
logger5.error(`Ordered handler error (${subject}):`, err);
|
|
4800
|
+
reportHandlerCompleted(msg, startedAt, "error");
|
|
4133
4801
|
return void 0;
|
|
4134
4802
|
}
|
|
4135
4803
|
if (!isPromiseLike2(pending)) {
|
|
4136
4804
|
warnIfSettlementAttempted();
|
|
4805
|
+
reportHandlerCompleted(msg, startedAt, "success");
|
|
4137
4806
|
return void 0;
|
|
4138
4807
|
}
|
|
4139
|
-
return pending.then(
|
|
4140
|
-
|
|
4141
|
-
|
|
4808
|
+
return pending.then(
|
|
4809
|
+
() => {
|
|
4810
|
+
warnIfSettlementAttempted();
|
|
4811
|
+
reportHandlerCompleted(msg, startedAt, "success");
|
|
4812
|
+
},
|
|
4813
|
+
(err) => {
|
|
4814
|
+
logger5.error(`Ordered handler error (${subject}):`, err);
|
|
4815
|
+
reportHandlerCompleted(msg, startedAt, "error");
|
|
4816
|
+
}
|
|
4817
|
+
);
|
|
4142
4818
|
};
|
|
4143
4819
|
const route = isOrdered ? handleOrderedSafe : handleSafe;
|
|
4144
4820
|
const maxActive = isOrdered ? 1 : concurrency ?? Number.POSITIVE_INFINITY;
|
|
@@ -4324,7 +5000,7 @@ var EventRouter = class {
|
|
|
4324
5000
|
};
|
|
4325
5001
|
|
|
4326
5002
|
// src/server/routing/rpc.router.ts
|
|
4327
|
-
var
|
|
5003
|
+
var import_common19 = require("@nestjs/common");
|
|
4328
5004
|
var import_transport_node5 = require("@nats-io/transport-node");
|
|
4329
5005
|
var RpcRouter = class {
|
|
4330
5006
|
constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap, options) {
|
|
@@ -4348,7 +5024,7 @@ var RpcRouter = class {
|
|
|
4348
5024
|
this.serverEndpoint = null;
|
|
4349
5025
|
}
|
|
4350
5026
|
}
|
|
4351
|
-
logger = new
|
|
5027
|
+
logger = new import_common19.Logger("Jetstream:RpcRouter");
|
|
4352
5028
|
timeout;
|
|
4353
5029
|
concurrency;
|
|
4354
5030
|
resolvedAckExtensionInterval;
|
|
@@ -4384,6 +5060,19 @@ var RpcRouter = class {
|
|
|
4384
5060
|
const emitRpcTimeout = (subject, correlationId) => {
|
|
4385
5061
|
eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
|
|
4386
5062
|
};
|
|
5063
|
+
const reportHandlerCompleted = (msg, startedAt, status) => {
|
|
5064
|
+
if (!eventBus.hasHook("handlerCompleted" /* HandlerCompleted */)) return;
|
|
5065
|
+
const declared = patternRegistry.resolveDeclared(msg.subject);
|
|
5066
|
+
const pattern = declared?.pattern ?? msg.subject;
|
|
5067
|
+
const declaredKind = declared?.kind ?? "cmd" /* Command */;
|
|
5068
|
+
eventBus.emit(
|
|
5069
|
+
"handlerCompleted" /* HandlerCompleted */,
|
|
5070
|
+
pattern,
|
|
5071
|
+
declaredKind,
|
|
5072
|
+
performance.now() - startedAt,
|
|
5073
|
+
status
|
|
5074
|
+
);
|
|
5075
|
+
};
|
|
4387
5076
|
const publishReply = (replyTo, correlationId, payload) => {
|
|
4388
5077
|
try {
|
|
4389
5078
|
const hdrs = (0, import_transport_node5.headers)();
|
|
@@ -4447,6 +5136,7 @@ var RpcRouter = class {
|
|
|
4447
5136
|
const subject = msg.subject;
|
|
4448
5137
|
const ctx = new RpcContext([msg]);
|
|
4449
5138
|
const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
|
|
5139
|
+
const startedAt = performance.now();
|
|
4450
5140
|
const reportHandlerError = (err) => {
|
|
4451
5141
|
eventBus.emit(
|
|
4452
5142
|
"error" /* Error */,
|
|
@@ -4477,12 +5167,14 @@ var RpcRouter = class {
|
|
|
4477
5167
|
} catch (err) {
|
|
4478
5168
|
if (stopAckExtension !== null) stopAckExtension();
|
|
4479
5169
|
reportHandlerError(err);
|
|
5170
|
+
reportHandlerCompleted(msg, startedAt, "error");
|
|
4480
5171
|
return void 0;
|
|
4481
5172
|
}
|
|
4482
5173
|
if (!isPromiseLike2(pending)) {
|
|
4483
5174
|
if (stopAckExtension !== null) stopAckExtension();
|
|
4484
5175
|
msg.ack();
|
|
4485
5176
|
publishReply(replyTo, correlationId, pending);
|
|
5177
|
+
reportHandlerCompleted(msg, startedAt, "success");
|
|
4486
5178
|
return void 0;
|
|
4487
5179
|
}
|
|
4488
5180
|
let settled = false;
|
|
@@ -4493,6 +5185,7 @@ var RpcRouter = class {
|
|
|
4493
5185
|
abortController.abort();
|
|
4494
5186
|
emitRpcTimeout(subject, correlationId);
|
|
4495
5187
|
msg.term("Handler timeout");
|
|
5188
|
+
reportHandlerCompleted(msg, startedAt, "terminated");
|
|
4496
5189
|
}, timeout);
|
|
4497
5190
|
return pending.then(
|
|
4498
5191
|
(result) => {
|
|
@@ -4502,6 +5195,7 @@ var RpcRouter = class {
|
|
|
4502
5195
|
if (stopAckExtension !== null) stopAckExtension();
|
|
4503
5196
|
msg.ack();
|
|
4504
5197
|
publishReply(replyTo, correlationId, result);
|
|
5198
|
+
reportHandlerCompleted(msg, startedAt, "success");
|
|
4505
5199
|
},
|
|
4506
5200
|
(err) => {
|
|
4507
5201
|
if (settled) return;
|
|
@@ -4509,6 +5203,7 @@ var RpcRouter = class {
|
|
|
4509
5203
|
clearTimeout(timeoutId);
|
|
4510
5204
|
if (stopAckExtension !== null) stopAckExtension();
|
|
4511
5205
|
reportHandlerError(err);
|
|
5206
|
+
reportHandlerCompleted(msg, startedAt, "error");
|
|
4512
5207
|
}
|
|
4513
5208
|
);
|
|
4514
5209
|
};
|
|
@@ -4568,14 +5263,14 @@ var RpcRouter = class {
|
|
|
4568
5263
|
};
|
|
4569
5264
|
|
|
4570
5265
|
// src/shutdown/shutdown.manager.ts
|
|
4571
|
-
var
|
|
5266
|
+
var import_common20 = require("@nestjs/common");
|
|
4572
5267
|
var ShutdownManager = class {
|
|
4573
5268
|
constructor(connection, eventBus, timeout) {
|
|
4574
5269
|
this.connection = connection;
|
|
4575
5270
|
this.eventBus = eventBus;
|
|
4576
5271
|
this.timeout = timeout;
|
|
4577
5272
|
}
|
|
4578
|
-
logger = new
|
|
5273
|
+
logger = new import_common20.Logger("Jetstream:Shutdown");
|
|
4579
5274
|
shutdownPromise;
|
|
4580
5275
|
/**
|
|
4581
5276
|
* Execute the full shutdown sequence.
|
|
@@ -4629,12 +5324,14 @@ var JetstreamModule = class {
|
|
|
4629
5324
|
return {
|
|
4630
5325
|
module: JetstreamModule,
|
|
4631
5326
|
global: true,
|
|
5327
|
+
imports: [JetstreamMetricsModule.forFeature()],
|
|
4632
5328
|
providers,
|
|
4633
5329
|
exports: [
|
|
4634
5330
|
JETSTREAM_CONNECTION,
|
|
4635
5331
|
JETSTREAM_CODEC,
|
|
4636
5332
|
JETSTREAM_EVENT_BUS,
|
|
4637
5333
|
JETSTREAM_OPTIONS,
|
|
5334
|
+
PatternRegistry,
|
|
4638
5335
|
ShutdownManager,
|
|
4639
5336
|
JetstreamStrategy,
|
|
4640
5337
|
JetstreamHealthIndicator
|
|
@@ -4656,13 +5353,14 @@ var JetstreamModule = class {
|
|
|
4656
5353
|
return {
|
|
4657
5354
|
module: JetstreamModule,
|
|
4658
5355
|
global: true,
|
|
4659
|
-
imports: asyncOptions.imports ?? [],
|
|
5356
|
+
imports: [...asyncOptions.imports ?? [], JetstreamMetricsModule.forFeature()],
|
|
4660
5357
|
providers: [...asyncProviders, ...coreProviders],
|
|
4661
5358
|
exports: [
|
|
4662
5359
|
JETSTREAM_CONNECTION,
|
|
4663
5360
|
JETSTREAM_CODEC,
|
|
4664
5361
|
JETSTREAM_EVENT_BUS,
|
|
4665
5362
|
JETSTREAM_OPTIONS,
|
|
5363
|
+
PatternRegistry,
|
|
4666
5364
|
ShutdownManager,
|
|
4667
5365
|
JetstreamStrategy,
|
|
4668
5366
|
JetstreamHealthIndicator
|
|
@@ -4711,7 +5409,7 @@ var JetstreamModule = class {
|
|
|
4711
5409
|
provide: JETSTREAM_EVENT_BUS,
|
|
4712
5410
|
inject: [JETSTREAM_OPTIONS],
|
|
4713
5411
|
useFactory: (options) => {
|
|
4714
|
-
const logger5 = new
|
|
5412
|
+
const logger5 = new import_common21.Logger("Jetstream:Module");
|
|
4715
5413
|
return new EventBus(logger5, options.hooks);
|
|
4716
5414
|
}
|
|
4717
5415
|
},
|
|
@@ -5005,12 +5703,12 @@ var JetstreamModule = class {
|
|
|
5005
5703
|
}
|
|
5006
5704
|
};
|
|
5007
5705
|
JetstreamModule = __decorateClass([
|
|
5008
|
-
(0,
|
|
5009
|
-
(0,
|
|
5010
|
-
__decorateParam(0, (0,
|
|
5011
|
-
__decorateParam(0, (0,
|
|
5012
|
-
__decorateParam(1, (0,
|
|
5013
|
-
__decorateParam(1, (0,
|
|
5706
|
+
(0, import_common21.Global)(),
|
|
5707
|
+
(0, import_common21.Module)({}),
|
|
5708
|
+
__decorateParam(0, (0, import_common21.Optional)()),
|
|
5709
|
+
__decorateParam(0, (0, import_common21.Inject)(ShutdownManager)),
|
|
5710
|
+
__decorateParam(1, (0, import_common21.Optional)()),
|
|
5711
|
+
__decorateParam(1, (0, import_common21.Inject)(JetstreamStrategy))
|
|
5014
5712
|
], JetstreamModule);
|
|
5015
5713
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5016
5714
|
0 && (module.exports = {
|