@onlineapps/conn-infra-mq 1.1.19 → 1.1.21
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 +3 -4
- package/package.json +1 -1
- package/src/ConnectorMQClient.js +2 -48
- package/src/index.js +2 -4
- package/src/layers/QueueManager.js +33 -10
- package/src/transports/rabbitmqClient.js +14 -13
- package/src/layers/RPCHandler.js +0 -324
package/README.md
CHANGED
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
[](https://codecov.io/gh/onlineapps/conn-infra-mq)
|
|
5
5
|
[](https://www.npmjs.com/package/@onlineapps/conn-infra-mq)
|
|
6
6
|
|
|
7
|
-
> Message queue connector with **layered architecture** for workflow orchestration,
|
|
7
|
+
> Message queue connector with **layered architecture** for workflow orchestration, fork-join, and retry patterns. Built on top of RabbitMQ with clean separation of concerns. **Asynchronous workflow pattern only** - synchronous RPC patterns are not supported and not aligned with our architecture philosophy.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
## 🚀 Features
|
|
12
12
|
|
|
13
|
-
- **Layered Architecture**: Clean separation into specialized layers (WorkflowRouter, QueueManager, ForkJoinHandler,
|
|
13
|
+
- **Layered Architecture**: Clean separation into specialized layers (WorkflowRouter, QueueManager, ForkJoinHandler, RetryHandler)
|
|
14
14
|
- **Workflow Orchestration**: Decentralized workflow routing without central orchestrator
|
|
15
15
|
- **Fork-Join Pattern**: Parallel processing with result aggregation and built-in join strategies
|
|
16
|
-
- **
|
|
16
|
+
- **Asynchronous First**: All communication is asynchronous (fire-and-forget), no synchronous blocking patterns
|
|
17
17
|
- **Automatic Retry**: Exponential backoff, dead letter queue management, configurable retry policies
|
|
18
18
|
- **Queue Management**: TTL, DLQ, auto-delete, temporary queues, exchange bindings
|
|
19
19
|
- **Promise-based API**: All operations return promises for clean async/await usage
|
|
@@ -43,7 +43,6 @@ ConnectorMQClient (main orchestrator - for business services only)
|
|
|
43
43
|
├── WorkflowRouter (workflow orchestration)
|
|
44
44
|
├── QueueManager (queue lifecycle management)
|
|
45
45
|
├── ForkJoinHandler (parallel processing)
|
|
46
|
-
├── RPCHandler (request-response patterns)
|
|
47
46
|
└── RetryHandler (error recovery & DLQ)
|
|
48
47
|
```
|
|
49
48
|
|
package/package.json
CHANGED
package/src/ConnectorMQClient.js
CHANGED
|
@@ -4,7 +4,6 @@ const BaseClient = require('@onlineapps/mq-client-core');
|
|
|
4
4
|
const WorkflowRouter = require('./layers/WorkflowRouter');
|
|
5
5
|
const QueueManager = require('./layers/QueueManager');
|
|
6
6
|
const ForkJoinHandler = require('./layers/ForkJoinHandler');
|
|
7
|
-
const RPCHandler = require('./layers/RPCHandler');
|
|
8
7
|
const RetryHandler = require('./layers/RetryHandler');
|
|
9
8
|
|
|
10
9
|
/**
|
|
@@ -25,8 +24,7 @@ const RetryHandler = require('./layers/RetryHandler');
|
|
|
25
24
|
* @example <caption>With Workflow</caption>
|
|
26
25
|
* const mqClient = new ConnectorMQClient({
|
|
27
26
|
* url: 'amqp://localhost:5672',
|
|
28
|
-
* serviceName: 'invoice-service'
|
|
29
|
-
* enableRPC: true
|
|
27
|
+
* serviceName: 'invoice-service'
|
|
30
28
|
* });
|
|
31
29
|
*/
|
|
32
30
|
class ConnectorMQClient extends BaseClient {
|
|
@@ -37,7 +35,6 @@ class ConnectorMQClient extends BaseClient {
|
|
|
37
35
|
* @param {Object} [config={}] - Configuration options
|
|
38
36
|
* @param {string} config.url - RabbitMQ connection URL
|
|
39
37
|
* @param {string} [config.serviceName='unknown'] - Service name
|
|
40
|
-
* @param {boolean} [config.enableRPC=true] - Enable RPC handler
|
|
41
38
|
* @param {number} [config.prefetchCount=10] - Message prefetch count
|
|
42
39
|
* @param {Object} [config.retry] - Retry configuration
|
|
43
40
|
*
|
|
@@ -56,7 +53,6 @@ class ConnectorMQClient extends BaseClient {
|
|
|
56
53
|
this.queues = new QueueManager(this, config);
|
|
57
54
|
this.retry = new RetryHandler(this, config);
|
|
58
55
|
this.forkJoin = new ForkJoinHandler(this, this.queues, config);
|
|
59
|
-
this.rpc = new RPCHandler(this, this.queues, config);
|
|
60
56
|
|
|
61
57
|
// Service identification
|
|
62
58
|
this.serviceName = config.serviceName || 'unknown';
|
|
@@ -86,11 +82,6 @@ class ConnectorMQClient extends BaseClient {
|
|
|
86
82
|
// Base connection
|
|
87
83
|
await super.connect();
|
|
88
84
|
|
|
89
|
-
// Initialize RPC handler if needed
|
|
90
|
-
if (this._config.enableRPC !== false) {
|
|
91
|
-
await this.rpc.initialize();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
85
|
// Setup service queues if service name provided
|
|
95
86
|
const shouldAutoSetup = this._config.autoSetupServiceQueues !== false;
|
|
96
87
|
|
|
@@ -126,7 +117,6 @@ class ConnectorMQClient extends BaseClient {
|
|
|
126
117
|
if (this._initialized) {
|
|
127
118
|
// Cleanup layers
|
|
128
119
|
await this.forkJoin.cleanupAll();
|
|
129
|
-
await this.rpc.cleanup();
|
|
130
120
|
await this.queues.cleanup();
|
|
131
121
|
}
|
|
132
122
|
|
|
@@ -291,38 +281,6 @@ class ConnectorMQClient extends BaseClient {
|
|
|
291
281
|
return this.forkJoin.join(queueName, joinStrategy, options);
|
|
292
282
|
}
|
|
293
283
|
|
|
294
|
-
// ============================================
|
|
295
|
-
// RPC Operations (via RPCHandler)
|
|
296
|
-
// ============================================
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Make RPC call
|
|
300
|
-
*/
|
|
301
|
-
async rpcCall(targetQueue, request, options) {
|
|
302
|
-
return this.rpc.call(targetQueue, request, options);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Setup RPC server
|
|
307
|
-
*/
|
|
308
|
-
async rpcServe(queue, handler, options) {
|
|
309
|
-
return this.rpc.serve(queue, handler, options);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Make multiple RPC calls
|
|
314
|
-
*/
|
|
315
|
-
async rpcCallMany(requests) {
|
|
316
|
-
return this.rpc.callMany(requests);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* RPC with retry
|
|
321
|
-
*/
|
|
322
|
-
async rpcCallWithRetry(targetQueue, request, options) {
|
|
323
|
-
return this.rpc.callWithRetry(targetQueue, request, options);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
284
|
// ============================================
|
|
327
285
|
// Retry Operations (via RetryHandler)
|
|
328
286
|
// ============================================
|
|
@@ -374,8 +332,6 @@ class ConnectorMQClient extends BaseClient {
|
|
|
374
332
|
return this.publishWorkflowInit(message, options);
|
|
375
333
|
} else if (options.service) {
|
|
376
334
|
return this.publishToService(options.service, message, options);
|
|
377
|
-
} else if (options.rpc) {
|
|
378
|
-
return this.rpcCall(options.rpc, message, options);
|
|
379
335
|
} else {
|
|
380
336
|
return this.publish(message, options);
|
|
381
337
|
}
|
|
@@ -416,12 +372,10 @@ class ConnectorMQClient extends BaseClient {
|
|
|
416
372
|
workflow: true,
|
|
417
373
|
queues: true,
|
|
418
374
|
retry: true,
|
|
419
|
-
forkJoin: true
|
|
420
|
-
rpc: this.rpc.getPendingCount() >= 0
|
|
375
|
+
forkJoin: true
|
|
421
376
|
},
|
|
422
377
|
stats: {
|
|
423
378
|
retry: this.retry.getStats(),
|
|
424
|
-
pendingRPC: this.rpc.getPendingCount(),
|
|
425
379
|
managedQueues: this.queues.getManagedQueues().length
|
|
426
380
|
}
|
|
427
381
|
};
|
package/src/index.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @module @onlineapps/conn-infra-mq
|
|
5
|
-
* @description RabbitMQ connector with workflow routing, fork-join,
|
|
6
|
-
* Provides layered architecture for
|
|
5
|
+
* @description RabbitMQ connector with workflow routing, fork-join, and retry patterns for OA Drive.
|
|
6
|
+
* Provides layered architecture for asynchronous messaging patterns.
|
|
7
7
|
*
|
|
8
8
|
* @see {@link https://github.com/onlineapps/oa-drive/tree/main/shared/connector/conn-infra-mq|GitHub Repository}
|
|
9
9
|
* @author OA Drive Team
|
|
@@ -18,7 +18,6 @@ const BaseClient = require('@onlineapps/mq-client-core');
|
|
|
18
18
|
const WorkflowRouter = require('./layers/WorkflowRouter');
|
|
19
19
|
const QueueManager = require('./layers/QueueManager');
|
|
20
20
|
const ForkJoinHandler = require('./layers/ForkJoinHandler');
|
|
21
|
-
const RPCHandler = require('./layers/RPCHandler');
|
|
22
21
|
const RetryHandler = require('./layers/RetryHandler');
|
|
23
22
|
|
|
24
23
|
// Default export - the main client
|
|
@@ -33,7 +32,6 @@ module.exports.layers = {
|
|
|
33
32
|
WorkflowRouter,
|
|
34
33
|
QueueManager,
|
|
35
34
|
ForkJoinHandler,
|
|
36
|
-
RPCHandler,
|
|
37
35
|
RetryHandler
|
|
38
36
|
};
|
|
39
37
|
|
|
@@ -103,17 +103,40 @@ class QueueManager {
|
|
|
103
103
|
throw new Error('Channel closed during checkQueue - cannot verify queue');
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
// If queue doesn't exist (404),
|
|
107
|
-
//
|
|
106
|
+
// If queue doesn't exist (404), create it using assertQueue with proper error handling
|
|
107
|
+
// Business queues MUST be created with specific arguments (TTL, DLQ, max-length) from queueConfig
|
|
108
|
+
// Lazy creation via sendToQueue() is NOT suitable - it creates queue with default arguments
|
|
108
109
|
if (checkErr.code === 404) {
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
110
|
+
// CRITICAL: Business queues MUST be created explicitly with correct arguments
|
|
111
|
+
// Use assertQueue() on regular channel (queueChannel) - this should work without RPC issues
|
|
112
|
+
// If it fails, it's a real problem that needs to be fixed, not worked around
|
|
113
|
+
try {
|
|
114
|
+
if (!channel || channel.closed) {
|
|
115
|
+
throw new Error('Channel closed - cannot create queue');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// For business queues: use assertQueue with queueOptions from queueConfig
|
|
119
|
+
// This ensures correct TTL, DLQ, max-length arguments
|
|
120
|
+
return await channel.assertQueue(queueName, queueOptions);
|
|
121
|
+
} catch (assertErr) {
|
|
122
|
+
// If channel closed during assertQueue, it's a real error
|
|
123
|
+
if (!channel || channel.closed) {
|
|
124
|
+
throw new Error(`Channel closed during assertQueue for ${queueName} - check channel management`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// If 406 PRECONDITION-FAILED, queue exists with different args
|
|
128
|
+
if (assertErr.code === 406) {
|
|
129
|
+
console.warn(`[QueueManager] Queue ${queueName} exists with different arguments:`, assertErr.message);
|
|
130
|
+
// Try to get queue info anyway
|
|
131
|
+
if (!channel || channel.closed) {
|
|
132
|
+
throw new Error('Channel closed - cannot check existing queue');
|
|
133
|
+
}
|
|
134
|
+
return await channel.checkQueue(queueName);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Other errors - rethrow
|
|
138
|
+
throw assertErr;
|
|
139
|
+
}
|
|
117
140
|
} else {
|
|
118
141
|
// Other error (including 406) - queue exists with different args
|
|
119
142
|
// Log warning and return queue info without asserting
|
|
@@ -222,23 +222,24 @@ class RabbitMQClient extends EventEmitter {
|
|
|
222
222
|
throw checkErr;
|
|
223
223
|
}
|
|
224
224
|
} else {
|
|
225
|
-
// Business queue -
|
|
225
|
+
// Business queue - should already be created by setupServiceQueues() BEFORE consume is called
|
|
226
226
|
// Use queueChannel (regular channel) for queue operations to avoid RPC reply queue issues
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// Try to assert queue with our config
|
|
230
|
-
// If it fails with 406 (PRECONDITION-FAILED), queue exists with different args - use it as-is
|
|
231
|
-
// IMPORTANT: Don't try to re-assert after 406, as it will close the channel
|
|
227
|
+
// Only check if it exists - don't try to create it here (that's setupServiceQueues() responsibility)
|
|
232
228
|
try {
|
|
233
|
-
await this._queueChannel.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
229
|
+
await this._queueChannel.checkQueue(queue);
|
|
230
|
+
// Queue exists - proceed to consume
|
|
231
|
+
} catch (checkErr) {
|
|
232
|
+
if (checkErr.code === 404) {
|
|
233
|
+
// Queue doesn't exist - this is an error, queue should have been created by setupServiceQueues()
|
|
234
|
+
throw new Error(`Business queue '${queue}' not found. Queue should be created by setupServiceQueues() before consuming.`);
|
|
235
|
+
}
|
|
236
|
+
// Other error (including 406) - queue exists with different args, use it as-is
|
|
237
|
+
if (checkErr.code === 406) {
|
|
238
|
+
console.warn(`[RabbitMQClient] Queue ${queue} exists with different arguments, using as-is:`, checkErr.message);
|
|
239
|
+
// Proceed to consume - queue exists, just with different args
|
|
239
240
|
} else {
|
|
240
241
|
// Other error - rethrow
|
|
241
|
-
throw
|
|
242
|
+
throw checkErr;
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
245
|
}
|
package/src/layers/RPCHandler.js
DELETED
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* RPCHandler - Manages RPC (Remote Procedure Call) patterns
|
|
5
|
-
* Handles request-response communication with correlation IDs and timeouts
|
|
6
|
-
*/
|
|
7
|
-
class RPCHandler {
|
|
8
|
-
constructor(mqClient, queueManager, config = {}) {
|
|
9
|
-
this.client = mqClient;
|
|
10
|
-
this.queueManager = queueManager;
|
|
11
|
-
this.config = {
|
|
12
|
-
defaultTimeout: config.defaultTimeout || 5000,
|
|
13
|
-
replyQueuePrefix: config.replyQueuePrefix || 'rpc.reply',
|
|
14
|
-
...config
|
|
15
|
-
};
|
|
16
|
-
this.pendingRequests = new Map();
|
|
17
|
-
this.replyQueue = null;
|
|
18
|
-
this.replyConsumer = null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Initialize RPC handler (create reply queue)
|
|
23
|
-
*/
|
|
24
|
-
async initialize() {
|
|
25
|
-
if (this.replyQueue) {
|
|
26
|
-
return this.replyQueue;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Create exclusive reply queue
|
|
30
|
-
this.replyQueue = await this.queueManager.createTemporaryQueue(
|
|
31
|
-
this.config.replyQueuePrefix,
|
|
32
|
-
{
|
|
33
|
-
exclusive: true,
|
|
34
|
-
autoDelete: true,
|
|
35
|
-
expires: 3600000 // 1 hour
|
|
36
|
-
}
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
// Start consuming replies
|
|
40
|
-
this.replyConsumer = await this.client.consume(
|
|
41
|
-
this.replyQueue,
|
|
42
|
-
(message, rawMsg) => this.handleReply(message, rawMsg),
|
|
43
|
-
{
|
|
44
|
-
noAck: true
|
|
45
|
-
}
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
return this.replyQueue;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Send RPC request and wait for response
|
|
53
|
-
* @param {string} targetQueue - Target service queue
|
|
54
|
-
* @param {Object} request - Request message
|
|
55
|
-
* @param {Object} options - RPC options
|
|
56
|
-
*/
|
|
57
|
-
async call(targetQueue, request, options = {}) {
|
|
58
|
-
// Ensure reply queue exists
|
|
59
|
-
await this.initialize();
|
|
60
|
-
|
|
61
|
-
const correlationId = this.generateCorrelationId();
|
|
62
|
-
const timeout = options.timeout || this.config.defaultTimeout;
|
|
63
|
-
|
|
64
|
-
// Create promise for response
|
|
65
|
-
const responsePromise = new Promise((resolve, reject) => {
|
|
66
|
-
// Setup timeout
|
|
67
|
-
const timeoutHandle = setTimeout(() => {
|
|
68
|
-
this.pendingRequests.delete(correlationId);
|
|
69
|
-
reject(new Error(`RPC request timeout after ${timeout}ms`));
|
|
70
|
-
}, timeout);
|
|
71
|
-
|
|
72
|
-
// Store pending request
|
|
73
|
-
this.pendingRequests.set(correlationId, {
|
|
74
|
-
resolve,
|
|
75
|
-
reject,
|
|
76
|
-
timeoutHandle,
|
|
77
|
-
startTime: Date.now(),
|
|
78
|
-
targetQueue
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// Send request with correlation ID and reply queue
|
|
83
|
-
await this.client.publish(request, {
|
|
84
|
-
queue: targetQueue,
|
|
85
|
-
correlationId,
|
|
86
|
-
replyTo: this.replyQueue,
|
|
87
|
-
expiration: timeout.toString(),
|
|
88
|
-
...options
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
return responsePromise;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Setup service as RPC server
|
|
96
|
-
* @param {string} queue - Queue to listen on
|
|
97
|
-
* @param {Function} handler - Request handler function
|
|
98
|
-
* @param {Object} options - Server options
|
|
99
|
-
*/
|
|
100
|
-
async serve(queue, handler, options = {}) {
|
|
101
|
-
return this.client.consume(async (request, rawMsg) => {
|
|
102
|
-
const { correlationId, replyTo } = rawMsg.properties;
|
|
103
|
-
|
|
104
|
-
if (!replyTo) {
|
|
105
|
-
// Not an RPC request, handle normally
|
|
106
|
-
if (options.allowNonRPC) {
|
|
107
|
-
await handler(request, rawMsg);
|
|
108
|
-
}
|
|
109
|
-
await this.client.ack(rawMsg);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
// Process request
|
|
115
|
-
const response = await handler(request, rawMsg);
|
|
116
|
-
|
|
117
|
-
// Send response
|
|
118
|
-
if (replyTo && correlationId) {
|
|
119
|
-
await this.client.publish(response, {
|
|
120
|
-
queue: replyTo,
|
|
121
|
-
correlationId
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
await this.client.ack(rawMsg);
|
|
126
|
-
} catch (error) {
|
|
127
|
-
// Send error response
|
|
128
|
-
if (replyTo && correlationId) {
|
|
129
|
-
await this.client.publish(
|
|
130
|
-
{
|
|
131
|
-
error: {
|
|
132
|
-
message: error.message,
|
|
133
|
-
code: error.code || 'RPC_ERROR',
|
|
134
|
-
timestamp: Date.now()
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
queue: replyTo,
|
|
139
|
-
correlationId
|
|
140
|
-
}
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Reject or acknowledge based on configuration
|
|
145
|
-
if (options.rejectOnError) {
|
|
146
|
-
await this.client.nack(rawMsg, false);
|
|
147
|
-
} else {
|
|
148
|
-
await this.client.ack(rawMsg);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}, {
|
|
152
|
-
queue,
|
|
153
|
-
...options
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Handle RPC reply
|
|
159
|
-
* @private
|
|
160
|
-
*/
|
|
161
|
-
handleReply(message, rawMsg) {
|
|
162
|
-
const { correlationId } = rawMsg.properties;
|
|
163
|
-
|
|
164
|
-
if (!correlationId || !this.pendingRequests.has(correlationId)) {
|
|
165
|
-
// Unknown or expired request
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const pending = this.pendingRequests.get(correlationId);
|
|
170
|
-
this.pendingRequests.delete(correlationId);
|
|
171
|
-
|
|
172
|
-
// Clear timeout
|
|
173
|
-
clearTimeout(pending.timeoutHandle);
|
|
174
|
-
|
|
175
|
-
// Calculate round-trip time
|
|
176
|
-
const rtt = Date.now() - pending.startTime;
|
|
177
|
-
|
|
178
|
-
// Check for error response
|
|
179
|
-
if (message && message.error) {
|
|
180
|
-
const error = new Error(message.error.message || 'RPC error');
|
|
181
|
-
error.code = message.error.code;
|
|
182
|
-
error.rtt = rtt;
|
|
183
|
-
pending.reject(error);
|
|
184
|
-
} else {
|
|
185
|
-
// Add metadata to response
|
|
186
|
-
if (typeof message === 'object' && message !== null) {
|
|
187
|
-
message._rpcMetadata = {
|
|
188
|
-
rtt,
|
|
189
|
-
correlationId,
|
|
190
|
-
targetQueue: pending.targetQueue
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
pending.resolve(message);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Call multiple RPC requests in parallel
|
|
199
|
-
* @param {Array} requests - Array of { queue, request, options } objects
|
|
200
|
-
*/
|
|
201
|
-
async callMany(requests) {
|
|
202
|
-
const promises = requests.map(req =>
|
|
203
|
-
this.call(req.queue, req.request, req.options)
|
|
204
|
-
.then(response => ({ success: true, response, queue: req.queue }))
|
|
205
|
-
.catch(error => ({ success: false, error, queue: req.queue }))
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
return Promise.all(promises);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Call with retry logic
|
|
213
|
-
* @param {string} targetQueue - Target service queue
|
|
214
|
-
* @param {Object} request - Request message
|
|
215
|
-
* @param {Object} options - RPC options with retry configuration
|
|
216
|
-
*/
|
|
217
|
-
async callWithRetry(targetQueue, request, options = {}) {
|
|
218
|
-
const maxRetries = options.maxRetries || 3;
|
|
219
|
-
const retryDelay = options.retryDelay || 1000;
|
|
220
|
-
let lastError;
|
|
221
|
-
|
|
222
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
223
|
-
try {
|
|
224
|
-
return await this.call(targetQueue, request, options);
|
|
225
|
-
} catch (error) {
|
|
226
|
-
lastError = error;
|
|
227
|
-
|
|
228
|
-
if (attempt < maxRetries) {
|
|
229
|
-
// Wait before retry with exponential backoff
|
|
230
|
-
const delay = retryDelay * Math.pow(2, attempt);
|
|
231
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
throw lastError;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Broadcast request to multiple queues and collect responses
|
|
241
|
-
* @param {Array} queues - Target queues
|
|
242
|
-
* @param {Object} request - Request message
|
|
243
|
-
* @param {Object} options - Broadcast options
|
|
244
|
-
*/
|
|
245
|
-
async broadcast(queues, request, options = {}) {
|
|
246
|
-
const timeout = options.timeout || this.config.defaultTimeout;
|
|
247
|
-
const waitForAll = options.waitForAll !== false;
|
|
248
|
-
|
|
249
|
-
const results = [];
|
|
250
|
-
const promises = [];
|
|
251
|
-
|
|
252
|
-
for (const queue of queues) {
|
|
253
|
-
const promise = this.call(queue, request, { timeout })
|
|
254
|
-
.then(response => {
|
|
255
|
-
results.push({ queue, response, success: true });
|
|
256
|
-
return { queue, response, success: true };
|
|
257
|
-
})
|
|
258
|
-
.catch(error => {
|
|
259
|
-
const result = { queue, error: error.message, success: false };
|
|
260
|
-
if (!waitForAll) {
|
|
261
|
-
results.push(result);
|
|
262
|
-
}
|
|
263
|
-
return result;
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
promises.push(promise);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (waitForAll) {
|
|
270
|
-
const allResults = await Promise.all(promises);
|
|
271
|
-
return allResults;
|
|
272
|
-
} else {
|
|
273
|
-
// Race for first successful response
|
|
274
|
-
await Promise.race(promises.filter(p => p.then(r => r.success)));
|
|
275
|
-
return results;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Generate unique correlation ID
|
|
281
|
-
* @private
|
|
282
|
-
*/
|
|
283
|
-
generateCorrelationId() {
|
|
284
|
-
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Get pending requests count
|
|
289
|
-
*/
|
|
290
|
-
getPendingCount() {
|
|
291
|
-
return this.pendingRequests.size;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Clear all pending requests
|
|
296
|
-
*/
|
|
297
|
-
clearPending() {
|
|
298
|
-
for (const pending of this.pendingRequests.values()) {
|
|
299
|
-
clearTimeout(pending.timeoutHandle);
|
|
300
|
-
pending.reject(new Error('RPC handler shutdown'));
|
|
301
|
-
}
|
|
302
|
-
this.pendingRequests.clear();
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Cleanup RPC handler
|
|
307
|
-
*/
|
|
308
|
-
async cleanup() {
|
|
309
|
-
this.clearPending();
|
|
310
|
-
|
|
311
|
-
if (this.replyQueue) {
|
|
312
|
-
try {
|
|
313
|
-
await this.queueManager.deleteQueue(this.replyQueue);
|
|
314
|
-
} catch (error) {
|
|
315
|
-
// Queue might already be deleted
|
|
316
|
-
}
|
|
317
|
-
this.replyQueue = null;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
this.replyConsumer = null;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
module.exports = RPCHandler;
|