@onlineapps/mq-client-core 1.0.45 → 1.0.47

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/mq-client-core",
3
- "version": "1.0.45",
3
+ "version": "1.0.47",
4
4
  "description": "Core MQ client library for RabbitMQ - shared by infrastructure services and connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -336,6 +336,7 @@ module.exports = {
336
336
  queueName.startsWith('infrastructure.') ||
337
337
  queueName.startsWith('validation.') ||
338
338
  queueName.startsWith('monitoring.') ||
339
+ queueName.startsWith('telemetry.') ||
339
340
  queueName.startsWith('delivery.');
340
341
  },
341
342
 
@@ -436,7 +437,7 @@ module.exports = {
436
437
 
437
438
  /**
438
439
  * Get monitoring queue configuration
439
- * @param {string} queueName - Queue name (e.g., 'monitoring.workflow.completed')
440
+ * @param {string} queueName - Queue name (e.g., 'monitoring.workflow')
440
441
  * @returns {Object} Queue configuration
441
442
  */
442
443
  getMonitoringQueueConfig(queueName) {
@@ -449,7 +450,7 @@ module.exports = {
449
450
 
450
451
  /**
451
452
  * Get infrastructure queue configuration by queue name (auto-detect type)
452
- * @param {string} queueName - Full queue name (e.g., 'workflow.init', 'registry.register', 'infrastructure.health.checks', 'monitoring.workflow.completed')
453
+ * @param {string} queueName - Full queue name (e.g., 'workflow.init', 'registry.register', 'infrastructure.health.checks', 'monitoring.workflow')
453
454
  * @returns {Object} Queue configuration
454
455
  */
455
456
  getInfrastructureQueueConfig(queueName) {
package/src/index.js CHANGED
@@ -25,6 +25,12 @@ const {
25
25
  QueueNotFoundError,
26
26
  classifyPublishError,
27
27
  } = require('./utils/publishErrors');
28
+ const {
29
+ publishToMonitoringResilient,
30
+ publishToMonitoringWorkflow,
31
+ publishToMonitoringServices,
32
+ isQueueUnavailableError,
33
+ } = require('./monitoring-publish');
28
34
 
29
35
  // Export BaseClient as default (constructor), with additional named exports
30
36
  // NOTE: When destructuring, use: const { BaseClient } = require('@onlineapps/mq-client-core');
@@ -46,4 +52,10 @@ module.exports.publishErrors = {
46
52
  QueueNotFoundError,
47
53
  classifyPublishError,
48
54
  };
55
+ module.exports.monitoring = {
56
+ publishToMonitoringResilient,
57
+ publishToMonitoringWorkflow,
58
+ publishToMonitoringServices,
59
+ isQueueUnavailableError,
60
+ };
49
61
 
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Unified Monitoring Publish Helper
3
+ *
4
+ * Provides resilient, fail-safe publishing to monitoring queues (monitoring.workflow, monitoring.services).
5
+ *
6
+ * Principles:
7
+ * - Fail-safe: Never throws errors, always logs failures
8
+ * - Resilient: Handles queue unavailability gracefully (queue doesn't exist yet, RabbitMQ down)
9
+ * - Unified: Single API for all monitoring publish operations across all services
10
+ * - Encapsulated: All monitoring publish logic in one place, no duplication
11
+ *
12
+ * Usage:
13
+ * const { publishToMonitoringWorkflow, publishToMonitoringServices } = require('@onlineapps/mq-client-core');
14
+ * await publishToMonitoringWorkflow(mqClient, { event_type: 'completed', workflow_id: '...', ... }, logger);
15
+ */
16
+
17
+ const { PublishError, ConnectionError } = require('./utils/errorHandler');
18
+ const { QueueNotFoundError, classifyPublishError } = require('./utils/publishErrors');
19
+
20
+ /**
21
+ * Check if error indicates queue doesn't exist or RabbitMQ is unavailable
22
+ * @private
23
+ */
24
+ function isQueueUnavailableError(error) {
25
+ if (!error) return false;
26
+
27
+ const errorMessage = error.message || String(error);
28
+ const errorCode = error.code || '';
29
+ const errorName = error.name || '';
30
+
31
+ // Connection errors
32
+ if (error instanceof ConnectionError ||
33
+ errorCode === 'ECONNREFUSED' ||
34
+ errorCode === 'ENOTFOUND' ||
35
+ errorCode === 'ETIMEDOUT' ||
36
+ errorMessage.includes('not connected') ||
37
+ errorMessage.includes('Connection closed')) {
38
+ return true;
39
+ }
40
+
41
+ // Queue doesn't exist errors
42
+ if (error instanceof QueueNotFoundError ||
43
+ errorName === 'QueueNotFoundError' ||
44
+ errorMessage.includes('NOT_FOUND') ||
45
+ errorMessage.includes('404') ||
46
+ errorMessage.includes('no queue') ||
47
+ errorMessage.includes('queue does not exist') ||
48
+ errorMessage.includes('Channel closed')) {
49
+ return true;
50
+ }
51
+
52
+ // Classify publish errors
53
+ const classified = classifyPublishError(error);
54
+ if (classified instanceof QueueNotFoundError) {
55
+ return true;
56
+ }
57
+
58
+ return false;
59
+ }
60
+
61
+ /**
62
+ * Unified resilient publish to monitoring queue
63
+ *
64
+ * @param {Object} mqClient - BaseClient instance (must be connected)
65
+ * @param {string} queueName - Queue name ('monitoring.workflow' or 'monitoring.services')
66
+ * @param {Object} message - Message to publish (must include event_type)
67
+ * @param {Object} [logger] - Logger instance (optional, for logging)
68
+ * @param {Object} [context] - Additional context for logging (optional)
69
+ * @returns {Promise<boolean>} - true if published successfully, false otherwise (never throws)
70
+ */
71
+ async function publishToMonitoringResilient(mqClient, queueName, message, logger = null, context = {}) {
72
+ // Validate inputs
73
+ if (!mqClient) {
74
+ if (logger) {
75
+ logger.warn('Monitoring publish skipped: mqClient not provided', context);
76
+ }
77
+ return false;
78
+ }
79
+
80
+ if (!mqClient.isConnected || typeof mqClient.isConnected !== 'function' || !mqClient.isConnected()) {
81
+ if (logger) {
82
+ logger.warn(`Monitoring publish skipped: mqClient not connected (queue: ${queueName})`, context);
83
+ }
84
+ return false;
85
+ }
86
+
87
+ if (!message || !message.event_type) {
88
+ if (logger) {
89
+ logger.warn(`Monitoring publish skipped: message missing event_type (queue: ${queueName})`, {
90
+ ...context,
91
+ hasMessage: !!message
92
+ });
93
+ }
94
+ return false;
95
+ }
96
+
97
+ // Validate queue name
98
+ if (queueName !== 'monitoring.workflow' && queueName !== 'monitoring.services') {
99
+ if (logger) {
100
+ logger.warn(`Monitoring publish skipped: invalid queue name (expected monitoring.workflow or monitoring.services, got: ${queueName})`, context);
101
+ }
102
+ return false;
103
+ }
104
+
105
+ try {
106
+ await mqClient.publish(queueName, message);
107
+
108
+ if (logger && logger.debug) {
109
+ logger.debug(`Published to ${queueName}`, {
110
+ ...context,
111
+ event_type: message.event_type,
112
+ workflow_id: message.workflow_id,
113
+ service_name: message.service_name
114
+ });
115
+ }
116
+
117
+ return true;
118
+ } catch (error) {
119
+ // Check if this is a queue unavailable error
120
+ if (isQueueUnavailableError(error)) {
121
+ // Queue doesn't exist yet or RabbitMQ is unavailable - log but don't fail
122
+ if (logger && logger.warn) {
123
+ logger.warn(`Monitoring queue ${queueName} not available (queue may not exist yet or RabbitMQ unavailable)`, {
124
+ ...context,
125
+ error: error.message,
126
+ errorCode: error.code,
127
+ event_type: message.event_type,
128
+ workflow_id: message.workflow_id,
129
+ service_name: message.service_name
130
+ });
131
+ }
132
+ return false;
133
+ }
134
+
135
+ // Other errors - log as warning but don't fail
136
+ if (logger && logger.warn) {
137
+ logger.warn(`Failed to publish to monitoring queue ${queueName}`, {
138
+ ...context,
139
+ error: error.message,
140
+ errorCode: error.code,
141
+ errorName: error.name,
142
+ event_type: message.event_type,
143
+ workflow_id: message.workflow_id,
144
+ service_name: message.service_name
145
+ });
146
+ }
147
+
148
+ return false;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Publish workflow event to monitoring.workflow queue
154
+ *
155
+ * @param {Object} mqClient - BaseClient instance (must be connected)
156
+ * @param {Object} message - Message with event_type ('completed' | 'failed' | 'progress'), workflow_id, etc.
157
+ * @param {Object} [logger] - Logger instance (optional)
158
+ * @param {Object} [context] - Additional context for logging (optional)
159
+ * @returns {Promise<boolean>} - true if published successfully, false otherwise (never throws)
160
+ *
161
+ * @example
162
+ * await publishToMonitoringWorkflow(mqClient, {
163
+ * event_type: 'completed',
164
+ * workflow_id: 'wf-123',
165
+ * service_name: 'hello-service',
166
+ * cookbook: { ... },
167
+ * context: { ... },
168
+ * timestamp: new Date().toISOString()
169
+ * }, logger);
170
+ */
171
+ async function publishToMonitoringWorkflow(mqClient, message, logger = null, context = {}) {
172
+ return publishToMonitoringResilient(mqClient, 'monitoring.workflow', message, logger, context);
173
+ }
174
+
175
+ /**
176
+ * Publish service lifecycle event to monitoring.services queue
177
+ *
178
+ * @param {Object} mqClient - BaseClient instance (must be connected)
179
+ * @param {Object} message - Message with event_type ('service.registered' | 'service.validation.completed' | etc.), service_name, etc.
180
+ * @param {Object} [logger] - Logger instance (optional)
181
+ * @param {Object} [context] - Additional context for logging (optional)
182
+ * @returns {Promise<boolean>} - true if published successfully, false otherwise (never throws)
183
+ *
184
+ * @example
185
+ * await publishToMonitoringServices(mqClient, {
186
+ * event_type: 'service.registered',
187
+ * service_name: 'hello-service',
188
+ * version: '1.0.0',
189
+ * status: 'pending',
190
+ * timestamp: new Date().toISOString()
191
+ * }, logger);
192
+ */
193
+ async function publishToMonitoringServices(mqClient, message, logger = null, context = {}) {
194
+ return publishToMonitoringResilient(mqClient, 'monitoring.services', message, logger, context);
195
+ }
196
+
197
+ module.exports = {
198
+ publishToMonitoringResilient,
199
+ publishToMonitoringWorkflow,
200
+ publishToMonitoringServices,
201
+ isQueueUnavailableError
202
+ };
203
+