@saga-bus/transport-nats 0.1.2

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 ADDED
@@ -0,0 +1,258 @@
1
+ # @saga-bus/transport-nats
2
+
3
+ NATS JetStream transport for saga-bus.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @saga-bus/transport-nats nats
9
+ # or
10
+ pnpm add @saga-bus/transport-nats nats
11
+ ```
12
+
13
+ ## Features
14
+
15
+ - **JetStream**: Persistent message storage with replay capability
16
+ - **Durable Consumers**: Reliable delivery with acknowledgment tracking
17
+ - **Work Queues**: Competing consumer pattern for load distribution
18
+ - **Low Latency**: High-performance messaging
19
+ - **Horizontal Scaling**: Native clustering support
20
+
21
+ ## Quick Start
22
+
23
+ ```typescript
24
+ import { createBus } from "@saga-bus/core";
25
+ import { NatsTransport } from "@saga-bus/transport-nats";
26
+
27
+ const transport = new NatsTransport({
28
+ connectionOptions: { servers: "localhost:4222" },
29
+ streamName: "SAGA_EVENTS",
30
+ });
31
+
32
+ const bus = createBus({
33
+ transport,
34
+ // ... other config
35
+ });
36
+
37
+ await bus.start();
38
+ ```
39
+
40
+ ## Configuration
41
+
42
+ ```typescript
43
+ interface NatsTransportOptions {
44
+ /** Existing NATS connection */
45
+ connection?: NatsConnection;
46
+
47
+ /** Connection options for creating new connection */
48
+ connectionOptions?: ConnectionOptions;
49
+
50
+ /** JetStream options */
51
+ jetStreamOptions?: JetStreamOptions;
52
+
53
+ /** Subject prefix for all messages (default: "saga-bus") */
54
+ subjectPrefix?: string;
55
+
56
+ /** Stream name for JetStream (default: "SAGA_BUS") */
57
+ streamName?: string;
58
+
59
+ /** Consumer name prefix (default: "saga-bus-consumer") */
60
+ consumerPrefix?: string;
61
+
62
+ /** Whether to auto-create streams (default: true) */
63
+ autoCreateStream?: boolean;
64
+
65
+ /** Stream retention policy (default: "workqueue") */
66
+ retentionPolicy?: "limits" | "interest" | "workqueue";
67
+
68
+ /** Max messages in stream (-1 for unlimited) */
69
+ maxMessages?: number;
70
+
71
+ /** Max bytes in stream (-1 for unlimited) */
72
+ maxBytes?: number;
73
+
74
+ /** Max age of messages in nanoseconds */
75
+ maxAge?: number;
76
+
77
+ /** Number of replicas (default: 1) */
78
+ replicas?: number;
79
+
80
+ /** Ack wait timeout in nanoseconds (default: 30s) */
81
+ ackWait?: number;
82
+
83
+ /** Max redelivery attempts (default: 5) */
84
+ maxDeliver?: number;
85
+ }
86
+ ```
87
+
88
+ ## Examples
89
+
90
+ ### Basic Usage
91
+
92
+ ```typescript
93
+ import { NatsTransport } from "@saga-bus/transport-nats";
94
+
95
+ const transport = new NatsTransport({
96
+ connectionOptions: { servers: "localhost:4222" },
97
+ });
98
+
99
+ await transport.start();
100
+
101
+ // Publish a message
102
+ await transport.publish(
103
+ { type: "OrderCreated", orderId: "123" },
104
+ { endpoint: "orders" }
105
+ );
106
+
107
+ // Subscribe to messages
108
+ await transport.subscribe(
109
+ { endpoint: "orders", group: "order-processor" },
110
+ async (envelope) => {
111
+ console.log("Received:", envelope.payload);
112
+ }
113
+ );
114
+ ```
115
+
116
+ ### With Existing Connection
117
+
118
+ ```typescript
119
+ import { connect } from "nats";
120
+
121
+ const nc = await connect({
122
+ servers: ["nats://server1:4222", "nats://server2:4222"],
123
+ token: "my-secret-token",
124
+ });
125
+
126
+ const transport = new NatsTransport({
127
+ connection: nc,
128
+ streamName: "MY_STREAM",
129
+ });
130
+ ```
131
+
132
+ ### Custom Stream Configuration
133
+
134
+ ```typescript
135
+ const transport = new NatsTransport({
136
+ connectionOptions: { servers: "localhost:4222" },
137
+ streamName: "ORDERS_STREAM",
138
+ retentionPolicy: "limits",
139
+ maxMessages: 100000,
140
+ maxBytes: 100 * 1024 * 1024, // 100MB
141
+ maxAge: 24 * 60 * 60 * 1000000000, // 24 hours in nanoseconds
142
+ replicas: 3,
143
+ });
144
+ ```
145
+
146
+ ### Multiple Consumer Groups
147
+
148
+ ```typescript
149
+ // Worker pool 1
150
+ await transport.subscribe(
151
+ { endpoint: "orders", group: "order-validators" },
152
+ async (envelope) => {
153
+ await validateOrder(envelope.payload);
154
+ }
155
+ );
156
+
157
+ // Worker pool 2
158
+ await transport.subscribe(
159
+ { endpoint: "orders", group: "order-emailers" },
160
+ async (envelope) => {
161
+ await sendOrderEmail(envelope.payload);
162
+ }
163
+ );
164
+ ```
165
+
166
+ ## Subject Hierarchy
167
+
168
+ Messages are published to subjects following this pattern:
169
+
170
+ ```
171
+ {subjectPrefix}.{endpoint}.{messageType}
172
+ ```
173
+
174
+ Examples:
175
+ - `saga-bus.orders.OrderCreated`
176
+ - `saga-bus.payments.PaymentReceived`
177
+ - `myapp.inventory.StockUpdated`
178
+
179
+ Consumers subscribe to patterns using `>` wildcard:
180
+ - `saga-bus.orders.>` - All order messages
181
+ - `saga-bus.>` - All messages
182
+
183
+ ## Retention Policies
184
+
185
+ | Policy | Description | Use Case |
186
+ |--------|-------------|----------|
187
+ | `limits` | Messages kept until limits reached | Event sourcing, audit logs |
188
+ | `interest` | Messages kept while consumers interested | Standard pub/sub |
189
+ | `workqueue` | Messages removed after acknowledgment | Task queues, job processing |
190
+
191
+ ## Message Format
192
+
193
+ Messages are published as JSON:
194
+
195
+ ```json
196
+ {
197
+ "id": "msg-uuid",
198
+ "type": "OrderCreated",
199
+ "payload": { "type": "OrderCreated", "orderId": "123" },
200
+ "headers": {},
201
+ "timestamp": "2024-01-01T00:00:00.000Z",
202
+ "partitionKey": "order-123"
203
+ }
204
+ ```
205
+
206
+ With NATS headers:
207
+ - `Nats-Msg-Id`: Unique message ID
208
+ - `X-Message-Type`: Message type
209
+ - `X-Correlation-Id`: Correlation/partition key
210
+
211
+ ## Limitations
212
+
213
+ ### No Delayed Messages
214
+
215
+ NATS JetStream does not support native delayed message delivery. Attempting to publish with `delayMs` will throw an error:
216
+
217
+ ```typescript
218
+ // This will throw an error
219
+ await transport.publish(message, { delayMs: 5000 });
220
+ // Error: NATS JetStream does not support delayed messages.
221
+ // Use an external scheduler for delayed delivery.
222
+ ```
223
+
224
+ **Alternatives:**
225
+ - Use Redis sorted sets for scheduling
226
+ - Implement delay in application logic
227
+ - Use a separate scheduler service
228
+
229
+ ## Error Handling
230
+
231
+ Messages that fail processing are automatically retried up to `maxDeliver` times:
232
+
233
+ ```typescript
234
+ const transport = new NatsTransport({
235
+ connectionOptions: { servers: "localhost:4222" },
236
+ maxDeliver: 10, // Retry up to 10 times
237
+ ackWait: 60_000_000_000, // 60 second ack timeout
238
+ });
239
+ ```
240
+
241
+ ## Testing
242
+
243
+ For testing, you can run NATS locally:
244
+
245
+ ```bash
246
+ # Run NATS with JetStream enabled
247
+ docker run -p 4222:4222 nats:latest -js
248
+ ```
249
+
250
+ Or use the NATS CLI:
251
+
252
+ ```bash
253
+ nats-server -js
254
+ ```
255
+
256
+ ## License
257
+
258
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ NatsTransport: () => NatsTransport
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/NatsTransport.ts
28
+ var import_nats = require("nats");
29
+ var import_crypto = require("crypto");
30
+ var sc = (0, import_nats.StringCodec)();
31
+ var NatsTransport = class {
32
+ nc = null;
33
+ jsm = null;
34
+ js = null;
35
+ options;
36
+ consumers = [];
37
+ started = false;
38
+ constructor(options) {
39
+ if (!options.connection && !options.connectionOptions) {
40
+ throw new Error(
41
+ "Either connection or connectionOptions must be provided"
42
+ );
43
+ }
44
+ this.options = {
45
+ subjectPrefix: "saga-bus",
46
+ streamName: "SAGA_BUS",
47
+ consumerPrefix: "saga-bus-consumer",
48
+ autoCreateStream: true,
49
+ retentionPolicy: "workqueue",
50
+ maxMessages: -1,
51
+ maxBytes: -1,
52
+ replicas: 1,
53
+ ackWait: 3e10,
54
+ // 30 seconds in nanoseconds
55
+ maxDeliver: 5,
56
+ ...options
57
+ };
58
+ }
59
+ async start() {
60
+ if (this.started) return;
61
+ if (this.options.connection) {
62
+ this.nc = this.options.connection;
63
+ } else {
64
+ this.nc = await (0, import_nats.connect)(this.options.connectionOptions);
65
+ }
66
+ this.jsm = await this.nc.jetstreamManager(this.options.jetStreamOptions);
67
+ this.js = this.nc.jetstream(this.options.jetStreamOptions);
68
+ if (this.options.autoCreateStream) {
69
+ await this.ensureStream();
70
+ }
71
+ this.started = true;
72
+ }
73
+ async stop() {
74
+ if (!this.started) return;
75
+ for (const consumer of this.consumers) {
76
+ consumer.stop();
77
+ }
78
+ this.consumers.length = 0;
79
+ if (!this.options.connection && this.nc) {
80
+ await this.nc.drain();
81
+ }
82
+ this.nc = null;
83
+ this.jsm = null;
84
+ this.js = null;
85
+ this.started = false;
86
+ }
87
+ async subscribe(options, handler) {
88
+ if (!this.js || !this.jsm) throw new Error("Transport not started");
89
+ const { endpoint, group } = options;
90
+ const subject = `${this.options.subjectPrefix}.${endpoint}.>`;
91
+ const consumerName = group ?? `${this.options.consumerPrefix}-${endpoint}`;
92
+ const consumerConfig = {
93
+ durable_name: consumerName,
94
+ filter_subject: subject,
95
+ ack_policy: import_nats.AckPolicy.Explicit,
96
+ deliver_policy: import_nats.DeliverPolicy.All,
97
+ ack_wait: this.options.ackWait,
98
+ max_deliver: this.options.maxDeliver
99
+ };
100
+ await this.jsm.consumers.add(this.options.streamName, consumerConfig);
101
+ const consumer = await this.js.consumers.get(
102
+ this.options.streamName,
103
+ consumerName
104
+ );
105
+ const messages = await consumer.consume();
106
+ (async () => {
107
+ for await (const msg of messages) {
108
+ try {
109
+ const rawEnvelope = JSON.parse(sc.decode(msg.data));
110
+ const envelope = {
111
+ id: rawEnvelope.id,
112
+ type: rawEnvelope.type,
113
+ payload: rawEnvelope.payload,
114
+ headers: rawEnvelope.headers,
115
+ timestamp: new Date(rawEnvelope.timestamp),
116
+ partitionKey: rawEnvelope.partitionKey
117
+ };
118
+ await handler(envelope);
119
+ msg.ack();
120
+ } catch (error) {
121
+ console.error("[NatsTransport] Message handler error:", error);
122
+ msg.nak();
123
+ }
124
+ }
125
+ })();
126
+ this.consumers.push({ stop: () => messages.stop() });
127
+ }
128
+ async publish(message, options) {
129
+ if (!this.js) throw new Error("Transport not started");
130
+ const { endpoint, key, headers: customHeaders = {}, delayMs } = options;
131
+ if (delayMs && delayMs > 0) {
132
+ throw new Error(
133
+ "NATS JetStream does not support delayed messages. Use an external scheduler for delayed delivery."
134
+ );
135
+ }
136
+ const subject = `${this.options.subjectPrefix}.${endpoint}.${message.type}`;
137
+ const envelope = {
138
+ id: (0, import_crypto.randomUUID)(),
139
+ type: message.type,
140
+ payload: message,
141
+ headers: customHeaders,
142
+ timestamp: /* @__PURE__ */ new Date(),
143
+ partitionKey: key
144
+ };
145
+ const h = (0, import_nats.headers)();
146
+ h.set("Nats-Msg-Id", envelope.id);
147
+ h.set("X-Message-Type", message.type);
148
+ if (key) h.set("X-Correlation-Id", key);
149
+ for (const [k, v] of Object.entries(customHeaders)) {
150
+ if (typeof v === "string") {
151
+ h.set(k, v);
152
+ }
153
+ }
154
+ const publishOptions = {
155
+ msgID: envelope.id,
156
+ headers: h
157
+ };
158
+ await this.js.publish(
159
+ subject,
160
+ sc.encode(JSON.stringify(envelope)),
161
+ publishOptions
162
+ );
163
+ }
164
+ async ensureStream() {
165
+ if (!this.jsm) return;
166
+ try {
167
+ await this.jsm.streams.info(this.options.streamName);
168
+ } catch {
169
+ const retentionMap = {
170
+ limits: import_nats.RetentionPolicy.Limits,
171
+ interest: import_nats.RetentionPolicy.Interest,
172
+ workqueue: import_nats.RetentionPolicy.Workqueue
173
+ };
174
+ await this.jsm.streams.add({
175
+ name: this.options.streamName,
176
+ subjects: [`${this.options.subjectPrefix}.>`],
177
+ retention: retentionMap[this.options.retentionPolicy],
178
+ max_msgs: this.options.maxMessages,
179
+ max_bytes: this.options.maxBytes,
180
+ num_replicas: this.options.replicas
181
+ });
182
+ }
183
+ }
184
+ };
185
+ // Annotate the CommonJS export names for ESM import in node:
186
+ 0 && (module.exports = {
187
+ NatsTransport
188
+ });
189
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/NatsTransport.ts"],"sourcesContent":["export { NatsTransport } from \"./NatsTransport.js\";\nexport type { NatsTransportOptions } from \"./types.js\";\n","import {\n connect,\n NatsConnection,\n JetStreamManager,\n JetStreamClient,\n JetStreamPublishOptions,\n ConsumerConfig,\n AckPolicy,\n DeliverPolicy,\n StringCodec,\n headers,\n RetentionPolicy,\n} from \"nats\";\nimport type {\n Transport,\n TransportSubscribeOptions,\n TransportPublishOptions,\n MessageEnvelope,\n BaseMessage,\n} from \"@saga-bus/core\";\nimport type { NatsTransportOptions } from \"./types.js\";\nimport { randomUUID } from \"crypto\";\n\nconst sc = StringCodec();\n\n/**\n * NATS JetStream transport for saga-bus.\n *\n * @example\n * ```typescript\n * import { NatsTransport } from \"@saga-bus/transport-nats\";\n *\n * const transport = new NatsTransport({\n * connectionOptions: { servers: \"localhost:4222\" },\n * streamName: \"SAGA_EVENTS\",\n * });\n *\n * await transport.start();\n * ```\n */\nexport class NatsTransport implements Transport {\n private nc: NatsConnection | null = null;\n private jsm: JetStreamManager | null = null;\n private js: JetStreamClient | null = null;\n private readonly options: Required<\n Pick<\n NatsTransportOptions,\n | \"subjectPrefix\"\n | \"streamName\"\n | \"consumerPrefix\"\n | \"autoCreateStream\"\n | \"retentionPolicy\"\n | \"maxMessages\"\n | \"maxBytes\"\n | \"replicas\"\n | \"ackWait\"\n | \"maxDeliver\"\n >\n > &\n NatsTransportOptions;\n\n private readonly consumers: Array<{ stop: () => void }> = [];\n private started = false;\n\n constructor(options: NatsTransportOptions) {\n if (!options.connection && !options.connectionOptions) {\n throw new Error(\n \"Either connection or connectionOptions must be provided\"\n );\n }\n\n this.options = {\n subjectPrefix: \"saga-bus\",\n streamName: \"SAGA_BUS\",\n consumerPrefix: \"saga-bus-consumer\",\n autoCreateStream: true,\n retentionPolicy: \"workqueue\",\n maxMessages: -1,\n maxBytes: -1,\n replicas: 1,\n ackWait: 30_000_000_000, // 30 seconds in nanoseconds\n maxDeliver: 5,\n ...options,\n };\n }\n\n async start(): Promise<void> {\n if (this.started) return;\n\n // Connect\n if (this.options.connection) {\n this.nc = this.options.connection;\n } else {\n this.nc = await connect(this.options.connectionOptions);\n }\n\n // Get JetStream manager and client\n this.jsm = await this.nc.jetstreamManager(this.options.jetStreamOptions);\n this.js = this.nc.jetstream(this.options.jetStreamOptions);\n\n // Create stream if needed\n if (this.options.autoCreateStream) {\n await this.ensureStream();\n }\n\n this.started = true;\n }\n\n async stop(): Promise<void> {\n if (!this.started) return;\n\n // Stop all consumers\n for (const consumer of this.consumers) {\n consumer.stop();\n }\n this.consumers.length = 0;\n\n // Close connection if we created it\n if (!this.options.connection && this.nc) {\n await this.nc.drain();\n }\n\n this.nc = null;\n this.jsm = null;\n this.js = null;\n this.started = false;\n }\n\n async subscribe<TMessage extends BaseMessage>(\n options: TransportSubscribeOptions,\n handler: (envelope: MessageEnvelope<TMessage>) => Promise<void>\n ): Promise<void> {\n if (!this.js || !this.jsm) throw new Error(\"Transport not started\");\n\n const { endpoint, group } = options;\n const subject = `${this.options.subjectPrefix}.${endpoint}.>`;\n const consumerName = group ?? `${this.options.consumerPrefix}-${endpoint}`;\n\n // Create durable consumer\n const consumerConfig: Partial<ConsumerConfig> = {\n durable_name: consumerName,\n filter_subject: subject,\n ack_policy: AckPolicy.Explicit,\n deliver_policy: DeliverPolicy.All,\n ack_wait: this.options.ackWait,\n max_deliver: this.options.maxDeliver,\n };\n\n await this.jsm.consumers.add(this.options.streamName, consumerConfig);\n\n // Subscribe\n const consumer = await this.js.consumers.get(\n this.options.streamName,\n consumerName\n );\n const messages = await consumer.consume();\n\n // Process messages asynchronously\n (async () => {\n for await (const msg of messages) {\n try {\n const rawEnvelope = JSON.parse(sc.decode(msg.data));\n const envelope: MessageEnvelope<TMessage> = {\n id: rawEnvelope.id,\n type: rawEnvelope.type,\n payload: rawEnvelope.payload as TMessage,\n headers: rawEnvelope.headers,\n timestamp: new Date(rawEnvelope.timestamp),\n partitionKey: rawEnvelope.partitionKey,\n };\n await handler(envelope);\n msg.ack();\n } catch (error) {\n console.error(\"[NatsTransport] Message handler error:\", error);\n msg.nak();\n }\n }\n })();\n\n this.consumers.push({ stop: () => messages.stop() });\n }\n\n async publish<TMessage extends BaseMessage>(\n message: TMessage,\n options: TransportPublishOptions\n ): Promise<void> {\n if (!this.js) throw new Error(\"Transport not started\");\n\n const { endpoint, key, headers: customHeaders = {}, delayMs } = options;\n\n // NATS JetStream doesn't support delayed messages natively\n if (delayMs && delayMs > 0) {\n throw new Error(\n \"NATS JetStream does not support delayed messages. \" +\n \"Use an external scheduler for delayed delivery.\"\n );\n }\n\n const subject = `${this.options.subjectPrefix}.${endpoint}.${message.type}`;\n\n // Create envelope\n const envelope: MessageEnvelope<TMessage> = {\n id: randomUUID(),\n type: message.type,\n payload: message,\n headers: customHeaders as Record<string, string>,\n timestamp: new Date(),\n partitionKey: key,\n };\n\n // Build headers\n const h = headers();\n h.set(\"Nats-Msg-Id\", envelope.id);\n h.set(\"X-Message-Type\", message.type);\n if (key) h.set(\"X-Correlation-Id\", key);\n for (const [k, v] of Object.entries(customHeaders)) {\n if (typeof v === \"string\") {\n h.set(k, v);\n }\n }\n\n // Publish\n const publishOptions: Partial<JetStreamPublishOptions> = {\n msgID: envelope.id,\n headers: h,\n };\n\n await this.js.publish(\n subject,\n sc.encode(JSON.stringify(envelope)),\n publishOptions\n );\n }\n\n private async ensureStream(): Promise<void> {\n if (!this.jsm) return;\n\n try {\n await this.jsm.streams.info(this.options.streamName);\n } catch {\n // Stream doesn't exist, create it\n const retentionMap: Record<string, RetentionPolicy> = {\n limits: RetentionPolicy.Limits,\n interest: RetentionPolicy.Interest,\n workqueue: RetentionPolicy.Workqueue,\n };\n\n await this.jsm.streams.add({\n name: this.options.streamName,\n subjects: [`${this.options.subjectPrefix}.>`],\n retention: retentionMap[this.options.retentionPolicy],\n max_msgs: this.options.maxMessages,\n max_bytes: this.options.maxBytes,\n num_replicas: this.options.replicas,\n });\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,68 @@
1
+ import { Transport, BaseMessage, TransportSubscribeOptions, MessageEnvelope, TransportPublishOptions } from '@saga-bus/core';
2
+ import { NatsConnection, ConnectionOptions, JetStreamOptions } from 'nats';
3
+
4
+ /**
5
+ * Configuration options for the NATS JetStream transport.
6
+ */
7
+ interface NatsTransportOptions {
8
+ /** Existing NATS connection */
9
+ connection?: NatsConnection;
10
+ /** Connection options for creating new connection */
11
+ connectionOptions?: ConnectionOptions;
12
+ /** JetStream options */
13
+ jetStreamOptions?: JetStreamOptions;
14
+ /** Subject prefix for all messages (default: "saga-bus") */
15
+ subjectPrefix?: string;
16
+ /** Stream name for JetStream (default: "SAGA_BUS") */
17
+ streamName?: string;
18
+ /** Consumer name prefix (default: "saga-bus-consumer") */
19
+ consumerPrefix?: string;
20
+ /** Whether to auto-create streams (default: true) */
21
+ autoCreateStream?: boolean;
22
+ /** Stream retention policy (default: "workqueue") */
23
+ retentionPolicy?: "limits" | "interest" | "workqueue";
24
+ /** Max messages in stream (-1 for unlimited, default: -1) */
25
+ maxMessages?: number;
26
+ /** Max bytes in stream (-1 for unlimited, default: -1) */
27
+ maxBytes?: number;
28
+ /** Max age of messages in nanoseconds */
29
+ maxAge?: number;
30
+ /** Number of replicas (default: 1) */
31
+ replicas?: number;
32
+ /** Ack wait timeout in nanoseconds (default: 30_000_000_000 = 30s) */
33
+ ackWait?: number;
34
+ /** Max redelivery attempts (default: 5) */
35
+ maxDeliver?: number;
36
+ }
37
+
38
+ /**
39
+ * NATS JetStream transport for saga-bus.
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * import { NatsTransport } from "@saga-bus/transport-nats";
44
+ *
45
+ * const transport = new NatsTransport({
46
+ * connectionOptions: { servers: "localhost:4222" },
47
+ * streamName: "SAGA_EVENTS",
48
+ * });
49
+ *
50
+ * await transport.start();
51
+ * ```
52
+ */
53
+ declare class NatsTransport implements Transport {
54
+ private nc;
55
+ private jsm;
56
+ private js;
57
+ private readonly options;
58
+ private readonly consumers;
59
+ private started;
60
+ constructor(options: NatsTransportOptions);
61
+ start(): Promise<void>;
62
+ stop(): Promise<void>;
63
+ subscribe<TMessage extends BaseMessage>(options: TransportSubscribeOptions, handler: (envelope: MessageEnvelope<TMessage>) => Promise<void>): Promise<void>;
64
+ publish<TMessage extends BaseMessage>(message: TMessage, options: TransportPublishOptions): Promise<void>;
65
+ private ensureStream;
66
+ }
67
+
68
+ export { NatsTransport, type NatsTransportOptions };
@@ -0,0 +1,68 @@
1
+ import { Transport, BaseMessage, TransportSubscribeOptions, MessageEnvelope, TransportPublishOptions } from '@saga-bus/core';
2
+ import { NatsConnection, ConnectionOptions, JetStreamOptions } from 'nats';
3
+
4
+ /**
5
+ * Configuration options for the NATS JetStream transport.
6
+ */
7
+ interface NatsTransportOptions {
8
+ /** Existing NATS connection */
9
+ connection?: NatsConnection;
10
+ /** Connection options for creating new connection */
11
+ connectionOptions?: ConnectionOptions;
12
+ /** JetStream options */
13
+ jetStreamOptions?: JetStreamOptions;
14
+ /** Subject prefix for all messages (default: "saga-bus") */
15
+ subjectPrefix?: string;
16
+ /** Stream name for JetStream (default: "SAGA_BUS") */
17
+ streamName?: string;
18
+ /** Consumer name prefix (default: "saga-bus-consumer") */
19
+ consumerPrefix?: string;
20
+ /** Whether to auto-create streams (default: true) */
21
+ autoCreateStream?: boolean;
22
+ /** Stream retention policy (default: "workqueue") */
23
+ retentionPolicy?: "limits" | "interest" | "workqueue";
24
+ /** Max messages in stream (-1 for unlimited, default: -1) */
25
+ maxMessages?: number;
26
+ /** Max bytes in stream (-1 for unlimited, default: -1) */
27
+ maxBytes?: number;
28
+ /** Max age of messages in nanoseconds */
29
+ maxAge?: number;
30
+ /** Number of replicas (default: 1) */
31
+ replicas?: number;
32
+ /** Ack wait timeout in nanoseconds (default: 30_000_000_000 = 30s) */
33
+ ackWait?: number;
34
+ /** Max redelivery attempts (default: 5) */
35
+ maxDeliver?: number;
36
+ }
37
+
38
+ /**
39
+ * NATS JetStream transport for saga-bus.
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * import { NatsTransport } from "@saga-bus/transport-nats";
44
+ *
45
+ * const transport = new NatsTransport({
46
+ * connectionOptions: { servers: "localhost:4222" },
47
+ * streamName: "SAGA_EVENTS",
48
+ * });
49
+ *
50
+ * await transport.start();
51
+ * ```
52
+ */
53
+ declare class NatsTransport implements Transport {
54
+ private nc;
55
+ private jsm;
56
+ private js;
57
+ private readonly options;
58
+ private readonly consumers;
59
+ private started;
60
+ constructor(options: NatsTransportOptions);
61
+ start(): Promise<void>;
62
+ stop(): Promise<void>;
63
+ subscribe<TMessage extends BaseMessage>(options: TransportSubscribeOptions, handler: (envelope: MessageEnvelope<TMessage>) => Promise<void>): Promise<void>;
64
+ publish<TMessage extends BaseMessage>(message: TMessage, options: TransportPublishOptions): Promise<void>;
65
+ private ensureStream;
66
+ }
67
+
68
+ export { NatsTransport, type NatsTransportOptions };
package/dist/index.js ADDED
@@ -0,0 +1,169 @@
1
+ // src/NatsTransport.ts
2
+ import {
3
+ connect,
4
+ AckPolicy,
5
+ DeliverPolicy,
6
+ StringCodec,
7
+ headers,
8
+ RetentionPolicy
9
+ } from "nats";
10
+ import { randomUUID } from "crypto";
11
+ var sc = StringCodec();
12
+ var NatsTransport = class {
13
+ nc = null;
14
+ jsm = null;
15
+ js = null;
16
+ options;
17
+ consumers = [];
18
+ started = false;
19
+ constructor(options) {
20
+ if (!options.connection && !options.connectionOptions) {
21
+ throw new Error(
22
+ "Either connection or connectionOptions must be provided"
23
+ );
24
+ }
25
+ this.options = {
26
+ subjectPrefix: "saga-bus",
27
+ streamName: "SAGA_BUS",
28
+ consumerPrefix: "saga-bus-consumer",
29
+ autoCreateStream: true,
30
+ retentionPolicy: "workqueue",
31
+ maxMessages: -1,
32
+ maxBytes: -1,
33
+ replicas: 1,
34
+ ackWait: 3e10,
35
+ // 30 seconds in nanoseconds
36
+ maxDeliver: 5,
37
+ ...options
38
+ };
39
+ }
40
+ async start() {
41
+ if (this.started) return;
42
+ if (this.options.connection) {
43
+ this.nc = this.options.connection;
44
+ } else {
45
+ this.nc = await connect(this.options.connectionOptions);
46
+ }
47
+ this.jsm = await this.nc.jetstreamManager(this.options.jetStreamOptions);
48
+ this.js = this.nc.jetstream(this.options.jetStreamOptions);
49
+ if (this.options.autoCreateStream) {
50
+ await this.ensureStream();
51
+ }
52
+ this.started = true;
53
+ }
54
+ async stop() {
55
+ if (!this.started) return;
56
+ for (const consumer of this.consumers) {
57
+ consumer.stop();
58
+ }
59
+ this.consumers.length = 0;
60
+ if (!this.options.connection && this.nc) {
61
+ await this.nc.drain();
62
+ }
63
+ this.nc = null;
64
+ this.jsm = null;
65
+ this.js = null;
66
+ this.started = false;
67
+ }
68
+ async subscribe(options, handler) {
69
+ if (!this.js || !this.jsm) throw new Error("Transport not started");
70
+ const { endpoint, group } = options;
71
+ const subject = `${this.options.subjectPrefix}.${endpoint}.>`;
72
+ const consumerName = group ?? `${this.options.consumerPrefix}-${endpoint}`;
73
+ const consumerConfig = {
74
+ durable_name: consumerName,
75
+ filter_subject: subject,
76
+ ack_policy: AckPolicy.Explicit,
77
+ deliver_policy: DeliverPolicy.All,
78
+ ack_wait: this.options.ackWait,
79
+ max_deliver: this.options.maxDeliver
80
+ };
81
+ await this.jsm.consumers.add(this.options.streamName, consumerConfig);
82
+ const consumer = await this.js.consumers.get(
83
+ this.options.streamName,
84
+ consumerName
85
+ );
86
+ const messages = await consumer.consume();
87
+ (async () => {
88
+ for await (const msg of messages) {
89
+ try {
90
+ const rawEnvelope = JSON.parse(sc.decode(msg.data));
91
+ const envelope = {
92
+ id: rawEnvelope.id,
93
+ type: rawEnvelope.type,
94
+ payload: rawEnvelope.payload,
95
+ headers: rawEnvelope.headers,
96
+ timestamp: new Date(rawEnvelope.timestamp),
97
+ partitionKey: rawEnvelope.partitionKey
98
+ };
99
+ await handler(envelope);
100
+ msg.ack();
101
+ } catch (error) {
102
+ console.error("[NatsTransport] Message handler error:", error);
103
+ msg.nak();
104
+ }
105
+ }
106
+ })();
107
+ this.consumers.push({ stop: () => messages.stop() });
108
+ }
109
+ async publish(message, options) {
110
+ if (!this.js) throw new Error("Transport not started");
111
+ const { endpoint, key, headers: customHeaders = {}, delayMs } = options;
112
+ if (delayMs && delayMs > 0) {
113
+ throw new Error(
114
+ "NATS JetStream does not support delayed messages. Use an external scheduler for delayed delivery."
115
+ );
116
+ }
117
+ const subject = `${this.options.subjectPrefix}.${endpoint}.${message.type}`;
118
+ const envelope = {
119
+ id: randomUUID(),
120
+ type: message.type,
121
+ payload: message,
122
+ headers: customHeaders,
123
+ timestamp: /* @__PURE__ */ new Date(),
124
+ partitionKey: key
125
+ };
126
+ const h = headers();
127
+ h.set("Nats-Msg-Id", envelope.id);
128
+ h.set("X-Message-Type", message.type);
129
+ if (key) h.set("X-Correlation-Id", key);
130
+ for (const [k, v] of Object.entries(customHeaders)) {
131
+ if (typeof v === "string") {
132
+ h.set(k, v);
133
+ }
134
+ }
135
+ const publishOptions = {
136
+ msgID: envelope.id,
137
+ headers: h
138
+ };
139
+ await this.js.publish(
140
+ subject,
141
+ sc.encode(JSON.stringify(envelope)),
142
+ publishOptions
143
+ );
144
+ }
145
+ async ensureStream() {
146
+ if (!this.jsm) return;
147
+ try {
148
+ await this.jsm.streams.info(this.options.streamName);
149
+ } catch {
150
+ const retentionMap = {
151
+ limits: RetentionPolicy.Limits,
152
+ interest: RetentionPolicy.Interest,
153
+ workqueue: RetentionPolicy.Workqueue
154
+ };
155
+ await this.jsm.streams.add({
156
+ name: this.options.streamName,
157
+ subjects: [`${this.options.subjectPrefix}.>`],
158
+ retention: retentionMap[this.options.retentionPolicy],
159
+ max_msgs: this.options.maxMessages,
160
+ max_bytes: this.options.maxBytes,
161
+ num_replicas: this.options.replicas
162
+ });
163
+ }
164
+ }
165
+ };
166
+ export {
167
+ NatsTransport
168
+ };
169
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/NatsTransport.ts"],"sourcesContent":["import {\n connect,\n NatsConnection,\n JetStreamManager,\n JetStreamClient,\n JetStreamPublishOptions,\n ConsumerConfig,\n AckPolicy,\n DeliverPolicy,\n StringCodec,\n headers,\n RetentionPolicy,\n} from \"nats\";\nimport type {\n Transport,\n TransportSubscribeOptions,\n TransportPublishOptions,\n MessageEnvelope,\n BaseMessage,\n} from \"@saga-bus/core\";\nimport type { NatsTransportOptions } from \"./types.js\";\nimport { randomUUID } from \"crypto\";\n\nconst sc = StringCodec();\n\n/**\n * NATS JetStream transport for saga-bus.\n *\n * @example\n * ```typescript\n * import { NatsTransport } from \"@saga-bus/transport-nats\";\n *\n * const transport = new NatsTransport({\n * connectionOptions: { servers: \"localhost:4222\" },\n * streamName: \"SAGA_EVENTS\",\n * });\n *\n * await transport.start();\n * ```\n */\nexport class NatsTransport implements Transport {\n private nc: NatsConnection | null = null;\n private jsm: JetStreamManager | null = null;\n private js: JetStreamClient | null = null;\n private readonly options: Required<\n Pick<\n NatsTransportOptions,\n | \"subjectPrefix\"\n | \"streamName\"\n | \"consumerPrefix\"\n | \"autoCreateStream\"\n | \"retentionPolicy\"\n | \"maxMessages\"\n | \"maxBytes\"\n | \"replicas\"\n | \"ackWait\"\n | \"maxDeliver\"\n >\n > &\n NatsTransportOptions;\n\n private readonly consumers: Array<{ stop: () => void }> = [];\n private started = false;\n\n constructor(options: NatsTransportOptions) {\n if (!options.connection && !options.connectionOptions) {\n throw new Error(\n \"Either connection or connectionOptions must be provided\"\n );\n }\n\n this.options = {\n subjectPrefix: \"saga-bus\",\n streamName: \"SAGA_BUS\",\n consumerPrefix: \"saga-bus-consumer\",\n autoCreateStream: true,\n retentionPolicy: \"workqueue\",\n maxMessages: -1,\n maxBytes: -1,\n replicas: 1,\n ackWait: 30_000_000_000, // 30 seconds in nanoseconds\n maxDeliver: 5,\n ...options,\n };\n }\n\n async start(): Promise<void> {\n if (this.started) return;\n\n // Connect\n if (this.options.connection) {\n this.nc = this.options.connection;\n } else {\n this.nc = await connect(this.options.connectionOptions);\n }\n\n // Get JetStream manager and client\n this.jsm = await this.nc.jetstreamManager(this.options.jetStreamOptions);\n this.js = this.nc.jetstream(this.options.jetStreamOptions);\n\n // Create stream if needed\n if (this.options.autoCreateStream) {\n await this.ensureStream();\n }\n\n this.started = true;\n }\n\n async stop(): Promise<void> {\n if (!this.started) return;\n\n // Stop all consumers\n for (const consumer of this.consumers) {\n consumer.stop();\n }\n this.consumers.length = 0;\n\n // Close connection if we created it\n if (!this.options.connection && this.nc) {\n await this.nc.drain();\n }\n\n this.nc = null;\n this.jsm = null;\n this.js = null;\n this.started = false;\n }\n\n async subscribe<TMessage extends BaseMessage>(\n options: TransportSubscribeOptions,\n handler: (envelope: MessageEnvelope<TMessage>) => Promise<void>\n ): Promise<void> {\n if (!this.js || !this.jsm) throw new Error(\"Transport not started\");\n\n const { endpoint, group } = options;\n const subject = `${this.options.subjectPrefix}.${endpoint}.>`;\n const consumerName = group ?? `${this.options.consumerPrefix}-${endpoint}`;\n\n // Create durable consumer\n const consumerConfig: Partial<ConsumerConfig> = {\n durable_name: consumerName,\n filter_subject: subject,\n ack_policy: AckPolicy.Explicit,\n deliver_policy: DeliverPolicy.All,\n ack_wait: this.options.ackWait,\n max_deliver: this.options.maxDeliver,\n };\n\n await this.jsm.consumers.add(this.options.streamName, consumerConfig);\n\n // Subscribe\n const consumer = await this.js.consumers.get(\n this.options.streamName,\n consumerName\n );\n const messages = await consumer.consume();\n\n // Process messages asynchronously\n (async () => {\n for await (const msg of messages) {\n try {\n const rawEnvelope = JSON.parse(sc.decode(msg.data));\n const envelope: MessageEnvelope<TMessage> = {\n id: rawEnvelope.id,\n type: rawEnvelope.type,\n payload: rawEnvelope.payload as TMessage,\n headers: rawEnvelope.headers,\n timestamp: new Date(rawEnvelope.timestamp),\n partitionKey: rawEnvelope.partitionKey,\n };\n await handler(envelope);\n msg.ack();\n } catch (error) {\n console.error(\"[NatsTransport] Message handler error:\", error);\n msg.nak();\n }\n }\n })();\n\n this.consumers.push({ stop: () => messages.stop() });\n }\n\n async publish<TMessage extends BaseMessage>(\n message: TMessage,\n options: TransportPublishOptions\n ): Promise<void> {\n if (!this.js) throw new Error(\"Transport not started\");\n\n const { endpoint, key, headers: customHeaders = {}, delayMs } = options;\n\n // NATS JetStream doesn't support delayed messages natively\n if (delayMs && delayMs > 0) {\n throw new Error(\n \"NATS JetStream does not support delayed messages. \" +\n \"Use an external scheduler for delayed delivery.\"\n );\n }\n\n const subject = `${this.options.subjectPrefix}.${endpoint}.${message.type}`;\n\n // Create envelope\n const envelope: MessageEnvelope<TMessage> = {\n id: randomUUID(),\n type: message.type,\n payload: message,\n headers: customHeaders as Record<string, string>,\n timestamp: new Date(),\n partitionKey: key,\n };\n\n // Build headers\n const h = headers();\n h.set(\"Nats-Msg-Id\", envelope.id);\n h.set(\"X-Message-Type\", message.type);\n if (key) h.set(\"X-Correlation-Id\", key);\n for (const [k, v] of Object.entries(customHeaders)) {\n if (typeof v === \"string\") {\n h.set(k, v);\n }\n }\n\n // Publish\n const publishOptions: Partial<JetStreamPublishOptions> = {\n msgID: envelope.id,\n headers: h,\n };\n\n await this.js.publish(\n subject,\n sc.encode(JSON.stringify(envelope)),\n publishOptions\n );\n }\n\n private async ensureStream(): Promise<void> {\n if (!this.jsm) return;\n\n try {\n await this.jsm.streams.info(this.options.streamName);\n } catch {\n // Stream doesn't exist, create it\n const retentionMap: Record<string, RetentionPolicy> = {\n limits: RetentionPolicy.Limits,\n interest: RetentionPolicy.Interest,\n workqueue: RetentionPolicy.Workqueue,\n };\n\n await this.jsm.streams.add({\n name: this.options.streamName,\n subjects: [`${this.options.subjectPrefix}.>`],\n retention: retentionMap[this.options.retentionPolicy],\n max_msgs: this.options.maxMessages,\n max_bytes: this.options.maxBytes,\n num_replicas: this.options.replicas,\n });\n }\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@saga-bus/transport-nats",
3
+ "version": "0.1.2",
4
+ "description": "NATS JetStream transport for saga-bus",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "dev": "tsup --watch",
23
+ "lint": "eslint src/",
24
+ "check-types": "tsc --noEmit",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest"
27
+ },
28
+ "dependencies": {
29
+ "@saga-bus/core": "workspace:*"
30
+ },
31
+ "peerDependencies": {
32
+ "nats": ">=2.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@repo/eslint-config": "workspace:*",
36
+ "@repo/typescript-config": "workspace:*",
37
+ "@types/node": "^22.10.1",
38
+ "eslint": "^9.16.0",
39
+ "tsup": "^8.3.5",
40
+ "typescript": "^5.7.2",
41
+ "vitest": "^2.1.8"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "https://github.com/d-e-a-n-f/saga-bus.git",
50
+ "directory": "packages/transport-nats"
51
+ },
52
+ "bugs": {
53
+ "url": "https://github.com/d-e-a-n-f/saga-bus/issues"
54
+ },
55
+ "homepage": "https://github.com/d-e-a-n-f/saga-bus#readme"
56
+ }