@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
|
@@ -2,268 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* queueConfig.js
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|