@onlineapps/mq-client-core 1.0.81 → 1.0.83

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.81",
3
+ "version": "1.0.83",
4
4
  "description": "Core MQ client library for RabbitMQ - shared by infrastructure services and connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -280,6 +280,19 @@ module.exports = {
280
280
  }
281
281
  },
282
282
 
283
+ /**
284
+ * monitoring.audit - Audit log for Doorman-exposed services (JWT-authenticated requests outside workflow)
285
+ * Meta-reader (and future Doorman-exposed services) publish audit entries via MQ.
286
+ * Monitoring consumer stores them in PostgreSQL audit_log table.
287
+ */
288
+ 'audit': {
289
+ durable: true,
290
+ arguments: {
291
+ 'x-message-ttl': 300000,
292
+ 'x-max-length': 50000
293
+ }
294
+ },
295
+
283
296
  /**
284
297
  * monitoring.workflow.fanout - Fanout exchange for workflow events
285
298
  * Publishers send to this exchange; monitoring.workflow + delivery.workflow.events both receive copies.
@@ -1716,25 +1716,23 @@ class RabbitMQClient extends EventEmitter {
1716
1716
  try {
1717
1717
  await onMessage(msg);
1718
1718
  // Acknowledge message after successful processing
1719
- if (!noAck) {
1720
- // Only ack on the original channel instance; if it's gone/closed, do not attempt ack.
1719
+ if (!noAck && !msg._mqProcessed) {
1721
1720
  if (!channelForMsg || channelForMsg.closed) {
1722
1721
  console.warn('[RabbitMQClient] [mq-client-core] [CONSUMER] Cannot ack - consumer channel is closed/recreated (message will be requeued by broker)');
1723
1722
  return;
1724
1723
  }
1725
1724
  try {
1726
1725
  channelForMsg.ack(msg);
1726
+ msg._mqProcessed = true;
1727
1727
  } catch (ackErr) {
1728
- // If the channel was closed/recreated during processing, delivery tag becomes invalid.
1729
- // Do NOT send further acks that would close the channel with 406.
1730
1728
  const m = ackErr && ackErr.message ? ackErr.message : '';
1731
1729
  if (m.includes('unknown delivery tag') || m.includes('PRECONDITION_FAILED') || m.includes('Channel closed')) {
1730
+ msg._mqProcessed = true;
1732
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 });
1733
1732
  return;
1734
1733
  }
1735
1734
  throw ackErr;
1736
1735
  }
1737
- // Structured log: message acknowledged
1738
1736
  this._log.output(wfId, 'MSG_ACKED', {
1739
1737
  handler: 'consume',
1740
1738
  function: 'RabbitMQClient.consume',
@@ -1742,7 +1740,6 @@ class RabbitMQClient extends EventEmitter {
1742
1740
  output: { status: 'acknowledged', queue }
1743
1741
  });
1744
1742
 
1745
- // Track ack (decrement in-flight)
1746
1743
  if (tracking) {
1747
1744
  tracking.inFlight = Math.max(0, tracking.inFlight - 1);
1748
1745
  tracking.lastCheck = Date.now();
@@ -1750,16 +1747,16 @@ class RabbitMQClient extends EventEmitter {
1750
1747
  }
1751
1748
  } catch (handlerErr) {
1752
1749
  // Negative acknowledge and requeue by default
1753
- // Check if channel is still valid before nacking
1754
- if (channelForMsg && !channelForMsg.closed) {
1750
+ // Skip if already acked/nacked by the handler (prevents double ack → 406)
1751
+ if (!msg._mqProcessed && channelForMsg && !channelForMsg.closed) {
1755
1752
  try {
1756
1753
  channelForMsg.nack(msg, false, true);
1754
+ msg._mqProcessed = true;
1757
1755
  } catch (nackErr) {
1758
- // Channel may have closed during nack - ignore
1756
+ msg._mqProcessed = true;
1759
1757
  console.warn(`[RabbitMQClient] [mq-client-core] Failed to nack message (channel may be closed): ${nackErr.message}`);
1760
1758
  }
1761
1759
  }
1762
- // Track nack (decrement in-flight)
1763
1760
  if (tracking) {
1764
1761
  tracking.inFlight = Math.max(0, tracking.inFlight - 1);
1765
1762
  tracking.lastCheck = Date.now();
@@ -2219,14 +2216,19 @@ class RabbitMQClient extends EventEmitter {
2219
2216
  * @param {Object} msg - RabbitMQ message object.
2220
2217
  */
2221
2218
  async ack(msg) {
2219
+ if (msg._mqProcessed) {
2220
+ return; // Already acked/nacked — idempotent
2221
+ }
2222
2222
  if (!this._consumerChannel) {
2223
2223
  throw new Error('Cannot ack: consumer channel is not initialized');
2224
2224
  }
2225
2225
  try {
2226
2226
  this._consumerChannel.ack(msg);
2227
+ msg._mqProcessed = true;
2227
2228
  } catch (err) {
2228
2229
  const m = err && err.message ? err.message : '';
2229
2230
  if (m.includes('unknown delivery tag') || m.includes('PRECONDITION_FAILED') || m.includes('Channel closed')) {
2231
+ msg._mqProcessed = true;
2230
2232
  console.warn('[RabbitMQClient] [mq-client-core] Cannot ack - consumer channel is closed/recreated (delivery tag invalid)', { error: m });
2231
2233
  return;
2232
2234
  }
@@ -2241,15 +2243,20 @@ class RabbitMQClient extends EventEmitter {
2241
2243
  * @param {Object} [options] - { requeue: boolean }.
2242
2244
  */
2243
2245
  async nack(msg, options = {}) {
2246
+ if (msg._mqProcessed) {
2247
+ return; // Already acked/nacked — idempotent
2248
+ }
2244
2249
  if (!this._consumerChannel) {
2245
2250
  throw new Error('Cannot nack: consumer channel is not initialized');
2246
2251
  }
2247
2252
  const requeue = options.requeue !== undefined ? options.requeue : true;
2248
2253
  try {
2249
2254
  this._consumerChannel.nack(msg, false, requeue);
2255
+ msg._mqProcessed = true;
2250
2256
  } catch (err) {
2251
2257
  const m = err && err.message ? err.message : '';
2252
2258
  if (m.includes('unknown delivery tag') || m.includes('PRECONDITION_FAILED') || m.includes('Channel closed')) {
2259
+ msg._mqProcessed = true;
2253
2260
  console.warn('[RabbitMQClient] [mq-client-core] Cannot nack - consumer channel is closed/recreated (delivery tag invalid)', { error: m });
2254
2261
  return;
2255
2262
  }