@onlineapps/conn-infra-mq 1.1.20 → 1.1.22
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 +1 -1
- package/src/layers/QueueManager.js +127 -60
- package/src/transports/rabbitmqClient.js +14 -13
package/package.json
CHANGED
|
@@ -103,17 +103,40 @@ class QueueManager {
|
|
|
103
103
|
throw new Error('Channel closed during checkQueue - cannot verify queue');
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
// If queue doesn't exist (404),
|
|
107
|
-
//
|
|
106
|
+
// If queue doesn't exist (404), create it using assertQueue with proper error handling
|
|
107
|
+
// Business queues MUST be created with specific arguments (TTL, DLQ, max-length) from queueConfig
|
|
108
|
+
// Lazy creation via sendToQueue() is NOT suitable - it creates queue with default arguments
|
|
108
109
|
if (checkErr.code === 404) {
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
110
|
+
// CRITICAL: Business queues MUST be created explicitly with correct arguments
|
|
111
|
+
// Use assertQueue() on regular channel (queueChannel) - this should work without RPC issues
|
|
112
|
+
// If it fails, it's a real problem that needs to be fixed, not worked around
|
|
113
|
+
try {
|
|
114
|
+
if (!channel || channel.closed) {
|
|
115
|
+
throw new Error('Channel closed - cannot create queue');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// For business queues: use assertQueue with queueOptions from queueConfig
|
|
119
|
+
// This ensures correct TTL, DLQ, max-length arguments
|
|
120
|
+
return await channel.assertQueue(queueName, queueOptions);
|
|
121
|
+
} catch (assertErr) {
|
|
122
|
+
// If channel closed during assertQueue, it's a real error
|
|
123
|
+
if (!channel || channel.closed) {
|
|
124
|
+
throw new Error(`Channel closed during assertQueue for ${queueName} - check channel management`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// If 406 PRECONDITION-FAILED, queue exists with different args
|
|
128
|
+
if (assertErr.code === 406) {
|
|
129
|
+
console.warn(`[QueueManager] Queue ${queueName} exists with different arguments:`, assertErr.message);
|
|
130
|
+
// Try to get queue info anyway
|
|
131
|
+
if (!channel || channel.closed) {
|
|
132
|
+
throw new Error('Channel closed - cannot check existing queue');
|
|
133
|
+
}
|
|
134
|
+
return await channel.checkQueue(queueName);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Other errors - rethrow
|
|
138
|
+
throw assertErr;
|
|
139
|
+
}
|
|
117
140
|
} else {
|
|
118
141
|
// Other error (including 406) - queue exists with different args
|
|
119
142
|
// Log warning and return queue info without asserting
|
|
@@ -192,6 +215,7 @@ class QueueManager {
|
|
|
192
215
|
|
|
193
216
|
/**
|
|
194
217
|
* Setup service queues (main + DLQ + optional workflow queue)
|
|
218
|
+
* CRITICAL: All queues MUST be created together - if one fails, all fail
|
|
195
219
|
* @param {string} serviceName - Name of the service
|
|
196
220
|
* @param {Object} options - Configuration options
|
|
197
221
|
*/
|
|
@@ -200,67 +224,110 @@ class QueueManager {
|
|
|
200
224
|
if (!transport || !transport.channel) {
|
|
201
225
|
throw new Error('MQ client not connected');
|
|
202
226
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
await this.ensureQueue(`${serviceName}.queue`, {
|
|
212
|
-
ttl: options.ttl || this.config.defaultTTL,
|
|
213
|
-
dlq: true,
|
|
214
|
-
durable: true,
|
|
215
|
-
maxRetries: this.config.maxRetries
|
|
216
|
-
});
|
|
217
|
-
} catch (error) {
|
|
218
|
-
if (error.code === 406) {
|
|
219
|
-
// Queue exists with different arguments - use it as-is
|
|
220
|
-
console.warn(`[QueueManager] Queue ${serviceName}.queue exists with different arguments, using as-is`);
|
|
221
|
-
// Verify queue exists
|
|
222
|
-
await channel.checkQueue(`${serviceName}.queue`);
|
|
223
|
-
} else {
|
|
224
|
-
throw error;
|
|
225
|
-
}
|
|
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');
|
|
226
234
|
}
|
|
227
|
-
queues.main = `${serviceName}.queue`;
|
|
228
235
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
});
|
|
237
|
-
} catch (error) {
|
|
238
|
-
if (error.code === 406) {
|
|
239
|
-
console.warn(`[QueueManager] Queue ${serviceName}.dlq exists with different arguments, using as-is`);
|
|
240
|
-
await channel.checkQueue(`${serviceName}.dlq`);
|
|
241
|
-
} else {
|
|
242
|
-
throw error;
|
|
243
|
-
}
|
|
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' });
|
|
244
244
|
}
|
|
245
|
-
queues.dlq = `${serviceName}.dlq`;
|
|
246
245
|
|
|
247
|
-
// Create
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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, {
|
|
251
268
|
ttl: options.workflowTTL || this.config.defaultTTL,
|
|
252
269
|
dlq: true,
|
|
253
270
|
durable: true
|
|
254
271
|
});
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
+
}
|
|
259
326
|
} else {
|
|
260
|
-
|
|
327
|
+
// Other error - critical, cannot continue
|
|
328
|
+
throw new Error(`Failed to create business queue ${queueName}: ${assertErr.message}`);
|
|
261
329
|
}
|
|
262
330
|
}
|
|
263
|
-
queues.workflow = `${serviceName}.workflow`;
|
|
264
331
|
}
|
|
265
332
|
|
|
266
333
|
// Setup DLQ exchange and bindings
|
|
@@ -222,23 +222,24 @@ class RabbitMQClient extends EventEmitter {
|
|
|
222
222
|
throw checkErr;
|
|
223
223
|
}
|
|
224
224
|
} else {
|
|
225
|
-
// Business queue -
|
|
225
|
+
// Business queue - should already be created by setupServiceQueues() BEFORE consume is called
|
|
226
226
|
// Use queueChannel (regular channel) for queue operations to avoid RPC reply queue issues
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// Try to assert queue with our config
|
|
230
|
-
// If it fails with 406 (PRECONDITION-FAILED), queue exists with different args - use it as-is
|
|
231
|
-
// IMPORTANT: Don't try to re-assert after 406, as it will close the channel
|
|
227
|
+
// Only check if it exists - don't try to create it here (that's setupServiceQueues() responsibility)
|
|
232
228
|
try {
|
|
233
|
-
await this._queueChannel.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
229
|
+
await this._queueChannel.checkQueue(queue);
|
|
230
|
+
// Queue exists - proceed to consume
|
|
231
|
+
} catch (checkErr) {
|
|
232
|
+
if (checkErr.code === 404) {
|
|
233
|
+
// Queue doesn't exist - this is an error, queue should have been created by setupServiceQueues()
|
|
234
|
+
throw new Error(`Business queue '${queue}' not found. Queue should be created by setupServiceQueues() before consuming.`);
|
|
235
|
+
}
|
|
236
|
+
// Other error (including 406) - queue exists with different args, use it as-is
|
|
237
|
+
if (checkErr.code === 406) {
|
|
238
|
+
console.warn(`[RabbitMQClient] Queue ${queue} exists with different arguments, using as-is:`, checkErr.message);
|
|
239
|
+
// Proceed to consume - queue exists, just with different args
|
|
239
240
|
} else {
|
|
240
241
|
// Other error - rethrow
|
|
241
|
-
throw
|
|
242
|
+
throw checkErr;
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
245
|
}
|