@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.
Files changed (161) hide show
  1. package/README.md +39 -1
  2. package/dist/index.cjs +2015 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +993 -0
  5. package/dist/index.d.ts +993 -13
  6. package/dist/index.js +2012 -39
  7. package/dist/index.js.map +1 -1
  8. package/package.json +29 -19
  9. package/dist/client/index.d.ts +0 -3
  10. package/dist/client/index.d.ts.map +0 -1
  11. package/dist/client/index.js +0 -9
  12. package/dist/client/index.js.map +0 -1
  13. package/dist/client/jetstream.client.d.ts +0 -76
  14. package/dist/client/jetstream.client.d.ts.map +0 -1
  15. package/dist/client/jetstream.client.js +0 -325
  16. package/dist/client/jetstream.client.js.map +0 -1
  17. package/dist/client/jetstream.record.d.ts +0 -55
  18. package/dist/client/jetstream.record.d.ts.map +0 -1
  19. package/dist/client/jetstream.record.js +0 -84
  20. package/dist/client/jetstream.record.js.map +0 -1
  21. package/dist/codec/index.d.ts +0 -2
  22. package/dist/codec/index.d.ts.map +0 -1
  23. package/dist/codec/index.js +0 -6
  24. package/dist/codec/index.js.map +0 -1
  25. package/dist/codec/json.codec.d.ts +0 -20
  26. package/dist/codec/json.codec.d.ts.map +0 -1
  27. package/dist/codec/json.codec.js +0 -30
  28. package/dist/codec/json.codec.js.map +0 -1
  29. package/dist/connection/connection.provider.d.ts +0 -50
  30. package/dist/connection/connection.provider.d.ts.map +0 -1
  31. package/dist/connection/connection.provider.js +0 -141
  32. package/dist/connection/connection.provider.js.map +0 -1
  33. package/dist/connection/index.d.ts +0 -2
  34. package/dist/connection/index.d.ts.map +0 -1
  35. package/dist/connection/index.js +0 -6
  36. package/dist/connection/index.js.map +0 -1
  37. package/dist/context/index.d.ts +0 -2
  38. package/dist/context/index.d.ts.map +0 -1
  39. package/dist/context/index.js +0 -6
  40. package/dist/context/index.js.map +0 -1
  41. package/dist/context/rpc.context.d.ts +0 -35
  42. package/dist/context/rpc.context.d.ts.map +0 -1
  43. package/dist/context/rpc.context.js +0 -44
  44. package/dist/context/rpc.context.js.map +0 -1
  45. package/dist/health/index.d.ts +0 -3
  46. package/dist/health/index.d.ts.map +0 -1
  47. package/dist/health/index.js +0 -6
  48. package/dist/health/index.js.map +0 -1
  49. package/dist/health/jetstream.health-indicator.d.ts +0 -47
  50. package/dist/health/jetstream.health-indicator.d.ts.map +0 -1
  51. package/dist/health/jetstream.health-indicator.js +0 -85
  52. package/dist/health/jetstream.health-indicator.js.map +0 -1
  53. package/dist/hooks/event-bus.d.ts +0 -31
  54. package/dist/hooks/event-bus.d.ts.map +0 -1
  55. package/dist/hooks/event-bus.js +0 -79
  56. package/dist/hooks/event-bus.js.map +0 -1
  57. package/dist/hooks/index.d.ts +0 -2
  58. package/dist/hooks/index.d.ts.map +0 -1
  59. package/dist/hooks/index.js +0 -6
  60. package/dist/hooks/index.js.map +0 -1
  61. package/dist/index.d.ts.map +0 -1
  62. package/dist/interfaces/client.interface.d.ts +0 -14
  63. package/dist/interfaces/client.interface.d.ts.map +0 -1
  64. package/dist/interfaces/client.interface.js +0 -3
  65. package/dist/interfaces/client.interface.js.map +0 -1
  66. package/dist/interfaces/codec.interface.d.ts +0 -28
  67. package/dist/interfaces/codec.interface.d.ts.map +0 -1
  68. package/dist/interfaces/codec.interface.js +0 -3
  69. package/dist/interfaces/codec.interface.js.map +0 -1
  70. package/dist/interfaces/hooks.interface.d.ts +0 -71
  71. package/dist/interfaces/hooks.interface.d.ts.map +0 -1
  72. package/dist/interfaces/hooks.interface.js +0 -16
  73. package/dist/interfaces/hooks.interface.js.map +0 -1
  74. package/dist/interfaces/index.d.ts +0 -8
  75. package/dist/interfaces/index.d.ts.map +0 -1
  76. package/dist/interfaces/index.js +0 -6
  77. package/dist/interfaces/index.js.map +0 -1
  78. package/dist/interfaces/options.interface.d.ts +0 -142
  79. package/dist/interfaces/options.interface.d.ts.map +0 -1
  80. package/dist/interfaces/options.interface.js +0 -3
  81. package/dist/interfaces/options.interface.js.map +0 -1
  82. package/dist/interfaces/routing.interface.d.ts +0 -15
  83. package/dist/interfaces/routing.interface.d.ts.map +0 -1
  84. package/dist/interfaces/routing.interface.js +0 -3
  85. package/dist/interfaces/routing.interface.js.map +0 -1
  86. package/dist/interfaces/stream.interface.d.ts +0 -5
  87. package/dist/interfaces/stream.interface.d.ts.map +0 -1
  88. package/dist/interfaces/stream.interface.js +0 -3
  89. package/dist/interfaces/stream.interface.js.map +0 -1
  90. package/dist/jetstream.constants.d.ts +0 -58
  91. package/dist/jetstream.constants.d.ts.map +0 -1
  92. package/dist/jetstream.constants.js +0 -168
  93. package/dist/jetstream.constants.js.map +0 -1
  94. package/dist/jetstream.module.d.ts +0 -89
  95. package/dist/jetstream.module.d.ts.map +0 -1
  96. package/dist/jetstream.module.js +0 -410
  97. package/dist/jetstream.module.js.map +0 -1
  98. package/dist/server/core-rpc.server.d.ts +0 -31
  99. package/dist/server/core-rpc.server.d.ts.map +0 -1
  100. package/dist/server/core-rpc.server.js +0 -95
  101. package/dist/server/core-rpc.server.js.map +0 -1
  102. package/dist/server/index.d.ts +0 -5
  103. package/dist/server/index.d.ts.map +0 -1
  104. package/dist/server/index.js +0 -16
  105. package/dist/server/index.js.map +0 -1
  106. package/dist/server/infrastructure/consumer.provider.d.ts +0 -36
  107. package/dist/server/infrastructure/consumer.provider.d.ts.map +0 -1
  108. package/dist/server/infrastructure/consumer.provider.js +0 -123
  109. package/dist/server/infrastructure/consumer.provider.js.map +0 -1
  110. package/dist/server/infrastructure/index.d.ts +0 -4
  111. package/dist/server/infrastructure/index.d.ts.map +0 -1
  112. package/dist/server/infrastructure/index.js +0 -10
  113. package/dist/server/infrastructure/index.js.map +0 -1
  114. package/dist/server/infrastructure/message.provider.d.ts +0 -46
  115. package/dist/server/infrastructure/message.provider.d.ts.map +0 -1
  116. package/dist/server/infrastructure/message.provider.js +0 -100
  117. package/dist/server/infrastructure/message.provider.js.map +0 -1
  118. package/dist/server/infrastructure/stream.provider.d.ts +0 -38
  119. package/dist/server/infrastructure/stream.provider.d.ts.map +0 -1
  120. package/dist/server/infrastructure/stream.provider.js +0 -109
  121. package/dist/server/infrastructure/stream.provider.js.map +0 -1
  122. package/dist/server/routing/event.router.d.ts +0 -56
  123. package/dist/server/routing/event.router.d.ts.map +0 -1
  124. package/dist/server/routing/event.router.js +0 -132
  125. package/dist/server/routing/event.router.js.map +0 -1
  126. package/dist/server/routing/index.d.ts +0 -5
  127. package/dist/server/routing/index.d.ts.map +0 -1
  128. package/dist/server/routing/index.js +0 -10
  129. package/dist/server/routing/index.js.map +0 -1
  130. package/dist/server/routing/pattern-registry.d.ts +0 -39
  131. package/dist/server/routing/pattern-registry.d.ts.map +0 -1
  132. package/dist/server/routing/pattern-registry.js +0 -116
  133. package/dist/server/routing/pattern-registry.js.map +0 -1
  134. package/dist/server/routing/rpc.router.d.ts +0 -37
  135. package/dist/server/routing/rpc.router.d.ts.map +0 -1
  136. package/dist/server/routing/rpc.router.js +0 -121
  137. package/dist/server/routing/rpc.router.js.map +0 -1
  138. package/dist/server/strategy.d.ts +0 -55
  139. package/dist/server/strategy.d.ts.map +0 -1
  140. package/dist/server/strategy.js +0 -113
  141. package/dist/server/strategy.js.map +0 -1
  142. package/dist/shutdown/index.d.ts +0 -2
  143. package/dist/shutdown/index.d.ts.map +0 -1
  144. package/dist/shutdown/index.js +0 -6
  145. package/dist/shutdown/index.js.map +0 -1
  146. package/dist/shutdown/shutdown.manager.d.ts +0 -27
  147. package/dist/shutdown/shutdown.manager.d.ts.map +0 -1
  148. package/dist/shutdown/shutdown.manager.js +0 -45
  149. package/dist/shutdown/shutdown.manager.js.map +0 -1
  150. package/dist/utils/index.d.ts +0 -3
  151. package/dist/utils/index.d.ts.map +0 -1
  152. package/dist/utils/index.js +0 -8
  153. package/dist/utils/index.js.map +0 -1
  154. package/dist/utils/serialize-error.d.ts +0 -10
  155. package/dist/utils/serialize-error.d.ts.map +0 -1
  156. package/dist/utils/serialize-error.js +0 -21
  157. package/dist/utils/serialize-error.js.map +0 -1
  158. package/dist/utils/unwrap-result.d.ts +0 -15
  159. package/dist/utils/unwrap-result.d.ts.map +0 -1
  160. package/dist/utils/unwrap-result.js +0 -49
  161. package/dist/utils/unwrap-result.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,40 +1,2013 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.JetstreamStrategy = exports.EventBus = exports.nanos = exports.JETSTREAM_OPTIONS = exports.JETSTREAM_EVENT_BUS = exports.JETSTREAM_CONNECTION = exports.JETSTREAM_CODEC = exports.JetstreamHeader = exports.getClientToken = exports.JetstreamHealthIndicator = exports.RpcContext = exports.JsonCodec = exports.JetstreamRecordBuilder = exports.JetstreamRecord = exports.JetstreamClient = exports.TransportEvent = exports.JetstreamModule = void 0;
4
- // Module
5
- var jetstream_module_1 = require("./jetstream.module");
6
- Object.defineProperty(exports, "JetstreamModule", { enumerable: true, get: function () { return jetstream_module_1.JetstreamModule; } });
7
- // Interfaces
8
- var interfaces_1 = require("./interfaces");
9
- Object.defineProperty(exports, "TransportEvent", { enumerable: true, get: function () { return interfaces_1.TransportEvent; } });
10
- // Client
11
- var client_1 = require("./client");
12
- Object.defineProperty(exports, "JetstreamClient", { enumerable: true, get: function () { return client_1.JetstreamClient; } });
13
- var client_2 = require("./client");
14
- Object.defineProperty(exports, "JetstreamRecord", { enumerable: true, get: function () { return client_2.JetstreamRecord; } });
15
- Object.defineProperty(exports, "JetstreamRecordBuilder", { enumerable: true, get: function () { return client_2.JetstreamRecordBuilder; } });
16
- // Codec
17
- var codec_1 = require("./codec");
18
- Object.defineProperty(exports, "JsonCodec", { enumerable: true, get: function () { return codec_1.JsonCodec; } });
19
- // Context
20
- var context_1 = require("./context");
21
- Object.defineProperty(exports, "RpcContext", { enumerable: true, get: function () { return context_1.RpcContext; } });
22
- // Health
23
- var health_1 = require("./health");
24
- Object.defineProperty(exports, "JetstreamHealthIndicator", { enumerable: true, get: function () { return health_1.JetstreamHealthIndicator; } });
25
- // Constants (selective — only what users need)
26
- var jetstream_constants_1 = require("./jetstream.constants");
27
- Object.defineProperty(exports, "getClientToken", { enumerable: true, get: function () { return jetstream_constants_1.getClientToken; } });
28
- Object.defineProperty(exports, "JetstreamHeader", { enumerable: true, get: function () { return jetstream_constants_1.JetstreamHeader; } });
29
- Object.defineProperty(exports, "JETSTREAM_CODEC", { enumerable: true, get: function () { return jetstream_constants_1.JETSTREAM_CODEC; } });
30
- Object.defineProperty(exports, "JETSTREAM_CONNECTION", { enumerable: true, get: function () { return jetstream_constants_1.JETSTREAM_CONNECTION; } });
31
- Object.defineProperty(exports, "JETSTREAM_EVENT_BUS", { enumerable: true, get: function () { return jetstream_constants_1.JETSTREAM_EVENT_BUS; } });
32
- Object.defineProperty(exports, "JETSTREAM_OPTIONS", { enumerable: true, get: function () { return jetstream_constants_1.JETSTREAM_OPTIONS; } });
33
- Object.defineProperty(exports, "nanos", { enumerable: true, get: function () { return jetstream_constants_1.nanos; } });
34
- // Hooks
35
- var hooks_1 = require("./hooks");
36
- Object.defineProperty(exports, "EventBus", { enumerable: true, get: function () { return hooks_1.EventBus; } });
37
- // Server (for advanced use cases)
38
- var server_1 = require("./server");
39
- Object.defineProperty(exports, "JetstreamStrategy", { enumerable: true, get: function () { return server_1.JetstreamStrategy; } });
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