@autofleet/rabbit 4.1.1 → 5.0.1

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.
Files changed (59) hide show
  1. package/README.md +8 -0
  2. package/dist/chunk-CUT6urMc.cjs +30 -0
  3. package/dist/index-B6pk4Ot8.d.cts +216 -0
  4. package/dist/index-K1ijaudV.d.ts +216 -0
  5. package/dist/index.cjs +1120 -0
  6. package/dist/index.cjs.map +1 -0
  7. package/dist/index.d.cts +2 -0
  8. package/dist/index.d.ts +2 -132
  9. package/dist/index.js +1112 -1112
  10. package/dist/index.js.map +1 -1
  11. package/dist/mock/index.cjs +20 -0
  12. package/dist/mock/index.cjs.map +1 -0
  13. package/dist/mock/index.d.cts +18 -0
  14. package/dist/mock/index.d.ts +16 -12
  15. package/dist/mock/index.js +18 -18
  16. package/dist/mock/index.js.map +1 -1
  17. package/dist/mock/vitest.cjs +21 -0
  18. package/dist/mock/vitest.cjs.map +1 -0
  19. package/dist/mock/vitest.d.cts +18 -0
  20. package/dist/mock/vitest.d.ts +16 -11
  21. package/dist/mock/vitest.js +18 -17
  22. package/dist/mock/vitest.js.map +1 -1
  23. package/package.json +48 -9
  24. package/.nvmrc +0 -1
  25. package/dist/lib/celery.d.ts +0 -9
  26. package/dist/lib/celery.js +0 -54
  27. package/dist/lib/celery.js.map +0 -1
  28. package/dist/lib/consts.d.ts +0 -27
  29. package/dist/lib/consts.js +0 -31
  30. package/dist/lib/consts.js.map +0 -1
  31. package/dist/lib/rabbitError.d.ts +0 -3
  32. package/dist/lib/rabbitError.js +0 -10
  33. package/dist/lib/rabbitError.js.map +0 -1
  34. package/dist/lib/redis.d.ts +0 -8
  35. package/dist/lib/redis.js +0 -6
  36. package/dist/lib/redis.js.map +0 -1
  37. package/dist/lib/types.d.ts +0 -57
  38. package/dist/lib/types.js +0 -12
  39. package/dist/lib/types.js.map +0 -1
  40. package/dist/lib/utils.d.ts +0 -14
  41. package/dist/lib/utils.js +0 -29
  42. package/dist/lib/utils.js.map +0 -1
  43. package/dist/logger.d.ts +0 -2
  44. package/dist/logger.js +0 -9
  45. package/dist/logger.js.map +0 -1
  46. package/src/index.ts +0 -1401
  47. package/src/lib/celery.ts +0 -89
  48. package/src/lib/consts.ts +0 -32
  49. package/src/lib/rabbitError.ts +0 -6
  50. package/src/lib/redis.ts +0 -11
  51. package/src/lib/types.ts +0 -77
  52. package/src/lib/utils.ts +0 -42
  53. package/src/logger.ts +0 -5
  54. package/src/mock/index.ts +0 -25
  55. package/src/mock/vitest.ts +0 -24
  56. package/src/redis-lock.d.ts +0 -6
  57. package/tsconfig.build.json +0 -5
  58. package/tsconfig.json +0 -16
  59. package/vitest.config.ts +0 -17
package/dist/index.js CHANGED
@@ -1,1117 +1,1117 @@
1
- "use strict";
2
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
- if (kind === "m") throw new TypeError("Private method is not writable");
4
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
1
+ import { env } from "node:process";
2
+ import { setImmediate } from "node:timers/promises";
3
+ import { EventEmitter, once } from "node:events";
4
+ import moment from "moment";
5
+ import RedisLock from "redis-lock";
6
+ import { connect } from "amqp-connection-manager";
7
+ import { backOff } from "exponential-backoff";
8
+ import { CONTEXTS_IDS_HEADER, createOrSetRabbitTrace, getUser, newTrace, outbreak, traceTypes } from "@autofleet/zehut";
9
+ import { randomUUID } from "node:crypto";
10
+ import Logger from "@autofleet/logger";
11
+ import { createClient } from "redis";
12
+
13
+ //#region src/logger.ts
14
+ const logger = Logger();
15
+ var logger_default = logger;
16
+
17
+ //#endregion
18
+ //#region src/lib/rabbitError.ts
19
+ var RabbitError = class extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = "RabbitError";
23
+ }
7
24
  };
8
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
25
+
26
+ //#endregion
27
+ //#region src/lib/redis.ts
28
+ const getRedisInstance = (config$1) => createClient({ socket: config$1 });
29
+ var redis_default = getRedisInstance;
30
+
31
+ //#endregion
32
+ //#region src/lib/consts.ts
33
+ const DEFAULT_DEAD_TTL_TWO_DAYS = 6e4 * 60 * 12;
34
+ const DEFAULT_LOCK_TIMEOUT = 1e3 * 5;
35
+ const RETRY_HEADER = "x-retry-count";
36
+ const TRACING_HEADER = "x-trace-id";
37
+ const USER_TRACING_HEADER = "x-af-user-id";
38
+ const AUTOMATION_ID_HEADER = "x-af-automation-id";
39
+ const DEFAULT_USE_CONSUME_WITH_LOCK = false;
40
+ const DEFAULT_OPTIONS = {
41
+ limit: 1,
42
+ retries: 1,
43
+ deadMessageTtl: DEFAULT_DEAD_TTL_TWO_DAYS,
44
+ lockTimeout: DEFAULT_LOCK_TIMEOUT,
45
+ useConsumeWithLock: DEFAULT_USE_CONSUME_WITH_LOCK,
46
+ auditContext: null,
47
+ enableRabbitTrace: false
12
48
  };
13
- var __importDefault = (this && this.__importDefault) || function (mod) {
14
- return (mod && mod.__esModule) ? mod : { "default": mod };
49
+ const ASSERT_VHOST_EXPONENTIAL_BACKOFF_DEV_ENV_CONFIG = {
50
+ startingDelay: 500,
51
+ timeMultiple: 4,
52
+ numOfAttempts: 5
15
53
  };
16
- var _RabbitMq_logger;
17
- Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.sendCeleryTaskViaHttp = void 0;
19
- const node_process_1 = require("node:process");
20
- const promises_1 = require("node:timers/promises");
21
- const node_events_1 = require("node:events");
22
- const moment_1 = __importDefault(require("moment"));
23
- const redis_lock_1 = __importDefault(require("redis-lock"));
24
- const amqp_connection_manager_1 = require("amqp-connection-manager");
25
- const exponential_backoff_1 = require("exponential-backoff");
26
- const zehut_1 = require("@autofleet/zehut");
27
- const node_crypto_1 = require("node:crypto");
28
- const logger_1 = __importDefault(require("./logger"));
29
- const rabbitError_1 = __importDefault(require("./lib/rabbitError"));
30
- const redis_1 = __importDefault(require("./lib/redis"));
31
- const utils_1 = require("./lib/utils");
32
- const consts_1 = require("./lib/consts");
33
- const types_1 = require("./lib/types");
34
- const PUBLISH_TIMEOUT = 1000 * 10;
35
- // TODO: [QUORUM-PHASE-3] Delete these env vars
36
- const { DISABLE_QUORUM_QUEUES_CONSUME, DISABLE_QUORUM_QUEUES_PUBLISH, } = node_process_1.env;
37
- const HEARTBEAT = '60';
38
- class RabbitMq {
39
- static parseMsg(msg) {
40
- let content = msg.content.toString();
41
- try {
42
- content = JSON.parse(content);
43
- }
44
- catch { /* ignore error */ }
45
- return {
46
- ...msg,
47
- content,
48
- };
49
- }
50
- static validateName(type, name) {
51
- if (!name || name === '') {
52
- throw new rabbitError_1.default(`error while using ${type} with no name`);
53
- }
54
- }
55
- static getPublishOptions(customHeaders = {}) {
56
- const user = (0, zehut_1.getUser)();
57
- const traceId = (0, zehut_1.getCurrentPayload)()?.context?.get(consts_1.TRACING_HEADER) || zehut_1.outbreak.getCurrentContext()?.context?.get(consts_1.TRACING_HEADER);
58
- return {
59
- timestamp: (0, moment_1.default)().unix(),
60
- timeout: PUBLISH_TIMEOUT,
61
- headers: {
62
- creationTimestamp: (0, moment_1.default)().valueOf(),
63
- ...customHeaders,
64
- [consts_1.USER_TRACING_HEADER]: user?.id,
65
- [consts_1.TRACING_HEADER]: traceId,
66
- },
67
- };
68
- }
69
- constructor(options = {}, redisConfig) {
70
- this.options = options;
71
- this.redisConfig = redisConfig;
72
- this.DISCONNECT_MSG = 'rabbit: connection disconnect';
73
- this.RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
74
- this.publishChannel = null;
75
- this.publishChannelSetupPromise = null;
76
- this.publishConnection = {
77
- amqpConnection: null,
78
- creatingConnection: false,
79
- connectionCreatedEventName: 'publishConnectionCreated',
80
- connectionFailedEventName: 'publishConnectionFailed',
81
- blockReconnect: false,
82
- };
83
- this.consumeConnection = {
84
- amqpConnection: null,
85
- creatingConnection: false,
86
- connectionCreatedEventName: 'consumeConnectionCreated',
87
- connectionFailedEventName: 'consumeConnectionFailed',
88
- blockReconnect: false,
89
- };
90
- this.em = new node_events_1.EventEmitter();
91
- this.exchanges = {};
92
- this.queues = {};
93
- this.queueSetupPromises = {};
94
- this.assertExchangePromises = {};
95
- /** Array of consumers tags used for canceling consumption */
96
- this.consumersTags = new Map();
97
- this.consumersToRegister = [];
98
- this.doesVHostExist = false;
99
- this.vhost = 'quorum-vhost';
100
- this.gracefulShutdownStarted = false;
101
- // TODO:[QUORUM-PHASE-3] Delete the old properties that we use for the old consumers and publishers
102
- this.oldPublishChannel = null;
103
- this.oldPublishChannelSetupPromise = null;
104
- this.oldPublishConnection = {
105
- amqpConnection: null,
106
- creatingConnection: false,
107
- connectionCreatedEventName: 'oldPublishConnectionCreated',
108
- connectionFailedEventName: 'oldPublishConnectionFailed',
109
- blockReconnect: false,
110
- };
111
- this.oldConsumeConnection = {
112
- amqpConnection: null,
113
- creatingConnection: false,
114
- connectionCreatedEventName: 'oldConsumeConnectionCreated',
115
- connectionFailedEventName: 'oldConsumeConnectionFailed',
116
- blockReconnect: false,
117
- };
118
- this.oldEm = new node_events_1.EventEmitter();
119
- this.oldExchanges = {};
120
- this.oldQueues = {};
121
- this.oldQueueSetupPromises = {};
122
- this.oldAssertExchangePromises = {};
123
- this.oldConsumersTags = new Map();
124
- this.oldConsumersToRegister = [];
125
- _RabbitMq_logger.set(this, void 0);
126
- this.assertVHost = async () => {
127
- if (this.doesVHostExist) {
128
- return;
129
- }
130
- const username = process.env.RABBITMQ_USERNAME || 'guest';
131
- const password = process.env.RABBITMQ_PASSWORD || 'guest';
132
- const credentials = Buffer.from(`${username}:${password}`).toString('base64');
133
- const headers = {
134
- Authorization: `Basic ${credentials}`,
135
- 'Content-Type': 'application/json',
136
- };
137
- const rabbitHost = `http://${(this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost').split(':')[0]}:15672`;
138
- const url = `${rabbitHost}/api/vhosts/${encodeURIComponent(this.vhost)}`;
139
- try {
140
- const response = await fetch(url, {
141
- method: 'GET',
142
- headers,
143
- });
144
- if (response.status === 200) {
145
- this.doesVHostExist = true;
146
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").info('Vhost exists', { vhost: this.vhost });
147
- return;
148
- }
149
- if (response.status !== 404) {
150
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error('Failed to check vhost', { response });
151
- throw new rabbitError_1.default('Failed to check vhost');
152
- }
153
- const createResponse = await fetch(url, {
154
- method: 'PUT',
155
- headers,
156
- body: JSON.stringify({ default_queue_type: 'quorum' }),
157
- });
158
- if (!createResponse.ok) {
159
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error('Failed to create vhost', { response: createResponse });
160
- throw new rabbitError_1.default('Failed to create vhost');
161
- }
162
- this.doesVHostExist = true;
163
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").info('Vhost created', { vhost: this.vhost });
164
- }
165
- catch (error) {
166
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error('Failed to check or create vhost', { error });
167
- throw error;
168
- }
169
- };
170
- this.shouldConsumeMessageByTimestamp = async (msg) => {
171
- if (msg) {
172
- const { headers } = msg.properties;
173
- const timestamp = headers?.creationTimestamp;
174
- if (timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
175
- const key = this.getRedisKey(headers.redisTimestampValidationKey);
176
- const lastMessageTimestamp = await this.redisClient.get(key);
177
- return !lastMessageTimestamp || (parseInt(lastMessageTimestamp, 10) <= parseInt(timestamp, 10));
178
- }
179
- return true;
180
- }
181
- return false;
182
- };
183
- this.ack = (channel, msg, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg) => {
184
- if (msg) {
185
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
186
- await channel.ack(msg);
187
- const { headers } = msg.properties;
188
- const timestamp = headers?.creationTimestamp;
189
- if (shouldUpdateRedisTimestamp && timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
190
- const parsedTimestamp = parseInt(timestamp, 10);
191
- const key = this.getRedisKey(headers.redisTimestampValidationKey);
192
- await this.redisClient.set(key, parsedTimestamp, { EX: 3600 });
193
- await this.unlockRedisIfNeeded(releaseLock);
194
- }
195
- }
196
- };
197
- this.nack = (channel, queue, options, deadQueueOptions, msg, releaseLock) => async (userMsg, { skipRetry = false, } = {}) => {
198
- await this.unlockRedisIfNeeded(releaseLock);
199
- if (!channel || !msg) {
200
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error('no channel or msg', { msg });
201
- return;
202
- }
203
- const currentRetryHeader = Number.parseInt(msg.properties.headers?.[consts_1.RETRY_HEADER] || '0', 10) || 0;
204
- const sendToDeadQueue = skipRetry || currentRetryHeader >= options.retries;
205
- await this.sendToQueue(`${queue}${sendToDeadQueue ? '-dead' : ''}`, RabbitMq.parseMsg(msg).content, sendToDeadQueue ? deadQueueOptions : options, {
206
- ...msg.properties.headers,
207
- [consts_1.RETRY_HEADER]: currentRetryHeader + 1,
208
- });
209
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit nacking message', { deliveryTag: msg.fields.deliveryTag });
210
- await channel.ack(msg);
211
- };
212
- this.maskURL = (url) => {
213
- try {
214
- const urlObj = new URL(url);
215
- urlObj.username = '***';
216
- urlObj.password = '***';
217
- return urlObj.toString();
218
- }
219
- catch {
220
- return url;
221
- }
222
- };
223
- __classPrivateFieldSet(this, _RabbitMq_logger, options?.logger || logger_1.default, "f");
224
- if (redisConfig) {
225
- this.redisClient = (0, redis_1.default)(redisConfig).on('error', (err) => {
226
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error('rabbit: Redis error', { err, redisConfig });
227
- });
228
- this.redisClient.connect().catch((err) => {
229
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error('rabbit: Failed to connect to Redis', { err, redisConfig });
230
- });
231
- this.redisLock = (0, redis_lock_1.default)(this.redisClient);
232
- }
233
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`);
234
- if (!this.options?.dontGracefulShutdown) {
235
- process.on('SIGTERM', async () => {
236
- await this.gracefulShutdown('SIGTERM');
237
- });
238
- process.on('SIGINT', async () => {
239
- await this.gracefulShutdown('SIGINT');
240
- });
241
- }
242
- }
243
- getRedisKey(key) {
244
- return `${this.redisConfig?.prefix || ''}${key}`;
245
- }
246
- async getConnection(connection) {
247
- const { amqpConnection: connectionLocal, creatingConnection, connectionCreatedEventName, connectionFailedEventName, blockReconnect, } = connection;
248
- if (blockReconnect) {
249
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit: block reconnect');
250
- // @ts-expect-error we are returning undefined while the function clearly expects a connection.
251
- return undefined;
252
- }
253
- if (connectionLocal !== null) {
254
- if (this.options?.disableReconnect || connectionLocal?.isConnected()) {
255
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit: connection - is connected');
256
- return connectionLocal;
257
- }
258
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit: connection - reconnecting');
259
- }
260
- if (creatingConnection) {
261
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit: creating connection emi');
262
- const [event, value] = await Promise.race([connectionCreatedEventName, connectionFailedEventName].map((e) => (0, node_events_1.once)(this.em, e).then(([result]) => [e, result])));
263
- if (event === connectionCreatedEventName) {
264
- return value;
265
- }
266
- throw value;
267
- }
268
- connection.creatingConnection = true;
269
- let isResolved = false;
270
- // It is import to use it as a function and not as a variable
271
- // because of k8s changes the env variables
272
- // and we want to use the new values
273
- const findServers = () => {
274
- const userName = process.env.RABBITMQ_USERNAME || 'guest';
275
- const password = process.env.RABBITMQ_PASSWORD || 'guest';
276
- const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
277
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit: creating connection', { host, userName, HEARTBEAT });
278
- return [`amqp://${userName}:${password}@${host}/${this.vhost}?heartbeat=${HEARTBEAT}`];
279
- };
280
- const defaultUrls = findServers();
281
- const newConnection = (0, amqp_connection_manager_1.connect)(defaultUrls, { findServers });
282
- connection.amqpConnection = newConnection;
283
- const { promise, reject, resolve } = (0, utils_1.createDeferredPromise)();
284
- newConnection.on('error', (err) => {
285
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error('rabbit: connection error', { err });
286
- if (!isResolved) {
287
- isResolved = true;
288
- reject(err);
289
- this.em.emit(connectionFailedEventName, err);
290
- }
291
- });
292
- newConnection.on('connectFailed', (err) => {
293
- this.consumersTags.clear();
294
- if (typeof err.url === 'string') {
295
- err.url = this.maskURL(err.url);
296
- }
297
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error('rabbit: connection connectFailed', { err, advice: 'Check if the vhost exist', vhost: this.vhost });
298
- if (!isResolved) {
299
- isResolved = true;
300
- reject(err);
301
- this.em.emit(connectionFailedEventName, err);
302
- }
303
- });
304
- newConnection.on('disconnect', ({ err }) => {
305
- this.consumersTags.clear();
306
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit: connection closed');
307
- if (this.options?.disableReconnect) {
308
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
309
- connection.blockReconnect = true;
310
- }
311
- else {
312
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
313
- }
314
- });
315
- newConnection.once('connect', async () => {
316
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit: connection established');
317
- connection.creatingConnection = false;
318
- this.em.emit(connectionCreatedEventName, newConnection);
319
- isResolved = true;
320
- resolve(newConnection);
321
- });
322
- return promise;
323
- }
324
- async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {}, connection, }) {
325
- let localConnection;
326
- try {
327
- localConnection = await this.getConnection(connection);
328
- }
329
- catch (e) {
330
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error(`rabbit: error on get connection for new channel ${name} `, { e });
331
- throw e;
332
- }
333
- const channel = localConnection?.createChannel({ ...options });
334
- (0, node_events_1.once)(channel, 'close').then((args) => {
335
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error(`rabbit: channel ${name} closed`);
336
- onClose?.(args);
337
- });
338
- try {
339
- await (0, node_events_1.once)(channel, 'connect');
340
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug(`rabbit: channel ${name} CONNECTED`);
341
- return channel;
342
- }
343
- catch (err) {
344
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error(`rabbit: channel error ${name} error`, { err });
345
- throw err;
346
- }
347
- }
348
- async assertChannel({ force = false, connection }) {
349
- if (this.publishChannelSetupPromise) {
350
- return this.publishChannelSetupPromise;
351
- }
352
- const { promise, resolve, reject } = (0, utils_1.createDeferredPromise)();
353
- this.publishChannelSetupPromise = promise;
354
- if (this.publishChannel && !force) {
355
- resolve(this.publishChannel);
356
- return promise;
357
- }
358
- try {
359
- const channel = await this.getNewChannel({ connection });
360
- channel.on('error', (err) => {
361
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error('rabbit: channel error', { err });
362
- });
363
- if (this.publishConnection === connection) {
364
- this.publishChannel = channel;
365
- }
366
- resolve(channel);
367
- }
368
- catch (e) {
369
- reject(e);
370
- }
371
- return promise;
372
- }
373
- async assertExchange(exchangeName, connection) {
374
- const channel = await this.assertChannel({ connection });
375
- if (this.exchanges[exchangeName]) {
376
- delete this.assertExchangePromises[exchangeName];
377
- return this.exchanges[exchangeName];
378
- }
379
- if (this.assertExchangePromises[exchangeName]) {
380
- return this.assertExchangePromises[exchangeName];
381
- }
382
- this.assertExchangePromises[exchangeName] = (0, utils_1.assertExchangeFanout)(channel, exchangeName);
383
- this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
384
- return this.exchanges[exchangeName];
385
- }
386
- async getQueueLength(queue) {
387
- RabbitMq.validateName('queue', queue);
388
- const { publishChannel: channel } = this;
389
- if (!channel) {
390
- throw new rabbitError_1.default('channel is not defined');
391
- }
392
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit: getting queue length', { queue, connected: this.publishConnection.amqpConnection?.isConnected() });
393
- return channel?.checkQueue(queue);
394
- }
395
- async deleteQueue(queue, connection) {
396
- RabbitMq.validateName('queue', queue);
397
- const channel = await this.assertChannel({ connection });
398
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").info('rabbit: deleting queue', { queue });
399
- const deleteQueueRes = await channel.deleteQueue(queue);
400
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('queue deleted', deleteQueueRes);
401
- return deleteQueueRes;
402
- }
403
- async bindQueue(queue, exchange) {
404
- const channel = await this.assertChannel({ connection: this.publishConnection });
405
- await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
406
- return channel.bindQueue(queue, exchange, '');
407
- }
408
- async setupQueue(queueName, options) {
409
- let queue;
410
- const connection = this.publishConnection;
411
- const localeOptions = {
412
- ...options,
413
- durable: true,
414
- arguments: {
415
- ...options?.arguments,
416
- 'x-consumer-timeout': 1000 * 60 * 60 * 24,
417
- 'x-queue-type': 'quorum',
418
- },
419
- };
420
- try {
421
- const channel = await this.assertChannel({ connection });
422
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('assertQueue->channel.addSetup', { queueName });
423
- await channel.addSetup(async (setupChannel) => {
424
- await setupChannel.assertQueue(queueName, localeOptions);
425
- });
426
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('assertQueue->channel.assertQueue', { queueName });
427
- queue = await channel.assertQueue(queueName, localeOptions);
428
- }
429
- catch (e) {
430
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error('rabbit: assertQueue error', { queueName, options, error: e });
431
- if (!this.options?.dontRetryAssert) {
432
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('retrying assertQueue', { queueName });
433
- const channel = await this.assertChannel({ force: true, connection });
434
- await this.deleteQueue(queueName, connection);
435
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('retrying assertQueue->channel.addSetup', { queueName });
436
- await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
437
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('retrying assertQueue->channel.assertQueue', { queueName });
438
- queue = await channel.assertQueue(queueName, localeOptions);
439
- }
440
- else {
441
- throw e;
442
- }
443
- }
444
- this.queues[queueName] = queue;
445
- return queue;
446
- }
447
- // TODO: [QUORUM-PHASE-3] Can be deleted after deleting the old consumers and publishers because all the queues are created us quorum queues
448
- static shouldUseQuorum(queueName) {
449
- const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
450
- if (envQuorumQueuesWhitelist === '*') {
451
- return true;
452
- }
453
- if (envQuorumQueuesWhitelist) {
454
- const whitelist = envQuorumQueuesWhitelist.split(',');
455
- return whitelist.includes(queueName);
456
- }
457
- return false;
458
- }
459
- async assertQueue(queueName, options) {
460
- var _a;
461
- RabbitMq.validateName('queue', queueName);
462
- if (this.queues[queueName]) {
463
- delete this.queueSetupPromises[queueName];
464
- return this.queues[queueName];
465
- }
466
- (_a = this.queueSetupPromises)[queueName] || (_a[queueName] = this.setupQueue(queueName, options));
467
- return this.queueSetupPromises[queueName];
468
- }
469
- saveConsumer(queue, callback, options) {
470
- const isConsumerExist = this.consumersToRegister.some((consumer) => consumer.queue === queue);
471
- if (!isConsumerExist) {
472
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").info(`rabbit: consumer: ${queue} saved in consumer array`);
473
- this.consumersToRegister.push({
474
- queue,
475
- callback,
476
- options,
477
- });
478
- }
479
- }
480
- // Used by the microservices to consume messages from the queue
481
- async consume(queue, callback, options) {
482
- this.saveConsumerOld(queue, callback, options);
483
- // TODO: [QUORUM-PHASE-3] Use only the implementation of consumeNew and delete consumeNew and consumeOld
484
- if (options?.isQuorumQueue !== false && DISABLE_QUORUM_QUEUES_CONSUME !== 'true') {
485
- this.saveConsumer(queue, callback, options);
486
- const backoffConfig = (0, utils_1.getAssertVhostExponentialBackoffConfig)();
487
- await (0, exponential_backoff_1.backOff)(() => this.assertVHost(), backoffConfig);
488
- await this.consumeNew(queue, callback, options);
489
- }
490
- await this.consumeOld(queue, callback, options);
491
- }
492
- // TODO: [QUORUM-PHASE-3] Delete consumeNew we do not use it anymore
493
- async consumeNew(queue, callback, options) {
494
- await this.consumeFromRabbit(queue, callback, options);
495
- }
496
- // TODO: [QUORUM-PHASE-3] Delete consumeOld we do not use it anymore
497
- async consumeOld(queue, callback, options) {
498
- await this.consumeFromRabbitOld(queue, callback, options);
499
- }
500
- async lockRedisIfNeeded(msg, options) {
501
- const { properties: { headers } } = msg;
502
- const timestamp = headers?.creationTimestamp;
503
- let releaseLock = null;
504
- if (options.useConsumeWithLock && timestamp && headers?.redisTimestampValidationKey && this.redisLock) {
505
- releaseLock = await this.redisLock(headers.redisTimestampValidationKey, options?.lockTimeout || consts_1.DEFAULT_LOCK_TIMEOUT);
506
- }
507
- return releaseLock;
508
- }
509
- async unlockRedisIfNeeded(releaseLock) {
510
- if (this.redisLock && releaseLock) {
511
- await releaseLock();
512
- }
513
- }
514
- async consumeFromRabbit(queue, callback, options) {
515
- const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
516
- RabbitMq.validateName('queue', queue);
517
- const uniqueId = (0, node_crypto_1.randomUUID)();
518
- const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
519
- if (useConsumeWithLock) {
520
- if (!this.redisLock) {
521
- throw new rabbitError_1.default('Usage of consumeWithLock requires RedisInstance');
522
- }
523
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
524
- }
525
- const channel = await this.getNewChannel({ connection: this.consumeConnection });
526
- return channel.addSetup(async (confirmChannel) => {
527
- await this.assertQueue(queue, optionsWithDefaults);
528
- await confirmChannel.prefetch(limit, false);
529
- const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
530
- if (!msg) {
531
- return null;
532
- }
533
- const { [consts_1.TRACING_HEADER]: traceId, [consts_1.USER_TRACING_HEADER]: userId, [consts_1.AUTOMATION_ID_HEADER]: automationId } = msg.properties.headers ?? {};
534
- const parsedMessage = RabbitMq.parseMsg(msg);
535
- const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
536
- const trace = (0, zehut_1.newTrace)(zehut_1.traceTypes.RABBIT);
537
- // setting also outbreak trace as part of legacy code
538
- const outbreakTrace = zehut_1.outbreak.newTrace(zehut_1.traceTypes.RABBIT);
539
- // enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
540
- // and we don't want to fail the flow because of it
541
- if (userId && enableRabbitTrace) {
542
- try {
543
- await Promise.all([
544
- (0, zehut_1.createOrSetRabbitTrace)(trace, userId),
545
- (0, zehut_1.createOrSetRabbitTrace)(outbreakTrace, userId),
546
- ]);
547
- }
548
- catch (e) {
549
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error('rabbit: failed to setRabbitTrace', { userId, e });
550
- return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
551
- }
552
- }
553
- if (traceId) {
554
- trace?.context?.set(consts_1.TRACING_HEADER, traceId);
555
- outbreakTrace?.context.set(consts_1.TRACING_HEADER, traceId);
556
- }
557
- if (auditContext) {
558
- await auditContext(queue, {
559
- userId,
560
- automationId,
561
- });
562
- }
563
- const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
564
- if (!shouldConsume) {
565
- await this.unlockRedisIfNeeded(releaseLock);
566
- return this.ack(confirmChannel, msg)(msg);
567
- }
568
- let messageAcked = false;
569
- // setting the localAck function to be used in the callback
570
- const localAck = async () => {
571
- if (messageAcked) {
572
- return;
573
- }
574
- messageAcked = true;
575
- await this.ack(confirmChannel, msg, true, releaseLock)(msg);
576
- };
577
- const localNack = async (_, nackOptions = {}) => {
578
- if (messageAcked) {
579
- return;
580
- }
581
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
582
- messageAcked = true;
583
- await this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
584
- };
585
- try {
586
- return await callback(parsedMessage, localAck, localNack);
587
- }
588
- catch (e) {
589
- return localNack(msg);
590
- }
591
- }, types_1.CONSUMER_DEFAULT_OPTIONS);
592
- if (!consumerTag) {
593
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error(`rabbit: failed to consume from queue ${queue}`);
594
- }
595
- else {
596
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").info(`rabbit: adding tag ${consumerTag} to the array.`);
597
- this.consumersTags.set(queue, { channel: confirmChannel, consumerTag });
598
- }
599
- });
600
- }
601
- // Used by the microservices to consume messages from the exchange
602
- async consumeFromExchange(queue, exchange, callback, options) {
603
- const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
604
- RabbitMq.validateName('exchange', exchange);
605
- RabbitMq.validateName('queue', queue);
606
- const { limit } = optionsWithDefaults;
607
- // TODO: [QUORUM-PHASE-3] Delete the if statement after all the queues are created as quorum queues
608
- if (options?.isQuorumQueue !== false && DISABLE_QUORUM_QUEUES_CONSUME !== 'true') {
609
- await this.saveConsumer(queue, callback, options);
610
- const backoffConfig = (0, utils_1.getAssertVhostExponentialBackoffConfig)();
611
- await (0, exponential_backoff_1.backOff)(() => this.assertVHost(), backoffConfig);
612
- const channel = await this.getNewChannel({
613
- name: `consume-exchange-${exchange}-queue-${queue}`,
614
- connection: this.consumeConnection,
615
- });
616
- await channel.addSetup(async (c) => {
617
- const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
618
- await c.assertQueue(queue);
619
- this.exchanges[exchange] = assertExchange;
620
- await c.prefetch(limit, false);
621
- return Promise.all([
622
- c.bindQueue(queue, exchange, ''),
623
- this.consumeNew(queue, callback, options),
624
- ]);
625
- });
626
- }
627
- // TODO: [QUORUM-PHASE-3] Delete the old implementation
628
- await this.saveConsumerOld(queue, callback, options);
629
- const channelOld = await this.getNewChannelOld({
630
- name: `consume-exchange-${exchange}-queue-${queue}-old`,
631
- connection: this.oldConsumeConnection,
632
- });
633
- await channelOld.addSetup(async (c) => {
634
- const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
635
- await c.assertQueue(queue);
636
- this.oldExchanges[exchange] = assertExchange;
637
- await c.prefetch(limit, false);
638
- return Promise.all([
639
- c.bindQueue(queue, exchange, ''),
640
- this.consumeOld(queue, callback, options),
641
- ]);
642
- });
643
- }
644
- // Used by the microservices to publish messages to the exchange
645
- // TODO: [QUORUM-PHASE-3] isQuorumQueue flag is not needed anymore because all the queues are created as quorum queues
646
- async publish(exchange, content, customHeaders, isQuorumQueue = true) {
647
- await (0, promises_1.setImmediate)();
648
- if (isQuorumQueue && DISABLE_QUORUM_QUEUES_PUBLISH !== 'true') {
649
- await this.assertVHost();
650
- RabbitMq.validateName('exchange', exchange);
651
- const channel = await this.assertChannel({ connection: this.publishConnection });
652
- await this.assertExchange(exchange, this.publishConnection);
653
- await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
654
- return;
655
- }
656
- // TODO: [QUORUM-PHASE-3] Delete the old implementation
657
- RabbitMq.validateName('exchange', exchange);
658
- const channel = await this.assertChannelOld({ connection: this.oldPublishConnection });
659
- await this.assertExchangeOld(exchange, this.oldPublishConnection);
660
- await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
661
- }
662
- // Used by the microservices to send messages to the queue
663
- // TODO: [QUORUM-PHASE-3] isQuorumQueue flag is not needed anymore because all the queues are created as quorum queues
664
- async sendToQueue(queue, content, options, customHeaders, isQuorumQueue = true) {
665
- if (isQuorumQueue && DISABLE_QUORUM_QUEUES_PUBLISH !== 'true') {
666
- try {
667
- await this.assertVHost();
668
- await this.assertChannel({ connection: this.publishConnection });
669
- }
670
- catch (e) {
671
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
672
- throw e;
673
- }
674
- try {
675
- RabbitMq.validateName('queue', queue);
676
- await this.assertQueue(queue, options);
677
- }
678
- catch (e) {
679
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
680
- throw e;
681
- }
682
- try {
683
- const res = await this.publishChannel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
684
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug(`rabbit: sending to queue ${queue}`, { res });
685
- return res;
686
- }
687
- catch (e) {
688
- const isConnected = await this.isConnected();
689
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
690
- throw e;
691
- }
692
- }
693
- else {
694
- try {
695
- await this.assertChannelOld({ connection: this.oldPublishConnection });
696
- }
697
- catch (e) {
698
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
699
- throw e;
700
- }
701
- try {
702
- RabbitMq.validateName('queue', queue);
703
- await this.assertQueueOld(queue, options);
704
- }
705
- catch (e) {
706
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
707
- throw e;
708
- }
709
- try {
710
- const res = await this.oldPublishChannel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
711
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug(`rabbit: sending to queue ${queue}`, { res });
712
- return res;
713
- }
714
- catch (e) {
715
- const isConnected = await this.isConnectedOld();
716
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
717
- throw e;
718
- }
719
- }
720
- }
721
- async isConnected() {
722
- let isConnected = true;
723
- // TODO:[QUORUM-PHASE-3] Remove the condition
724
- if (!this.gracefulShutdownStarted) {
725
- if (DISABLE_QUORUM_QUEUES_CONSUME !== 'true' || DISABLE_QUORUM_QUEUES_PUBLISH !== 'true') {
726
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit: start is connected');
727
- const [consumeConnection, publishConnection] = await Promise.all([
728
- this.getConnection(this.consumeConnection),
729
- this.getConnection(this.publishConnection),
730
- ]);
731
- isConnected = consumeConnection.isConnected() && publishConnection.isConnected();
732
- if (!isConnected) {
733
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error('rabbit: isConnected - false');
734
- return false;
735
- }
736
- try {
737
- const unRegisteredConsumers = this.consumersToRegister.filter((c) => !this.consumersTags.get(c.queue));
738
- if (unRegisteredConsumers.length > 0) {
739
- const queueNames = unRegisteredConsumers.map((c) => c.queue);
740
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error('rabbit: found unregistered consumers for queues', {
741
- count: queueNames.length,
742
- queues: queueNames,
743
- });
744
- throw new rabbitError_1.default('Found unregistered consumers');
745
- }
746
- const channel = await this.assertChannel({ connection: this.publishConnection });
747
- await channel.waitForConnect();
748
- await Promise.all(this.consumersToRegister.map((c) => channel.checkQueue(c.queue)));
749
- }
750
- catch (e) {
751
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error('rabbit: isConnected - false', { msg: e.message });
752
- return false;
753
- }
754
- }
755
- // TODO:[QUORUM-PHASE-3] Delete the old is connected and adjust the return value to return true
756
- isConnected = await this.isConnectedOld();
757
- }
758
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug(`rabbit: isConnected - ${isConnected}`);
759
- return isConnected;
760
- }
761
- async gracefulShutdown(signal) {
762
- // TODO: [QUORUM-PHASE-3] Delete the old implementation they are not needed anymore
763
- this.gracefulShutdownStarted = true;
764
- const tagsNumber = this.consumersTags.size + this.oldConsumersTags.size;
765
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
766
- const cancelTagPromises = [...this.consumersTags].map(([, { channel, consumerTag }]) => channel.cancel(consumerTag));
767
- const cancelTagPromisesOld = [...this.oldConsumersTags].map(([, { channel, consumerTag }]) => channel.cancel(consumerTag));
768
- // Clean the array to avoid race
769
- this.consumersTags.clear();
770
- this.oldConsumersTags.clear();
771
- const results = await Promise.allSettled([...cancelTagPromises, ...cancelTagPromisesOld]);
772
- const rejected = results.filter((p) => p.status === 'rejected');
773
- if (rejected.length > 0) {
774
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
775
- }
776
- else {
777
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
778
- }
779
- }
780
- async consumeFromRabbitOld(queue, callback, options) {
781
- const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
782
- RabbitMq.validateName('queue', queue);
783
- const uniqueId = (0, node_crypto_1.randomUUID)();
784
- const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
785
- if (useConsumeWithLock) {
786
- if (!this.redisLock) {
787
- throw new rabbitError_1.default('Usage of consumeWithLock requires RedisInstance');
788
- }
789
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
790
- }
791
- const channel = await this.getNewChannelOld({ connection: this.oldConsumeConnection });
792
- return channel.addSetup(async (confirmChannel) => {
793
- await this.assertQueueOld(queue, optionsWithDefaults);
794
- await confirmChannel.prefetch(limit, false);
795
- const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
796
- if (!msg) {
797
- return null;
798
- }
799
- const { [consts_1.TRACING_HEADER]: traceId, [consts_1.USER_TRACING_HEADER]: userId, [consts_1.AUTOMATION_ID_HEADER]: automationId } = msg.properties.headers ?? {};
800
- const parsedMessage = RabbitMq.parseMsg(msg);
801
- const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
802
- const trace = (0, zehut_1.newTrace)(zehut_1.traceTypes.RABBIT);
803
- // setting also outbreak trace as part of legacy code
804
- const outbreakTrace = zehut_1.outbreak.newTrace(zehut_1.traceTypes.RABBIT);
805
- // enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
806
- // and we don't want to fail the flow because of it
807
- if (userId && enableRabbitTrace) {
808
- try {
809
- await Promise.all([
810
- (0, zehut_1.createOrSetRabbitTrace)(trace, userId),
811
- (0, zehut_1.createOrSetRabbitTrace)(outbreakTrace, userId),
812
- ]);
813
- }
814
- catch (e) {
815
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error('rabbit: failed to setRabbitTrace', { userId, e });
816
- return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
817
- }
818
- }
819
- if (traceId) {
820
- trace?.context?.set(consts_1.TRACING_HEADER, traceId);
821
- outbreakTrace?.context.set(consts_1.TRACING_HEADER, traceId);
822
- }
823
- if (auditContext) {
824
- await auditContext(queue, {
825
- userId,
826
- automationId,
827
- });
828
- }
829
- const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
830
- if (!shouldConsume) {
831
- await this.unlockRedisIfNeeded(releaseLock);
832
- return this.ack(confirmChannel, msg)(msg);
833
- }
834
- let messageAcked = false;
835
- // setting the localAck function to be used in the callback
836
- const localAck = async () => {
837
- if (messageAcked) {
838
- return;
839
- }
840
- messageAcked = true;
841
- await this.ack(confirmChannel, msg, true, releaseLock)(msg);
842
- };
843
- const localNack = async (_, nackOptions = {}) => {
844
- if (messageAcked) {
845
- return;
846
- }
847
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
848
- messageAcked = true;
849
- await this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
850
- };
851
- try {
852
- return await callback(parsedMessage, localAck, localNack);
853
- }
854
- catch (e) {
855
- return localNack(msg);
856
- }
857
- }, types_1.CONSUMER_DEFAULT_OPTIONS);
858
- if (!consumerTag) {
859
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error(`rabbit: failed to consume from queue ${queue} old`);
860
- }
861
- else {
862
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").info(`rabbit: adding tag ${consumerTag} to the array old`);
863
- this.oldConsumersTags.set(queue, { channel: confirmChannel, consumerTag });
864
- }
865
- });
866
- }
867
- // TODO: [QUORUM-PHASE-3] Delete all the function under this line.
868
- async getNewChannelOld({ name = (0, utils_1.rand)().toString(), onClose = null, options = {}, connection, }) {
869
- let localConnection;
870
- try {
871
- localConnection = await this.getConnectionOld(connection);
872
- }
873
- catch (e) {
874
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error(`rabbit: error on get connection for new channel ${name} `, { e });
875
- throw e;
876
- }
877
- if (!localConnection) {
878
- throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
879
- }
880
- const channel = localConnection?.createChannel({ ...options });
881
- (0, node_events_1.once)(channel, 'close').then((args) => {
882
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error(`rabbit: channel ${name} closed`);
883
- onClose?.(args);
884
- });
885
- try {
886
- await (0, node_events_1.once)(channel, 'connect');
887
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug(`rabbit: channel ${name} CONNECTED`);
888
- return channel;
889
- }
890
- catch (err) {
891
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error(`rabbit: channel error ${name} error`, { err });
892
- throw err;
893
- }
894
- }
895
- async getConnectionOld(connection) {
896
- const { amqpConnection: connectionLocal, creatingConnection, connectionCreatedEventName, connectionFailedEventName, blockReconnect, } = connection;
897
- if (blockReconnect) {
898
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit: block reconnect');
899
- // @ts-expect-error we are returning undefined while the function clearly expects a connection.
900
- return undefined;
901
- }
902
- if (connectionLocal !== null) {
903
- if (this.options?.disableReconnect || connectionLocal?.isConnected()) {
904
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit: connection - is connected');
905
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
906
- // @ts-ignore
907
- return connectionLocal;
908
- }
909
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit: connection - reconnecting');
910
- }
911
- if (creatingConnection) {
912
- const [event, value] = await Promise.race([connectionCreatedEventName, connectionFailedEventName].map((e) => (0, node_events_1.once)(this.oldEm, e).then(([result]) => [e, result])));
913
- if (event === connectionCreatedEventName) {
914
- return value;
915
- }
916
- throw value;
917
- }
918
- connection.creatingConnection = true;
919
- let isResolved = false;
920
- // It is import to use it as a function and not as a variable
921
- // because of k8s changes the env variables
922
- // and we want to use the new values
923
- const findServers = () => {
924
- const userName = process.env.RABBITMQ_USERNAME || 'guest';
925
- const password = process.env.RABBITMQ_PASSWORD || 'guest';
926
- const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
927
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit: creating connection', { host, userName, HEARTBEAT });
928
- return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
929
- };
930
- const defaultUrls = findServers();
931
- const newConnection = await (0, amqp_connection_manager_1.connect)(defaultUrls, { findServers });
932
- connection.amqpConnection = newConnection;
933
- const { promise, reject, resolve } = (0, utils_1.createDeferredPromise)();
934
- newConnection.on('error', (err) => {
935
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").error('rabbit: connection error', { err });
936
- if (!isResolved) {
937
- isResolved = true;
938
- reject(err);
939
- this.oldEm.emit(connectionFailedEventName, err);
940
- }
941
- });
942
- newConnection.on('connectFailed', (err) => {
943
- this.oldConsumersTags.clear();
944
- if (typeof err.url === 'string') {
945
- err.url = this.maskURL(err.url);
946
- }
947
- logger_1.default.error('rabbit: connection connectFailed', { err });
948
- if (!isResolved) {
949
- isResolved = true;
950
- reject(err);
951
- this.oldEm.emit(connectionFailedEventName, err);
952
- }
953
- });
954
- newConnection.on('disconnect', ({ err }) => {
955
- this.oldConsumersTags.clear();
956
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit: connection closed');
957
- if (this.options?.disableReconnect) {
958
- logger_1.default.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
959
- connection.blockReconnect = true;
960
- }
961
- else {
962
- logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
963
- }
964
- });
965
- newConnection.once('connect', async () => {
966
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit: connection established');
967
- connection.creatingConnection = false;
968
- this.oldEm.emit(connectionCreatedEventName, newConnection);
969
- isResolved = true;
970
- resolve(newConnection);
971
- });
972
- return promise;
973
- }
974
- saveConsumerOld(queue, callback, options) {
975
- const isConsumerExist = this.oldConsumersToRegister.some((consumer) => consumer.queue === queue);
976
- if (!isConsumerExist) {
977
- logger_1.default.info(`rabbit: consumer: ${queue} saved in consumer array`);
978
- this.oldConsumersToRegister.push({
979
- queue,
980
- callback,
981
- options,
982
- });
983
- }
984
- }
985
- async assertQueueOld(queueName, options) {
986
- var _a;
987
- RabbitMq.validateName('queue', queueName);
988
- if (this.oldQueues[queueName]) {
989
- delete this.oldQueueSetupPromises[queueName];
990
- return this.oldQueues[queueName];
991
- }
992
- (_a = this.oldQueueSetupPromises)[queueName] || (_a[queueName] = this.setupQueueOld(queueName, options));
993
- return this.oldQueueSetupPromises[queueName];
994
- }
995
- async setupQueueOld(queueName, options) {
996
- let queue;
997
- const connection = this.oldPublishConnection;
998
- const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
999
- const localeOptions = {
1000
- ...options,
1001
- durable: true,
1002
- arguments: {
1003
- ...options?.arguments,
1004
- 'x-consumer-timeout': 1000 * 60 * 60 * 24,
1005
- 'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
1006
- },
1007
- };
1008
- try {
1009
- const channel = await this.assertChannelOld({ connection });
1010
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('assertQueue->channel.addSetup', { queueName });
1011
- await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
1012
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('assertQueue->channel.assertQueue', { queueName });
1013
- queue = await channel.assertQueue(queueName, localeOptions);
1014
- }
1015
- catch (e) {
1016
- logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
1017
- if (!this.options?.dontRetryAssert) {
1018
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('retrying assertQueue', { queueName });
1019
- const channel = await this.assertChannelOld({ force: true, connection });
1020
- await this.deleteQueueOld(queueName, connection);
1021
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('retrying assertQueue->channel.addSetup', { queueName });
1022
- await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
1023
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('retrying assertQueue->channel.assertQueue', { queueName });
1024
- queue = await channel.assertQueue(queueName, localeOptions);
1025
- }
1026
- else {
1027
- throw e;
1028
- }
1029
- }
1030
- this.oldQueues[queueName] = queue;
1031
- return queue;
1032
- }
1033
- async assertChannelOld({ force = false, connection }) {
1034
- if (this.oldPublishChannelSetupPromise) {
1035
- return this.oldPublishChannelSetupPromise;
1036
- }
1037
- const { promise, resolve, reject } = (0, utils_1.createDeferredPromise)();
1038
- this.oldPublishChannelSetupPromise = promise;
1039
- if (this.oldPublishChannel && !force) {
1040
- resolve(this.oldPublishChannel);
1041
- return promise;
1042
- }
1043
- try {
1044
- const channel = await this.getNewChannelOld({ connection });
1045
- channel.on('error', (err) => {
1046
- logger_1.default.error('rabbit: channel error', { err });
1047
- });
1048
- if (this.oldPublishConnection === connection) {
1049
- this.oldPublishChannel = channel;
1050
- }
1051
- resolve(channel);
1052
- }
1053
- catch (e) {
1054
- reject(e);
1055
- }
1056
- return promise;
1057
- }
1058
- async deleteQueueOld(queue, connection) {
1059
- RabbitMq.validateName('queue', queue);
1060
- const channel = await this.assertChannelOld({ connection });
1061
- logger_1.default.info('rabbit: deleting queue', { queue });
1062
- const deleteQueueRes = await channel.deleteQueue(queue);
1063
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('queue deleted', deleteQueueRes);
1064
- return deleteQueueRes;
1065
- }
1066
- async assertExchangeOld(exchangeName, connection) {
1067
- const channel = await this.assertChannelOld({ connection });
1068
- if (this.oldExchanges[exchangeName]) {
1069
- delete this.oldAssertExchangePromises[exchangeName];
1070
- return this.oldExchanges[exchangeName];
1071
- }
1072
- if (this.oldAssertExchangePromises[exchangeName]) {
1073
- return this.oldAssertExchangePromises[exchangeName];
1074
- }
1075
- this.oldAssertExchangePromises[exchangeName] = (0, utils_1.assertExchangeFanout)(channel, exchangeName);
1076
- this.oldExchanges[exchangeName] = await this.oldAssertExchangePromises[exchangeName];
1077
- return this.oldExchanges[exchangeName];
1078
- }
1079
- async isConnectedOld() {
1080
- if (!this.gracefulShutdownStarted) {
1081
- __classPrivateFieldGet(this, _RabbitMq_logger, "f").debug('rabbit: start is connected old');
1082
- const [oldConsumeConnection, oldPublishConnection] = await Promise.all([
1083
- this.getConnectionOld(this.oldConsumeConnection),
1084
- this.getConnectionOld(this.oldPublishConnection),
1085
- ]);
1086
- const isConnected = oldConsumeConnection.isConnected() && oldPublishConnection.isConnected();
1087
- if (!isConnected) {
1088
- logger_1.default.error('rabbit: isConnected old - false');
1089
- return false;
1090
- }
1091
- try {
1092
- const unRegisteredConsumersOld = this.oldConsumersToRegister.filter((c) => !this.oldConsumersTags.get(c.queue));
1093
- if (unRegisteredConsumersOld.length > 0) {
1094
- const queueNames = unRegisteredConsumersOld.map((c) => c.queue);
1095
- logger_1.default.error('rabbit: found unregistered consumers for queues old', {
1096
- count: queueNames.length,
1097
- queues: queueNames,
1098
- });
1099
- throw new rabbitError_1.default('Found unregistered consumers old');
1100
- }
1101
- const channelOld = await this.assertChannelOld({ connection: this.oldPublishConnection });
1102
- await channelOld.waitForConnect();
1103
- await Promise.all(this.oldConsumersToRegister.map((c) => channelOld.checkQueue(c.queue)));
1104
- }
1105
- catch (e) {
1106
- logger_1.default.error('rabbit: isConnected - false old', { msg: e.message });
1107
- return false;
1108
- }
1109
- }
1110
- return true;
1111
- }
54
+ const ASSERT_VHOST_EXPONENTIAL_BACKOFF_PROD_CONFIG = {
55
+ startingDelay: 1,
56
+ timeMultiple: 1,
57
+ numOfAttempts: 5
58
+ };
59
+
60
+ //#endregion
61
+ //#region src/lib/utils.ts
62
+ const { PROJECT_ID } = process.env;
63
+ const assertExchangeFanout = async (c, exchangeName) => c.assertExchange(exchangeName, "fanout");
64
+ const rand = () => Math.floor(Math.random() * 1e5);
65
+ /** This is polyfill for `Promise.withResolvers` which exists only on Node v22 and onwards */
66
+ const createDeferredPromise = () => {
67
+ if (Promise.withResolvers) return Promise.withResolvers();
68
+ let resolve;
69
+ let reject;
70
+ const promise = new Promise((res, rej) => {
71
+ resolve = res;
72
+ reject = rej;
73
+ });
74
+ return {
75
+ promise,
76
+ resolve,
77
+ reject
78
+ };
79
+ };
80
+ const isDevEnv = () => ["af-experiment-manager", "dev1-experiment-manager"].includes(PROJECT_ID || "");
81
+ const getAssertVhostExponentialBackoffConfig = () => isDevEnv() ? ASSERT_VHOST_EXPONENTIAL_BACKOFF_DEV_ENV_CONFIG : ASSERT_VHOST_EXPONENTIAL_BACKOFF_PROD_CONFIG;
82
+
83
+ //#endregion
84
+ //#region src/lib/types.ts
85
+ const HA_PROMOTE_ON_FAILURE = "ha-promote-on-failure";
86
+ const HA_PROMOTE_ON_SHUTDOWN = "ha-promote-on-shutdown";
87
+ const CONSUMER_DEFAULT_OPTIONS = { arguments: {
88
+ [HA_PROMOTE_ON_FAILURE]: "always",
89
+ [HA_PROMOTE_ON_SHUTDOWN]: "always"
90
+ } };
91
+
92
+ //#endregion
93
+ //#region src/lib/celery.ts
94
+ const config = {
95
+ host: process.env.RABBITMQ_SERVICE_HOST || "localhost",
96
+ username: process.env.RABBITMQ_USERNAME || "guest",
97
+ password: process.env.RABBITMQ_PASSWORD || "guest"
98
+ };
99
+ async function sendCeleryTaskViaHttp(data, { taskName, queueName }) {
100
+ const apiUrl = `http://${config.host}:15672/api/exchanges/%2f/amq.default/publish`;
101
+ const message = {
102
+ task: taskName,
103
+ id: randomUUID(),
104
+ args: [data]
105
+ };
106
+ const payload = {
107
+ properties: {
108
+ delivery_mode: 2,
109
+ content_type: "application/json"
110
+ },
111
+ routing_key: queueName,
112
+ payload: JSON.stringify(message),
113
+ payload_encoding: "string"
114
+ };
115
+ try {
116
+ const response = await fetch(apiUrl, {
117
+ method: "POST",
118
+ headers: {
119
+ "Content-Type": "application/json",
120
+ Authorization: `Basic ${Buffer.from(`${config.username}:${config.password}`).toString("base64")}`
121
+ },
122
+ body: JSON.stringify(payload)
123
+ });
124
+ if (response.ok) {
125
+ const result = await response.json();
126
+ logger_default.info("Successfully published message:", result);
127
+ } else {
128
+ logger_default.error(`Failed to publish message. Status code: ${response.status}`);
129
+ logger_default.error(`Response: ${await response.text()}`);
130
+ }
131
+ } catch (error) {
132
+ logger_default.error("Error sending request:", error instanceof Error ? error.message : String(error));
133
+ throw error;
134
+ }
1112
135
  }
1113
- _RabbitMq_logger = new WeakMap();
1114
- exports.default = RabbitMq;
1115
- var celery_1 = require("./lib/celery");
1116
- Object.defineProperty(exports, "sendCeleryTaskViaHttp", { enumerable: true, get: function () { return celery_1.sendCeleryTaskViaHttp; } });
136
+
137
+ //#endregion
138
+ //#region src/index.ts
139
+ const PUBLISH_TIMEOUT = 1e3 * 10;
140
+ const { DISABLE_QUORUM_QUEUES_CONSUME, DISABLE_QUORUM_QUEUES_PUBLISH } = env;
141
+ const HEARTBEAT = "60";
142
+ var RabbitMq = class RabbitMq {
143
+ static parseMsg(msg) {
144
+ let content = msg.content.toString();
145
+ try {
146
+ content = JSON.parse(content);
147
+ } catch {}
148
+ return {
149
+ ...msg,
150
+ content
151
+ };
152
+ }
153
+ static validateName(type, name) {
154
+ if (!name || name === "") throw new RabbitError(`error while using ${type} with no name`);
155
+ }
156
+ static getPublishOptions(customHeaders = {}) {
157
+ const user = getUser();
158
+ const traceId = outbreak.getCurrentContextTraceId();
159
+ return {
160
+ timestamp: moment().unix(),
161
+ timeout: PUBLISH_TIMEOUT,
162
+ headers: {
163
+ creationTimestamp: moment().valueOf(),
164
+ ...customHeaders,
165
+ [USER_TRACING_HEADER]: user?.id,
166
+ [CONTEXTS_IDS_HEADER]: user?.contextIds,
167
+ [TRACING_HEADER]: traceId
168
+ }
169
+ };
170
+ }
171
+ DISCONNECT_MSG = "rabbit: connection disconnect";
172
+ RECONNECT_MSG = "rabbit: connection disconnect - reconnecting";
173
+ publishChannel = null;
174
+ publishChannelSetupPromise = null;
175
+ publishConnection = {
176
+ amqpConnection: null,
177
+ creatingConnection: false,
178
+ connectionCreatedEventName: "publishConnectionCreated",
179
+ connectionFailedEventName: "publishConnectionFailed",
180
+ blockReconnect: false
181
+ };
182
+ consumeConnection = {
183
+ amqpConnection: null,
184
+ creatingConnection: false,
185
+ connectionCreatedEventName: "consumeConnectionCreated",
186
+ connectionFailedEventName: "consumeConnectionFailed",
187
+ blockReconnect: false
188
+ };
189
+ em = new EventEmitter();
190
+ exchanges = {};
191
+ queues = {};
192
+ queueSetupPromises = {};
193
+ assertExchangePromises = {};
194
+ redisClient;
195
+ redisLock;
196
+ /** A map of consumers' tags used for canceling consumption */
197
+ consumersTags = /* @__PURE__ */ new Map();
198
+ consumersToRegister = [];
199
+ doesVHostExist = false;
200
+ vhost = "quorum-vhost";
201
+ gracefulShutdownStarted = false;
202
+ oldPublishChannel = null;
203
+ oldPublishChannelSetupPromise = null;
204
+ oldPublishConnection = {
205
+ amqpConnection: null,
206
+ creatingConnection: false,
207
+ connectionCreatedEventName: "oldPublishConnectionCreated",
208
+ connectionFailedEventName: "oldPublishConnectionFailed",
209
+ blockReconnect: false
210
+ };
211
+ oldConsumeConnection = {
212
+ amqpConnection: null,
213
+ creatingConnection: false,
214
+ connectionCreatedEventName: "oldConsumeConnectionCreated",
215
+ connectionFailedEventName: "oldConsumeConnectionFailed",
216
+ blockReconnect: false
217
+ };
218
+ oldEm = new EventEmitter();
219
+ oldExchanges = {};
220
+ oldQueues = {};
221
+ oldQueueSetupPromises = {};
222
+ oldAssertExchangePromises = {};
223
+ oldConsumersTags = /* @__PURE__ */ new Map();
224
+ oldConsumersToRegister = [];
225
+ #logger;
226
+ constructor(options = {}, redisConfig) {
227
+ this.options = options;
228
+ this.redisConfig = redisConfig;
229
+ this.#logger = options?.logger || logger_default;
230
+ if (redisConfig) {
231
+ this.redisClient = redis_default(redisConfig).on("error", (err) => {
232
+ this.#logger.error("rabbit: Redis error", {
233
+ err,
234
+ redisConfig
235
+ });
236
+ });
237
+ this.redisClient.connect().catch((err) => {
238
+ this.#logger.error("rabbit: Failed to connect to Redis", {
239
+ err,
240
+ redisConfig
241
+ });
242
+ });
243
+ this.redisLock = RedisLock(this.redisClient);
244
+ }
245
+ this.#logger.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`);
246
+ if (!this.options?.dontGracefulShutdown) {
247
+ process.on("SIGTERM", async () => {
248
+ await this.gracefulShutdown("SIGTERM");
249
+ });
250
+ process.on("SIGINT", async () => {
251
+ await this.gracefulShutdown("SIGINT");
252
+ });
253
+ }
254
+ }
255
+ getRedisKey(key) {
256
+ return `${this.redisConfig?.prefix || ""}${key}`;
257
+ }
258
+ assertVHost = async () => {
259
+ if (this.doesVHostExist) return;
260
+ const username = process.env.RABBITMQ_USERNAME || "guest";
261
+ const password = process.env.RABBITMQ_PASSWORD || "guest";
262
+ const credentials = Buffer.from(`${username}:${password}`).toString("base64");
263
+ const headers = {
264
+ Authorization: `Basic ${credentials}`,
265
+ "Content-Type": "application/json"
266
+ };
267
+ const rabbitHost = `http://${(this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || "localhost").split(":")[0]}:15672`;
268
+ const url = `${rabbitHost}/api/vhosts/${encodeURIComponent(this.vhost)}`;
269
+ try {
270
+ const response = await fetch(url, {
271
+ method: "GET",
272
+ headers
273
+ });
274
+ if (response.status === 200) {
275
+ this.doesVHostExist = true;
276
+ this.#logger.info("Vhost exists", { vhost: this.vhost });
277
+ return;
278
+ }
279
+ if (response.status !== 404) {
280
+ this.#logger.error("Failed to check vhost", { response });
281
+ throw new RabbitError("Failed to check vhost");
282
+ }
283
+ const createResponse = await fetch(url, {
284
+ method: "PUT",
285
+ headers,
286
+ body: JSON.stringify({ default_queue_type: "quorum" })
287
+ });
288
+ if (!createResponse.ok) {
289
+ this.#logger.error("Failed to create vhost", { response: createResponse });
290
+ throw new RabbitError("Failed to create vhost");
291
+ }
292
+ this.doesVHostExist = true;
293
+ this.#logger.info("Vhost created", { vhost: this.vhost });
294
+ } catch (error) {
295
+ this.#logger.error("Failed to check or create vhost", { error });
296
+ throw error;
297
+ }
298
+ };
299
+ shouldConsumeMessageByTimestamp = async (msg) => {
300
+ if (!msg) return false;
301
+ const { headers } = msg.properties;
302
+ const timestamp = headers?.creationTimestamp;
303
+ if (timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
304
+ const key = this.getRedisKey(headers.redisTimestampValidationKey);
305
+ const lastMessageTimestamp = await this.redisClient.get(key);
306
+ return !lastMessageTimestamp || parseInt(lastMessageTimestamp, 10) <= parseInt(timestamp, 10);
307
+ }
308
+ return true;
309
+ };
310
+ ack = (channel, msg, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg) => {
311
+ if (!msg) return;
312
+ this.#logger.debug("rabbit acking message", { deliveryTag: msg.fields.deliveryTag });
313
+ await channel.ack(msg);
314
+ const { headers } = msg.properties;
315
+ const timestamp = headers?.creationTimestamp;
316
+ if (shouldUpdateRedisTimestamp && timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
317
+ const parsedTimestamp = parseInt(timestamp, 10);
318
+ const key = this.getRedisKey(headers.redisTimestampValidationKey);
319
+ await this.redisClient.set(key, parsedTimestamp, { EX: 3600 });
320
+ await this.unlockRedisIfNeeded(releaseLock);
321
+ }
322
+ };
323
+ nack = (channel, queue, options, deadQueueOptions, msg, releaseLock) => async (userMsg, { skipRetry = false } = {}) => {
324
+ await this.unlockRedisIfNeeded(releaseLock);
325
+ if (!channel || !msg) {
326
+ this.#logger.error("no channel or msg", { msg });
327
+ return;
328
+ }
329
+ const currentRetryHeader = Number.parseInt(msg.properties.headers?.[RETRY_HEADER] || "0", 10) || 0;
330
+ const sendToDeadQueue = skipRetry || currentRetryHeader >= options.retries;
331
+ await this.sendToQueue(`${queue}${sendToDeadQueue ? "-dead" : ""}`, RabbitMq.parseMsg(msg).content, sendToDeadQueue ? deadQueueOptions : options, {
332
+ ...msg.properties.headers,
333
+ [RETRY_HEADER]: currentRetryHeader + 1
334
+ });
335
+ this.#logger.debug("rabbit nacking message", { deliveryTag: msg.fields.deliveryTag });
336
+ await channel.ack(msg);
337
+ };
338
+ async getConnection(connection) {
339
+ const { amqpConnection: connectionLocal, creatingConnection, connectionCreatedEventName, connectionFailedEventName, blockReconnect } = connection;
340
+ if (blockReconnect) {
341
+ this.#logger.debug("rabbit: block reconnect");
342
+ return void 0;
343
+ }
344
+ if (connectionLocal !== null) {
345
+ if (this.options?.disableReconnect || connectionLocal?.isConnected()) {
346
+ this.#logger.debug("rabbit: connection - is connected");
347
+ return connectionLocal;
348
+ }
349
+ this.#logger.debug("rabbit: connection - reconnecting");
350
+ }
351
+ if (creatingConnection) {
352
+ this.#logger.debug("rabbit: creating connection emi");
353
+ const [event, value] = await Promise.race([connectionCreatedEventName, connectionFailedEventName].map((e) => once(this.em, e).then(([result]) => [e, result])));
354
+ if (event === connectionCreatedEventName) return value;
355
+ throw value;
356
+ }
357
+ connection.creatingConnection = true;
358
+ let isResolved = false;
359
+ const findServers = () => {
360
+ const userName = process.env.RABBITMQ_USERNAME || "guest";
361
+ const password = process.env.RABBITMQ_PASSWORD || "guest";
362
+ const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || "localhost";
363
+ this.#logger.debug("rabbit: creating connection", {
364
+ host,
365
+ userName,
366
+ HEARTBEAT
367
+ });
368
+ return [`amqp://${userName}:${password}@${host}/${this.vhost}?heartbeat=${HEARTBEAT}`];
369
+ };
370
+ const defaultUrls = findServers();
371
+ const newConnection = connect(defaultUrls, { findServers });
372
+ connection.amqpConnection = newConnection;
373
+ const { promise, reject, resolve } = createDeferredPromise();
374
+ newConnection.on("error", (err) => {
375
+ this.#logger.error("rabbit: connection error", { err });
376
+ if (!isResolved) {
377
+ isResolved = true;
378
+ reject(err);
379
+ this.em.emit(connectionFailedEventName, err);
380
+ }
381
+ });
382
+ newConnection.on("connectFailed", (err) => {
383
+ this.consumersTags.clear();
384
+ if (typeof err.url === "string") err.url = this.maskURL(err.url);
385
+ this.#logger.error("rabbit: connection connectFailed", {
386
+ err,
387
+ advice: "Check if the vhost exist",
388
+ vhost: this.vhost
389
+ });
390
+ if (!isResolved) {
391
+ isResolved = true;
392
+ reject(err);
393
+ this.em.emit(connectionFailedEventName, err);
394
+ }
395
+ });
396
+ newConnection.on("disconnect", ({ err }) => {
397
+ this.consumersTags.clear();
398
+ this.#logger.debug("rabbit: connection closed");
399
+ if (this.options?.disableReconnect) {
400
+ this.#logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
401
+ connection.blockReconnect = true;
402
+ } else this.#logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
403
+ });
404
+ newConnection.once("connect", async () => {
405
+ this.#logger.debug("rabbit: connection established");
406
+ connection.creatingConnection = false;
407
+ this.em.emit(connectionCreatedEventName, newConnection);
408
+ isResolved = true;
409
+ resolve(newConnection);
410
+ });
411
+ return promise;
412
+ }
413
+ async getNewChannel({ name = rand().toString(), onClose = null, options = {}, connection }) {
414
+ let localConnection;
415
+ try {
416
+ localConnection = await this.getConnection(connection);
417
+ } catch (e) {
418
+ this.#logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
419
+ throw e;
420
+ }
421
+ const channel = localConnection?.createChannel({ ...options });
422
+ once(channel, "close").then((args) => {
423
+ this.#logger.error(`rabbit: channel ${name} closed`);
424
+ onClose?.(args);
425
+ });
426
+ try {
427
+ await once(channel, "connect");
428
+ this.#logger.debug(`rabbit: channel ${name} CONNECTED`);
429
+ return channel;
430
+ } catch (err) {
431
+ this.#logger.error(`rabbit: channel error ${name} error`, { err });
432
+ throw err;
433
+ }
434
+ }
435
+ async assertChannel({ force = false, connection }) {
436
+ if (this.publishChannelSetupPromise) return this.publishChannelSetupPromise;
437
+ const { promise, resolve, reject } = createDeferredPromise();
438
+ this.publishChannelSetupPromise = promise;
439
+ if (this.publishChannel && !force) {
440
+ resolve(this.publishChannel);
441
+ return promise;
442
+ }
443
+ try {
444
+ const channel = await this.getNewChannel({ connection });
445
+ channel.on("error", (err) => {
446
+ this.#logger.error("rabbit: channel error", { err });
447
+ });
448
+ if (this.publishConnection === connection) this.publishChannel = channel;
449
+ resolve(channel);
450
+ } catch (e) {
451
+ reject(e);
452
+ }
453
+ return promise;
454
+ }
455
+ async assertExchange(exchangeName, connection) {
456
+ const channel = await this.assertChannel({ connection });
457
+ if (this.exchanges[exchangeName]) {
458
+ delete this.assertExchangePromises[exchangeName];
459
+ return this.exchanges[exchangeName];
460
+ }
461
+ if (this.assertExchangePromises[exchangeName]) return this.assertExchangePromises[exchangeName];
462
+ this.assertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
463
+ this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
464
+ return this.exchanges[exchangeName];
465
+ }
466
+ async getQueueLength(queue) {
467
+ RabbitMq.validateName("queue", queue);
468
+ const { publishChannel: channel } = this;
469
+ if (!channel) throw new RabbitError("channel is not defined");
470
+ this.#logger.debug("rabbit: getting queue length", {
471
+ queue,
472
+ connected: this.publishConnection.amqpConnection?.isConnected()
473
+ });
474
+ return channel?.checkQueue(queue);
475
+ }
476
+ async deleteQueue(queue, connection) {
477
+ RabbitMq.validateName("queue", queue);
478
+ const channel = await this.assertChannel({ connection });
479
+ this.#logger.info("rabbit: deleting queue", { queue });
480
+ const deleteQueueRes = await channel.deleteQueue(queue);
481
+ this.#logger.debug("queue deleted", deleteQueueRes);
482
+ return deleteQueueRes;
483
+ }
484
+ async bindQueue(queue, exchange) {
485
+ const channel = await this.assertChannel({ connection: this.publishConnection });
486
+ await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ""));
487
+ return channel.bindQueue(queue, exchange, "");
488
+ }
489
+ async setupQueue(queueName, options) {
490
+ let queue;
491
+ const connection = this.publishConnection;
492
+ const localeOptions = {
493
+ ...options,
494
+ durable: true,
495
+ arguments: {
496
+ ...options?.arguments,
497
+ "x-consumer-timeout": 1e3 * 60 * 60 * 24,
498
+ "x-queue-type": "quorum"
499
+ }
500
+ };
501
+ try {
502
+ const channel = await this.assertChannel({ connection });
503
+ this.#logger.debug("assertQueue->channel.addSetup", { queueName });
504
+ await channel.addSetup(async (setupChannel) => {
505
+ await setupChannel.assertQueue(queueName, localeOptions);
506
+ });
507
+ this.#logger.debug("assertQueue->channel.assertQueue", { queueName });
508
+ queue = await channel.assertQueue(queueName, localeOptions);
509
+ } catch (e) {
510
+ this.#logger.error("rabbit: assertQueue error", {
511
+ queueName,
512
+ options,
513
+ error: e
514
+ });
515
+ if (!this.options?.dontRetryAssert) {
516
+ this.#logger.debug("retrying assertQueue", { queueName });
517
+ const channel = await this.assertChannel({
518
+ force: true,
519
+ connection
520
+ });
521
+ await this.deleteQueue(queueName, connection);
522
+ this.#logger.debug("retrying assertQueue->channel.addSetup", { queueName });
523
+ await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
524
+ this.#logger.debug("retrying assertQueue->channel.assertQueue", { queueName });
525
+ queue = await channel.assertQueue(queueName, localeOptions);
526
+ } else throw e;
527
+ }
528
+ this.queues[queueName] = queue;
529
+ return queue;
530
+ }
531
+ static shouldUseQuorum(queueName) {
532
+ const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
533
+ if (envQuorumQueuesWhitelist === "*") return true;
534
+ if (envQuorumQueuesWhitelist) {
535
+ const whitelist = envQuorumQueuesWhitelist.split(",");
536
+ return whitelist.includes(queueName);
537
+ }
538
+ return false;
539
+ }
540
+ async assertQueue(queueName, options) {
541
+ RabbitMq.validateName("queue", queueName);
542
+ if (this.queues[queueName]) {
543
+ delete this.queueSetupPromises[queueName];
544
+ return this.queues[queueName];
545
+ }
546
+ this.queueSetupPromises[queueName] ||= this.setupQueue(queueName, options);
547
+ return this.queueSetupPromises[queueName];
548
+ }
549
+ saveConsumer(queue, callback, options) {
550
+ const isConsumerExist = this.consumersToRegister.some((consumer) => consumer.queue === queue);
551
+ if (!isConsumerExist) {
552
+ this.#logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
553
+ this.consumersToRegister.push({
554
+ queue,
555
+ callback,
556
+ options
557
+ });
558
+ }
559
+ }
560
+ async consume(queue, callback, options) {
561
+ this.saveConsumerOld(queue, callback, options);
562
+ if (options?.isQuorumQueue !== false && DISABLE_QUORUM_QUEUES_CONSUME !== "true") {
563
+ this.saveConsumer(queue, callback, options);
564
+ const backoffConfig = getAssertVhostExponentialBackoffConfig();
565
+ await backOff(() => this.assertVHost(), backoffConfig);
566
+ await this.consumeNew(queue, callback, options);
567
+ }
568
+ await this.consumeOld(queue, callback, options);
569
+ }
570
+ async consumeNew(queue, callback, options) {
571
+ await this.consumeFromRabbit(queue, callback, options);
572
+ }
573
+ async consumeOld(queue, callback, options) {
574
+ await this.consumeFromRabbitOld(queue, callback, options);
575
+ }
576
+ async lockRedisIfNeeded(msg, options) {
577
+ const { properties: { headers } } = msg;
578
+ const timestamp = headers?.creationTimestamp;
579
+ let releaseLock = null;
580
+ if (options.useConsumeWithLock && timestamp && headers?.redisTimestampValidationKey && this.redisLock) releaseLock = await this.redisLock(headers.redisTimestampValidationKey, options?.lockTimeout || DEFAULT_LOCK_TIMEOUT);
581
+ return releaseLock;
582
+ }
583
+ async unlockRedisIfNeeded(releaseLock) {
584
+ if (this.redisLock && releaseLock) await releaseLock();
585
+ }
586
+ async consumeFromRabbit(queue, callback, options) {
587
+ const optionsWithDefaults = {
588
+ ...DEFAULT_OPTIONS,
589
+ ...options
590
+ };
591
+ RabbitMq.validateName("queue", queue);
592
+ const uniqueId = randomUUID();
593
+ const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace } = optionsWithDefaults;
594
+ if (useConsumeWithLock) {
595
+ if (!this.redisLock) throw new RabbitError("Usage of consumeWithLock requires RedisInstance");
596
+ this.#logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
597
+ }
598
+ const channel = await this.getNewChannel({ connection: this.consumeConnection });
599
+ return channel.addSetup(async (confirmChannel) => {
600
+ await this.assertQueue(queue, optionsWithDefaults);
601
+ await confirmChannel.prefetch(limit, false);
602
+ const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
603
+ if (!msg) return null;
604
+ const { [TRACING_HEADER]: traceId, [USER_TRACING_HEADER]: userId, [AUTOMATION_ID_HEADER]: automationId, [CONTEXTS_IDS_HEADER]: userContextIds } = msg.properties.headers || {};
605
+ const parsedMessage = RabbitMq.parseMsg(msg);
606
+ const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
607
+ const trace = newTrace(traceTypes.RABBIT);
608
+ if (userId && enableRabbitTrace) try {
609
+ await createOrSetRabbitTrace(trace, userId, userContextIds);
610
+ } catch (e) {
611
+ this.#logger.error("rabbit: failed to setRabbitTrace", {
612
+ userId,
613
+ e
614
+ });
615
+ return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
616
+ }
617
+ if (traceId) trace.context?.set(TRACING_HEADER, traceId);
618
+ if (auditContext) await auditContext(queue, {
619
+ userId,
620
+ automationId
621
+ });
622
+ const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
623
+ if (!shouldConsume) {
624
+ await this.unlockRedisIfNeeded(releaseLock);
625
+ return this.ack(confirmChannel, msg)(msg);
626
+ }
627
+ let messageAcked = false;
628
+ const localAck = async () => {
629
+ if (messageAcked) return;
630
+ messageAcked = true;
631
+ await this.ack(confirmChannel, msg, true, releaseLock)(msg);
632
+ };
633
+ const localNack = async (_, nackOptions = {}) => {
634
+ if (messageAcked) return;
635
+ this.#logger.debug("rabbit localNack", {
636
+ messageAcked,
637
+ uniqueId,
638
+ deliveryTag: msg.fields.deliveryTag
639
+ });
640
+ messageAcked = true;
641
+ await this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
642
+ };
643
+ try {
644
+ return await callback(parsedMessage, localAck, localNack);
645
+ } catch (e) {
646
+ return localNack(msg);
647
+ }
648
+ }, CONSUMER_DEFAULT_OPTIONS);
649
+ if (!consumerTag) this.#logger.error(`rabbit: failed to consume from queue ${queue}`);
650
+ else {
651
+ this.#logger.info(`rabbit: adding tag ${consumerTag} to the array.`);
652
+ this.consumersTags.set(queue, {
653
+ channel: confirmChannel,
654
+ consumerTag
655
+ });
656
+ }
657
+ });
658
+ }
659
+ async consumeFromExchange(queue, exchange, callback, options) {
660
+ const optionsWithDefaults = {
661
+ ...DEFAULT_OPTIONS,
662
+ ...options
663
+ };
664
+ RabbitMq.validateName("exchange", exchange);
665
+ RabbitMq.validateName("queue", queue);
666
+ const { limit } = optionsWithDefaults;
667
+ if (options?.isQuorumQueue !== false && DISABLE_QUORUM_QUEUES_CONSUME !== "true") {
668
+ await this.saveConsumer(queue, callback, options);
669
+ const backoffConfig = getAssertVhostExponentialBackoffConfig();
670
+ await backOff(() => this.assertVHost(), backoffConfig);
671
+ const channel = await this.getNewChannel({
672
+ name: `consume-exchange-${exchange}-queue-${queue}`,
673
+ connection: this.consumeConnection
674
+ });
675
+ await channel.addSetup(async (c) => {
676
+ const assertExchange = await assertExchangeFanout(c, exchange);
677
+ await c.assertQueue(queue);
678
+ this.exchanges[exchange] = assertExchange;
679
+ await c.prefetch(limit, false);
680
+ return Promise.all([c.bindQueue(queue, exchange, ""), this.consumeNew(queue, callback, options)]);
681
+ });
682
+ }
683
+ await this.saveConsumerOld(queue, callback, options);
684
+ const channelOld = await this.getNewChannelOld({
685
+ name: `consume-exchange-${exchange}-queue-${queue}-old`,
686
+ connection: this.oldConsumeConnection
687
+ });
688
+ await channelOld.addSetup(async (c) => {
689
+ const assertExchange = await assertExchangeFanout(c, exchange);
690
+ await c.assertQueue(queue);
691
+ this.oldExchanges[exchange] = assertExchange;
692
+ await c.prefetch(limit, false);
693
+ return Promise.all([c.bindQueue(queue, exchange, ""), this.consumeOld(queue, callback, options)]);
694
+ });
695
+ }
696
+ async publish(exchange, content, customHeaders, isQuorumQueue = true) {
697
+ await setImmediate();
698
+ if (isQuorumQueue && DISABLE_QUORUM_QUEUES_PUBLISH !== "true") {
699
+ await this.assertVHost();
700
+ RabbitMq.validateName("exchange", exchange);
701
+ const channel$1 = await this.assertChannel({ connection: this.publishConnection });
702
+ await this.assertExchange(exchange, this.publishConnection);
703
+ await channel$1.publish(exchange, "", Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
704
+ return;
705
+ }
706
+ RabbitMq.validateName("exchange", exchange);
707
+ const channel = await this.assertChannelOld({ connection: this.oldPublishConnection });
708
+ await this.assertExchangeOld(exchange, this.oldPublishConnection);
709
+ await channel.publish(exchange, "", Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
710
+ }
711
+ async sendToQueue(queue, content, options, customHeaders, isQuorumQueue = true) {
712
+ if (isQuorumQueue && DISABLE_QUORUM_QUEUES_PUBLISH !== "true") {
713
+ try {
714
+ await this.assertVHost();
715
+ await this.assertChannel({ connection: this.publishConnection });
716
+ } catch (e) {
717
+ this.#logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
718
+ throw e;
719
+ }
720
+ try {
721
+ RabbitMq.validateName("queue", queue);
722
+ await this.assertQueue(queue, options);
723
+ } catch (e) {
724
+ this.#logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
725
+ throw e;
726
+ }
727
+ try {
728
+ const res = await this.publishChannel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
729
+ this.#logger.debug(`rabbit: sending to queue ${queue}`, { res });
730
+ return res;
731
+ } catch (e) {
732
+ const isConnected = await this.isConnected();
733
+ this.#logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
734
+ throw e;
735
+ }
736
+ } else {
737
+ try {
738
+ await this.assertChannelOld({ connection: this.oldPublishConnection });
739
+ } catch (e) {
740
+ this.#logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
741
+ throw e;
742
+ }
743
+ try {
744
+ RabbitMq.validateName("queue", queue);
745
+ await this.assertQueueOld(queue, options);
746
+ } catch (e) {
747
+ this.#logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
748
+ throw e;
749
+ }
750
+ try {
751
+ const res = await this.oldPublishChannel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
752
+ this.#logger.debug(`rabbit: sending to queue ${queue}`, { res });
753
+ return res;
754
+ } catch (e) {
755
+ const isConnected = await this.isConnectedOld();
756
+ this.#logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
757
+ throw e;
758
+ }
759
+ }
760
+ }
761
+ async isConnected() {
762
+ let isConnected = true;
763
+ if (!this.gracefulShutdownStarted) {
764
+ if (DISABLE_QUORUM_QUEUES_CONSUME !== "true" || DISABLE_QUORUM_QUEUES_PUBLISH !== "true") {
765
+ this.#logger.debug("rabbit: start is connected");
766
+ const [consumeConnection, publishConnection] = await Promise.all([this.getConnection(this.consumeConnection), this.getConnection(this.publishConnection)]);
767
+ isConnected = consumeConnection.isConnected() && publishConnection.isConnected();
768
+ if (!isConnected) {
769
+ this.#logger.error("rabbit: isConnected - false");
770
+ return false;
771
+ }
772
+ try {
773
+ const unRegisteredConsumers = this.consumersToRegister.filter((c) => !this.consumersTags.get(c.queue));
774
+ if (unRegisteredConsumers.length > 0) {
775
+ const queueNames = unRegisteredConsumers.map((c) => c.queue);
776
+ this.#logger.error("rabbit: found unregistered consumers for queues", {
777
+ count: queueNames.length,
778
+ queues: queueNames
779
+ });
780
+ throw new RabbitError("Found unregistered consumers");
781
+ }
782
+ const channel = await this.assertChannel({ connection: this.publishConnection });
783
+ await channel.waitForConnect();
784
+ await Promise.all(this.consumersToRegister.map((c) => channel.checkQueue(c.queue)));
785
+ } catch (e) {
786
+ this.#logger.error("rabbit: isConnected - false", { msg: e.message });
787
+ return false;
788
+ }
789
+ }
790
+ isConnected = await this.isConnectedOld();
791
+ }
792
+ this.#logger.debug(`rabbit: isConnected - ${isConnected}`);
793
+ return isConnected;
794
+ }
795
+ async gracefulShutdown(signal) {
796
+ this.gracefulShutdownStarted = true;
797
+ const tagsNumber = this.consumersTags.size + this.oldConsumersTags.size;
798
+ this.#logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
799
+ const cancelTagPromises = [...this.consumersTags].map(([, { channel, consumerTag }]) => channel.cancel(consumerTag));
800
+ const cancelTagPromisesOld = [...this.oldConsumersTags].map(([, { channel, consumerTag }]) => channel.cancel(consumerTag));
801
+ this.consumersTags.clear();
802
+ this.oldConsumersTags.clear();
803
+ const results = await Promise.allSettled([...cancelTagPromises, ...cancelTagPromisesOld]);
804
+ const rejected = results.filter((p) => p.status === "rejected");
805
+ if (rejected.length > 0) this.#logger.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
806
+ else this.#logger.info("rabbit: [gracefully-shutdown] all tags successfully canceled.");
807
+ }
808
+ async consumeFromRabbitOld(queue, callback, options) {
809
+ const optionsWithDefaults = {
810
+ ...DEFAULT_OPTIONS,
811
+ ...options
812
+ };
813
+ RabbitMq.validateName("queue", queue);
814
+ const uniqueId = randomUUID();
815
+ const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace } = optionsWithDefaults;
816
+ if (useConsumeWithLock) {
817
+ if (!this.redisLock) throw new RabbitError("Usage of consumeWithLock requires RedisInstance");
818
+ this.#logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
819
+ }
820
+ const channel = await this.getNewChannelOld({ connection: this.oldConsumeConnection });
821
+ return channel.addSetup(async (confirmChannel) => {
822
+ await this.assertQueueOld(queue, optionsWithDefaults);
823
+ await confirmChannel.prefetch(limit, false);
824
+ const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
825
+ if (!msg) return null;
826
+ const { [TRACING_HEADER]: traceId, [USER_TRACING_HEADER]: userId, [AUTOMATION_ID_HEADER]: automationId, [CONTEXTS_IDS_HEADER]: userContextIds } = msg.properties.headers || {};
827
+ const parsedMessage = RabbitMq.parseMsg(msg);
828
+ const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
829
+ const trace = newTrace(traceTypes.RABBIT);
830
+ if (userId && enableRabbitTrace) try {
831
+ await createOrSetRabbitTrace(trace, userId, userContextIds);
832
+ } catch (e) {
833
+ this.#logger.error("rabbit: failed to setRabbitTrace", {
834
+ userId,
835
+ e
836
+ });
837
+ return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
838
+ }
839
+ if (traceId) trace.context?.set(TRACING_HEADER, traceId);
840
+ if (auditContext) await auditContext(queue, {
841
+ userId,
842
+ automationId
843
+ });
844
+ const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
845
+ if (!shouldConsume) {
846
+ await this.unlockRedisIfNeeded(releaseLock);
847
+ return this.ack(confirmChannel, msg)(msg);
848
+ }
849
+ let messageAcked = false;
850
+ const localAck = async () => {
851
+ if (messageAcked) return;
852
+ messageAcked = true;
853
+ await this.ack(confirmChannel, msg, true, releaseLock)(msg);
854
+ };
855
+ const localNack = async (_, nackOptions = {}) => {
856
+ if (messageAcked) return;
857
+ this.#logger.debug("rabbit localNack", {
858
+ messageAcked,
859
+ uniqueId,
860
+ deliveryTag: msg.fields.deliveryTag
861
+ });
862
+ messageAcked = true;
863
+ await this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
864
+ };
865
+ try {
866
+ return await callback(parsedMessage, localAck, localNack);
867
+ } catch (e) {
868
+ return localNack(msg);
869
+ }
870
+ }, CONSUMER_DEFAULT_OPTIONS);
871
+ if (!consumerTag) this.#logger.error(`rabbit: failed to consume from queue ${queue} old`);
872
+ else {
873
+ this.#logger.info(`rabbit: adding tag ${consumerTag} to the array old`);
874
+ this.oldConsumersTags.set(queue, {
875
+ channel: confirmChannel,
876
+ consumerTag
877
+ });
878
+ }
879
+ });
880
+ }
881
+ async getNewChannelOld({ name = rand().toString(), onClose = null, options = {}, connection }) {
882
+ let localConnection;
883
+ try {
884
+ localConnection = await this.getConnectionOld(connection);
885
+ } catch (e) {
886
+ this.#logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
887
+ throw e;
888
+ }
889
+ if (!localConnection) throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
890
+ const channel = localConnection?.createChannel({ ...options });
891
+ once(channel, "close").then((args) => {
892
+ this.#logger.error(`rabbit: channel ${name} closed`);
893
+ onClose?.(args);
894
+ });
895
+ try {
896
+ await once(channel, "connect");
897
+ this.#logger.debug(`rabbit: channel ${name} CONNECTED`);
898
+ return channel;
899
+ } catch (err) {
900
+ this.#logger.error(`rabbit: channel error ${name} error`, { err });
901
+ throw err;
902
+ }
903
+ }
904
+ async getConnectionOld(connection) {
905
+ const { amqpConnection: connectionLocal, creatingConnection, connectionCreatedEventName, connectionFailedEventName, blockReconnect } = connection;
906
+ if (blockReconnect) {
907
+ this.#logger.debug("rabbit: block reconnect");
908
+ return void 0;
909
+ }
910
+ if (connectionLocal !== null) {
911
+ if (this.options?.disableReconnect || connectionLocal?.isConnected()) {
912
+ this.#logger.debug("rabbit: connection - is connected");
913
+ return connectionLocal;
914
+ }
915
+ this.#logger.debug("rabbit: connection - reconnecting");
916
+ }
917
+ if (creatingConnection) {
918
+ const [event, value] = await Promise.race([connectionCreatedEventName, connectionFailedEventName].map((e) => once(this.oldEm, e).then(([result]) => [e, result])));
919
+ if (event === connectionCreatedEventName) return value;
920
+ throw value;
921
+ }
922
+ connection.creatingConnection = true;
923
+ let isResolved = false;
924
+ const findServers = () => {
925
+ const userName = process.env.RABBITMQ_USERNAME || "guest";
926
+ const password = process.env.RABBITMQ_PASSWORD || "guest";
927
+ const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || "localhost";
928
+ this.#logger.debug("rabbit: creating connection", {
929
+ host,
930
+ userName,
931
+ HEARTBEAT
932
+ });
933
+ return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
934
+ };
935
+ const defaultUrls = findServers();
936
+ const newConnection = await connect(defaultUrls, { findServers });
937
+ connection.amqpConnection = newConnection;
938
+ const { promise, reject, resolve } = createDeferredPromise();
939
+ newConnection.on("error", (err) => {
940
+ this.#logger.error("rabbit: connection error", { err });
941
+ if (!isResolved) {
942
+ isResolved = true;
943
+ reject(err);
944
+ this.oldEm.emit(connectionFailedEventName, err);
945
+ }
946
+ });
947
+ newConnection.on("connectFailed", (err) => {
948
+ this.oldConsumersTags.clear();
949
+ if (typeof err.url === "string") err.url = this.maskURL(err.url);
950
+ this.#logger.error("rabbit: connection connectFailed", { err });
951
+ if (!isResolved) {
952
+ isResolved = true;
953
+ reject(err);
954
+ this.oldEm.emit(connectionFailedEventName, err);
955
+ }
956
+ });
957
+ newConnection.on("disconnect", ({ err }) => {
958
+ this.oldConsumersTags.clear();
959
+ this.#logger.debug("rabbit: connection closed");
960
+ if (this.options?.disableReconnect) {
961
+ this.#logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
962
+ connection.blockReconnect = true;
963
+ } else this.#logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
964
+ });
965
+ newConnection.once("connect", async () => {
966
+ this.#logger.debug("rabbit: connection established");
967
+ connection.creatingConnection = false;
968
+ this.oldEm.emit(connectionCreatedEventName, newConnection);
969
+ isResolved = true;
970
+ resolve(newConnection);
971
+ });
972
+ return promise;
973
+ }
974
+ saveConsumerOld(queue, callback, options) {
975
+ const isConsumerExist = this.oldConsumersToRegister.some((consumer) => consumer.queue === queue);
976
+ if (!isConsumerExist) {
977
+ this.#logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
978
+ this.oldConsumersToRegister.push({
979
+ queue,
980
+ callback,
981
+ options
982
+ });
983
+ }
984
+ }
985
+ async assertQueueOld(queueName, options) {
986
+ RabbitMq.validateName("queue", queueName);
987
+ if (this.oldQueues[queueName]) {
988
+ delete this.oldQueueSetupPromises[queueName];
989
+ return this.oldQueues[queueName];
990
+ }
991
+ this.oldQueueSetupPromises[queueName] ||= this.setupQueueOld(queueName, options);
992
+ return this.oldQueueSetupPromises[queueName];
993
+ }
994
+ async setupQueueOld(queueName, options) {
995
+ let queue;
996
+ const connection = this.oldPublishConnection;
997
+ const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
998
+ const localeOptions = {
999
+ ...options,
1000
+ durable: true,
1001
+ arguments: {
1002
+ ...options?.arguments,
1003
+ "x-consumer-timeout": 1e3 * 60 * 60 * 24,
1004
+ "x-queue-type": shouldUseQuorum ? "quorum" : "classic"
1005
+ }
1006
+ };
1007
+ try {
1008
+ const channel = await this.assertChannelOld({ connection });
1009
+ this.#logger.debug("assertQueue->channel.addSetup", { queueName });
1010
+ await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
1011
+ this.#logger.debug("assertQueue->channel.assertQueue", { queueName });
1012
+ queue = await channel.assertQueue(queueName, localeOptions);
1013
+ } catch (e) {
1014
+ this.#logger.error("rabbit: assertQueue error", {
1015
+ queueName,
1016
+ options,
1017
+ error: e
1018
+ });
1019
+ if (!this.options?.dontRetryAssert) {
1020
+ this.#logger.debug("retrying assertQueue", { queueName });
1021
+ const channel = await this.assertChannelOld({
1022
+ force: true,
1023
+ connection
1024
+ });
1025
+ await this.deleteQueueOld(queueName, connection);
1026
+ this.#logger.debug("retrying assertQueue->channel.addSetup", { queueName });
1027
+ await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
1028
+ this.#logger.debug("retrying assertQueue->channel.assertQueue", { queueName });
1029
+ queue = await channel.assertQueue(queueName, localeOptions);
1030
+ } else throw e;
1031
+ }
1032
+ this.oldQueues[queueName] = queue;
1033
+ return queue;
1034
+ }
1035
+ async assertChannelOld({ force = false, connection }) {
1036
+ if (this.oldPublishChannelSetupPromise) return this.oldPublishChannelSetupPromise;
1037
+ const { promise, resolve, reject } = createDeferredPromise();
1038
+ this.oldPublishChannelSetupPromise = promise;
1039
+ if (this.oldPublishChannel && !force) {
1040
+ resolve(this.oldPublishChannel);
1041
+ return promise;
1042
+ }
1043
+ try {
1044
+ const channel = await this.getNewChannelOld({ connection });
1045
+ channel.on("error", (err) => {
1046
+ this.#logger.error("rabbit: channel error", { err });
1047
+ });
1048
+ if (this.oldPublishConnection === connection) this.oldPublishChannel = channel;
1049
+ resolve(channel);
1050
+ } catch (e) {
1051
+ reject(e);
1052
+ }
1053
+ return promise;
1054
+ }
1055
+ async deleteQueueOld(queue, connection) {
1056
+ RabbitMq.validateName("queue", queue);
1057
+ const channel = await this.assertChannelOld({ connection });
1058
+ this.#logger.info("rabbit: deleting queue", { queue });
1059
+ const deleteQueueRes = await channel.deleteQueue(queue);
1060
+ this.#logger.debug("queue deleted", deleteQueueRes);
1061
+ return deleteQueueRes;
1062
+ }
1063
+ async assertExchangeOld(exchangeName, connection) {
1064
+ const channel = await this.assertChannelOld({ connection });
1065
+ if (this.oldExchanges[exchangeName]) {
1066
+ delete this.oldAssertExchangePromises[exchangeName];
1067
+ return this.oldExchanges[exchangeName];
1068
+ }
1069
+ if (this.oldAssertExchangePromises[exchangeName]) return this.oldAssertExchangePromises[exchangeName];
1070
+ this.oldAssertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
1071
+ this.oldExchanges[exchangeName] = await this.oldAssertExchangePromises[exchangeName];
1072
+ return this.oldExchanges[exchangeName];
1073
+ }
1074
+ async isConnectedOld() {
1075
+ if (this.gracefulShutdownStarted) return true;
1076
+ this.#logger.debug("rabbit: start is connected old");
1077
+ const [oldConsumeConnection, oldPublishConnection] = await Promise.all([this.getConnectionOld(this.oldConsumeConnection), this.getConnectionOld(this.oldPublishConnection)]);
1078
+ const isConnected = oldConsumeConnection.isConnected() && oldPublishConnection.isConnected();
1079
+ if (!isConnected) {
1080
+ this.#logger.error("rabbit: isConnected old - false");
1081
+ return false;
1082
+ }
1083
+ try {
1084
+ const unRegisteredConsumersOld = this.consumersToRegister.filter((c) => !this.consumersTags.get(c.queue));
1085
+ if (unRegisteredConsumersOld.length > 0) {
1086
+ const queueNames = unRegisteredConsumersOld.map((c) => c.queue);
1087
+ this.#logger.error("rabbit: found unregistered consumers for queues old", {
1088
+ count: queueNames.length,
1089
+ queues: queueNames
1090
+ });
1091
+ throw new RabbitError("Found unregistered consumers old");
1092
+ }
1093
+ const channelOld = await this.assertChannelOld({ connection: this.oldPublishConnection });
1094
+ await channelOld.waitForConnect();
1095
+ await Promise.all(this.oldConsumersToRegister.map((c) => channelOld.checkQueue(c.queue)));
1096
+ return true;
1097
+ } catch (e) {
1098
+ this.#logger.error("rabbit: isConnected - false old", { msg: e.message });
1099
+ return false;
1100
+ }
1101
+ }
1102
+ maskURL = (url) => {
1103
+ try {
1104
+ const urlObj = new URL(url);
1105
+ urlObj.username = "***";
1106
+ urlObj.password = "***";
1107
+ return urlObj.toString();
1108
+ } catch {
1109
+ return url;
1110
+ }
1111
+ };
1112
+ };
1113
+ var src_default = RabbitMq;
1114
+
1115
+ //#endregion
1116
+ export { src_default as default, sendCeleryTaskViaHttp };
1117
1117
  //# sourceMappingURL=index.js.map