@onlineapps/conn-infra-mq 1.1.21 → 1.1.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/conn-infra-mq",
3
- "version": "1.1.21",
3
+ "version": "1.1.23",
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": {
@@ -215,6 +215,7 @@ class QueueManager {
215
215
 
216
216
  /**
217
217
  * Setup service queues (main + DLQ + optional workflow queue)
218
+ * CRITICAL: All queues MUST be created together - if one fails, all fail
218
219
  * @param {string} serviceName - Name of the service
219
220
  * @param {Object} options - Configuration options
220
221
  */
@@ -223,67 +224,110 @@ class QueueManager {
223
224
  if (!transport || !transport.channel) {
224
225
  throw new Error('MQ client not connected');
225
226
  }
226
- // Use queueChannel for queue operations to avoid RPC reply queue issues
227
- const channel = transport.queueChannel || transport.channel;
228
-
229
- const queues = {};
230
-
231
- // Create main processing queue
232
- // Handle PRECONDITION-FAILED gracefully - queue may exist with different args
233
- try {
234
- await this.ensureQueue(`${serviceName}.queue`, {
235
- ttl: options.ttl || this.config.defaultTTL,
236
- dlq: true,
237
- durable: true,
238
- maxRetries: this.config.maxRetries
239
- });
240
- } catch (error) {
241
- if (error.code === 406) {
242
- // Queue exists with different arguments - use it as-is
243
- console.warn(`[QueueManager] Queue ${serviceName}.queue exists with different arguments, using as-is`);
244
- // Verify queue exists
245
- await channel.checkQueue(`${serviceName}.queue`);
246
- } else {
247
- throw error;
248
- }
227
+
228
+ // CRITICAL: Use queueChannel for queue operations
229
+ // If channel is closed, we cannot continue - this is a critical error
230
+ let channel = transport.queueChannel || transport.channel;
231
+
232
+ if (!channel || channel.closed) {
233
+ throw new Error('Queue channel is not available or closed - cannot create business queues');
249
234
  }
250
- queues.main = `${serviceName}.queue`;
251
235
 
252
- // Create dead letter queue
253
- try {
254
- await this.ensureQueue(`${serviceName}.dlq`, {
255
- ttl: null, // No TTL for DLQ
256
- dlq: false,
257
- durable: true,
258
- autoDelete: false
259
- });
260
- } catch (error) {
261
- if (error.code === 406) {
262
- console.warn(`[QueueManager] Queue ${serviceName}.dlq exists with different arguments, using as-is`);
263
- await channel.checkQueue(`${serviceName}.dlq`);
264
- } else {
265
- throw error;
266
- }
236
+ const queues = {};
237
+ const queueNames = [];
238
+
239
+ // Collect all queue names that need to be created
240
+ queueNames.push({ name: `${serviceName}.queue`, type: 'main' });
241
+ queueNames.push({ name: `${serviceName}.dlq`, type: 'dlq' });
242
+ if (options.includeWorkflow !== false) {
243
+ queueNames.push({ name: `${serviceName}.workflow`, type: 'workflow' });
267
244
  }
268
- queues.dlq = `${serviceName}.dlq`;
269
245
 
270
- // Create workflow queue if requested
271
- if (options.includeWorkflow !== false) {
272
- try {
273
- await this.ensureQueue(`${serviceName}.workflow`, {
246
+ // CRITICAL: Create ALL queues using assertQueue directly (no checkQueue first to avoid channel closure)
247
+ // This ensures all queues are created atomically
248
+ for (const queueInfo of queueNames) {
249
+ const queueName = queueInfo.name;
250
+ let queueOptions;
251
+
252
+ if (queueInfo.type === 'main') {
253
+ queueOptions = this._buildQueueOptions(queueName, {
254
+ ttl: options.ttl || this.config.defaultTTL,
255
+ dlq: true,
256
+ durable: true,
257
+ maxRetries: this.config.maxRetries
258
+ });
259
+ } else if (queueInfo.type === 'dlq') {
260
+ queueOptions = this._buildQueueOptions(queueName, {
261
+ ttl: null,
262
+ dlq: false,
263
+ durable: true,
264
+ autoDelete: false
265
+ });
266
+ } else if (queueInfo.type === 'workflow') {
267
+ queueOptions = this._buildQueueOptions(queueName, {
274
268
  ttl: options.workflowTTL || this.config.defaultTTL,
275
269
  dlq: true,
276
270
  durable: true
277
271
  });
278
- } catch (error) {
279
- if (error.code === 406) {
280
- console.warn(`[QueueManager] Queue ${serviceName}.workflow exists with different arguments, using as-is`);
281
- await channel.checkQueue(`${serviceName}.workflow`);
272
+ }
273
+
274
+ // Use business queue config from queueConfig if available
275
+ if (queueConfig.isBusinessQueue(queueName)) {
276
+ try {
277
+ const parsed = queueConfig.parseBusinessQueue(queueName);
278
+ if (parsed) {
279
+ const businessConfig = queueConfig.getBusinessQueueConfig(parsed.queueType, parsed.serviceName);
280
+ queueOptions = {
281
+ durable: businessConfig.durable !== false,
282
+ arguments: { ...businessConfig.arguments }
283
+ };
284
+ // Replace placeholder {service} with actual service name
285
+ if (queueOptions.arguments['x-dead-letter-routing-key']) {
286
+ queueOptions.arguments['x-dead-letter-routing-key'] =
287
+ queueOptions.arguments['x-dead-letter-routing-key'].replace('{service}', serviceName);
288
+ }
289
+ }
290
+ } catch (error) {
291
+ // If config not found, use built options
292
+ console.warn(`[QueueManager] Business queue config not found for ${queueName}, using provided options:`, error.message);
293
+ }
294
+ }
295
+
296
+ // CRITICAL: Use assertQueue directly - if queue exists with different args (406), that's OK
297
+ // We'll handle it gracefully, but channel should NOT close
298
+ try {
299
+ // Check if channel is still open before each operation
300
+ if (!channel || channel.closed) {
301
+ throw new Error(`Channel closed before creating ${queueName} - cannot continue`);
302
+ }
303
+
304
+ await channel.assertQueue(queueName, queueOptions);
305
+ queues[queueInfo.type] = queueName;
306
+ console.info(`[QueueManager] ✓ Created business queue: ${queueName}`);
307
+ } catch (assertErr) {
308
+ // If channel closed, this is a critical error
309
+ if (!channel || channel.closed) {
310
+ throw new Error(`Channel closed during assertQueue for ${queueName} - cannot create remaining queues`);
311
+ }
312
+
313
+ // If 406 PRECONDITION-FAILED, queue exists with different args - use it as-is
314
+ if (assertErr.code === 406) {
315
+ console.warn(`[QueueManager] Queue ${queueName} exists with different arguments, using as-is:`, assertErr.message);
316
+ // Verify queue exists
317
+ if (!channel || channel.closed) {
318
+ throw new Error(`Channel closed after 406 error for ${queueName}`);
319
+ }
320
+ try {
321
+ await channel.checkQueue(queueName);
322
+ queues[queueInfo.type] = queueName;
323
+ } catch (checkErr) {
324
+ throw new Error(`Failed to verify existing queue ${queueName}: ${checkErr.message}`);
325
+ }
282
326
  } else {
283
- throw error;
327
+ // Other error - critical, cannot continue
328
+ throw new Error(`Failed to create business queue ${queueName}: ${assertErr.message}`);
284
329
  }
285
330
  }
286
- queues.workflow = `${serviceName}.workflow`;
287
331
  }
288
332
 
289
333
  // Setup DLQ exchange and bindings