@onlineapps/conn-infra-mq 1.1.51 → 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.51",
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,42 +336,27 @@ 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'}`);
339
340
 
340
- // CRITICAL: Ensure _queueChannel is open before using it
341
- // If it's closed (e.g., due to 406 error), recreate it
342
- if (!this._queueChannel || this._queueChannel.closed) {
343
- console.warn(`[RabbitMQClient] [CONSUMER] _queueChannel is closed or null, recreating...`);
344
- try {
345
- this._queueChannel = await this._connection.createChannel();
346
- this._queueChannel._createdAt = new Date().toISOString();
347
- this._queueChannel._closeReason = null;
348
- this._queueChannel._lastOperation = null;
349
-
350
- // Re-attach event listeners
351
- this._queueChannel.on('error', (err) => {
352
- console.error('[RabbitMQClient] Queue channel error:', err.message);
353
- console.error('[RabbitMQClient] Error code:', err.code);
354
- console.error('[RabbitMQClient] Channel created at:', this._queueChannel._createdAt);
355
- console.error('[RabbitMQClient] Last operation:', this._queueChannel._lastOperation);
356
- this._queueChannel._closeReason = `Error: ${err.message} (code: ${err.code})`;
357
- });
358
- this._queueChannel.on('close', () => {
359
- console.error('[RabbitMQClient] Queue channel closed');
360
- console.error('[RabbitMQClient] Channel created at:', this._queueChannel._createdAt);
361
- console.error('[RabbitMQClient] Close reason:', this._queueChannel._closeReason || 'Unknown');
362
- console.error('[RabbitMQClient] Last operation:', this._queueChannel._lastOperation);
363
- });
364
-
365
- console.log(`[RabbitMQClient] [CONSUMER] ✓ _queueChannel recreated`);
366
- } catch (recreateErr) {
367
- console.error(`[RabbitMQClient] [CONSUMER] ✗ Failed to recreate _queueChannel:`, recreateErr.message);
368
- throw new Error(`Cannot assertQueue: _queueChannel is closed and recreation failed: ${recreateErr.message}`);
369
- }
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.`);
370
355
  }
371
356
 
372
357
  // Use queueChannel for assertQueue to avoid RPC reply queue issues
373
358
  const channelForAssert = this._queueChannel;
374
- this._queueChannel._lastOperation = `About to assertQueue ${queue}`;
359
+ this._queueChannel._lastOperation = `About to assertQueue ${queue} with options: ${JSON.stringify(queueOptions)}`;
375
360
  await channelForAssert.assertQueue(queue, queueOptions);
376
361
  this._queueChannel._lastOperation = `assertQueue ${queue} succeeded`;
377
362
 
@@ -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