runmq 0.0.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +470 -0
- package/dist/index.cjs +796 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +133 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.js +762 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -8
- package/.vscode/settings.json +0 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 RunMQ
|
|
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,470 @@
|
|
|
1
|
+
# RunMQ
|
|
2
|
+
|
|
3
|
+
RunMQ is a robust message queue library for Node.js built on RabbitMQ. Enables async background processing, or event-driven architectures via message bus — both with automatic retries, schema validation and DLQ.
|
|
4
|
+
|
|
5
|
+
RunMQ can be used to implement two main patterns:
|
|
6
|
+
- **Event Bus** for event-driven microservices architectures, where multiple services independently react to the same events
|
|
7
|
+
- **Job Queue** for async background task processing, with retries and dead letter queues
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Automatic Connection Management**: Built-in retry logic with configurable attempts and delays
|
|
12
|
+
- **Message Processing with Retries**: Automatic retry mechanism for failed messages with configurable retry delays
|
|
13
|
+
- **Dead Letter Queue (DLQ) Support**: Failed messages automatically move to DLQ after exhausting retry attempts
|
|
14
|
+
- **Isolated Queues**: Each processor maintains its own queue and DLQ, ensuring complete isolation between services
|
|
15
|
+
- **Schema Validation**: Optional message validation using JSON Schema (AJV)
|
|
16
|
+
- **Concurrent Processing**: Support for multiple concurrent consumers per queue
|
|
17
|
+
- **Correlation ID Support**: Built-in correlation ID generation and tracking for distributed tracing
|
|
18
|
+
- **Custom Logging**: Pluggable logging interface with default console logger
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install runmq
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Architecture Overview
|
|
27
|
+
|
|
28
|
+
RunMQ can be used to implement various messaging patterns. Here are two common architectures:
|
|
29
|
+
|
|
30
|
+
### 1. Event-Driven Architecture (Event Bus Pattern)
|
|
31
|
+
|
|
32
|
+
In this pattern, multiple processors (or services) subscribe to the same event topic. Each processor gets its own isolated queue and DLQ, enabling true microservices autonomy.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Publisher → Topic (user.created)
|
|
36
|
+
├→ Queue: emailService → DLQ: emailService_dlq
|
|
37
|
+
├→ Queue: analyticsService → DLQ: analyticsService_dlq
|
|
38
|
+
└→ Queue: notificationService → DLQ: notificationService_dlq
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Key Benefits:**
|
|
42
|
+
- Services remain independent and isolated
|
|
43
|
+
- Each service can fail/retry without affecting others
|
|
44
|
+
- Easy to add new services by subscribing to existing events
|
|
45
|
+
- Natural implementation of CQRS and event sourcing patterns
|
|
46
|
+
|
|
47
|
+
### 2. Background Processing Pattern
|
|
48
|
+
|
|
49
|
+
RunMQ can also be used as a job queue for background processing tasks. A single worker service processes jobs from a dedicated queue with retries and DLQ support.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
Publisher → Topic (email.send) → Queue: emailWorker → DLQ: emailWorker_dlq
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Key Benefits:**
|
|
56
|
+
- Simple async job processing
|
|
57
|
+
- Automatic retries for failed jobs
|
|
58
|
+
- Scalable with multiple concurrent workers
|
|
59
|
+
- Dead letter queue for failed job analysis
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
### Basic Setup
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { RunMQ } from 'runmq';
|
|
67
|
+
|
|
68
|
+
// 1. Initialize RunMQ
|
|
69
|
+
const runMQ = await RunMQ.start({
|
|
70
|
+
url: 'amqp://localhost:5672',
|
|
71
|
+
reconnectDelay: 5000, // optional, default: 5000ms
|
|
72
|
+
maxReconnectAttempts: 5 // optional, default: 5
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// 2. Process messages (create a consumer)
|
|
76
|
+
await runMQ.process('user.created', {
|
|
77
|
+
name: 'emailService', // Unique processor name (creates isolated queue)
|
|
78
|
+
consumersCount: 2, // Number of concurrent workers
|
|
79
|
+
attempts: 3, // Try processing a message up to 3 times
|
|
80
|
+
attemptsDelay: 2000 // Wait 2 seconds between retries
|
|
81
|
+
}, async (message) => {
|
|
82
|
+
// Your processing logic here
|
|
83
|
+
console.log('Received:', message.message);
|
|
84
|
+
await sendEmail(message.message);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// 3. Publish messages
|
|
88
|
+
runMQ.publish('user.created', {
|
|
89
|
+
userId: '123',
|
|
90
|
+
email: 'user@example.com',
|
|
91
|
+
name: 'John Doe'
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// That's it! The message will be delivered to all processors subscribed to 'user.created'
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Event-Driven Architecture Example
|
|
98
|
+
|
|
99
|
+
One of the most powerful patterns with RunMQ is the Event Bus pattern, where multiple services independently react to the same events.
|
|
100
|
+
The main advantage is that each service has its own isolated queue and dead letter queue, allowing for true microservices autonomy
|
|
101
|
+
Publishing a single message (event) results in multiple services receiving and processing it independently.
|
|
102
|
+
|
|
103
|
+
### Scenario: User Registration System
|
|
104
|
+
|
|
105
|
+
When a user registers, multiple services need to react independently.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { RunMQ, RunMQMessage } from 'runmq';
|
|
109
|
+
|
|
110
|
+
interface UserCreatedEvent {
|
|
111
|
+
userId: string;
|
|
112
|
+
email: string;
|
|
113
|
+
name: string;
|
|
114
|
+
createdAt: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Initialize RunMQ in each service
|
|
118
|
+
const runMQ = await RunMQ.start({
|
|
119
|
+
url: 'amqp://localhost:5672'
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ============================================
|
|
123
|
+
// SERVICE 1: Email Service
|
|
124
|
+
// ============================================
|
|
125
|
+
await runMQ.process<UserCreatedEvent>('user.created', {
|
|
126
|
+
name: 'emailService', // Creates queue: emailService
|
|
127
|
+
consumersCount: 2,
|
|
128
|
+
attempts: 3,
|
|
129
|
+
attemptsDelay: 2000
|
|
130
|
+
}, async (message: RunMQMessage<UserCreatedEvent>) => {
|
|
131
|
+
console.log(`[Email Service] Sending welcome email to ${message.message.email}`);
|
|
132
|
+
await sendWelcomeEmail(message.message);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ============================================
|
|
136
|
+
// SERVICE 2: Analytics Service
|
|
137
|
+
// ============================================
|
|
138
|
+
await runMQ.process<UserCreatedEvent>('user.created', {
|
|
139
|
+
name: 'analyticsService', // Creates queue: analyticsService
|
|
140
|
+
consumersCount: 1,
|
|
141
|
+
attempts: 3
|
|
142
|
+
}, async (message: RunMQMessage<UserCreatedEvent>) => {
|
|
143
|
+
console.log(`[Analytics] Recording user registration for ${message.message.userId}`);
|
|
144
|
+
await trackUserRegistration(message.message);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ============================================
|
|
148
|
+
// SERVICE 3: Notification Service
|
|
149
|
+
// ============================================
|
|
150
|
+
await runMQ.process<UserCreatedEvent>('user.created', {
|
|
151
|
+
name: 'notificationService', // Creates queue: notificationService
|
|
152
|
+
consumersCount: 3,
|
|
153
|
+
attempts: 5,
|
|
154
|
+
attemptsDelay: 1000
|
|
155
|
+
}, async (message: RunMQMessage<UserCreatedEvent>) => {
|
|
156
|
+
console.log(`[Notifications] Sending push notification to ${message.message.userId}`);
|
|
157
|
+
await sendPushNotification(message.message);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// ============================================
|
|
161
|
+
// PUBLISHER: User Registration Handler
|
|
162
|
+
// ============================================
|
|
163
|
+
// When a user registers, publish one event
|
|
164
|
+
runMQ.publish('user.created', {
|
|
165
|
+
userId: 'user-123',
|
|
166
|
+
email: 'john@example.com',
|
|
167
|
+
name: 'John Doe',
|
|
168
|
+
createdAt: new Date().toISOString()
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// All three services receive the event independently!
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Adding a New Processor
|
|
175
|
+
|
|
176
|
+
Want to add a new service? Just subscribe to existing events:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// NEW SERVICE 4: CRM Sync Service
|
|
180
|
+
await runMQ.process<UserCreatedEvent>('user.created', {
|
|
181
|
+
name: 'crmSyncService', // Creates new isolated queue
|
|
182
|
+
consumersCount: 1,
|
|
183
|
+
attempts: 3
|
|
184
|
+
}, async (message: RunMQMessage<UserCreatedEvent>) => {
|
|
185
|
+
console.log(`[CRM] Syncing user to CRM: ${message.message.userId}`);
|
|
186
|
+
await syncToCRM(message.message);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// This new service automatically receives all future user.created events
|
|
190
|
+
// No changes needed to existing services!
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Job Queue Pattern Example
|
|
194
|
+
|
|
195
|
+
### Scenario: Background Email Processing
|
|
196
|
+
|
|
197
|
+
Use RunMQ for async job processing with a single worker service.
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { RunMQ, RunMQMessage } from 'runmq';
|
|
201
|
+
|
|
202
|
+
interface EmailJob {
|
|
203
|
+
to: string;
|
|
204
|
+
subject: string;
|
|
205
|
+
body: string;
|
|
206
|
+
attachments?: string[];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const runMQ = await RunMQ.start({
|
|
210
|
+
url: 'amqp://localhost:5672'
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// ============================================
|
|
214
|
+
// WORKER: Email Processing Service
|
|
215
|
+
// ============================================
|
|
216
|
+
await runMQ.process<EmailJob>('email.send', {
|
|
217
|
+
name: 'emailWorker', // Single queue for job processing
|
|
218
|
+
consumersCount: 5, // 5 concurrent workers
|
|
219
|
+
attempts: 3,
|
|
220
|
+
attemptsDelay: 5000,
|
|
221
|
+
messageSchema: {
|
|
222
|
+
type: 'ajv',
|
|
223
|
+
schema: {
|
|
224
|
+
type: 'object',
|
|
225
|
+
properties: {
|
|
226
|
+
to: { type: 'string', format: 'email' },
|
|
227
|
+
subject: { type: 'string' },
|
|
228
|
+
body: { type: 'string' },
|
|
229
|
+
attachments: {
|
|
230
|
+
type: 'array',
|
|
231
|
+
items: { type: 'string' }
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
required: ['to', 'subject', 'body']
|
|
235
|
+
},
|
|
236
|
+
failureStrategy: 'dlq'
|
|
237
|
+
}
|
|
238
|
+
}, async (message: RunMQMessage<EmailJob>) => {
|
|
239
|
+
console.log(`[Worker] Sending email to ${message.message.to}`);
|
|
240
|
+
|
|
241
|
+
await sendEmail({
|
|
242
|
+
to: message.message.to,
|
|
243
|
+
subject: message.message.subject,
|
|
244
|
+
body: message.message.body,
|
|
245
|
+
attachments: message.message.attachments
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
console.log(`[Worker] Email sent successfully to ${message.message.to}`);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// ============================================
|
|
252
|
+
// PUBLISHER: API Endpoint
|
|
253
|
+
// ============================================
|
|
254
|
+
// Your API can now queue emails for background processing
|
|
255
|
+
app.post('/api/send-email', async (req, res) => {
|
|
256
|
+
const { to, subject, body } = req.body;
|
|
257
|
+
|
|
258
|
+
// Queue the job - returns immediately
|
|
259
|
+
runMQ.publish('email.send', {
|
|
260
|
+
to,
|
|
261
|
+
subject,
|
|
262
|
+
body,
|
|
263
|
+
attachments: []
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
res.json({ status: 'queued' });
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Job Processing Flow
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
API Request → Publish Job → Queue (emailWorker)
|
|
274
|
+
↓
|
|
275
|
+
5 Concurrent Workers
|
|
276
|
+
↓
|
|
277
|
+
[Success] or [Try processing for 3 times]
|
|
278
|
+
↓
|
|
279
|
+
[Final Failure] → DLQ (emailWorker_dlq)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Advanced Examples
|
|
283
|
+
|
|
284
|
+
### Event Choreography with Multiple Events
|
|
285
|
+
|
|
286
|
+
Build complex workflows by publishing new events from processors:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
// Order Service - publishes order.placed
|
|
290
|
+
await runMQ.process('order.placed', {
|
|
291
|
+
name: 'paymentService',
|
|
292
|
+
consumersCount: 2,
|
|
293
|
+
attempts: 3
|
|
294
|
+
}, async (message) => {
|
|
295
|
+
const payment = await processPayment(message.message);
|
|
296
|
+
|
|
297
|
+
if (payment.success) {
|
|
298
|
+
// Trigger next event in the workflow
|
|
299
|
+
runMQ.publish('payment.completed', {
|
|
300
|
+
orderId: message.message.orderId,
|
|
301
|
+
paymentId: payment.id,
|
|
302
|
+
amount: payment.amount
|
|
303
|
+
}, message.meta.correlationId); // Preserve correlation ID
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Inventory Service - reacts to payment.completed
|
|
308
|
+
await runMQ.process('payment.completed', {
|
|
309
|
+
name: 'inventoryService',
|
|
310
|
+
consumersCount: 3,
|
|
311
|
+
attempts: 5
|
|
312
|
+
}, async (message) => {
|
|
313
|
+
await reserveInventory(message.message.orderId);
|
|
314
|
+
|
|
315
|
+
// Trigger next step
|
|
316
|
+
runMQ.publish('inventory.reserved', {
|
|
317
|
+
orderId: message.message.orderId
|
|
318
|
+
}, message.meta.correlationId);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Shipping Service - reacts to inventory.reserved
|
|
322
|
+
await runMQ.process('inventory.reserved', {
|
|
323
|
+
name: 'shippingService',
|
|
324
|
+
consumersCount: 2,
|
|
325
|
+
attempts: 3
|
|
326
|
+
}, async (message) => {
|
|
327
|
+
await scheduleShipment(message.message.orderId);
|
|
328
|
+
|
|
329
|
+
runMQ.publish('order.fulfilled', {
|
|
330
|
+
orderId: message.message.orderId,
|
|
331
|
+
fulfilledAt: new Date().toISOString()
|
|
332
|
+
}, message.meta.correlationId);
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Schema Validation
|
|
337
|
+
|
|
338
|
+
RunMQ supports JSON schema validation to ensure message integrity, so only valid messages are passed to your processors
|
|
339
|
+
Currently, only AJV is supported for schema validation, with a single failure strategy of sending invalid messages to the DLQ in the meantime.
|
|
340
|
+
if the schema validation fails, the message is sent directly to the DLQ without being processed.
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
const orderSchema = {
|
|
344
|
+
type: 'object',
|
|
345
|
+
properties: {
|
|
346
|
+
orderId: { type: 'string', pattern: '^ORD-[0-9]+$' },
|
|
347
|
+
customerId: { type: 'string' },
|
|
348
|
+
items: {
|
|
349
|
+
type: 'array',
|
|
350
|
+
minItems: 1,
|
|
351
|
+
items: {
|
|
352
|
+
type: 'object',
|
|
353
|
+
properties: {
|
|
354
|
+
sku: { type: 'string' },
|
|
355
|
+
quantity: { type: 'number', minimum: 1 },
|
|
356
|
+
price: { type: 'number', minimum: 0 }
|
|
357
|
+
},
|
|
358
|
+
required: ['sku', 'quantity', 'price']
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
total: { type: 'number', minimum: 0 }
|
|
362
|
+
},
|
|
363
|
+
required: ['orderId', 'customerId', 'items', 'total']
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
await runMQ.process('order.placed', {
|
|
367
|
+
name: 'orderProcessor',
|
|
368
|
+
consumersCount: 3,
|
|
369
|
+
attempts: 3,
|
|
370
|
+
messageSchema: {
|
|
371
|
+
type: 'ajv',
|
|
372
|
+
schema: orderSchema,
|
|
373
|
+
failureStrategy: 'dlq' // Invalid messages go straight to DLQ
|
|
374
|
+
}
|
|
375
|
+
}, async (message) => {
|
|
376
|
+
// Message is guaranteed to be valid
|
|
377
|
+
await processOrder(message.message);
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## Configuration
|
|
382
|
+
|
|
383
|
+
### Connection Configuration
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
interface RunMQConnectionConfig {
|
|
387
|
+
url: string; // The URL of the RabbitMQ server.
|
|
388
|
+
reconnectDelay?: number; // The delay in milliseconds before attempting to reconnect after a disconnection (default: 5000)
|
|
389
|
+
maxReconnectAttempts?: number; // Maximum reconnection attempts (default: 5)
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Processor Configuration
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
interface RunMQProcessorConfiguration {
|
|
397
|
+
name: string; // The name of the processor, used to create isolated queues for each processor.
|
|
398
|
+
consumersCount: number; // The number of concurrent consumers to run for this processor.
|
|
399
|
+
attempts?: number; // The maximum number attempts processing a message, default is 1 attempt.
|
|
400
|
+
attemptsDelay?: number; // The delay in milliseconds between attempts.
|
|
401
|
+
messageSchema?: MessageSchema; // The schema configuration for message validation.
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Message Schema Configuration
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
interface MessageSchema {
|
|
409
|
+
type: 'ajv'; // The type of schema used for validation (Currently only 'ajv').
|
|
410
|
+
schema: any; // The schema definition of the chosen schemaType, used for validating messages.
|
|
411
|
+
failureStrategy: 'dlq'; // The strategy to apply when schema validation fails (e.g., 'dlq').
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
## Message Structure
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
interface RunMQMessageContent<T> {
|
|
419
|
+
message: T; // Your message payload
|
|
420
|
+
meta: {
|
|
421
|
+
id: string; // The unique identifier of the message.
|
|
422
|
+
publishedAt: number; // The timestamp when the message was published.
|
|
423
|
+
correlationId: string; // The correlation identifier.
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
## Queue Isolation and Naming
|
|
429
|
+
|
|
430
|
+
**Important:** Each processor creates an isolated queue based on its `name` parameter:
|
|
431
|
+
|
|
432
|
+
- Queue name: `{processor.name}`
|
|
433
|
+
- DLQ name: `{processor.name}_dlq`
|
|
434
|
+
|
|
435
|
+
This ensures:
|
|
436
|
+
- ✅ Processors can't interfere with each other
|
|
437
|
+
- ✅ Each processor controls its own retry logic
|
|
438
|
+
- ✅ Failed messages are isolated per processor
|
|
439
|
+
- ✅ Easy to monitor and debug per-processor queues
|
|
440
|
+
|
|
441
|
+
Example:
|
|
442
|
+
```typescript
|
|
443
|
+
// Creates queue: userEmailService and userEmailService_dlq
|
|
444
|
+
await runMQ.process('user.created', { name: 'userEmailService', ... }, handler);
|
|
445
|
+
|
|
446
|
+
// Creates queue: userAnalytics and userAnalytics_dlq
|
|
447
|
+
await runMQ.process('user.created', { name: 'userAnalytics', ... }, handler);
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## Custom Logger
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
import { RunMQLogger } from 'runmq';
|
|
454
|
+
|
|
455
|
+
class CustomLogger implements RunMQLogger {
|
|
456
|
+
log(message: string): void {
|
|
457
|
+
// Your logging implementation
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
error(message: string, error?: any): void {
|
|
461
|
+
// Your error logging implementation
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const runMQ = await RunMQ.start(config, new CustomLogger());
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## License
|
|
469
|
+
|
|
470
|
+
MIT
|