@motiadev/adapter-rabbitmq-events 0.8.2-beta.140-709523

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/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - 2025-10-22
4
+
5
+ ### Added
6
+ - Initial release of RabbitMQ event adapter for Motia
7
+ - Support for distributed event handling across multiple instances
8
+ - Topic-based message routing with configurable exchange types
9
+ - Durable queues for message persistence
10
+ - Automatic reconnection on connection failures
11
+ - Message acknowledgment for reliability
12
+ - Configurable prefetch for load balancing
13
+ - Full TypeScript support with type definitions
14
+ - Comprehensive documentation and examples
15
+
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Motia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # @motiadev/adapter-rabbitmq-events
2
+
3
+ RabbitMQ event adapter for Motia framework, enabling distributed event handling across multiple instances.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @motiadev/adapter-rabbitmq-events
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Configure the RabbitMQ adapter in your `motia.config.ts`:
14
+
15
+ ```typescript
16
+ import { config } from '@motiadev/core'
17
+ import { RabbitMQEventAdapter } from '@motiadev/adapter-rabbitmq-events'
18
+
19
+ export default config({
20
+ adapters: {
21
+ events: new RabbitMQEventAdapter({
22
+ url: process.env.RABBITMQ_URL || 'amqp://localhost',
23
+ exchangeName: 'motia.events',
24
+ exchangeType: 'topic',
25
+ durable: true,
26
+ }),
27
+ },
28
+ })
29
+ ```
30
+
31
+ ## Configuration Options
32
+
33
+ ### RabbitMQEventAdapterConfig
34
+
35
+ | Option | Type | Default | Description |
36
+ |--------|------|---------|-------------|
37
+ | `url` | `string` | Required | RabbitMQ connection URL (e.g., `amqp://localhost`) |
38
+ | `exchangeName` | `string` | Required | Name of the exchange to use |
39
+ | `exchangeType` | `'direct' \| 'topic' \| 'fanout' \| 'headers'` | Required | Type of exchange |
40
+ | `durable` | `boolean` | `true` | Whether the exchange should survive broker restarts |
41
+ | `autoDelete` | `boolean` | `false` | Whether to delete the exchange when all queues are unbound |
42
+ | `connectionTimeout` | `number` | `10000` | Connection timeout in milliseconds |
43
+ | `reconnectDelay` | `number` | `5000` | Delay before attempting reconnection in milliseconds |
44
+ | `prefetch` | `number` | `10` | Number of messages to prefetch |
45
+
46
+ ## Features
47
+
48
+ - **Topic-based Routing**: Flexible event routing using RabbitMQ exchanges
49
+ - **Durable Messaging**: Messages persist across broker restarts
50
+ - **Automatic Reconnection**: Handles connection failures gracefully
51
+ - **Message Acknowledgment**: Ensures reliable message delivery
52
+ - **Load Balancing**: Distributes events across multiple instances
53
+ - **Dead Letter Queues**: Failed messages are properly handled
54
+
55
+ ## Example
56
+
57
+ ```typescript
58
+ import { RabbitMQEventAdapter } from '@motiadev/adapter-rabbitmq-events'
59
+
60
+ const adapter = new RabbitMQEventAdapter({
61
+ url: 'amqp://user:password@rabbitmq.example.com:5672',
62
+ exchangeName: 'motia.events',
63
+ exchangeType: 'topic',
64
+ durable: true,
65
+ prefetch: 20,
66
+ })
67
+ ```
68
+
69
+ ## Environment Variables
70
+
71
+ ```bash
72
+ RABBITMQ_URL=amqp://localhost:5672
73
+ RABBITMQ_EXCHANGE=motia.events
74
+ ```
75
+
76
+ ## Performance Considerations
77
+
78
+ - Use topic exchanges for flexible routing patterns
79
+ - Set appropriate prefetch values based on your workload
80
+ - Enable message persistence for critical events
81
+ - Monitor queue depths to detect processing bottlenecks
82
+
83
+ ## Troubleshooting
84
+
85
+ ### Connection Issues
86
+
87
+ If you experience connection problems:
88
+ 1. Verify RabbitMQ is running and accessible
89
+ 2. Check your connection URL and credentials
90
+ 3. Ensure firewall rules allow connections on port 5672
91
+ 4. Review RabbitMQ logs for errors
92
+
93
+ ### Message Delivery
94
+
95
+ If messages are not being delivered:
96
+ 1. Verify exchange and queue bindings
97
+ 2. Check message acknowledgment settings
98
+ 3. Review consumer prefetch settings
99
+ 4. Monitor dead letter queues for failed messages
100
+
101
+ ## License
102
+
103
+ MIT
104
+
@@ -0,0 +1,3 @@
1
+ export { RabbitMQEventAdapter } from './rabbitmq-event-adapter';
2
+ export type { RabbitMQEventAdapterConfig, RabbitMQSubscribeOptions } from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAC/D,YAAY,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RabbitMQEventAdapter = void 0;
4
+ var rabbitmq_event_adapter_1 = require("./rabbitmq-event-adapter");
5
+ Object.defineProperty(exports, "RabbitMQEventAdapter", { enumerable: true, get: function () { return rabbitmq_event_adapter_1.RabbitMQEventAdapter; } });
@@ -0,0 +1,21 @@
1
+ import type { Event, EventAdapter, QueueConfig, SubscriptionHandle } from '@motiadev/core';
2
+ import type { RabbitMQEventAdapterConfig } from './types';
3
+ export declare class RabbitMQEventAdapter implements EventAdapter {
4
+ private connection;
5
+ private channel;
6
+ private config;
7
+ private subscriptions;
8
+ private reconnecting;
9
+ private shutdownRequested;
10
+ constructor(config: RabbitMQEventAdapterConfig);
11
+ private ensureConnection;
12
+ private connect;
13
+ private handleConnectionError;
14
+ emit<TData>(event: Event<TData>): Promise<void>;
15
+ subscribe<TData>(topic: string, stepName: string, handler: (event: Event<TData>) => void | Promise<void>, options?: QueueConfig): Promise<SubscriptionHandle>;
16
+ unsubscribe(handle: SubscriptionHandle): Promise<void>;
17
+ shutdown(): Promise<void>;
18
+ getSubscriptionCount(topic: string): Promise<number>;
19
+ listTopics(): Promise<string[]>;
20
+ }
21
+ //# sourceMappingURL=rabbitmq-event-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rabbitmq-event-adapter.d.ts","sourceRoot":"","sources":["../src/rabbitmq-event-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAG1F,OAAO,KAAK,EAAE,0BAA0B,EAA4B,MAAM,SAAS,CAAA;AAEnF,qBAAa,oBAAqB,YAAW,YAAY;IACvD,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,MAAM,CAAsC;IACpD,OAAO,CAAC,aAAa,CAA6C;IAClE,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,iBAAiB,CAAQ;gBAErB,MAAM,EAAE,0BAA0B;YAWhC,gBAAgB;YAUhB,OAAO;YA4CP,qBAAqB;IAuB7B,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB/C,SAAS,CAAC,KAAK,EACnB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACtD,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,kBAAkB,CAAC;IA4ExB,WAAW,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMtD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBzB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIpD,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;CAGtC"}
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RabbitMQEventAdapter = void 0;
7
+ const amqplib_1 = __importDefault(require("amqplib"));
8
+ class RabbitMQEventAdapter {
9
+ constructor(config) {
10
+ this.connection = null;
11
+ this.channel = null;
12
+ this.subscriptions = new Map();
13
+ this.reconnecting = false;
14
+ this.shutdownRequested = false;
15
+ this.config = {
16
+ durable: true,
17
+ autoDelete: false,
18
+ connectionTimeout: 10000,
19
+ reconnectDelay: 5000,
20
+ prefetch: 10,
21
+ ...config,
22
+ };
23
+ }
24
+ async ensureConnection() {
25
+ if (!this.connection || !this.channel) {
26
+ await this.connect();
27
+ }
28
+ if (!this.channel) {
29
+ throw new Error('Failed to establish channel');
30
+ }
31
+ return this.channel;
32
+ }
33
+ async connect() {
34
+ try {
35
+ this.connection = await amqplib_1.default.connect(this.config.url, {
36
+ timeout: this.config.connectionTimeout,
37
+ });
38
+ if (this.connection) {
39
+ this.connection.on('error', (err) => {
40
+ console.error('[RabbitMQ] Connection error:', err);
41
+ this.handleConnectionError();
42
+ });
43
+ this.connection.on('close', () => {
44
+ console.warn('[RabbitMQ] Connection closed');
45
+ this.handleConnectionError();
46
+ });
47
+ this.channel = await this.connection.createChannel();
48
+ if (this.channel) {
49
+ this.channel.on('error', (err) => {
50
+ console.error('[RabbitMQ] Channel error:', err);
51
+ });
52
+ this.channel.on('close', () => {
53
+ console.warn('[RabbitMQ] Channel closed');
54
+ });
55
+ await this.channel.assertExchange(this.config.exchangeName, this.config.exchangeType, {
56
+ durable: this.config.durable,
57
+ autoDelete: this.config.autoDelete,
58
+ });
59
+ if (this.config.prefetch) {
60
+ await this.channel.prefetch(this.config.prefetch);
61
+ }
62
+ }
63
+ }
64
+ }
65
+ catch (error) {
66
+ console.error('[RabbitMQ] Failed to connect:', error);
67
+ throw error;
68
+ }
69
+ }
70
+ async handleConnectionError() {
71
+ if (this.shutdownRequested || this.reconnecting) {
72
+ return;
73
+ }
74
+ this.reconnecting = true;
75
+ this.connection = null;
76
+ this.channel = null;
77
+ console.log(`[RabbitMQ] Attempting to reconnect in ${this.config.reconnectDelay}ms...`);
78
+ setTimeout(async () => {
79
+ try {
80
+ await this.connect();
81
+ this.reconnecting = false;
82
+ }
83
+ catch (error) {
84
+ console.error('[RabbitMQ] Reconnection failed:', error);
85
+ this.reconnecting = false;
86
+ this.handleConnectionError();
87
+ }
88
+ }, this.config.reconnectDelay);
89
+ }
90
+ async emit(event) {
91
+ const channel = await this.ensureConnection();
92
+ const message = {
93
+ topic: event.topic,
94
+ data: event.data,
95
+ traceId: event.traceId,
96
+ flows: event.flows,
97
+ messageGroupId: event.messageGroupId,
98
+ timestamp: Date.now(),
99
+ };
100
+ const content = Buffer.from(JSON.stringify(message));
101
+ const published = channel.publish(this.config.exchangeName, event.topic, content, {
102
+ persistent: this.config.durable,
103
+ contentType: 'application/json',
104
+ timestamp: Date.now(),
105
+ });
106
+ if (!published) {
107
+ throw new Error(`Failed to publish message to RabbitMQ for topic: ${event.topic}`);
108
+ }
109
+ }
110
+ async subscribe(topic, stepName, handler, options) {
111
+ const channel = await this.ensureConnection();
112
+ const queueName = `motia.${topic}.${stepName}`;
113
+ const subscribeOptions = {
114
+ durable: this.config.durable,
115
+ exclusive: false,
116
+ prefetch: this.config.prefetch,
117
+ };
118
+ const queueArgs = {};
119
+ if (options?.visibilityTimeout && options.visibilityTimeout > 0) {
120
+ queueArgs['x-consumer-timeout'] = options.visibilityTimeout * 1000;
121
+ }
122
+ if (options?.type === 'fifo') {
123
+ queueArgs['x-single-active-consumer'] = true;
124
+ }
125
+ const queue = await channel.assertQueue(queueName, {
126
+ durable: subscribeOptions.durable ?? true,
127
+ exclusive: subscribeOptions.exclusive ?? false,
128
+ autoDelete: !subscribeOptions.durable,
129
+ arguments: Object.keys(queueArgs).length > 0 ? queueArgs : undefined,
130
+ });
131
+ await channel.bindQueue(queue.queue, this.config.exchangeName, topic);
132
+ if (subscribeOptions.prefetch) {
133
+ await channel.prefetch(subscribeOptions.prefetch);
134
+ }
135
+ const consumerTag = await channel.consume(queue.queue, async (msg) => {
136
+ if (!msg)
137
+ return;
138
+ try {
139
+ const retryCount = msg.properties.headers?.['x-retry-count'] || 0;
140
+ if (options?.maxRetries && retryCount >= options.maxRetries) {
141
+ console.warn(`[RabbitMQ] Message exceeded max retries (${options.maxRetries})`);
142
+ channel.nack(msg, false, false);
143
+ return;
144
+ }
145
+ const content = JSON.parse(msg.content.toString());
146
+ await handler(content);
147
+ channel.ack(msg);
148
+ }
149
+ catch (error) {
150
+ console.error('[RabbitMQ] Error processing message:', error);
151
+ if (options?.maxRetries) {
152
+ const retryCount = msg.properties.headers?.['x-retry-count'] || 0;
153
+ if (retryCount < options.maxRetries) {
154
+ const headers = { ...msg.properties.headers, 'x-retry-count': retryCount + 1 };
155
+ channel.publish(this.config.exchangeName, topic, msg.content, { ...msg.properties, headers });
156
+ }
157
+ }
158
+ channel.nack(msg, false, false);
159
+ }
160
+ });
161
+ const handle = {
162
+ topic,
163
+ id: consumerTag.consumerTag,
164
+ unsubscribe: async () => {
165
+ await this.unsubscribe(handle);
166
+ },
167
+ };
168
+ this.subscriptions.set(handle.id, handle);
169
+ return handle;
170
+ }
171
+ async unsubscribe(handle) {
172
+ const channel = await this.ensureConnection();
173
+ await channel.cancel(handle.id);
174
+ this.subscriptions.delete(handle.id);
175
+ }
176
+ async shutdown() {
177
+ this.shutdownRequested = true;
178
+ if (this.channel) {
179
+ try {
180
+ await this.channel.close();
181
+ }
182
+ catch (error) {
183
+ console.error('[RabbitMQ] Error closing channel:', error);
184
+ }
185
+ }
186
+ if (this.connection) {
187
+ try {
188
+ const conn = this.connection;
189
+ if (typeof conn.close === 'function') {
190
+ await conn.close();
191
+ }
192
+ }
193
+ catch (error) {
194
+ console.error('[RabbitMQ] Error closing connection:', error);
195
+ }
196
+ }
197
+ this.subscriptions.clear();
198
+ }
199
+ async getSubscriptionCount(topic) {
200
+ return Array.from(this.subscriptions.values()).filter((sub) => sub.topic === topic).length;
201
+ }
202
+ async listTopics() {
203
+ return Array.from(new Set(Array.from(this.subscriptions.values()).map((sub) => sub.topic)));
204
+ }
205
+ }
206
+ exports.RabbitMQEventAdapter = RabbitMQEventAdapter;
@@ -0,0 +1,17 @@
1
+ export interface RabbitMQEventAdapterConfig {
2
+ url: string;
3
+ exchangeName: string;
4
+ exchangeType: 'direct' | 'topic' | 'fanout' | 'headers';
5
+ durable?: boolean;
6
+ autoDelete?: boolean;
7
+ connectionTimeout?: number;
8
+ reconnectDelay?: number;
9
+ prefetch?: number;
10
+ }
11
+ export interface RabbitMQSubscribeOptions {
12
+ queue?: string;
13
+ exclusive?: boolean;
14
+ durable?: boolean;
15
+ prefetch?: number;
16
+ }
17
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,0BAA0B;IACzC,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAA;IACvD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@motiadev/adapter-rabbitmq-events",
3
+ "description": "RabbitMQ event adapter for Motia framework, enabling distributed event handling across multiple instances.",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "version": "0.8.2-beta.140-709523",
7
+ "dependencies": {
8
+ "amqplib": "^0.10.4",
9
+ "uuid": "^11.1.0",
10
+ "@motiadev/core": "0.8.2-beta.140-709523"
11
+ },
12
+ "devDependencies": {
13
+ "@types/amqplib": "^0.10.5",
14
+ "typescript": "^5.7.2"
15
+ },
16
+ "peerDependencies": {
17
+ "@motiadev/core": "^0.8.0"
18
+ },
19
+ "scripts": {
20
+ "build": "rm -rf dist && tsc",
21
+ "lint": "biome check .",
22
+ "watch": "tsc --watch"
23
+ }
24
+ }