@onlineapps/mq-client-core 1.0.40 → 1.0.43

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,13 +1,15 @@
1
1
  {
2
2
  "name": "@onlineapps/mq-client-core",
3
- "version": "1.0.40",
3
+ "version": "1.0.43",
4
4
  "description": "Core MQ client library for RabbitMQ - shared by infrastructure services and connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
7
7
  "test": "jest",
8
8
  "test:unit": "jest --testPathPattern=tests/unit",
9
9
  "test:component": "jest --testPathPattern=tests/component",
10
- "test:integration": "jest --testPathPattern=tests/integration"
10
+ "test:integration": "jest --testPathPattern=tests/integration",
11
+ "prepublishOnly": "cd $(git rev-parse --show-toplevel) && bash scripts/pre-publish-compatibility-check.sh shared/mq-client-core",
12
+ "postpublish": "cd $(git rev-parse --show-toplevel) && bash scripts/update-manifest-from-npm.sh && bash scripts/update-all-services.sh"
11
13
  },
12
14
  "keywords": [
13
15
  "rabbitmq",
package/src/BaseClient.js CHANGED
@@ -32,6 +32,20 @@ class BaseClient {
32
32
 
33
33
  // Merge user config with defaults
34
34
  this._config = merge({}, defaultConfig, config || {});
35
+
36
+ // EXCEPTION: Fallback for infrastructure client configuration
37
+ // ENV variable name: RABBITMQ_URL (has priority over config.host and defaultConfig.host)
38
+ // Priority: config.host (explicit) → RABBITMQ_URL ENV → defaultConfig.host fallback
39
+ // Only use ENV if host was not explicitly provided in config
40
+ const userProvidedHost = (config && config.host);
41
+ if (!userProvidedHost) {
42
+ // ENV has priority over defaultConfig fallback
43
+ // Check ENV at runtime (not at module load time)
44
+ if (process.env.RABBITMQ_URL && process.env.RABBITMQ_URL.trim() !== '') {
45
+ this._config.host = process.env.RABBITMQ_URL;
46
+ }
47
+ }
48
+
35
49
  this._config.heartbeat =
36
50
  this._config.heartbeat ?? Number(process.env.RABBITMQ_HEARTBEAT || 30);
37
51
  this._config.connectionName =
@@ -6,14 +6,28 @@
6
6
  * Provides default configuration values for MQ Client Core.
7
7
  * Users can override any of these by passing a custom config object
8
8
  * or by supplying overrides to connect().
9
+ *
10
+ * EXCEPTION: Fallbacks are allowed ONLY for infrastructure client configuration.
11
+ * This is the ONLY place where fallbacks are acceptable.
12
+ *
13
+ * Environment variable (checked at runtime, has priority):
14
+ * - RABBITMQ_URL: Full RabbitMQ URL (e.g., amqp://guest:guest@api_services_queuer:5672)
15
+ *
16
+ * Fallback (used only if RABBITMQ_URL is not set):
17
+ * - amqp://guest:guest@api_services_queuer:5672 (docker-compose default)
18
+ *
19
+ * NOTE: host is resolved at runtime in BaseClient constructor, not at module load time.
9
20
  */
10
21
 
11
22
  module.exports = {
12
23
  // Transport type: currently only 'rabbitmq' is fully supported.
13
24
  type: 'rabbitmq',
14
25
 
15
- // RabbitMQ connection URI or hostname (e.g., 'amqp://localhost:5672').
16
- host: 'amqp://localhost:5672',
26
+ // RabbitMQ connection URI or hostname.
27
+ // NOTE: This is a fallback value. BaseClient constructor will check RABBITMQ_URL ENV variable
28
+ // and override this if ENV is set. ENV variable name: RABBITMQ_URL
29
+ // Fallback: amqp://guest:guest@api_services_queuer:5672 (docker-compose default)
30
+ host: 'amqp://guest:guest@api_services_queuer:5672',
17
31
 
18
32
  // Default queue name; can be overridden per call to publish/consume.
19
33
  // Optional for infrastructure services (they may not need a default queue).
@@ -23,12 +23,22 @@ module.exports = {
23
23
  /**
24
24
  * workflow.init - Entry point for all workflows
25
25
  * All services listen as competing consumers
26
+ *
27
+ * TTL: 30 seconds - if no business service processes message within 30s, it expires
28
+ * DLQ routing: Expired messages go to workflow.failed (via x-dead-letter-exchange)
29
+ *
30
+ * Rationale:
31
+ * - Fast failure detection (30s is enough for service discovery + processing start)
32
+ * - Prevents message accumulation when no business services are running
33
+ * - Failed messages go to workflow.failed for monitoring/alerting
26
34
  */
27
35
  init: {
28
36
  durable: true,
29
37
  arguments: {
30
- 'x-message-ttl': 300000, // 5 minutes TTL
31
- 'x-max-length': 10000 // Max 10k messages
38
+ 'x-message-ttl': 30000, // 30 seconds TTL (fast failure if no service available)
39
+ 'x-max-length': 10000, // Max 10k messages
40
+ 'x-dead-letter-exchange': '', // Use default exchange for DLQ routing
41
+ 'x-dead-letter-routing-key': 'workflow.failed' // Route expired messages to workflow.failed
32
42
  }
33
43
  },
34
44
 
@@ -122,6 +122,7 @@ class RabbitMQClient extends EventEmitter {
122
122
  client: this,
123
123
  scope: this._config.recoveryScope || 'infrastructure',
124
124
  queueCreationFilter: this._config.queueCreationFilter || null,
125
+ queueCreationCallback: this._config.queueCreationCallback || null, // Delegates to QueueManager if provided
125
126
  logger: console,
126
127
  });
127
128
 
@@ -232,6 +233,14 @@ class RabbitMQClient extends EventEmitter {
232
233
  };
233
234
  }
234
235
 
236
+ /**
237
+ * Check if client is connected
238
+ * @returns {boolean} True if connection exists and is not closed
239
+ */
240
+ isConnected() {
241
+ return !!(this._connection && !this._connection.closed);
242
+ }
243
+
235
244
  /**
236
245
  * Getter for channel - provides compatibility with QueueManager
237
246
  * Returns ConfirmChannel for publish operations
@@ -1110,6 +1119,21 @@ class RabbitMQClient extends EventEmitter {
1110
1119
  // Jasný, hlasitý signál pro infra služby – fronta chybí, je to programátorská chyba
1111
1120
  throw new QueueNotFoundError(queue, true, checkErr);
1112
1121
  }
1122
+ // For non-infrastructure queues, check if queue creation is allowed before auto-creating
1123
+ // Check recovery scope and filter
1124
+ if (this._recoveryWorker && !this._recoveryWorker._queueCreationEnabled) {
1125
+ // Queue creation disabled for this scope (e.g., infrastructure scope)
1126
+ throw new QueueNotFoundError(queue, false, checkErr);
1127
+ }
1128
+
1129
+ // Check queue creation filter if provided
1130
+ if (this._recoveryWorker && this._recoveryWorker._queueCreationFilter) {
1131
+ if (!this._recoveryWorker._queueCreationFilter(queue)) {
1132
+ // Queue creation filtered out - throw QueueNotFoundError so RecoveryWorker can handle it
1133
+ throw new QueueNotFoundError(queue, false, checkErr);
1134
+ }
1135
+ }
1136
+
1113
1137
  // For non-infrastructure queues, allow auto-creation with default options
1114
1138
  const queueOptions = options.queueOptions || { durable: this._config.durable };
1115
1139
  console.warn(`[RabbitMQClient] [mq-client-core] [PUBLISH] Auto-creating non-infrastructure queue ${queue} with default options (no TTL). This should be avoided for production.`);
@@ -8,12 +8,18 @@ const { QueueNotFoundError } = require('../utils/publishErrors');
8
8
  * Scope:
9
9
  * - infrastructure: connection recovery, channel recreation, consumer re-registration (NENÍ queue creation)
10
10
  * - business: connection recovery, channel recreation, consumer re-registration, queue creation (pokud není infra queue)
11
+ *
12
+ * Queue Creation:
13
+ * - For business queues: delegates to queueCreationCallback if provided (e.g., QueueManager.setupServiceQueues)
14
+ * - Falls back to direct queue creation if callback not provided (for backward compatibility)
11
15
  */
12
16
  class RecoveryWorker {
13
17
  /**
14
18
  * @param {Object} options
15
19
  * @param {Object} options.client - RabbitMQClient instance
16
20
  * @param {string} [options.scope='infrastructure'] - 'infrastructure' nebo 'business'
21
+ * @param {Function} [options.queueCreationCallback] - Callback for queue creation: async (queueName, options) => void
22
+ * Should delegate to QueueManager (e.g., setupServiceQueues or ensureQueue)
17
23
  * @param {Object} [options.logger] - Logger
18
24
  */
19
25
  constructor(options = {}) {
@@ -28,6 +34,9 @@ class RecoveryWorker {
28
34
  // Scope configuration
29
35
  this._queueCreationEnabled = this._scope === 'business';
30
36
  this._queueCreationFilter = options.queueCreationFilter || null;
37
+
38
+ // Queue creation callback - delegates to QueueManager if provided
39
+ this._queueCreationCallback = options.queueCreationCallback || null;
31
40
  }
32
41
 
33
42
  /**
@@ -66,8 +75,17 @@ class RecoveryWorker {
66
75
  }
67
76
 
68
77
  try {
69
- await this.createQueue(error.queueName, context.options);
70
- this._logger?.info?.(`[RecoveryWorker] Created queue '${error.queueName}'`);
78
+ // Use queue creation callback if provided (delegates to QueueManager)
79
+ // Otherwise fall back to direct queue creation (backward compatibility)
80
+ if (this._queueCreationCallback) {
81
+ this._logger?.info?.(`[RecoveryWorker] Delegating queue creation to callback for '${error.queueName}'`);
82
+ await this._queueCreationCallback(error.queueName, context.options);
83
+ this._logger?.info?.(`[RecoveryWorker] ✓ Queue '${error.queueName}' created via callback`);
84
+ } else {
85
+ // Fallback: direct queue creation (for backward compatibility)
86
+ await this.createQueue(error.queueName, context.options);
87
+ this._logger?.info?.(`[RecoveryWorker] ✓ Created queue '${error.queueName}' (direct)`);
88
+ }
71
89
  } catch (createErr) {
72
90
  this._logger?.error?.(`[RecoveryWorker] Failed to create queue '${error.queueName}': ${createErr.message}`);
73
91
  throw error;