@onlineapps/conn-infra-mq 1.1.19 → 1.1.20

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 CHANGED
@@ -4,16 +4,16 @@
4
4
  [![Coverage Status](https://codecov.io/gh/onlineapps/conn-infra-mq/branch/main/graph/badge.svg)](https://codecov.io/gh/onlineapps/conn-infra-mq)
5
5
  [![npm version](https://img.shields.io/npm/v/@onlineapps/conn-infra-mq)](https://www.npmjs.com/package/@onlineapps/conn-infra-mq)
6
6
 
7
- > Message queue connector with **layered architecture** for workflow orchestration, RPC, fork-join, and retry patterns. Built on top of RabbitMQ with clean separation of concerns.
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, RPCHandler, RetryHandler)
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
- - **RPC Support**: Request-response communication with correlation IDs and timeouts
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/conn-infra-mq",
3
- "version": "1.1.19",
3
+ "version": "1.1.20",
4
4
  "description": "A promise-based, broker-agnostic client for sending and receiving messages via RabbitMQ",
5
5
  "main": "src/index.js",
6
6
  "repository": {
@@ -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, RPC, and retry patterns for OA Drive.
6
- * Provides layered architecture for complex messaging patterns.
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
 
@@ -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;