@onlineapps/mq-client-core 1.0.80 → 1.0.82
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
|
@@ -249,6 +249,7 @@ module.exports = {
|
|
|
249
249
|
monitoring: {
|
|
250
250
|
/**
|
|
251
251
|
* monitoring.workflow - Unified workflow lifecycle monitoring
|
|
252
|
+
* Bound to monitoring.workflow.fanout exchange (fanout).
|
|
252
253
|
* Delivery Dispatcher publishes 'completed'/'failed' events after processing workflow.completed/workflow.failed
|
|
253
254
|
* Business services publish 'progress' events during workflow step processing
|
|
254
255
|
* Message format includes event_type: 'completed' | 'failed' | 'progress'
|
|
@@ -277,6 +278,73 @@ module.exports = {
|
|
|
277
278
|
'x-message-ttl': 600000, // 10 minutes TTL (longer for service tracking)
|
|
278
279
|
'x-max-length': 20000
|
|
279
280
|
}
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* monitoring.workflow.fanout - Fanout exchange for workflow events
|
|
285
|
+
* Publishers send to this exchange; monitoring.workflow + delivery.workflow.events both receive copies.
|
|
286
|
+
*/
|
|
287
|
+
'workflow.fanout': {
|
|
288
|
+
type: 'exchange',
|
|
289
|
+
exchangeType: 'fanout',
|
|
290
|
+
durable: true
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Delivery endpoint event queue configurations
|
|
296
|
+
* These are queues consumed by api_delivery_endpoint for real-time WS push.
|
|
297
|
+
*/
|
|
298
|
+
deliveryEvents: {
|
|
299
|
+
/**
|
|
300
|
+
* delivery.workflow.events - Workflow progress events for WS clients
|
|
301
|
+
* Bound to monitoring.workflow.fanout exchange.
|
|
302
|
+
*/
|
|
303
|
+
'workflow.events': {
|
|
304
|
+
durable: true,
|
|
305
|
+
arguments: {
|
|
306
|
+
'x-message-ttl': 60000, // 60s TTL - real-time events, stale ones are useless
|
|
307
|
+
'x-max-length': 10000
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* delivery.health.events - Infrastructure health events for WS clients
|
|
313
|
+
* Bound to infrastructure.health.events exchange.
|
|
314
|
+
*/
|
|
315
|
+
'health.events': {
|
|
316
|
+
durable: true,
|
|
317
|
+
arguments: {
|
|
318
|
+
'x-message-ttl': 60000,
|
|
319
|
+
'x-max-length': 5000
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* delivery.alert.events - Alert events for WS clients
|
|
325
|
+
* Bound to alert.events.fanout exchange.
|
|
326
|
+
*/
|
|
327
|
+
'alert.events': {
|
|
328
|
+
durable: true,
|
|
329
|
+
arguments: {
|
|
330
|
+
'x-message-ttl': 300000, // 5 minutes - alerts are more important
|
|
331
|
+
'x-max-length': 5000
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Alert exchange configuration
|
|
338
|
+
*/
|
|
339
|
+
alerts: {
|
|
340
|
+
/**
|
|
341
|
+
* alert.events.fanout - Fanout exchange for alert events
|
|
342
|
+
* Monitoring watchdogs publish alerts here; delivery.alert.events consumes copies.
|
|
343
|
+
*/
|
|
344
|
+
'events.fanout': {
|
|
345
|
+
type: 'exchange',
|
|
346
|
+
exchangeType: 'fanout',
|
|
347
|
+
durable: true
|
|
280
348
|
}
|
|
281
349
|
},
|
|
282
350
|
|
|
@@ -103,7 +103,11 @@ async function publishToMonitoringResilient(mqClient, queueName, message, logger
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
try {
|
|
106
|
-
|
|
106
|
+
const WORKFLOW_FANOUT = 'monitoring.workflow.fanout';
|
|
107
|
+
const publishOptions = queueName === 'monitoring.workflow'
|
|
108
|
+
? { exchange: WORKFLOW_FANOUT, exchangeType: 'fanout', routingKey: '' }
|
|
109
|
+
: {};
|
|
110
|
+
await mqClient.publish(queueName, message, publishOptions);
|
|
107
111
|
|
|
108
112
|
if (logger && logger.debug) {
|
|
109
113
|
logger.debug(`Published to ${queueName}`, {
|
|
@@ -1258,7 +1258,8 @@ class RabbitMQClient extends EventEmitter {
|
|
|
1258
1258
|
// Track operation for debugging
|
|
1259
1259
|
this._trackChannelOperation(this._channel, `publish to ${queue}`);
|
|
1260
1260
|
|
|
1261
|
-
const exchange = this._config.exchange || '';
|
|
1261
|
+
const exchange = options.exchange || this._config.exchange || '';
|
|
1262
|
+
const exchangeType = options.exchangeType || 'direct';
|
|
1262
1263
|
const routingKey = options.routingKey || queue;
|
|
1263
1264
|
const persistent = options.persistent !== undefined ? options.persistent : this._config.durable;
|
|
1264
1265
|
const headers = options.headers || {};
|
|
@@ -1473,7 +1474,7 @@ class RabbitMQClient extends EventEmitter {
|
|
|
1473
1474
|
} else {
|
|
1474
1475
|
// If exchange is specified, assert exchange and publish to it
|
|
1475
1476
|
// Channel is guaranteed to be open (ensured above)
|
|
1476
|
-
await this._channel.assertExchange(exchange,
|
|
1477
|
+
await this._channel.assertExchange(exchange, exchangeType, { durable: this._config.durable });
|
|
1477
1478
|
|
|
1478
1479
|
// Use callback-based confirmation - kanály jsou spolehlivé
|
|
1479
1480
|
const confirmPromise = new Promise((resolve, reject) => {
|
|
@@ -1715,25 +1716,23 @@ class RabbitMQClient extends EventEmitter {
|
|
|
1715
1716
|
try {
|
|
1716
1717
|
await onMessage(msg);
|
|
1717
1718
|
// Acknowledge message after successful processing
|
|
1718
|
-
if (!noAck) {
|
|
1719
|
-
// Only ack on the original channel instance; if it's gone/closed, do not attempt ack.
|
|
1719
|
+
if (!noAck && !msg._mqProcessed) {
|
|
1720
1720
|
if (!channelForMsg || channelForMsg.closed) {
|
|
1721
1721
|
console.warn('[RabbitMQClient] [mq-client-core] [CONSUMER] Cannot ack - consumer channel is closed/recreated (message will be requeued by broker)');
|
|
1722
1722
|
return;
|
|
1723
1723
|
}
|
|
1724
1724
|
try {
|
|
1725
1725
|
channelForMsg.ack(msg);
|
|
1726
|
+
msg._mqProcessed = true;
|
|
1726
1727
|
} catch (ackErr) {
|
|
1727
|
-
// If the channel was closed/recreated during processing, delivery tag becomes invalid.
|
|
1728
|
-
// Do NOT send further acks that would close the channel with 406.
|
|
1729
1728
|
const m = ackErr && ackErr.message ? ackErr.message : '';
|
|
1730
1729
|
if (m.includes('unknown delivery tag') || m.includes('PRECONDITION_FAILED') || m.includes('Channel closed')) {
|
|
1730
|
+
msg._mqProcessed = true;
|
|
1731
1731
|
console.warn('[RabbitMQClient] [mq-client-core] [CONSUMER] Ack failed due to invalid delivery tag / closed channel (message delivery will be handled by broker)', { error: m });
|
|
1732
1732
|
return;
|
|
1733
1733
|
}
|
|
1734
1734
|
throw ackErr;
|
|
1735
1735
|
}
|
|
1736
|
-
// Structured log: message acknowledged
|
|
1737
1736
|
this._log.output(wfId, 'MSG_ACKED', {
|
|
1738
1737
|
handler: 'consume',
|
|
1739
1738
|
function: 'RabbitMQClient.consume',
|
|
@@ -1741,7 +1740,6 @@ class RabbitMQClient extends EventEmitter {
|
|
|
1741
1740
|
output: { status: 'acknowledged', queue }
|
|
1742
1741
|
});
|
|
1743
1742
|
|
|
1744
|
-
// Track ack (decrement in-flight)
|
|
1745
1743
|
if (tracking) {
|
|
1746
1744
|
tracking.inFlight = Math.max(0, tracking.inFlight - 1);
|
|
1747
1745
|
tracking.lastCheck = Date.now();
|
|
@@ -1749,16 +1747,16 @@ class RabbitMQClient extends EventEmitter {
|
|
|
1749
1747
|
}
|
|
1750
1748
|
} catch (handlerErr) {
|
|
1751
1749
|
// Negative acknowledge and requeue by default
|
|
1752
|
-
//
|
|
1753
|
-
if (channelForMsg && !channelForMsg.closed) {
|
|
1750
|
+
// Skip if already acked/nacked by the handler (prevents double ack → 406)
|
|
1751
|
+
if (!msg._mqProcessed && channelForMsg && !channelForMsg.closed) {
|
|
1754
1752
|
try {
|
|
1755
1753
|
channelForMsg.nack(msg, false, true);
|
|
1754
|
+
msg._mqProcessed = true;
|
|
1756
1755
|
} catch (nackErr) {
|
|
1757
|
-
|
|
1756
|
+
msg._mqProcessed = true;
|
|
1758
1757
|
console.warn(`[RabbitMQClient] [mq-client-core] Failed to nack message (channel may be closed): ${nackErr.message}`);
|
|
1759
1758
|
}
|
|
1760
1759
|
}
|
|
1761
|
-
// Track nack (decrement in-flight)
|
|
1762
1760
|
if (tracking) {
|
|
1763
1761
|
tracking.inFlight = Math.max(0, tracking.inFlight - 1);
|
|
1764
1762
|
tracking.lastCheck = Date.now();
|
|
@@ -2218,14 +2216,19 @@ class RabbitMQClient extends EventEmitter {
|
|
|
2218
2216
|
* @param {Object} msg - RabbitMQ message object.
|
|
2219
2217
|
*/
|
|
2220
2218
|
async ack(msg) {
|
|
2219
|
+
if (msg._mqProcessed) {
|
|
2220
|
+
return; // Already acked/nacked — idempotent
|
|
2221
|
+
}
|
|
2221
2222
|
if (!this._consumerChannel) {
|
|
2222
2223
|
throw new Error('Cannot ack: consumer channel is not initialized');
|
|
2223
2224
|
}
|
|
2224
2225
|
try {
|
|
2225
2226
|
this._consumerChannel.ack(msg);
|
|
2227
|
+
msg._mqProcessed = true;
|
|
2226
2228
|
} catch (err) {
|
|
2227
2229
|
const m = err && err.message ? err.message : '';
|
|
2228
2230
|
if (m.includes('unknown delivery tag') || m.includes('PRECONDITION_FAILED') || m.includes('Channel closed')) {
|
|
2231
|
+
msg._mqProcessed = true;
|
|
2229
2232
|
console.warn('[RabbitMQClient] [mq-client-core] Cannot ack - consumer channel is closed/recreated (delivery tag invalid)', { error: m });
|
|
2230
2233
|
return;
|
|
2231
2234
|
}
|
|
@@ -2240,15 +2243,20 @@ class RabbitMQClient extends EventEmitter {
|
|
|
2240
2243
|
* @param {Object} [options] - { requeue: boolean }.
|
|
2241
2244
|
*/
|
|
2242
2245
|
async nack(msg, options = {}) {
|
|
2246
|
+
if (msg._mqProcessed) {
|
|
2247
|
+
return; // Already acked/nacked — idempotent
|
|
2248
|
+
}
|
|
2243
2249
|
if (!this._consumerChannel) {
|
|
2244
2250
|
throw new Error('Cannot nack: consumer channel is not initialized');
|
|
2245
2251
|
}
|
|
2246
2252
|
const requeue = options.requeue !== undefined ? options.requeue : true;
|
|
2247
2253
|
try {
|
|
2248
2254
|
this._consumerChannel.nack(msg, false, requeue);
|
|
2255
|
+
msg._mqProcessed = true;
|
|
2249
2256
|
} catch (err) {
|
|
2250
2257
|
const m = err && err.message ? err.message : '';
|
|
2251
2258
|
if (m.includes('unknown delivery tag') || m.includes('PRECONDITION_FAILED') || m.includes('Channel closed')) {
|
|
2259
|
+
msg._mqProcessed = true;
|
|
2252
2260
|
console.warn('[RabbitMQClient] [mq-client-core] Cannot nack - consumer channel is closed/recreated (delivery tag invalid)', { error: m });
|
|
2253
2261
|
return;
|
|
2254
2262
|
}
|