@autofleet/rabbit 4.1.1 → 5.0.0

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