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