@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.js
CHANGED
|
@@ -13,10 +13,10 @@ var __decorateParam = (index, decorator) => (target, key) => decorator(target, k
|
|
|
13
13
|
// src/jetstream.module.ts
|
|
14
14
|
import {
|
|
15
15
|
Global,
|
|
16
|
-
Inject,
|
|
17
|
-
Logger as
|
|
18
|
-
Module,
|
|
19
|
-
Optional
|
|
16
|
+
Inject as Inject2,
|
|
17
|
+
Logger as Logger20,
|
|
18
|
+
Module as Module2,
|
|
19
|
+
Optional as Optional2
|
|
20
20
|
} from "@nestjs/common";
|
|
21
21
|
|
|
22
22
|
// src/client/jetstream.client.ts
|
|
@@ -46,6 +46,10 @@ var TransportEvent = /* @__PURE__ */ ((TransportEvent2) => {
|
|
|
46
46
|
TransportEvent2["ShutdownStart"] = "shutdownStart";
|
|
47
47
|
TransportEvent2["ShutdownComplete"] = "shutdownComplete";
|
|
48
48
|
TransportEvent2["DeadLetter"] = "deadLetter";
|
|
49
|
+
TransportEvent2["ConsumerRecovered"] = "consumerRecovered";
|
|
50
|
+
TransportEvent2["HandlerCompleted"] = "handlerCompleted";
|
|
51
|
+
TransportEvent2["Published"] = "published";
|
|
52
|
+
TransportEvent2["RpcCompleted"] = "rpcCompleted";
|
|
49
53
|
return TransportEvent2;
|
|
50
54
|
})(TransportEvent || {});
|
|
51
55
|
|
|
@@ -442,6 +446,8 @@ var resolveCaptureBody = (option) => {
|
|
|
442
446
|
};
|
|
443
447
|
};
|
|
444
448
|
var resolveOtelOptions = (options = {}) => {
|
|
449
|
+
if (options === true) options = {};
|
|
450
|
+
if (options === false) options = { enabled: false };
|
|
445
451
|
return {
|
|
446
452
|
enabled: options.enabled ?? true,
|
|
447
453
|
traces: expandTracesOption(options.traces),
|
|
@@ -523,7 +529,7 @@ var extractContext = (ctx, carrier, getter) => propagation.extract(ctx, carrier,
|
|
|
523
529
|
|
|
524
530
|
// src/otel/tracer.ts
|
|
525
531
|
import { trace } from "@opentelemetry/api";
|
|
526
|
-
var PACKAGE_VERSION = true ? "2.
|
|
532
|
+
var PACKAGE_VERSION = true ? "2.11.1" : "0.0.0";
|
|
527
533
|
var getTracer = () => trace.getTracer(TRACER_NAME, PACKAGE_VERSION);
|
|
528
534
|
|
|
529
535
|
// src/otel/carrier.ts
|
|
@@ -1325,6 +1331,17 @@ var detectEventKind = (pattern) => {
|
|
|
1325
1331
|
if (pattern.startsWith("ordered:" /* Ordered */)) return "ordered" /* Ordered */;
|
|
1326
1332
|
return "event" /* Event */;
|
|
1327
1333
|
};
|
|
1334
|
+
var declaredEventPattern = (pattern) => {
|
|
1335
|
+
if (pattern.startsWith("broadcast:" /* Broadcast */))
|
|
1336
|
+
return pattern.slice("broadcast:" /* Broadcast */.length);
|
|
1337
|
+
if (pattern.startsWith("ordered:" /* Ordered */)) return pattern.slice("ordered:" /* Ordered */.length);
|
|
1338
|
+
return pattern;
|
|
1339
|
+
};
|
|
1340
|
+
var eventStreamKind = (kind) => {
|
|
1341
|
+
if (kind === "broadcast" /* Broadcast */) return "broadcast" /* Broadcast */;
|
|
1342
|
+
if (kind === "ordered" /* Ordered */) return "ordered" /* Ordered */;
|
|
1343
|
+
return "ev" /* Event */;
|
|
1344
|
+
};
|
|
1328
1345
|
var JetstreamClient = class extends ClientProxy {
|
|
1329
1346
|
constructor(rootOptions, targetServiceName, connection, codec, eventBus) {
|
|
1330
1347
|
super();
|
|
@@ -1440,50 +1457,60 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
1440
1457
|
const encoded = this.codec.encode(data);
|
|
1441
1458
|
const effectiveMsgId = messageId ?? nuid.next();
|
|
1442
1459
|
const record = packet.data instanceof JetstreamRecord ? packet.data : new JetstreamRecord(data, /* @__PURE__ */ new Map());
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1460
|
+
const publishKind = detectEventKind(packet.pattern);
|
|
1461
|
+
const declaredPattern = declaredEventPattern(packet.pattern);
|
|
1462
|
+
const streamKind = eventStreamKind(publishKind);
|
|
1463
|
+
const startedAt = performance.now();
|
|
1464
|
+
try {
|
|
1465
|
+
await withPublishSpan(
|
|
1466
|
+
{
|
|
1467
|
+
subject: publishSubject,
|
|
1468
|
+
pattern: packet.pattern,
|
|
1469
|
+
record,
|
|
1470
|
+
kind: publishKind,
|
|
1471
|
+
payloadBytes: encoded.length,
|
|
1472
|
+
payload: encoded,
|
|
1473
|
+
messageId: effectiveMsgId,
|
|
1474
|
+
headers: msgHeaders,
|
|
1475
|
+
serviceName: this.callerName,
|
|
1476
|
+
endpoint: this.serverEndpoint,
|
|
1477
|
+
scheduleTarget: schedule ? eventSubject : void 0
|
|
1478
|
+
},
|
|
1479
|
+
this.otel,
|
|
1480
|
+
async () => {
|
|
1481
|
+
const warnIfDuplicate = (kindLabel, ack2) => {
|
|
1482
|
+
if (ack2.duplicate) {
|
|
1483
|
+
this.logger.warn(
|
|
1484
|
+
`Duplicate ${kindLabel} publish detected: ${publishSubject} (seq: ${ack2.seq})`
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
if (schedule) {
|
|
1489
|
+
const ack2 = await this.connection.getJetStreamClient().publish(publishSubject, encoded, {
|
|
1490
|
+
headers: msgHeaders,
|
|
1491
|
+
msgID: effectiveMsgId,
|
|
1492
|
+
ttl,
|
|
1493
|
+
schedule: {
|
|
1494
|
+
specification: schedule.at,
|
|
1495
|
+
target: eventSubject
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
warnIfDuplicate("scheduled", ack2);
|
|
1499
|
+
return;
|
|
1464
1500
|
}
|
|
1465
|
-
|
|
1466
|
-
if (schedule) {
|
|
1467
|
-
const ack2 = await this.connection.getJetStreamClient().publish(publishSubject, encoded, {
|
|
1501
|
+
const ack = await this.connection.getJetStreamClient().publish(publishSubject, encoded, {
|
|
1468
1502
|
headers: msgHeaders,
|
|
1469
1503
|
msgID: effectiveMsgId,
|
|
1470
|
-
ttl
|
|
1471
|
-
schedule: {
|
|
1472
|
-
specification: schedule.at,
|
|
1473
|
-
target: eventSubject
|
|
1474
|
-
}
|
|
1504
|
+
ttl
|
|
1475
1505
|
});
|
|
1476
|
-
warnIfDuplicate("
|
|
1477
|
-
return;
|
|
1506
|
+
warnIfDuplicate("event", ack);
|
|
1478
1507
|
}
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
}
|
|
1486
|
-
);
|
|
1508
|
+
);
|
|
1509
|
+
this.reportPublished(declaredPattern, streamKind, startedAt, "success");
|
|
1510
|
+
} catch (err) {
|
|
1511
|
+
this.reportPublished(declaredPattern, streamKind, startedAt, "error");
|
|
1512
|
+
throw err;
|
|
1513
|
+
}
|
|
1487
1514
|
return void 0;
|
|
1488
1515
|
}
|
|
1489
1516
|
/**
|
|
@@ -1511,14 +1538,17 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
1511
1538
|
};
|
|
1512
1539
|
let jetStreamCorrelationId = null;
|
|
1513
1540
|
if (this.isCoreMode) {
|
|
1514
|
-
this.publishCoreRpc(subject, data, hdrs, timeout, callback).catch(
|
|
1541
|
+
this.publishCoreRpc(subject, data, hdrs, timeout, callback, packet.pattern).catch(
|
|
1542
|
+
onUnhandled
|
|
1543
|
+
);
|
|
1515
1544
|
} else {
|
|
1516
1545
|
jetStreamCorrelationId = nuid.next();
|
|
1517
1546
|
this.publishJetStreamRpc(subject, data, callback, {
|
|
1518
1547
|
headers: hdrs,
|
|
1519
1548
|
timeout,
|
|
1520
1549
|
correlationId: jetStreamCorrelationId,
|
|
1521
|
-
messageId
|
|
1550
|
+
messageId,
|
|
1551
|
+
declaredPattern: packet.pattern
|
|
1522
1552
|
}).catch(onUnhandled);
|
|
1523
1553
|
}
|
|
1524
1554
|
return () => {
|
|
@@ -1533,7 +1563,7 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
1533
1563
|
};
|
|
1534
1564
|
}
|
|
1535
1565
|
/** Core mode: nc.request() with timeout. */
|
|
1536
|
-
async publishCoreRpc(subject, data, customHeaders, timeout, callback) {
|
|
1566
|
+
async publishCoreRpc(subject, data, customHeaders, timeout, callback, declaredPattern) {
|
|
1537
1567
|
const effectiveTimeout = timeout ?? this.defaultRpcTimeout;
|
|
1538
1568
|
const hdrs = this.buildHeaders(customHeaders, { subject });
|
|
1539
1569
|
const encoded = this.codec.encode(data);
|
|
@@ -1548,6 +1578,7 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
1548
1578
|
},
|
|
1549
1579
|
this.otel
|
|
1550
1580
|
);
|
|
1581
|
+
const startedAt = performance.now();
|
|
1551
1582
|
try {
|
|
1552
1583
|
const nc = this.readyForPublish ? this.connection.unwrap : await this.connect();
|
|
1553
1584
|
const response = await context6.with(
|
|
@@ -1560,9 +1591,13 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
1560
1591
|
const decoded = this.codec.decode(response.data);
|
|
1561
1592
|
if (response.headers?.get("x-error" /* Error */)) {
|
|
1562
1593
|
spanHandle.finish({ kind: "reply-error" /* ReplyError */, replyPayload: decoded });
|
|
1594
|
+
this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "success");
|
|
1595
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "error");
|
|
1563
1596
|
callback({ err: decoded, response: null, isDisposed: true });
|
|
1564
1597
|
} else {
|
|
1565
1598
|
spanHandle.finish({ kind: "ok" /* Ok */, reply: decoded });
|
|
1599
|
+
this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "success");
|
|
1600
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "success");
|
|
1566
1601
|
callback({ err: null, response: decoded, isDisposed: true });
|
|
1567
1602
|
}
|
|
1568
1603
|
} catch (err) {
|
|
@@ -1570,16 +1605,20 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
1570
1605
|
if (error instanceof TimeoutError) {
|
|
1571
1606
|
spanHandle.finish({ kind: "timeout" /* Timeout */ });
|
|
1572
1607
|
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, "");
|
|
1608
|
+
this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "success");
|
|
1609
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "timeout");
|
|
1573
1610
|
} else {
|
|
1574
1611
|
spanHandle.finish({ kind: "error" /* Error */, error });
|
|
1575
1612
|
this.eventBus.emit("error" /* Error */, error, "client-rpc");
|
|
1613
|
+
this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "error");
|
|
1614
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "error");
|
|
1576
1615
|
}
|
|
1577
1616
|
callback({ err: error, response: null, isDisposed: true });
|
|
1578
1617
|
}
|
|
1579
1618
|
}
|
|
1580
1619
|
/** JetStream mode: publish to stream + wait for inbox response. */
|
|
1581
1620
|
async publishJetStreamRpc(subject, data, callback, options) {
|
|
1582
|
-
const { headers: customHeaders, correlationId, messageId } = options;
|
|
1621
|
+
const { headers: customHeaders, correlationId, messageId, declaredPattern } = options;
|
|
1583
1622
|
const effectiveTimeout = options.timeout ?? this.defaultRpcTimeout;
|
|
1584
1623
|
const hdrs = this.buildHeaders(customHeaders, {
|
|
1585
1624
|
subject,
|
|
@@ -1600,6 +1639,7 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
1600
1639
|
},
|
|
1601
1640
|
this.otel
|
|
1602
1641
|
);
|
|
1642
|
+
const startedAt = performance.now();
|
|
1603
1643
|
this.pendingMessages.set(correlationId, (packet) => {
|
|
1604
1644
|
if (packet.err) {
|
|
1605
1645
|
if (packet.err instanceof Error) {
|
|
@@ -1607,8 +1647,10 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
1607
1647
|
} else {
|
|
1608
1648
|
spanHandle.finish({ kind: "reply-error" /* ReplyError */, replyPayload: packet.err });
|
|
1609
1649
|
}
|
|
1650
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "error");
|
|
1610
1651
|
} else {
|
|
1611
1652
|
spanHandle.finish({ kind: "ok" /* Ok */, reply: packet.response });
|
|
1653
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "success");
|
|
1612
1654
|
}
|
|
1613
1655
|
callback(packet);
|
|
1614
1656
|
});
|
|
@@ -1618,6 +1660,7 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
1618
1660
|
this.pendingMessages.delete(correlationId);
|
|
1619
1661
|
spanHandle.finish({ kind: "timeout" /* Timeout */ });
|
|
1620
1662
|
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
|
|
1663
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "timeout");
|
|
1621
1664
|
callback({ err: new Error(RPC_TIMEOUT_MESSAGE), response: null, isDisposed: true });
|
|
1622
1665
|
}, effectiveTimeout);
|
|
1623
1666
|
this.pendingTimeouts.set(correlationId, timeoutId);
|
|
@@ -1630,6 +1673,8 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
1630
1673
|
this.pendingMessages.delete(correlationId);
|
|
1631
1674
|
const inboxError = new Error("Inbox not initialized");
|
|
1632
1675
|
spanHandle.finish({ kind: "error" /* Error */, error: inboxError });
|
|
1676
|
+
this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "error");
|
|
1677
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "error");
|
|
1633
1678
|
callback({
|
|
1634
1679
|
err: new Error("Inbox not initialized \u2014 JetStream RPC mode requires a connected inbox"),
|
|
1635
1680
|
response: null,
|
|
@@ -1644,6 +1689,7 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
1644
1689
|
msgID: messageId ?? nuid.next()
|
|
1645
1690
|
})
|
|
1646
1691
|
);
|
|
1692
|
+
this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "success");
|
|
1647
1693
|
} catch (err) {
|
|
1648
1694
|
const existingTimeout = this.pendingTimeouts.get(correlationId);
|
|
1649
1695
|
if (existingTimeout) {
|
|
@@ -1655,9 +1701,32 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
1655
1701
|
const error = err instanceof Error ? err : new Error("Unknown error");
|
|
1656
1702
|
spanHandle.finish({ kind: "error" /* Error */, error });
|
|
1657
1703
|
this.eventBus.emit("error" /* Error */, error, `jetstream-rpc-publish:${subject}`);
|
|
1704
|
+
this.reportPublished(declaredPattern, "cmd" /* Command */, startedAt, "error");
|
|
1705
|
+
this.reportRpcCompleted(declaredPattern, startedAt, "error");
|
|
1658
1706
|
callback({ err: error, response: null, isDisposed: true });
|
|
1659
1707
|
}
|
|
1660
1708
|
}
|
|
1709
|
+
// hasHook is per-emit so late subscribers (JetstreamMetricsService during
|
|
1710
|
+
// OnApplicationBootstrap) still receive events.
|
|
1711
|
+
reportPublished(declaredPattern, kind, startedAt, status) {
|
|
1712
|
+
if (!this.eventBus.hasHook("published" /* Published */)) return;
|
|
1713
|
+
this.eventBus.emit(
|
|
1714
|
+
"published" /* Published */,
|
|
1715
|
+
declaredPattern,
|
|
1716
|
+
kind,
|
|
1717
|
+
performance.now() - startedAt,
|
|
1718
|
+
status
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
reportRpcCompleted(declaredPattern, startedAt, status) {
|
|
1722
|
+
if (!this.eventBus.hasHook("rpcCompleted" /* RpcCompleted */)) return;
|
|
1723
|
+
this.eventBus.emit(
|
|
1724
|
+
"rpcCompleted" /* RpcCompleted */,
|
|
1725
|
+
declaredPattern,
|
|
1726
|
+
performance.now() - startedAt,
|
|
1727
|
+
status
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1661
1730
|
/** Fail-fast all pending JetStream RPC callbacks on connection loss. */
|
|
1662
1731
|
handleDisconnect() {
|
|
1663
1732
|
this.rejectPendingRpcs(new Error("Connection lost"));
|
|
@@ -2068,43 +2137,53 @@ var ConnectionProvider = class {
|
|
|
2068
2137
|
var EventBus = class {
|
|
2069
2138
|
hooks;
|
|
2070
2139
|
logger;
|
|
2140
|
+
subscribers = /* @__PURE__ */ new Map();
|
|
2071
2141
|
constructor(logger5, hooks) {
|
|
2072
2142
|
this.logger = logger5;
|
|
2073
2143
|
this.hooks = hooks ?? {};
|
|
2074
2144
|
}
|
|
2075
2145
|
/**
|
|
2076
|
-
*
|
|
2077
|
-
*
|
|
2078
|
-
|
|
2079
|
-
|
|
2146
|
+
* Subscribe to a transport event. Used by built-in observers (e.g. metrics).
|
|
2147
|
+
* Multiple subscribers per event are supported; each is called independently.
|
|
2148
|
+
*/
|
|
2149
|
+
subscribe(event, handler) {
|
|
2150
|
+
const list = this.subscribers.get(event) ?? [];
|
|
2151
|
+
list.push(handler);
|
|
2152
|
+
this.subscribers.set(event, list);
|
|
2153
|
+
}
|
|
2154
|
+
/**
|
|
2155
|
+
* Emit a lifecycle event. Dispatches to all internal subscribers and the
|
|
2156
|
+
* registered user hook (if any).
|
|
2080
2157
|
*/
|
|
2081
2158
|
emit(event, ...args) {
|
|
2082
|
-
|
|
2083
|
-
if (!hook) return;
|
|
2084
|
-
this.callHook(event, hook, ...args);
|
|
2159
|
+
this.dispatch(event, args);
|
|
2085
2160
|
}
|
|
2086
2161
|
/**
|
|
2087
2162
|
* Hot-path optimized emit for MessageRouted events.
|
|
2088
2163
|
* Avoids rest/spread overhead of the generic `emit()`.
|
|
2089
2164
|
*/
|
|
2090
2165
|
emitMessageRouted(subject, kind) {
|
|
2091
|
-
|
|
2092
|
-
if (!hook) return;
|
|
2093
|
-
this.callHook(
|
|
2094
|
-
"messageRouted" /* MessageRouted */,
|
|
2095
|
-
hook,
|
|
2096
|
-
subject,
|
|
2097
|
-
kind
|
|
2098
|
-
);
|
|
2166
|
+
this.dispatch("messageRouted" /* MessageRouted */, [subject, kind]);
|
|
2099
2167
|
}
|
|
2100
2168
|
/**
|
|
2101
|
-
* Check whether
|
|
2102
|
-
*
|
|
2103
|
-
*
|
|
2104
|
-
* transport owner did not register a listener.
|
|
2169
|
+
* Check whether any listener (user hook or internal subscriber) is registered
|
|
2170
|
+
* for the given event. Used by routing hot path to elide the emit call when
|
|
2171
|
+
* no one is listening.
|
|
2105
2172
|
*/
|
|
2106
2173
|
hasHook(event) {
|
|
2107
|
-
return this.hooks[event] !== void 0;
|
|
2174
|
+
return this.hooks[event] !== void 0 || (this.subscribers.get(event)?.length ?? 0) > 0;
|
|
2175
|
+
}
|
|
2176
|
+
dispatch(event, args) {
|
|
2177
|
+
const subs = this.subscribers.get(event);
|
|
2178
|
+
if (subs?.length) {
|
|
2179
|
+
for (const sub of [...subs]) {
|
|
2180
|
+
this.callHook(event, sub, ...args);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
const hook = this.hooks[event];
|
|
2184
|
+
if (hook) {
|
|
2185
|
+
this.callHook(event, hook, ...args);
|
|
2186
|
+
}
|
|
2108
2187
|
}
|
|
2109
2188
|
callHook(event, hook, ...args) {
|
|
2110
2189
|
try {
|
|
@@ -2183,12 +2262,697 @@ var JetstreamHealthIndicator = class {
|
|
|
2183
2262
|
isHealthCheckError: true
|
|
2184
2263
|
});
|
|
2185
2264
|
}
|
|
2186
|
-
return { [key]: details };
|
|
2265
|
+
return { [key]: details };
|
|
2266
|
+
}
|
|
2267
|
+
};
|
|
2268
|
+
JetstreamHealthIndicator = __decorateClass([
|
|
2269
|
+
Injectable()
|
|
2270
|
+
], JetstreamHealthIndicator);
|
|
2271
|
+
|
|
2272
|
+
// src/metrics/metrics.module.ts
|
|
2273
|
+
import { Module } from "@nestjs/common";
|
|
2274
|
+
|
|
2275
|
+
// src/server/routing/pattern-registry.ts
|
|
2276
|
+
import { Logger as Logger8 } from "@nestjs/common";
|
|
2277
|
+
var HANDLER_LABELS = {
|
|
2278
|
+
["broadcast" /* Broadcast */]: "broadcast" /* Broadcast */,
|
|
2279
|
+
["ordered" /* Ordered */]: "ordered" /* Ordered */,
|
|
2280
|
+
["ev" /* Event */]: "event" /* Event */,
|
|
2281
|
+
["cmd" /* Command */]: "rpc" /* Rpc */
|
|
2282
|
+
};
|
|
2283
|
+
var PatternRegistry = class {
|
|
2284
|
+
constructor(options) {
|
|
2285
|
+
this.options = options;
|
|
2286
|
+
}
|
|
2287
|
+
logger = new Logger8("Jetstream:PatternRegistry");
|
|
2288
|
+
registry = /* @__PURE__ */ new Map();
|
|
2289
|
+
// Cached after registerHandlers() — the registry is immutable from that point
|
|
2290
|
+
cachedPatterns = null;
|
|
2291
|
+
_hasEvents = false;
|
|
2292
|
+
_hasCommands = false;
|
|
2293
|
+
_hasBroadcasts = false;
|
|
2294
|
+
_hasOrdered = false;
|
|
2295
|
+
_hasMetadata = false;
|
|
2296
|
+
/**
|
|
2297
|
+
* Register all handlers from the NestJS strategy.
|
|
2298
|
+
*
|
|
2299
|
+
* @param handlers Map of pattern -> MessageHandler from `Server.getHandlers()`.
|
|
2300
|
+
*/
|
|
2301
|
+
registerHandlers(handlers) {
|
|
2302
|
+
const serviceName = this.options.name;
|
|
2303
|
+
for (const [pattern, handler] of handlers) {
|
|
2304
|
+
const extras = handler.extras;
|
|
2305
|
+
const isEvent = handler.isEventHandler ?? false;
|
|
2306
|
+
const isBroadcast = !!extras?.broadcast;
|
|
2307
|
+
const isOrdered = !!extras?.ordered;
|
|
2308
|
+
const meta = extras?.meta;
|
|
2309
|
+
if (isBroadcast && isOrdered) {
|
|
2310
|
+
throw new Error(
|
|
2311
|
+
`Handler "${pattern}" cannot be both broadcast and ordered. Use one or the other.`
|
|
2312
|
+
);
|
|
2313
|
+
}
|
|
2314
|
+
let kind;
|
|
2315
|
+
if (isBroadcast) kind = "broadcast" /* Broadcast */;
|
|
2316
|
+
else if (isOrdered) kind = "ordered" /* Ordered */;
|
|
2317
|
+
else if (isEvent) kind = "ev" /* Event */;
|
|
2318
|
+
else kind = "cmd" /* Command */;
|
|
2319
|
+
const fullSubject = kind === "broadcast" /* Broadcast */ ? buildBroadcastSubject(pattern) : buildSubject(serviceName, kind, pattern);
|
|
2320
|
+
this.registry.set(fullSubject, {
|
|
2321
|
+
handler,
|
|
2322
|
+
pattern,
|
|
2323
|
+
isEvent: isEvent && !isOrdered,
|
|
2324
|
+
isBroadcast,
|
|
2325
|
+
isOrdered,
|
|
2326
|
+
meta
|
|
2327
|
+
});
|
|
2328
|
+
this.logger.debug(`Registered ${HANDLER_LABELS[kind]}: ${pattern} -> ${fullSubject}`);
|
|
2329
|
+
}
|
|
2330
|
+
this.cachedPatterns = this.buildPatternsByKind();
|
|
2331
|
+
this._hasEvents = this.cachedPatterns.events.length > 0;
|
|
2332
|
+
this._hasCommands = this.cachedPatterns.commands.length > 0;
|
|
2333
|
+
this._hasBroadcasts = this.cachedPatterns.broadcasts.length > 0;
|
|
2334
|
+
this._hasOrdered = this.cachedPatterns.ordered.length > 0;
|
|
2335
|
+
this._hasMetadata = [...this.registry.values()].some((entry) => entry.meta !== void 0);
|
|
2336
|
+
this.logSummary();
|
|
2337
|
+
}
|
|
2338
|
+
/** Find handler for a full NATS subject. */
|
|
2339
|
+
getHandler(subject) {
|
|
2340
|
+
return this.registry.get(subject)?.handler ?? null;
|
|
2341
|
+
}
|
|
2342
|
+
/**
|
|
2343
|
+
* Resolve the declared pattern and {@link StreamKind} for a full NATS subject.
|
|
2344
|
+
*
|
|
2345
|
+
* Returns `null` when the subject is not registered. The declared pattern is
|
|
2346
|
+
* the value the user passed to `@EventPattern`/`@MessagePattern` — stable and
|
|
2347
|
+
* bounded, suitable for use as a Prometheus label without cardinality risk.
|
|
2348
|
+
*/
|
|
2349
|
+
resolveDeclared(subject) {
|
|
2350
|
+
const entry = this.registry.get(subject);
|
|
2351
|
+
if (!entry) return null;
|
|
2352
|
+
return { pattern: entry.pattern, kind: this.resolveStreamKind(entry) };
|
|
2353
|
+
}
|
|
2354
|
+
/** Get all registered broadcast patterns (for consumer filter_subject setup). */
|
|
2355
|
+
getBroadcastPatterns() {
|
|
2356
|
+
return this.getPatternsByKind().broadcasts.map((p) => buildBroadcastSubject(p));
|
|
2357
|
+
}
|
|
2358
|
+
hasBroadcastHandlers() {
|
|
2359
|
+
return this._hasBroadcasts;
|
|
2360
|
+
}
|
|
2361
|
+
hasRpcHandlers() {
|
|
2362
|
+
return this._hasCommands;
|
|
2363
|
+
}
|
|
2364
|
+
hasEventHandlers() {
|
|
2365
|
+
return this._hasEvents;
|
|
2366
|
+
}
|
|
2367
|
+
hasOrderedHandlers() {
|
|
2368
|
+
return this._hasOrdered;
|
|
2369
|
+
}
|
|
2370
|
+
/** Get fully-qualified NATS subjects for ordered handlers. */
|
|
2371
|
+
getOrderedSubjects() {
|
|
2372
|
+
return this.getPatternsByKind().ordered.map(
|
|
2373
|
+
(p) => buildSubject(this.options.name, "ordered" /* Ordered */, p)
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
/** Check if any registered handler has metadata. */
|
|
2377
|
+
hasMetadata() {
|
|
2378
|
+
return this._hasMetadata;
|
|
2379
|
+
}
|
|
2380
|
+
/**
|
|
2381
|
+
* Get handler metadata entries for KV publishing.
|
|
2382
|
+
*
|
|
2383
|
+
* Returns a map of KV key -> metadata object for all handlers that have `meta`.
|
|
2384
|
+
* Key format: `{serviceName}.{kind}.{pattern}`.
|
|
2385
|
+
*/
|
|
2386
|
+
getMetadataEntries() {
|
|
2387
|
+
const entries = /* @__PURE__ */ new Map();
|
|
2388
|
+
for (const entry of this.registry.values()) {
|
|
2389
|
+
if (!entry.meta) continue;
|
|
2390
|
+
const kind = this.resolveStreamKind(entry);
|
|
2391
|
+
const key = metadataKey(this.options.name, kind, entry.pattern);
|
|
2392
|
+
entries.set(key, entry.meta);
|
|
2393
|
+
}
|
|
2394
|
+
return entries;
|
|
2395
|
+
}
|
|
2396
|
+
/** Get patterns grouped by kind (cached after registration). */
|
|
2397
|
+
getPatternsByKind() {
|
|
2398
|
+
const patterns = this.cachedPatterns ?? this.buildPatternsByKind();
|
|
2399
|
+
return {
|
|
2400
|
+
events: [...patterns.events],
|
|
2401
|
+
commands: [...patterns.commands],
|
|
2402
|
+
broadcasts: [...patterns.broadcasts],
|
|
2403
|
+
ordered: [...patterns.ordered]
|
|
2404
|
+
};
|
|
2405
|
+
}
|
|
2406
|
+
/** Normalize a full NATS subject back to the user-facing pattern. */
|
|
2407
|
+
normalizeSubject(subject) {
|
|
2408
|
+
const name = internalName(this.options.name);
|
|
2409
|
+
const prefixes = [
|
|
2410
|
+
`${name}.${"cmd" /* Command */}.`,
|
|
2411
|
+
`${name}.${"ev" /* Event */}.`,
|
|
2412
|
+
`${name}.${"ordered" /* Ordered */}.`,
|
|
2413
|
+
`${"broadcast" /* Broadcast */}.`
|
|
2414
|
+
];
|
|
2415
|
+
for (const prefix of prefixes) {
|
|
2416
|
+
if (subject.startsWith(prefix)) {
|
|
2417
|
+
return subject.slice(prefix.length);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
return subject;
|
|
2421
|
+
}
|
|
2422
|
+
buildPatternsByKind() {
|
|
2423
|
+
const events = [];
|
|
2424
|
+
const commands = [];
|
|
2425
|
+
const broadcasts = [];
|
|
2426
|
+
const ordered = [];
|
|
2427
|
+
for (const entry of this.registry.values()) {
|
|
2428
|
+
if (entry.isBroadcast) broadcasts.push(entry.pattern);
|
|
2429
|
+
else if (entry.isOrdered) ordered.push(entry.pattern);
|
|
2430
|
+
else if (entry.isEvent) events.push(entry.pattern);
|
|
2431
|
+
else commands.push(entry.pattern);
|
|
2432
|
+
}
|
|
2433
|
+
return { events, commands, broadcasts, ordered };
|
|
2434
|
+
}
|
|
2435
|
+
resolveStreamKind(entry) {
|
|
2436
|
+
if (entry.isBroadcast) return "broadcast" /* Broadcast */;
|
|
2437
|
+
if (entry.isOrdered) return "ordered" /* Ordered */;
|
|
2438
|
+
if (entry.isEvent) return "ev" /* Event */;
|
|
2439
|
+
return "cmd" /* Command */;
|
|
2440
|
+
}
|
|
2441
|
+
logSummary() {
|
|
2442
|
+
const { events, commands, broadcasts, ordered } = this.getPatternsByKind();
|
|
2443
|
+
const parts = [
|
|
2444
|
+
`${commands.length} RPC`,
|
|
2445
|
+
`${events.length} events`,
|
|
2446
|
+
`${broadcasts.length} broadcasts`
|
|
2447
|
+
];
|
|
2448
|
+
if (ordered.length > 0) {
|
|
2449
|
+
parts.push(`${ordered.length} ordered`);
|
|
2450
|
+
}
|
|
2451
|
+
this.logger.log(`Registered handlers: ${parts.join(", ")}`);
|
|
2452
|
+
}
|
|
2453
|
+
};
|
|
2454
|
+
|
|
2455
|
+
// src/metrics/metrics.constants.ts
|
|
2456
|
+
var JETSTREAM_METRICS_CONFIG = /* @__PURE__ */ Symbol("JETSTREAM_METRICS_CONFIG");
|
|
2457
|
+
var JETSTREAM_METRICS_REGISTRY = /* @__PURE__ */ Symbol("JETSTREAM_METRICS_REGISTRY");
|
|
2458
|
+
var JETSTREAM_METRICS_PROM_CLIENT = /* @__PURE__ */ Symbol("JETSTREAM_METRICS_PROM_CLIENT");
|
|
2459
|
+
var DEFAULT_METRICS_PREFIX = "jetstream_";
|
|
2460
|
+
var DEFAULT_POLL_INTERVAL_MS = 15e3;
|
|
2461
|
+
var DEFAULT_HISTOGRAM_BUCKETS = {
|
|
2462
|
+
handlerDuration: [1e-3, 5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
|
|
2463
|
+
publishDuration: [1e-3, 5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
|
|
2464
|
+
rpcDuration: [1e-3, 5e-3, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
|
|
2465
|
+
};
|
|
2466
|
+
var ERROR_CONTEXT_PREFIXES = [
|
|
2467
|
+
["connection", "connection"],
|
|
2468
|
+
["codec", "codec"],
|
|
2469
|
+
["client-rpc", "publish"],
|
|
2470
|
+
["jetstream-rpc-publish", "publish"],
|
|
2471
|
+
["publish", "publish"],
|
|
2472
|
+
["message-provider", "consume"],
|
|
2473
|
+
["consume", "consume"],
|
|
2474
|
+
["core-rpc-handler", "handler"],
|
|
2475
|
+
["rpc-handler", "handler"],
|
|
2476
|
+
// EventRouter formats contexts as `${StreamKind.*}-handler:...` — the enum
|
|
2477
|
+
// uses short forms (`ev`, `ordered`, `broadcast`) so both surface in the wild.
|
|
2478
|
+
["ev-handler", "handler"],
|
|
2479
|
+
["event-handler", "handler"],
|
|
2480
|
+
["broadcast-handler", "handler"],
|
|
2481
|
+
["ordered-handler", "handler"],
|
|
2482
|
+
["handler", "handler"],
|
|
2483
|
+
["shutdown", "shutdown"]
|
|
2484
|
+
];
|
|
2485
|
+
var UNMATCHED_SUBJECT_LABEL = "<unmatched>";
|
|
2486
|
+
var STREAM_KIND_LABEL = {
|
|
2487
|
+
["ev" /* Event */]: "event",
|
|
2488
|
+
["cmd" /* Command */]: "command",
|
|
2489
|
+
["broadcast" /* Broadcast */]: "broadcast",
|
|
2490
|
+
["ordered" /* Ordered */]: "ordered"
|
|
2491
|
+
};
|
|
2492
|
+
|
|
2493
|
+
// src/metrics/metrics.service.ts
|
|
2494
|
+
import {
|
|
2495
|
+
Inject,
|
|
2496
|
+
Injectable as Injectable2,
|
|
2497
|
+
Logger as Logger10,
|
|
2498
|
+
Optional
|
|
2499
|
+
} from "@nestjs/common";
|
|
2500
|
+
|
|
2501
|
+
// src/metrics/error-context-mapper.ts
|
|
2502
|
+
var mapErrorContext = (context7) => {
|
|
2503
|
+
if (!context7) return "other";
|
|
2504
|
+
for (const [prefix, mapped] of ERROR_CONTEXT_PREFIXES) {
|
|
2505
|
+
if (context7 === prefix || context7.startsWith(`${prefix}:`)) {
|
|
2506
|
+
return mapped;
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
return "other";
|
|
2510
|
+
};
|
|
2511
|
+
|
|
2512
|
+
// src/metrics/metrics.factory.ts
|
|
2513
|
+
var createMetrics = (opts) => {
|
|
2514
|
+
const { register, promClient } = opts;
|
|
2515
|
+
const prefix = opts.prefix ?? DEFAULT_METRICS_PREFIX;
|
|
2516
|
+
const buckets = {
|
|
2517
|
+
handlerDuration: opts.buckets?.handlerDuration ?? DEFAULT_HISTOGRAM_BUCKETS.handlerDuration,
|
|
2518
|
+
publishDuration: opts.buckets?.publishDuration ?? DEFAULT_HISTOGRAM_BUCKETS.publishDuration,
|
|
2519
|
+
rpcDuration: opts.buckets?.rpcDuration ?? DEFAULT_HISTOGRAM_BUCKETS.rpcDuration
|
|
2520
|
+
};
|
|
2521
|
+
if (opts.defaultLabels && Object.keys(opts.defaultLabels).length > 0) {
|
|
2522
|
+
register.setDefaultLabels(opts.defaultLabels);
|
|
2523
|
+
}
|
|
2524
|
+
const counter = (name, help, labelNames) => new promClient.Counter({ name: `${prefix}${name}`, help, labelNames, registers: [register] });
|
|
2525
|
+
const histogram = (name, help, labelNames, bucketArr) => new promClient.Histogram({
|
|
2526
|
+
name: `${prefix}${name}`,
|
|
2527
|
+
help,
|
|
2528
|
+
labelNames,
|
|
2529
|
+
buckets: bucketArr,
|
|
2530
|
+
registers: [register]
|
|
2531
|
+
});
|
|
2532
|
+
const gauge = (name, help, labelNames) => new promClient.Gauge({ name: `${prefix}${name}`, help, labelNames, registers: [register] });
|
|
2533
|
+
return {
|
|
2534
|
+
messagesReceivedTotal: counter(
|
|
2535
|
+
"messages_received_total",
|
|
2536
|
+
"Total messages routed to a handler.",
|
|
2537
|
+
["stream", "subject", "kind"]
|
|
2538
|
+
),
|
|
2539
|
+
messagesProcessedTotal: counter(
|
|
2540
|
+
"messages_processed_total",
|
|
2541
|
+
"Total messages whose handler completed.",
|
|
2542
|
+
["stream", "subject", "kind", "status"]
|
|
2543
|
+
),
|
|
2544
|
+
messagesUnhandledTotal: counter(
|
|
2545
|
+
"messages_unhandled_total",
|
|
2546
|
+
"Messages received but not matching any registered handler.",
|
|
2547
|
+
["subject"]
|
|
2548
|
+
),
|
|
2549
|
+
messagesDeadLetterTotal: counter(
|
|
2550
|
+
"messages_dead_letter_total",
|
|
2551
|
+
"Messages routed to dead-letter after exhausting redelivery attempts.",
|
|
2552
|
+
["stream", "subject"]
|
|
2553
|
+
),
|
|
2554
|
+
publishTotal: counter(
|
|
2555
|
+
"publish_total",
|
|
2556
|
+
"Total publish/send operations performed by the client.",
|
|
2557
|
+
["subject", "kind", "status"]
|
|
2558
|
+
),
|
|
2559
|
+
rpcTimeoutTotal: counter("rpc_timeout_total", "RPC calls that exceeded the timeout deadline.", [
|
|
2560
|
+
"subject"
|
|
2561
|
+
]),
|
|
2562
|
+
consumerRecoveredTotal: counter(
|
|
2563
|
+
"consumer_recovered_total",
|
|
2564
|
+
"Self-healing recoveries after consume-loop failures.",
|
|
2565
|
+
["kind"]
|
|
2566
|
+
),
|
|
2567
|
+
errorsTotal: counter("errors_total", "Transport-level errors emitted on the EventBus.", [
|
|
2568
|
+
"context"
|
|
2569
|
+
]),
|
|
2570
|
+
handlerDurationSeconds: histogram(
|
|
2571
|
+
"handler_duration_seconds",
|
|
2572
|
+
"Wall-clock duration of handler execution.",
|
|
2573
|
+
["stream", "subject", "kind", "status"],
|
|
2574
|
+
buckets.handlerDuration
|
|
2575
|
+
),
|
|
2576
|
+
publishDurationSeconds: histogram(
|
|
2577
|
+
"publish_duration_seconds",
|
|
2578
|
+
"Wall-clock duration of client publish/send operations.",
|
|
2579
|
+
["subject", "kind", "status"],
|
|
2580
|
+
buckets.publishDuration
|
|
2581
|
+
),
|
|
2582
|
+
rpcDurationSeconds: histogram(
|
|
2583
|
+
"rpc_duration_seconds",
|
|
2584
|
+
"Wall-clock duration of RPC round-trips from client perspective.",
|
|
2585
|
+
["subject", "status"],
|
|
2586
|
+
buckets.rpcDuration
|
|
2587
|
+
),
|
|
2588
|
+
consumerNumPending: gauge(
|
|
2589
|
+
"consumer_num_pending",
|
|
2590
|
+
"Messages not yet delivered to this consumer.",
|
|
2591
|
+
["stream", "consumer", "kind"]
|
|
2592
|
+
),
|
|
2593
|
+
consumerNumAckPending: gauge(
|
|
2594
|
+
"consumer_num_ack_pending",
|
|
2595
|
+
"Messages delivered but not yet acked.",
|
|
2596
|
+
["stream", "consumer", "kind"]
|
|
2597
|
+
),
|
|
2598
|
+
consumerNumRedelivered: gauge(
|
|
2599
|
+
"consumer_num_redelivered",
|
|
2600
|
+
"Messages currently in redelivery state.",
|
|
2601
|
+
["stream", "consumer", "kind"]
|
|
2602
|
+
),
|
|
2603
|
+
consumerNumWaiting: gauge(
|
|
2604
|
+
"consumer_num_waiting",
|
|
2605
|
+
"Pull-request waiting count for this consumer.",
|
|
2606
|
+
["stream", "consumer", "kind"]
|
|
2607
|
+
),
|
|
2608
|
+
streamMessages: gauge("stream_messages", "Total messages stored in this stream.", ["stream"]),
|
|
2609
|
+
streamBytes: gauge("stream_bytes", "Total bytes stored in this stream.", ["stream"]),
|
|
2610
|
+
connectionUp: gauge("connection_up", "NATS connection state (1 connected, 0 disconnected).", [
|
|
2611
|
+
"server"
|
|
2612
|
+
]),
|
|
2613
|
+
metricsPollErrorsTotal: counter(
|
|
2614
|
+
"metrics_poll_errors_total",
|
|
2615
|
+
"Errors encountered while polling JetStreamManager for gauge data.",
|
|
2616
|
+
["target"]
|
|
2617
|
+
)
|
|
2618
|
+
};
|
|
2619
|
+
};
|
|
2620
|
+
|
|
2621
|
+
// src/metrics/poll-runner.ts
|
|
2622
|
+
import { Logger as Logger9 } from "@nestjs/common";
|
|
2623
|
+
var PollRunner = class {
|
|
2624
|
+
constructor(opts) {
|
|
2625
|
+
this.opts = opts;
|
|
2626
|
+
}
|
|
2627
|
+
logger = new Logger9("Jetstream:Metrics:Poll");
|
|
2628
|
+
timer = null;
|
|
2629
|
+
inFlight = null;
|
|
2630
|
+
start() {
|
|
2631
|
+
if (this.timer !== null) return;
|
|
2632
|
+
if (this.opts.intervalMs <= 0) return;
|
|
2633
|
+
if (this.opts.targets.length === 0) return;
|
|
2634
|
+
this.timer = setInterval(() => {
|
|
2635
|
+
if (this.inFlight !== null) {
|
|
2636
|
+
this.logger.warn("Skipping poll tick \u2014 previous cycle still in flight");
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
this.inFlight = this.tick().finally(() => {
|
|
2640
|
+
this.inFlight = null;
|
|
2641
|
+
});
|
|
2642
|
+
}, this.opts.intervalMs);
|
|
2643
|
+
}
|
|
2644
|
+
async stop() {
|
|
2645
|
+
if (this.timer !== null) {
|
|
2646
|
+
clearInterval(this.timer);
|
|
2647
|
+
this.timer = null;
|
|
2648
|
+
}
|
|
2649
|
+
if (this.inFlight !== null) await this.inFlight;
|
|
2650
|
+
}
|
|
2651
|
+
/** @internal Visible for tests. Runs one poll cycle. */
|
|
2652
|
+
async tick() {
|
|
2653
|
+
let jsm;
|
|
2654
|
+
try {
|
|
2655
|
+
jsm = await this.opts.jsmFactory();
|
|
2656
|
+
} catch {
|
|
2657
|
+
this.recordPollError("jsm.connect");
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2660
|
+
await Promise.all([this.pollConsumers(jsm), this.pollStreams(jsm)]);
|
|
2661
|
+
}
|
|
2662
|
+
async pollConsumers(jsm) {
|
|
2663
|
+
for (const target of this.opts.targets) {
|
|
2664
|
+
try {
|
|
2665
|
+
const info = await jsm.consumers.info(target.stream, target.consumer);
|
|
2666
|
+
const labels = {
|
|
2667
|
+
stream: target.stream,
|
|
2668
|
+
consumer: target.consumer,
|
|
2669
|
+
kind: STREAM_KIND_LABEL[target.kind]
|
|
2670
|
+
};
|
|
2671
|
+
this.opts.metrics.consumerNumPending.labels(labels).set(info.num_pending);
|
|
2672
|
+
this.opts.metrics.consumerNumAckPending.labels(labels).set(info.num_ack_pending);
|
|
2673
|
+
this.opts.metrics.consumerNumRedelivered.labels(labels).set(info.num_redelivered);
|
|
2674
|
+
this.opts.metrics.consumerNumWaiting.labels(labels).set(info.num_waiting);
|
|
2675
|
+
} catch {
|
|
2676
|
+
this.recordPollError("consumer.info");
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
async pollStreams(jsm) {
|
|
2681
|
+
const uniqueStreams = new Set(this.opts.targets.map((t) => t.stream));
|
|
2682
|
+
for (const stream of uniqueStreams) {
|
|
2683
|
+
try {
|
|
2684
|
+
const info = await jsm.streams.info(stream);
|
|
2685
|
+
this.opts.metrics.streamMessages.labels({ stream }).set(info.state.messages);
|
|
2686
|
+
this.opts.metrics.streamBytes.labels({ stream }).set(info.state.bytes);
|
|
2687
|
+
} catch {
|
|
2688
|
+
this.recordPollError("stream.info");
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
recordPollError(target) {
|
|
2693
|
+
this.opts.metrics.metricsPollErrorsTotal.labels({ target }).inc();
|
|
2694
|
+
}
|
|
2695
|
+
};
|
|
2696
|
+
|
|
2697
|
+
// src/metrics/metrics.service.ts
|
|
2698
|
+
var JetstreamMetricsService = class {
|
|
2699
|
+
constructor(eventBus, config, promClient, options, patternRegistry, connection = null) {
|
|
2700
|
+
this.eventBus = eventBus;
|
|
2701
|
+
this.config = config;
|
|
2702
|
+
this.promClient = promClient;
|
|
2703
|
+
this.options = options;
|
|
2704
|
+
this.patternRegistry = patternRegistry;
|
|
2705
|
+
this.connection = connection;
|
|
2706
|
+
}
|
|
2707
|
+
logger = new Logger10("Jetstream:Metrics");
|
|
2708
|
+
metrics = null;
|
|
2709
|
+
pollRunner = null;
|
|
2710
|
+
activeServers = /* @__PURE__ */ new Set();
|
|
2711
|
+
async onApplicationBootstrap() {
|
|
2712
|
+
if (this.metrics !== null) return;
|
|
2713
|
+
if (!this.options.metrics || !this.config || !this.promClient) return;
|
|
2714
|
+
if (!this.config.register) {
|
|
2715
|
+
throw new Error(
|
|
2716
|
+
"JetstreamMetricsService requires a prom-client Registry \u2014 none was resolved by JetstreamMetricsModule."
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
2719
|
+
this.metrics = createMetrics({
|
|
2720
|
+
register: this.config.register,
|
|
2721
|
+
promClient: this.promClient,
|
|
2722
|
+
prefix: this.config.prefix,
|
|
2723
|
+
defaultLabels: this.config.defaultLabels,
|
|
2724
|
+
buckets: this.config.buckets
|
|
2725
|
+
});
|
|
2726
|
+
this.subscribeToEvents();
|
|
2727
|
+
this.syncInitialConnectionState();
|
|
2728
|
+
this.startPolling();
|
|
2729
|
+
this.logger.log(
|
|
2730
|
+
`Metrics enabled (prefix=${this.config.prefix ?? DEFAULT_METRICS_PREFIX}, poll=${this.getEffectivePollInterval()}ms)`
|
|
2731
|
+
);
|
|
2732
|
+
}
|
|
2733
|
+
async onModuleDestroy() {
|
|
2734
|
+
await this.pollRunner?.stop();
|
|
2735
|
+
this.pollRunner = null;
|
|
2736
|
+
}
|
|
2737
|
+
/** @internal Visible for tests. `0` disables polling. */
|
|
2738
|
+
getEffectivePollInterval() {
|
|
2739
|
+
return this.config?.pollInterval ?? DEFAULT_POLL_INTERVAL_MS;
|
|
2740
|
+
}
|
|
2741
|
+
/**
|
|
2742
|
+
* NATS connects during early bootstrap, before this service subscribes to
|
|
2743
|
+
* the EventBus — the initial `Connect` emission misses us. Mirror the
|
|
2744
|
+
* current state here so `connection_up` reflects reality the moment metrics
|
|
2745
|
+
* come online; later disconnects/reconnects update it normally.
|
|
2746
|
+
*/
|
|
2747
|
+
syncInitialConnectionState() {
|
|
2748
|
+
const nc = this.connection?.unwrap;
|
|
2749
|
+
if (!nc) return;
|
|
2750
|
+
const server = nc.getServer();
|
|
2751
|
+
this.activeServers.add(server);
|
|
2752
|
+
this.metrics?.connectionUp.labels({ server }).set(1);
|
|
2753
|
+
}
|
|
2754
|
+
/** Skips polling for publisher-only deployments and when no kinds are active. */
|
|
2755
|
+
startPolling() {
|
|
2756
|
+
const interval = this.getEffectivePollInterval();
|
|
2757
|
+
const connection = this.connection;
|
|
2758
|
+
if (interval <= 0 || !this.patternRegistry || !connection || !this.metrics) return;
|
|
2759
|
+
const targets = this.buildPollTargets();
|
|
2760
|
+
if (targets.length === 0) return;
|
|
2761
|
+
this.pollRunner = new PollRunner({
|
|
2762
|
+
intervalMs: interval,
|
|
2763
|
+
jsmFactory: async () => connection.getJetStreamManager(),
|
|
2764
|
+
metrics: this.metrics,
|
|
2765
|
+
targets
|
|
2766
|
+
});
|
|
2767
|
+
this.pollRunner.start();
|
|
2768
|
+
}
|
|
2769
|
+
buildPollTargets() {
|
|
2770
|
+
const registry = this.patternRegistry;
|
|
2771
|
+
if (!registry) return [];
|
|
2772
|
+
const targets = [];
|
|
2773
|
+
if (registry.hasEventHandlers()) {
|
|
2774
|
+
targets.push({
|
|
2775
|
+
kind: "ev" /* Event */,
|
|
2776
|
+
stream: streamName(this.options.name, "ev" /* Event */),
|
|
2777
|
+
consumer: consumerName(this.options.name, "ev" /* Event */)
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
if (registry.hasRpcHandlers() && isJetStreamRpcMode(this.options.rpc)) {
|
|
2781
|
+
targets.push({
|
|
2782
|
+
kind: "cmd" /* Command */,
|
|
2783
|
+
stream: streamName(this.options.name, "cmd" /* Command */),
|
|
2784
|
+
consumer: consumerName(this.options.name, "cmd" /* Command */)
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
if (registry.hasBroadcastHandlers()) {
|
|
2788
|
+
targets.push({
|
|
2789
|
+
kind: "broadcast" /* Broadcast */,
|
|
2790
|
+
stream: streamName(this.options.name, "broadcast" /* Broadcast */),
|
|
2791
|
+
consumer: consumerName(this.options.name, "broadcast" /* Broadcast */)
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
return targets;
|
|
2795
|
+
}
|
|
2796
|
+
subscribeToEvents() {
|
|
2797
|
+
this.eventBus.subscribe("connect" /* Connect */, this.onConnect);
|
|
2798
|
+
this.eventBus.subscribe("disconnect" /* Disconnect */, this.onDisconnect);
|
|
2799
|
+
this.eventBus.subscribe("reconnect" /* Reconnect */, this.onReconnect);
|
|
2800
|
+
this.eventBus.subscribe("error" /* Error */, this.onError);
|
|
2801
|
+
this.eventBus.subscribe("rpcTimeout" /* RpcTimeout */, this.onRpcTimeout);
|
|
2802
|
+
this.eventBus.subscribe("messageRouted" /* MessageRouted */, this.onMessageRouted);
|
|
2803
|
+
this.eventBus.subscribe("deadLetter" /* DeadLetter */, this.onDeadLetter);
|
|
2804
|
+
this.eventBus.subscribe("consumerRecovered" /* ConsumerRecovered */, this.onConsumerRecovered);
|
|
2805
|
+
this.eventBus.subscribe("handlerCompleted" /* HandlerCompleted */, this.onHandlerCompleted);
|
|
2806
|
+
this.eventBus.subscribe("published" /* Published */, this.onPublished);
|
|
2807
|
+
this.eventBus.subscribe("rpcCompleted" /* RpcCompleted */, this.onRpcCompleted);
|
|
2808
|
+
}
|
|
2809
|
+
onConnect = (server) => {
|
|
2810
|
+
this.activeServers.add(server);
|
|
2811
|
+
this.metrics?.connectionUp.labels({ server }).set(1);
|
|
2812
|
+
};
|
|
2813
|
+
onReconnect = (server) => {
|
|
2814
|
+
this.activeServers.add(server);
|
|
2815
|
+
this.metrics?.connectionUp.labels({ server }).set(1);
|
|
2816
|
+
};
|
|
2817
|
+
onDisconnect = () => {
|
|
2818
|
+
for (const server of this.activeServers) {
|
|
2819
|
+
this.metrics?.connectionUp.labels({ server }).set(0);
|
|
2820
|
+
}
|
|
2821
|
+
};
|
|
2822
|
+
onError = (_err, context7) => {
|
|
2823
|
+
this.metrics?.errorsTotal.labels({ context: mapErrorContext(context7) }).inc();
|
|
2824
|
+
};
|
|
2825
|
+
onRpcTimeout = (subject, _correlationId) => {
|
|
2826
|
+
const declared = this.resolveDeclared(subject);
|
|
2827
|
+
const subjectLabel = declared?.pattern ?? UNMATCHED_SUBJECT_LABEL;
|
|
2828
|
+
this.metrics?.rpcTimeoutTotal.labels({ subject: subjectLabel }).inc();
|
|
2829
|
+
};
|
|
2830
|
+
// `_kind` collapses broadcast/ordered into MessageKind.Event — we use
|
|
2831
|
+
// declared.kind from PatternRegistry for the precise label instead.
|
|
2832
|
+
onMessageRouted = (subject, _kind) => {
|
|
2833
|
+
if (!this.metrics) return;
|
|
2834
|
+
const declared = this.resolveDeclared(subject);
|
|
2835
|
+
if (!declared) {
|
|
2836
|
+
this.metrics.messagesUnhandledTotal.labels({ subject: UNMATCHED_SUBJECT_LABEL }).inc();
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
this.metrics.messagesReceivedTotal.labels({
|
|
2840
|
+
stream: streamName(this.options.name, declared.kind),
|
|
2841
|
+
subject: declared.pattern,
|
|
2842
|
+
kind: STREAM_KIND_LABEL[declared.kind]
|
|
2843
|
+
}).inc();
|
|
2844
|
+
};
|
|
2845
|
+
onDeadLetter = (info) => {
|
|
2846
|
+
const declared = this.resolveDeclared(info.subject);
|
|
2847
|
+
const subjectLabel = declared?.pattern ?? UNMATCHED_SUBJECT_LABEL;
|
|
2848
|
+
this.metrics?.messagesDeadLetterTotal.labels({ stream: info.stream, subject: subjectLabel }).inc();
|
|
2849
|
+
};
|
|
2850
|
+
onConsumerRecovered = (label, _attempts) => {
|
|
2851
|
+
const kindLabel = STREAM_KIND_LABEL[label] ?? String(label);
|
|
2852
|
+
this.metrics?.consumerRecoveredTotal.labels({ kind: kindLabel }).inc();
|
|
2853
|
+
};
|
|
2854
|
+
onHandlerCompleted = (pattern, kind, durationMs, status) => {
|
|
2855
|
+
if (!this.metrics) return;
|
|
2856
|
+
const stream = streamName(this.options.name, kind);
|
|
2857
|
+
const kindLabel = STREAM_KIND_LABEL[kind];
|
|
2858
|
+
const labels = { stream, subject: pattern, kind: kindLabel, status };
|
|
2859
|
+
this.metrics.messagesProcessedTotal.labels(labels).inc();
|
|
2860
|
+
this.metrics.handlerDurationSeconds.labels(labels).observe(durationMs / 1e3);
|
|
2861
|
+
};
|
|
2862
|
+
onPublished = (pattern, kind, durationMs, status) => {
|
|
2863
|
+
if (!this.metrics) return;
|
|
2864
|
+
const labels = { subject: pattern, kind: STREAM_KIND_LABEL[kind], status };
|
|
2865
|
+
this.metrics.publishTotal.labels(labels).inc();
|
|
2866
|
+
this.metrics.publishDurationSeconds.labels(labels).observe(durationMs / 1e3);
|
|
2867
|
+
};
|
|
2868
|
+
onRpcCompleted = (pattern, durationMs, status) => {
|
|
2869
|
+
this.metrics?.rpcDurationSeconds.labels({ subject: pattern, status }).observe(durationMs / 1e3);
|
|
2870
|
+
};
|
|
2871
|
+
resolveDeclared(subject) {
|
|
2872
|
+
return this.patternRegistry?.resolveDeclared(subject) ?? null;
|
|
2187
2873
|
}
|
|
2188
2874
|
};
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2875
|
+
JetstreamMetricsService = __decorateClass([
|
|
2876
|
+
Injectable2(),
|
|
2877
|
+
__decorateParam(1, Inject(JETSTREAM_METRICS_CONFIG)),
|
|
2878
|
+
__decorateParam(2, Inject(JETSTREAM_METRICS_PROM_CLIENT)),
|
|
2879
|
+
__decorateParam(3, Inject(JETSTREAM_OPTIONS)),
|
|
2880
|
+
__decorateParam(4, Optional()),
|
|
2881
|
+
__decorateParam(5, Optional()),
|
|
2882
|
+
__decorateParam(5, Inject(JETSTREAM_CONNECTION))
|
|
2883
|
+
], JetstreamMetricsService);
|
|
2884
|
+
|
|
2885
|
+
// src/metrics/metrics.module.ts
|
|
2886
|
+
var PROM_CLIENT_INSTALL_MESSAGE = "prom-client is required when JetstreamModule.forRoot({ metrics: ... }) is enabled. Install it with: pnpm add prom-client";
|
|
2887
|
+
var resolvePromClient = async () => {
|
|
2888
|
+
try {
|
|
2889
|
+
return await import("prom-client");
|
|
2890
|
+
} catch {
|
|
2891
|
+
throw new Error(PROM_CLIENT_INSTALL_MESSAGE);
|
|
2892
|
+
}
|
|
2893
|
+
};
|
|
2894
|
+
var normalizeMetricsConfig = (option, promClient) => {
|
|
2895
|
+
const user = option && option !== true ? option : {};
|
|
2896
|
+
return {
|
|
2897
|
+
register: user.register ?? promClient.register,
|
|
2898
|
+
prefix: user.prefix ?? DEFAULT_METRICS_PREFIX,
|
|
2899
|
+
defaultLabels: user.defaultLabels,
|
|
2900
|
+
pollInterval: user.pollInterval ?? DEFAULT_POLL_INTERVAL_MS,
|
|
2901
|
+
buckets: user.buckets
|
|
2902
|
+
};
|
|
2903
|
+
};
|
|
2904
|
+
var JetstreamMetricsModule = class {
|
|
2905
|
+
static forFeature() {
|
|
2906
|
+
const promClientProvider = {
|
|
2907
|
+
provide: JETSTREAM_METRICS_PROM_CLIENT,
|
|
2908
|
+
inject: [JETSTREAM_OPTIONS],
|
|
2909
|
+
useFactory: async (opts) => {
|
|
2910
|
+
if (!opts.metrics) return null;
|
|
2911
|
+
const mod = await resolvePromClient();
|
|
2912
|
+
return { Counter: mod.Counter, Histogram: mod.Histogram, Gauge: mod.Gauge };
|
|
2913
|
+
}
|
|
2914
|
+
};
|
|
2915
|
+
const configProvider = {
|
|
2916
|
+
provide: JETSTREAM_METRICS_CONFIG,
|
|
2917
|
+
inject: [JETSTREAM_OPTIONS],
|
|
2918
|
+
useFactory: async (opts) => {
|
|
2919
|
+
if (!opts.metrics) return null;
|
|
2920
|
+
const mod = await resolvePromClient();
|
|
2921
|
+
return normalizeMetricsConfig(opts.metrics, mod);
|
|
2922
|
+
}
|
|
2923
|
+
};
|
|
2924
|
+
const registryProvider = {
|
|
2925
|
+
provide: JETSTREAM_METRICS_REGISTRY,
|
|
2926
|
+
inject: [JETSTREAM_METRICS_CONFIG],
|
|
2927
|
+
useFactory: (cfg) => cfg?.register ?? null
|
|
2928
|
+
};
|
|
2929
|
+
const serviceProvider = {
|
|
2930
|
+
provide: JetstreamMetricsService,
|
|
2931
|
+
inject: [
|
|
2932
|
+
JETSTREAM_EVENT_BUS,
|
|
2933
|
+
JETSTREAM_METRICS_CONFIG,
|
|
2934
|
+
JETSTREAM_METRICS_PROM_CLIENT,
|
|
2935
|
+
JETSTREAM_OPTIONS,
|
|
2936
|
+
{ token: PatternRegistry, optional: true },
|
|
2937
|
+
{ token: JETSTREAM_CONNECTION, optional: true }
|
|
2938
|
+
],
|
|
2939
|
+
useFactory: (eventBus, cfg, runtime, opts, patternRegistry, connection) => new JetstreamMetricsService(eventBus, cfg, runtime, opts, patternRegistry, connection)
|
|
2940
|
+
};
|
|
2941
|
+
return {
|
|
2942
|
+
module: JetstreamMetricsModule,
|
|
2943
|
+
providers: [promClientProvider, configProvider, registryProvider, serviceProvider],
|
|
2944
|
+
exports: [
|
|
2945
|
+
JetstreamMetricsService,
|
|
2946
|
+
JETSTREAM_METRICS_CONFIG,
|
|
2947
|
+
JETSTREAM_METRICS_REGISTRY,
|
|
2948
|
+
JETSTREAM_METRICS_PROM_CLIENT
|
|
2949
|
+
]
|
|
2950
|
+
};
|
|
2951
|
+
}
|
|
2952
|
+
};
|
|
2953
|
+
JetstreamMetricsModule = __decorateClass([
|
|
2954
|
+
Module({})
|
|
2955
|
+
], JetstreamMetricsModule);
|
|
2192
2956
|
|
|
2193
2957
|
// src/server/strategy.ts
|
|
2194
2958
|
import { Server } from "@nestjs/microservices";
|
|
@@ -2264,6 +3028,26 @@ var JetstreamStrategy = class extends Server {
|
|
|
2264
3028
|
this.messageProvider.destroy();
|
|
2265
3029
|
this.started = false;
|
|
2266
3030
|
}
|
|
3031
|
+
/**
|
|
3032
|
+
* Override NestJS `Server.addHandler` to fail-fast on duplicate pattern registration.
|
|
3033
|
+
*
|
|
3034
|
+
* The base class silently overwrites duplicate RPC handlers (last wins) and appends
|
|
3035
|
+
* duplicate event handlers to a linked list. Both behaviors are hazardous in a
|
|
3036
|
+
* JetStream context: silent overwrite drops a handler the user wrote, and double
|
|
3037
|
+
* event dispatch double-acks/double-processes the same JetStream message.
|
|
3038
|
+
*
|
|
3039
|
+
* We treat any pattern collision as a fatal misconfiguration so it surfaces at
|
|
3040
|
+
* bootstrap instead of in production traffic.
|
|
3041
|
+
*/
|
|
3042
|
+
addHandler(pattern, callback, isEventHandler = false, extras = {}) {
|
|
3043
|
+
const normalizedPattern = this.normalizePattern(pattern);
|
|
3044
|
+
if (this.messageHandlers.has(normalizedPattern)) {
|
|
3045
|
+
throw new Error(
|
|
3046
|
+
`Duplicate handler registered for pattern "${normalizedPattern}". Each @EventPattern() / @MessagePattern() value must be unique within a microservice \u2014 find and remove the second declaration.`
|
|
3047
|
+
);
|
|
3048
|
+
}
|
|
3049
|
+
super.addHandler(pattern, callback, isEventHandler, extras);
|
|
3050
|
+
}
|
|
2267
3051
|
/**
|
|
2268
3052
|
* Register event listener (required by Server base class).
|
|
2269
3053
|
*
|
|
@@ -2336,7 +3120,7 @@ var JetstreamStrategy = class extends Server {
|
|
|
2336
3120
|
};
|
|
2337
3121
|
|
|
2338
3122
|
// src/server/core-rpc.server.ts
|
|
2339
|
-
import { Logger as
|
|
3123
|
+
import { Logger as Logger11 } from "@nestjs/common";
|
|
2340
3124
|
import { headers as natsHeaders2 } from "@nats-io/transport-node";
|
|
2341
3125
|
|
|
2342
3126
|
// src/context/rpc.context.ts
|
|
@@ -2605,7 +3389,7 @@ var CoreRpcServer = class {
|
|
|
2605
3389
|
this.serviceName = derived.serviceName;
|
|
2606
3390
|
this.serverEndpoint = derived.serverEndpoint;
|
|
2607
3391
|
}
|
|
2608
|
-
logger = new
|
|
3392
|
+
logger = new Logger11("Jetstream:CoreRpc");
|
|
2609
3393
|
subscription = null;
|
|
2610
3394
|
otel;
|
|
2611
3395
|
serviceName;
|
|
@@ -2658,6 +3442,7 @@ var CoreRpcServer = class {
|
|
|
2658
3442
|
return;
|
|
2659
3443
|
}
|
|
2660
3444
|
const ctx = new RpcContext([msg]);
|
|
3445
|
+
const startedAt = performance.now();
|
|
2661
3446
|
try {
|
|
2662
3447
|
const raw = await withConsumeSpan(
|
|
2663
3448
|
{
|
|
@@ -2676,6 +3461,7 @@ var CoreRpcServer = class {
|
|
|
2676
3461
|
}
|
|
2677
3462
|
);
|
|
2678
3463
|
msg.respond(this.codec.encode(raw));
|
|
3464
|
+
this.reportHandlerCompleted(msg, startedAt, "success");
|
|
2679
3465
|
} catch (err) {
|
|
2680
3466
|
this.eventBus.emit(
|
|
2681
3467
|
"error" /* Error */,
|
|
@@ -2683,7 +3469,23 @@ var CoreRpcServer = class {
|
|
|
2683
3469
|
`core-rpc-handler:${msg.subject}`
|
|
2684
3470
|
);
|
|
2685
3471
|
this.respondWithError(msg, err);
|
|
2686
|
-
|
|
3472
|
+
this.reportHandlerCompleted(msg, startedAt, "error");
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
// See EventRouter.reportHandlerCompleted for the rationale on declared
|
|
3476
|
+
// pattern + per-emit hasHook check.
|
|
3477
|
+
reportHandlerCompleted(msg, startedAt, status) {
|
|
3478
|
+
if (!this.eventBus.hasHook("handlerCompleted" /* HandlerCompleted */)) return;
|
|
3479
|
+
const declared = this.patternRegistry.resolveDeclared(msg.subject);
|
|
3480
|
+
const pattern = declared?.pattern ?? msg.subject;
|
|
3481
|
+
const kind = declared?.kind ?? "cmd" /* Command */;
|
|
3482
|
+
this.eventBus.emit(
|
|
3483
|
+
"handlerCompleted" /* HandlerCompleted */,
|
|
3484
|
+
pattern,
|
|
3485
|
+
kind,
|
|
3486
|
+
performance.now() - startedAt,
|
|
3487
|
+
status
|
|
3488
|
+
);
|
|
2687
3489
|
}
|
|
2688
3490
|
/** Send an error response back to the caller with x-error header. */
|
|
2689
3491
|
respondWithError(msg, error) {
|
|
@@ -2698,7 +3500,7 @@ var CoreRpcServer = class {
|
|
|
2698
3500
|
};
|
|
2699
3501
|
|
|
2700
3502
|
// src/server/infrastructure/stream.provider.ts
|
|
2701
|
-
import { Logger as
|
|
3503
|
+
import { Logger as Logger13 } from "@nestjs/common";
|
|
2702
3504
|
import { JetStreamApiError as JetStreamApiError2 } from "@nats-io/jetstream";
|
|
2703
3505
|
|
|
2704
3506
|
// src/server/infrastructure/nats-error-codes.ts
|
|
@@ -2765,7 +3567,7 @@ var isEqual = (a, b) => {
|
|
|
2765
3567
|
};
|
|
2766
3568
|
|
|
2767
3569
|
// src/server/infrastructure/stream-migration.ts
|
|
2768
|
-
import { Logger as
|
|
3570
|
+
import { Logger as Logger12 } from "@nestjs/common";
|
|
2769
3571
|
import { JetStreamApiError } from "@nats-io/jetstream";
|
|
2770
3572
|
var MIGRATION_BACKUP_SUFFIX = "__migration_backup";
|
|
2771
3573
|
var DEFAULT_SOURCING_TIMEOUT_MS = 3e4;
|
|
@@ -2774,7 +3576,7 @@ var StreamMigration = class {
|
|
|
2774
3576
|
constructor(sourcingTimeoutMs = DEFAULT_SOURCING_TIMEOUT_MS) {
|
|
2775
3577
|
this.sourcingTimeoutMs = sourcingTimeoutMs;
|
|
2776
3578
|
}
|
|
2777
|
-
logger = new
|
|
3579
|
+
logger = new Logger12("Jetstream:Stream");
|
|
2778
3580
|
async migrate(jsm, streamName2, newConfig) {
|
|
2779
3581
|
const backupName = `${streamName2}${MIGRATION_BACKUP_SUFFIX}`;
|
|
2780
3582
|
const startTime = Date.now();
|
|
@@ -2861,7 +3663,7 @@ var StreamProvider = class {
|
|
|
2861
3663
|
this.otelServiceName = derived.serviceName;
|
|
2862
3664
|
this.otelEndpoint = derived.serverEndpoint;
|
|
2863
3665
|
}
|
|
2864
|
-
logger = new
|
|
3666
|
+
logger = new Logger13("Jetstream:Stream");
|
|
2865
3667
|
migration = new StreamMigration();
|
|
2866
3668
|
otel;
|
|
2867
3669
|
otelServiceName;
|
|
@@ -3116,7 +3918,7 @@ var StreamProvider = class {
|
|
|
3116
3918
|
};
|
|
3117
3919
|
|
|
3118
3920
|
// src/server/infrastructure/consumer.provider.ts
|
|
3119
|
-
import { Logger as
|
|
3921
|
+
import { Logger as Logger14 } from "@nestjs/common";
|
|
3120
3922
|
import { JetStreamApiError as JetStreamApiError3 } from "@nats-io/jetstream";
|
|
3121
3923
|
var ConsumerProvider = class {
|
|
3122
3924
|
constructor(options, connection, streamProvider, patternRegistry) {
|
|
@@ -3129,7 +3931,7 @@ var ConsumerProvider = class {
|
|
|
3129
3931
|
this.otelServiceName = derived.serviceName;
|
|
3130
3932
|
this.otelEndpoint = derived.serverEndpoint;
|
|
3131
3933
|
}
|
|
3132
|
-
logger = new
|
|
3934
|
+
logger = new Logger14("Jetstream:Consumer");
|
|
3133
3935
|
otel;
|
|
3134
3936
|
otelServiceName;
|
|
3135
3937
|
otelEndpoint;
|
|
@@ -3340,7 +4142,7 @@ var ConsumerProvider = class {
|
|
|
3340
4142
|
};
|
|
3341
4143
|
|
|
3342
4144
|
// src/server/infrastructure/message.provider.ts
|
|
3343
|
-
import { Logger as
|
|
4145
|
+
import { Logger as Logger15 } from "@nestjs/common";
|
|
3344
4146
|
import { DeliverPolicy as DeliverPolicy2 } from "@nats-io/jetstream";
|
|
3345
4147
|
import {
|
|
3346
4148
|
catchError,
|
|
@@ -3350,7 +4152,6 @@ import {
|
|
|
3350
4152
|
repeat,
|
|
3351
4153
|
Subject,
|
|
3352
4154
|
takeUntil,
|
|
3353
|
-
tap,
|
|
3354
4155
|
timer
|
|
3355
4156
|
} from "rxjs";
|
|
3356
4157
|
var MessageProvider = class {
|
|
@@ -3360,7 +4161,7 @@ var MessageProvider = class {
|
|
|
3360
4161
|
this.consumeOptionsMap = consumeOptionsMap;
|
|
3361
4162
|
this.consumerRecoveryFn = consumerRecoveryFn;
|
|
3362
4163
|
}
|
|
3363
|
-
logger = new
|
|
4164
|
+
logger = new Logger15("Jetstream:Message");
|
|
3364
4165
|
activeIterators = /* @__PURE__ */ new Set();
|
|
3365
4166
|
orderedReadyResolve = null;
|
|
3366
4167
|
orderedReadyReject = null;
|
|
@@ -3458,10 +4259,13 @@ var MessageProvider = class {
|
|
|
3458
4259
|
/** Create a self-healing consumer flow for a specific kind. */
|
|
3459
4260
|
createFlow(kind, info) {
|
|
3460
4261
|
const target$ = this.getTargetSubject(kind);
|
|
3461
|
-
return this.createSelfHealingFlow(
|
|
4262
|
+
return this.createSelfHealingFlow(
|
|
4263
|
+
(onConnected) => this.consumeOnce(kind, info, target$, onConnected),
|
|
4264
|
+
info.name
|
|
4265
|
+
);
|
|
3462
4266
|
}
|
|
3463
4267
|
/** Single iteration: get consumer -> pull messages -> emit to subject. */
|
|
3464
|
-
async consumeOnce(kind, info, target
|
|
4268
|
+
async consumeOnce(kind, info, target$, onConnected) {
|
|
3465
4269
|
const js = this.connection.getJetStreamClient();
|
|
3466
4270
|
let consumer;
|
|
3467
4271
|
let consumerName2 = info.name;
|
|
@@ -3489,6 +4293,7 @@ var MessageProvider = class {
|
|
|
3489
4293
|
});
|
|
3490
4294
|
this.activeIterators.add(messages);
|
|
3491
4295
|
this.monitorConsumerHealth(messages, consumerName2);
|
|
4296
|
+
onConnected();
|
|
3492
4297
|
try {
|
|
3493
4298
|
await messages.closed();
|
|
3494
4299
|
} finally {
|
|
@@ -3543,7 +4348,7 @@ var MessageProvider = class {
|
|
|
3543
4348
|
/** Create a self-healing ordered consumer flow. */
|
|
3544
4349
|
createOrderedFlow(streamName2, consumerOpts) {
|
|
3545
4350
|
return this.createSelfHealingFlow(
|
|
3546
|
-
() => this.consumeOrderedOnce(streamName2, consumerOpts),
|
|
4351
|
+
(onConnected) => this.consumeOrderedOnce(streamName2, consumerOpts, onConnected),
|
|
3547
4352
|
"ordered" /* Ordered */,
|
|
3548
4353
|
(err) => {
|
|
3549
4354
|
if (this.orderedReadyReject) {
|
|
@@ -3557,10 +4362,15 @@ var MessageProvider = class {
|
|
|
3557
4362
|
/** Shared self-healing flow: defer -> retry with exponential backoff on error/completion. */
|
|
3558
4363
|
createSelfHealingFlow(source, label, onFirstError) {
|
|
3559
4364
|
let consecutiveFailures = 0;
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
4365
|
+
const onConnected = () => {
|
|
4366
|
+
if (consecutiveFailures > 0) {
|
|
4367
|
+
const attempts = consecutiveFailures;
|
|
4368
|
+
this.logger.log(`Consumer ${label} recovered after ${attempts} failed attempt(s)`);
|
|
4369
|
+
this.eventBus.emit("consumerRecovered" /* ConsumerRecovered */, label, attempts);
|
|
4370
|
+
}
|
|
4371
|
+
consecutiveFailures = 0;
|
|
4372
|
+
};
|
|
4373
|
+
return defer2(() => source(onConnected)).pipe(
|
|
3564
4374
|
catchError((err) => {
|
|
3565
4375
|
consecutiveFailures++;
|
|
3566
4376
|
this.logger.error(`Consumer ${label} error, will restart:`, err);
|
|
@@ -3583,7 +4393,7 @@ var MessageProvider = class {
|
|
|
3583
4393
|
);
|
|
3584
4394
|
}
|
|
3585
4395
|
/** Single iteration: create ordered consumer -> push messages into the subject. */
|
|
3586
|
-
async consumeOrderedOnce(streamName2, consumerOpts) {
|
|
4396
|
+
async consumeOrderedOnce(streamName2, consumerOpts, onConnected) {
|
|
3587
4397
|
const js = this.connection.getJetStreamClient();
|
|
3588
4398
|
const consumer = await js.consumers.get(streamName2, consumerOpts);
|
|
3589
4399
|
const orderedMessages$ = this.orderedMessages$;
|
|
@@ -3598,6 +4408,7 @@ var MessageProvider = class {
|
|
|
3598
4408
|
this.orderedReadyReject = null;
|
|
3599
4409
|
}
|
|
3600
4410
|
this.activeIterators.add(messages);
|
|
4411
|
+
onConnected();
|
|
3601
4412
|
try {
|
|
3602
4413
|
await messages.closed();
|
|
3603
4414
|
} finally {
|
|
@@ -3607,7 +4418,7 @@ var MessageProvider = class {
|
|
|
3607
4418
|
};
|
|
3608
4419
|
|
|
3609
4420
|
// src/server/infrastructure/metadata.provider.ts
|
|
3610
|
-
import { Logger as
|
|
4421
|
+
import { Logger as Logger16 } from "@nestjs/common";
|
|
3611
4422
|
import { Kvm } from "@nats-io/kv";
|
|
3612
4423
|
var MetadataProvider = class {
|
|
3613
4424
|
constructor(options, connection) {
|
|
@@ -3616,7 +4427,7 @@ var MetadataProvider = class {
|
|
|
3616
4427
|
this.replicas = options.metadata?.replicas ?? DEFAULT_METADATA_REPLICAS;
|
|
3617
4428
|
this.ttl = Math.max(options.metadata?.ttl ?? DEFAULT_METADATA_TTL, MIN_METADATA_TTL);
|
|
3618
4429
|
}
|
|
3619
|
-
logger = new
|
|
4430
|
+
logger = new Logger16("Jetstream:Metadata");
|
|
3620
4431
|
bucketName;
|
|
3621
4432
|
replicas;
|
|
3622
4433
|
ttl;
|
|
@@ -3708,176 +4519,8 @@ var MetadataProvider = class {
|
|
|
3708
4519
|
}
|
|
3709
4520
|
};
|
|
3710
4521
|
|
|
3711
|
-
// src/server/routing/pattern-registry.ts
|
|
3712
|
-
import { Logger as Logger14 } from "@nestjs/common";
|
|
3713
|
-
var HANDLER_LABELS = {
|
|
3714
|
-
["broadcast" /* Broadcast */]: "broadcast" /* Broadcast */,
|
|
3715
|
-
["ordered" /* Ordered */]: "ordered" /* Ordered */,
|
|
3716
|
-
["ev" /* Event */]: "event" /* Event */,
|
|
3717
|
-
["cmd" /* Command */]: "rpc" /* Rpc */
|
|
3718
|
-
};
|
|
3719
|
-
var PatternRegistry = class {
|
|
3720
|
-
constructor(options) {
|
|
3721
|
-
this.options = options;
|
|
3722
|
-
}
|
|
3723
|
-
logger = new Logger14("Jetstream:PatternRegistry");
|
|
3724
|
-
registry = /* @__PURE__ */ new Map();
|
|
3725
|
-
// Cached after registerHandlers() — the registry is immutable from that point
|
|
3726
|
-
cachedPatterns = null;
|
|
3727
|
-
_hasEvents = false;
|
|
3728
|
-
_hasCommands = false;
|
|
3729
|
-
_hasBroadcasts = false;
|
|
3730
|
-
_hasOrdered = false;
|
|
3731
|
-
_hasMetadata = false;
|
|
3732
|
-
/**
|
|
3733
|
-
* Register all handlers from the NestJS strategy.
|
|
3734
|
-
*
|
|
3735
|
-
* @param handlers Map of pattern -> MessageHandler from `Server.getHandlers()`.
|
|
3736
|
-
*/
|
|
3737
|
-
registerHandlers(handlers) {
|
|
3738
|
-
const serviceName = this.options.name;
|
|
3739
|
-
for (const [pattern, handler] of handlers) {
|
|
3740
|
-
const extras = handler.extras;
|
|
3741
|
-
const isEvent = handler.isEventHandler ?? false;
|
|
3742
|
-
const isBroadcast = !!extras?.broadcast;
|
|
3743
|
-
const isOrdered = !!extras?.ordered;
|
|
3744
|
-
const meta = extras?.meta;
|
|
3745
|
-
if (isBroadcast && isOrdered) {
|
|
3746
|
-
throw new Error(
|
|
3747
|
-
`Handler "${pattern}" cannot be both broadcast and ordered. Use one or the other.`
|
|
3748
|
-
);
|
|
3749
|
-
}
|
|
3750
|
-
let kind;
|
|
3751
|
-
if (isBroadcast) kind = "broadcast" /* Broadcast */;
|
|
3752
|
-
else if (isOrdered) kind = "ordered" /* Ordered */;
|
|
3753
|
-
else if (isEvent) kind = "ev" /* Event */;
|
|
3754
|
-
else kind = "cmd" /* Command */;
|
|
3755
|
-
const fullSubject = kind === "broadcast" /* Broadcast */ ? buildBroadcastSubject(pattern) : buildSubject(serviceName, kind, pattern);
|
|
3756
|
-
this.registry.set(fullSubject, {
|
|
3757
|
-
handler,
|
|
3758
|
-
pattern,
|
|
3759
|
-
isEvent: isEvent && !isOrdered,
|
|
3760
|
-
isBroadcast,
|
|
3761
|
-
isOrdered,
|
|
3762
|
-
meta
|
|
3763
|
-
});
|
|
3764
|
-
this.logger.debug(`Registered ${HANDLER_LABELS[kind]}: ${pattern} -> ${fullSubject}`);
|
|
3765
|
-
}
|
|
3766
|
-
this.cachedPatterns = this.buildPatternsByKind();
|
|
3767
|
-
this._hasEvents = this.cachedPatterns.events.length > 0;
|
|
3768
|
-
this._hasCommands = this.cachedPatterns.commands.length > 0;
|
|
3769
|
-
this._hasBroadcasts = this.cachedPatterns.broadcasts.length > 0;
|
|
3770
|
-
this._hasOrdered = this.cachedPatterns.ordered.length > 0;
|
|
3771
|
-
this._hasMetadata = [...this.registry.values()].some((entry) => entry.meta !== void 0);
|
|
3772
|
-
this.logSummary();
|
|
3773
|
-
}
|
|
3774
|
-
/** Find handler for a full NATS subject. */
|
|
3775
|
-
getHandler(subject) {
|
|
3776
|
-
return this.registry.get(subject)?.handler ?? null;
|
|
3777
|
-
}
|
|
3778
|
-
/** Get all registered broadcast patterns (for consumer filter_subject setup). */
|
|
3779
|
-
getBroadcastPatterns() {
|
|
3780
|
-
return this.getPatternsByKind().broadcasts.map((p) => buildBroadcastSubject(p));
|
|
3781
|
-
}
|
|
3782
|
-
hasBroadcastHandlers() {
|
|
3783
|
-
return this._hasBroadcasts;
|
|
3784
|
-
}
|
|
3785
|
-
hasRpcHandlers() {
|
|
3786
|
-
return this._hasCommands;
|
|
3787
|
-
}
|
|
3788
|
-
hasEventHandlers() {
|
|
3789
|
-
return this._hasEvents;
|
|
3790
|
-
}
|
|
3791
|
-
hasOrderedHandlers() {
|
|
3792
|
-
return this._hasOrdered;
|
|
3793
|
-
}
|
|
3794
|
-
/** Get fully-qualified NATS subjects for ordered handlers. */
|
|
3795
|
-
getOrderedSubjects() {
|
|
3796
|
-
return this.getPatternsByKind().ordered.map(
|
|
3797
|
-
(p) => buildSubject(this.options.name, "ordered" /* Ordered */, p)
|
|
3798
|
-
);
|
|
3799
|
-
}
|
|
3800
|
-
/** Check if any registered handler has metadata. */
|
|
3801
|
-
hasMetadata() {
|
|
3802
|
-
return this._hasMetadata;
|
|
3803
|
-
}
|
|
3804
|
-
/**
|
|
3805
|
-
* Get handler metadata entries for KV publishing.
|
|
3806
|
-
*
|
|
3807
|
-
* Returns a map of KV key -> metadata object for all handlers that have `meta`.
|
|
3808
|
-
* Key format: `{serviceName}.{kind}.{pattern}`.
|
|
3809
|
-
*/
|
|
3810
|
-
getMetadataEntries() {
|
|
3811
|
-
const entries = /* @__PURE__ */ new Map();
|
|
3812
|
-
for (const entry of this.registry.values()) {
|
|
3813
|
-
if (!entry.meta) continue;
|
|
3814
|
-
const kind = this.resolveStreamKind(entry);
|
|
3815
|
-
const key = metadataKey(this.options.name, kind, entry.pattern);
|
|
3816
|
-
entries.set(key, entry.meta);
|
|
3817
|
-
}
|
|
3818
|
-
return entries;
|
|
3819
|
-
}
|
|
3820
|
-
/** Get patterns grouped by kind (cached after registration). */
|
|
3821
|
-
getPatternsByKind() {
|
|
3822
|
-
const patterns = this.cachedPatterns ?? this.buildPatternsByKind();
|
|
3823
|
-
return {
|
|
3824
|
-
events: [...patterns.events],
|
|
3825
|
-
commands: [...patterns.commands],
|
|
3826
|
-
broadcasts: [...patterns.broadcasts],
|
|
3827
|
-
ordered: [...patterns.ordered]
|
|
3828
|
-
};
|
|
3829
|
-
}
|
|
3830
|
-
/** Normalize a full NATS subject back to the user-facing pattern. */
|
|
3831
|
-
normalizeSubject(subject) {
|
|
3832
|
-
const name = internalName(this.options.name);
|
|
3833
|
-
const prefixes = [
|
|
3834
|
-
`${name}.${"cmd" /* Command */}.`,
|
|
3835
|
-
`${name}.${"ev" /* Event */}.`,
|
|
3836
|
-
`${name}.${"ordered" /* Ordered */}.`,
|
|
3837
|
-
`${"broadcast" /* Broadcast */}.`
|
|
3838
|
-
];
|
|
3839
|
-
for (const prefix of prefixes) {
|
|
3840
|
-
if (subject.startsWith(prefix)) {
|
|
3841
|
-
return subject.slice(prefix.length);
|
|
3842
|
-
}
|
|
3843
|
-
}
|
|
3844
|
-
return subject;
|
|
3845
|
-
}
|
|
3846
|
-
buildPatternsByKind() {
|
|
3847
|
-
const events = [];
|
|
3848
|
-
const commands = [];
|
|
3849
|
-
const broadcasts = [];
|
|
3850
|
-
const ordered = [];
|
|
3851
|
-
for (const entry of this.registry.values()) {
|
|
3852
|
-
if (entry.isBroadcast) broadcasts.push(entry.pattern);
|
|
3853
|
-
else if (entry.isOrdered) ordered.push(entry.pattern);
|
|
3854
|
-
else if (entry.isEvent) events.push(entry.pattern);
|
|
3855
|
-
else commands.push(entry.pattern);
|
|
3856
|
-
}
|
|
3857
|
-
return { events, commands, broadcasts, ordered };
|
|
3858
|
-
}
|
|
3859
|
-
resolveStreamKind(entry) {
|
|
3860
|
-
if (entry.isBroadcast) return "broadcast" /* Broadcast */;
|
|
3861
|
-
if (entry.isOrdered) return "ordered" /* Ordered */;
|
|
3862
|
-
if (entry.isEvent) return "ev" /* Event */;
|
|
3863
|
-
return "cmd" /* Command */;
|
|
3864
|
-
}
|
|
3865
|
-
logSummary() {
|
|
3866
|
-
const { events, commands, broadcasts, ordered } = this.getPatternsByKind();
|
|
3867
|
-
const parts = [
|
|
3868
|
-
`${commands.length} RPC`,
|
|
3869
|
-
`${events.length} events`,
|
|
3870
|
-
`${broadcasts.length} broadcasts`
|
|
3871
|
-
];
|
|
3872
|
-
if (ordered.length > 0) {
|
|
3873
|
-
parts.push(`${ordered.length} ordered`);
|
|
3874
|
-
}
|
|
3875
|
-
this.logger.log(`Registered handlers: ${parts.join(", ")}`);
|
|
3876
|
-
}
|
|
3877
|
-
};
|
|
3878
|
-
|
|
3879
4522
|
// src/server/routing/event.router.ts
|
|
3880
|
-
import { Logger as
|
|
4523
|
+
import { Logger as Logger17 } from "@nestjs/common";
|
|
3881
4524
|
import { headers as natsHeaders3 } from "@nats-io/transport-node";
|
|
3882
4525
|
var eventConsumeKindFor = (kind) => {
|
|
3883
4526
|
if (kind === "broadcast" /* Broadcast */) return "broadcast" /* Broadcast */;
|
|
@@ -3906,7 +4549,7 @@ var EventRouter = class {
|
|
|
3906
4549
|
this.serverEndpoint = null;
|
|
3907
4550
|
}
|
|
3908
4551
|
}
|
|
3909
|
-
logger = new
|
|
4552
|
+
logger = new Logger17("Jetstream:EventRouter");
|
|
3910
4553
|
subscriptions = [];
|
|
3911
4554
|
otel;
|
|
3912
4555
|
serviceName;
|
|
@@ -3950,7 +4593,14 @@ var EventRouter = class {
|
|
|
3950
4593
|
const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
|
|
3951
4594
|
const concurrency = this.getConcurrency(kind);
|
|
3952
4595
|
const hasDlqCheck = deadLetterConfig !== void 0;
|
|
3953
|
-
const
|
|
4596
|
+
const reportHandlerCompleted = (msg, startedAt, status) => {
|
|
4597
|
+
if (!eventBus.hasHook("handlerCompleted" /* HandlerCompleted */)) return;
|
|
4598
|
+
const declared = patternRegistry.resolveDeclared(msg.subject);
|
|
4599
|
+
const pattern = declared?.pattern ?? msg.subject;
|
|
4600
|
+
const declaredKind = declared?.kind ?? kind;
|
|
4601
|
+
const durationMs = performance.now() - startedAt;
|
|
4602
|
+
eventBus.emit("handlerCompleted" /* HandlerCompleted */, pattern, declaredKind, durationMs, status);
|
|
4603
|
+
};
|
|
3954
4604
|
const isDeadLetter = (msg) => {
|
|
3955
4605
|
if (!hasDlqCheck) return false;
|
|
3956
4606
|
const maxDeliver = deadLetterConfig.maxDeliverByStream?.get(msg.info.stream);
|
|
@@ -3987,7 +4637,7 @@ var EventRouter = class {
|
|
|
3987
4637
|
logger5.error(`Decode error for ${subject}:`, err);
|
|
3988
4638
|
return null;
|
|
3989
4639
|
}
|
|
3990
|
-
|
|
4640
|
+
eventBus.emitMessageRouted(subject, "event" /* Event */);
|
|
3991
4641
|
return { handler, data };
|
|
3992
4642
|
} catch (err) {
|
|
3993
4643
|
logger5.error(`Unexpected error in ${kind} event router`, err);
|
|
@@ -3999,12 +4649,18 @@ var EventRouter = class {
|
|
|
3999
4649
|
return null;
|
|
4000
4650
|
}
|
|
4001
4651
|
};
|
|
4652
|
+
const statusForContext = (ctx) => {
|
|
4653
|
+
if (ctx.shouldTerminate) return "terminated";
|
|
4654
|
+
if (ctx.shouldRetry) return "retried";
|
|
4655
|
+
return "success";
|
|
4656
|
+
};
|
|
4002
4657
|
const handleSafe = (msg) => {
|
|
4003
4658
|
const resolved = resolveEvent(msg);
|
|
4004
4659
|
if (resolved === null) return void 0;
|
|
4005
4660
|
const { handler, data } = resolved;
|
|
4006
4661
|
const ctx = new RpcContext([msg]);
|
|
4007
4662
|
const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
|
|
4663
|
+
const startedAt = performance.now();
|
|
4008
4664
|
let pending;
|
|
4009
4665
|
try {
|
|
4010
4666
|
pending = withConsumeSpan(
|
|
@@ -4027,18 +4683,21 @@ var EventRouter = class {
|
|
|
4027
4683
|
err instanceof Error ? err : new Error(String(err)),
|
|
4028
4684
|
`${kind}-handler:${msg.subject}`
|
|
4029
4685
|
);
|
|
4686
|
+
reportHandlerCompleted(msg, startedAt, "error");
|
|
4030
4687
|
return settleFailure(msg, data, err).finally(() => {
|
|
4031
4688
|
if (stopAckExtension !== null) stopAckExtension();
|
|
4032
4689
|
});
|
|
4033
4690
|
}
|
|
4034
4691
|
if (!isPromiseLike2(pending)) {
|
|
4035
4692
|
settleSuccess(msg, ctx);
|
|
4693
|
+
reportHandlerCompleted(msg, startedAt, statusForContext(ctx));
|
|
4036
4694
|
if (stopAckExtension !== null) stopAckExtension();
|
|
4037
4695
|
return void 0;
|
|
4038
4696
|
}
|
|
4039
4697
|
return pending.then(
|
|
4040
4698
|
() => {
|
|
4041
4699
|
settleSuccess(msg, ctx);
|
|
4700
|
+
reportHandlerCompleted(msg, startedAt, statusForContext(ctx));
|
|
4042
4701
|
if (stopAckExtension !== null) stopAckExtension();
|
|
4043
4702
|
},
|
|
4044
4703
|
async (err) => {
|
|
@@ -4047,6 +4706,7 @@ var EventRouter = class {
|
|
|
4047
4706
|
err instanceof Error ? err : new Error(String(err)),
|
|
4048
4707
|
`${kind}-handler:${msg.subject}`
|
|
4049
4708
|
);
|
|
4709
|
+
reportHandlerCompleted(msg, startedAt, "error");
|
|
4050
4710
|
try {
|
|
4051
4711
|
await settleFailure(msg, data, err);
|
|
4052
4712
|
} finally {
|
|
@@ -4071,7 +4731,7 @@ var EventRouter = class {
|
|
|
4071
4731
|
logger5.error(`Decode error for ${subject}:`, err);
|
|
4072
4732
|
return void 0;
|
|
4073
4733
|
}
|
|
4074
|
-
|
|
4734
|
+
eventBus.emitMessageRouted(subject, "event" /* Event */);
|
|
4075
4735
|
} catch (err) {
|
|
4076
4736
|
logger5.error(`Ordered handler error (${subject}):`, err);
|
|
4077
4737
|
return void 0;
|
|
@@ -4084,6 +4744,7 @@ var EventRouter = class {
|
|
|
4084
4744
|
);
|
|
4085
4745
|
}
|
|
4086
4746
|
};
|
|
4747
|
+
const startedAt = performance.now();
|
|
4087
4748
|
let pending;
|
|
4088
4749
|
try {
|
|
4089
4750
|
pending = withConsumeSpan(
|
|
@@ -4102,15 +4763,24 @@ var EventRouter = class {
|
|
|
4102
4763
|
);
|
|
4103
4764
|
} catch (err) {
|
|
4104
4765
|
logger5.error(`Ordered handler error (${subject}):`, err);
|
|
4766
|
+
reportHandlerCompleted(msg, startedAt, "error");
|
|
4105
4767
|
return void 0;
|
|
4106
4768
|
}
|
|
4107
4769
|
if (!isPromiseLike2(pending)) {
|
|
4108
4770
|
warnIfSettlementAttempted();
|
|
4771
|
+
reportHandlerCompleted(msg, startedAt, "success");
|
|
4109
4772
|
return void 0;
|
|
4110
4773
|
}
|
|
4111
|
-
return pending.then(
|
|
4112
|
-
|
|
4113
|
-
|
|
4774
|
+
return pending.then(
|
|
4775
|
+
() => {
|
|
4776
|
+
warnIfSettlementAttempted();
|
|
4777
|
+
reportHandlerCompleted(msg, startedAt, "success");
|
|
4778
|
+
},
|
|
4779
|
+
(err) => {
|
|
4780
|
+
logger5.error(`Ordered handler error (${subject}):`, err);
|
|
4781
|
+
reportHandlerCompleted(msg, startedAt, "error");
|
|
4782
|
+
}
|
|
4783
|
+
);
|
|
4114
4784
|
};
|
|
4115
4785
|
const route = isOrdered ? handleOrderedSafe : handleSafe;
|
|
4116
4786
|
const maxActive = isOrdered ? 1 : concurrency ?? Number.POSITIVE_INFINITY;
|
|
@@ -4296,7 +4966,7 @@ var EventRouter = class {
|
|
|
4296
4966
|
};
|
|
4297
4967
|
|
|
4298
4968
|
// src/server/routing/rpc.router.ts
|
|
4299
|
-
import { Logger as
|
|
4969
|
+
import { Logger as Logger18 } from "@nestjs/common";
|
|
4300
4970
|
import { headers } from "@nats-io/transport-node";
|
|
4301
4971
|
var RpcRouter = class {
|
|
4302
4972
|
constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap, options) {
|
|
@@ -4320,7 +4990,7 @@ var RpcRouter = class {
|
|
|
4320
4990
|
this.serverEndpoint = null;
|
|
4321
4991
|
}
|
|
4322
4992
|
}
|
|
4323
|
-
logger = new
|
|
4993
|
+
logger = new Logger18("Jetstream:RpcRouter");
|
|
4324
4994
|
timeout;
|
|
4325
4995
|
concurrency;
|
|
4326
4996
|
resolvedAckExtensionInterval;
|
|
@@ -4356,6 +5026,19 @@ var RpcRouter = class {
|
|
|
4356
5026
|
const emitRpcTimeout = (subject, correlationId) => {
|
|
4357
5027
|
eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
|
|
4358
5028
|
};
|
|
5029
|
+
const reportHandlerCompleted = (msg, startedAt, status) => {
|
|
5030
|
+
if (!eventBus.hasHook("handlerCompleted" /* HandlerCompleted */)) return;
|
|
5031
|
+
const declared = patternRegistry.resolveDeclared(msg.subject);
|
|
5032
|
+
const pattern = declared?.pattern ?? msg.subject;
|
|
5033
|
+
const declaredKind = declared?.kind ?? "cmd" /* Command */;
|
|
5034
|
+
eventBus.emit(
|
|
5035
|
+
"handlerCompleted" /* HandlerCompleted */,
|
|
5036
|
+
pattern,
|
|
5037
|
+
declaredKind,
|
|
5038
|
+
performance.now() - startedAt,
|
|
5039
|
+
status
|
|
5040
|
+
);
|
|
5041
|
+
};
|
|
4359
5042
|
const publishReply = (replyTo, correlationId, payload) => {
|
|
4360
5043
|
try {
|
|
4361
5044
|
const hdrs = headers();
|
|
@@ -4419,6 +5102,7 @@ var RpcRouter = class {
|
|
|
4419
5102
|
const subject = msg.subject;
|
|
4420
5103
|
const ctx = new RpcContext([msg]);
|
|
4421
5104
|
const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
|
|
5105
|
+
const startedAt = performance.now();
|
|
4422
5106
|
const reportHandlerError = (err) => {
|
|
4423
5107
|
eventBus.emit(
|
|
4424
5108
|
"error" /* Error */,
|
|
@@ -4449,12 +5133,14 @@ var RpcRouter = class {
|
|
|
4449
5133
|
} catch (err) {
|
|
4450
5134
|
if (stopAckExtension !== null) stopAckExtension();
|
|
4451
5135
|
reportHandlerError(err);
|
|
5136
|
+
reportHandlerCompleted(msg, startedAt, "error");
|
|
4452
5137
|
return void 0;
|
|
4453
5138
|
}
|
|
4454
5139
|
if (!isPromiseLike2(pending)) {
|
|
4455
5140
|
if (stopAckExtension !== null) stopAckExtension();
|
|
4456
5141
|
msg.ack();
|
|
4457
5142
|
publishReply(replyTo, correlationId, pending);
|
|
5143
|
+
reportHandlerCompleted(msg, startedAt, "success");
|
|
4458
5144
|
return void 0;
|
|
4459
5145
|
}
|
|
4460
5146
|
let settled = false;
|
|
@@ -4465,6 +5151,7 @@ var RpcRouter = class {
|
|
|
4465
5151
|
abortController.abort();
|
|
4466
5152
|
emitRpcTimeout(subject, correlationId);
|
|
4467
5153
|
msg.term("Handler timeout");
|
|
5154
|
+
reportHandlerCompleted(msg, startedAt, "terminated");
|
|
4468
5155
|
}, timeout);
|
|
4469
5156
|
return pending.then(
|
|
4470
5157
|
(result) => {
|
|
@@ -4474,6 +5161,7 @@ var RpcRouter = class {
|
|
|
4474
5161
|
if (stopAckExtension !== null) stopAckExtension();
|
|
4475
5162
|
msg.ack();
|
|
4476
5163
|
publishReply(replyTo, correlationId, result);
|
|
5164
|
+
reportHandlerCompleted(msg, startedAt, "success");
|
|
4477
5165
|
},
|
|
4478
5166
|
(err) => {
|
|
4479
5167
|
if (settled) return;
|
|
@@ -4481,6 +5169,7 @@ var RpcRouter = class {
|
|
|
4481
5169
|
clearTimeout(timeoutId);
|
|
4482
5170
|
if (stopAckExtension !== null) stopAckExtension();
|
|
4483
5171
|
reportHandlerError(err);
|
|
5172
|
+
reportHandlerCompleted(msg, startedAt, "error");
|
|
4484
5173
|
}
|
|
4485
5174
|
);
|
|
4486
5175
|
};
|
|
@@ -4540,14 +5229,14 @@ var RpcRouter = class {
|
|
|
4540
5229
|
};
|
|
4541
5230
|
|
|
4542
5231
|
// src/shutdown/shutdown.manager.ts
|
|
4543
|
-
import { Logger as
|
|
5232
|
+
import { Logger as Logger19 } from "@nestjs/common";
|
|
4544
5233
|
var ShutdownManager = class {
|
|
4545
5234
|
constructor(connection, eventBus, timeout) {
|
|
4546
5235
|
this.connection = connection;
|
|
4547
5236
|
this.eventBus = eventBus;
|
|
4548
5237
|
this.timeout = timeout;
|
|
4549
5238
|
}
|
|
4550
|
-
logger = new
|
|
5239
|
+
logger = new Logger19("Jetstream:Shutdown");
|
|
4551
5240
|
shutdownPromise;
|
|
4552
5241
|
/**
|
|
4553
5242
|
* Execute the full shutdown sequence.
|
|
@@ -4601,12 +5290,14 @@ var JetstreamModule = class {
|
|
|
4601
5290
|
return {
|
|
4602
5291
|
module: JetstreamModule,
|
|
4603
5292
|
global: true,
|
|
5293
|
+
imports: [JetstreamMetricsModule.forFeature()],
|
|
4604
5294
|
providers,
|
|
4605
5295
|
exports: [
|
|
4606
5296
|
JETSTREAM_CONNECTION,
|
|
4607
5297
|
JETSTREAM_CODEC,
|
|
4608
5298
|
JETSTREAM_EVENT_BUS,
|
|
4609
5299
|
JETSTREAM_OPTIONS,
|
|
5300
|
+
PatternRegistry,
|
|
4610
5301
|
ShutdownManager,
|
|
4611
5302
|
JetstreamStrategy,
|
|
4612
5303
|
JetstreamHealthIndicator
|
|
@@ -4628,13 +5319,14 @@ var JetstreamModule = class {
|
|
|
4628
5319
|
return {
|
|
4629
5320
|
module: JetstreamModule,
|
|
4630
5321
|
global: true,
|
|
4631
|
-
imports: asyncOptions.imports ?? [],
|
|
5322
|
+
imports: [...asyncOptions.imports ?? [], JetstreamMetricsModule.forFeature()],
|
|
4632
5323
|
providers: [...asyncProviders, ...coreProviders],
|
|
4633
5324
|
exports: [
|
|
4634
5325
|
JETSTREAM_CONNECTION,
|
|
4635
5326
|
JETSTREAM_CODEC,
|
|
4636
5327
|
JETSTREAM_EVENT_BUS,
|
|
4637
5328
|
JETSTREAM_OPTIONS,
|
|
5329
|
+
PatternRegistry,
|
|
4638
5330
|
ShutdownManager,
|
|
4639
5331
|
JetstreamStrategy,
|
|
4640
5332
|
JetstreamHealthIndicator
|
|
@@ -4683,7 +5375,7 @@ var JetstreamModule = class {
|
|
|
4683
5375
|
provide: JETSTREAM_EVENT_BUS,
|
|
4684
5376
|
inject: [JETSTREAM_OPTIONS],
|
|
4685
5377
|
useFactory: (options) => {
|
|
4686
|
-
const logger5 = new
|
|
5378
|
+
const logger5 = new Logger20("Jetstream:Module");
|
|
4687
5379
|
return new EventBus(logger5, options.hooks);
|
|
4688
5380
|
}
|
|
4689
5381
|
},
|
|
@@ -4978,11 +5670,11 @@ var JetstreamModule = class {
|
|
|
4978
5670
|
};
|
|
4979
5671
|
JetstreamModule = __decorateClass([
|
|
4980
5672
|
Global(),
|
|
4981
|
-
|
|
4982
|
-
__decorateParam(0,
|
|
4983
|
-
__decorateParam(0,
|
|
4984
|
-
__decorateParam(1,
|
|
4985
|
-
__decorateParam(1,
|
|
5673
|
+
Module2({}),
|
|
5674
|
+
__decorateParam(0, Optional2()),
|
|
5675
|
+
__decorateParam(0, Inject2(ShutdownManager)),
|
|
5676
|
+
__decorateParam(1, Optional2()),
|
|
5677
|
+
__decorateParam(1, Inject2(JetstreamStrategy))
|
|
4986
5678
|
], JetstreamModule);
|
|
4987
5679
|
export {
|
|
4988
5680
|
ConsumeKind,
|