@onlineapps/conn-infra-mq 1.1.50 → 1.1.52

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.50",
3
+ "version": "1.1.52",
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": {
@@ -2,268 +2,15 @@
2
2
 
3
3
  /**
4
4
  * queueConfig.js
5
- *
6
- * Central configuration for INFRASTRUCTURE queues only.
7
- * Business queues ({service}.workflow, {service}.queue, {service}.dlq) are created
8
- * by individual services via QueueManager.setupServiceQueues().
9
- *
10
- * This ensures consistent configuration for infrastructure queues across all services.
5
+ *
6
+ * Re-exports queueConfig from @onlineapps/mq-client-core.
7
+ *
8
+ * NOTE: queueConfig is now part of mq-client-core (spodní vrstva) because it's used by both:
9
+ * - Infrastructure services (via mq-client-core)
10
+ * - Business services (via conn-infra-mq connector)
11
+ *
12
+ * This file maintains backward compatibility for existing code that imports from conn-infra-mq.
11
13
  */
12
14
 
13
- module.exports = {
14
- /**
15
- * Workflow infrastructure queue configurations
16
- * These are shared infrastructure queues used by all services
17
- */
18
- workflow: {
19
- /**
20
- * workflow.init - Entry point for all workflows
21
- * All services listen as competing consumers
22
- */
23
- init: {
24
- durable: true,
25
- arguments: {
26
- 'x-message-ttl': 300000, // 5 minutes TTL
27
- 'x-max-length': 10000 // Max 10k messages
28
- }
29
- },
30
-
31
- /**
32
- * workflow.completed - Completed workflows
33
- */
34
- completed: {
35
- durable: true,
36
- arguments: {
37
- 'x-message-ttl': 300000, // 5 minutes TTL
38
- 'x-max-length': 10000
39
- }
40
- },
41
-
42
- /**
43
- * workflow.failed - Failed workflows
44
- */
45
- failed: {
46
- durable: true,
47
- arguments: {
48
- 'x-message-ttl': 300000, // 5 minutes TTL
49
- 'x-max-length': 10000
50
- }
51
- },
52
-
53
- /**
54
- * workflow.dlq - Dead letter queue for workflows
55
- */
56
- dlq: {
57
- durable: true,
58
- arguments: {
59
- // No TTL for DLQ - messages should persist
60
- 'x-max-length': 50000 // Higher limit for DLQ
61
- }
62
- }
63
- },
64
-
65
- /**
66
- * Business queue templates
67
- * These are templates for service-specific queues ({service}.workflow, {service}.queue, {service}.dlq)
68
- * Used by QueueManager.setupServiceQueues() to ensure consistent configuration
69
- */
70
- business: {
71
- /**
72
- * {service}.workflow - Service-specific workflow queue
73
- * Used for routing workflow steps to specific services
74
- */
75
- workflow: {
76
- durable: true,
77
- arguments: {
78
- 'x-message-ttl': 300000, // 5 minutes TTL
79
- 'x-max-length': 10000, // Max 10k messages
80
- 'x-dead-letter-exchange': 'dlx',
81
- 'x-dead-letter-routing-key': '{service}.dlq' // Placeholder, replaced with actual service name
82
- }
83
- },
84
-
85
- /**
86
- * {service}.queue - Service main processing queue
87
- * Used for direct service-to-service communication
88
- */
89
- queue: {
90
- durable: true,
91
- arguments: {
92
- 'x-message-ttl': 30000, // 30 seconds TTL
93
- 'x-max-length': 10000, // Max 10k messages
94
- 'x-dead-letter-exchange': 'dlx',
95
- 'x-dead-letter-routing-key': '{service}.dlq' // Placeholder, replaced with actual service name
96
- }
97
- },
98
-
99
- /**
100
- * {service}.dlq - Service dead letter queue
101
- * Receives failed messages from service.queue and service.workflow
102
- */
103
- dlq: {
104
- durable: true,
105
- arguments: {
106
- // No TTL for DLQ - messages should persist
107
- 'x-max-length': 50000 // Higher limit for DLQ
108
- }
109
- }
110
- },
111
-
112
- /**
113
- * Registry infrastructure queue configurations
114
- */
115
- registry: {
116
- /**
117
- * registry.register - Registration requests
118
- */
119
- register: {
120
- durable: true,
121
- arguments: {
122
- 'x-message-ttl': 60000, // 1 minute TTL
123
- 'x-max-length': 1000
124
- }
125
- },
126
-
127
- /**
128
- * registry.heartbeats - Service heartbeats
129
- */
130
- heartbeats: {
131
- durable: true,
132
- arguments: {
133
- 'x-message-ttl': 120000, // 2 minutes TTL
134
- 'x-max-length': 5000
135
- }
136
- },
137
-
138
- /**
139
- * registry.changes - Registry event fanout exchange
140
- * (This is an exchange, not a queue, but included for reference)
141
- */
142
- changes: {
143
- type: 'exchange',
144
- durable: true
145
- }
146
- },
147
-
148
- /**
149
- * Get infrastructure queue configuration by type and name
150
- * @param {string} type - Queue type: 'workflow', 'registry'
151
- * @param {string} name - Queue name: 'init', 'completed', 'register', etc.
152
- * @returns {Object} Queue configuration
153
- */
154
- getQueueConfig(type, name) {
155
- const config = this[type]?.[name];
156
- if (!config) {
157
- throw new Error(`Infrastructure queue config not found: ${type}.${name}`);
158
- }
159
-
160
- // Deep clone to avoid modifying original
161
- return JSON.parse(JSON.stringify(config));
162
- },
163
-
164
- /**
165
- * Get workflow infrastructure queue configuration
166
- * @param {string} queueName - Queue name (e.g., 'workflow.init', 'workflow.completed')
167
- * @returns {Object} Queue configuration
168
- */
169
- getWorkflowQueueConfig(queueName) {
170
- const parts = queueName.split('.');
171
- if (parts.length !== 2 || parts[0] !== 'workflow') {
172
- throw new Error(`Invalid workflow queue name: ${queueName}. Expected format: workflow.{name}`);
173
- }
174
- return this.getQueueConfig('workflow', parts[1]);
175
- },
176
-
177
- /**
178
- * Get registry infrastructure queue configuration
179
- * @param {string} queueName - Queue name (e.g., 'registry.register', 'registry.heartbeats')
180
- * @returns {Object} Queue configuration
181
- */
182
- getRegistryQueueConfig(queueName) {
183
- const parts = queueName.split('.');
184
- if (parts.length !== 2 || parts[0] !== 'registry') {
185
- throw new Error(`Invalid registry queue name: ${queueName}. Expected format: registry.{name}`);
186
- }
187
- return this.getQueueConfig('registry', parts[1]);
188
- },
189
-
190
- /**
191
- * Check if queue name is an infrastructure queue
192
- * @param {string} queueName - Queue name to check
193
- * @returns {boolean} True if infrastructure queue
194
- */
195
- isInfrastructureQueue(queueName) {
196
- return queueName.startsWith('workflow.') || queueName.startsWith('registry.');
197
- },
198
-
199
- /**
200
- * Check if queue name is a business queue
201
- * @param {string} queueName - Queue name to check
202
- * @returns {boolean} True if business queue
203
- */
204
- isBusinessQueue(queueName) {
205
- // Business queues follow pattern: {service}.{type}
206
- // where type is: workflow, queue, dlq
207
- const parts = queueName.split('.');
208
- if (parts.length !== 2) return false;
209
- return ['workflow', 'queue', 'dlq'].includes(parts[1]);
210
- },
211
-
212
- /**
213
- * Parse business queue name to extract service name and queue type
214
- * @param {string} queueName - Business queue name (e.g., 'hello-service.workflow')
215
- * @returns {Object} { serviceName, queueType } or null if not a business queue
216
- */
217
- parseBusinessQueue(queueName) {
218
- if (!this.isBusinessQueue(queueName)) {
219
- return null;
220
- }
221
- const parts = queueName.split('.');
222
- return {
223
- serviceName: parts[0],
224
- queueType: parts[1]
225
- };
226
- },
227
-
228
- /**
229
- * Get business queue configuration template
230
- * @param {string} queueType - Queue type: 'workflow', 'queue', 'dlq'
231
- * @param {string} serviceName - Service name (replaces {service} placeholder)
232
- * @returns {Object} Queue configuration
233
- */
234
- getBusinessQueueConfig(queueType, serviceName) {
235
- const template = this.business?.[queueType];
236
- if (!template) {
237
- throw new Error(`Business queue template not found: ${queueType}`);
238
- }
239
-
240
- // Deep clone to avoid modifying original
241
- const config = JSON.parse(JSON.stringify(template));
242
-
243
- // Replace {service} placeholder in routing keys
244
- if (config.arguments && serviceName) {
245
- const routingKey = config.arguments['x-dead-letter-routing-key'];
246
- if (routingKey && typeof routingKey === 'string') {
247
- config.arguments['x-dead-letter-routing-key'] = routingKey.replace('{service}', serviceName);
248
- }
249
- }
250
-
251
- return config;
252
- },
253
-
254
- /**
255
- * Get infrastructure queue configuration by queue name (auto-detect type)
256
- * @param {string} queueName - Full queue name (e.g., 'workflow.init', 'registry.register')
257
- * @returns {Object} Queue configuration
258
- */
259
- getInfrastructureQueueConfig(queueName) {
260
- if (queueName.startsWith('workflow.')) {
261
- return this.getWorkflowQueueConfig(queueName);
262
- } else if (queueName.startsWith('registry.')) {
263
- return this.getRegistryQueueConfig(queueName);
264
- } else {
265
- throw new Error(`Queue ${queueName} is not an infrastructure queue. Infrastructure queues must start with 'workflow.' or 'registry.'`);
266
- }
267
- }
268
- };
269
-
15
+ // Re-export from mq-client-core
16
+ module.exports = require('@onlineapps/mq-client-core/src/config/queueConfig');
@@ -336,10 +336,29 @@ class RabbitMQClient extends EventEmitter {
336
336
  const assertStartTime = Date.now();
337
337
  console.log(`[RabbitMQClient] [CONSUMER] Asserting queue ${queue} before consume() at ${new Date().toISOString()}`);
338
338
  console.log(`[RabbitMQClient] [CONSUMER] Queue options:`, JSON.stringify(queueOptions, null, 2));
339
+ console.log(`[RabbitMQClient] [CONSUMER] _queueChannel state: exists=${!!this._queueChannel}, closed=${this._queueChannel ? this._queueChannel.closed : 'N/A'}`);
340
+
341
+ // CRITICAL: Check if _queueChannel is closed - if so, this indicates a previous 406 error
342
+ // The root cause is that assertQueue() was called without parameters somewhere else
343
+ // We should NOT recreate the channel - we should fix the root cause
344
+ if (this._queueChannel && this._queueChannel.closed) {
345
+ const errorMsg = `[RabbitMQClient] [CONSUMER] ✗ CRITICAL: _queueChannel is CLOSED (likely due to 406 PRECONDITION-FAILED). This means assertQueue() was called without parameters somewhere else. Root cause must be fixed - do NOT recreate channel!`;
346
+ console.error(errorMsg);
347
+ console.error(`[RabbitMQClient] [CONSUMER] Channel was created at: ${this._queueChannel._createdAt}`);
348
+ console.error(`[RabbitMQClient] [CONSUMER] Close reason: ${this._queueChannel._closeReason || 'Unknown'}`);
349
+ console.error(`[RabbitMQClient] [CONSUMER] Last operation: ${this._queueChannel._lastOperation || 'Unknown'}`);
350
+ throw new Error(`Cannot assertQueue: _queueChannel is closed due to 406 error. Root cause: assertQueue() was called without parameters. Fix the root cause instead of recreating channel.`);
351
+ }
352
+
353
+ if (!this._queueChannel) {
354
+ throw new Error(`Cannot assertQueue: _queueChannel is null. Connection may not be established.`);
355
+ }
339
356
 
340
357
  // Use queueChannel for assertQueue to avoid RPC reply queue issues
341
- const channelForAssert = this._queueChannel || this._channel;
358
+ const channelForAssert = this._queueChannel;
359
+ this._queueChannel._lastOperation = `About to assertQueue ${queue} with options: ${JSON.stringify(queueOptions)}`;
342
360
  await channelForAssert.assertQueue(queue, queueOptions);
361
+ this._queueChannel._lastOperation = `assertQueue ${queue} succeeded`;
343
362
 
344
363
  const assertEndTime = Date.now();
345
364
  console.log(`[RabbitMQClient] [CONSUMER] ✓ Queue ${queue} asserted (took ${assertEndTime - assertStartTime}ms)`);
@@ -354,6 +373,15 @@ class RabbitMQClient extends EventEmitter {
354
373
  console.log(`[RabbitMQClient] [CONSUMER] Options: prefetch=${prefetch}, noAck=${noAck}, durable=${durable}`);
355
374
  console.log(`[RabbitMQClient] [CONSUMER] Channel state before consume: closed=${this._channel.closed}`);
356
375
 
376
+ // CRITICAL WARNING: amqplib's channel.consume() may internally call assertQueue() WITHOUT parameters
377
+ // This happens if the queue doesn't exist or if amqplib needs to verify queue existence
378
+ // If queue exists with different arguments (e.g., x-message-ttl), this will cause 406 PRECONDITION-FAILED
379
+ // and close the channel. That's why we assertQueue() with correct parameters BEFORE consume() above.
380
+ console.log(`[RabbitMQClient] [CONSUMER] ⚠ WARNING: About to call amqplib's channel.consume() for queue: ${queue}`);
381
+ console.log(`[RabbitMQClient] [CONSUMER] ⚠ WARNING: amqplib may internally call assertQueue() WITHOUT parameters if queue doesn't exist`);
382
+ console.log(`[RabbitMQClient] [CONSUMER] ⚠ WARNING: If queue exists with different arguments, this will cause 406 PRECONDITION-FAILED and close channel`);
383
+ console.log(`[RabbitMQClient] [CONSUMER] ⚠ WARNING: We already asserted queue with correct parameters above - this should prevent 406`);
384
+
357
385
  const consumeResult = await this._channel.consume(
358
386
  queue,
359
387
  async (msg) => {
@@ -45,21 +45,41 @@ async function initInfrastructureQueues(channel, options = {}) {
45
45
  // Get unified configuration
46
46
  const config = queueConfig.getInfrastructureQueueConfig(queueName);
47
47
 
48
- // Create queue with unified config
49
- await channel.assertQueue(queueName, {
50
- durable: config.durable !== false,
51
- arguments: { ...config.arguments }
52
- });
53
-
54
- logger.log(`[QueueInit] ✓ Created infrastructure queue: ${queueName}`);
55
- } catch (error) {
56
- // If queue exists with different args (406), log warning but continue
57
- if (error.code === 406) {
58
- logger.warn(`[QueueInit] Queue ${queueName} exists with different arguments - using as-is`);
59
- } else {
60
- logger.error(`[QueueInit] ✗ Failed to create ${queueName}:`, error.message);
61
- throw error;
48
+ // CRITICAL: Check if queue exists with different arguments (406 error)
49
+ // If so, delete it and recreate with correct parameters
50
+ try {
51
+ await channel.assertQueue(queueName, {
52
+ durable: config.durable !== false,
53
+ arguments: { ...config.arguments }
54
+ });
55
+ logger.log(`[QueueInit] Created/verified infrastructure queue: ${queueName}`);
56
+ } catch (assertError) {
57
+ if (assertError.code === 406) {
58
+ // Queue exists with different arguments - delete and recreate
59
+ logger.warn(`[QueueInit] ⚠ Queue ${queueName} exists with different arguments - deleting and recreating...`);
60
+ try {
61
+ await channel.deleteQueue(queueName);
62
+ logger.log(`[QueueInit] ✓ Deleted queue ${queueName} with incorrect parameters`);
63
+ } catch (deleteError) {
64
+ logger.error(`[QueueInit] ✗ Failed to delete queue ${queueName}:`, deleteError.message);
65
+ throw new Error(`Cannot delete queue ${queueName} with incorrect parameters: ${deleteError.message}`);
66
+ }
67
+
68
+ // Recreate with correct parameters
69
+ await channel.assertQueue(queueName, {
70
+ durable: config.durable !== false,
71
+ arguments: { ...config.arguments }
72
+ });
73
+ logger.log(`[QueueInit] ✓ Recreated infrastructure queue: ${queueName} with correct parameters`);
74
+ } else {
75
+ // Other error - rethrow
76
+ logger.error(`[QueueInit] ✗ Failed to create ${queueName}:`, assertError.message);
77
+ throw assertError;
78
+ }
62
79
  }
80
+ } catch (error) {
81
+ logger.error(`[QueueInit] ✗ Failed to initialize queue ${queueName}:`, error.message);
82
+ throw error;
63
83
  }
64
84
  }
65
85