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