@onlineapps/mq-client-core 1.0.43 → 1.0.46

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.43",
3
+ "version": "1.0.46",
4
4
  "description": "Core MQ client library for RabbitMQ - shared by infrastructure services and connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -207,14 +207,20 @@ module.exports = {
207
207
 
208
208
  /**
209
209
  * Monitoring infrastructure queue configurations
210
- * These are dedicated queues for Monitoring service (separate from delivery queues)
210
+ * These are dedicated queues for Monitoring service (infrastructure service)
211
+ *
212
+ * CRITICAL: Monitoring is an infrastructure service, monitoring queues are infrastructure queues!
213
+ * All services (both infrastructure and business) publish monitoring events here.
211
214
  */
212
215
  monitoring: {
213
216
  /**
214
- * monitoring.workflow.completed - Completed workflows for monitoring
215
- * Business services publish here for observability (separate from workflow.completed for delivery)
217
+ * monitoring.workflow - Unified workflow lifecycle monitoring
218
+ * Delivery Dispatcher publishes 'completed'/'failed' events after processing workflow.completed/workflow.failed
219
+ * Business services publish 'progress' events during workflow step processing
220
+ * Message format includes event_type: 'completed' | 'failed' | 'progress'
221
+ * Includes full cookbook and context snapshots for trace storage
216
222
  */
217
- 'workflow.completed': {
223
+ 'workflow': {
218
224
  durable: true,
219
225
  arguments: {
220
226
  'x-message-ttl': 300000, // 5 minutes TTL
@@ -223,36 +229,19 @@ module.exports = {
223
229
  },
224
230
 
225
231
  /**
226
- * monitoring.workflow.failed - Failed workflows for monitoring
227
- * Business services publish here for observability (separate from workflow.failed for delivery)
232
+ * monitoring.services - Service lifecycle events monitoring
233
+ * Registry publishes service lifecycle events (registered, validation.completed, version.changed, status.changed)
234
+ * InfrastructureHealthTracker publishes service status changes
235
+ * Message format includes event_type: 'service.registered' | 'service.validation.completed' |
236
+ * 'service.version.changed' | 'service.deregistered' | 'service.status.changed'
237
+ * NOTE: Heartbeats are NOT published here (too frequent, every 10s)
238
+ * NOTE: Healthcheck messages go to infrastructure.health.events exchange
228
239
  */
229
- 'workflow.failed': {
240
+ 'services': {
230
241
  durable: true,
231
242
  arguments: {
232
- 'x-message-ttl': 300000, // 5 minutes TTL
233
- 'x-max-length': 10000
234
- }
235
- },
236
-
237
- /**
238
- * monitoring.registry.events - Registry events for monitoring
239
- */
240
- 'registry.events': {
241
- durable: true,
242
- arguments: {
243
- 'x-message-ttl': 60000, // 1 minute TTL
244
- 'x-max-length': 5000
245
- }
246
- },
247
-
248
- /**
249
- * monitoring.service.heartbeats - Service heartbeats for monitoring
250
- */
251
- 'service.heartbeats': {
252
- durable: true,
253
- arguments: {
254
- 'x-message-ttl': 120000, // 2 minutes TTL
255
- 'x-max-length': 10000
243
+ 'x-message-ttl': 600000, // 10 minutes TTL (longer for service tracking)
244
+ 'x-max-length': 20000
256
245
  }
257
246
  }
258
247
  },
@@ -262,27 +251,28 @@ module.exports = {
262
251
  */
263
252
  registry: {
264
253
  /**
265
- * registry.register - Registration requests
254
+ * registry.register - Main queue for ALL service communication with Registry
255
+ *
256
+ * CRITICAL: This is the ONLY queue used for service-to-Registry communication.
257
+ * Registry listener processes different message types based on msg.type:
258
+ * - type: 'register' - Service registration requests
259
+ * - type: 'heartbeat' - Periodic health check messages (sent every 10s)
260
+ * - type: 'apiDescription' - API specification responses
261
+ *
262
+ * Architecture rationale:
263
+ * - Single consumer on registry.register handles all message types
264
+ * - Simplifies queue management (one queue instead of multiple)
265
+ * - Registry can process messages in order and maintain state consistency
266
+ * - All business services send both registration AND heartbeats to this queue
266
267
  */
267
268
  register: {
268
269
  durable: true,
269
270
  arguments: {
270
- 'x-message-ttl': 60000, // 1 minute TTL
271
+ 'x-message-ttl': 60000, // 1 minute TTL (for registration requests)
271
272
  'x-max-length': 1000
272
273
  }
273
274
  },
274
275
 
275
- /**
276
- * registry.heartbeats - Service heartbeats
277
- */
278
- heartbeats: {
279
- durable: true,
280
- arguments: {
281
- 'x-message-ttl': 120000, // 2 minutes TTL
282
- 'x-max-length': 5000
283
- }
284
- },
285
-
286
276
  /**
287
277
  * registry.changes - Registry event fanout exchange
288
278
  * (This is an exchange, not a queue, but included for reference)
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
+