@onlineapps/conn-infra-mq 1.1.51 → 1.1.53

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.53",
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": {
@@ -46,11 +46,60 @@ class ConnectorMQClient extends BaseClient {
46
46
  * });
47
47
  */
48
48
  constructor(config = {}) {
49
- super(config);
49
+ // Setup queue creation callback for RecoveryWorker (delegates to QueueManager)
50
+ // This ensures RecoveryWorker uses QueueManager for queue creation instead of direct creation
51
+ // Note: We create a closure that will access this.queues after it's initialized
52
+ let queueManagerRef = null;
53
+ const queueCreationCallback = async (queueName, options = {}) => {
54
+ if (!queueManagerRef) {
55
+ throw new Error('QueueManager not initialized - cannot create queue via RecoveryWorker');
56
+ }
57
+ // Use ensureQueue for individual queues (RecoveryWorker handles single queue creation)
58
+ // Note: setupServiceQueues creates multiple queues, but RecoveryWorker only needs one
59
+ // However, ensureQueue throws for business queues, so we need to handle that
60
+ try {
61
+ await queueManagerRef.ensureQueue(queueName, options);
62
+ } catch (err) {
63
+ // If ensureQueue fails because it's a business queue, we can't create it via RecoveryWorker
64
+ // RecoveryWorker should only handle transient errors, not business queue creation
65
+ // Business queues must be created via setupServiceQueues() after registration
66
+ throw new Error(`Cannot create business queue '${queueName}' via RecoveryWorker. Business queues must be created via setupServiceQueues() after registration.`);
67
+ }
68
+ };
69
+
70
+ // Pass queueCreationCallback to config for RecoveryWorker
71
+ const enhancedConfig = {
72
+ ...config,
73
+ queueCreationCallback,
74
+ recoveryScope: config.recoveryScope || 'business', // Business services can create queues
75
+ };
76
+
77
+ super(enhancedConfig);
50
78
 
51
79
  // Initialize layers
52
80
  this.workflow = new WorkflowRouter(this, config);
53
81
  this.queues = new QueueManager(this, config);
82
+
83
+ // Setup queue creation callback for RecoveryWorker (delegates to QueueManager)
84
+ // This ensures RecoveryWorker uses QueueManager for queue creation instead of direct creation
85
+ // Note: We create a closure that will access this.queues after it's initialized
86
+ let queueManagerRef = null;
87
+ const queueCreationCallback = async (queueName, options = {}) => {
88
+ if (!queueManagerRef) {
89
+ throw new Error('QueueManager not initialized - cannot create queue via RecoveryWorker');
90
+ }
91
+ // Use ensureQueue for individual queues (RecoveryWorker handles single queue creation)
92
+ // Note: setupServiceQueues creates multiple queues, but RecoveryWorker only needs one
93
+ // However, ensureQueue throws for business queues, so we need to handle that
94
+ try {
95
+ await queueManagerRef.ensureQueue(queueName, options);
96
+ } catch (err) {
97
+ // If ensureQueue fails because it's a business queue, we can't create it via RecoveryWorker
98
+ // RecoveryWorker should only handle transient errors, not business queue creation
99
+ // Business queues must be created via setupServiceQueues() after registration
100
+ throw new Error(`Cannot create business queue '${queueName}' via RecoveryWorker. Business queues must be created via setupServiceQueues() after registration.`);
101
+ }
102
+ };
54
103
  this.retry = new RetryHandler(this, config);
55
104
  this.forkJoin = new ForkJoinHandler(this, this.queues, config);
56
105
 
@@ -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');
@@ -13,6 +13,7 @@ class RetryHandler {
13
13
  maxDelay: config.maxDelay || 30000,
14
14
  backoffMultiplier: config.backoffMultiplier || 2,
15
15
  dlqSuffix: config.dlqSuffix || '.dlq',
16
+ emitMonitoringEvents: config.emitMonitoringEvents !== false, // Enable by default
16
17
  ...config
17
18
  };
18
19
  this.retryStats = {
@@ -22,6 +23,36 @@ class RetryHandler {
22
23
  dlqMessages: 0
23
24
  };
24
25
  }
26
+
27
+ /**
28
+ * Emit retry event to monitoring (non-blocking)
29
+ * @private
30
+ */
31
+ async _emitRetryEvent(eventType, data) {
32
+ if (!this.config.emitMonitoringEvents) return;
33
+
34
+ try {
35
+ const { publishToMonitoringWorkflow } = require('@onlineapps/mq-client-core').monitoring;
36
+
37
+ await publishToMonitoringWorkflow(this.client, {
38
+ event_type: eventType,
39
+ workflow_id: data.workflow_id || data.message?.workflow_id || 'unknown',
40
+ timestamp: new Date().toISOString(),
41
+ retry_info: {
42
+ queue: data.queue,
43
+ retry_count: data.retryCount,
44
+ max_retries: data.maxRetries || this.config.maxRetries,
45
+ delay_ms: data.delay,
46
+ error_message: data.error?.message,
47
+ error_code: data.error?.code
48
+ }
49
+ }, null, { source: 'RetryHandler' });
50
+
51
+ } catch (e) {
52
+ // Silent fail - don't break retry logic due to monitoring
53
+ console.warn('[RetryHandler] Failed to emit monitoring event:', e.message);
54
+ }
55
+ }
25
56
 
26
57
  /**
27
58
  * Process message with retry logic
@@ -75,12 +106,23 @@ class RetryHandler {
75
106
 
76
107
  if (retryCount < maxRetries) {
77
108
  // Retry the message
109
+ const delay = this.calculateDelay(retryCount + 1);
78
110
  await this.retryMessage(enrichedMessage, queueName, retryCount + 1, options);
79
111
 
80
112
  // Acknowledge original to remove from queue
81
113
  await this.client.ack(rawMsg);
82
114
 
83
115
  this.retryStats.totalRetries++;
116
+
117
+ // Emit retry event to monitoring
118
+ this._emitRetryEvent('message_retry', {
119
+ workflow_id: message.workflow_id,
120
+ queue: queueName,
121
+ retryCount: retryCount + 1,
122
+ maxRetries,
123
+ delay,
124
+ error
125
+ });
84
126
 
85
127
  return { success: false, retried: true, retryCount: retryCount + 1 };
86
128
  } else {
@@ -92,6 +134,15 @@ class RetryHandler {
92
134
 
93
135
  this.retryStats.failedRetries++;
94
136
  this.retryStats.dlqMessages++;
137
+
138
+ // Emit DLQ event to monitoring
139
+ this._emitRetryEvent('message_dlq', {
140
+ workflow_id: message.workflow_id,
141
+ queue: queueName,
142
+ retryCount,
143
+ maxRetries,
144
+ error
145
+ });
95
146
 
96
147
  return { success: false, retried: false, dlq: true, retryCount };
97
148
  }
@@ -197,6 +197,36 @@ class RabbitMQClient extends EventEmitter {
197
197
  }
198
198
  }
199
199
 
200
+ /**
201
+ * Emits structured PRECONDITION_FAILED events and invokes optional hooks.
202
+ * @param {Object} context - Details about the failing operation
203
+ * @param {Error} error - Original amqplib error
204
+ * @private
205
+ */
206
+ _handlePreconditionFailed(context, error) {
207
+ const event = {
208
+ timestamp: new Date().toISOString(),
209
+ serviceName: this._config.serviceName || this._config.clientId || 'unknown',
210
+ host: this._config.host,
211
+ ...context,
212
+ error: {
213
+ message: error?.message,
214
+ code: error?.code,
215
+ stack: error?.stack
216
+ }
217
+ };
218
+
219
+ this.emit('preconditionFailed', event);
220
+
221
+ if (typeof this._config.onPreconditionFailed === 'function') {
222
+ try {
223
+ this._config.onPreconditionFailed(event);
224
+ } catch (hookError) {
225
+ console.warn('[RabbitMQClient] onPreconditionFailed hook threw error:', hookError.message);
226
+ }
227
+ }
228
+ }
229
+
200
230
  /**
201
231
  * Publishes a message buffer to the specified queue (or default queue) or exchange.
202
232
  * @param {string} queue - Target queue name.
@@ -265,6 +295,19 @@ class RabbitMQClient extends EventEmitter {
265
295
  // Wait for confirmation
266
296
  await this._channel.waitForConfirms();
267
297
  } catch (err) {
298
+ if (err?.code === 406) {
299
+ this._handlePreconditionFailed(
300
+ {
301
+ operation: 'publish',
302
+ queueName: queue,
303
+ routingKey,
304
+ payloadSample: buffer?.toString?.().slice(0, 512) || null,
305
+ persistent,
306
+ headers
307
+ },
308
+ err
309
+ );
310
+ }
268
311
  this.emit('error', err);
269
312
  throw err;
270
313
  }
@@ -336,44 +379,45 @@ class RabbitMQClient extends EventEmitter {
336
379
  const assertStartTime = Date.now();
337
380
  console.log(`[RabbitMQClient] [CONSUMER] Asserting queue ${queue} before consume() at ${new Date().toISOString()}`);
338
381
  console.log(`[RabbitMQClient] [CONSUMER] Queue options:`, JSON.stringify(queueOptions, null, 2));
382
+ console.log(`[RabbitMQClient] [CONSUMER] _queueChannel state: exists=${!!this._queueChannel}, closed=${this._queueChannel ? this._queueChannel.closed : 'N/A'}`);
339
383
 
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
- }
384
+ // CRITICAL: Check if _queueChannel is closed - if so, this indicates a previous 406 error
385
+ // The root cause is that assertQueue() was called without parameters somewhere else
386
+ // We should NOT recreate the channel - we should fix the root cause
387
+ if (this._queueChannel && this._queueChannel.closed) {
388
+ 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!`;
389
+ console.error(errorMsg);
390
+ console.error(`[RabbitMQClient] [CONSUMER] Channel was created at: ${this._queueChannel._createdAt}`);
391
+ console.error(`[RabbitMQClient] [CONSUMER] Close reason: ${this._queueChannel._closeReason || 'Unknown'}`);
392
+ console.error(`[RabbitMQClient] [CONSUMER] Last operation: ${this._queueChannel._lastOperation || 'Unknown'}`);
393
+ 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.`);
394
+ }
395
+
396
+ if (!this._queueChannel) {
397
+ throw new Error(`Cannot assertQueue: _queueChannel is null. Connection may not be established.`);
370
398
  }
371
399
 
372
400
  // Use queueChannel for assertQueue to avoid RPC reply queue issues
373
401
  const channelForAssert = this._queueChannel;
374
- this._queueChannel._lastOperation = `About to assertQueue ${queue}`;
375
- await channelForAssert.assertQueue(queue, queueOptions);
376
- this._queueChannel._lastOperation = `assertQueue ${queue} succeeded`;
402
+ this._queueChannel._lastOperation = `About to assertQueue ${queue} with options: ${JSON.stringify(queueOptions)}`;
403
+ try {
404
+ await channelForAssert.assertQueue(queue, queueOptions);
405
+ this._queueChannel._lastOperation = `assertQueue ${queue} succeeded`;
406
+ } catch (assertErr) {
407
+ this._queueChannel._closeReason = `assertQueue failed: ${assertErr.message}`;
408
+ if (assertErr?.code === 406) {
409
+ this._handlePreconditionFailed(
410
+ {
411
+ operation: 'assertQueue',
412
+ phase: 'consume.setup',
413
+ queueName: queue,
414
+ queueOptions
415
+ },
416
+ assertErr
417
+ );
418
+ }
419
+ throw assertErr;
420
+ }
377
421
 
378
422
  const assertEndTime = Date.now();
379
423
  console.log(`[RabbitMQClient] [CONSUMER] ✓ Queue ${queue} asserted (took ${assertEndTime - assertStartTime}ms)`);
@@ -1,72 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * initInfrastructureQueues.js
5
- *
6
- * Unified tool for creating all infrastructure queues.
7
- * Used by ALL relevant services (infrastructure AND business) during startup.
8
- *
9
- * Ensures consistent queue parameters across all services.
10
- */
11
-
12
- const queueConfig = require('../config/queueConfig');
13
-
14
- /**
15
- * Initialize all infrastructure queues
16
- * @param {Object} channel - RabbitMQ channel
17
- * @param {Object} options - Options
18
- * @param {Array<string>} [options.queues] - Specific queues to create (default: all infrastructure queues)
19
- * @param {Object} [options.logger] - Logger instance (default: console)
20
- * @returns {Promise<void>}
21
- */
22
- async function initInfrastructureQueues(channel, options = {}) {
23
- const logger = options.logger || console;
24
- const queuesToCreate = options.queues || [
25
- // Workflow infrastructure queues
26
- 'workflow.init',
27
- 'workflow.completed',
28
- 'workflow.failed',
29
- 'workflow.dlq',
30
- // Registry infrastructure queues
31
- 'registry.register',
32
- 'registry.heartbeats'
33
- ];
34
-
35
- logger.log(`[QueueInit] Initializing ${queuesToCreate.length} infrastructure queues...`);
36
-
37
- for (const queueName of queuesToCreate) {
38
- try {
39
- // Verify it's an infrastructure queue
40
- if (!queueConfig.isInfrastructureQueue(queueName)) {
41
- logger.warn(`[QueueInit] Skipping ${queueName} - not an infrastructure queue`);
42
- continue;
43
- }
44
-
45
- // Get unified configuration
46
- const config = queueConfig.getInfrastructureQueueConfig(queueName);
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;
62
- }
63
- }
64
- }
65
-
66
- logger.log(`[QueueInit] Infrastructure queues initialization complete`);
67
- }
68
-
69
- module.exports = {
70
- initInfrastructureQueues
71
- };
72
-