@horizon-republic/nestjs-jetstream 2.2.0 → 2.3.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 +39 -1
- package/dist/index.cjs +2015 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +993 -0
- package/dist/index.d.ts +993 -13
- package/dist/index.js +2012 -39
- package/dist/index.js.map +1 -1
- package/package.json +29 -19
- package/dist/client/index.d.ts +0 -3
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/index.js +0 -9
- package/dist/client/index.js.map +0 -1
- package/dist/client/jetstream.client.d.ts +0 -76
- package/dist/client/jetstream.client.d.ts.map +0 -1
- package/dist/client/jetstream.client.js +0 -325
- package/dist/client/jetstream.client.js.map +0 -1
- package/dist/client/jetstream.record.d.ts +0 -55
- package/dist/client/jetstream.record.d.ts.map +0 -1
- package/dist/client/jetstream.record.js +0 -84
- package/dist/client/jetstream.record.js.map +0 -1
- package/dist/codec/index.d.ts +0 -2
- package/dist/codec/index.d.ts.map +0 -1
- package/dist/codec/index.js +0 -6
- package/dist/codec/index.js.map +0 -1
- package/dist/codec/json.codec.d.ts +0 -20
- package/dist/codec/json.codec.d.ts.map +0 -1
- package/dist/codec/json.codec.js +0 -30
- package/dist/codec/json.codec.js.map +0 -1
- package/dist/connection/connection.provider.d.ts +0 -50
- package/dist/connection/connection.provider.d.ts.map +0 -1
- package/dist/connection/connection.provider.js +0 -141
- package/dist/connection/connection.provider.js.map +0 -1
- package/dist/connection/index.d.ts +0 -2
- package/dist/connection/index.d.ts.map +0 -1
- package/dist/connection/index.js +0 -6
- package/dist/connection/index.js.map +0 -1
- package/dist/context/index.d.ts +0 -2
- package/dist/context/index.d.ts.map +0 -1
- package/dist/context/index.js +0 -6
- package/dist/context/index.js.map +0 -1
- package/dist/context/rpc.context.d.ts +0 -35
- package/dist/context/rpc.context.d.ts.map +0 -1
- package/dist/context/rpc.context.js +0 -44
- package/dist/context/rpc.context.js.map +0 -1
- package/dist/health/index.d.ts +0 -3
- package/dist/health/index.d.ts.map +0 -1
- package/dist/health/index.js +0 -6
- package/dist/health/index.js.map +0 -1
- package/dist/health/jetstream.health-indicator.d.ts +0 -47
- package/dist/health/jetstream.health-indicator.d.ts.map +0 -1
- package/dist/health/jetstream.health-indicator.js +0 -85
- package/dist/health/jetstream.health-indicator.js.map +0 -1
- package/dist/hooks/event-bus.d.ts +0 -31
- package/dist/hooks/event-bus.d.ts.map +0 -1
- package/dist/hooks/event-bus.js +0 -79
- package/dist/hooks/event-bus.js.map +0 -1
- package/dist/hooks/index.d.ts +0 -2
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/index.js +0 -6
- package/dist/hooks/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/interfaces/client.interface.d.ts +0 -14
- package/dist/interfaces/client.interface.d.ts.map +0 -1
- package/dist/interfaces/client.interface.js +0 -3
- package/dist/interfaces/client.interface.js.map +0 -1
- package/dist/interfaces/codec.interface.d.ts +0 -28
- package/dist/interfaces/codec.interface.d.ts.map +0 -1
- package/dist/interfaces/codec.interface.js +0 -3
- package/dist/interfaces/codec.interface.js.map +0 -1
- package/dist/interfaces/hooks.interface.d.ts +0 -71
- package/dist/interfaces/hooks.interface.d.ts.map +0 -1
- package/dist/interfaces/hooks.interface.js +0 -16
- package/dist/interfaces/hooks.interface.js.map +0 -1
- package/dist/interfaces/index.d.ts +0 -8
- package/dist/interfaces/index.d.ts.map +0 -1
- package/dist/interfaces/index.js +0 -6
- package/dist/interfaces/index.js.map +0 -1
- package/dist/interfaces/options.interface.d.ts +0 -142
- package/dist/interfaces/options.interface.d.ts.map +0 -1
- package/dist/interfaces/options.interface.js +0 -3
- package/dist/interfaces/options.interface.js.map +0 -1
- package/dist/interfaces/routing.interface.d.ts +0 -15
- package/dist/interfaces/routing.interface.d.ts.map +0 -1
- package/dist/interfaces/routing.interface.js +0 -3
- package/dist/interfaces/routing.interface.js.map +0 -1
- package/dist/interfaces/stream.interface.d.ts +0 -5
- package/dist/interfaces/stream.interface.d.ts.map +0 -1
- package/dist/interfaces/stream.interface.js +0 -3
- package/dist/interfaces/stream.interface.js.map +0 -1
- package/dist/jetstream.constants.d.ts +0 -58
- package/dist/jetstream.constants.d.ts.map +0 -1
- package/dist/jetstream.constants.js +0 -168
- package/dist/jetstream.constants.js.map +0 -1
- package/dist/jetstream.module.d.ts +0 -89
- package/dist/jetstream.module.d.ts.map +0 -1
- package/dist/jetstream.module.js +0 -410
- package/dist/jetstream.module.js.map +0 -1
- package/dist/server/core-rpc.server.d.ts +0 -31
- package/dist/server/core-rpc.server.d.ts.map +0 -1
- package/dist/server/core-rpc.server.js +0 -95
- package/dist/server/core-rpc.server.js.map +0 -1
- package/dist/server/index.d.ts +0 -5
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/index.js +0 -16
- package/dist/server/index.js.map +0 -1
- package/dist/server/infrastructure/consumer.provider.d.ts +0 -36
- package/dist/server/infrastructure/consumer.provider.d.ts.map +0 -1
- package/dist/server/infrastructure/consumer.provider.js +0 -123
- package/dist/server/infrastructure/consumer.provider.js.map +0 -1
- package/dist/server/infrastructure/index.d.ts +0 -4
- package/dist/server/infrastructure/index.d.ts.map +0 -1
- package/dist/server/infrastructure/index.js +0 -10
- package/dist/server/infrastructure/index.js.map +0 -1
- package/dist/server/infrastructure/message.provider.d.ts +0 -46
- package/dist/server/infrastructure/message.provider.d.ts.map +0 -1
- package/dist/server/infrastructure/message.provider.js +0 -100
- package/dist/server/infrastructure/message.provider.js.map +0 -1
- package/dist/server/infrastructure/stream.provider.d.ts +0 -38
- package/dist/server/infrastructure/stream.provider.d.ts.map +0 -1
- package/dist/server/infrastructure/stream.provider.js +0 -109
- package/dist/server/infrastructure/stream.provider.js.map +0 -1
- package/dist/server/routing/event.router.d.ts +0 -56
- package/dist/server/routing/event.router.d.ts.map +0 -1
- package/dist/server/routing/event.router.js +0 -132
- package/dist/server/routing/event.router.js.map +0 -1
- package/dist/server/routing/index.d.ts +0 -5
- package/dist/server/routing/index.d.ts.map +0 -1
- package/dist/server/routing/index.js +0 -10
- package/dist/server/routing/index.js.map +0 -1
- package/dist/server/routing/pattern-registry.d.ts +0 -39
- package/dist/server/routing/pattern-registry.d.ts.map +0 -1
- package/dist/server/routing/pattern-registry.js +0 -116
- package/dist/server/routing/pattern-registry.js.map +0 -1
- package/dist/server/routing/rpc.router.d.ts +0 -37
- package/dist/server/routing/rpc.router.d.ts.map +0 -1
- package/dist/server/routing/rpc.router.js +0 -121
- package/dist/server/routing/rpc.router.js.map +0 -1
- package/dist/server/strategy.d.ts +0 -55
- package/dist/server/strategy.d.ts.map +0 -1
- package/dist/server/strategy.js +0 -113
- package/dist/server/strategy.js.map +0 -1
- package/dist/shutdown/index.d.ts +0 -2
- package/dist/shutdown/index.d.ts.map +0 -1
- package/dist/shutdown/index.js +0 -6
- package/dist/shutdown/index.js.map +0 -1
- package/dist/shutdown/shutdown.manager.d.ts +0 -27
- package/dist/shutdown/shutdown.manager.d.ts.map +0 -1
- package/dist/shutdown/shutdown.manager.js +0 -45
- package/dist/shutdown/shutdown.manager.js.map +0 -1
- package/dist/utils/index.d.ts +0 -3
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -8
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/serialize-error.d.ts +0 -10
- package/dist/utils/serialize-error.d.ts.map +0 -1
- package/dist/utils/serialize-error.js +0 -21
- package/dist/utils/serialize-error.js.map +0 -1
- package/dist/utils/unwrap-result.d.ts +0 -15
- package/dist/utils/unwrap-result.d.ts.map +0 -1
- package/dist/utils/unwrap-result.js +0 -49
- package/dist/utils/unwrap-result.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,40 +1,2013 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
var
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
var
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
12
|
+
|
|
13
|
+
// src/jetstream.module.ts
|
|
14
|
+
import {
|
|
15
|
+
Global,
|
|
16
|
+
Inject,
|
|
17
|
+
Logger as Logger12,
|
|
18
|
+
Module,
|
|
19
|
+
Optional
|
|
20
|
+
} from "@nestjs/common";
|
|
21
|
+
|
|
22
|
+
// src/client/jetstream.client.ts
|
|
23
|
+
import { Logger } from "@nestjs/common";
|
|
24
|
+
import { ClientProxy } from "@nestjs/microservices";
|
|
25
|
+
import {
|
|
26
|
+
createInbox,
|
|
27
|
+
Events,
|
|
28
|
+
headers as natsHeaders
|
|
29
|
+
} from "nats";
|
|
30
|
+
|
|
31
|
+
// src/interfaces/hooks.interface.ts
|
|
32
|
+
var TransportEvent = /* @__PURE__ */ ((TransportEvent2) => {
|
|
33
|
+
TransportEvent2["Connect"] = "connect";
|
|
34
|
+
TransportEvent2["Disconnect"] = "disconnect";
|
|
35
|
+
TransportEvent2["Reconnect"] = "reconnect";
|
|
36
|
+
TransportEvent2["Error"] = "error";
|
|
37
|
+
TransportEvent2["RpcTimeout"] = "rpcTimeout";
|
|
38
|
+
TransportEvent2["MessageRouted"] = "messageRouted";
|
|
39
|
+
TransportEvent2["ShutdownStart"] = "shutdownStart";
|
|
40
|
+
TransportEvent2["ShutdownComplete"] = "shutdownComplete";
|
|
41
|
+
TransportEvent2["DeadLetter"] = "deadLetter";
|
|
42
|
+
return TransportEvent2;
|
|
43
|
+
})(TransportEvent || {});
|
|
44
|
+
|
|
45
|
+
// src/jetstream.constants.ts
|
|
46
|
+
import {
|
|
47
|
+
AckPolicy,
|
|
48
|
+
DeliverPolicy,
|
|
49
|
+
DiscardPolicy,
|
|
50
|
+
ReplayPolicy,
|
|
51
|
+
RetentionPolicy,
|
|
52
|
+
StorageType,
|
|
53
|
+
StoreCompression
|
|
54
|
+
} from "nats";
|
|
55
|
+
var JETSTREAM_OPTIONS = /* @__PURE__ */ Symbol("JETSTREAM_OPTIONS");
|
|
56
|
+
var JETSTREAM_CONNECTION = /* @__PURE__ */ Symbol("JETSTREAM_CONNECTION");
|
|
57
|
+
var JETSTREAM_CODEC = /* @__PURE__ */ Symbol("JETSTREAM_CODEC");
|
|
58
|
+
var JETSTREAM_EVENT_BUS = /* @__PURE__ */ Symbol("JETSTREAM_EVENT_BUS");
|
|
59
|
+
var getClientToken = (name) => name;
|
|
60
|
+
var KB = 1024;
|
|
61
|
+
var MB = 1024 * KB;
|
|
62
|
+
var GB = 1024 * MB;
|
|
63
|
+
var nanos = (ms) => ms * 1e6;
|
|
64
|
+
var baseStreamConfig = {
|
|
65
|
+
retention: RetentionPolicy.Workqueue,
|
|
66
|
+
storage: StorageType.File,
|
|
67
|
+
num_replicas: 1,
|
|
68
|
+
discard: DiscardPolicy.Old,
|
|
69
|
+
allow_direct: true,
|
|
70
|
+
compression: StoreCompression.None
|
|
71
|
+
};
|
|
72
|
+
var DEFAULT_EVENT_STREAM_CONFIG = {
|
|
73
|
+
...baseStreamConfig,
|
|
74
|
+
allow_rollup_hdrs: true,
|
|
75
|
+
max_consumers: 100,
|
|
76
|
+
max_msg_size: 10 * MB,
|
|
77
|
+
max_msgs_per_subject: 5e6,
|
|
78
|
+
max_msgs: 5e7,
|
|
79
|
+
max_bytes: 5 * GB,
|
|
80
|
+
max_age: nanos(7 * 24 * 60 * 60 * 1e3),
|
|
81
|
+
// 7 days
|
|
82
|
+
duplicate_window: nanos(2 * 60 * 1e3)
|
|
83
|
+
// 2 min
|
|
84
|
+
};
|
|
85
|
+
var DEFAULT_COMMAND_STREAM_CONFIG = {
|
|
86
|
+
...baseStreamConfig,
|
|
87
|
+
allow_rollup_hdrs: false,
|
|
88
|
+
max_consumers: 50,
|
|
89
|
+
max_msg_size: 5 * MB,
|
|
90
|
+
max_msgs_per_subject: 1e5,
|
|
91
|
+
max_msgs: 1e6,
|
|
92
|
+
max_bytes: 100 * MB,
|
|
93
|
+
max_age: nanos(3 * 60 * 1e3),
|
|
94
|
+
// 3 min
|
|
95
|
+
duplicate_window: nanos(30 * 1e3)
|
|
96
|
+
// 30s
|
|
97
|
+
};
|
|
98
|
+
var DEFAULT_BROADCAST_STREAM_CONFIG = {
|
|
99
|
+
...baseStreamConfig,
|
|
100
|
+
retention: RetentionPolicy.Limits,
|
|
101
|
+
allow_rollup_hdrs: true,
|
|
102
|
+
max_consumers: 200,
|
|
103
|
+
max_msg_size: 10 * MB,
|
|
104
|
+
max_msgs_per_subject: 1e6,
|
|
105
|
+
max_msgs: 1e7,
|
|
106
|
+
max_bytes: 2 * GB,
|
|
107
|
+
max_age: nanos(24 * 60 * 60 * 1e3),
|
|
108
|
+
// 1 day
|
|
109
|
+
duplicate_window: nanos(2 * 60 * 1e3)
|
|
110
|
+
// 2 min
|
|
111
|
+
};
|
|
112
|
+
var DEFAULT_EVENT_CONSUMER_CONFIG = {
|
|
113
|
+
ack_wait: nanos(10 * 1e3),
|
|
114
|
+
// 10s
|
|
115
|
+
max_deliver: 3,
|
|
116
|
+
max_ack_pending: 100,
|
|
117
|
+
ack_policy: AckPolicy.Explicit,
|
|
118
|
+
deliver_policy: DeliverPolicy.All,
|
|
119
|
+
replay_policy: ReplayPolicy.Instant
|
|
120
|
+
};
|
|
121
|
+
var DEFAULT_COMMAND_CONSUMER_CONFIG = {
|
|
122
|
+
ack_wait: nanos(5 * 60 * 1e3),
|
|
123
|
+
// 5 min
|
|
124
|
+
max_deliver: 1,
|
|
125
|
+
max_ack_pending: 100,
|
|
126
|
+
ack_policy: AckPolicy.Explicit,
|
|
127
|
+
deliver_policy: DeliverPolicy.All,
|
|
128
|
+
replay_policy: ReplayPolicy.Instant
|
|
129
|
+
};
|
|
130
|
+
var DEFAULT_BROADCAST_CONSUMER_CONFIG = {
|
|
131
|
+
ack_wait: nanos(10 * 1e3),
|
|
132
|
+
// 10s
|
|
133
|
+
max_deliver: 3,
|
|
134
|
+
max_ack_pending: 100,
|
|
135
|
+
ack_policy: AckPolicy.Explicit,
|
|
136
|
+
deliver_policy: DeliverPolicy.All,
|
|
137
|
+
replay_policy: ReplayPolicy.Instant
|
|
138
|
+
};
|
|
139
|
+
var DEFAULT_RPC_TIMEOUT = 3e4;
|
|
140
|
+
var DEFAULT_JETSTREAM_RPC_TIMEOUT = 18e4;
|
|
141
|
+
var DEFAULT_SHUTDOWN_TIMEOUT = 1e4;
|
|
142
|
+
var JetstreamHeader = /* @__PURE__ */ ((JetstreamHeader2) => {
|
|
143
|
+
JetstreamHeader2["CorrelationId"] = "x-correlation-id";
|
|
144
|
+
JetstreamHeader2["ReplyTo"] = "x-reply-to";
|
|
145
|
+
JetstreamHeader2["Subject"] = "x-subject";
|
|
146
|
+
JetstreamHeader2["CallerName"] = "x-caller-name";
|
|
147
|
+
JetstreamHeader2["RequestId"] = "x-request-id";
|
|
148
|
+
JetstreamHeader2["TraceId"] = "x-trace-id";
|
|
149
|
+
JetstreamHeader2["SpanId"] = "x-span-id";
|
|
150
|
+
JetstreamHeader2["Error"] = "x-error";
|
|
151
|
+
return JetstreamHeader2;
|
|
152
|
+
})(JetstreamHeader || {});
|
|
153
|
+
var RESERVED_HEADERS = /* @__PURE__ */ new Set([
|
|
154
|
+
"x-correlation-id" /* CorrelationId */,
|
|
155
|
+
"x-reply-to" /* ReplyTo */,
|
|
156
|
+
"x-error" /* Error */
|
|
157
|
+
]);
|
|
158
|
+
var internalName = (name) => `${name}__microservice`;
|
|
159
|
+
var buildSubject = (serviceName, kind, pattern) => `${internalName(serviceName)}.${kind}.${pattern}`;
|
|
160
|
+
var buildBroadcastSubject = (pattern) => `broadcast.${pattern}`;
|
|
161
|
+
var streamName = (serviceName, kind) => {
|
|
162
|
+
if (kind === "broadcast") return "broadcast-stream";
|
|
163
|
+
return `${internalName(serviceName)}_${kind}-stream`;
|
|
164
|
+
};
|
|
165
|
+
var consumerName = (serviceName, kind) => {
|
|
166
|
+
if (kind === "broadcast") return `${internalName(serviceName)}_broadcast-consumer`;
|
|
167
|
+
return `${internalName(serviceName)}_${kind}-consumer`;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// src/client/jetstream.record.ts
|
|
171
|
+
var JetstreamRecord = class {
|
|
172
|
+
constructor(data, headers2, timeout) {
|
|
173
|
+
this.data = data;
|
|
174
|
+
this.headers = headers2;
|
|
175
|
+
this.timeout = timeout;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
var JetstreamRecordBuilder = class {
|
|
179
|
+
data;
|
|
180
|
+
headers = /* @__PURE__ */ new Map();
|
|
181
|
+
timeout;
|
|
182
|
+
constructor(data) {
|
|
183
|
+
this.data = data;
|
|
184
|
+
}
|
|
185
|
+
/** Set the message payload. */
|
|
186
|
+
setData(data) {
|
|
187
|
+
this.data = data;
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Set a single custom header.
|
|
192
|
+
*
|
|
193
|
+
* @throws Error if the header name is reserved by the transport.
|
|
194
|
+
*/
|
|
195
|
+
setHeader(key, value) {
|
|
196
|
+
this.validateHeaderKey(key);
|
|
197
|
+
this.headers.set(key, value);
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Set multiple custom headers at once.
|
|
202
|
+
*
|
|
203
|
+
* @throws Error if any header name is reserved by the transport.
|
|
204
|
+
*/
|
|
205
|
+
setHeaders(headers2) {
|
|
206
|
+
for (const [key, value] of Object.entries(headers2)) {
|
|
207
|
+
this.setHeader(key, value);
|
|
208
|
+
}
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
/** Set RPC request timeout in milliseconds. */
|
|
212
|
+
setTimeout(ms) {
|
|
213
|
+
this.timeout = ms;
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
216
|
+
/** Build the immutable JetstreamRecord. */
|
|
217
|
+
build() {
|
|
218
|
+
return new JetstreamRecord(this.data, new Map(this.headers), this.timeout);
|
|
219
|
+
}
|
|
220
|
+
/** Validate that a header key is not reserved. */
|
|
221
|
+
validateHeaderKey(key) {
|
|
222
|
+
if (RESERVED_HEADERS.has(key)) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Header "${key}" is reserved by the JetStream transport and cannot be set manually. Reserved headers: ${[...RESERVED_HEADERS].join(", ")}`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// src/client/jetstream.client.ts
|
|
231
|
+
var JetstreamClient = class extends ClientProxy {
|
|
232
|
+
constructor(rootOptions, targetServiceName, connection, codec, eventBus) {
|
|
233
|
+
super();
|
|
234
|
+
this.rootOptions = rootOptions;
|
|
235
|
+
this.connection = connection;
|
|
236
|
+
this.codec = codec;
|
|
237
|
+
this.eventBus = eventBus;
|
|
238
|
+
this.targetName = targetServiceName;
|
|
239
|
+
}
|
|
240
|
+
logger = new Logger("Jetstream:Client");
|
|
241
|
+
/** Target service name this client sends messages to. */
|
|
242
|
+
targetName;
|
|
243
|
+
/** Shared inbox for JetStream-mode RPC responses. */
|
|
244
|
+
inbox = null;
|
|
245
|
+
inboxSubscription = null;
|
|
246
|
+
/** Pending JetStream-mode RPC callbacks, keyed by correlation ID. */
|
|
247
|
+
pendingMessages = /* @__PURE__ */ new Map();
|
|
248
|
+
/** Pending JetStream-mode RPC timeouts, keyed by correlation ID. */
|
|
249
|
+
pendingTimeouts = /* @__PURE__ */ new Map();
|
|
250
|
+
/** Subscription to connection status events for disconnect handling. */
|
|
251
|
+
statusSubscription = null;
|
|
252
|
+
/** Establish connection. Called automatically by NestJS on first use. */
|
|
253
|
+
async connect() {
|
|
254
|
+
const nc = await this.connection.getConnection();
|
|
255
|
+
if (this.isJetStreamRpcMode() && !this.inboxSubscription) {
|
|
256
|
+
this.setupInbox(nc);
|
|
257
|
+
}
|
|
258
|
+
this.statusSubscription ??= this.connection.status$.subscribe((status) => {
|
|
259
|
+
if (status.type === Events.Disconnect) {
|
|
260
|
+
this.handleDisconnect();
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
return nc;
|
|
264
|
+
}
|
|
265
|
+
/** Clean up resources. */
|
|
266
|
+
async close() {
|
|
267
|
+
this.statusSubscription?.unsubscribe();
|
|
268
|
+
this.statusSubscription = null;
|
|
269
|
+
this.rejectPendingRpcs(new Error("Client closed"));
|
|
270
|
+
}
|
|
271
|
+
/** Direct access to the raw NATS connection. */
|
|
272
|
+
unwrap() {
|
|
273
|
+
return this.connection.unwrap;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Publish a fire-and-forget event to JetStream.
|
|
277
|
+
*
|
|
278
|
+
* Events are published to either the workqueue stream or broadcast stream
|
|
279
|
+
* depending on the subject prefix.
|
|
280
|
+
*/
|
|
281
|
+
async dispatchEvent(packet) {
|
|
282
|
+
const nc = await this.connect();
|
|
283
|
+
const { data, hdrs } = this.extractRecordData(packet.data);
|
|
284
|
+
const subject = this.buildEventSubject(packet.pattern);
|
|
285
|
+
const msgHeaders = this.buildHeaders(hdrs, { subject });
|
|
286
|
+
await nc.jetstream().publish(subject, this.codec.encode(data), {
|
|
287
|
+
headers: msgHeaders,
|
|
288
|
+
msgID: crypto.randomUUID()
|
|
289
|
+
});
|
|
290
|
+
return void 0;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Publish an RPC command and register callback for response.
|
|
294
|
+
*
|
|
295
|
+
* Core mode: uses nc.request() with timeout.
|
|
296
|
+
* JetStream mode: publishes to stream + waits for inbox response.
|
|
297
|
+
*/
|
|
298
|
+
publish(packet, callback) {
|
|
299
|
+
const subject = buildSubject(this.targetName, "cmd", packet.pattern);
|
|
300
|
+
const { data, hdrs, timeout } = this.extractRecordData(packet.data);
|
|
301
|
+
const onUnhandled = (err) => {
|
|
302
|
+
this.logger.error("Unhandled publish error:", err);
|
|
303
|
+
callback({ err: new Error("Internal transport error"), response: null, isDisposed: true });
|
|
304
|
+
};
|
|
305
|
+
let jetStreamCorrelationId = null;
|
|
306
|
+
if (this.isCoreRpcMode()) {
|
|
307
|
+
this.publishCoreRpc(subject, data, hdrs, timeout, callback).catch(onUnhandled);
|
|
308
|
+
} else {
|
|
309
|
+
jetStreamCorrelationId = crypto.randomUUID();
|
|
310
|
+
this.publishJetStreamRpc(
|
|
311
|
+
subject,
|
|
312
|
+
data,
|
|
313
|
+
hdrs,
|
|
314
|
+
timeout,
|
|
315
|
+
callback,
|
|
316
|
+
jetStreamCorrelationId
|
|
317
|
+
).catch(onUnhandled);
|
|
318
|
+
}
|
|
319
|
+
return () => {
|
|
320
|
+
if (jetStreamCorrelationId) {
|
|
321
|
+
const timeoutId = this.pendingTimeouts.get(jetStreamCorrelationId);
|
|
322
|
+
if (timeoutId) {
|
|
323
|
+
clearTimeout(timeoutId);
|
|
324
|
+
this.pendingTimeouts.delete(jetStreamCorrelationId);
|
|
325
|
+
}
|
|
326
|
+
this.pendingMessages.delete(jetStreamCorrelationId);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/** Core mode: nc.request() with timeout. */
|
|
331
|
+
async publishCoreRpc(subject, data, customHeaders, timeout, callback) {
|
|
332
|
+
try {
|
|
333
|
+
const nc = await this.connect();
|
|
334
|
+
const effectiveTimeout = timeout ?? this.getRpcTimeout();
|
|
335
|
+
const hdrs = this.buildHeaders(customHeaders, { subject });
|
|
336
|
+
const response = await nc.request(subject, this.codec.encode(data), {
|
|
337
|
+
timeout: effectiveTimeout,
|
|
338
|
+
headers: hdrs
|
|
339
|
+
});
|
|
340
|
+
const decoded = this.codec.decode(response.data);
|
|
341
|
+
if (response.headers?.get("x-error" /* Error */)) {
|
|
342
|
+
callback({ err: decoded, response: null, isDisposed: true });
|
|
343
|
+
} else {
|
|
344
|
+
callback({ err: null, response: decoded, isDisposed: true });
|
|
345
|
+
}
|
|
346
|
+
} catch (err) {
|
|
347
|
+
const error = err instanceof Error ? err : new Error("Unknown error");
|
|
348
|
+
this.logger.error(`Core RPC error (${subject}):`, err);
|
|
349
|
+
this.eventBus.emit("error" /* Error */, error, "client-rpc");
|
|
350
|
+
callback({ err: error, response: null, isDisposed: true });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/** JetStream mode: publish to stream + wait for inbox response. */
|
|
354
|
+
async publishJetStreamRpc(subject, data, customHeaders, timeout, callback, correlationId = crypto.randomUUID()) {
|
|
355
|
+
const effectiveTimeout = timeout ?? this.getRpcTimeout();
|
|
356
|
+
this.pendingMessages.set(correlationId, callback);
|
|
357
|
+
const timeoutId = setTimeout(() => {
|
|
358
|
+
if (!this.pendingMessages.has(correlationId)) return;
|
|
359
|
+
this.pendingTimeouts.delete(correlationId);
|
|
360
|
+
this.pendingMessages.delete(correlationId);
|
|
361
|
+
this.logger.error(`JetStream RPC timeout (${effectiveTimeout}ms): ${subject}`);
|
|
362
|
+
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
|
|
363
|
+
callback({ err: new Error("RPC timeout"), response: null, isDisposed: true });
|
|
364
|
+
}, effectiveTimeout);
|
|
365
|
+
this.pendingTimeouts.set(correlationId, timeoutId);
|
|
366
|
+
try {
|
|
367
|
+
const nc = await this.connect();
|
|
368
|
+
if (!this.inbox) {
|
|
369
|
+
throw new Error("Inbox not initialized \u2014 JetStream RPC mode requires a connected inbox");
|
|
370
|
+
}
|
|
371
|
+
const hdrs = this.buildHeaders(customHeaders, {
|
|
372
|
+
subject,
|
|
373
|
+
correlationId,
|
|
374
|
+
replyTo: this.inbox
|
|
375
|
+
});
|
|
376
|
+
await nc.jetstream().publish(subject, this.codec.encode(data), {
|
|
377
|
+
headers: hdrs,
|
|
378
|
+
msgID: crypto.randomUUID()
|
|
379
|
+
});
|
|
380
|
+
} catch (err) {
|
|
381
|
+
clearTimeout(timeoutId);
|
|
382
|
+
this.pendingTimeouts.delete(correlationId);
|
|
383
|
+
if (!this.pendingMessages.has(correlationId)) return;
|
|
384
|
+
this.pendingMessages.delete(correlationId);
|
|
385
|
+
const error = err instanceof Error ? err : new Error("Unknown error");
|
|
386
|
+
this.logger.error(`JetStream RPC publish error (${subject}):`, err);
|
|
387
|
+
callback({ err: error, response: null, isDisposed: true });
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/** Fail-fast all pending JetStream RPC callbacks on connection loss. */
|
|
391
|
+
handleDisconnect() {
|
|
392
|
+
this.rejectPendingRpcs(new Error("Connection lost"));
|
|
393
|
+
this.inbox = null;
|
|
394
|
+
}
|
|
395
|
+
/** Reject all pending RPC callbacks, clear timeouts, and tear down inbox. */
|
|
396
|
+
rejectPendingRpcs(error) {
|
|
397
|
+
for (const callback of this.pendingMessages.values()) {
|
|
398
|
+
callback({ err: error, response: null, isDisposed: true });
|
|
399
|
+
}
|
|
400
|
+
for (const timeoutId of this.pendingTimeouts.values()) {
|
|
401
|
+
clearTimeout(timeoutId);
|
|
402
|
+
}
|
|
403
|
+
this.pendingMessages.clear();
|
|
404
|
+
this.pendingTimeouts.clear();
|
|
405
|
+
this.inboxSubscription?.unsubscribe();
|
|
406
|
+
this.inboxSubscription = null;
|
|
407
|
+
}
|
|
408
|
+
/** Setup shared inbox subscription for JetStream RPC responses. */
|
|
409
|
+
setupInbox(nc) {
|
|
410
|
+
this.inbox = createInbox(internalName(this.rootOptions.name));
|
|
411
|
+
this.inboxSubscription = nc.subscribe(this.inbox, {
|
|
412
|
+
callback: (err, msg) => {
|
|
413
|
+
if (err) {
|
|
414
|
+
this.logger.error("Inbox subscription error:", err);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
this.routeInboxReply(msg);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
this.logger.debug(`Inbox subscription: ${this.inbox}`);
|
|
421
|
+
}
|
|
422
|
+
/** Route an inbox reply to the matching pending callback. */
|
|
423
|
+
routeInboxReply(msg) {
|
|
424
|
+
const correlationId = msg.headers?.get("x-correlation-id" /* CorrelationId */);
|
|
425
|
+
if (!correlationId) {
|
|
426
|
+
this.logger.warn("Inbox reply without correlation-id, ignoring");
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const callback = this.pendingMessages.get(correlationId);
|
|
430
|
+
if (!callback) {
|
|
431
|
+
this.logger.warn(`No pending handler for correlation-id: ${correlationId}`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const timeoutId = this.pendingTimeouts.get(correlationId);
|
|
435
|
+
if (timeoutId) {
|
|
436
|
+
clearTimeout(timeoutId);
|
|
437
|
+
this.pendingTimeouts.delete(correlationId);
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
const decoded = this.codec.decode(msg.data);
|
|
441
|
+
if (msg.headers?.get("x-error" /* Error */)) {
|
|
442
|
+
callback({ err: decoded, response: null, isDisposed: true });
|
|
443
|
+
} else {
|
|
444
|
+
callback({ err: null, response: decoded, isDisposed: true });
|
|
445
|
+
}
|
|
446
|
+
} catch (err) {
|
|
447
|
+
callback({
|
|
448
|
+
err: err instanceof Error ? err : new Error("Decode error"),
|
|
449
|
+
response: null,
|
|
450
|
+
isDisposed: true
|
|
451
|
+
});
|
|
452
|
+
} finally {
|
|
453
|
+
this.pendingMessages.delete(correlationId);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/** Build event subject — workqueue or broadcast. */
|
|
457
|
+
buildEventSubject(pattern) {
|
|
458
|
+
if (pattern.startsWith("broadcast:")) {
|
|
459
|
+
return buildBroadcastSubject(pattern.slice("broadcast:".length));
|
|
460
|
+
}
|
|
461
|
+
return buildSubject(this.targetName, "ev", pattern);
|
|
462
|
+
}
|
|
463
|
+
/** Build NATS headers merging custom headers with transport headers. */
|
|
464
|
+
buildHeaders(customHeaders, transport) {
|
|
465
|
+
const hdrs = natsHeaders();
|
|
466
|
+
hdrs.set("x-subject" /* Subject */, transport.subject);
|
|
467
|
+
hdrs.set("x-caller-name" /* CallerName */, internalName(this.rootOptions.name));
|
|
468
|
+
if (transport.correlationId) {
|
|
469
|
+
hdrs.set("x-correlation-id" /* CorrelationId */, transport.correlationId);
|
|
470
|
+
}
|
|
471
|
+
if (transport.replyTo) {
|
|
472
|
+
hdrs.set("x-reply-to" /* ReplyTo */, transport.replyTo);
|
|
473
|
+
}
|
|
474
|
+
if (customHeaders) {
|
|
475
|
+
for (const [key, value] of customHeaders) {
|
|
476
|
+
hdrs.set(key, value);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return hdrs;
|
|
480
|
+
}
|
|
481
|
+
/** Extract data, headers, and timeout from raw packet data or JetstreamRecord. */
|
|
482
|
+
extractRecordData(rawData) {
|
|
483
|
+
if (rawData instanceof JetstreamRecord) {
|
|
484
|
+
return {
|
|
485
|
+
data: rawData.data,
|
|
486
|
+
hdrs: rawData.headers.size > 0 ? new Map(rawData.headers) : null,
|
|
487
|
+
timeout: rawData.timeout
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
return { data: rawData, hdrs: null, timeout: void 0 };
|
|
491
|
+
}
|
|
492
|
+
isCoreRpcMode() {
|
|
493
|
+
return !this.rootOptions.rpc || this.rootOptions.rpc.mode === "core";
|
|
494
|
+
}
|
|
495
|
+
isJetStreamRpcMode() {
|
|
496
|
+
return this.rootOptions.rpc?.mode === "jetstream";
|
|
497
|
+
}
|
|
498
|
+
getRpcTimeout() {
|
|
499
|
+
if (!this.rootOptions.rpc) return DEFAULT_RPC_TIMEOUT;
|
|
500
|
+
const defaultTimeout = this.isJetStreamRpcMode() ? DEFAULT_JETSTREAM_RPC_TIMEOUT : DEFAULT_RPC_TIMEOUT;
|
|
501
|
+
return this.rootOptions.rpc.timeout ?? defaultTimeout;
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// src/codec/json.codec.ts
|
|
506
|
+
import { JSONCodec as NatsJSONCodec } from "nats";
|
|
507
|
+
var JsonCodec = class {
|
|
508
|
+
inner = NatsJSONCodec();
|
|
509
|
+
encode(data) {
|
|
510
|
+
return this.inner.encode(data);
|
|
511
|
+
}
|
|
512
|
+
decode(data) {
|
|
513
|
+
return this.inner.decode(data);
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
// src/connection/connection.provider.ts
|
|
518
|
+
import { Logger as Logger2 } from "@nestjs/common";
|
|
519
|
+
import {
|
|
520
|
+
connect,
|
|
521
|
+
DebugEvents,
|
|
522
|
+
Events as Events2
|
|
523
|
+
} from "nats";
|
|
524
|
+
import { defer, from, share, shareReplay, switchMap } from "rxjs";
|
|
525
|
+
var ConnectionProvider = class {
|
|
526
|
+
constructor(options, eventBus) {
|
|
527
|
+
this.options = options;
|
|
528
|
+
this.eventBus = eventBus;
|
|
529
|
+
this.nc$ = defer(() => this.getConnection()).pipe(
|
|
530
|
+
shareReplay({ bufferSize: 1, refCount: false })
|
|
531
|
+
);
|
|
532
|
+
this.status$ = this.nc$.pipe(
|
|
533
|
+
switchMap((nc) => from(nc.status())),
|
|
534
|
+
share()
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
/** Cached observable that replays the established connection to new subscribers. */
|
|
538
|
+
nc$;
|
|
539
|
+
/** Live stream of connection status events (no replay). */
|
|
540
|
+
status$;
|
|
541
|
+
logger = new Logger2("Jetstream:Connection");
|
|
542
|
+
connection = null;
|
|
543
|
+
connectionPromise = null;
|
|
544
|
+
jsmInstance = null;
|
|
545
|
+
/**
|
|
546
|
+
* Establish NATS connection. Idempotent — returns cached connection on subsequent calls.
|
|
547
|
+
*
|
|
548
|
+
* @throws Error if connection is refused (fail fast).
|
|
549
|
+
*/
|
|
550
|
+
async getConnection() {
|
|
551
|
+
if (this.connection && !this.connection.isClosed()) {
|
|
552
|
+
return this.connection;
|
|
553
|
+
}
|
|
554
|
+
if (this.connectionPromise) {
|
|
555
|
+
return this.connectionPromise;
|
|
556
|
+
}
|
|
557
|
+
this.connectionPromise = this.establish();
|
|
558
|
+
return this.connectionPromise;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Get JetStream manager. Cached after first call.
|
|
562
|
+
*/
|
|
563
|
+
async getJetStreamManager() {
|
|
564
|
+
if (this.jsmInstance) return this.jsmInstance;
|
|
565
|
+
const nc = await this.getConnection();
|
|
566
|
+
this.jsmInstance = await nc.jetstreamManager();
|
|
567
|
+
this.logger.log("JetStream manager initialized");
|
|
568
|
+
return this.jsmInstance;
|
|
569
|
+
}
|
|
570
|
+
/** Direct access to the raw NATS connection (assumes already connected). */
|
|
571
|
+
get unwrap() {
|
|
572
|
+
return this.connection;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Gracefully shut down the connection.
|
|
576
|
+
*
|
|
577
|
+
* Sequence: drain → wait for close. Falls back to force-close on error.
|
|
578
|
+
*/
|
|
579
|
+
async shutdown() {
|
|
580
|
+
if (!this.connection || this.connection.isClosed()) return;
|
|
581
|
+
try {
|
|
582
|
+
await this.connection.drain();
|
|
583
|
+
await this.connection.closed();
|
|
584
|
+
} catch {
|
|
585
|
+
try {
|
|
586
|
+
await this.connection.close();
|
|
587
|
+
} catch {
|
|
588
|
+
}
|
|
589
|
+
} finally {
|
|
590
|
+
this.connection = null;
|
|
591
|
+
this.connectionPromise = null;
|
|
592
|
+
this.jsmInstance = null;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
/** Internal: establish the physical connection with reconnect monitoring. */
|
|
596
|
+
async establish() {
|
|
597
|
+
const name = internalName(this.options.name);
|
|
598
|
+
try {
|
|
599
|
+
const nc = await connect({
|
|
600
|
+
...this.options.connectionOptions,
|
|
601
|
+
servers: this.options.servers,
|
|
602
|
+
name
|
|
603
|
+
});
|
|
604
|
+
this.connection = nc;
|
|
605
|
+
this.logger.log(`NATS connection established: ${nc.getServer()}`);
|
|
606
|
+
this.eventBus.emit("connect" /* Connect */, nc.getServer());
|
|
607
|
+
this.monitorStatus(nc);
|
|
608
|
+
return nc;
|
|
609
|
+
} catch (err) {
|
|
610
|
+
const natsErr = err;
|
|
611
|
+
if (natsErr.code === "CONNECTION_REFUSED") {
|
|
612
|
+
throw new Error(`NATS connection refused: ${this.options.servers.join(", ")}`);
|
|
613
|
+
}
|
|
614
|
+
throw err;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
/** Subscribe to connection status events and emit hooks. */
|
|
618
|
+
monitorStatus(nc) {
|
|
619
|
+
(async () => {
|
|
620
|
+
for await (const status of nc.status()) {
|
|
621
|
+
switch (status.type) {
|
|
622
|
+
case Events2.Disconnect:
|
|
623
|
+
this.eventBus.emit("disconnect" /* Disconnect */);
|
|
624
|
+
break;
|
|
625
|
+
case Events2.Reconnect:
|
|
626
|
+
this.jsmInstance = null;
|
|
627
|
+
this.eventBus.emit("reconnect" /* Reconnect */, nc.getServer());
|
|
628
|
+
break;
|
|
629
|
+
case Events2.Error:
|
|
630
|
+
this.eventBus.emit("error" /* Error */, new Error(String(status.data)), "connection");
|
|
631
|
+
break;
|
|
632
|
+
case Events2.Update:
|
|
633
|
+
case Events2.LDM:
|
|
634
|
+
case DebugEvents.Reconnecting:
|
|
635
|
+
case DebugEvents.PingTimer:
|
|
636
|
+
case DebugEvents.StaleConnection:
|
|
637
|
+
case DebugEvents.ClientInitiatedReconnect:
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
})().catch((err) => {
|
|
642
|
+
this.logger.error("Status monitor error", err);
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
// src/hooks/event-bus.ts
|
|
648
|
+
var EventBus = class {
|
|
649
|
+
hooks;
|
|
650
|
+
logger;
|
|
651
|
+
constructor(logger, hooks) {
|
|
652
|
+
this.logger = logger;
|
|
653
|
+
this.hooks = hooks ?? {};
|
|
654
|
+
}
|
|
655
|
+
/** Emit a lifecycle event. Dispatches to custom hook or Logger fallback. */
|
|
656
|
+
emit(event, ...args) {
|
|
657
|
+
const hook = this.hooks[event];
|
|
658
|
+
if (hook) {
|
|
659
|
+
try {
|
|
660
|
+
hook(...args);
|
|
661
|
+
} catch (err) {
|
|
662
|
+
this.logger.error(
|
|
663
|
+
`Hook "${event}" threw an error: ${err instanceof Error ? err.message : err}`
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
this.defaultHandler(event, args);
|
|
669
|
+
}
|
|
670
|
+
/** Default Logger-based handlers for each event type. */
|
|
671
|
+
defaultHandler(event, args) {
|
|
672
|
+
switch (event) {
|
|
673
|
+
case "connect" /* Connect */:
|
|
674
|
+
this.logger.log(`Connected to NATS: ${args[0]}`);
|
|
675
|
+
break;
|
|
676
|
+
case "disconnect" /* Disconnect */:
|
|
677
|
+
this.logger.warn("NATS connection lost");
|
|
678
|
+
break;
|
|
679
|
+
case "reconnect" /* Reconnect */:
|
|
680
|
+
this.logger.log(`Reconnected to NATS: ${args[0]}`);
|
|
681
|
+
break;
|
|
682
|
+
case "error" /* Error */:
|
|
683
|
+
this.logger.error(`Transport error: ${args[0]}`, args[1] ?? "");
|
|
684
|
+
break;
|
|
685
|
+
case "rpcTimeout" /* RpcTimeout */:
|
|
686
|
+
this.logger.warn(`RPC timeout: ${args[0]} (cid: ${args[1]})`);
|
|
687
|
+
break;
|
|
688
|
+
case "messageRouted" /* MessageRouted */:
|
|
689
|
+
this.logger.debug(`Message routed: ${args[0]} [${args[1]}]`);
|
|
690
|
+
break;
|
|
691
|
+
case "shutdownStart" /* ShutdownStart */:
|
|
692
|
+
this.logger.log("Graceful shutdown initiated");
|
|
693
|
+
break;
|
|
694
|
+
case "shutdownComplete" /* ShutdownComplete */:
|
|
695
|
+
this.logger.log("Graceful shutdown complete");
|
|
696
|
+
break;
|
|
697
|
+
case "deadLetter" /* DeadLetter */: {
|
|
698
|
+
const info = args[0];
|
|
699
|
+
this.logger.warn(`Dead letter: ${info?.subject ?? "unknown"}`);
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
// src/health/jetstream.health-indicator.ts
|
|
707
|
+
import { Injectable, Logger as Logger3 } from "@nestjs/common";
|
|
708
|
+
var JetstreamHealthIndicator = class {
|
|
709
|
+
constructor(connection) {
|
|
710
|
+
this.connection = connection;
|
|
711
|
+
}
|
|
712
|
+
logger = new Logger3("Jetstream:Health");
|
|
713
|
+
/**
|
|
714
|
+
* Plain health status check.
|
|
715
|
+
*
|
|
716
|
+
* Returns the current connection status without throwing.
|
|
717
|
+
* Use this for custom health endpoints or monitoring integrations.
|
|
718
|
+
*/
|
|
719
|
+
async check() {
|
|
720
|
+
const nc = this.connection.unwrap;
|
|
721
|
+
if (!nc || nc.isClosed()) {
|
|
722
|
+
return { connected: false, server: null, latency: null };
|
|
723
|
+
}
|
|
724
|
+
try {
|
|
725
|
+
const start = performance.now();
|
|
726
|
+
await nc.rtt();
|
|
727
|
+
const latency = Math.round(performance.now() - start);
|
|
728
|
+
return { connected: true, server: nc.getServer(), latency };
|
|
729
|
+
} catch (err) {
|
|
730
|
+
this.logger.warn(`Health check failed: ${err instanceof Error ? err.message : err}`);
|
|
731
|
+
return { connected: false, server: nc.getServer(), latency: null };
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Terminus-compatible health check.
|
|
736
|
+
*
|
|
737
|
+
* Returns `{ [key]: { status: 'up', ... } }` on success.
|
|
738
|
+
* Throws an error with `{ [key]: { status: 'down', ... } }` on failure.
|
|
739
|
+
*
|
|
740
|
+
* @param key Health indicator key (default: 'jetstream')
|
|
741
|
+
*/
|
|
742
|
+
async isHealthy(key = "jetstream") {
|
|
743
|
+
const status = await this.check();
|
|
744
|
+
const details = {
|
|
745
|
+
status: status.connected ? "up" : "down",
|
|
746
|
+
server: status.server,
|
|
747
|
+
latency: status.latency
|
|
748
|
+
};
|
|
749
|
+
if (!status.connected) {
|
|
750
|
+
throw Object.assign(new Error("Jetstream health check failed"), {
|
|
751
|
+
[key]: details
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
return { [key]: details };
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
JetstreamHealthIndicator = __decorateClass([
|
|
758
|
+
Injectable()
|
|
759
|
+
], JetstreamHealthIndicator);
|
|
760
|
+
|
|
761
|
+
// src/server/strategy.ts
|
|
762
|
+
import { Server } from "@nestjs/microservices";
|
|
763
|
+
var JetstreamStrategy = class extends Server {
|
|
764
|
+
constructor(options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer) {
|
|
765
|
+
super();
|
|
766
|
+
this.options = options;
|
|
767
|
+
this.connection = connection;
|
|
768
|
+
this.patternRegistry = patternRegistry;
|
|
769
|
+
this.streamProvider = streamProvider;
|
|
770
|
+
this.consumerProvider = consumerProvider;
|
|
771
|
+
this.messageProvider = messageProvider;
|
|
772
|
+
this.eventRouter = eventRouter;
|
|
773
|
+
this.rpcRouter = rpcRouter;
|
|
774
|
+
this.coreRpcServer = coreRpcServer;
|
|
775
|
+
}
|
|
776
|
+
transportId = /* @__PURE__ */ Symbol("jetstream-transport");
|
|
777
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
778
|
+
listeners = /* @__PURE__ */ new Map();
|
|
779
|
+
/**
|
|
780
|
+
* Start the transport: register handlers, create infrastructure, begin consumption.
|
|
781
|
+
*
|
|
782
|
+
* Called by NestJS when `connectMicroservice()` is used, or internally by the module.
|
|
783
|
+
*/
|
|
784
|
+
async listen(callback) {
|
|
785
|
+
this.patternRegistry.registerHandlers(this.getHandlers());
|
|
786
|
+
const streamKinds = this.resolveStreamKinds();
|
|
787
|
+
if (streamKinds.length > 0) {
|
|
788
|
+
await this.streamProvider.ensureStreams(streamKinds);
|
|
789
|
+
const consumers = await this.consumerProvider.ensureConsumers(streamKinds);
|
|
790
|
+
this.messageProvider.start(consumers);
|
|
791
|
+
if (this.patternRegistry.hasEventHandlers() || this.patternRegistry.hasBroadcastHandlers()) {
|
|
792
|
+
this.eventRouter.start();
|
|
793
|
+
}
|
|
794
|
+
if (this.isJetStreamRpcMode() && this.patternRegistry.hasRpcHandlers()) {
|
|
795
|
+
this.rpcRouter.start();
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (this.isCoreRpcMode() && this.patternRegistry.hasRpcHandlers()) {
|
|
799
|
+
await this.coreRpcServer.start();
|
|
800
|
+
}
|
|
801
|
+
callback();
|
|
802
|
+
}
|
|
803
|
+
/** Gracefully stop the transport. */
|
|
804
|
+
close() {
|
|
805
|
+
this.eventRouter.destroy();
|
|
806
|
+
this.rpcRouter.destroy();
|
|
807
|
+
this.coreRpcServer.stop();
|
|
808
|
+
this.messageProvider.destroy();
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Register event listener (required by Server base class).
|
|
812
|
+
*
|
|
813
|
+
* Stores callbacks for potential use. Primary lifecycle events
|
|
814
|
+
* are routed through EventBus.
|
|
815
|
+
*/
|
|
816
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
817
|
+
on(event, callback) {
|
|
818
|
+
const existing = this.listeners.get(event) ?? [];
|
|
819
|
+
existing.push(callback);
|
|
820
|
+
this.listeners.set(event, existing);
|
|
821
|
+
}
|
|
822
|
+
/** Unwrap the underlying NATS connection. */
|
|
823
|
+
unwrap() {
|
|
824
|
+
return this.connection.unwrap;
|
|
825
|
+
}
|
|
826
|
+
/** Access the pattern registry (for module-level introspection). */
|
|
827
|
+
getPatternRegistry() {
|
|
828
|
+
return this.patternRegistry;
|
|
829
|
+
}
|
|
830
|
+
/** Determine which JetStream stream kinds are needed. */
|
|
831
|
+
resolveStreamKinds() {
|
|
832
|
+
const kinds = [];
|
|
833
|
+
if (this.patternRegistry.hasEventHandlers()) {
|
|
834
|
+
kinds.push("ev");
|
|
835
|
+
}
|
|
836
|
+
if (this.isJetStreamRpcMode() && this.patternRegistry.hasRpcHandlers()) {
|
|
837
|
+
kinds.push("cmd");
|
|
838
|
+
}
|
|
839
|
+
if (this.patternRegistry.hasBroadcastHandlers()) {
|
|
840
|
+
kinds.push("broadcast");
|
|
841
|
+
}
|
|
842
|
+
return kinds;
|
|
843
|
+
}
|
|
844
|
+
isCoreRpcMode() {
|
|
845
|
+
return !this.options.rpc || this.options.rpc.mode === "core";
|
|
846
|
+
}
|
|
847
|
+
isJetStreamRpcMode() {
|
|
848
|
+
return this.options.rpc?.mode === "jetstream";
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
// src/server/core-rpc.server.ts
|
|
853
|
+
import { Logger as Logger4 } from "@nestjs/common";
|
|
854
|
+
import { headers as natsHeaders2 } from "nats";
|
|
855
|
+
|
|
856
|
+
// src/context/rpc.context.ts
|
|
857
|
+
import { BaseRpcContext } from "@nestjs/microservices";
|
|
858
|
+
var RpcContext = class extends BaseRpcContext {
|
|
859
|
+
/** Get the underlying NATS message (JsMsg for JetStream, Msg for Core). */
|
|
860
|
+
getMessage() {
|
|
861
|
+
return this.args[0];
|
|
862
|
+
}
|
|
863
|
+
/** Get the NATS subject this message was published to. */
|
|
864
|
+
getSubject() {
|
|
865
|
+
return this.args[0].subject;
|
|
866
|
+
}
|
|
867
|
+
/** Get all NATS message headers, or undefined if none are present. */
|
|
868
|
+
getHeaders() {
|
|
869
|
+
return this.args[0].headers;
|
|
870
|
+
}
|
|
871
|
+
/** Get a single header value by key. Returns undefined if the header or headers object is missing. */
|
|
872
|
+
getHeader(key) {
|
|
873
|
+
return this.args[0].headers?.get(key);
|
|
874
|
+
}
|
|
875
|
+
/** Type guard: narrows getMessage() return type to JsMsg when true. */
|
|
876
|
+
isJetStream() {
|
|
877
|
+
return "ack" in this.args[0];
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
// src/utils/serialize-error.ts
|
|
882
|
+
import { RpcException } from "@nestjs/microservices";
|
|
883
|
+
var serializeError = (err) => {
|
|
884
|
+
if (err instanceof RpcException) return err.getError();
|
|
885
|
+
if (err instanceof Error) return { message: err.message };
|
|
886
|
+
return err;
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
// src/utils/unwrap-result.ts
|
|
890
|
+
import { isObservable } from "rxjs";
|
|
891
|
+
var unwrapResult = async (result) => {
|
|
892
|
+
if (isObservable(result)) {
|
|
893
|
+
return subscribeToFirst(result);
|
|
894
|
+
}
|
|
895
|
+
const resolved = await result;
|
|
896
|
+
if (isObservable(resolved)) {
|
|
897
|
+
return subscribeToFirst(resolved);
|
|
898
|
+
}
|
|
899
|
+
return resolved;
|
|
900
|
+
};
|
|
901
|
+
var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
|
|
902
|
+
let done = false;
|
|
903
|
+
obs.subscribe({
|
|
904
|
+
next: (val) => {
|
|
905
|
+
if (!done) {
|
|
906
|
+
done = true;
|
|
907
|
+
resolve(val);
|
|
908
|
+
}
|
|
909
|
+
},
|
|
910
|
+
error: reject,
|
|
911
|
+
complete: () => {
|
|
912
|
+
if (!done) resolve(void 0);
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
// src/server/core-rpc.server.ts
|
|
918
|
+
var CoreRpcServer = class {
|
|
919
|
+
constructor(options, connection, patternRegistry, codec, eventBus) {
|
|
920
|
+
this.options = options;
|
|
921
|
+
this.connection = connection;
|
|
922
|
+
this.patternRegistry = patternRegistry;
|
|
923
|
+
this.codec = codec;
|
|
924
|
+
this.eventBus = eventBus;
|
|
925
|
+
}
|
|
926
|
+
logger = new Logger4("Jetstream:CoreRpc");
|
|
927
|
+
subscription = null;
|
|
928
|
+
/** Start listening for RPC requests on the command subject. */
|
|
929
|
+
async start() {
|
|
930
|
+
const nc = await this.connection.getConnection();
|
|
931
|
+
const serviceName = internalName(this.options.name);
|
|
932
|
+
const subject = `${serviceName}.cmd.>`;
|
|
933
|
+
const queue = `${serviceName}_cmd_queue`;
|
|
934
|
+
this.subscription = nc.subscribe(subject, {
|
|
935
|
+
queue,
|
|
936
|
+
callback: (err, msg) => {
|
|
937
|
+
if (err) {
|
|
938
|
+
this.logger.error("Core RPC subscription error:", err);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
this.handleRequest(msg).catch((err2) => {
|
|
942
|
+
this.logger.error("Unhandled request error:", err2);
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
this.logger.log(`Core RPC listening: ${subject} (queue: ${queue})`);
|
|
947
|
+
}
|
|
948
|
+
/** Stop listening and clean up the subscription. */
|
|
949
|
+
stop() {
|
|
950
|
+
if (this.subscription) {
|
|
951
|
+
this.subscription.unsubscribe();
|
|
952
|
+
this.subscription = null;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
/** Handle an incoming Core NATS request. */
|
|
956
|
+
async handleRequest(msg) {
|
|
957
|
+
const handler = this.patternRegistry.getHandler(msg.subject);
|
|
958
|
+
if (!handler) {
|
|
959
|
+
this.logger.warn(`No handler for Core RPC: ${msg.subject}`);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc");
|
|
963
|
+
let data;
|
|
964
|
+
try {
|
|
965
|
+
data = this.codec.decode(msg.data);
|
|
966
|
+
} catch (err) {
|
|
967
|
+
this.logger.error(`Decode error for Core RPC ${msg.subject}:`, err);
|
|
968
|
+
this.respondWithError(msg, err);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
const ctx = new RpcContext([msg]);
|
|
972
|
+
try {
|
|
973
|
+
const result = await unwrapResult(handler(data, ctx));
|
|
974
|
+
msg.respond(this.codec.encode(result));
|
|
975
|
+
} catch (err) {
|
|
976
|
+
this.logger.error(`Handler error for Core RPC ${msg.subject}:`, err);
|
|
977
|
+
this.respondWithError(msg, err);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
/** Send an error response back to the caller with x-error header. */
|
|
981
|
+
respondWithError(msg, error) {
|
|
982
|
+
try {
|
|
983
|
+
const hdrs = natsHeaders2();
|
|
984
|
+
hdrs.set("x-error" /* Error */, "true");
|
|
985
|
+
msg.respond(this.codec.encode(serializeError(error)), { headers: hdrs });
|
|
986
|
+
} catch {
|
|
987
|
+
this.logger.error("Failed to encode error response");
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
|
|
992
|
+
// src/server/infrastructure/stream.provider.ts
|
|
993
|
+
import { Logger as Logger5 } from "@nestjs/common";
|
|
994
|
+
var STREAM_NOT_FOUND = 10059;
|
|
995
|
+
var StreamProvider = class {
|
|
996
|
+
constructor(options, connection) {
|
|
997
|
+
this.options = options;
|
|
998
|
+
this.connection = connection;
|
|
999
|
+
}
|
|
1000
|
+
logger = new Logger5("Jetstream:Stream");
|
|
1001
|
+
/**
|
|
1002
|
+
* Ensure all required streams exist with correct configuration.
|
|
1003
|
+
*
|
|
1004
|
+
* @param kinds Which stream kinds to create. Determined by the module based
|
|
1005
|
+
* on RPC mode and registered handler patterns.
|
|
1006
|
+
*/
|
|
1007
|
+
async ensureStreams(kinds) {
|
|
1008
|
+
const jsm = await this.connection.getJetStreamManager();
|
|
1009
|
+
await Promise.all(kinds.map((kind) => this.ensureStream(jsm, kind)));
|
|
1010
|
+
}
|
|
1011
|
+
/** Get the stream name for a given kind. */
|
|
1012
|
+
getStreamName(kind) {
|
|
1013
|
+
return streamName(this.options.name, kind);
|
|
1014
|
+
}
|
|
1015
|
+
/** Get the subjects pattern for a given kind. */
|
|
1016
|
+
getSubjects(kind) {
|
|
1017
|
+
const name = internalName(this.options.name);
|
|
1018
|
+
switch (kind) {
|
|
1019
|
+
case "ev":
|
|
1020
|
+
return [`${name}.ev.>`];
|
|
1021
|
+
case "cmd":
|
|
1022
|
+
return [`${name}.cmd.>`];
|
|
1023
|
+
case "broadcast":
|
|
1024
|
+
return ["broadcast.>"];
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
/** Ensure a single stream exists, creating or updating as needed. */
|
|
1028
|
+
async ensureStream(jsm, kind) {
|
|
1029
|
+
const config = this.buildConfig(kind);
|
|
1030
|
+
this.logger.log(`Ensuring stream: ${config.name}`);
|
|
1031
|
+
try {
|
|
1032
|
+
await jsm.streams.info(config.name);
|
|
1033
|
+
this.logger.debug(`Stream exists, updating: ${config.name}`);
|
|
1034
|
+
return await jsm.streams.update(config.name, config);
|
|
1035
|
+
} catch (err) {
|
|
1036
|
+
const natsErr = err;
|
|
1037
|
+
if (natsErr.api_error?.err_code === STREAM_NOT_FOUND) {
|
|
1038
|
+
this.logger.log(`Creating stream: ${config.name}`);
|
|
1039
|
+
return await jsm.streams.add(config);
|
|
1040
|
+
}
|
|
1041
|
+
throw err;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
/** Build the full stream config by merging defaults with user overrides. */
|
|
1045
|
+
buildConfig(kind) {
|
|
1046
|
+
const name = this.getStreamName(kind);
|
|
1047
|
+
const subjects = this.getSubjects(kind);
|
|
1048
|
+
const description = `JetStream ${kind} stream for ${this.options.name}`;
|
|
1049
|
+
const defaults = this.getDefaults(kind);
|
|
1050
|
+
const overrides = this.getOverrides(kind);
|
|
1051
|
+
return {
|
|
1052
|
+
...defaults,
|
|
1053
|
+
...overrides,
|
|
1054
|
+
name,
|
|
1055
|
+
subjects,
|
|
1056
|
+
description
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
/** Get default config for a stream kind. */
|
|
1060
|
+
getDefaults(kind) {
|
|
1061
|
+
switch (kind) {
|
|
1062
|
+
case "ev":
|
|
1063
|
+
return DEFAULT_EVENT_STREAM_CONFIG;
|
|
1064
|
+
case "cmd":
|
|
1065
|
+
return DEFAULT_COMMAND_STREAM_CONFIG;
|
|
1066
|
+
case "broadcast":
|
|
1067
|
+
return DEFAULT_BROADCAST_STREAM_CONFIG;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
/** Get user-provided overrides for a stream kind. */
|
|
1071
|
+
getOverrides(kind) {
|
|
1072
|
+
switch (kind) {
|
|
1073
|
+
case "ev":
|
|
1074
|
+
return this.options.events?.stream ?? {};
|
|
1075
|
+
case "cmd":
|
|
1076
|
+
return this.options.rpc?.mode === "jetstream" ? this.options.rpc.stream ?? {} : {};
|
|
1077
|
+
case "broadcast":
|
|
1078
|
+
return this.options.broadcast?.stream ?? {};
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
// src/server/infrastructure/consumer.provider.ts
|
|
1084
|
+
import { Logger as Logger6 } from "@nestjs/common";
|
|
1085
|
+
var CONSUMER_NOT_FOUND = 10014;
|
|
1086
|
+
var ConsumerProvider = class {
|
|
1087
|
+
constructor(options, connection, streamProvider, patternRegistry) {
|
|
1088
|
+
this.options = options;
|
|
1089
|
+
this.connection = connection;
|
|
1090
|
+
this.streamProvider = streamProvider;
|
|
1091
|
+
this.patternRegistry = patternRegistry;
|
|
1092
|
+
}
|
|
1093
|
+
logger = new Logger6("Jetstream:Consumer");
|
|
1094
|
+
/**
|
|
1095
|
+
* Ensure consumers exist for the specified kinds.
|
|
1096
|
+
*
|
|
1097
|
+
* @returns Map of kind -> ConsumerInfo for downstream use.
|
|
1098
|
+
*/
|
|
1099
|
+
async ensureConsumers(kinds) {
|
|
1100
|
+
const jsm = await this.connection.getJetStreamManager();
|
|
1101
|
+
const results = /* @__PURE__ */ new Map();
|
|
1102
|
+
await Promise.all(
|
|
1103
|
+
kinds.map(async (kind) => {
|
|
1104
|
+
const info = await this.ensureConsumer(jsm, kind);
|
|
1105
|
+
results.set(kind, info);
|
|
1106
|
+
})
|
|
1107
|
+
);
|
|
1108
|
+
return results;
|
|
1109
|
+
}
|
|
1110
|
+
/** Get the consumer name for a given kind. */
|
|
1111
|
+
getConsumerName(kind) {
|
|
1112
|
+
return consumerName(this.options.name, kind);
|
|
1113
|
+
}
|
|
1114
|
+
/** Ensure a single consumer exists, creating if needed. */
|
|
1115
|
+
async ensureConsumer(jsm, kind) {
|
|
1116
|
+
const stream = this.streamProvider.getStreamName(kind);
|
|
1117
|
+
const config = this.buildConfig(kind);
|
|
1118
|
+
const name = config.durable_name;
|
|
1119
|
+
this.logger.log(`Ensuring consumer: ${name} on stream: ${stream}`);
|
|
1120
|
+
try {
|
|
1121
|
+
const info = await jsm.consumers.info(stream, name);
|
|
1122
|
+
this.logger.debug(`Consumer exists: ${name}`);
|
|
1123
|
+
return info;
|
|
1124
|
+
} catch (err) {
|
|
1125
|
+
const natsErr = err;
|
|
1126
|
+
if (natsErr.api_error?.err_code === CONSUMER_NOT_FOUND) {
|
|
1127
|
+
this.logger.log(`Creating consumer: ${name}`);
|
|
1128
|
+
return await jsm.consumers.add(stream, config);
|
|
1129
|
+
}
|
|
1130
|
+
throw err;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
/** Build consumer config by merging defaults with user overrides. */
|
|
1134
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention -- NATS API uses snake_case
|
|
1135
|
+
buildConfig(kind) {
|
|
1136
|
+
const name = this.getConsumerName(kind);
|
|
1137
|
+
const serviceName = internalName(this.options.name);
|
|
1138
|
+
const defaults = this.getDefaults(kind);
|
|
1139
|
+
const overrides = this.getOverrides(kind);
|
|
1140
|
+
if (kind === "broadcast") {
|
|
1141
|
+
const broadcastPatterns = this.patternRegistry.getBroadcastPatterns();
|
|
1142
|
+
if (broadcastPatterns.length === 1) {
|
|
1143
|
+
return {
|
|
1144
|
+
...defaults,
|
|
1145
|
+
...overrides,
|
|
1146
|
+
name,
|
|
1147
|
+
durable_name: name,
|
|
1148
|
+
filter_subject: broadcastPatterns[0]
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
return {
|
|
1152
|
+
...defaults,
|
|
1153
|
+
...overrides,
|
|
1154
|
+
name,
|
|
1155
|
+
durable_name: name,
|
|
1156
|
+
filter_subjects: broadcastPatterns
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
const filter_subject = kind === "ev" ? `${serviceName}.ev.>` : `${serviceName}.cmd.>`;
|
|
1160
|
+
return {
|
|
1161
|
+
...defaults,
|
|
1162
|
+
...overrides,
|
|
1163
|
+
name,
|
|
1164
|
+
durable_name: name,
|
|
1165
|
+
filter_subject
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
/** Get default config for a consumer kind. */
|
|
1169
|
+
getDefaults(kind) {
|
|
1170
|
+
switch (kind) {
|
|
1171
|
+
case "ev":
|
|
1172
|
+
return DEFAULT_EVENT_CONSUMER_CONFIG;
|
|
1173
|
+
case "cmd":
|
|
1174
|
+
return DEFAULT_COMMAND_CONSUMER_CONFIG;
|
|
1175
|
+
case "broadcast":
|
|
1176
|
+
return DEFAULT_BROADCAST_CONSUMER_CONFIG;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
/** Get user-provided overrides for a consumer kind. */
|
|
1180
|
+
getOverrides(kind) {
|
|
1181
|
+
switch (kind) {
|
|
1182
|
+
case "ev":
|
|
1183
|
+
return this.options.events?.consumer ?? {};
|
|
1184
|
+
case "cmd":
|
|
1185
|
+
return this.options.rpc?.mode === "jetstream" ? this.options.rpc.consumer ?? {} : {};
|
|
1186
|
+
case "broadcast":
|
|
1187
|
+
return this.options.broadcast?.consumer ?? {};
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1192
|
+
// src/server/infrastructure/message.provider.ts
|
|
1193
|
+
import { Logger as Logger7 } from "@nestjs/common";
|
|
1194
|
+
import {
|
|
1195
|
+
catchError,
|
|
1196
|
+
defer as defer2,
|
|
1197
|
+
EMPTY,
|
|
1198
|
+
merge,
|
|
1199
|
+
repeat,
|
|
1200
|
+
Subject,
|
|
1201
|
+
takeUntil,
|
|
1202
|
+
timer
|
|
1203
|
+
} from "rxjs";
|
|
1204
|
+
var MessageProvider = class {
|
|
1205
|
+
constructor(connection, eventBus) {
|
|
1206
|
+
this.connection = connection;
|
|
1207
|
+
this.eventBus = eventBus;
|
|
1208
|
+
}
|
|
1209
|
+
logger = new Logger7("Jetstream:Message");
|
|
1210
|
+
destroy$ = new Subject();
|
|
1211
|
+
eventMessages$ = new Subject();
|
|
1212
|
+
commandMessages$ = new Subject();
|
|
1213
|
+
broadcastMessages$ = new Subject();
|
|
1214
|
+
/** Observable stream of workqueue event messages. */
|
|
1215
|
+
get events$() {
|
|
1216
|
+
return this.eventMessages$.asObservable();
|
|
1217
|
+
}
|
|
1218
|
+
/** Observable stream of RPC command messages (jetstream mode). */
|
|
1219
|
+
get commands$() {
|
|
1220
|
+
return this.commandMessages$.asObservable();
|
|
1221
|
+
}
|
|
1222
|
+
/** Observable stream of broadcast event messages. */
|
|
1223
|
+
get broadcasts$() {
|
|
1224
|
+
return this.broadcastMessages$.asObservable();
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Start consuming messages from the given consumer infos.
|
|
1228
|
+
*
|
|
1229
|
+
* Each consumer gets an independent self-healing flow.
|
|
1230
|
+
* Call `destroy()` to stop all consumers.
|
|
1231
|
+
*/
|
|
1232
|
+
start(consumers) {
|
|
1233
|
+
const flows = [];
|
|
1234
|
+
for (const [kind, info] of consumers) {
|
|
1235
|
+
flows.push(this.createFlow(kind, info));
|
|
1236
|
+
}
|
|
1237
|
+
if (flows.length > 0) {
|
|
1238
|
+
merge(...flows).pipe(takeUntil(this.destroy$)).subscribe();
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
/** Stop all consumer flows and complete all subjects. */
|
|
1242
|
+
destroy() {
|
|
1243
|
+
this.destroy$.next();
|
|
1244
|
+
this.destroy$.complete();
|
|
1245
|
+
this.eventMessages$.complete();
|
|
1246
|
+
this.commandMessages$.complete();
|
|
1247
|
+
this.broadcastMessages$.complete();
|
|
1248
|
+
}
|
|
1249
|
+
/** Create a self-healing consumer flow for a specific kind. */
|
|
1250
|
+
createFlow(kind, info) {
|
|
1251
|
+
const target$ = this.getTargetSubject(kind);
|
|
1252
|
+
return defer2(() => this.consumeOnce(info, target$)).pipe(
|
|
1253
|
+
catchError((err) => {
|
|
1254
|
+
this.logger.error(`Consumer ${info.name} error, will restart:`, err);
|
|
1255
|
+
this.eventBus.emit(
|
|
1256
|
+
"error" /* Error */,
|
|
1257
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
1258
|
+
"message-provider"
|
|
1259
|
+
);
|
|
1260
|
+
return EMPTY;
|
|
1261
|
+
}),
|
|
1262
|
+
repeat({
|
|
1263
|
+
delay: () => {
|
|
1264
|
+
this.logger.warn(`Consumer ${info.name} stream ended, restarting...`);
|
|
1265
|
+
this.eventBus.emit(
|
|
1266
|
+
"error" /* Error */,
|
|
1267
|
+
new Error(`Consumer ${info.name} stream ended`),
|
|
1268
|
+
"message-provider"
|
|
1269
|
+
);
|
|
1270
|
+
return timer(100);
|
|
1271
|
+
}
|
|
1272
|
+
}),
|
|
1273
|
+
takeUntil(this.destroy$)
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
/** Single iteration: get consumer -> pull messages -> emit to subject. */
|
|
1277
|
+
async consumeOnce(info, target$) {
|
|
1278
|
+
const js = (await this.connection.getConnection()).jetstream();
|
|
1279
|
+
const consumer = await js.consumers.get(info.stream_name, info.name);
|
|
1280
|
+
const messages = await consumer.consume();
|
|
1281
|
+
for await (const msg of messages) {
|
|
1282
|
+
target$.next(msg);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
/** Get the target subject for a consumer kind. */
|
|
1286
|
+
getTargetSubject(kind) {
|
|
1287
|
+
switch (kind) {
|
|
1288
|
+
case "ev":
|
|
1289
|
+
return this.eventMessages$;
|
|
1290
|
+
case "cmd":
|
|
1291
|
+
return this.commandMessages$;
|
|
1292
|
+
case "broadcast":
|
|
1293
|
+
return this.broadcastMessages$;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
// src/server/routing/pattern-registry.ts
|
|
1299
|
+
import { Logger as Logger8 } from "@nestjs/common";
|
|
1300
|
+
var PatternRegistry = class {
|
|
1301
|
+
constructor(options) {
|
|
1302
|
+
this.options = options;
|
|
1303
|
+
}
|
|
1304
|
+
logger = new Logger8("Jetstream:PatternRegistry");
|
|
1305
|
+
registry = /* @__PURE__ */ new Map();
|
|
1306
|
+
/**
|
|
1307
|
+
* Register all handlers from the NestJS strategy.
|
|
1308
|
+
*
|
|
1309
|
+
* @param handlers Map of pattern -> MessageHandler from `Server.getHandlers()`.
|
|
1310
|
+
*/
|
|
1311
|
+
registerHandlers(handlers) {
|
|
1312
|
+
const serviceName = this.options.name;
|
|
1313
|
+
for (const [pattern, handler] of handlers) {
|
|
1314
|
+
const isEvent = handler.isEventHandler ?? false;
|
|
1315
|
+
const isBroadcast = !!handler.extras?.broadcast;
|
|
1316
|
+
let fullSubject;
|
|
1317
|
+
if (isBroadcast) {
|
|
1318
|
+
fullSubject = buildBroadcastSubject(pattern);
|
|
1319
|
+
} else if (isEvent) {
|
|
1320
|
+
fullSubject = buildSubject(serviceName, "ev", pattern);
|
|
1321
|
+
} else {
|
|
1322
|
+
fullSubject = buildSubject(serviceName, "cmd", pattern);
|
|
1323
|
+
}
|
|
1324
|
+
this.registry.set(fullSubject, {
|
|
1325
|
+
handler,
|
|
1326
|
+
pattern,
|
|
1327
|
+
isEvent,
|
|
1328
|
+
isBroadcast
|
|
1329
|
+
});
|
|
1330
|
+
let kind;
|
|
1331
|
+
if (isBroadcast) {
|
|
1332
|
+
kind = "broadcast";
|
|
1333
|
+
} else if (isEvent) {
|
|
1334
|
+
kind = "event";
|
|
1335
|
+
} else {
|
|
1336
|
+
kind = "rpc";
|
|
1337
|
+
}
|
|
1338
|
+
this.logger.debug(`Registered ${kind}: ${pattern} -> ${fullSubject}`);
|
|
1339
|
+
}
|
|
1340
|
+
this.logSummary();
|
|
1341
|
+
}
|
|
1342
|
+
/** Find handler for a full NATS subject. */
|
|
1343
|
+
getHandler(subject) {
|
|
1344
|
+
return this.registry.get(subject)?.handler ?? null;
|
|
1345
|
+
}
|
|
1346
|
+
/** Get all registered broadcast patterns (for consumer filter_subject setup). */
|
|
1347
|
+
getBroadcastPatterns() {
|
|
1348
|
+
return Array.from(this.registry.values()).filter((r) => r.isBroadcast).map((r) => `broadcast.${r.pattern}`);
|
|
1349
|
+
}
|
|
1350
|
+
/** Check if any broadcast handlers are registered. */
|
|
1351
|
+
hasBroadcastHandlers() {
|
|
1352
|
+
return Array.from(this.registry.values()).some((r) => r.isBroadcast);
|
|
1353
|
+
}
|
|
1354
|
+
/** Check if any RPC (command) handlers are registered. */
|
|
1355
|
+
hasRpcHandlers() {
|
|
1356
|
+
return Array.from(this.registry.values()).some((r) => !r.isEvent && !r.isBroadcast);
|
|
1357
|
+
}
|
|
1358
|
+
/** Check if any workqueue event handlers are registered. */
|
|
1359
|
+
hasEventHandlers() {
|
|
1360
|
+
return Array.from(this.registry.values()).some((r) => r.isEvent && !r.isBroadcast);
|
|
1361
|
+
}
|
|
1362
|
+
/** Get patterns grouped by kind. */
|
|
1363
|
+
getPatternsByKind() {
|
|
1364
|
+
const events = [];
|
|
1365
|
+
const commands = [];
|
|
1366
|
+
const broadcasts = [];
|
|
1367
|
+
for (const entry of this.registry.values()) {
|
|
1368
|
+
if (entry.isBroadcast) broadcasts.push(entry.pattern);
|
|
1369
|
+
else if (entry.isEvent) events.push(entry.pattern);
|
|
1370
|
+
else commands.push(entry.pattern);
|
|
1371
|
+
}
|
|
1372
|
+
return { events, commands, broadcasts };
|
|
1373
|
+
}
|
|
1374
|
+
/** Normalize a full NATS subject back to the user-facing pattern. */
|
|
1375
|
+
normalizeSubject(subject) {
|
|
1376
|
+
const name = internalName(this.options.name);
|
|
1377
|
+
const prefixes = [`${name}.cmd.`, `${name}.ev.`, "broadcast."];
|
|
1378
|
+
for (const prefix of prefixes) {
|
|
1379
|
+
if (subject.startsWith(prefix)) {
|
|
1380
|
+
return subject.slice(prefix.length);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
return subject;
|
|
1384
|
+
}
|
|
1385
|
+
/** Log a summary of all registered handlers. */
|
|
1386
|
+
logSummary() {
|
|
1387
|
+
const { events, commands, broadcasts } = this.getPatternsByKind();
|
|
1388
|
+
this.logger.log(
|
|
1389
|
+
`Registered handlers: ${commands.length} RPC, ${events.length} events, ${broadcasts.length} broadcasts`
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
};
|
|
1393
|
+
|
|
1394
|
+
// src/server/routing/event.router.ts
|
|
1395
|
+
import { Logger as Logger9 } from "@nestjs/common";
|
|
1396
|
+
import {
|
|
1397
|
+
catchError as catchError2,
|
|
1398
|
+
EMPTY as EMPTY2,
|
|
1399
|
+
from as from2,
|
|
1400
|
+
isObservable as isObservable2,
|
|
1401
|
+
lastValueFrom,
|
|
1402
|
+
mergeMap
|
|
1403
|
+
} from "rxjs";
|
|
1404
|
+
var EventRouter = class {
|
|
1405
|
+
constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig) {
|
|
1406
|
+
this.messageProvider = messageProvider;
|
|
1407
|
+
this.patternRegistry = patternRegistry;
|
|
1408
|
+
this.codec = codec;
|
|
1409
|
+
this.eventBus = eventBus;
|
|
1410
|
+
this.deadLetterConfig = deadLetterConfig;
|
|
1411
|
+
}
|
|
1412
|
+
logger = new Logger9("Jetstream:EventRouter");
|
|
1413
|
+
subscriptions = [];
|
|
1414
|
+
/** Start routing event and broadcast messages to handlers. */
|
|
1415
|
+
start() {
|
|
1416
|
+
this.subscribeToStream(this.messageProvider.events$, "workqueue");
|
|
1417
|
+
this.subscribeToStream(this.messageProvider.broadcasts$, "broadcast");
|
|
1418
|
+
}
|
|
1419
|
+
/** Stop routing and unsubscribe from all streams. */
|
|
1420
|
+
destroy() {
|
|
1421
|
+
for (const sub of this.subscriptions) {
|
|
1422
|
+
sub.unsubscribe();
|
|
1423
|
+
}
|
|
1424
|
+
this.subscriptions.length = 0;
|
|
1425
|
+
}
|
|
1426
|
+
/** Subscribe to a message stream and route each message. */
|
|
1427
|
+
subscribeToStream(stream$, label) {
|
|
1428
|
+
const subscription = stream$.pipe(
|
|
1429
|
+
mergeMap((msg) => this.handle(msg)),
|
|
1430
|
+
catchError2((err, caught) => {
|
|
1431
|
+
this.logger.error(`Unexpected error in ${label} event router`, err);
|
|
1432
|
+
return caught;
|
|
1433
|
+
})
|
|
1434
|
+
).subscribe();
|
|
1435
|
+
this.subscriptions.push(subscription);
|
|
1436
|
+
}
|
|
1437
|
+
/** Handle a single event message: decode -> execute handler -> ack/nak. */
|
|
1438
|
+
handle(msg) {
|
|
1439
|
+
const handler = this.patternRegistry.getHandler(msg.subject);
|
|
1440
|
+
if (!handler) {
|
|
1441
|
+
msg.term(`No handler for event: ${msg.subject}`);
|
|
1442
|
+
this.logger.error(`No handler for event subject: ${msg.subject}`);
|
|
1443
|
+
return EMPTY2;
|
|
1444
|
+
}
|
|
1445
|
+
let data;
|
|
1446
|
+
try {
|
|
1447
|
+
data = this.codec.decode(msg.data);
|
|
1448
|
+
} catch (err) {
|
|
1449
|
+
msg.term("Decode error");
|
|
1450
|
+
this.logger.error(`Decode error for ${msg.subject}:`, err);
|
|
1451
|
+
return EMPTY2;
|
|
1452
|
+
}
|
|
1453
|
+
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "event");
|
|
1454
|
+
const ctx = new RpcContext([msg]);
|
|
1455
|
+
return from2(this.executeHandler(handler, data, ctx, msg));
|
|
1456
|
+
}
|
|
1457
|
+
/** Execute handler, then ack on success or nak/dead-letter on failure. */
|
|
1458
|
+
async executeHandler(handler, data, ctx, msg) {
|
|
1459
|
+
try {
|
|
1460
|
+
const result = await handler(data, ctx);
|
|
1461
|
+
if (isObservable2(result)) {
|
|
1462
|
+
await lastValueFrom(result, { defaultValue: void 0 });
|
|
1463
|
+
}
|
|
1464
|
+
msg.ack();
|
|
1465
|
+
} catch (err) {
|
|
1466
|
+
this.logger.error(`Event handler error (${msg.subject}):`, err);
|
|
1467
|
+
if (this.isDeadLetter(msg)) {
|
|
1468
|
+
await this.handleDeadLetter(msg, data, err);
|
|
1469
|
+
} else {
|
|
1470
|
+
msg.nak();
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
/** Check if the message has exhausted all delivery attempts. */
|
|
1475
|
+
isDeadLetter(msg) {
|
|
1476
|
+
if (!this.deadLetterConfig) return false;
|
|
1477
|
+
const maxDeliver = this.deadLetterConfig.maxDeliverByStream.get(msg.info.stream);
|
|
1478
|
+
if (maxDeliver === void 0) return false;
|
|
1479
|
+
return msg.info.deliveryCount >= maxDeliver;
|
|
1480
|
+
}
|
|
1481
|
+
/** Handle a dead letter: invoke callback, then term or nak based on result. */
|
|
1482
|
+
async handleDeadLetter(msg, data, error) {
|
|
1483
|
+
const info = {
|
|
1484
|
+
subject: msg.subject,
|
|
1485
|
+
data,
|
|
1486
|
+
headers: msg.headers,
|
|
1487
|
+
error,
|
|
1488
|
+
deliveryCount: msg.info.deliveryCount,
|
|
1489
|
+
stream: msg.info.stream,
|
|
1490
|
+
streamSequence: msg.info.streamSequence,
|
|
1491
|
+
timestamp: new Date(msg.info.timestampNanos / 1e6).toISOString()
|
|
1492
|
+
};
|
|
1493
|
+
this.eventBus.emit("deadLetter" /* DeadLetter */, info);
|
|
1494
|
+
if (!this.deadLetterConfig) return;
|
|
1495
|
+
try {
|
|
1496
|
+
await this.deadLetterConfig.onDeadLetter(info);
|
|
1497
|
+
msg.term("Dead letter processed");
|
|
1498
|
+
} catch (hookErr) {
|
|
1499
|
+
this.logger.error(`onDeadLetter callback failed for ${msg.subject}, nak for retry:`, hookErr);
|
|
1500
|
+
msg.nak();
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
|
|
1505
|
+
// src/server/routing/rpc.router.ts
|
|
1506
|
+
import { Logger as Logger10 } from "@nestjs/common";
|
|
1507
|
+
import { headers } from "nats";
|
|
1508
|
+
import { catchError as catchError3, EMPTY as EMPTY3, from as from3, mergeMap as mergeMap2 } from "rxjs";
|
|
1509
|
+
var RpcRouter = class {
|
|
1510
|
+
constructor(messageProvider, patternRegistry, connection, codec, eventBus, timeout) {
|
|
1511
|
+
this.messageProvider = messageProvider;
|
|
1512
|
+
this.patternRegistry = patternRegistry;
|
|
1513
|
+
this.connection = connection;
|
|
1514
|
+
this.codec = codec;
|
|
1515
|
+
this.eventBus = eventBus;
|
|
1516
|
+
this.timeout = timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT;
|
|
1517
|
+
}
|
|
1518
|
+
logger = new Logger10("Jetstream:RpcRouter");
|
|
1519
|
+
timeout;
|
|
1520
|
+
subscription = null;
|
|
1521
|
+
/** Start routing command messages to handlers. */
|
|
1522
|
+
start() {
|
|
1523
|
+
this.subscription = this.messageProvider.commands$.pipe(
|
|
1524
|
+
mergeMap2((msg) => this.handle(msg)),
|
|
1525
|
+
catchError3((err, caught) => {
|
|
1526
|
+
this.logger.error("Unexpected error in RPC router", err);
|
|
1527
|
+
return caught;
|
|
1528
|
+
})
|
|
1529
|
+
).subscribe();
|
|
1530
|
+
}
|
|
1531
|
+
/** Stop routing and unsubscribe. */
|
|
1532
|
+
destroy() {
|
|
1533
|
+
this.subscription?.unsubscribe();
|
|
1534
|
+
this.subscription = null;
|
|
1535
|
+
}
|
|
1536
|
+
/** Handle a single RPC command message. */
|
|
1537
|
+
handle(msg) {
|
|
1538
|
+
const handler = this.patternRegistry.getHandler(msg.subject);
|
|
1539
|
+
if (!handler) {
|
|
1540
|
+
msg.term(`No handler for RPC: ${msg.subject}`);
|
|
1541
|
+
this.logger.error(`No handler for RPC subject: ${msg.subject}`);
|
|
1542
|
+
return EMPTY3;
|
|
1543
|
+
}
|
|
1544
|
+
const replyTo = msg.headers?.get("x-reply-to" /* ReplyTo */);
|
|
1545
|
+
const correlationId = msg.headers?.get("x-correlation-id" /* CorrelationId */);
|
|
1546
|
+
if (!replyTo || !correlationId) {
|
|
1547
|
+
msg.term("Missing required headers (reply-to or correlation-id)");
|
|
1548
|
+
this.logger.error(`Missing headers for RPC: ${msg.subject}`);
|
|
1549
|
+
return EMPTY3;
|
|
1550
|
+
}
|
|
1551
|
+
let data;
|
|
1552
|
+
try {
|
|
1553
|
+
data = this.codec.decode(msg.data);
|
|
1554
|
+
} catch (err) {
|
|
1555
|
+
msg.term("Decode error");
|
|
1556
|
+
this.logger.error(`Decode error for RPC ${msg.subject}:`, err);
|
|
1557
|
+
return EMPTY3;
|
|
1558
|
+
}
|
|
1559
|
+
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc");
|
|
1560
|
+
return from3(this.executeHandler(handler, data, msg, replyTo, correlationId));
|
|
1561
|
+
}
|
|
1562
|
+
/** Execute handler, publish response, settle message. */
|
|
1563
|
+
async executeHandler(handler, data, msg, replyTo, correlationId) {
|
|
1564
|
+
const nc = await this.connection.getConnection();
|
|
1565
|
+
const ctx = new RpcContext([msg]);
|
|
1566
|
+
const hdrs = headers();
|
|
1567
|
+
hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
|
|
1568
|
+
let settled = false;
|
|
1569
|
+
const timeoutId = setTimeout(() => {
|
|
1570
|
+
if (settled) return;
|
|
1571
|
+
settled = true;
|
|
1572
|
+
this.logger.error(`RPC timeout (${this.timeout}ms): ${msg.subject}`);
|
|
1573
|
+
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, msg.subject, correlationId);
|
|
1574
|
+
msg.term("Handler timeout");
|
|
1575
|
+
}, this.timeout);
|
|
1576
|
+
try {
|
|
1577
|
+
const result = await unwrapResult(handler(data, ctx));
|
|
1578
|
+
if (settled) return;
|
|
1579
|
+
settled = true;
|
|
1580
|
+
clearTimeout(timeoutId);
|
|
1581
|
+
nc.publish(replyTo, this.codec.encode(result), { headers: hdrs });
|
|
1582
|
+
msg.ack();
|
|
1583
|
+
} catch (err) {
|
|
1584
|
+
if (settled) return;
|
|
1585
|
+
settled = true;
|
|
1586
|
+
clearTimeout(timeoutId);
|
|
1587
|
+
try {
|
|
1588
|
+
hdrs.set("x-error" /* Error */, "true");
|
|
1589
|
+
nc.publish(replyTo, this.codec.encode(serializeError(err)), { headers: hdrs });
|
|
1590
|
+
} catch (encodeErr) {
|
|
1591
|
+
this.logger.error(`Failed to encode RPC error for ${msg.subject}`, encodeErr);
|
|
1592
|
+
}
|
|
1593
|
+
msg.term(`Handler error: ${msg.subject}`);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
|
|
1598
|
+
// src/shutdown/shutdown.manager.ts
|
|
1599
|
+
import { Logger as Logger11 } from "@nestjs/common";
|
|
1600
|
+
var ShutdownManager = class {
|
|
1601
|
+
constructor(connection, eventBus, timeout) {
|
|
1602
|
+
this.connection = connection;
|
|
1603
|
+
this.eventBus = eventBus;
|
|
1604
|
+
this.timeout = timeout;
|
|
1605
|
+
}
|
|
1606
|
+
logger = new Logger11("Jetstream:Shutdown");
|
|
1607
|
+
/**
|
|
1608
|
+
* Execute the full shutdown sequence.
|
|
1609
|
+
*
|
|
1610
|
+
* @param strategy Optional strategy to close (stops consumers and subscriptions).
|
|
1611
|
+
*/
|
|
1612
|
+
async shutdown(strategy) {
|
|
1613
|
+
this.eventBus.emit("shutdownStart" /* ShutdownStart */);
|
|
1614
|
+
this.logger.log(`Graceful shutdown started (timeout: ${this.timeout}ms)`);
|
|
1615
|
+
strategy?.close();
|
|
1616
|
+
await Promise.race([
|
|
1617
|
+
this.connection.shutdown(),
|
|
1618
|
+
new Promise((resolve) => setTimeout(resolve, this.timeout))
|
|
1619
|
+
]);
|
|
1620
|
+
this.eventBus.emit("shutdownComplete" /* ShutdownComplete */);
|
|
1621
|
+
this.logger.log("Graceful shutdown complete");
|
|
1622
|
+
}
|
|
1623
|
+
};
|
|
1624
|
+
|
|
1625
|
+
// src/jetstream.module.ts
|
|
1626
|
+
var JetstreamModule = class {
|
|
1627
|
+
constructor(shutdownManager, strategy) {
|
|
1628
|
+
this.shutdownManager = shutdownManager;
|
|
1629
|
+
this.strategy = strategy;
|
|
1630
|
+
}
|
|
1631
|
+
// -------------------------------------------------------------------
|
|
1632
|
+
// forRoot — global module registration
|
|
1633
|
+
// -------------------------------------------------------------------
|
|
1634
|
+
/**
|
|
1635
|
+
* Register the JetStream transport globally.
|
|
1636
|
+
*
|
|
1637
|
+
* Creates a shared NATS connection, codec, event bus, and optionally
|
|
1638
|
+
* the full consumer infrastructure (streams, consumers, routers).
|
|
1639
|
+
*
|
|
1640
|
+
* @param options Module configuration.
|
|
1641
|
+
* @returns Dynamic module ready to be imported.
|
|
1642
|
+
*/
|
|
1643
|
+
static forRoot(options) {
|
|
1644
|
+
const providers = this.createCoreProviders(options);
|
|
1645
|
+
return {
|
|
1646
|
+
module: JetstreamModule,
|
|
1647
|
+
global: true,
|
|
1648
|
+
providers,
|
|
1649
|
+
exports: [
|
|
1650
|
+
JETSTREAM_CONNECTION,
|
|
1651
|
+
JETSTREAM_CODEC,
|
|
1652
|
+
JETSTREAM_EVENT_BUS,
|
|
1653
|
+
JETSTREAM_OPTIONS,
|
|
1654
|
+
ShutdownManager,
|
|
1655
|
+
JetstreamStrategy,
|
|
1656
|
+
JetstreamHealthIndicator
|
|
1657
|
+
]
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
// -------------------------------------------------------------------
|
|
1661
|
+
// forRootAsync — async global module registration
|
|
1662
|
+
// -------------------------------------------------------------------
|
|
1663
|
+
/**
|
|
1664
|
+
* Register the JetStream transport globally with async configuration.
|
|
1665
|
+
*
|
|
1666
|
+
* Supports `useFactory`, `useExisting`, and `useClass` patterns
|
|
1667
|
+
* for loading configuration from ConfigService, environment, etc.
|
|
1668
|
+
*
|
|
1669
|
+
* @param asyncOptions Async configuration.
|
|
1670
|
+
* @returns Dynamic module ready to be imported.
|
|
1671
|
+
*/
|
|
1672
|
+
static forRootAsync(asyncOptions) {
|
|
1673
|
+
const asyncProviders = this.createAsyncOptionsProvider(asyncOptions);
|
|
1674
|
+
const coreProviders = this.createCoreDependentProviders();
|
|
1675
|
+
return {
|
|
1676
|
+
module: JetstreamModule,
|
|
1677
|
+
global: true,
|
|
1678
|
+
imports: asyncOptions.imports ?? [],
|
|
1679
|
+
providers: [...asyncProviders, ...coreProviders],
|
|
1680
|
+
exports: [
|
|
1681
|
+
JETSTREAM_CONNECTION,
|
|
1682
|
+
JETSTREAM_CODEC,
|
|
1683
|
+
JETSTREAM_EVENT_BUS,
|
|
1684
|
+
JETSTREAM_OPTIONS,
|
|
1685
|
+
ShutdownManager,
|
|
1686
|
+
JetstreamStrategy,
|
|
1687
|
+
JetstreamHealthIndicator
|
|
1688
|
+
]
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
// -------------------------------------------------------------------
|
|
1692
|
+
// forFeature — per-module client registration
|
|
1693
|
+
// -------------------------------------------------------------------
|
|
1694
|
+
/**
|
|
1695
|
+
* Register a lightweight client proxy for a target service.
|
|
1696
|
+
*
|
|
1697
|
+
* Reuses the shared NATS connection from `forRoot()`.
|
|
1698
|
+
* Import in each feature module that needs to communicate with a specific service.
|
|
1699
|
+
*
|
|
1700
|
+
* @param options Feature options with target service name.
|
|
1701
|
+
* @returns Dynamic module with the client provider.
|
|
1702
|
+
*/
|
|
1703
|
+
static forFeature(options) {
|
|
1704
|
+
const clientToken = getClientToken(options.name);
|
|
1705
|
+
const clientProvider = {
|
|
1706
|
+
provide: clientToken,
|
|
1707
|
+
inject: [JETSTREAM_OPTIONS, JETSTREAM_CONNECTION, JETSTREAM_CODEC, JETSTREAM_EVENT_BUS],
|
|
1708
|
+
useFactory: (rootOptions, connection, rootCodec, eventBus) => {
|
|
1709
|
+
const codec = options.codec ?? rootCodec;
|
|
1710
|
+
return new JetstreamClient(rootOptions, options.name, connection, codec, eventBus);
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
return {
|
|
1714
|
+
module: JetstreamModule,
|
|
1715
|
+
providers: [clientProvider],
|
|
1716
|
+
exports: [clientToken]
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
// -------------------------------------------------------------------
|
|
1720
|
+
// Provider factories
|
|
1721
|
+
// -------------------------------------------------------------------
|
|
1722
|
+
/** Create all providers for synchronous forRoot(). */
|
|
1723
|
+
/**
|
|
1724
|
+
* Build a map of stream name -> max_deliver for dead letter detection.
|
|
1725
|
+
* Each stream kind (ev, broadcast) has its own consumer config with potentially
|
|
1726
|
+
* different max_deliver values.
|
|
1727
|
+
*/
|
|
1728
|
+
static buildMaxDeliverMap(options) {
|
|
1729
|
+
const map = /* @__PURE__ */ new Map();
|
|
1730
|
+
const defaultEventMax = DEFAULT_EVENT_CONSUMER_CONFIG.max_deliver ?? 3;
|
|
1731
|
+
const defaultBroadcastMax = DEFAULT_BROADCAST_CONSUMER_CONFIG.max_deliver ?? 3;
|
|
1732
|
+
map.set(
|
|
1733
|
+
streamName(options.name, "ev"),
|
|
1734
|
+
options.events?.consumer?.max_deliver ?? defaultEventMax
|
|
1735
|
+
);
|
|
1736
|
+
map.set(
|
|
1737
|
+
streamName(options.name, "broadcast"),
|
|
1738
|
+
options.broadcast?.consumer?.max_deliver ?? defaultBroadcastMax
|
|
1739
|
+
);
|
|
1740
|
+
return map;
|
|
1741
|
+
}
|
|
1742
|
+
static createCoreProviders(options) {
|
|
1743
|
+
return [
|
|
1744
|
+
{
|
|
1745
|
+
provide: JETSTREAM_OPTIONS,
|
|
1746
|
+
useValue: options
|
|
1747
|
+
},
|
|
1748
|
+
...this.createCoreDependentProviders()
|
|
1749
|
+
];
|
|
1750
|
+
}
|
|
1751
|
+
/** Create providers that depend on JETSTREAM_OPTIONS (shared by sync and async). */
|
|
1752
|
+
static createCoreDependentProviders() {
|
|
1753
|
+
return [
|
|
1754
|
+
// EventBus — hook system with Logger fallback
|
|
1755
|
+
{
|
|
1756
|
+
provide: JETSTREAM_EVENT_BUS,
|
|
1757
|
+
inject: [JETSTREAM_OPTIONS],
|
|
1758
|
+
useFactory: (options) => {
|
|
1759
|
+
const logger = new Logger12("Jetstream:Module");
|
|
1760
|
+
return new EventBus(logger, options.hooks);
|
|
1761
|
+
}
|
|
1762
|
+
},
|
|
1763
|
+
// Codec — global encode/decode
|
|
1764
|
+
{
|
|
1765
|
+
provide: JETSTREAM_CODEC,
|
|
1766
|
+
inject: [JETSTREAM_OPTIONS],
|
|
1767
|
+
useFactory: (options) => {
|
|
1768
|
+
return options.codec ?? new JsonCodec();
|
|
1769
|
+
}
|
|
1770
|
+
},
|
|
1771
|
+
// ConnectionProvider — single NATS connection
|
|
1772
|
+
{
|
|
1773
|
+
provide: JETSTREAM_CONNECTION,
|
|
1774
|
+
inject: [JETSTREAM_OPTIONS, JETSTREAM_EVENT_BUS],
|
|
1775
|
+
useFactory: (options, eventBus) => {
|
|
1776
|
+
return new ConnectionProvider(options, eventBus);
|
|
1777
|
+
}
|
|
1778
|
+
},
|
|
1779
|
+
// JetstreamHealthIndicator — health check for NATS connection
|
|
1780
|
+
{
|
|
1781
|
+
provide: JetstreamHealthIndicator,
|
|
1782
|
+
inject: [JETSTREAM_CONNECTION],
|
|
1783
|
+
useFactory: (connection) => {
|
|
1784
|
+
return new JetstreamHealthIndicator(connection);
|
|
1785
|
+
}
|
|
1786
|
+
},
|
|
1787
|
+
// ShutdownManager — graceful shutdown orchestration
|
|
1788
|
+
{
|
|
1789
|
+
provide: ShutdownManager,
|
|
1790
|
+
inject: [JETSTREAM_CONNECTION, JETSTREAM_EVENT_BUS, JETSTREAM_OPTIONS],
|
|
1791
|
+
useFactory: (connection, eventBus, options) => {
|
|
1792
|
+
return new ShutdownManager(
|
|
1793
|
+
connection,
|
|
1794
|
+
eventBus,
|
|
1795
|
+
options.shutdownTimeout ?? DEFAULT_SHUTDOWN_TIMEOUT
|
|
1796
|
+
);
|
|
1797
|
+
}
|
|
1798
|
+
},
|
|
1799
|
+
// ---------------------------------------------------------------
|
|
1800
|
+
// Consumer infrastructure — only created when consumer !== false.
|
|
1801
|
+
// Providers return null when consumer is disabled (publisher-only mode).
|
|
1802
|
+
// ---------------------------------------------------------------
|
|
1803
|
+
// PatternRegistry — subject-to-handler mapping
|
|
1804
|
+
{
|
|
1805
|
+
provide: PatternRegistry,
|
|
1806
|
+
inject: [JETSTREAM_OPTIONS],
|
|
1807
|
+
useFactory: (options) => {
|
|
1808
|
+
if (options.consumer === false) return null;
|
|
1809
|
+
return new PatternRegistry(options);
|
|
1810
|
+
}
|
|
1811
|
+
},
|
|
1812
|
+
// StreamProvider — JetStream stream lifecycle
|
|
1813
|
+
{
|
|
1814
|
+
provide: StreamProvider,
|
|
1815
|
+
inject: [JETSTREAM_OPTIONS, JETSTREAM_CONNECTION],
|
|
1816
|
+
useFactory: (options, connection) => {
|
|
1817
|
+
if (options.consumer === false) return null;
|
|
1818
|
+
return new StreamProvider(options, connection);
|
|
1819
|
+
}
|
|
1820
|
+
},
|
|
1821
|
+
// ConsumerProvider — JetStream consumer lifecycle (receives PatternRegistry for broadcast filtering)
|
|
1822
|
+
{
|
|
1823
|
+
provide: ConsumerProvider,
|
|
1824
|
+
inject: [JETSTREAM_OPTIONS, JETSTREAM_CONNECTION, StreamProvider, PatternRegistry],
|
|
1825
|
+
useFactory: (options, connection, streamProvider, patternRegistry) => {
|
|
1826
|
+
if (options.consumer === false) return null;
|
|
1827
|
+
return new ConsumerProvider(options, connection, streamProvider, patternRegistry);
|
|
1828
|
+
}
|
|
1829
|
+
},
|
|
1830
|
+
// MessageProvider — pull-based message consumption
|
|
1831
|
+
{
|
|
1832
|
+
provide: MessageProvider,
|
|
1833
|
+
inject: [JETSTREAM_OPTIONS, JETSTREAM_CONNECTION, JETSTREAM_EVENT_BUS],
|
|
1834
|
+
useFactory: (options, connection, eventBus) => {
|
|
1835
|
+
if (options.consumer === false) return null;
|
|
1836
|
+
return new MessageProvider(connection, eventBus);
|
|
1837
|
+
}
|
|
1838
|
+
},
|
|
1839
|
+
// EventRouter — routes event and broadcast messages to handlers
|
|
1840
|
+
{
|
|
1841
|
+
provide: EventRouter,
|
|
1842
|
+
inject: [
|
|
1843
|
+
JETSTREAM_OPTIONS,
|
|
1844
|
+
MessageProvider,
|
|
1845
|
+
PatternRegistry,
|
|
1846
|
+
JETSTREAM_CODEC,
|
|
1847
|
+
JETSTREAM_EVENT_BUS
|
|
1848
|
+
],
|
|
1849
|
+
useFactory: (options, messageProvider, patternRegistry, codec, eventBus) => {
|
|
1850
|
+
if (options.consumer === false) return null;
|
|
1851
|
+
const deadLetterConfig = options.onDeadLetter ? {
|
|
1852
|
+
maxDeliverByStream: JetstreamModule.buildMaxDeliverMap(options),
|
|
1853
|
+
onDeadLetter: options.onDeadLetter
|
|
1854
|
+
} : void 0;
|
|
1855
|
+
return new EventRouter(
|
|
1856
|
+
messageProvider,
|
|
1857
|
+
patternRegistry,
|
|
1858
|
+
codec,
|
|
1859
|
+
eventBus,
|
|
1860
|
+
deadLetterConfig
|
|
1861
|
+
);
|
|
1862
|
+
}
|
|
1863
|
+
},
|
|
1864
|
+
// RpcRouter — routes RPC command messages in JetStream mode
|
|
1865
|
+
{
|
|
1866
|
+
provide: RpcRouter,
|
|
1867
|
+
inject: [
|
|
1868
|
+
JETSTREAM_OPTIONS,
|
|
1869
|
+
MessageProvider,
|
|
1870
|
+
PatternRegistry,
|
|
1871
|
+
JETSTREAM_CONNECTION,
|
|
1872
|
+
JETSTREAM_CODEC,
|
|
1873
|
+
JETSTREAM_EVENT_BUS
|
|
1874
|
+
],
|
|
1875
|
+
useFactory: (options, messageProvider, patternRegistry, connection, codec, eventBus) => {
|
|
1876
|
+
if (options.consumer === false) return null;
|
|
1877
|
+
const timeout = options.rpc?.mode === "jetstream" ? options.rpc.timeout : void 0;
|
|
1878
|
+
return new RpcRouter(
|
|
1879
|
+
messageProvider,
|
|
1880
|
+
patternRegistry,
|
|
1881
|
+
connection,
|
|
1882
|
+
codec,
|
|
1883
|
+
eventBus,
|
|
1884
|
+
timeout
|
|
1885
|
+
);
|
|
1886
|
+
}
|
|
1887
|
+
},
|
|
1888
|
+
// CoreRpcServer — RPC via NATS Core request/reply
|
|
1889
|
+
{
|
|
1890
|
+
provide: CoreRpcServer,
|
|
1891
|
+
inject: [
|
|
1892
|
+
JETSTREAM_OPTIONS,
|
|
1893
|
+
JETSTREAM_CONNECTION,
|
|
1894
|
+
PatternRegistry,
|
|
1895
|
+
JETSTREAM_CODEC,
|
|
1896
|
+
JETSTREAM_EVENT_BUS
|
|
1897
|
+
],
|
|
1898
|
+
useFactory: (options, connection, patternRegistry, codec, eventBus) => {
|
|
1899
|
+
if (options.consumer === false) return null;
|
|
1900
|
+
return new CoreRpcServer(options, connection, patternRegistry, codec, eventBus);
|
|
1901
|
+
}
|
|
1902
|
+
},
|
|
1903
|
+
// JetstreamStrategy — server-side transport (only when consumer enabled)
|
|
1904
|
+
{
|
|
1905
|
+
provide: JetstreamStrategy,
|
|
1906
|
+
inject: [
|
|
1907
|
+
JETSTREAM_OPTIONS,
|
|
1908
|
+
JETSTREAM_CONNECTION,
|
|
1909
|
+
PatternRegistry,
|
|
1910
|
+
StreamProvider,
|
|
1911
|
+
ConsumerProvider,
|
|
1912
|
+
MessageProvider,
|
|
1913
|
+
EventRouter,
|
|
1914
|
+
RpcRouter,
|
|
1915
|
+
CoreRpcServer
|
|
1916
|
+
],
|
|
1917
|
+
useFactory: (options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer) => {
|
|
1918
|
+
if (options.consumer === false) return null;
|
|
1919
|
+
return new JetstreamStrategy(
|
|
1920
|
+
options,
|
|
1921
|
+
connection,
|
|
1922
|
+
patternRegistry,
|
|
1923
|
+
streamProvider,
|
|
1924
|
+
consumerProvider,
|
|
1925
|
+
messageProvider,
|
|
1926
|
+
eventRouter,
|
|
1927
|
+
rpcRouter,
|
|
1928
|
+
coreRpcServer
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
];
|
|
1933
|
+
}
|
|
1934
|
+
/** Create async options provider from useFactory/useExisting/useClass. */
|
|
1935
|
+
static createAsyncOptionsProvider(asyncOptions) {
|
|
1936
|
+
if (asyncOptions.useFactory) {
|
|
1937
|
+
const factory = asyncOptions.useFactory;
|
|
1938
|
+
return [
|
|
1939
|
+
{
|
|
1940
|
+
provide: JETSTREAM_OPTIONS,
|
|
1941
|
+
useFactory: async (...args) => {
|
|
1942
|
+
const partial = await factory(...args);
|
|
1943
|
+
return { ...partial, name: asyncOptions.name };
|
|
1944
|
+
},
|
|
1945
|
+
inject: asyncOptions.inject ?? []
|
|
1946
|
+
}
|
|
1947
|
+
];
|
|
1948
|
+
}
|
|
1949
|
+
if (asyncOptions.useExisting) {
|
|
1950
|
+
return [
|
|
1951
|
+
{
|
|
1952
|
+
provide: JETSTREAM_OPTIONS,
|
|
1953
|
+
useFactory: (config) => ({
|
|
1954
|
+
...config,
|
|
1955
|
+
name: asyncOptions.name
|
|
1956
|
+
}),
|
|
1957
|
+
inject: [asyncOptions.useExisting]
|
|
1958
|
+
}
|
|
1959
|
+
];
|
|
1960
|
+
}
|
|
1961
|
+
const useClass = asyncOptions.useClass;
|
|
1962
|
+
return [
|
|
1963
|
+
{ provide: useClass, useClass },
|
|
1964
|
+
{
|
|
1965
|
+
provide: JETSTREAM_OPTIONS,
|
|
1966
|
+
useFactory: (config) => ({
|
|
1967
|
+
...config,
|
|
1968
|
+
name: asyncOptions.name
|
|
1969
|
+
}),
|
|
1970
|
+
inject: [useClass]
|
|
1971
|
+
}
|
|
1972
|
+
];
|
|
1973
|
+
}
|
|
1974
|
+
// -------------------------------------------------------------------
|
|
1975
|
+
// Lifecycle hooks
|
|
1976
|
+
// -------------------------------------------------------------------
|
|
1977
|
+
/**
|
|
1978
|
+
* Gracefully shut down the transport on application termination.
|
|
1979
|
+
*/
|
|
1980
|
+
async onApplicationShutdown() {
|
|
1981
|
+
if (this.shutdownManager) {
|
|
1982
|
+
await this.shutdownManager.shutdown(this.strategy ?? void 0);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
};
|
|
1986
|
+
JetstreamModule = __decorateClass([
|
|
1987
|
+
Global(),
|
|
1988
|
+
Module({}),
|
|
1989
|
+
__decorateParam(0, Optional()),
|
|
1990
|
+
__decorateParam(0, Inject(ShutdownManager)),
|
|
1991
|
+
__decorateParam(1, Optional()),
|
|
1992
|
+
__decorateParam(1, Inject(JetstreamStrategy))
|
|
1993
|
+
], JetstreamModule);
|
|
1994
|
+
export {
|
|
1995
|
+
EventBus,
|
|
1996
|
+
JETSTREAM_CODEC,
|
|
1997
|
+
JETSTREAM_CONNECTION,
|
|
1998
|
+
JETSTREAM_EVENT_BUS,
|
|
1999
|
+
JETSTREAM_OPTIONS,
|
|
2000
|
+
JetstreamClient,
|
|
2001
|
+
JetstreamHeader,
|
|
2002
|
+
JetstreamHealthIndicator,
|
|
2003
|
+
JetstreamModule,
|
|
2004
|
+
JetstreamRecord,
|
|
2005
|
+
JetstreamRecordBuilder,
|
|
2006
|
+
JetstreamStrategy,
|
|
2007
|
+
JsonCodec,
|
|
2008
|
+
RpcContext,
|
|
2009
|
+
TransportEvent,
|
|
2010
|
+
getClientToken,
|
|
2011
|
+
nanos
|
|
2012
|
+
};
|
|
40
2013
|
//# sourceMappingURL=index.js.map
|