@onlineapps/mq-client-core 1.0.22 → 1.0.25
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
package/src/BaseClient.js
CHANGED
|
@@ -32,6 +32,11 @@ class BaseClient {
|
|
|
32
32
|
|
|
33
33
|
// Merge user config with defaults
|
|
34
34
|
this._config = merge({}, defaultConfig, config || {});
|
|
35
|
+
this._config.heartbeat =
|
|
36
|
+
this._config.heartbeat ?? Number(process.env.RABBITMQ_HEARTBEAT || 30);
|
|
37
|
+
this._config.connectionName =
|
|
38
|
+
this._config.connectionName ||
|
|
39
|
+
`${process.env.SERVICE_NAME || 'oa-service'}:${process.pid}`;
|
|
35
40
|
|
|
36
41
|
// Validate merged config
|
|
37
42
|
const valid = validate(this._config);
|
|
@@ -46,6 +51,8 @@ class BaseClient {
|
|
|
46
51
|
this._transport = null;
|
|
47
52
|
this._connected = false;
|
|
48
53
|
this._errorHandlers = [];
|
|
54
|
+
|
|
55
|
+
BaseClient._registerInstance(this);
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
/**
|
|
@@ -60,6 +67,11 @@ class BaseClient {
|
|
|
60
67
|
console.log(`[BaseClient] Starting connect() with config:`, JSON.stringify({ type: this._config.type, host: this._config.host }));
|
|
61
68
|
// Merge overrides into existing config
|
|
62
69
|
this._config = merge({}, this._config, options);
|
|
70
|
+
this._config.heartbeat =
|
|
71
|
+
this._config.heartbeat ?? Number(process.env.RABBITMQ_HEARTBEAT || 30);
|
|
72
|
+
this._config.connectionName =
|
|
73
|
+
this._config.connectionName ||
|
|
74
|
+
`${process.env.SERVICE_NAME || 'oa-service'}:${process.pid}`;
|
|
63
75
|
|
|
64
76
|
try {
|
|
65
77
|
console.log(`[BaseClient] Creating transport...`);
|
|
@@ -90,6 +102,7 @@ class BaseClient {
|
|
|
90
102
|
await this._transport.disconnect();
|
|
91
103
|
this._connected = false;
|
|
92
104
|
this._transport = null;
|
|
105
|
+
BaseClient._unregisterInstance(this);
|
|
93
106
|
} catch (err) {
|
|
94
107
|
throw new Error(`Error during disconnect: ${err.message}`);
|
|
95
108
|
}
|
|
@@ -243,3 +256,44 @@ class BaseClient {
|
|
|
243
256
|
|
|
244
257
|
module.exports = BaseClient;
|
|
245
258
|
|
|
259
|
+
BaseClient._instances = new Set();
|
|
260
|
+
BaseClient._cleanupRegistered = false;
|
|
261
|
+
BaseClient._cleanupRunning = false;
|
|
262
|
+
|
|
263
|
+
BaseClient._registerInstance = function registerInstance(instance) {
|
|
264
|
+
BaseClient._instances.add(instance);
|
|
265
|
+
if (!BaseClient._cleanupRegistered) {
|
|
266
|
+
BaseClient._registerCleanupHooks();
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
BaseClient._unregisterInstance = function unregisterInstance(instance) {
|
|
271
|
+
BaseClient._instances.delete(instance);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
BaseClient._registerCleanupHooks = function registerCleanupHooks() {
|
|
275
|
+
const handler = async (signal) => {
|
|
276
|
+
if (BaseClient._cleanupRunning) return;
|
|
277
|
+
BaseClient._cleanupRunning = true;
|
|
278
|
+
const disconnects = Array.from(BaseClient._instances).map((instance) =>
|
|
279
|
+
instance
|
|
280
|
+
.disconnect()
|
|
281
|
+
.catch((err) =>
|
|
282
|
+
console.warn('[BaseClient] Failed to disconnect during shutdown:', err.message)
|
|
283
|
+
)
|
|
284
|
+
);
|
|
285
|
+
await Promise.allSettled(disconnects);
|
|
286
|
+
BaseClient._instances.clear();
|
|
287
|
+
BaseClient._cleanupRunning = false;
|
|
288
|
+
if (signal && signal !== 'beforeExit') {
|
|
289
|
+
process.exit(0); // eslint-disable-line no-process-exit
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
['SIGINT', 'SIGTERM'].forEach((sig) => {
|
|
294
|
+
process.on(sig, () => handler(sig));
|
|
295
|
+
});
|
|
296
|
+
process.on('beforeExit', () => handler('beforeExit'));
|
|
297
|
+
BaseClient._cleanupRegistered = true;
|
|
298
|
+
};
|
|
299
|
+
|
|
@@ -113,6 +113,22 @@ module.exports = {
|
|
|
113
113
|
}
|
|
114
114
|
},
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Infrastructure health tracking queue configurations
|
|
118
|
+
*/
|
|
119
|
+
infrastructure: {
|
|
120
|
+
/**
|
|
121
|
+
* infrastructure.health.checks - Health check messages from infrastructure services
|
|
122
|
+
*/
|
|
123
|
+
'health.checks': {
|
|
124
|
+
durable: true,
|
|
125
|
+
arguments: {
|
|
126
|
+
'x-message-ttl': 10000, // 10 seconds TTL (prevent stale data)
|
|
127
|
+
'x-max-length': 1000
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
|
|
116
132
|
/**
|
|
117
133
|
* Registry infrastructure queue configurations
|
|
118
134
|
*/
|
|
@@ -197,7 +213,10 @@ module.exports = {
|
|
|
197
213
|
* @returns {boolean} True if infrastructure queue
|
|
198
214
|
*/
|
|
199
215
|
isInfrastructureQueue(queueName) {
|
|
200
|
-
return queueName.startsWith('workflow.') ||
|
|
216
|
+
return queueName.startsWith('workflow.') ||
|
|
217
|
+
queueName.startsWith('registry.') ||
|
|
218
|
+
queueName.startsWith('infrastructure.') ||
|
|
219
|
+
queueName.startsWith('validation.');
|
|
201
220
|
},
|
|
202
221
|
|
|
203
222
|
/**
|
|
@@ -255,9 +274,23 @@ module.exports = {
|
|
|
255
274
|
return config;
|
|
256
275
|
},
|
|
257
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Get infrastructure health queue configuration
|
|
279
|
+
* @param {string} queueName - Queue name (e.g., 'infrastructure.health.checks')
|
|
280
|
+
* @returns {Object} Queue configuration
|
|
281
|
+
*/
|
|
282
|
+
getInfrastructureHealthQueueConfig(queueName) {
|
|
283
|
+
const parts = queueName.split('.');
|
|
284
|
+
if (parts.length < 2 || parts[0] !== 'infrastructure') {
|
|
285
|
+
throw new Error(`Invalid infrastructure health queue name: ${queueName}. Expected format: infrastructure.{name}`);
|
|
286
|
+
}
|
|
287
|
+
const name = parts.slice(1).join('.');
|
|
288
|
+
return this.getQueueConfig('infrastructure', name);
|
|
289
|
+
},
|
|
290
|
+
|
|
258
291
|
/**
|
|
259
292
|
* Get infrastructure queue configuration by queue name (auto-detect type)
|
|
260
|
-
* @param {string} queueName - Full queue name (e.g., 'workflow.init', 'registry.register')
|
|
293
|
+
* @param {string} queueName - Full queue name (e.g., 'workflow.init', 'registry.register', 'infrastructure.health.checks')
|
|
261
294
|
* @returns {Object} Queue configuration
|
|
262
295
|
*/
|
|
263
296
|
getInfrastructureQueueConfig(queueName) {
|
|
@@ -265,8 +298,10 @@ module.exports = {
|
|
|
265
298
|
return this.getWorkflowQueueConfig(queueName);
|
|
266
299
|
} else if (queueName.startsWith('registry.')) {
|
|
267
300
|
return this.getRegistryQueueConfig(queueName);
|
|
301
|
+
} else if (queueName.startsWith('infrastructure.')) {
|
|
302
|
+
return this.getInfrastructureHealthQueueConfig(queueName);
|
|
268
303
|
} else {
|
|
269
|
-
throw new Error(`Queue ${queueName} is not an infrastructure queue. Infrastructure queues must start with 'workflow.' or '
|
|
304
|
+
throw new Error(`Queue ${queueName} is not an infrastructure queue. Infrastructure queues must start with 'workflow.', 'registry.', or 'infrastructure.'`);
|
|
270
305
|
}
|
|
271
306
|
}
|
|
272
307
|
};
|
package/src/index.js
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Core MQ client library for RabbitMQ - shared by infrastructure services and connectors.
|
|
7
7
|
* Provides basic publish/consume functionality without business-specific features.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: Infrastructure orchestration utilities (waitForInfrastructureReady, initInfrastructureQueues)
|
|
10
|
+
* have been moved to @onlineapps/infrastructure-tools to keep this library focused on core MQ operations.
|
|
8
11
|
*/
|
|
9
12
|
|
|
10
13
|
const BaseClient = require('./BaseClient');
|
|
@@ -16,16 +19,14 @@ const {
|
|
|
16
19
|
ConsumeError,
|
|
17
20
|
SerializationError,
|
|
18
21
|
} = require('./utils/errorHandler');
|
|
19
|
-
const { initInfrastructureQueues } = require('./utils/initInfrastructureQueues');
|
|
20
22
|
|
|
21
23
|
// Export BaseClient as default (constructor), with additional named exports
|
|
22
|
-
// NOTE: When destructuring, use: const {
|
|
24
|
+
// NOTE: When destructuring, use: const { BaseClient } = require('@onlineapps/mq-client-core');
|
|
23
25
|
// When using default, use: const BaseClient = require('@onlineapps/mq-client-core');
|
|
24
26
|
// BaseClient must be a constructor, so we export it directly and attach other exports as properties
|
|
25
27
|
module.exports = BaseClient;
|
|
26
28
|
module.exports.BaseClient = BaseClient;
|
|
27
29
|
module.exports.RabbitMQClient = RabbitMQClient;
|
|
28
|
-
module.exports.initInfrastructureQueues = initInfrastructureQueues;
|
|
29
30
|
module.exports.errors = {
|
|
30
31
|
ValidationError,
|
|
31
32
|
ConnectionError,
|
|
@@ -43,6 +43,13 @@ class RabbitMQClient extends EventEmitter {
|
|
|
43
43
|
get channel() {
|
|
44
44
|
return this._channel;
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Getter for underlying connection
|
|
49
|
+
*/
|
|
50
|
+
get connection() {
|
|
51
|
+
return this._connection;
|
|
52
|
+
}
|
|
46
53
|
|
|
47
54
|
/**
|
|
48
55
|
* Getter for queue channel - regular channel for queue operations
|
|
@@ -59,15 +66,25 @@ class RabbitMQClient extends EventEmitter {
|
|
|
59
66
|
*/
|
|
60
67
|
async connect() {
|
|
61
68
|
try {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
69
|
+
const rawTarget = this._config.host || this._config.url;
|
|
70
|
+
const heartbeat = this._config.heartbeat ?? 30;
|
|
71
|
+
const connectionName =
|
|
72
|
+
this._config.connectionName ||
|
|
73
|
+
this._config.clientName ||
|
|
74
|
+
`oa-client:${process.env.SERVICE_NAME || 'unknown'}:${process.pid}`;
|
|
75
|
+
|
|
76
|
+
console.log(`[RabbitMQClient] Attempting to connect to: ${rawTarget}`);
|
|
77
|
+
const connectArgs = typeof rawTarget === 'string'
|
|
78
|
+
? [rawTarget, { heartbeat, clientProperties: { connection_name: connectionName } }]
|
|
79
|
+
: [{ ...rawTarget, heartbeat, clientProperties: { connection_name: connectionName } }];
|
|
80
|
+
|
|
81
|
+
const connectPromise = amqp.connect(...connectArgs);
|
|
65
82
|
const timeoutPromise = new Promise((_, reject) => {
|
|
66
83
|
setTimeout(() => reject(new Error('Connection timeout after 10 seconds')), 10000);
|
|
67
84
|
});
|
|
68
|
-
console.log(
|
|
85
|
+
console.log('[RabbitMQClient] Starting connection race...');
|
|
69
86
|
this._connection = await Promise.race([connectPromise, timeoutPromise]);
|
|
70
|
-
console.log(
|
|
87
|
+
console.log('[RabbitMQClient] Connection established');
|
|
71
88
|
this._connection.on('error', (err) => this.emit('error', err));
|
|
72
89
|
this._connection.on('close', () => {
|
|
73
90
|
// Emit a connection close error to notify listeners
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* initInfrastructureQueues.js
|
|
5
|
-
*
|
|
6
|
-
* Utility for infrastructure services to initialize infrastructure queues.
|
|
7
|
-
* Uses queueConfig from @onlineapps/conn-infra-mq for configuration.
|
|
8
|
-
*
|
|
9
|
-
* NOTE: This is for infrastructure services (gateway, monitoring, etc.)
|
|
10
|
-
* Business services should use @onlineapps/conn-infra-mq connector instead.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Initialize all infrastructure queues
|
|
15
|
-
* @param {Object} channel - RabbitMQ channel
|
|
16
|
-
* @param {Object} options - Options
|
|
17
|
-
* @param {Array<string>} [options.queues] - Specific queues to create (default: all infrastructure queues)
|
|
18
|
-
* @param {Object} [options.logger] - Logger instance (default: console)
|
|
19
|
-
* @returns {Promise<void>}
|
|
20
|
-
*/
|
|
21
|
-
async function initInfrastructureQueues(channel, options = {}) {
|
|
22
|
-
const logger = options.logger || console;
|
|
23
|
-
|
|
24
|
-
// Load queueConfig from mq-client-core (spodní vrstva)
|
|
25
|
-
// NOTE: queueConfig is part of mq-client-core because it's used by both:
|
|
26
|
-
// - Infrastructure services (via mq-client-core)
|
|
27
|
-
// - Business services (via conn-infra-mq connector)
|
|
28
|
-
const queueConfig = options.queueConfig || require('../config/queueConfig');
|
|
29
|
-
|
|
30
|
-
const queuesToCreate = options.queues || [
|
|
31
|
-
// Workflow infrastructure queues
|
|
32
|
-
'workflow.init',
|
|
33
|
-
'workflow.completed',
|
|
34
|
-
'workflow.failed',
|
|
35
|
-
'workflow.dlq',
|
|
36
|
-
// Registry infrastructure queues
|
|
37
|
-
'registry.register',
|
|
38
|
-
'registry.heartbeats'
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
logger.log(`[QueueInit] Initializing ${queuesToCreate.length} infrastructure queues...`);
|
|
42
|
-
|
|
43
|
-
for (const queueName of queuesToCreate) {
|
|
44
|
-
try {
|
|
45
|
-
// Verify it's an infrastructure queue
|
|
46
|
-
if (!queueConfig.isInfrastructureQueue(queueName)) {
|
|
47
|
-
logger.warn(`[QueueInit] Skipping ${queueName} - not an infrastructure queue`);
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Get unified configuration
|
|
52
|
-
const config = queueConfig.getInfrastructureQueueConfig(queueName);
|
|
53
|
-
|
|
54
|
-
// CRITICAL: Check if queue exists with different arguments (406 error)
|
|
55
|
-
// If so, delete it and recreate with correct parameters
|
|
56
|
-
try {
|
|
57
|
-
await channel.assertQueue(queueName, {
|
|
58
|
-
durable: config.durable !== false,
|
|
59
|
-
arguments: { ...config.arguments }
|
|
60
|
-
});
|
|
61
|
-
logger.log(`[QueueInit] ✓ Created/verified infrastructure queue: ${queueName}`);
|
|
62
|
-
} catch (assertError) {
|
|
63
|
-
if (assertError.code === 406) {
|
|
64
|
-
// Queue exists with different arguments - delete and recreate
|
|
65
|
-
logger.warn(`[QueueInit] ⚠ Queue ${queueName} exists with different arguments - deleting and recreating...`);
|
|
66
|
-
try {
|
|
67
|
-
await channel.deleteQueue(queueName);
|
|
68
|
-
logger.log(`[QueueInit] ✓ Deleted queue ${queueName} with incorrect parameters`);
|
|
69
|
-
} catch (deleteError) {
|
|
70
|
-
logger.error(`[QueueInit] ✗ Failed to delete queue ${queueName}:`, deleteError.message);
|
|
71
|
-
throw new Error(`Cannot delete queue ${queueName} with incorrect parameters: ${deleteError.message}`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Recreate with correct parameters
|
|
75
|
-
await channel.assertQueue(queueName, {
|
|
76
|
-
durable: config.durable !== false,
|
|
77
|
-
arguments: { ...config.arguments }
|
|
78
|
-
});
|
|
79
|
-
logger.log(`[QueueInit] ✓ Recreated infrastructure queue: ${queueName} with correct parameters`);
|
|
80
|
-
} else {
|
|
81
|
-
// Other error - rethrow
|
|
82
|
-
logger.error(`[QueueInit] ✗ Failed to create ${queueName}:`, assertError.message);
|
|
83
|
-
throw assertError;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
} catch (error) {
|
|
87
|
-
logger.error(`[QueueInit] ✗ Failed to initialize queue ${queueName}:`, error.message);
|
|
88
|
-
throw error;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
logger.log(`[QueueInit] Infrastructure queues initialization complete`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
module.exports = {
|
|
96
|
-
initInfrastructureQueues
|
|
97
|
-
};
|
|
98
|
-
|